diff --git a/abogen/conversion.py b/abogen/conversion.py index 1d3d645..737e606 100644 --- a/abogen/conversion.py +++ b/abogen/conversion.py @@ -558,6 +558,11 @@ class ConversionThread(QThread): merged_subtitle_file.write("[Script Info]\n") merged_subtitle_file.write("Title: Generated by Abogen\n") merged_subtitle_file.write("ScriptType: v4.00+\n\n") + # Add style definitions for karaoke highlighting + if self.subtitle_mode == "Sentence + Highlighting": + merged_subtitle_file.write("[V4+ Styles]\n") + merged_subtitle_file.write("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n") + merged_subtitle_file.write("Style: Default,Arial,24,&H00FFFFFF,&H00808080,&H00000000,&H00404040,0,0,0,0,100,100,0,0,3,2,0,5,10,10,10,1\n\n") merged_subtitle_file.write("[Events]\n") merged_subtitle_file.write( "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n" @@ -700,6 +705,13 @@ class ConversionThread(QThread): chapter_subtitle_file.write("[Script Info]\n") chapter_subtitle_file.write("Title: Generated by Abogen\n") chapter_subtitle_file.write("ScriptType: v4.00+\n\n") + + # Add style definitions for karaoke highlighting + if self.subtitle_mode == "Sentence + Highlighting": + chapter_subtitle_file.write("[V4+ Styles]\n") + chapter_subtitle_file.write("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n") + chapter_subtitle_file.write("Style: Default,Arial,24,&H00FFFFFF,&H00808080,&H00000000,&H00404040,0,0,0,0,100,100,0,0,3,2,0,5,10,10,10,1\n\n") + chapter_subtitle_file.write("[Events]\n") chapter_subtitle_file.write( "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n" @@ -823,8 +835,10 @@ class ConversionThread(QThread): for start, end, text in new_entries: start_time = self._ass_time(start) end_time = self._ass_time(end) + # Use karaoke effect for highlighting mode + effect = "karaoke" if self.subtitle_mode == "Sentence + Highlighting" else "" merged_subtitle_file.write( - f"Dialogue: 0,{start_time},{end_time},Default,,{merged_subtitle_margin},{merged_subtitle_margin},0,,{merged_subtitle_alignment_tag}{text}\n" + f"Dialogue: 0,{start_time},{end_time},Default,,{merged_subtitle_margin},{merged_subtitle_margin},0,{effect},{merged_subtitle_alignment_tag}{text}\n" ) else: for entry in new_entries: @@ -850,8 +864,10 @@ class ConversionThread(QThread): for start, end, text in new_chapter_entries: start_time = self._ass_time(start) end_time = self._ass_time(end) + # Use karaoke effect for highlighting mode + effect = "karaoke" if self.subtitle_mode == "Sentence + Highlighting" else "" chapter_subtitle_file.write( - f"Dialogue: 0,{start_time},{end_time},Default,,{chapter_subtitle_margin},{chapter_subtitle_margin},0,,{chapter_subtitle_alignment_tag}{text}\n" + f"Dialogue: 0,{start_time},{end_time},Default,,{chapter_subtitle_margin},{chapter_subtitle_margin},0,{effect},{chapter_subtitle_alignment_tag}{text}\n" ) else: for entry in new_chapter_entries: @@ -1152,7 +1168,61 @@ class ConversionThread(QThread): if not tokens_with_timestamps: return - if self.subtitle_mode == "Sentence" or self.subtitle_mode == "Sentence + Comma": + if self.subtitle_mode == "Sentence + Highlighting": + # Sentence-based processing with karaoke highlighting + separator = r"[.!?]" + current_sentence = [] + word_count = 0 + + for token in tokens_with_timestamps: + current_sentence.append(token) + word_count += 1 + + # Split sentences based on separator or word count + if ( + re.search(separator, token["text"]) and token["whitespace"] == " " + ) or word_count >= max_subtitle_words: + if current_sentence: + # Create karaoke subtitle entry for this sentence + start_time = current_sentence[0]["start"] + end_time = current_sentence[-1]["end"] + + # Generate karaoke text with background highlighting + karaoke_text = "" + for t in current_sentence: + # Calculate duration in centiseconds + duration = t["end"] - t["start"] if t["end"] and t["start"] else 0.5 + duration_cs = int(duration * 100) + # Add karaoke effect - relies on style's SecondaryColour for highlighting + karaoke_text += f"{{\\kf{duration_cs}}}{t['text']}{t.get('whitespace', '') or ''}" + + subtitle_entries.append( + (start_time, end_time, karaoke_text.strip()) + ) + current_sentence = [] + word_count = 0 + + # Add any remaining tokens as a sentence + if current_sentence: + start_time = current_sentence[0]["start"] + end_time = current_sentence[-1]["end"] + + # Generate karaoke text for remaining tokens + karaoke_text = "" + for t in current_sentence: + duration = t["end"] - t["start"] if t["end"] and t["start"] else 0.5 + duration_cs = int(duration * 100) + karaoke_text += f"{{\\kf{duration_cs}}}{t['text']}{t.get('whitespace', '') or ''}" + subtitle_entries.append((start_time, end_time, karaoke_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) + + elif self.subtitle_mode == "Sentence" or self.subtitle_mode == "Sentence + Comma": # Define separator pattern based on mode separator = r"[.!?]" if self.subtitle_mode == "Sentence" else r"[.!?,]" current_sentence = [] diff --git a/abogen/gui.py b/abogen/gui.py index 755338e..de16023 100644 --- a/abogen/gui.py +++ b/abogen/gui.py @@ -895,6 +895,7 @@ class abogen(QWidget): "Disabled: No subtitles will be generated.\n" "Sentence: Subtitles will be generated for each sentence.\n" "Sentence + Comma: Subtitles will be generated for each sentence and comma.\n" + "Sentence + Highlighting: Subtitles with word-by-word karaoke highlighting.\n" "1+ word: Subtitles will be generated for each word(s).\n\n" "Supported languages for subtitle generation:\n" + "\n".join( @@ -902,7 +903,7 @@ class abogen(QWidget): for lang in SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION ) ) - subtitle_options = ["Disabled", "Sentence", "Sentence + Comma"] + [ + subtitle_options = ["Disabled", "Sentence", "Sentence + Comma", "Sentence + Highlighting"] + [ f"{i} word" if i == 1 else f"{i} words" for i in range(1, 11) ] self.subtitle_combo.addItems(subtitle_options)