| from __future__ import annotations |
|
|
| import json |
| import wave |
| from pathlib import Path |
|
|
| import pytest |
|
|
| from src.jobs import JobPaths |
| from src.longform import ( |
| CHUNK_MODE_AUTO, |
| CHUNK_MODE_FIXED, |
| RANGE_MODE_CUSTOM, |
| RANGE_MODE_PREVIEW, |
| build_long_form_plan, |
| ) |
| from src.media import prepare_inputs, probe_duration |
|
|
|
|
| def test_preview_range_is_clamped_per_input(): |
| plan = build_long_form_plan( |
| [100.0, 50.0], |
| range_mode=RANGE_MODE_PREVIEW, |
| range_start_seconds=40, |
| preview_seconds=30, |
| chunk_mode=CHUNK_MODE_AUTO, |
| ) |
| assert plan.total_selected_seconds == 40.0 |
| assert plan.input_ranges[0]["end_seconds"] == 70.0 |
| assert plan.input_ranges[1]["end_seconds"] == 50.0 |
| assert plan.input_ranges[1]["source_end_clamped"] is True |
| assert plan.resolved_chunk_seconds == 0 |
|
|
|
|
| def test_custom_range_rejects_empty_selection(): |
| with pytest.raises(ValueError, match="outside input"): |
| build_long_form_plan( |
| [20.0], |
| range_mode=RANGE_MODE_CUSTOM, |
| range_start_seconds=20, |
| range_end_seconds=25, |
| ) |
|
|
|
|
| def test_auto_chunk_policy_uses_5_or_10_minute_chunks(): |
| medium = build_long_form_plan([1800], chunk_mode=CHUNK_MODE_AUTO, model_count=1) |
| long_single = build_long_form_plan([7200], chunk_mode=CHUNK_MODE_AUTO, model_count=1) |
| long_ensemble = build_long_form_plan([7200], chunk_mode=CHUNK_MODE_AUTO, model_count=2) |
| assert medium.resolved_chunk_seconds == 300 |
| assert long_single.resolved_chunk_seconds == 600 |
| assert long_ensemble.resolved_chunk_seconds == 300 |
| assert long_single.chunk_count_total == 12 |
|
|
|
|
| def test_fixed_chunk_rejects_unknown_duration(): |
| with pytest.raises(ValueError, match="120, 300, or 600"): |
| build_long_form_plan([3600], chunk_mode=CHUNK_MODE_FIXED, fixed_chunk_seconds=240) |
|
|
|
|
| def _write_silence(path: Path, seconds: int = 4) -> None: |
| with wave.open(str(path), "wb") as handle: |
| handle.setnchannels(2) |
| handle.setsampwidth(2) |
| handle.setframerate(44100) |
| handle.writeframes(b"\x00\x00\x00\x00" * 44100 * seconds) |
|
|
|
|
| def test_prepare_inputs_extracts_selected_range_before_gpu(tmp_path): |
| source = tmp_path / "source.wav" |
| _write_silence(source, 4) |
| root = tmp_path / "job" |
| job = JobPaths("a" * 32, root, root / "input", root / "work", root / "output") |
| for directory in (job.input_dir, job.work_dir, job.output_dir, job.logs_dir, job.bundle_dir, job.config_dir): |
| directory.mkdir(parents=True, exist_ok=True) |
|
|
| prepared, total = prepare_inputs( |
| job, |
| [source], |
| input_ranges=[{"start_seconds": 1.0, "end_seconds": 3.0}], |
| ) |
| assert total == pytest.approx(2.0, abs=0.01) |
| assert prepared[0].parent == job.work_dir |
| assert prepared[0].suffix == ".wav" |
| assert probe_duration(prepared[0]) == pytest.approx(2.0, abs=0.03) |
|
|
|
|
| def test_ui_wrapper_maps_long_form_fields(monkeypatch): |
| from src import ui |
|
|
| captured = {} |
|
|
| def fake_prepare(*args, **kwargs): |
| captured["args"] = args |
| captured["kwargs"] = kwargs |
| return "state", "markdown", "preflight.json", "prepare.log" |
|
|
| monkeypatch.setattr(ui, "prepare_job", fake_prepare) |
| values = list(range(34)) |
| result = ui.prepare_separation_with_progress(*values, progress=None) |
| assert result[:4] == ("state", "markdown", "preflight.json", "prepare.log") |
| assert len(captured["args"]) == 29 |
| assert captured["args"][22] == 0 |
| assert captured["kwargs"]["range_mode"] == 22 |
| assert captured["kwargs"]["range_start_seconds"] == 23 |
| assert captured["kwargs"]["range_end_seconds"] == 24 |
| assert captured["kwargs"]["preview_seconds"] == 25 |
| assert captured["kwargs"]["chunk_mode"] == 26 |
| assert captured["kwargs"]["fixed_chunk_seconds"] == 27 |
| assert captured["kwargs"]["batch_continue_on_item_error"] is True |
|
|