From 3e18e7ed87e4d4220ac7f2d9833c3e6f64aad694 Mon Sep 17 00:00:00 2001 From: Palak Sheth Date: Sat, 10 Jan 2026 16:13:48 -0800 Subject: [PATCH 1/8] Add tests for subgen and CI workflow --- .github/workflows/tests.yml | 25 + .../conftest.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 12804 bytes .../test_subgen.cpython-313-pytest-9.0.2.pyc | Bin 0 -> 132047 bytes tests/conftest.py | 233 ++++++ tests/test_subgen.py | 787 ++++++++++++++++++ 5 files changed, 1045 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc create mode 100644 tests/__pycache__/test_subgen.cpython-313-pytest-9.0.2.pyc create mode 100644 tests/conftest.py create mode 100644 tests/test_subgen.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 00000000..9b19850a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,25 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + pytest: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install test dependencies + run: | + python -m pip install --upgrade pip + pip install numpy pytest + + - name: Run tests + run: pytest -q diff --git a/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee293c870f030d23b1e9fa9f7bc4e9970e8bbfc1 GIT binary patch literal 12804 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 fyxW$7L_rc7pg>Y0gvXE^5}1K| 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 U=T*uf+)|Z6Hq)$ literal 0 HcmV?d00001 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..5f173116 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,233 @@ +import importlib +import sys +import types + +import pytest + + +def _install_stub(monkeypatch, name, module): + monkeypatch.setitem(sys.modules, name, module) + + +def _build_fastapi_stub(): + fastapi = types.ModuleType("fastapi") + + class FastAPI: + def get(self, *args, **kwargs): + def decorator(func): + return func + + return decorator + + def post(self, *args, **kwargs): + def decorator(func): + return func + + return decorator + + def _param(*args, **kwargs): + return None + + fastapi.FastAPI = FastAPI + fastapi.File = _param + fastapi.UploadFile = type("UploadFile", (), {}) + fastapi.Query = _param + fastapi.Header = _param + fastapi.Body = _param + fastapi.Form = _param + fastapi.Request = type("Request", (), {}) + + responses = types.ModuleType("fastapi.responses") + + class StreamingResponse: + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + responses.StreamingResponse = StreamingResponse + + return fastapi, responses + + +def _build_watchdog_stub(): + watchdog = types.ModuleType("watchdog") + observers = types.ModuleType("watchdog.observers") + polling = types.ModuleType("watchdog.observers.polling") + events = types.ModuleType("watchdog.events") + + class PollingObserver: + def schedule(self, *args, **kwargs): + return None + + def start(self): + return None + + class FileSystemEventHandler: + pass + + polling.PollingObserver = PollingObserver + observers.polling = polling + events.FileSystemEventHandler = FileSystemEventHandler + watchdog.observers = observers + watchdog.events = events + + return watchdog, observers, polling, events + + +def _build_stable_whisper_stub(): + stable_whisper = types.ModuleType("stable_whisper") + + class Segment: + def __init__(self, start, end, text, words=None, id=0): + self.start = start + self.end = end + self.text = text + self.words = words or [] + self.id = id + + class DummyResult: + def __init__(self, language="en"): + self.language = language + self.segments = [Segment(0, 1, "hello", [], 0)] + self.to_srt_vtt_calls = [] + + def to_srt_vtt(self, filepath=None, word_level=False): + self.to_srt_vtt_calls.append((filepath, word_level)) + return ["dummy"] + + class DummyModel: + def __init__(self, language="en"): + self.language = language + self.model = types.SimpleNamespace(unload_model=lambda: None) + self.transcribe_calls = [] + + def transcribe(self, *args, **kwargs): + self.transcribe_calls.append((args, kwargs)) + return DummyResult(language=self.language) + + def load_faster_whisper(*args, **kwargs): + return DummyModel() + + stable_whisper.Segment = Segment + stable_whisper.DummyResult = DummyResult + stable_whisper.DummyModel = DummyModel + stable_whisper.load_faster_whisper = load_faster_whisper + stable_whisper.__version__ = "0.0" + + return stable_whisper + + +def _build_ffmpeg_stub(): + ffmpeg = types.ModuleType("ffmpeg") + + class Error(Exception): + def __init__(self, message="", stderr=b""): + super().__init__(message) + self.stderr = stderr + + class DummyInput: + def output(self, *args, **kwargs): + return self + + def run(self, *args, **kwargs): + return b"", b"" + + def input_stub(*args, **kwargs): + return DummyInput() + + def probe_stub(*args, **kwargs): + return {"streams": []} + + ffmpeg.Error = Error + ffmpeg.input = input_stub + ffmpeg.probe = probe_stub + + return ffmpeg + + +def _build_av_stub(): + av = types.ModuleType("av") + + class FFmpegError(Exception): + pass + + def open_stub(*args, **kwargs): + raise FFmpegError("av.open stub not configured") + + av.FFmpegError = FFmpegError + av.open = open_stub + + return av + + +def _build_requests_stub(): + requests = types.ModuleType("requests") + + class RequestException(Exception): + pass + + class Exceptions: + pass + + Exceptions.RequestException = RequestException + + def _not_implemented(*args, **kwargs): + raise NotImplementedError("requests stub not configured") + + requests.get = _not_implemented + requests.post = _not_implemented + requests.put = _not_implemented + requests.exceptions = Exceptions + + return requests + + +def _build_torch_stub(): + torch = types.ModuleType("torch") + + class DummyCuda: + @staticmethod + def is_available(): + return False + + @staticmethod + def empty_cache(): + return None + + torch.cuda = DummyCuda + return torch + + +@pytest.fixture +def subgen_module(monkeypatch): + monkeypatch.setenv("CONCURRENT_TRANSCRIPTIONS", "0") + monkeypatch.setenv("MONITOR", "False") + + fastapi, fastapi_responses = _build_fastapi_stub() + watchdog, observers, polling, events = _build_watchdog_stub() + stable_whisper = _build_stable_whisper_stub() + ffmpeg = _build_ffmpeg_stub() + av = _build_av_stub() + requests = _build_requests_stub() + torch = _build_torch_stub() + + _install_stub(monkeypatch, "fastapi", fastapi) + _install_stub(monkeypatch, "fastapi.responses", fastapi_responses) + _install_stub(monkeypatch, "watchdog", watchdog) + _install_stub(monkeypatch, "watchdog.observers", observers) + _install_stub(monkeypatch, "watchdog.observers.polling", polling) + _install_stub(monkeypatch, "watchdog.events", events) + _install_stub(monkeypatch, "stable_whisper", stable_whisper) + _install_stub(monkeypatch, "faster_whisper", types.ModuleType("faster_whisper")) + _install_stub(monkeypatch, "whisper", types.ModuleType("whisper")) + _install_stub(monkeypatch, "ffmpeg", ffmpeg) + _install_stub(monkeypatch, "av", av) + _install_stub(monkeypatch, "requests", requests) + _install_stub(monkeypatch, "torch", torch) + + sys.modules["faster_whisper"].__version__ = "0.0" + + if "subgen" in sys.modules: + del sys.modules["subgen"] + module = importlib.import_module("subgen") + return module diff --git a/tests/test_subgen.py b/tests/test_subgen.py new file mode 100644 index 00000000..67d3066b --- /dev/null +++ b/tests/test_subgen.py @@ -0,0 +1,787 @@ +import asyncio +import io +import json +import os +import types + +import numpy as np + +from language_code import LanguageCode + + +class DummyUploadFile: + def __init__(self, data=b"audio"): + self.file = io.BytesIO(data) + + async def close(self): + return None + + +class AsyncFile: + def __init__(self, data): + self.data = data + self.pos = 0 + + async def seek(self, pos): + self.pos = pos + + async def read(self, length): + chunk = self.data[self.pos : self.pos + length] + self.pos += length + return chunk + + +class DummyResponse: + def __init__(self, status_code=200, content=b""): + self.status_code = status_code + self.content = content + + def raise_for_status(self): + if self.status_code >= 400: + raise Exception(f"HTTP {self.status_code}") + + +class DummyStream: + def __init__(self, metadata=None, stream_type="audio"): + self.metadata = metadata or {} + self.type = stream_type + self.codec_context = types.SimpleNamespace(name="aac") + + +class DummyContainer: + def __init__(self, streams): + self.streams = streams + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc, tb): + return False + + +class DummyStreams: + def __init__(self, subtitles=None, streams=None): + self.subtitles = subtitles or [] + self._streams = streams if streams is not None else [] + + def __iter__(self): + return iter(self._streams) + + +def _configure_no_skip(subgen_module): + subgen_module.transcribe_or_translate = "transcribe" + subgen_module.lrc_for_audio_files = False + subgen_module.skip_unknown_language = False + subgen_module.skip_if_to_transcribe_sub_already_exist = False + subgen_module.skipifinternalsublang = LanguageCode.NONE + subgen_module.skipifexternalsub = False + subgen_module.namesublang = "" + subgen_module.skip_lang_codes_list = [] + subgen_module.limit_to_preferred_audio_languages = False + subgen_module.preferred_audio_languages = [] + subgen_module.skip_if_audio_track_is_in_list = [] + subgen_module.only_skip_if_subgen_subtitle = False + + subgen_module.has_subtitle_language = lambda *args, **kwargs: False + subgen_module.has_subtitle_language_in_file = lambda *args, **kwargs: False + subgen_module.has_subtitle_of_language_in_folder = lambda *args, **kwargs: False + subgen_module.get_subtitle_languages = lambda *args, **kwargs: [] + subgen_module.get_audio_languages = lambda *args, **kwargs: [] + + +def test_convert_to_bool(subgen_module): + convert = subgen_module.convert_to_bool + assert convert("true") + assert convert("On") + assert convert("1") + assert convert("y") + assert convert("yes") + assert not convert("false") + assert not convert("0") + assert not convert(None) + + +def test_get_env_with_fallback(monkeypatch, subgen_module): + monkeypatch.setenv("NEW_NAME", "123") + monkeypatch.setenv("OLD_NAME", "456") + value = subgen_module.get_env_with_fallback( + "NEW_NAME", + "OLD_NAME", + default_value="0", + convert_func=int, + ) + assert value == 123 + + monkeypatch.delenv("NEW_NAME", raising=False) + value = subgen_module.get_env_with_fallback("NEW_NAME", "OLD_NAME", default_value="0") + assert value == "456" + + monkeypatch.delenv("OLD_NAME", raising=False) + value = subgen_module.get_env_with_fallback("NEW_NAME", "OLD_NAME", default_value="fallback") + assert value == "fallback" + + +def test_deduplicated_queue_tracks_processing(subgen_module): + queue = subgen_module.DeduplicatedQueue() + queue.put({"path": "one"}) + queue.put({"path": "one"}) + assert queue.qsize() == 1 + + item = queue.get() + assert item["path"] == "one" + assert queue.get_processing_tasks() == ["one"] + queue.task_done() + assert queue.get_processing_tasks() == [] + + +def test_progress_updates_timestamp(subgen_module): + subgen_module.docker_status = "Docker" + subgen_module.last_print_time = None + subgen_module.progress(1, 10) + assert subgen_module.last_print_time is not None + + +def test_appendLine_adds_segment(subgen_module): + subgen_module.append = True + result = subgen_module.stable_whisper.DummyResult(language="English") + original_len = len(result.segments) + subgen_module.appendLine(result) + assert len(result.segments) == original_len + 1 + assert "Transcribed by whisperAI" in result.segments[-1].text + + +def test_handle_get_request(subgen_module): + response = subgen_module.handle_get_request(None) + assert isinstance(response, set) + assert any("incorrectly via a GET request" in item for item in response) + + +def test_webui_and_status(subgen_module): + response = subgen_module.webui() + assert isinstance(response, set) + assert any("webui for configuration was removed" in item.lower() for item in response) + + status = subgen_module.status() + assert "Subgen" in status["version"] + + +def test_receive_tautulli_webhook_triggers_queue(monkeypatch, subgen_module): + calls = [] + subgen_module.procaddedmedia = True + subgen_module.procmediaonplay = False + monkeypatch.setattr(subgen_module, "gen_subtitles_queue", lambda path, mode: calls.append((path, mode))) + monkeypatch.setattr(subgen_module, "path_mapping", lambda path: f"mapped:{path}") + + result = subgen_module.receive_tautulli_webhook( + source="Tautulli", + event="added", + file="/media/show.mkv", + ) + assert result == "" + assert calls == [("mapped:/media/show.mkv", subgen_module.transcribe_or_translate)] + + +def test_receive_tautulli_webhook_rejects_invalid(subgen_module): + response = subgen_module.receive_tautulli_webhook(source="Other", event="added", file="/media/show.mkv") + assert "properly configured" in response["message"] + + +def test_receive_plex_webhook(monkeypatch, subgen_module): + calls = [] + refresh_calls = [] + subgen_module.procaddedmedia = True + subgen_module.procmediaonplay = False + subgen_module.plex_queue_next_episode = False + subgen_module.plex_queue_season = False + subgen_module.plex_queue_series = False + + monkeypatch.setattr(subgen_module, "get_plex_file_name", lambda *args, **kwargs: "/media/plex.mkv") + monkeypatch.setattr(subgen_module, "refresh_plex_metadata", lambda *args, **kwargs: refresh_calls.append(args)) + monkeypatch.setattr(subgen_module, "gen_subtitles_queue", lambda path, mode: calls.append((path, mode))) + monkeypatch.setattr(subgen_module, "path_mapping", lambda path: path) + + payload = {"event": "library.new", "Metadata": {"ratingKey": "1"}} + result = subgen_module.receive_plex_webhook(user_agent="PlexMediaServer", payload=json.dumps(payload)) + assert result == "" + assert calls == [("/media/plex.mkv", subgen_module.transcribe_or_translate)] + assert refresh_calls + + +def test_receive_plex_webhook_rejects_invalid(subgen_module): + payload = {"event": "library.new", "Metadata": {"ratingKey": "1"}} + response = subgen_module.receive_plex_webhook(user_agent="Other", payload=json.dumps(payload)) + assert "properly configured Plex webhook" in response["message"] + + +def test_receive_jellyfin_webhook(monkeypatch, subgen_module): + calls = [] + subgen_module.procaddedmedia = True + subgen_module.procmediaonplay = False + monkeypatch.setattr(subgen_module, "get_jellyfin_file_name", lambda *args, **kwargs: "/media/jellyfin.mkv") + monkeypatch.setattr(subgen_module, "refresh_jellyfin_metadata", lambda *args, **kwargs: None) + monkeypatch.setattr(subgen_module, "gen_subtitles_queue", lambda path, mode: calls.append((path, mode))) + monkeypatch.setattr(subgen_module, "path_mapping", lambda path: path) + + result = subgen_module.receive_jellyfin_webhook( + user_agent="Jellyfin-Server", + NotificationType="ItemAdded", + file=None, + ItemId="abc", + ) + assert result == "" + assert calls == [("/media/jellyfin.mkv", subgen_module.transcribe_or_translate)] + + +def test_receive_jellyfin_webhook_rejects_invalid(subgen_module): + response = subgen_module.receive_jellyfin_webhook( + user_agent="Other", + NotificationType="ItemAdded", + file=None, + ItemId="abc", + ) + assert "properly configured Jellyfin webhook" in response["message"] + + +def test_receive_emby_webhook(monkeypatch, subgen_module): + calls = [] + subgen_module.procaddedmedia = True + subgen_module.procmediaonplay = False + monkeypatch.setattr(subgen_module, "gen_subtitles_queue", lambda path, mode: calls.append((path, mode))) + monkeypatch.setattr(subgen_module, "path_mapping", lambda path: path) + + test_payload = {"Event": "system.notificationtest"} + response = subgen_module.receive_emby_webhook(user_agent=None, data=json.dumps(test_payload)) + assert "Notification test received" in response["message"] + + payload = {"Event": "library.new", "Item": {"Path": "/media/emby.mkv"}} + result = subgen_module.receive_emby_webhook(user_agent=None, data=json.dumps(payload)) + assert result == "" + assert calls == [("/media/emby.mkv", subgen_module.transcribe_or_translate)] + + +def test_batch_calls_transcribe_existing(monkeypatch, subgen_module): + calls = [] + monkeypatch.setattr(subgen_module, "transcribe_existing", lambda path, lang: calls.append((path, lang))) + subgen_module.batch(directory="/media", forceLanguage="eng") + assert calls == [("/media", LanguageCode.ENGLISH)] + + +def test_asr_success(monkeypatch, subgen_module): + monkeypatch.setattr(subgen_module, "delete_model", lambda: None) + subgen_module.append = False + upload = DummyUploadFile(b"audio") + response = asyncio.run(subgen_module.asr(task="transcribe", audio_file=upload, encode=True, output="srt")) + assert isinstance(response, subgen_module.StreamingResponse) + assert "Source" in response.kwargs["headers"] + + +def test_detect_language_forced(subgen_module): + subgen_module.force_detected_language_to = LanguageCode.ENGLISH + upload = DummyUploadFile(b"audio") + result = asyncio.run(subgen_module.detect_language(audio_file=upload, encode=True)) + assert result["language_code"] == "en" + + +def test_detect_language_updates_window(monkeypatch, subgen_module): + subgen_module.force_detected_language_to = LanguageCode.NONE + subgen_module.model = subgen_module.stable_whisper.DummyModel(language="English") + monkeypatch.setattr(subgen_module, "start_model", lambda: None) + monkeypatch.setattr(subgen_module, "delete_model", lambda: None) + monkeypatch.setattr(subgen_module, "extract_audio_segment_to_memory", lambda *args, **kwargs: io.BytesIO(b"data")) + upload = DummyUploadFile(b"audio") + + result = asyncio.run( + subgen_module.detect_language( + audio_file=upload, + encode=True, + detect_lang_length=10, + detect_lang_offset=2, + ) + ) + assert result["language_code"] == "en" + assert subgen_module.detect_language_length == 10 + assert subgen_module.detect_language_offset == 2 + + +def test_get_audio_chunk_returns_audio(subgen_module): + data = np.array([0, 1, 2, 3], dtype=np.int16).tobytes() + file_obj = AsyncFile(data) + result = asyncio.run(subgen_module.get_audio_chunk(file_obj, offset=1, length=1, sample_rate=2)) + assert isinstance(result, np.ndarray) + assert result.shape[0] == 2 + + +def test_detect_language_task_queues(monkeypatch, subgen_module): + queued = [] + subgen_module.model = subgen_module.stable_whisper.DummyModel(language="English") + monkeypatch.setattr(subgen_module, "start_model", lambda: None) + monkeypatch.setattr(subgen_module, "delete_model", lambda: None) + monkeypatch.setattr(subgen_module, "extract_audio_segment_to_memory", lambda *args, **kwargs: io.BytesIO(b"data")) + monkeypatch.setattr(subgen_module.task_queue, "task_done", lambda: None) + monkeypatch.setattr(subgen_module.task_queue, "put", lambda item: queued.append(item)) + + subgen_module.detect_language_task("/media/file.mkv") + assert queued + assert queued[-1]["path"] == "/media/file.mkv" + assert queued[-1]["force_language"] == LanguageCode.ENGLISH + + +def test_extract_audio_segment_to_memory_errors(monkeypatch, subgen_module): + assert subgen_module.extract_audio_segment_to_memory(123, 0, 1) is None + + class DummyInput: + def output(self, *args, **kwargs): + return self + + def run(self, *args, **kwargs): + return b"data", b"" + + monkeypatch.setattr(subgen_module.ffmpeg, "input", lambda *args, **kwargs: DummyInput()) + result = subgen_module.extract_audio_segment_to_memory("/media/file.mkv", 0, 1) + assert isinstance(result, io.BytesIO) + + +def test_start_model_and_delete_model(monkeypatch, subgen_module): + dummy_model = subgen_module.stable_whisper.DummyModel(language="English") + monkeypatch.setattr(subgen_module.stable_whisper, "load_faster_whisper", lambda *args, **kwargs: dummy_model) + monkeypatch.setattr(subgen_module.task_queue, "is_idle", lambda: True) + monkeypatch.setattr(subgen_module.gc, "collect", lambda: None) + monkeypatch.setattr(subgen_module.ctypes.util, "find_library", lambda name: "c") + monkeypatch.setattr(subgen_module.ctypes, "CDLL", lambda name: types.SimpleNamespace(malloc_trim=lambda x: None)) + subgen_module.transcribe_device = "cpu" + subgen_module.model = None + + subgen_module.start_model() + assert subgen_module.model is dummy_model + + subgen_module.delete_model() + assert subgen_module.model is None + + +def test_isAudioFileExtension_and_extensions(subgen_module): + assert subgen_module.isAudioFileExtension(".mp3") + assert subgen_module.has_audio_extension("track.m4a") + assert subgen_module.has_video_extension("movie.mkv") + assert not subgen_module.has_video_extension("movie.txt") + + +def test_write_lrc(tmp_path, subgen_module): + result = subgen_module.stable_whisper.DummyResult(language="English") + result.segments = [subgen_module.stable_whisper.Segment(65.12, 70.0, "Hello\nWorld", [], 0)] + path = tmp_path / "track.lrc" + subgen_module.write_lrc(result, str(path)) + content = path.read_text() + assert "[01:05.12]HelloWorld" in content + + +def test_gen_subtitles_audio_path(monkeypatch, subgen_module): + calls = [] + subgen_module.append = False + subgen_module.lrc_for_audio_files = True + monkeypatch.setattr(subgen_module, "start_model", lambda: None) + subgen_module.model = subgen_module.stable_whisper.DummyModel(language="English") + monkeypatch.setattr(subgen_module, "handle_multiple_audio_tracks", lambda *args, **kwargs: None) + monkeypatch.setattr(subgen_module, "isAudioFileExtension", lambda ext: True) + monkeypatch.setattr(subgen_module, "write_lrc", lambda result, path: calls.append(path)) + monkeypatch.setattr(subgen_module, "delete_model", lambda: None) + + subgen_module.gen_subtitles("/media/track.mp3", "transcribe", LanguageCode.NONE) + assert calls == ["/media/track.lrc"] + + +def test_gen_subtitles_video_path(monkeypatch, subgen_module): + subgen_module.append = False + subgen_module.lrc_for_audio_files = False + monkeypatch.setattr(subgen_module, "start_model", lambda: None) + model = subgen_module.stable_whisper.DummyModel(language="English") + subgen_module.model = model + monkeypatch.setattr(subgen_module, "handle_multiple_audio_tracks", lambda *args, **kwargs: io.BytesIO(b"data")) + monkeypatch.setattr(subgen_module, "isAudioFileExtension", lambda ext: False) + monkeypatch.setattr(subgen_module, "name_subtitle", lambda path, lang: "/media/track.en.srt") + monkeypatch.setattr(subgen_module, "delete_model", lambda: None) + + subgen_module.gen_subtitles("/media/track.mkv", "transcribe", LanguageCode.NONE) + assert model.transcribe_calls + assert model.transcribe_calls[0][1]["task"] == "transcribe" + + +def test_define_subtitle_language_naming(subgen_module): + subgen_module.namesublang = "custom" + assert subgen_module.define_subtitle_language_naming(LanguageCode.FRENCH, "ISO_639_1") == "custom" + + subgen_module.namesublang = "" + subgen_module.transcribe_or_translate = "translate" + assert subgen_module.define_subtitle_language_naming(LanguageCode.FRENCH, "ISO_639_1") == "fr" + + +def test_name_subtitle(subgen_module): + subgen_module.show_in_subname_subgen = True + subgen_module.show_in_subname_model = True + subgen_module.whisper_model = "tiny" + subgen_module.subtitle_language_naming_type = "ISO_639_1" + name = subgen_module.name_subtitle("/media/show.mkv", LanguageCode.ENGLISH) + assert name.endswith(".subgen.tiny.en.srt") + + +def test_handle_multiple_audio_tracks(monkeypatch, subgen_module): + tracks = [ + {"index": 0, "language": LanguageCode.FRENCH, "codec": "aac", "default": True}, + {"index": 1, "language": LanguageCode.ENGLISH, "codec": "aac", "default": False}, + ] + monkeypatch.setattr(subgen_module, "get_audio_tracks", lambda path: tracks) + monkeypatch.setattr(subgen_module, "get_audio_track_by_language", lambda tracks, language: tracks[1]) + monkeypatch.setattr(subgen_module, "extract_audio_track_to_memory", lambda *args, **kwargs: io.BytesIO(b"data")) + result = subgen_module.handle_multiple_audio_tracks("/media/show.mkv", LanguageCode.ENGLISH) + assert isinstance(result, io.BytesIO) + + monkeypatch.setattr(subgen_module, "get_audio_tracks", lambda path: tracks[:1]) + result = subgen_module.handle_multiple_audio_tracks("/media/show.mkv", LanguageCode.ENGLISH) + assert result is None + + +def test_extract_audio_track_to_memory(subgen_module, monkeypatch): + assert subgen_module.extract_audio_track_to_memory("/media/show.mkv", None) is None + + def raise_error(*args, **kwargs): + raise subgen_module.ffmpeg.Error("ffmpeg failed") + + monkeypatch.setattr(subgen_module.ffmpeg, "input", raise_error) + assert subgen_module.extract_audio_track_to_memory("/media/show.mkv", 0) is None + + +def test_get_audio_track_by_language(subgen_module): + tracks = [ + {"language": LanguageCode.FRENCH}, + {"language": LanguageCode.ENGLISH}, + ] + assert subgen_module.get_audio_track_by_language(tracks, LanguageCode.ENGLISH) == tracks[1] + assert subgen_module.get_audio_track_by_language(tracks, LanguageCode.SPANISH) is None + + +def test_choose_transcribe_language(monkeypatch, subgen_module): + forced = subgen_module.choose_transcribe_language("/media/file.mkv", LanguageCode.FRENCH) + assert forced == LanguageCode.FRENCH + + subgen_module.force_detected_language_to = LanguageCode.ENGLISH + result = subgen_module.choose_transcribe_language("/media/file.mkv", LanguageCode.NONE) + assert result == LanguageCode.ENGLISH + + subgen_module.force_detected_language_to = LanguageCode.NONE + monkeypatch.setattr(subgen_module, "get_audio_tracks", lambda path: []) + monkeypatch.setattr(subgen_module, "find_language_audio_track", lambda tracks, languages: LanguageCode.SPANISH) + subgen_module.preferred_audio_languages = [LanguageCode.SPANISH] + result = subgen_module.choose_transcribe_language("/media/file.mkv", LanguageCode.NONE) + assert result == LanguageCode.SPANISH + + +def test_get_audio_tracks(monkeypatch, subgen_module): + def probe_stub(*args, **kwargs): + return { + "streams": [ + { + "index": 0, + "codec_name": "aac", + "channels": 2, + "tags": {"language": "eng", "title": "English"}, + "disposition": {"default": 1, "forced": 0, "original": 1}, + }, + { + "index": 1, + "codec_name": "aac", + "channels": 2, + "tags": {"language": "fra", "title": "French commentary"}, + "disposition": {"default": 0, "forced": 0, "original": 0}, + }, + ] + } + + monkeypatch.setattr(subgen_module.ffmpeg, "probe", probe_stub) + tracks = subgen_module.get_audio_tracks("/media/file.mkv") + assert tracks[0]["language"] == LanguageCode.ENGLISH + assert tracks[1]["commentary"] is True + + +def test_find_language_audio_track(subgen_module): + tracks = [{"language": LanguageCode.ENGLISH}] + result = subgen_module.find_language_audio_track(tracks, [LanguageCode.SPANISH, LanguageCode.ENGLISH]) + assert result == LanguageCode.ENGLISH + + +def test_find_default_audio_track_language(subgen_module): + tracks = [ + {"language": LanguageCode.ENGLISH, "default": False}, + {"language": LanguageCode.FRENCH, "default": True}, + ] + assert subgen_module.find_default_audio_track_language(tracks) == LanguageCode.FRENCH + + +def test_gen_subtitles_queue_skips(monkeypatch, subgen_module): + monkeypatch.setattr(subgen_module, "has_audio", lambda path: False) + monkeypatch.setattr(subgen_module.task_queue, "put", lambda item: None) + subgen_module.gen_subtitles_queue("/media/file.mkv", "transcribe", LanguageCode.NONE) + + +def test_gen_subtitles_queue_detect_language(monkeypatch, subgen_module): + queued = [] + monkeypatch.setattr(subgen_module, "has_audio", lambda path: True) + monkeypatch.setattr(subgen_module, "choose_transcribe_language", lambda path, language: LanguageCode.NONE) + monkeypatch.setattr(subgen_module, "should_skip_file", lambda *args, **kwargs: False) + subgen_module.should_whiser_detect_audio_language = True + monkeypatch.setattr(subgen_module.task_queue, "put", lambda item: queued.append(item)) + + subgen_module.gen_subtitles_queue("/media/file.mkv", "transcribe", LanguageCode.NONE) + assert queued[0]["type"] == "detect_language" + + +def test_gen_subtitles_queue_normal(monkeypatch, subgen_module): + queued = [] + monkeypatch.setattr(subgen_module, "has_audio", lambda path: True) + monkeypatch.setattr(subgen_module, "choose_transcribe_language", lambda path, language: LanguageCode.ENGLISH) + monkeypatch.setattr(subgen_module, "should_skip_file", lambda *args, **kwargs: False) + subgen_module.should_whiser_detect_audio_language = False + monkeypatch.setattr(subgen_module.task_queue, "put", lambda item: queued.append(item)) + + subgen_module.gen_subtitles_queue("/media/file.mkv", "transcribe", LanguageCode.NONE) + assert queued[0]["force_language"] == LanguageCode.ENGLISH + + +def test_should_skip_file_lrc_exists(tmp_path, subgen_module): + _configure_no_skip(subgen_module) + subgen_module.lrc_for_audio_files = True + subgen_module.isAudioFileExtension = lambda ext: True + lrc_path = tmp_path / "track.lrc" + lrc_path.write_text("test") + result = subgen_module.should_skip_file(str(tmp_path / "track.mp3"), LanguageCode.ENGLISH) + assert result is True + + +def test_should_skip_file_unknown_language(subgen_module): + _configure_no_skip(subgen_module) + subgen_module.skip_unknown_language = True + assert subgen_module.should_skip_file("/media/file.mkv", LanguageCode.NONE) is True + + +def test_should_skip_file_existing_subtitles(monkeypatch, subgen_module): + _configure_no_skip(subgen_module) + subgen_module.skip_if_to_transcribe_sub_already_exist = True + monkeypatch.setattr(subgen_module, "has_subtitle_language", lambda *args, **kwargs: True) + assert subgen_module.should_skip_file("/media/file.mkv", LanguageCode.ENGLISH) is True + + +def test_should_skip_file_internal_subtitles(monkeypatch, subgen_module): + _configure_no_skip(subgen_module) + subgen_module.skipifinternalsublang = LanguageCode.ENGLISH + monkeypatch.setattr(subgen_module, "has_subtitle_language_in_file", lambda *args, **kwargs: True) + assert subgen_module.should_skip_file("/media/file.mkv", LanguageCode.FRENCH) is True + + +def test_should_skip_file_external_subtitles(monkeypatch, subgen_module): + _configure_no_skip(subgen_module) + subgen_module.skipifexternalsub = True + subgen_module.namesublang = "eng" + monkeypatch.setattr(subgen_module, "has_subtitle_of_language_in_folder", lambda *args, **kwargs: True) + assert subgen_module.should_skip_file("/media/file.mkv", LanguageCode.FRENCH) is True + + +def test_should_skip_file_skip_lang_codes(monkeypatch, subgen_module): + _configure_no_skip(subgen_module) + subgen_module.skip_lang_codes_list = [LanguageCode.ENGLISH] + monkeypatch.setattr(subgen_module, "get_subtitle_languages", lambda *args, **kwargs: [LanguageCode.ENGLISH]) + assert subgen_module.should_skip_file("/media/file.mkv", LanguageCode.FRENCH) is True + + +def test_should_skip_file_preferred_audio(monkeypatch, subgen_module): + _configure_no_skip(subgen_module) + subgen_module.limit_to_preferred_audio_languages = True + subgen_module.preferred_audio_languages = [LanguageCode.ENGLISH] + monkeypatch.setattr(subgen_module, "get_audio_languages", lambda *args, **kwargs: [LanguageCode.FRENCH]) + assert subgen_module.should_skip_file("/media/file.mkv", LanguageCode.FRENCH) is True + + +def test_should_skip_file_skip_audio(monkeypatch, subgen_module): + _configure_no_skip(subgen_module) + subgen_module.skip_if_audio_track_is_in_list = [LanguageCode.ENGLISH] + monkeypatch.setattr(subgen_module, "get_audio_languages", lambda *args, **kwargs: [LanguageCode.ENGLISH]) + assert subgen_module.should_skip_file("/media/file.mkv", LanguageCode.FRENCH) is True + + +def test_should_skip_file_none(monkeypatch, subgen_module): + _configure_no_skip(subgen_module) + monkeypatch.setattr(subgen_module, "get_audio_languages", lambda *args, **kwargs: []) + assert subgen_module.should_skip_file("/media/file.mkv", LanguageCode.FRENCH) is False + + +def test_get_subtitle_languages(monkeypatch, subgen_module): + subtitles = [ + DummyStream(metadata={"language": "eng"}, stream_type="subtitle"), + DummyStream(metadata={}, stream_type="subtitle"), + ] + streams = DummyStreams(subtitles=subtitles) + monkeypatch.setattr(subgen_module.av, "open", lambda *args, **kwargs: DummyContainer(streams)) + languages = subgen_module.get_subtitle_languages("/media/file.mkv") + assert languages == [LanguageCode.ENGLISH, LanguageCode.NONE] + + +def test_has_subtitle_language_in_file(monkeypatch, subgen_module): + subtitles = [DummyStream(metadata={"language": "eng"}, stream_type="subtitle")] + streams = DummyStreams(streams=subtitles) + monkeypatch.setattr(subgen_module.av, "open", lambda *args, **kwargs: DummyContainer(streams)) + + subgen_module.skip_if_language_is_not_set_but_subtitles_exist = True + assert subgen_module.has_subtitle_language_in_file("/media/file.mkv", LanguageCode.NONE) is True + + subgen_module.skip_if_language_is_not_set_but_subtitles_exist = False + subgen_module.only_skip_if_subgen_subtitle = True + assert subgen_module.has_subtitle_language_in_file("/media/file.mkv", LanguageCode.NONE) is False + + subgen_module.only_skip_if_subgen_subtitle = False + assert subgen_module.has_subtitle_language_in_file("/media/file.mkv", LanguageCode.ENGLISH) is True + + +def test_has_subtitle_of_language_in_folder(tmp_path, subgen_module): + video_path = tmp_path / "movie.mkv" + video_path.write_text("video") + + (tmp_path / "movie.subgen.srt").write_text("sub") + assert subgen_module.has_subtitle_of_language_in_folder( + str(video_path), + LanguageCode.NONE, + recursion=False, + only_skip_if_subgen_subtitle=True, + ) is False + + (tmp_path / "movie.en.srt").write_text("sub") + assert subgen_module.has_subtitle_of_language_in_folder( + str(video_path), + LanguageCode.ENGLISH, + recursion=False, + only_skip_if_subgen_subtitle=True, + ) is False + + (tmp_path / "movie.subgen.en.srt").write_text("sub") + assert subgen_module.has_subtitle_of_language_in_folder( + str(video_path), + LanguageCode.ENGLISH, + recursion=False, + only_skip_if_subgen_subtitle=True, + ) is True + + +def test_is_valid_subtitle_language(subgen_module): + assert subgen_module.is_valid_subtitle_language(["eng"], LanguageCode.ENGLISH) + assert not subgen_module.is_valid_subtitle_language(["fra"], LanguageCode.ENGLISH) + + +def test_get_next_plex_episode(monkeypatch, subgen_module): + metadata_xml = b""" + + + """ + seasons_xml = b""" + + + + """ + episodes_xml = b""" + + + """ + + responses = [ + DummyResponse(200, metadata_xml), + DummyResponse(200, seasons_xml), + DummyResponse(200, episodes_xml), + ] + + def get_stub(*args, **kwargs): + return responses.pop(0) + + monkeypatch.setattr(subgen_module.requests, "get", get_stub) + result = subgen_module.get_next_plex_episode("1", stay_in_season=True) + assert result == "2" + + +def test_get_plex_file_name_and_refresh(monkeypatch, subgen_module): + xml = b"""""" + monkeypatch.setattr(subgen_module.requests, "get", lambda *args, **kwargs: DummyResponse(200, xml)) + file_name = subgen_module.get_plex_file_name("1", "http://plex", "token") + assert file_name == "/media/show.mkv" + + monkeypatch.setattr(subgen_module.requests, "put", lambda *args, **kwargs: DummyResponse(200, b"")) + subgen_module.refresh_plex_metadata("1", "http://plex", "token") + + +def test_refresh_jellyfin_and_get_file_name(monkeypatch, subgen_module): + users = [{"Id": "admin", "Policy": {"IsAdministrator": True}}] + users_payload = json.dumps(users).encode("utf-8") + file_payload = b"{\"Path\": \"/media/jellyfin.mkv\"}" + + def get_stub(url, *args, **kwargs): + if url.endswith("/Users"): + return DummyResponse(200, users_payload) + return DummyResponse(200, file_payload) + + monkeypatch.setattr(subgen_module.requests, "get", get_stub) + monkeypatch.setattr(subgen_module.requests, "post", lambda *args, **kwargs: DummyResponse(204, b"")) + subgen_module.refresh_jellyfin_metadata("1", "http://jellyfin", "token") + + file_name = subgen_module.get_jellyfin_file_name("1", "http://jellyfin", "token") + assert file_name == "/media/jellyfin.mkv" + + +def test_get_jellyfin_admin(subgen_module): + users = [ + {"Id": "user", "Policy": {"IsAdministrator": False}}, + {"Id": "admin", "Policy": {"IsAdministrator": True}}, + ] + assert subgen_module.get_jellyfin_admin(users) == "admin" + + +def test_has_audio(monkeypatch, subgen_module, tmp_path): + file_path = tmp_path / "audio.mkv" + file_path.write_text("data") + subgen_module.is_valid_path = lambda path: True + monkeypatch.setattr(subgen_module, "has_video_extension", lambda path: True) + monkeypatch.setattr(subgen_module, "has_audio_extension", lambda path: False) + streams = DummyStreams(streams=[DummyStream(stream_type="audio")]) + monkeypatch.setattr(subgen_module.av, "open", lambda *args, **kwargs: DummyContainer(streams)) + assert subgen_module.has_audio(str(file_path)) is True + + +def test_is_valid_path(tmp_path, subgen_module): + file_path = tmp_path / "file.txt" + file_path.write_text("data") + assert subgen_module.is_valid_path(str(file_path)) is True + assert subgen_module.is_valid_path(str(tmp_path)) is False + assert subgen_module.is_valid_path(str(tmp_path / "missing.txt")) is False + + +def test_path_mapping(subgen_module): + subgen_module.use_path_mapping = True + subgen_module.path_mapping_from = "/tv" + subgen_module.path_mapping_to = "/media/tv" + assert subgen_module.path_mapping("/tv/show.mkv") == "/media/tv/show.mkv" + + subgen_module.use_path_mapping = False + assert subgen_module.path_mapping("/tv/show.mkv") == "/tv/show.mkv" + + +def test_is_file_stable(tmp_path, subgen_module): + file_path = tmp_path / "stable.txt" + file_path.write_text("data") + assert subgen_module.is_file_stable(str(file_path), wait_time=0, check_intervals=2) is True + assert subgen_module.is_file_stable(str(tmp_path / "missing.txt"), wait_time=0, check_intervals=1) is False + + +def test_transcribe_existing(monkeypatch, subgen_module, tmp_path): + file_path = tmp_path / "audio.mkv" + file_path.write_text("data") + calls = [] + monkeypatch.setattr(subgen_module, "has_audio", lambda path: True) + monkeypatch.setattr(subgen_module, "gen_subtitles_queue", lambda path, mode, lang=None: calls.append((path, mode, lang))) + + subgen_module.transcribe_existing(str(file_path), LanguageCode.ENGLISH) + assert calls From b05c3625c6b57f0e00903c670c2b2b275d400368 Mon Sep 17 00:00:00 2001 From: Palak Sheth Date: Sat, 10 Jan 2026 16:14:42 -0800 Subject: [PATCH 2/8] Ignore pytest cache files --- .gitignore | 4 +++- .../conftest.cpython-313-pytest-9.0.2.pyc | Bin 12804 -> 0 bytes .../test_subgen.cpython-313-pytest-9.0.2.pyc | Bin 132047 -> 0 bytes 3 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc delete mode 100644 tests/__pycache__/test_subgen.cpython-313-pytest-9.0.2.pyc diff --git a/.gitignore b/.gitignore index f7ddb6eb..fbaae841 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ #ignore our settings subgen.env -models/ \ No newline at end of file +models/ +__pycache__/ +*.pyc diff --git a/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc b/tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc deleted file mode 100644 index ee293c870f030d23b1e9fa9f7bc4e9970e8bbfc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12804 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 fyxW$7L_rc7pg>Y0gvXE^5}1K| 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 U=T*uf+)|Z6Hq)$ From 4465e019006449a7f769e5b1e37ccfbf7488b6ad Mon Sep 17 00:00:00 2001 From: Palak Sheth Date: Sat, 10 Jan 2026 16:16:01 -0800 Subject: [PATCH 3/8] Add coverage reporting to CI --- .github/workflows/tests.yml | 10 ++++++++-- .gitignore | 3 +++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9b19850a..4c3cc07f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,13 @@ jobs: - name: Install test dependencies run: | python -m pip install --upgrade pip - pip install numpy pytest + pip install numpy pytest pytest-cov - name: Run tests - run: pytest -q + run: pytest -q --cov=subgen --cov-report=term-missing --cov-report=xml + + - name: Upload coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage.xml diff --git a/.gitignore b/.gitignore index fbaae841..b482908e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ subgen.env models/ __pycache__/ *.pyc +.coverage +coverage.xml +htmlcov/ From 7bc5570ade11b772fea9fceec874f31052eb70bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:16:57 +0000 Subject: [PATCH 4/8] Initial plan From cf975d60b1f23946eff39d95c9601a3ad2da94a5 Mon Sep 17 00:00:00 2001 From: Palak Sheth Date: Sat, 10 Jan 2026 16:18:51 -0800 Subject: [PATCH 5/8] Update tests/test_subgen.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- tests/test_subgen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_subgen.py b/tests/test_subgen.py index 67d3066b..42743136 100644 --- a/tests/test_subgen.py +++ b/tests/test_subgen.py @@ -784,4 +784,4 @@ def test_transcribe_existing(monkeypatch, subgen_module, tmp_path): monkeypatch.setattr(subgen_module, "gen_subtitles_queue", lambda path, mode, lang=None: calls.append((path, mode, lang))) subgen_module.transcribe_existing(str(file_path), LanguageCode.ENGLISH) - assert calls + assert calls == [(str(file_path), subgen_module.transcribe_or_translate, LanguageCode.ENGLISH)] From 46bf2e8cc197715a7f36053b0423dbc2bf2534fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:19:06 +0000 Subject: [PATCH 6/8] Initial plan From 5e0cc7acfc9566b16d7a1b1a86d1787a22428b14 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:20:40 +0000 Subject: [PATCH 7/8] Fix failing tests by adding pytest.ini with pythonpath config Co-authored-by: palakpsheth <13122115+palakpsheth@users.noreply.github.com> --- pytest.ini | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 pytest.ini diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..a635c5c0 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +pythonpath = . From bc3914b1d7115689a9d466702985487f1e5fc35d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 11 Jan 2026 00:21:31 +0000 Subject: [PATCH 8/8] Apply review feedback: fix fixture ordering and remove unused import Co-authored-by: palakpsheth <13122115+palakpsheth@users.noreply.github.com> --- tests/test_subgen.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_subgen.py b/tests/test_subgen.py index 42743136..8025dc1f 100644 --- a/tests/test_subgen.py +++ b/tests/test_subgen.py @@ -1,7 +1,6 @@ import asyncio import io import json -import os import types import numpy as np @@ -439,7 +438,7 @@ def test_handle_multiple_audio_tracks(monkeypatch, subgen_module): assert result is None -def test_extract_audio_track_to_memory(subgen_module, monkeypatch): +def test_extract_audio_track_to_memory(monkeypatch, subgen_module): assert subgen_module.extract_audio_track_to_memory("/media/show.mkv", None) is None def raise_error(*args, **kwargs): @@ -740,7 +739,7 @@ def test_get_jellyfin_admin(subgen_module): assert subgen_module.get_jellyfin_admin(users) == "admin" -def test_has_audio(monkeypatch, subgen_module, tmp_path): +def test_has_audio(monkeypatch, tmp_path, subgen_module): file_path = tmp_path / "audio.mkv" file_path.write_text("data") subgen_module.is_valid_path = lambda path: True