From 75522ede50a0ac63205940eaa1e5e61507ce759e Mon Sep 17 00:00:00 2001 From: oimwiodev Date: Fri, 22 May 2026 20:47:21 +0100 Subject: [PATCH] Add cookie file upload --- README.md | 2 +- tests/test_web_app.py | 34 +++++++++++++++++++++++++++++++++- web_app.py | 39 +++++++++++++++++++++++++++++++++------ 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index b6df67f..8504c16 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ Open `http://127.0.0.1:7860` and submit a YouTube URL. Jobs run through the same The OpenAI-compatible translation endpoint, API key, and model can be changed in the UI under **OpenAI-Compatible Settings**. Click **Save Settings** to persist them to `.cache/web_settings.json` for future web jobs. Unsaved values in the fields are still used for the next job you start. -You can also upload a local `.mp4` instead of entering a YouTube URL. Uploaded videos are staged under `.cache/uploads` and processed with the same transcription, translation, dubbing, and render pipeline. +You can also upload a local `.mp4` instead of entering a YouTube URL. Uploaded videos are staged under `.cache/uploads` and processed with the same transcription, translation, dubbing, and render pipeline. Restricted YouTube videos can use the **Upload Cookies File** control instead of typing a local cookies path. ### Docker diff --git a/tests/test_web_app.py b/tests/test_web_app.py index 78157ef..6189c83 100644 --- a/tests/test_web_app.py +++ b/tests/test_web_app.py @@ -5,7 +5,13 @@ from __future__ import annotations import sys import web_app -from web_app import build_pipeline_command, create_app, load_translation_settings, save_translation_settings +from web_app import ( + _stage_uploaded_cookies, + build_pipeline_command, + create_app, + load_translation_settings, + save_translation_settings, +) def test_build_pipeline_command_uses_cli_parser_defaults(): @@ -91,3 +97,29 @@ def test_load_translation_settings_uses_env_defaults(tmp_path, monkeypatch): "api_key": "env-key", "model": "env-model", } + + +def test_stage_uploaded_cookies_copies_to_upload_dir(tmp_path, monkeypatch): + upload_dir = tmp_path / "uploads" + source_file = tmp_path / "cookies.txt" + source_file.write_text("# Netscape HTTP Cookie File\n", encoding="utf-8") + monkeypatch.setattr(web_app, "UPLOAD_DIR", upload_dir) + + staged_path = _stage_uploaded_cookies(str(source_file)) + + assert staged_path.endswith(".txt") + assert staged_path != str(source_file) + assert upload_dir in web_app.Path(staged_path).parents + assert web_app.Path(staged_path).read_text(encoding="utf-8") == "# Netscape HTTP Cookie File\n" + + +def test_stage_uploaded_cookies_rejects_unsupported_extension(tmp_path): + source_file = tmp_path / "cookies.json" + source_file.write_text("{}", encoding="utf-8") + + try: + _stage_uploaded_cookies(str(source_file)) + except ValueError as exc: + assert "Expected one of" in str(exc) + else: + raise AssertionError("Expected ValueError for unsupported cookie upload") diff --git a/web_app.py b/web_app.py index 44b7a26..757ecb3 100644 --- a/web_app.py +++ b/web_app.py @@ -179,17 +179,35 @@ def _form_to_cli_args(form: dict[str, str | bool]) -> list[str]: def _stage_uploaded_mp4(uploaded_file: str | None) -> str: + return _stage_uploaded_file(uploaded_file, allowed_suffixes={".mp4"}, fallback_name="upload") + + +def _stage_uploaded_cookies(uploaded_file: str | None) -> str: + return _stage_uploaded_file( + uploaded_file, + allowed_suffixes={".txt", ".cookies", ".cookie"}, + fallback_name="cookies", + ) + + +def _stage_uploaded_file( + uploaded_file: str | None, + allowed_suffixes: set[str], + fallback_name: str, +) -> str: if not uploaded_file: return "" source_path = Path(uploaded_file) - if source_path.suffix.lower() != ".mp4": - raise ValueError("Only MP4 uploads are supported.") + suffix = source_path.suffix.lower() + if suffix not in allowed_suffixes: + expected = ", ".join(sorted(allowed_suffixes)) + raise ValueError(f"Unsupported upload type. Expected one of: {expected}.") if not source_path.exists(): raise FileNotFoundError(f"Uploaded file not found: {source_path}") safe_stem = "".join(char if char.isalnum() or char in {"-", "_"} else "_" for char in source_path.stem) - staged_name = f"{uuid.uuid4().hex[:12]}_{safe_stem or 'upload'}.mp4" + staged_name = f"{uuid.uuid4().hex[:12]}_{safe_stem or fallback_name}{suffix}" UPLOAD_DIR.mkdir(parents=True, exist_ok=True) staged_path = UPLOAD_DIR / staged_name shutil.copy2(source_path, staged_path) @@ -267,7 +285,7 @@ def _start_job( whisper_model: str, mix_mode: str, browser: str, - cookies: str, + cookies_upload: str | None, lmstudio_base_url: str, lmstudio_api_key: str, lmstudio_model: str, @@ -282,6 +300,11 @@ def _start_job( except (OSError, ValueError) as exc: message = str(exc) or "Invalid uploaded MP4." return "", message, message, gr.update(choices=_output_choices()) + try: + cookies = _stage_uploaded_cookies(cookies_upload) + except (OSError, ValueError) as exc: + message = str(exc) or "Invalid uploaded cookies file." + return "", message, message, gr.update(choices=_output_choices()) form = { "url": url, @@ -382,7 +405,11 @@ def create_app() -> gr.Blocks: choices=["", "chrome", "edge", "firefox", "brave"], value="", ) - cookies = gr.Textbox(label="Cookies File", placeholder=r"C:\path\to\cookies.txt") + cookies_upload = gr.File( + label="Upload Cookies File", + file_types=[".txt", ".cookies", ".cookie"], + type="filepath", + ) with gr.Accordion("OpenAI-Compatible Settings", open=False): lmstudio_base_url = gr.Textbox( @@ -432,7 +459,7 @@ def create_app() -> gr.Blocks: whisper_model, mix_mode, browser, - cookies, + cookies_upload, lmstudio_base_url, lmstudio_api_key, lmstudio_model,