photolib/photoapp/types.py

215 lines
7.3 KiB
Python
Raw Normal View History

2018-09-15 16:48:47 -07:00
from sqlalchemy import Column, Integer, String, DateTime, Unicode, DECIMAL, ForeignKey, Boolean, Enum
2018-09-08 15:49:16 -07:00
from sqlalchemy.orm import relationship
2018-09-09 16:47:21 -07:00
from sqlalchemy.schema import UniqueConstraint
2019-06-17 22:43:57 -07:00
from photoapp.dbutils import Base
2018-09-09 16:47:21 -07:00
from datetime import datetime
2020-11-15 19:44:55 -08:00
import enum as py_enum
2018-09-08 15:49:16 -07:00
import uuid
2018-09-15 16:48:47 -07:00
import enum
2018-09-08 15:49:16 -07:00
2020-11-15 19:44:55 -08:00
class fcategory(py_enum.Enum):
image = 0
raw = 1
video = 2
ftypes = dict(jpg=dict(category=fcategory.image,
extensions={"jpeg"},
mimes={"image/jpeg"}),
png=dict(category=fcategory.image,
mimes={"image/png"}),
gif=dict(category=fcategory.image,
mimes={"image/gif"}),
cr2=dict(category=fcategory.raw,
mimes={"image/x-canon-cr2"}),
xmp=dict(category=fcategory.raw,
mimes={"application/octet-stream-xmp"}),
psd=dict(category=fcategory.raw,
mimes={"image/vnd.adobe.photoshop"}),
mp4=dict(category=fcategory.video,
mimes={"audio/mp4",
"video/mp4"}),
mov=dict(category=fcategory.video,
mimes={"video/quicktime"}))
# set various defaults in ftypes
# it should look like:
# ftypes = {
# <default_extension>: {
# "category": <fcategory key>, # required, fcategory type
# "extensions": {<*default_extension|optional extensions>}, # optional, defaults to set([<default_extension>])
# "mimes": { # required, mimes mapped to this type
# <mime string>
# },
# "type": <str(*image|video)>, # optional, defaults to str("image")
# },
# }
for extension, entry in ftypes.items():
entry["extensions"] = entry.get("extensions", set()) | set([extension])
entry["type"] = entry.get("type", "image")
2019-07-04 13:10:52 -07:00
# file extensions we allow
2020-11-15 19:44:55 -08:00
known_extensions = set.union(*[i["extensions"] for i in ftypes.values()])
# allowed file types (based on magic identification)
known_mimes = set.union(*[i["mimes"] for i in ftypes.values()])
2019-07-04 13:10:52 -07:00
# categorizaiton of media type based on extension
2020-11-15 19:44:55 -08:00
# we can pull metadata out of these
# jpg, png, gif etc
regular_images = set([extension for extension, ftype in ftypes.items() if ftype["category"] == fcategory.image])
# "derived" files, treated as black boxes, we can't open them because proprietary
# cr2, xmp, etc
files_raw = set([extension for extension, ftype in ftypes.items() if ftype["category"] == fcategory.raw])
# video types
# mp4, mov, etc
files_video = set([extension for extension, ftype in ftypes.items() if ftype["category"] == fcategory.video])
2019-07-04 13:10:52 -07:00
2020-11-15 19:44:55 -08:00
def mime2ext(mime):
"""
Given a mime type return the canonical file extension
"""
for ext, ftype in ftypes.items():
if mime in ftype["mimes"]:
return ext
raise Exception(f"Unknown mime: '{mime}'")
2019-07-04 13:10:52 -07:00
2019-07-04 14:24:54 -07:00
def genuuid():
return str(uuid.uuid4())
2019-06-25 21:34:32 -07:00
def map_extension(ext):
2020-11-15 19:44:55 -08:00
for known_ext, ftype in ftypes.items():
if ext in ftype["extensions"]:
return known_ext
raise Exception(f"Unknown extension: '{ext}'")
2019-06-21 09:22:42 -07:00
2019-07-09 21:10:44 -07:00
def generate_storage_path(timestamp, sha, extension):
basepath = timestamp.strftime("%Y/%m/%d/%Y-%m-%d_%H.%M.%S")
return f"{basepath}_{sha[0:8]}.{extension.lower()}"
2018-09-15 16:48:47 -07:00
class PhotoStatus(enum.Enum):
private = 0
public = 1
hidden = 2
2018-09-08 15:49:16 -07:00
class PhotoSet(Base):
__tablename__ = 'photos'
id = Column(Integer, primary_key=True)
2019-07-06 11:25:15 -07:00
uuid = Column(Unicode(length=36), unique=True, default=lambda: str(uuid.uuid4()))
2018-09-08 15:49:16 -07:00
date = Column(DateTime)
2018-09-15 16:48:47 -07:00
date_real = Column(DateTime)
date_offset = Column(Integer, default=0) # minutes
2019-07-09 21:10:44 -07:00
lat = Column(DECIMAL(precision=11, scale=8))
lon = Column(DECIMAL(precision=11, scale=8))
2018-09-08 15:49:16 -07:00
files = relationship("Photo", back_populates="set")
2018-09-09 16:47:21 -07:00
tags = relationship("TagItem", back_populates="set")
2018-09-08 15:49:16 -07:00
2019-07-06 11:25:15 -07:00
title = Column(String(length=128))
description = Column(String(length=1024))
slug = Column(String(length=128))
2018-09-09 14:10:42 -07:00
2018-09-15 16:48:47 -07:00
status = Column(Enum(PhotoStatus), default=PhotoStatus.private)
2019-07-04 16:57:45 -07:00
def to_json(self, files=True):
2019-06-18 18:38:01 -07:00
s = {attr: getattr(self, attr) for attr in {"uuid", "title", "description"}}
2019-06-25 21:34:32 -07:00
s["lat"] = str(self.lat) if self.lat else None
s["lon"] = str(self.lon) if self.lon else None
2019-06-18 18:38:01 -07:00
s["date"] = self.date.isoformat()
2019-07-04 16:57:45 -07:00
if files:
s["files"] = {i.uuid: i.to_json() for i in self.files}
s["tags"] = [t.tag.name for t in self.tags]
s["status"] = self.status.name if self.status else PhotoStatus.private.name # duplicate default definition
2019-06-18 18:38:01 -07:00
return s
2018-09-08 15:49:16 -07:00
class Photo(Base):
__tablename__ = 'files'
id = Column(Integer, primary_key=True)
set_id = Column(Integer, ForeignKey("photos.id"))
2019-07-06 11:25:15 -07:00
uuid = Column(Unicode(length=36), unique=True, default=genuuid)
2018-09-08 15:49:16 -07:00
set = relationship("PhotoSet", back_populates="files", foreign_keys=[set_id])
2018-09-09 12:05:13 -07:00
size = Column(Integer)
width = Column(Integer)
height = Column(Integer)
2018-09-09 13:45:26 -07:00
orientation = Column(Integer, default=0)
2018-09-08 15:49:16 -07:00
hash = Column(String(length=64), unique=True)
2019-07-06 11:25:15 -07:00
path = Column(Unicode(length=64))
format = Column(String(length=32)) # TODO how long can a mime string be
2019-07-04 14:25:19 -07:00
fname = Column(String(length=64)) # seems generous enough
2018-09-09 14:10:42 -07:00
2019-06-18 18:38:01 -07:00
def to_json(self):
j = {attr: getattr(self, attr) for attr in
2019-07-04 14:25:19 -07:00
{"uuid", "size", "width", "height", "orientation", "format", "hash", "fname"}}
2019-07-02 23:03:28 -07:00
j["set"] = self.set.uuid if self.set else None
2019-06-18 18:38:01 -07:00
return j
2018-09-09 14:10:42 -07:00
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
2019-07-06 11:25:15 -07:00
uuid = Column(Unicode(length=36), unique=True, default=lambda: str(uuid.uuid4()))
2018-09-09 16:47:21 -07:00
created = Column(DateTime, default=lambda: datetime.now())
modified = Column(DateTime, default=lambda: datetime.now())
2018-09-23 13:04:50 -07:00
is_album = Column(Boolean, default=False)
2018-09-23 15:00:54 -07:00
# slug-like short name such as "iomtrip"
2019-07-06 11:25:15 -07:00
name = Column(String(length=32), unique=True)
2018-09-23 15:00:54 -07:00
# longer human-format title like "Isle of Man trip"
2019-07-06 11:25:15 -07:00
title = Column(String(length=32))
2018-09-23 15:00:54 -07:00
# url slug like "isle-of-man-trip"
2019-07-06 11:25:15 -07:00
slug = Column(String(length=32), unique=True)
2018-09-23 15:00:54 -07:00
# fulltext description
2019-07-06 11:25:15 -07:00
description = Column(String(length=256))
2018-09-09 14:10:42 -07:00
entries = relationship("TagItem", back_populates="tag")
2019-07-20 16:05:17 -07:00
def to_json(self):
return {attr: getattr(self, attr) for attr in
{"uuid", "is_album", "name", "title", "description"}}
2018-09-09 14:10:42 -07:00
class TagItem(Base):
__tablename__ = 'tag_items'
id = Column(Integer, primary_key=True)
tag_id = Column(Integer, ForeignKey("tags.id"))
2018-09-09 16:47:21 -07:00
set_id = Column(Integer, ForeignKey("photos.id"))
order = Column(Integer, default=0)
2018-09-09 14:10:42 -07:00
tag = relationship("Tag", back_populates="entries", foreign_keys=[tag_id])
2018-09-09 16:47:21 -07:00
set = relationship("PhotoSet", back_populates="tags", foreign_keys=[set_id])
2018-09-09 14:10:42 -07:00
2018-09-09 16:47:21 -07:00
UniqueConstraint(tag_id, set_id)
2018-09-23 16:39:46 -07:00
class UserStatus(enum.Enum):
banned = -1
guest = 0
normal = 1
admin = 2
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String(length=64), unique=True)
password = Column(String(length=64)) # sha256
status = Column(Enum(UserStatus), default=UserStatus.normal)
2019-06-22 16:36:06 -07:00
def to_json(self):
return dict(name=self.name, status=self.status.name)