Add the non-yt-dlp ingest path so a user-uploaded file joins the same download
library and inherits its function set. Any file type is accepted: media
(video/audio) gets the full set (metadata, share, play/edit), a generic file
(pdf/zip/docx/…) is download-only.
- MediaAsset.media_kind ("video"|"audio"|"file") — the stored discriminator that
gates library actions. Set at every finalize (download probe, editor carries the
source's kind, upload probe); consolidates the ad-hoc audio-detect that lived only
in links.py into one invariant (downloads.mediakind.derive). Migration 0076 adds
the column + backfills existing rows.
- downloads/upload.py: ingest_upload probes the file (ffprobe → codecs/dims/duration),
places it in a per-user .uploads tree, and creates an already-ready asset + done job
(the same cache-hit shortcut enqueue uses). Uploads never expire (irreplaceable).
- POST /api/downloads/upload: streaming multipart with the per-file cap
(download_upload_max_bytes, default 500 MiB) enforced on actual bytes read; quota
charges the known size up front (quota.check_upload).
- Security: a generic file is always served force-download (attachment +
application/octet-stream) on both the in-app and public endpoints, so uploaded
HTML/SVG can't execute inline. Edit + redownload reject non-media / upload sources.
- python-multipart added (first multipart endpoint in the app).
42 lines
1.6 KiB
Python
42 lines
1.6 KiB
Python
"""media_assets.media_kind (E7 S5a: the video|audio|file discriminator for the media library)
|
|
|
|
Adds a stored media-kind to each asset so the library can gate its actions on it (play/edit for
|
|
media, download-only for a generic uploaded file) instead of re-deriving from codecs in prose. The
|
|
column is the single home of the invariant `downloads.mediakind.derive` computes at finalize.
|
|
|
|
Backfill for existing rows: a stored video codec -> 'video', an audio-only codec -> 'audio', an
|
|
editor derivative (source_kind='edit', which carries no codecs but is always a video clip) ->
|
|
'video'. Rows still pending/errored (no codecs, not an edit) stay NULL — kind is set when they
|
|
finalize. Empty `media_kind` never reaches the UI: only `ready`/`done` rows render actions, and
|
|
those are all backfilled here.
|
|
"""
|
|
|
|
from typing import Sequence, Union
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision: str = "0076_media_asset_kind"
|
|
down_revision: Union[str, None] = "0075_playlist_sync_baseline"
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.add_column("media_assets", sa.Column("media_kind", sa.String(length=16), nullable=True))
|
|
op.execute(
|
|
"""
|
|
UPDATE media_assets
|
|
SET media_kind = CASE
|
|
WHEN vcodec IS NOT NULL THEN 'video'
|
|
WHEN acodec IS NOT NULL THEN 'audio'
|
|
WHEN source_kind = 'edit' THEN 'video'
|
|
ELSE NULL
|
|
END
|
|
"""
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_column("media_assets", "media_kind")
|