Added new option: Subtitle format

This commit is contained in:
Deniz Şafak
2025-05-28 13:28:59 +03:00
parent 010274e703
commit 2f5780f40e
8 changed files with 243 additions and 740 deletions
+73 -20
View File
@@ -333,6 +333,7 @@ class ConversionThread(QThread):
self.log_updated.emit(f"- Speed: {self.speed}")
self.log_updated.emit(f"- Subtitle mode: {self.subtitle_mode}")
self.log_updated.emit(f"- Output format: {self.output_format}")
self.log_updated.emit(f"- Subtitle format: {getattr(self, 'subtitle_format', 'srt')}")
self.log_updated.emit(f"- Save option: {self.save_option}")
if self.replace_single_newlines:
self.log_updated.emit(f"- Replace single newlines: Yes")
@@ -682,24 +683,34 @@ class ConversionThread(QThread):
self.log_updated.emit((f"Failed to write {separate_format.upper()} file.", "red"))
continue
# Generate .srt subtitle file for chapter if not Disabled
# Generate subtitle file for chapter if not Disabled
if self.subtitle_mode != "Disabled" and chapter_subtitle_entries:
chapter_srt_path = os.path.join(
chapters_out_dir, f"{chapter_filename}.srt"
subtitle_format = getattr(self, 'subtitle_format', 'srt')
file_extension = 'ass' if 'ass' in subtitle_format else 'srt'
chapter_subtitle_path = os.path.join(
chapters_out_dir, f"{chapter_filename}.{file_extension}"
)
with open(
chapter_srt_path, "w", encoding="utf-8", errors="replace"
) as srt_file:
for i, (start, end, text) in enumerate(
chapter_subtitle_entries, 1
):
srt_file.write(
f"{i}\n{self._srt_time(start)} --> {self._srt_time(end)}\n{text}\n\n"
)
if 'ass' in subtitle_format:
# Generate ASS subtitle
is_centered = 'centered' in subtitle_format
is_narrow = 'narrow' in subtitle_format
self._write_ass_subtitle(chapter_subtitle_path, chapter_subtitle_entries, is_centered, is_narrow)
else:
# Generate SRT subtitle (default)
with open(
chapter_subtitle_path, "w", encoding="utf-8", errors="replace"
) as srt_file:
for i, (start, end, text) in enumerate(
chapter_subtitle_entries, 1
):
srt_file.write(
f"{i}\n{self._srt_time(start)} --> {self._srt_time(end)}\n{text}\n\n"
)
self.log_updated.emit(
(
f"\nChapter {chapter_idx} saved to: {chapter_out_path}\n\nSubtitle saved to: {chapter_srt_path}",
f"\nChapter {chapter_idx} saved to: {chapter_out_path}\n\nSubtitle saved to: {chapter_subtitle_path}",
"green",
)
)
@@ -756,15 +767,25 @@ class ConversionThread(QThread):
# Subtitle and final message logic
if final_out_path:
if self.subtitle_mode != "Disabled":
srt_path = os.path.splitext(final_out_path)[0] + ".srt"
with open(srt_path, "w", encoding="utf-8", errors="replace") as srt_file:
for i, (start, end, text) in enumerate(subtitle_entries, 1):
srt_file.write(
f"{i}\n{self._srt_time(start)} --> {self._srt_time(end)}\n{text}\n\n"
)
subtitle_format = getattr(self, 'subtitle_format', 'srt')
file_extension = 'ass' if 'ass' in subtitle_format else 'srt'
subtitle_path = os.path.splitext(final_out_path)[0] + f".{file_extension}"
if 'ass' in subtitle_format:
# Generate ASS subtitle with optional centering and margin
is_centered = 'centered' in subtitle_format
is_narrow = 'narrow' in subtitle_format
self._write_ass_subtitle(subtitle_path, subtitle_entries, is_centered, is_narrow)
else:
# Generate SRT subtitle (default)
with open(subtitle_path, "w", encoding="utf-8", errors="replace") as srt_file:
for i, (start, end, text) in enumerate(subtitle_entries, 1):
srt_file.write(
f"{i}\n{self._srt_time(start)} --> {self._srt_time(end)}\n{text}\n\n"
)
self.conversion_finished.emit(
(
f"\nAudiobook saved to: {final_out_path}\n\nSubtitle saved to: {srt_path}",
f"\nAudiobook saved to: {final_out_path}\n\nSubtitle saved to: {subtitle_path}",
"green",
),
final_out_path,
@@ -981,6 +1002,14 @@ class ConversionThread(QThread):
s = int(t % 60)
ms = int((t - int(t)) * 1000)
return f"{h:02}:{m:02}:{s:02},{ms:03}"
def _ass_time(self, t):
"""Helper function to format time for ASS files"""
h = int(t // 3600)
m = int((t % 3600) // 60)
s = int(t % 60)
cs = int((t - int(t)) * 100) # Centiseconds for ASS format
return f"{h}:{m:02}:{s:02}.{cs:02}"
def _process_subtitle_tokens(
self, tokens_with_timestamps, subtitle_entries, max_subtitle_words
@@ -1068,6 +1097,30 @@ class ConversionThread(QThread):
(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")
def cancel(self):
self.cancel_requested = True
self.waiting_for_user_input = (
+26 -1
View File
@@ -1329,6 +1329,8 @@ class abogen(QWidget):
)
# Pass separate_chapters_format setting
self.conversion_thread.separate_chapters_format = self.separate_chapters_format
# Pass subtitle format setting
self.conversion_thread.subtitle_format = self.config.get("subtitle_format", "srt")
# Pass chapter count for EPUB or PDF files
if self.selected_file_type in ["epub", "pdf"] and hasattr(
self, "selected_chapters"
@@ -1925,6 +1927,24 @@ class abogen(QWidget):
max_words_action.triggered.connect(self.set_max_subtitle_words)
menu.addAction(max_words_action)
# Add subtitle format option
subtitle_format_menu = QMenu("Subtitle format", self)
subtitle_format_menu.setToolTip("Choose the format for generated subtitles")
subtitle_format_group = QActionGroup(self)
subtitle_format_group.setExclusive(True)
subtitle_format = self.config.get("subtitle_format", "srt")
for format_option in ["srt", "ass (wide)", "ass (narrow)", "ass (centered wide)", "ass (centered narrow)"]:
format_action = QAction(f"{format_option}", self)
format_action.setCheckable(True)
format_action.setChecked(subtitle_format == format_option)
format_action.triggered.connect(lambda checked, fmt=format_option: self.set_subtitle_format(fmt))
subtitle_format_group.addAction(format_action)
subtitle_format_menu.addAction(format_action)
menu.addMenu(subtitle_format_menu)
# Add separate chapters format option
separate_chapters_format_menu = QMenu("Separate chapters audio format", self)
separate_chapters_format_menu.setToolTip("Choose the format for individual chapter files")
@@ -1932,7 +1952,7 @@ class abogen(QWidget):
format_group = QActionGroup(self)
format_group.setExclusive(True)
for format_option in ["wav", "mp3", "flac", "opus"]:
for format_option in ["wav", "flac", "mp3", "opus"]:
format_action = QAction(format_option, self)
format_action.setCheckable(True)
format_action.setChecked(self.separate_chapters_format == format_option)
@@ -2475,6 +2495,11 @@ Categories=AudioVideo;Audio;Utility;
self.config["separate_chapters_format"] = fmt
save_config(self.config)
def set_subtitle_format(self, fmt):
"""Set the subtitle format."""
self.config["subtitle_format"] = fmt
save_config(self.config)
def show_model_download_warning(self, title, message):
QMessageBox.information(self, title, message)