"""download editor (phase 2): per-user trim/crop derivatives Revision ID: 0041_download_edit Revises: 0040_reset_profiles Create Date: 2026-07-04 Adds the editor columns to `download_jobs`. An edit job (`job_kind='edit'`) derives a new per-user clip from an already-downloaded job by running ffmpeg (trim = stream-copy, crop = re-encode). It still hangs off a `media_assets` row (with `source_kind='edit'` and a per-user cache key) so the existing file-serve / ref-count / GC / quota machinery is reused unchanged. `source_job_id` / `source_asset_id` point at the parent download; `edit_spec` is the trim/crop recipe. """ from typing import Sequence, Union import sqlalchemy as sa from alembic import op revision: str = "0041_download_edit" down_revision: Union[str, None] = "0040_reset_profiles" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: op.add_column( "download_jobs", sa.Column( "job_kind", sa.String(length=16), server_default="download", nullable=False ), ) op.add_column( "download_jobs", sa.Column("source_job_id", sa.Integer(), nullable=True) ) op.add_column( "download_jobs", sa.Column("source_asset_id", sa.Integer(), nullable=True) ) op.add_column("download_jobs", sa.Column("edit_spec", sa.JSON(), nullable=True)) op.create_foreign_key( "fk_download_jobs_source_job", "download_jobs", "download_jobs", ["source_job_id"], ["id"], ondelete="SET NULL", ) op.create_foreign_key( "fk_download_jobs_source_asset", "download_jobs", "media_assets", ["source_asset_id"], ["id"], ondelete="SET NULL", ) def downgrade() -> None: op.drop_constraint( "fk_download_jobs_source_asset", "download_jobs", type_="foreignkey" ) op.drop_constraint( "fk_download_jobs_source_job", "download_jobs", type_="foreignkey" ) op.drop_column("download_jobs", "edit_spec") op.drop_column("download_jobs", "source_asset_id") op.drop_column("download_jobs", "source_job_id") op.drop_column("download_jobs", "job_kind")