From f30defdc1446bca1fb43cb914a34561e3e55582a Mon Sep 17 00:00:00 2001 From: Chu Khac Minh <87845619+Minh3132@users.noreply.github.com> Date: Mon, 14 Sep 2026 13:24:28 +0800 Subject: [PATCH 1/4] test: cover concatenated audio subtitle timeline --- tests/test_audio_clip_srt_timeline.py | 48 +++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/test_audio_clip_srt_timeline.py diff --git a/tests/test_audio_clip_srt_timeline.py b/tests/test_audio_clip_srt_timeline.py new file mode 100644 index 0000000..f38d332 --- /dev/null +++ b/tests/test_audio_clip_srt_timeline.py @@ -0,0 +1,48 @@ +import copy +import sys +from pathlib import Path + +import numpy as np + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "funclip")) + +from videoclipper import VideoClipper + + +def _state(): + sentences = [ + {"text": "hello", "timestamp": [[1000, 2000]], "spk": 0}, + {"text": "world", "timestamp": [[4000, 5000]], "spk": 0}, + ] + return { + "audio_input": (16000, np.zeros(96000)), + "recog_res_raw": "hello world", + "timestamp": [[1000, 2000], [4000, 5000]], + "sentences": sentences, + "sd_sentences": copy.deepcopy(sentences), + } + + +def test_concatenated_audio_subtitles_accumulate_output_time(): + clipper = VideoClipper(None) + + (rate, audio), _, subtitles = clipper.clip( + "hello#world", 0, 0, copy.deepcopy(_state()) + ) + + assert rate == 16000 + assert len(audio) == 32000 + assert "00:00:00,000 --> 00:00:01,000" in subtitles + assert "00:00:01,000 --> 00:00:02,000" in subtitles + + +def test_explicit_timestamp_list_uses_same_accumulated_timeline(): + clipper = VideoClipper(None) + + (_, audio), _, subtitles = clipper.clip( + "", 0, 0, copy.deepcopy(_state()), + timestamp_list=[[16000, 32000], [64000, 80000]], + ) + + assert len(audio) == 32000 + assert "00:00:01,000 --> 00:00:02,000" in subtitles From 3a62344bad0b40bee1d713bd01762dce3f280c0f Mon Sep 17 00:00:00 2001 From: Chu Khac Minh <87845619+Minh3132@users.noreply.github.com> Date: Mon, 14 Sep 2026 13:25:38 +0800 Subject: [PATCH 2/4] fix: accumulate audio subtitle timeline across clips --- funclip/videoclipper.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/funclip/videoclipper.py b/funclip/videoclipper.py index 8655940..fc28368 100644 --- a/funclip/videoclipper.py +++ b/funclip/videoclipper.py @@ -221,6 +221,7 @@ def clip(self, dest_text, start_ost, end_ost, state, dest_spk=None, output_dir=N ts = all_ts # ts.sort() srt_index = 0 + time_acc_ost = 0.0 clip_srt = "" if len(ts): start, end = ts[0] @@ -228,16 +229,30 @@ def clip(self, dest_text, start_ost, end_ost, state, dest_spk=None, output_dir=N end = min(max(0, end+end_ost*16), len(data)) res_audio = data[start:end] start_end_info = "from {} to {}".format(start/16000, end/16000) - srt_clip, _, srt_index = generate_srt_clip(sentences, start/16000.0, end/16000.0, begin_index=srt_index) + srt_clip, _, srt_index = generate_srt_clip( + sentences, + start/16000.0, + end/16000.0, + begin_index=srt_index, + time_acc_ost=time_acc_ost, + ) clip_srt += srt_clip + time_acc_ost += (end - start) / 16000.0 for _ts in ts[1:]: # multiple sentence input or multiple output matched start, end = _ts start = min(max(0, start+start_ost*16), len(data)) end = min(max(0, end+end_ost*16), len(data)) start_end_info += ", from {} to {}".format(start, end) res_audio = np.concatenate([res_audio, data[start:end]], -1) - srt_clip, _, srt_index = generate_srt_clip(sentences, start/16000.0, end/16000.0, begin_index=srt_index-1) + srt_clip, _, srt_index = generate_srt_clip( + sentences, + start/16000.0, + end/16000.0, + begin_index=srt_index-1, + time_acc_ost=time_acc_ost, + ) clip_srt += srt_clip + time_acc_ost += (end - start) / 16000.0 if len(ts): message = "{} periods found in the speech: ".format(len(ts)) + start_end_info + log_append else: @@ -284,8 +299,8 @@ def video_clip(self, state, font_size=32, font_color='white', - add_sub=False, - dest_spk=None, + add_sub=False, + dest_spk=None, output_dir=None, timestamp_list=None): # get from state @@ -567,4 +582,4 @@ def main(cmd=None): if __name__ == '__main__': - main() + main() \ No newline at end of file From fe2db2f68003b757adfc66a8ae76db9d65cf1017 Mon Sep 17 00:00:00 2001 From: Chu Khac Minh <87845619+Minh3132@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:14:40 +0800 Subject: [PATCH 3/4] test: preserve SRT timeline across concatenated audio clips --- tests/test_audio_srt_timeline.py | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/test_audio_srt_timeline.py diff --git a/tests/test_audio_srt_timeline.py b/tests/test_audio_srt_timeline.py new file mode 100644 index 0000000..9d8e5cb --- /dev/null +++ b/tests/test_audio_srt_timeline.py @@ -0,0 +1,50 @@ +import copy +import sys +from pathlib import Path + +import numpy as np + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "funclip")) + +from videoclipper import VideoClipper + + +def _state(): + sentences = [ + {"text": "hello", "timestamp": [[1000, 2000]], "spk": 0}, + {"text": "world", "timestamp": [[4000, 5000]], "spk": 0}, + ] + return { + "audio_input": (16000, np.zeros(96000)), + "recog_res_raw": "hello world", + "timestamp": [[1000, 2000], [4000, 5000]], + "sentences": sentences, + "sd_sentences": copy.deepcopy(sentences), + } + + +def test_clip_accumulates_srt_time_for_multiple_text_ranges(): + clipper = VideoClipper(None) + (rate, audio), _, subtitles = clipper.clip( + "hello#world", 0, 0, copy.deepcopy(_state()) + ) + + assert rate == 16000 + assert len(audio) == 32000 + assert "00:00:00,000 --> 00:00:01,000" in subtitles + assert "00:00:01,000 --> 00:00:02,000" in subtitles + + +def test_clip_accumulates_srt_time_for_explicit_timestamp_ranges(): + clipper = VideoClipper(None) + (_, audio), _, subtitles = clipper.clip( + "", + 0, + 0, + copy.deepcopy(_state()), + timestamp_list=[[16000, 32000], [64000, 80000]], + ) + + assert len(audio) == 32000 + assert "00:00:00,000 --> 00:00:01,000" in subtitles + assert "00:00:01,000 --> 00:00:02,000" in subtitles From 91977f65bdebcf4bb01e87af44a72cf4a03879f9 Mon Sep 17 00:00:00 2001 From: Chu Khac Minh <87845619+Minh3132@users.noreply.github.com> Date: Mon, 14 Sep 2026 14:14:54 +0800 Subject: [PATCH 4/4] test: remove duplicate audio SRT regression --- tests/test_audio_srt_timeline.py | 50 -------------------------------- 1 file changed, 50 deletions(-) delete mode 100644 tests/test_audio_srt_timeline.py diff --git a/tests/test_audio_srt_timeline.py b/tests/test_audio_srt_timeline.py deleted file mode 100644 index 9d8e5cb..0000000 --- a/tests/test_audio_srt_timeline.py +++ /dev/null @@ -1,50 +0,0 @@ -import copy -import sys -from pathlib import Path - -import numpy as np - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "funclip")) - -from videoclipper import VideoClipper - - -def _state(): - sentences = [ - {"text": "hello", "timestamp": [[1000, 2000]], "spk": 0}, - {"text": "world", "timestamp": [[4000, 5000]], "spk": 0}, - ] - return { - "audio_input": (16000, np.zeros(96000)), - "recog_res_raw": "hello world", - "timestamp": [[1000, 2000], [4000, 5000]], - "sentences": sentences, - "sd_sentences": copy.deepcopy(sentences), - } - - -def test_clip_accumulates_srt_time_for_multiple_text_ranges(): - clipper = VideoClipper(None) - (rate, audio), _, subtitles = clipper.clip( - "hello#world", 0, 0, copy.deepcopy(_state()) - ) - - assert rate == 16000 - assert len(audio) == 32000 - assert "00:00:00,000 --> 00:00:01,000" in subtitles - assert "00:00:01,000 --> 00:00:02,000" in subtitles - - -def test_clip_accumulates_srt_time_for_explicit_timestamp_ranges(): - clipper = VideoClipper(None) - (_, audio), _, subtitles = clipper.clip( - "", - 0, - 0, - copy.deepcopy(_state()), - timestamp_list=[[16000, 32000], [64000, 80000]], - ) - - assert len(audio) == 32000 - assert "00:00:00,000 --> 00:00:01,000" in subtitles - assert "00:00:01,000 --> 00:00:02,000" in subtitles