diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a22dfe..4fd0eda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.3 +- Fixed issue where the app would not restart properly on Windows. +- Fixed last sentence/subtitle entry timing in generated subtitles, the end time of the final subtitle entry now correctly matches the end of the audio chunk, preventing zero or invalid timings at the end. + # v1.1.2 - Now you can play the audio files while they are processing. - Audio and subtitle files are now written directly to disk during generation, which significantly reduces memory usage. diff --git a/abogen/conversion.py b/abogen/conversion.py index 0072588..8591b65 100644 --- a/abogen/conversion.py +++ b/abogen/conversion.py @@ -793,6 +793,7 @@ class ConversionThread(QThread): tokens_with_timestamps, new_entries, self.max_subtitle_words, + fallback_end_time=chunk_start + chunk_dur ) if merged_subtitle_file: subtitle_format = getattr( @@ -819,6 +820,7 @@ class ConversionThread(QThread): chapter_tokens_with_timestamps, new_chapter_entries, self.max_subtitle_words, + fallback_end_time=chapter_current_time + chunk_dur, ) if chapter_subtitle_file: subtitle_format = getattr( @@ -1146,7 +1148,7 @@ class ConversionThread(QThread): return f"{h}:{m:02}:{s:02}.{cs:02}" def _process_subtitle_tokens( - self, tokens_with_timestamps, subtitle_entries, max_subtitle_words + self, tokens_with_timestamps, subtitle_entries, max_subtitle_words, fallback_end_time=None ): """Helper function to process subtitle tokens according to the subtitle mode""" if not tokens_with_timestamps: @@ -1191,9 +1193,15 @@ class ConversionThread(QThread): sentence_text = "" for t in current_sentence: sentence_text += t["text"] + (t.get("whitespace", "") or "") - subtitle_entries.append((start_time, end_time, sentence_text.strip())) + # Fallback for last entry + if subtitle_entries and fallback_end_time is not None: + last_entry = subtitle_entries[-1] + start, end, text = last_entry + if end is None or end <= start or end <= 0: + subtitle_entries[-1] = (start, fallback_end_time, text) + else: # Word count-based grouping try: @@ -1230,37 +1238,12 @@ class ConversionThread(QThread): subtitle_entries.append( (group[0]["start"], group[-1]["end"], text.strip()) ) - - def _write_ass_subtitle( - self, file_path, subtitle_entries, is_centered=False, is_narrow=False - ): - with open(file_path, "w", encoding="utf-8", errors="replace") as f: - # Minimal ASS header - f.write("[Script Info]\n") - f.write("Title: Generated by Abogen\n") - f.write("ScriptType: v4.00+\n\n") - - # Only events section, use override tags for positioning - f.write("[Events]\n") - f.write( - "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n" - ) - - # Set margin based on is_narrow parameter - margin = "90" if is_narrow else "" - alignment_tag = "" - if is_centered: - alignment = 5 - alignment_tag = f"{{\\an{alignment}}}" - - # Write each subtitle with override tag for alignment and margins - for i, (start, end, text) in enumerate(subtitle_entries, 1): - start_time = self._ass_time(start) - end_time = self._ass_time(end) - f.write( - f"Dialogue: 0,{start_time},{end_time},Default,,{margin},{margin},0,,{alignment_tag}{text}\n" - ) - + # Fallback for last entry + if subtitle_entries and fallback_end_time is not None: + last_entry = subtitle_entries[-1] + start, end, text = last_entry + if end is None or end <= start or end <= 0: + subtitle_entries[-1] = (start, fallback_end_time, text) def cancel(self): self.cancel_requested = True self.should_cancel = True