{"openapi":"3.1.0","info":{"title":"VideoGen API","description":"# VideoGen — AI short video generation + post-production\n\n## Endpoints\n\n### Generation\n- `POST /api/v1/generate` — generate a video from a textual idea\n- `POST /api/v1/generate-edited` — atomic generate + edit in one call\n\n### Editing\n- `POST /api/v1/edit` — apply trim/resize/fade/normalize (Slice 1)\n- `POST /api/v1/edit-with-captions` — Slice 1 + URL sources + Whisper captions (Slice 2)\n- `POST /api/v1/edit-v3` — Slice 2 + music + watermark (Slice 3)\n- `GET  /api/v1/presets` — list platform presets\n- `GET  /api/v1/presets/{name}` — single preset\n\n### Outputs\n- `GET  /api/v1/outputs/` — list edited videos\n- `GET  /api/v1/outputs/{filename}` — download an edited video\n- `HEAD /api/v1/outputs/{filename}` — metadata only (no body)\n\n### Misc\n- `GET  /health` — health check\n- `GET  /docs` — this OpenAPI documentation\n\n## Notes\n\n- In `DEMO_MODE=true` (default), providers generate real local placeholder MP4s\n  so `/generate-edited` works end-to-end without external API keys.\n- Output files have a TTL (default 1h); use `?token=...` if `OUTPUT_TOKEN` is set.\n- Filter chains use `-filter_complex` with `;` separating video and audio chains.\n","contact":{"name":"VideoGen Maintainers","url":"https://github.com/TheBoomerDev/videogen"},"license":{"name":"MIT","url":"https://opensource.org/licenses/MIT"},"version":"1.0.0"},"paths":{"/api/v1/auth/register":{"post":{"tags":["auth"],"summary":"Register","description":"Register a new user.","operationId":"register_api_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/login":{"post":{"tags":["auth"],"summary":"Login","description":"Login user.","operationId":"login_api_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserLogin"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/auth/me":{"get":{"tags":["auth"],"summary":"Get Me","description":"Get current user.","operationId":"get_me_api_v1_auth_me_get","parameters":[{"name":"api-key","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Api-Key"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/edit":{"post":{"tags":["edit"],"summary":"Edit Video","description":"Apply post-generation edits to a video file.\n\nThe source may be a local path or an http(s) URL (auto-downloaded).","operationId":"edit_video_api_v1_edit_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/presets":{"get":{"tags":["edit"],"summary":"List Presets","description":"List all built-in edit presets.","operationId":"list_presets_api_v1_presets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresetsResponse"}}}}}}},"/api/v1/presets/{name}":{"get":{"tags":["edit"],"summary":"Get Preset","description":"Get a single preset by name.","operationId":"get_preset_api_v1_presets__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Preset Api V1 Presets  Name  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/edit-with-captions":{"post":{"tags":["edit"],"summary":"Edit Video With Captions","description":"Apply edits AND burn Whisper-generated captions.\n\nExtends /edit with:\n- URL sources (downloaded into work_dir)\n- Captions via OpenAI Whisper local CLI (model=tiny/base/small/medium/large/turbo)","operationId":"edit_video_with_captions_api_v1_edit_with_captions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditRequestV2"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/edit-v3":{"post":{"tags":["edit"],"summary":"Edit Video V3","description":"Slice 3: trim/resize/fade/normalize + music + watermark + captions.\n\nCombines all edit operations and overlay additions in a single endpoint:\n- URL sources (downloaded into work_dir)\n- Music overlay (mix audio, loop if shorter, fade in/out)\n- Watermark overlay (corner preset OR pixel-precise position)\n- Captions via OpenAI Whisper local CLI","operationId":"edit_video_v3_api_v1_edit_v3_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditRequestV3"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EditResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/generate":{"post":{"tags":["generate"],"summary":"Generate Video","description":"Generate a video from idea (delegates to provider pipeline).","operationId":"generate_video_api_v1_generate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/generate-edited":{"post":{"tags":["generate"],"summary":"Generate Edited","description":"Atomic generate + edit.\n\nTwo flows:\n1. If `idea` is provided: run /generate internally, download the result,\n   then apply `edit` to the downloaded video.\n2. If `source` is provided (URL or local path): skip generation, apply\n   `edit` directly to that source.\n\nThe `edit` field is an EditRequestV3 but its `source` is ignored (we\noverride it with the generated/downloaded video path).","operationId":"generate_edited_api_v1_generate_edited_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateEditedRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateEditedResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/outputs/{filename}":{"head":{"tags":["outputs"],"summary":"Head Output","description":"HEAD metadata for an output file (for clients to check size before GET).","operationId":"head_output_api_v1_outputs__filename__head","parameters":[{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}},{"name":"X-Output-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Output-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["outputs"],"summary":"Get Output","description":"GET an edited video file.\n\nThe file must have been produced by a prior call to /edit, /edit-v3, or\n/generate-edited. Files are subject to TTL cleanup (default 1 hour).\n\nQuery params:\n    token: optional auth token (alternative to X-Output-Token header)\n\nHeaders:\n    X-Output-Token: optional auth token\n    Range: standard HTTP Range header (for video seeking)","operationId":"get_output_api_v1_outputs__filename__get","parameters":[{"name":"filename","in":"path","required":true,"schema":{"type":"string","title":"Filename"}},{"name":"token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token"}},{"name":"X-Output-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Output-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/outputs/":{"get":{"tags":["outputs"],"summary":"List Outputs","description":"List available outputs (metadata only, no file content).","operationId":"list_outputs_api_v1_outputs__get","parameters":[{"name":"token","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Token"}},{"name":"X-Output-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Output-Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health Check","description":"Health check endpoint. Reports MongoDB connection status.","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}}},"components":{"schemas":{"CaptionStyleSpec":{"properties":{"font":{"type":"string","title":"Font","description":"Font name available to ffmpeg/libass","default":"DejaVu Sans"},"font_size":{"type":"integer","maximum":72.0,"minimum":8.0,"title":"Font Size","default":22},"primary_color":{"type":"string","enum":["white","black","yellow","red","green","blue"],"title":"Primary Color","default":"white"},"outline_color":{"type":"string","enum":["white","black","yellow","red","green","blue"],"title":"Outline Color","default":"black"},"outline_width":{"type":"integer","maximum":8.0,"minimum":0.0,"title":"Outline Width","default":2},"margin_v":{"type":"integer","maximum":200.0,"minimum":0.0,"title":"Margin V","description":"Bottom margin in pixels","default":30},"alignment":{"type":"integer","enum":[1,2],"title":"Alignment","description":"1=bottom-left, 2=bottom-center","default":2}},"type":"object","title":"CaptionStyleSpec"},"CaptionsSpec":{"properties":{"enabled":{"type":"boolean","title":"Enabled","default":true},"model":{"type":"string","enum":["tiny","base","small","medium","large","turbo"],"title":"Model","description":"Whisper model size. Larger = more accurate, slower.","default":"base"},"language":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Language","description":"Force language (ISO-639-1). None = auto-detect."},"style":{"$ref":"#/components/schemas/CaptionStyleSpec"},"timeout":{"type":"number","maximum":1800.0,"minimum":10.0,"title":"Timeout","description":"Whisper timeout in seconds","default":600.0}},"type":"object","title":"CaptionsSpec","description":"Auto-generate captions from the source video's audio track using Whisper.\n\nThe captions are burned into the output video as hardcoded subtitles.\nRequires a working local `whisper` CLI (openai-whisper package)."},"EditRequest":{"properties":{"source":{"type":"string","minLength":1,"title":"Source","description":"Local path to source video. URL sources require Slice 2."},"trim":{"anyOf":[{"$ref":"#/components/schemas/TrimSpec"},{"type":"null"}],"description":"Cut to [start, end] in seconds"},"resize":{"anyOf":[{"$ref":"#/components/schemas/ResizeSpec"},{"type":"null"}],"description":"Scale to target dimensions"},"fade":{"anyOf":[{"$ref":"#/components/schemas/FadeSpec"},{"type":"null"}],"description":"Add fade in/out"},"normalize":{"anyOf":[{"$ref":"#/components/schemas/NormalizeSpec"},{"type":"null"}],"description":"EBU R128 loudness normalize"},"output_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Output Filename","description":"Optional fixed filename for the output (else random uuid)."},"timeout":{"type":"number","maximum":600.0,"minimum":5.0,"title":"Timeout","description":"Max seconds for the FFmpeg call","default":60.0}},"type":"object","required":["source"],"title":"EditRequest","description":"Apply post-generation edits to a video file (local path)."},"EditRequestV2":{"properties":{"source":{"type":"string","minLength":1,"title":"Source","description":"Local path to source video. URL sources require Slice 2."},"trim":{"anyOf":[{"$ref":"#/components/schemas/TrimSpec"},{"type":"null"}],"description":"Cut to [start, end] in seconds"},"resize":{"anyOf":[{"$ref":"#/components/schemas/ResizeSpec"},{"type":"null"}],"description":"Scale to target dimensions"},"fade":{"anyOf":[{"$ref":"#/components/schemas/FadeSpec"},{"type":"null"}],"description":"Add fade in/out"},"normalize":{"anyOf":[{"$ref":"#/components/schemas/NormalizeSpec"},{"type":"null"}],"description":"EBU R128 loudness normalize"},"output_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Output Filename","description":"Optional fixed filename for the output (else random uuid)."},"timeout":{"type":"number","maximum":600.0,"minimum":5.0,"title":"Timeout","description":"Max seconds for the FFmpeg call","default":60.0},"captions":{"anyOf":[{"$ref":"#/components/schemas/CaptionsSpec"},{"type":"null"}],"description":"Auto-generate and burn captions"}},"type":"object","required":["source"],"title":"EditRequestV2","description":"Extended edit request supporting URL sources and captions."},"EditRequestV3":{"properties":{"source":{"type":"string","minLength":1,"title":"Source","description":"Local path to source video. URL sources require Slice 2."},"trim":{"anyOf":[{"$ref":"#/components/schemas/TrimSpec"},{"type":"null"}],"description":"Cut to [start, end] in seconds"},"resize":{"anyOf":[{"$ref":"#/components/schemas/ResizeSpec"},{"type":"null"}],"description":"Scale to target dimensions"},"fade":{"anyOf":[{"$ref":"#/components/schemas/FadeSpec"},{"type":"null"}],"description":"Add fade in/out"},"normalize":{"anyOf":[{"$ref":"#/components/schemas/NormalizeSpec"},{"type":"null"}],"description":"EBU R128 loudness normalize"},"output_filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Output Filename","description":"Optional fixed filename for the output (else random uuid)."},"timeout":{"type":"number","maximum":600.0,"minimum":5.0,"title":"Timeout","description":"Max seconds for the FFmpeg call","default":60.0},"captions":{"anyOf":[{"$ref":"#/components/schemas/CaptionsSpec"},{"type":"null"}],"description":"Auto-generate and burn captions"},"music":{"anyOf":[{"$ref":"#/components/schemas/MusicSpec"},{"type":"null"}],"description":"Background music overlay"},"watermark":{"anyOf":[{"$ref":"#/components/schemas/WatermarkSpec"},{"type":"null"}],"description":"Logo overlay"}},"type":"object","required":["source"],"title":"EditRequestV3","description":"Extended edit request supporting URL sources, captions, music, and watermark."},"EditResponse":{"properties":{"status":{"type":"string","enum":["success","error"],"title":"Status","default":"success"},"output_path":{"type":"string","title":"Output Path"},"output_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Output Url"},"size_bytes":{"type":"integer","title":"Size Bytes"},"elapsed_seconds":{"type":"number","title":"Elapsed Seconds"},"duration_seconds":{"type":"number","title":"Duration Seconds"},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width"},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["output_path","size_bytes","elapsed_seconds","duration_seconds"],"title":"EditResponse"},"FadeSpec":{"properties":{"in_seconds":{"type":"number","maximum":10.0,"minimum":0.0,"title":"In Seconds","description":"Fade-in duration in seconds","default":0.0},"out_seconds":{"type":"number","maximum":10.0,"minimum":0.0,"title":"Out Seconds","description":"Fade-out duration in seconds","default":0.0},"color":{"type":"string","enum":["black","white"],"title":"Color","description":"Fade color","default":"black"}},"type":"object","title":"FadeSpec"},"GenerateEditedRequest":{"properties":{"idea":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Idea","description":"Textual idea to generate a video (Flow 1: generate then edit)."},"duracion":{"anyOf":[{"type":"integer","enum":[15,30,60]},{"type":"null"}],"title":"Duracion","description":"Duration in seconds (required if idea is set)."},"estilo":{"anyOf":[{"type":"string","enum":["animado","real","corporativo"]},{"type":"null"}],"title":"Estilo","description":"Visual style (required if idea is set)."},"plataforma":{"anyOf":[{"type":"string","enum":["instagram","tiktok","youtube_shorts"]},{"type":"null"}],"title":"Plataforma","description":"Target platform (required if idea is set)."},"source":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Source","description":"URL or local path of an existing video (Flow 2: edit only)."},"edit":{"$ref":"#/components/schemas/EditRequestV3","description":"Edit pipeline to apply (music, watermark, captions, ...)."}},"type":"object","required":["edit"],"title":"GenerateEditedRequest","description":"Atomic generate + edit request.\n\nTwo ways to use it:\n1. Pass `idea` (+ duracion/estilo/plataforma) to generate a video and edit it.\n2. Pass `source` (URL or local path) to edit an existing video directly.\n\nExactly one of (idea, source) must be set. The `edit` field carries the\nfull EditRequestV3 spec; its `source` is ignored (overridden by the\ngenerated or caller-supplied source)."},"GenerateEditedResponse":{"properties":{"status":{"type":"string","enum":["success","error"],"title":"Status","default":"success"},"generated_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Generated Url"},"proveedor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proveedor"},"timestamp":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timestamp"},"output_path":{"type":"string","title":"Output Path"},"size_bytes":{"type":"integer","title":"Size Bytes"},"elapsed_seconds":{"type":"number","title":"Elapsed Seconds"},"duration_seconds":{"type":"number","title":"Duration Seconds"},"width":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Width"},"height":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Height"},"music":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Music"},"watermark":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Watermark"},"captions":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Captions"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["output_path","size_bytes","elapsed_seconds","duration_seconds"],"title":"GenerateEditedResponse","description":"Response combining the generation metadata and the final edited video."},"GenerateRequest":{"properties":{"idea":{"type":"string","minLength":1,"title":"Idea","description":"Textual idea for the video"},"duracion":{"type":"integer","enum":[15,30,60],"title":"Duracion","description":"Duration in seconds"},"estilo":{"type":"string","enum":["animado","real","corporativo"],"title":"Estilo","description":"Visual style"},"plataforma":{"type":"string","enum":["instagram","tiktok","youtube_shorts"],"title":"Plataforma","description":"Target platform"}},"type":"object","required":["idea","duracion","estilo","plataforma"],"title":"GenerateRequest"},"GenerateResponse":{"properties":{"status":{"type":"string","enum":["success","error"],"title":"Status"},"video_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Video Url"},"proveedor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Proveedor"},"duracion_real":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duracion Real"},"timestamp":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timestamp"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["status"],"title":"GenerateResponse"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status"},"service":{"type":"string","title":"Service"},"version":{"type":"string","title":"Version"},"mongodb":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Mongodb"}},"type":"object","required":["status","service","version"],"title":"HealthResponse"},"MusicSpec":{"properties":{"source":{"type":"string","minLength":1,"title":"Source","description":"Local path OR http(s) URL to music file"},"volume":{"type":"number","maximum":2.0,"minimum":0.0,"title":"Volume","description":"Music volume relative to source audio (1.0 = equal)","default":0.5},"fade_in_seconds":{"type":"number","maximum":10.0,"minimum":0.0,"title":"Fade In Seconds","default":0.0},"fade_out_seconds":{"type":"number","maximum":10.0,"minimum":0.0,"title":"Fade Out Seconds","default":1.0},"loop":{"type":"boolean","title":"Loop","description":"Loop music to fill video duration if shorter","default":true}},"type":"object","required":["source"],"title":"MusicSpec","description":"Background music overlay mixed over the source video's audio.\n\nThe source can be a local path OR an http(s) URL. ffmpeg will mix the\nmusic track at the given volume and fade it in/out. If the music is\nshorter than the video it will loop (default) or stop. Voice from the\nsource audio remains at 1.0 by default — we don't change the source\naudio gain, only add the music on top."},"NormalizeSpec":{"properties":{"target_lufs":{"type":"number","maximum":-6.0,"minimum":-30.0,"title":"Target Lufs","description":"Target integrated loudness in LUFS (EBU R128). -16 = social media, -23 = broadcast, -14 = streaming.","default":-16.0}},"type":"object","title":"NormalizeSpec"},"PresetInfo":{"properties":{"name":{"type":"string","title":"Name"},"description":{"type":"string","title":"Description"},"resize":{"$ref":"#/components/schemas/ResizeSpec"},"trim_default":{"anyOf":[{"$ref":"#/components/schemas/TrimSpec"},{"type":"null"}]},"fade":{"anyOf":[{"$ref":"#/components/schemas/FadeSpec"},{"type":"null"}]}},"type":"object","required":["name","description","resize"],"title":"PresetInfo"},"PresetsResponse":{"properties":{"presets":{"items":{"$ref":"#/components/schemas/PresetInfo"},"type":"array","title":"Presets"}},"type":"object","required":["presets"],"title":"PresetsResponse"},"ResizeSpec":{"properties":{"width":{"type":"integer","maximum":7680.0,"exclusiveMinimum":0.0,"title":"Width","description":"Target width in pixels"},"height":{"type":"integer","maximum":4320.0,"exclusiveMinimum":0.0,"title":"Height","description":"Target height in pixels"},"fit":{"type":"string","enum":["scale","pad","crop"],"title":"Fit","description":"How to fit: scale=stretch, pad=letterbox, crop=center-crop","default":"scale"}},"type":"object","required":["width","height"],"title":"ResizeSpec"},"SessionResponse":{"properties":{"user":{"additionalProperties":true,"type":"object","title":"User"},"token":{"type":"string","title":"Token"}},"type":"object","required":["user","token"],"title":"SessionResponse"},"TrimSpec":{"properties":{"start":{"type":"number","minimum":0.0,"title":"Start","description":"Start time in seconds","default":0.0},"end":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"End","description":"End time in seconds (None = end of input)"}},"type":"object","title":"TrimSpec"},"UserCreate":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"},"name":{"type":"string","title":"Name"}},"type":"object","required":["email","password","name"],"title":"UserCreate"},"UserLogin":{"properties":{"email":{"type":"string","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"UserLogin"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WatermarkPositionSpec":{"properties":{"x":{"type":"integer","minimum":0.0,"title":"X","description":"X pixel offset from left edge"},"y":{"type":"integer","minimum":0.0,"title":"Y","description":"Y pixel offset from top edge"}},"type":"object","required":["x","y"],"title":"WatermarkPositionSpec","description":"Pixel-accurate position for the watermark."},"WatermarkSpec":{"properties":{"source":{"type":"string","minLength":1,"title":"Source","description":"Local path OR http(s) URL to image (PNG/JPG)"},"position":{"type":"string","enum":["top-left","top-right","bottom-left","bottom-right","center"],"title":"Position","description":"Corner preset (ignored if custom_position is set)","default":"bottom-right"},"custom_position":{"anyOf":[{"$ref":"#/components/schemas/WatermarkPositionSpec"},{"type":"null"}],"description":"Pixel-precise position (overrides position)"},"opacity":{"type":"number","maximum":1.0,"minimum":0.0,"title":"Opacity","description":"Logo opacity (0=invisible, 1=opaque)","default":0.7},"scale":{"type":"number","maximum":1.0,"minimum":0.01,"title":"Scale","description":"Logo width as fraction of video width (height auto-derived from aspect)","default":0.15},"margin":{"type":"integer","maximum":200.0,"minimum":0.0,"title":"Margin","description":"Margin from video edge (pixels)","default":20}},"type":"object","required":["source"],"title":"WatermarkSpec","description":"Overlay an image (logo) on the video.\n\nThe source can be a local path OR an http(s) URL to PNG/JPG. By default\nthe watermark goes to the bottom-right with 15% width and 70% opacity.\nUse either `position` (corner preset) OR `custom_position` (pixel-precise)."}}},"tags":[{"name":"auth","description":"Authentication and session management"},{"name":"generate","description":"Video generation from ideas"},{"name":"edit","description":"Post-generation editing operations"},{"name":"outputs","description":"Serve edited output files"}]}