diff --git a/test/services/test_webui_task.py b/test/services/test_webui_task.py index 4138493d5..6369db98e 100644 --- a/test/services/test_webui_task.py +++ b/test/services/test_webui_task.py @@ -156,6 +156,7 @@ def test_completed_task_renders_subject_named_video_download( selected_nodes = [] target_names = { "_DOWNLOAD_FILENAME_INVALID_PATTERN", + "_WINDOWS_RESERVED_FILENAMES", "_build_video_download_name", "_normalize_task_state", "_render_generation_task_snapshot", diff --git a/test/services/test_webui_task_history.py b/test/services/test_webui_task_history.py index 56b33a820..5f1401892 100644 --- a/test/services/test_webui_task_history.py +++ b/test/services/test_webui_task_history.py @@ -16,6 +16,7 @@ TASK_HISTORY_CONSTANTS = { "_FINAL_VIDEO_PATTERN", "_DOWNLOAD_FILENAME_INVALID_PATTERN", + "_WINDOWS_RESERVED_FILENAMES", "VOICE_MODE_TTS", "VOICE_MODE_UPLOAD", "VOICE_MODE_NONE", @@ -90,6 +91,12 @@ def test_build_video_download_name_handles_empty_and_long_subjects(): assert len(build_video_download_name("a" * 100, 1, 1)) == 84 +def test_build_video_download_name_avoids_windows_reserved_names(): + assert build_video_download_name("CON", 1, 1) == "_CON.mp4" + assert build_video_download_name("aux.extra", 1, 1) == "_aux.extra.mp4" + assert build_video_download_name("lpt1", 1, 1) == "_lpt1.mp4" + + def test_restore_requirements_block_missing_uploaded_files(): params = { "video_source": "local", diff --git a/webui/Main.py b/webui/Main.py index c20dddbfa..c092bdc82 100644 --- a/webui/Main.py +++ b/webui/Main.py @@ -138,6 +138,11 @@ re.IGNORECASE, ) _DOWNLOAD_FILENAME_INVALID_PATTERN = re.compile(r'[<>:"/\\|?*\x00-\x1f]') +_WINDOWS_RESERVED_FILENAMES = frozenset( + {"CON", "PRN", "AUX", "NUL"} + | {f"COM{number}" for number in range(1, 10)} + | {f"LPT{number}" for number in range(1, 10)} +) _RUNTIME_CONFIG_SECTIONS = { "app": config.app, "azure": config.azure, @@ -990,6 +995,8 @@ def _build_video_download_name(subject, index, total): safe_subject = re.sub(r"\s+", " ", safe_subject).strip(" .")[:80].rstrip(" .") if not safe_subject: safe_subject = "video" + if safe_subject.split(".", 1)[0].upper() in _WINDOWS_RESERVED_FILENAMES: + safe_subject = f"_{safe_subject}" suffix = f"-{index}" if total > 1 else "" return f"{safe_subject}{suffix}.mp4"