Skip to content

Commit

Permalink
feat: smoelen
Browse files Browse the repository at this point in the history
  • Loading branch information
sverben committed Apr 17, 2024
1 parent 2c93b09 commit 073b9c5
Show file tree
Hide file tree
Showing 20 changed files with 1,041 additions and 288 deletions.
2 changes: 1 addition & 1 deletion api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ WORKDIR /app
COPY . /app

ENV PYTHONPATH=/
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
ENTRYPOINT ["/app/start.sh"]
2 changes: 2 additions & 0 deletions api/app/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class Settings(BaseSettings):
allowed_users: list[str]
openid_configuration: str
database_url: str
client_id: str
client_secret: str


settings = Settings()
234 changes: 157 additions & 77 deletions api/app/db/crud.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import datetime
import os
from functools import lru_cache
from typing import Type
from uuid import uuid4, UUID

import ffmpeg
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from fastapi import UploadFile
from app.fileresponse import FastApiBaizeFileResponse as FileResponse
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, selectinload
from sqlalchemy import func

from app.conf import settings
from app.db import models, schemas
from app.fileresponse import FastApiBaizeFileResponse as FileResponse


@lru_cache()
Expand Down Expand Up @@ -41,10 +43,10 @@ def sign_item(item_data: models.Item):
expiry = (datetime.datetime.now() + datetime.timedelta(days=1)).timestamp()

item.cover_path = sign_url(
f"{settings.base_url}/items/{item_data.album_id}/{item.id}/{expiry}/cover"
f"{settings.base_url}/items/{item.id}/{expiry}/cover"
)
item.path = sign_url(
f"{settings.base_url}/items/{item_data.album_id}/{item.id}/{expiry}/full"
f"{settings.base_url}/items/{item.id}/{expiry}/full"
)

return item
Expand All @@ -61,6 +63,17 @@ def get_album(db: Session, album_id: UUID):
return album


def get_smoel_album(db: Session, album_id: UUID):
smoel_data = db.query(models.Smoel).filter(models.Smoel.id == album_id).first()
smoel = schemas.SmoelAlbum.model_validate(smoel_data)
smoel.items = []

for item in smoel_data.items:
smoel.items.append(sign_item(item))

return smoel


def get_full(db: Session, item_id: UUID):
item = db.query(models.Item).filter(models.Item.id == item_id).first()

Expand All @@ -87,6 +100,24 @@ def get_albums(db: Session):
return result


def get_smoelen_albums(db: Session):
smoelen = sorted(db.query(models.Smoel).all(), key=lambda x: len(x.items), reverse=True)
result = []

for smoel_data in smoelen:
smoel = schemas.SmoelAlbumList.model_validate(smoel_data)
if smoel_data.preview_id:
smoel.preview = sign_item(smoel_data.preview)

smoel.items = []
for item in smoel_data.items[:2]:
smoel.items.append(sign_item(item))

result.append(smoel)

return result


def create_album(db: Session, album: schemas.AlbumCreate):
album_id = uuid4()
os.mkdir(f"data/items/{album_id}")
Expand Down Expand Up @@ -119,98 +150,132 @@ def order_albums(db: Session, albums: list[schemas.AlbumOrder]):
return db.query(models.Album).all()


async def create_item(
def create_item(
db: Session,
user: schemas.User,
items: list[UploadFile],
album_id: UUID,
date: datetime = None,
user: schemas.User | None,
item: bytes,
content_type: str,
album_id: UUID | None,
date: datetime = None
):
db_items = []

for item in items:
# write temp file
item_id = uuid4()
os.mkdir(f"data/items/{album_id}/{item_id}")
with open(f"/tmp/{item_id}", "wb") as buffer:
buffer.write(await item.read())

# get type
file_type = item.content_type.split("/")[0]
if file_type not in ["image", "video"]:
continue

if file_type == "image":
file_type = models.Type.IMAGE
else:
file_type = models.Type.VIDEO
# write temp file
item_id = uuid4()
if album_id is None:
album_folder = "data/items/smoelen"
if not os.path.exists(album_folder):
os.mkdir(album_folder)

else:
album_folder = f"data/items/{album_id}"

os.mkdir(f"{album_folder}/{item_id}")
with open(f"/tmp/{item_id}", "wb") as buffer:
buffer.write(item)

# get type
file_type = content_type.split("/")[0]
if file_type not in ["image", "video"]:
return None

if file_type == "image":
file_type = models.Type.IMAGE
else:
file_type = models.Type.VIDEO

# get metadata
probe = ffmpeg.probe(f"/tmp/{item_id}")
width = probe["streams"][0]["width"]
height = probe["streams"][0]["height"]

# create cover image
cover_path = f"{album_folder}/{item_id}/cover.jpg"

if file_type == models.Type.VIDEO:
stream = ffmpeg.input(f"/tmp/{item_id}")
stream = ffmpeg.filter(stream, "scale", 400, -1)
stream = ffmpeg.output(stream, cover_path, vframes=1)
ffmpeg.run(stream)
else:
stream = ffmpeg.input(f"/tmp/{item_id}")
stream = ffmpeg.filter(stream, "scale", 400, -1)
stream = ffmpeg.output(stream, cover_path)
ffmpeg.run(stream)

# store optimized full size image/video
if file_type == models.Type.VIDEO:
path = f"{album_folder}/{item_id}/item.mp4"

stream = ffmpeg.input(f"/tmp/{item_id}")
stream = ffmpeg.output(stream, path, crf=23)
ffmpeg.run(stream)
else:
path = f"{album_folder}/{item_id}/item.jpg"

stream = ffmpeg.input(f"/tmp/{item_id}")
stream = ffmpeg.output(stream, path)
ffmpeg.run(stream)

if user:
user_id = user.id
else:
user_id = None

db_item = models.Item(
id=item_id,
user=user_id,
album_id=album_id,
type=file_type,
width=width,
height=height,
cover_path=cover_path,
path=path,
date=date,
)
db.add(db_item)
db.commit()
db.refresh(db_item)

# get metadata
probe = ffmpeg.probe(f"/tmp/{item_id}")
width = probe["streams"][0]["width"]
height = probe["streams"][0]["height"]
os.remove(f"/tmp/{item_id}")
return db_item

# create cover image
cover_path = f"data/items/{album_id}/{item_id}/cover.jpg"

if file_type == models.Type.VIDEO:
stream = ffmpeg.input(f"/tmp/{item_id}")
stream = ffmpeg.filter(stream, "scale", 400, -1)
stream = ffmpeg.output(stream, cover_path, vframes=1)
ffmpeg.run(stream)
else:
stream = ffmpeg.input(f"/tmp/{item_id}")
stream = ffmpeg.filter(stream, "scale", 400, -1)
stream = ffmpeg.output(stream, cover_path)
ffmpeg.run(stream)

# store optimized full size image/video
if file_type == models.Type.VIDEO:
path = f"data/items/{album_id}/{item_id}/item.mp4"

stream = ffmpeg.input(f"/tmp/{item_id}")
stream = ffmpeg.output(stream, path, crf=23)
ffmpeg.run(stream)
else:
path = f"data/items/{album_id}/{item_id}/item.jpg"

stream = ffmpeg.input(f"/tmp/{item_id}")
stream = ffmpeg.output(stream, path)
ffmpeg.run(stream)

db_item = models.Item(
id=item_id,
user=user.id,
album_id=album_id,
type=file_type,
width=width,
height=height,
cover_path=cover_path,
path=path,
date=date,
)
db.add(db_item)
db.commit()
db.refresh(db_item)
db_items.append(db_item)
async def create_items(
db: Session,
user: schemas.User | None,
items: list[UploadFile],
album_id: UUID | None,
date: datetime = None,
) -> list[models.Item]:
db_items = []

os.remove(f"/tmp/{item_id}")
for item in items:
db_item = create_item(db, user, await item.read(), item.content_type, album_id, date)
if db_item is not None:
db_items.append(db_item)

return db_items


def delete_items(db: Session, user: schemas.User, album_id: UUID, items: list[UUID]):
def delete_items(db: Session, user: schemas.User | None, album_id: UUID | None, items: list[UUID]):
for item in items:
db_item = db.query(models.Item).filter(models.Item.id == item).first()
if db_item.user != user.id and not user.admin:
if user is not None and db_item.user != user.id and not user.admin:
continue

os.remove(db_item.path)
os.remove(db_item.cover_path)
os.rmdir(f"data/items/{album_id}/{item}")

if album_id is None:
album_folder = "data/items/smoelen"
else:
album_folder = f"data/items/{album_id}"
os.rmdir(f"{album_folder}/{item}")
db.delete(db_item)
db.commit()

if not album_id:
return None

return db.query(models.Album).filter(models.Album.id == album_id).first()


Expand All @@ -224,3 +289,18 @@ def set_preview(db: Session, album_id: UUID, item_id: UUID):
db.commit()

return db_album


def get_smoel(db: Session, smoel_id: UUID, name: str):
smoel = db.query(models.Smoel).filter(models.Smoel.id == smoel_id).first()
if not smoel:
smoel = models.Smoel(name=name, id=smoel_id)
db.add(smoel)
db.commit()

return smoel


def set_smoel(db: Session, item: models.Item | Type[models.Item], smoel: models.Smoel):
smoel.items.append(item)
db.commit()
36 changes: 33 additions & 3 deletions api/app/db/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from __future__ import annotations

import enum
from typing import List

from sqlalchemy import Column, ForeignKey, String, Uuid, Enum, Integer, DateTime
from sqlalchemy import Column, ForeignKey, String, Uuid, Enum, Integer, DateTime, Boolean, Table
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from sqlalchemy.sql import func, expression

from app.db.database import Base

Expand Down Expand Up @@ -30,15 +34,41 @@ class Album(Base):
)


association_table = Table(
"association",
Base.metadata,
Column("item_id", Uuid, ForeignKey("items.id")),
Column("smoel_id", Uuid, ForeignKey("smoelen.id")),
)


class Smoel(Base):
__tablename__ = "smoelen"
id = Column(Uuid, primary_key=True, index=True)
name = Column(String)

preview_id = Column(Uuid, ForeignKey("items.id"), nullable=True)
preview = relationship("Item", foreign_keys=[preview_id])

items: Mapped[List[Item]] = relationship(
secondary=association_table, back_populates="smoelen"
)


class Item(Base):
__tablename__ = "items"

id = Column(Uuid, primary_key=True, index=True)
user = Column(String)
album_id = Column(Uuid, ForeignKey("albums.id"))
album_id = Column(Uuid, ForeignKey("albums.id"), nullable=True)
album = relationship("Album", back_populates="items", foreign_keys=[album_id])
date = Column(DateTime(timezone=True), server_default=func.now())

processed = Column(Boolean, server_default=expression.false(), nullable=False)
smoelen: Mapped[List[Smoel]] = relationship(
secondary=association_table, back_populates="items"
)

type = Column(Enum(Type))
width = Column(String)
height = Column(String)
Expand Down
Loading

0 comments on commit 073b9c5

Please sign in to comment.