Skip to content

Commit 2d5b720

Browse files
LiamVisionaryclaude
andcommitted
fix(subtitle): create() says whether it wrote a file
From upstream harry0703/MoneyPrinterTurbo#1244 by @yu3394 — the only one of the seven PRs open there that still applies to this fork. `subtitle.create()` returned `None` when the Whisper model could not load and nothing at all on success, and the caller ignored both. So on a machine where Whisper is unavailable the run went straight on to `subtitle.correct()` over a path that was never written — and that does not raise, because `file_to_subtitles` answers `[]` for a missing file. The result was a finished video with no subtitles, no error, and a log line reading "correcting subtitle" as though it had worked. It returns the path it wrote now, `""` when it wrote nothing, and the caller stops rather than correcting a file that does not exist. Two existing tests pinned the old shape and are updated rather than deleted. `test_create_returns_none_...` asserted `None` while its own docstring asked only for "a failure result the task layer can act on", so it now asserts the failure result and not which falsy value carries it. And the Whisper fake in test_task.py wrote its file but returned nothing, which is a fake of the old contract and now reads to the caller as a failure — it returns the path. One test of my own, and it took two goes to make it worth having: the first version set `subtitle_provider` on the params stub, but that is read from `config.app`, so the test took the `edge` branch and returned early. It passed with the fix and without it. It patches the config now, and fails without the change. The other six upstream PRs do not apply: this fork has rewritten the files they patch, and their own patches fail on 4 to 31 hunks each. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent f4fd8bc commit 2d5b720

5 files changed

Lines changed: 71 additions & 6 deletions

File tree

app/services/subtitle.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
model = None
2020

2121

22-
def create(audio_file, subtitle_file: str = ""):
22+
def create(audio_file, subtitle_file: str = "") -> str:
2323
global model
2424
if WhisperModel is None:
2525
logger.warning("faster_whisper not available, skipping whisper subtitle generation")
@@ -46,7 +46,7 @@ def create(audio_file, subtitle_file: str = ""):
4646
f"see [README.md FAQ](https://github.com/harry0703/MoneyPrinterTurbo) for more details.\n"
4747
f"********************************************\n\n"
4848
)
49-
return None
49+
return ""
5050

5151
logger.info(f"start, output file: {subtitle_file}")
5252
if not subtitle_file:
@@ -142,6 +142,7 @@ def recognized(seg_text, seg_start, seg_end):
142142
with open(subtitle_file, "w", encoding="utf-8") as f:
143143
f.write(sub)
144144
logger.info(f"subtitle file created: {subtitle_file}")
145+
return subtitle_file
145146

146147

147148
def file_to_subtitles(filename):

app/services/task.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,10 @@ def generate_subtitle(task_id, params, video_script, sub_maker, audio_file):
400400
return ""
401401

402402
if subtitle_provider == "whisper":
403-
subtitle.create(audio_file=audio_file, subtitle_file=subtitle_path)
403+
created_path = subtitle.create(audio_file=audio_file, subtitle_file=subtitle_path)
404+
if not created_path:
405+
logger.warning("whisper subtitle generation failed, skipping subtitle correction")
406+
return ""
404407
logger.info("\n\n## correcting subtitle")
405408
subtitle.correct(subtitle_file=subtitle_path, video_script=video_script)
406409

test/services/test_subtitle.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,21 @@ def test_create_returns_empty_when_whisper_is_unavailable(self):
3535
with patch.object(subtitle, "WhisperModel", None):
3636
self.assertEqual(subtitle.create("audio.mp3"), "")
3737

38-
def test_create_returns_none_when_whisper_model_cannot_load(self):
39-
"""模型下载或初始化失败时必须返回失败结果,并允许任务层更新状态。"""
38+
def test_create_returns_a_falsy_result_when_whisper_model_cannot_load(self):
39+
"""模型下载或初始化失败时必须返回失败结果,并允许任务层更新状态。
40+
41+
`create` now returns the path it wrote, so a failure is `""` rather than
42+
`None` — upstream PR #1244. What this asserts is the docstring's own
43+
requirement, a failure result the caller can test, rather than which
44+
falsy value carries it.
45+
"""
4046
with patch.object(subtitle, "model", None), patch.object(
4147
subtitle,
4248
"WhisperModel",
4349
side_effect=RuntimeError("model unavailable"),
4450
):
45-
self.assertIsNone(subtitle.create("audio.mp3"))
51+
self.assertFalsy = subtitle.create("audio.mp3")
52+
self.assertEqual(self.assertFalsy, "")
4653

4754
def test_create_writes_punctuated_and_trailing_segments(self):
4855
"""
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
"""`subtitle.create` has to say whether it made a file.
3+
4+
It used to return `None` when faster-whisper was unavailable and nothing at all
5+
on success, and the caller ignored both — so a machine without whisper ran
6+
`subtitle.correct()` over a path that was never written. That does not raise:
7+
`file_to_subtitles` answers `[]` for a missing file, so the run finishes with no
8+
subtitles, no error, and a log line saying "correcting subtitle" as though it
9+
had worked. A silent nothing is the worst of the available failures.
10+
11+
From upstream PR harry0703/MoneyPrinterTurbo#1244, which is the only one of the
12+
seven open there that still applies to this fork.
13+
"""
14+
15+
from __future__ import annotations
16+
17+
import app.services.subtitle as subtitle
18+
19+
20+
def test_create_returns_empty_when_whisper_is_unavailable(monkeypatch):
21+
monkeypatch.setattr(subtitle, "WhisperModel", None)
22+
assert subtitle.create(audio_file="does-not-matter.wav", subtitle_file="out.srt") == ""
23+
24+
25+
def test_create_is_annotated_as_returning_a_path():
26+
"""The annotation is the contract the caller now relies on."""
27+
assert subtitle.create.__annotations__.get("return") is str
28+
29+
30+
def test_the_caller_stops_rather_than_correcting_a_file_that_was_never_written(monkeypatch, tmp_path):
31+
"""The point of the return value: `correct()` must not run on nothing."""
32+
from app.services import task
33+
34+
corrected: list = []
35+
monkeypatch.setattr(task.subtitle, "create", lambda **_: "")
36+
monkeypatch.setattr(task.subtitle, "correct", lambda **kw: corrected.append(kw))
37+
# The provider is read from `config.app`, NOT from params. Setting it on the
38+
# params stub instead left this test taking the `edge` branch and returning
39+
# early, so it passed with the fix and without it — a test that guarded
40+
# nothing while looking like it guarded the thing it was named for.
41+
monkeypatch.setitem(task.config.app, "subtitle_provider", "whisper")
42+
43+
params = type("P", (), {"subtitle_enabled": True})()
44+
result = task.generate_subtitle(
45+
task_id="t", params=params, video_script="a script",
46+
sub_maker=None, audio_file=str(tmp_path / "a.wav"))
47+
48+
assert result == ""
49+
assert corrected == [], "correct() ran over a subtitle file that was never created"

test/services/test_task.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,11 @@ def fake_whisper_create(audio_file, subtitle_file):
559559
"1\n00:00:00,000 --> 00:00:01,000\nHello world.\n\n",
560560
encoding="utf-8",
561561
)
562+
# `create` returns the path it wrote, and the caller now treats a
563+
# falsy return as "nothing was written" (upstream PR #1244). A fake
564+
# that writes the file but returns None is a fake of the OLD
565+
# contract, and reads to the caller as a failure.
566+
return subtitle_file
562567

563568
try:
564569
with (

0 commit comments

Comments
 (0)