diff --git a/CHANGELOG.md b/CHANGELOG.md index edfa426..b29bc0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # v1.0.9 (pre-release) - Added chunking/segmenting system that fixes memory outage issues when processing large audio files. +- Added new option: `Subtitle format`, allowing users to choose between `srt` , `ass (wide)`, `ass (narrow)`, and `ass (centered wide)` and `ass (centered narrow)` - Improved chapter filename generation with smart word-boundary truncation at 80 characters, preventing mid-word cuts in filenames. - `Composer` and `Genre` metadata fields for M4B files are now editable from the text editor. diff --git a/README.md b/README.md index 49f29e1..be3b779 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,8 @@ Here’s Abogen in action: in this demo, it processes ∼3,000 characters of tex - **Options**: - **Replace single newlines with spaces**: Replaces single newlines with spaces in the text. This is useful for texts that have imaginary line breaks. - **Configure max words per subtitle**: Configures the maximum number of words per subtitle entry. - - **Separate chapters audio format**: Configures the audio format for separate chapters (e.g., `WAV`, `FLAC`, `MP3`, `OPUS`). + - **Subtitle format**: Configures the subtitle format as `srt`, `ass (wide)`, `ass (narrow)`, `ass (centered wide)`, or `ass (centered narrow)`. + - **Separate chapters audio format**: Configures the audio format for separate chapters as `wav`, `flac`, `mp3`, or `opus`. - **Create desktop shortcut**: Creates a shortcut on your desktop for easy access. - **Open config.json directory**: Opens the directory where the configuration file is stored. - **Open temp directory**: Opens the temporary directory where converted text files are stored. @@ -194,11 +195,13 @@ For a complete list of supported languages and voices, refer to Kokoro's [VOICES ## `MPV Config` I highly recommend using [MPV](https://mpv.io/installation/) to play your audio files, as it supports displaying subtitles even without a video track. Here's my `mpv.conf`: ``` +# --- MPV Settings --- save-position-on-quit keep-open=yes ---audio-device=openal ---sub-margin-x=235 ---sub-pos=60 +# --- Subtitle --- +sub-ass-override=no +sub-margin-y=50 +sub-margin-x=50 # --- Audio Quality --- audio-spdif=ac3,dts,eac3,truehd,dts-hd audio-channels=auto diff --git a/abogen/conversion.py b/abogen/conversion.py index ff72a8e..5761f9c 100644 --- a/abogen/conversion.py +++ b/abogen/conversion.py @@ -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 = ( diff --git a/abogen/gui.py b/abogen/gui.py index cb5fb42..710f25e 100644 --- a/abogen/gui.py +++ b/abogen/gui.py @@ -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) diff --git a/demo/README.md b/demo/README.md index 16f390e..648d4cb 100644 --- a/demo/README.md +++ b/demo/README.md @@ -7,9 +7,10 @@ https://github.com/user-attachments/assets/9e4fc237-a3cd-46bd-b82c-c608336d6411 ### What You Need - A background image (bg.jpg) -- The subtitle file (.srt) **(created by Abogen)** +- The subtitle file (.ass) **(created by Abogen)** + - Select `ass(centered narrow)` as subtitle format in settings. - The audio recording (.wav) **(created by Abogen)** -- [Python](https://www.python.org/downloads/) and FFmpeg installed on your computer: +- FFmpeg installed on your computer: ```bash # Windows @@ -22,22 +23,12 @@ brew install ffmpeg sudo apt install ffmpeg ``` -### Step 1: Process the Subtitle File - -Run this command to process Abogen's subtitle file: - -``` -python convert.py your_subtitle.srt -``` - -This creates a properly formatted subtitle file called "your_subtitle_demo.ass" with centered text and appropriate styling. - -### Step 2: Create the Video (.webm) +### Create the Video (.webm) Run this FFmpeg command to create the tiny video: ``` -ffmpeg -loop 1 -framerate 24 -i bg.jpg -i audio.wav -vf "ass=your_subtitle_demo.ass" -c:v libvpx-vp9 -b:v 0 -crf 30 -c:a libopus -shortest demo.webm +ffmpeg -loop 1 -framerate 24 -i bg.jpg -i audio.wav -vf "ass=subtitle.ass" -c:v libvpx-vp9 -b:v 0 -crf 30 -c:a libopus -shortest demo.webm ``` ## For Higher Quality (But Larger) Video (.mp4) @@ -45,7 +36,7 @@ ffmpeg -loop 1 -framerate 24 -i bg.jpg -i audio.wav -vf "ass=your_subtitle_demo. If you need better quality for distribution, use this command instead: ``` -ffmpeg -loop 1 -framerate 24 -i bg.jpg -i audio.wav -vf "ass=your_subtitle_demo.ass" -c:v libx264 -preset slow -crf 18 -movflags +faststart -c:a copy -shortest demo.mp4 +ffmpeg -loop 1 -framerate 24 -i bg.jpg -i audio.wav -vf "ass=subtitle.ass" -c:v libx264 -preset slow -crf 18 -movflags +faststart -c:a copy -shortest demo.mp4 ``` This creates an MP4 file that's compatible with more devices but larger in size. diff --git a/demo/convert.py b/demo/convert.py deleted file mode 100644 index 6c5238b..0000000 --- a/demo/convert.py +++ /dev/null @@ -1,220 +0,0 @@ -# FFmpeg commands for creating videos: -# -# For WebM (smaller filesize): -# ffmpeg -loop 1 -framerate 24 -i bg.jpg -i audio.wav -vf "ass=your_subtitle_demo.ass" -c:v libvpx-vp9 -b:v 0 -crf 30 -c:a libopus -shortest demo.webm -# -# For MP4 (higher quality): -# ffmpeg -loop 1 -framerate 24 -i bg.jpg -i audio.wav -vf "ass=your_subtitle_demo.ass" -c:v libx264 -preset slow -crf 18 -movflags +faststart -c:a copy -shortest demo.mp4 -# -# Note: Replace 'audio.wav' with your audio file and 'your_subtitle_demo.ass' with your processed subtitle file - - -import re -import sys -from datetime import timedelta - - -def parse_time(s): - # For ASS format - if ":" in s and "." in s: - h, m, s = s.split(":") - sec, cs = s.split(".") - return timedelta( - hours=int(h), minutes=int(m), seconds=int(sec), milliseconds=int(cs) * 10 - ) - # For SRT format (00:00:00,000) - elif ":" in s and "," in s: - parts = s.split(":") - h = int(parts[0]) - m = int(parts[1]) - sec_parts = parts[2].split(",") - sec = int(sec_parts[0]) - ms = int(sec_parts[1]) - return timedelta(hours=h, minutes=m, seconds=sec, milliseconds=ms) - return None - - -def format_time(t): - total_seconds = int(t.total_seconds()) - cs = int((t.total_seconds() - total_seconds) * 100) - h = total_seconds // 3600 - m = (total_seconds % 3600) // 60 - s = total_seconds % 60 - return f"{h}:{m:02}:{s:02}.{cs:02}" - - -# Desired script info and style -DESIRED_SCRIPT_INFO = """[Script Info] -Title: Centered Subs -ScriptType: v4.00+ -PlayResX: 1280 -PlayResY: 720 -""" - -DESIRED_STYLE = """[V4+ Styles] -Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding -Style: Default,Arial,60,&H00FFFFFF,&H000000FF,&H00000000,&H64000000,-1,0,0,0,100,100,0,0,1,2,0,5,10,10,10,1 -""" - - -def process_subtitle_file(input_file): - output_file = input_file.replace(".ass", "_demo.ass").replace(".srt", "_demo.ass") - if output_file == input_file: - output_file = f"{input_file}_demo.ass" - - try: - with open(input_file, "r", encoding="utf-8") as f: - lines = f.readlines() - except FileNotFoundError: - print(f"❌ Error: Input file '{input_file}' not found") - sys.exit(1) - except Exception as e: - print(f"❌ Error reading file: {e}") - sys.exit(1) - - # Check if it's an SRT file - is_srt = input_file.lower().endswith(".srt") - - if is_srt: - return convert_srt_to_ass(input_file, output_file, lines) - - # Process ASS file - header = [] - events = [] - in_events = False - script_info_found = False - styles_found = False - - for line in lines: - if line.strip().startswith("[Script Info]"): - script_info_found = True - header.append(DESIRED_SCRIPT_INFO) - continue - elif line.strip().startswith("[V4+ Styles]"): - styles_found = True - header.append(DESIRED_STYLE) - continue - elif line.strip().startswith("[Events]"): - in_events = True - header.append(line) - elif not in_events: - if not script_info_found and not styles_found: - header.append(line) - else: - if line.startswith("Dialogue:"): - events.append(line) - else: - header.append(line) - - parsed_events = [] - for line in events: - match = re.match(r"Dialogue: \d,([^,]+),([^,]+),", line) - if not match: - parsed_events.append((None, None, line)) - continue - start = parse_time(match.group(1)) - end = parse_time(match.group(2)) - parsed_events.append((start, end, line)) - - # Cut overlaps - fixed_lines = [] - for i in range(len(parsed_events)): - start, end, line = parsed_events[i] - if start is None: - fixed_lines.append(line) - continue - - # Check for next subtitle - if i + 1 < len(parsed_events): - next_start, _, _ = parsed_events[i + 1] - if end > next_start: - end = next_start # cut current subtitle to stop at next one's start - - fixed_line = re.sub( - r"Dialogue: \d,[^,]+,[^,]+,", - f"Dialogue: 0,{format_time(start)},{format_time(end)},", - line, - count=1, - ) - fixed_lines.append(fixed_line) - - try: - with open(output_file, "w", encoding="utf-8") as f: - f.writelines(header) - f.writelines(fixed_lines) - print(f"✅ Successfully processed. Output file: {output_file}") - except Exception as e: - print(f"❌ Error writing output file: {e}") - sys.exit(1) - - -def convert_srt_to_ass(input_file, output_file, lines): - """Convert SRT format to ASS format.""" - events = [] - subtitle_blocks = [] - current_block = [] - - # Parse SRT file - for line in lines: - line = line.strip() - if not line: - if current_block: - subtitle_blocks.append(current_block) - current_block = [] - else: - current_block.append(line) - - # Don't forget the last block - if current_block: - subtitle_blocks.append(current_block) - - # Process subtitle blocks - for block in subtitle_blocks: - if len(block) < 3: - continue # Invalid block - - # Skip the subtitle number - timing_line = block[1] - - # Parse timing information - timing_match = re.match( - r"(\d+:\d+:\d+,\d+)\s+-->\s+(\d+:\d+:\d+,\d+)", timing_line - ) - if not timing_match: - continue - - start_time = parse_time(timing_match.group(1)) - end_time = parse_time(timing_match.group(2)) - - # Combine text lines - text = "\\N".join(block[2:]) - - # Create ASS Dialogue line - dialogue_line = f"Dialogue: 0,{format_time(start_time)},{format_time(end_time)},Default,,0,0,0,,{text}\n" - events.append(dialogue_line) - - # Create ASS file - try: - with open(output_file, "w", encoding="utf-8") as f: - f.write(DESIRED_SCRIPT_INFO) - f.write(DESIRED_STYLE) - f.write("[Events]\n") - f.write( - "Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n" - ) - f.writelines(events) - print(f"✅ Successfully converted SRT to ASS. Output file: {output_file}") - return True - except Exception as e: - print(f"❌ Error writing output file: {e}") - sys.exit(1) - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("❌ Error: No input file specified") - print(f"Usage: {sys.argv[0]} ") - sys.exit(1) - - input_file = sys.argv[1] - process_subtitle_file(input_file) diff --git a/demo/demo.ass b/demo/demo.ass new file mode 100644 index 0000000..86f71b8 --- /dev/null +++ b/demo/demo.ass @@ -0,0 +1,130 @@ +[Script Info] +Title: Generated by Abogen +ScriptType: v4.00+ + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: 0,0:00:00.27,0:00:00.83,Default,,100,100,0,,{\an5}Abogen +Dialogue: 0,0:00:00.83,0:00:00.96,Default,,100,100,0,,{\an5}is +Dialogue: 0,0:00:00.96,0:00:01.11,Default,,100,100,0,,{\an5}your +Dialogue: 0,0:00:01.11,0:00:01.66,Default,,100,100,0,,{\an5}all-in-one +Dialogue: 0,0:00:01.66,0:00:02.04,Default,,100,100,0,,{\an5}tool +Dialogue: 0,0:00:02.04,0:00:02.17,Default,,100,100,0,,{\an5}for +Dialogue: 0,0:00:02.17,0:00:02.53,Default,,100,100,0,,{\an5}turning +Dialogue: 0,0:00:02.53,0:00:03.22,Default,,100,100,0,,{\an5}text +Dialogue: 0,0:00:03.22,0:00:03.45,Default,,100,100,0,,{\an5}into +Dialogue: 0,0:00:03.45,0:00:03.75,Default,,100,100,0,,{\an5}something +Dialogue: 0,0:00:03.75,0:00:03.85,Default,,100,100,0,,{\an5}you +Dialogue: 0,0:00:03.85,0:00:04.04,Default,,100,100,0,,{\an5}can +Dialogue: 0,0:00:04.04,0:00:04.32,Default,,100,100,0,,{\an5}see +Dialogue: 0,0:00:04.32,0:00:04.45,Default,,100,100,0,,{\an5}and +Dialogue: 0,0:00:04.45,0:00:05.61,Default,,100,100,0,,{\an5}hear. +Dialogue: 0,0:00:05.61,0:00:06.02,Default,,100,100,0,,{\an5}Easily +Dialogue: 0,0:00:06.02,0:00:06.48,Default,,100,100,0,,{\an5}convert +Dialogue: 0,0:00:06.48,0:00:07.31,Default,,100,100,0,,{\an5}ePub, +Dialogue: 0,0:00:07.31,0:00:08.40,Default,,100,100,0,,{\an5}PDF, +Dialogue: 0,0:00:08.40,0:00:08.55,Default,,100,100,0,,{\an5}or +Dialogue: 0,0:00:08.55,0:00:08.90,Default,,100,100,0,,{\an5}text +Dialogue: 0,0:00:08.90,0:00:09.21,Default,,100,100,0,,{\an5}file +Dialogue: 0,0:00:09.21,0:00:09.46,Default,,100,100,0,,{\an5}into +Dialogue: 0,0:00:09.46,0:00:10.34,Default,,100,100,0,,{\an5}audio +Dialogue: 0,0:00:10.34,0:00:10.56,Default,,100,100,0,,{\an5}with +Dialogue: 0,0:00:10.56,0:00:11.17,Default,,100,100,0,,{\an5}subtitles +Dialogue: 0,0:00:11.17,0:00:11.32,Default,,100,100,0,,{\an5}that +Dialogue: 0,0:00:11.32,0:00:11.51,Default,,100,100,0,,{\an5}light +Dialogue: 0,0:00:11.51,0:00:11.71,Default,,100,100,0,,{\an5}up +Dialogue: 0,0:00:11.71,0:00:12.05,Default,,100,100,0,,{\an5}every +Dialogue: 0,0:00:12.05,0:00:12.40,Default,,100,100,0,,{\an5}word +Dialogue: 0,0:00:12.40,0:00:12.51,Default,,100,100,0,,{\an5}as +Dialogue: 0,0:00:12.51,0:00:12.65,Default,,100,100,0,,{\an5}it’s +Dialogue: 0,0:00:12.65,0:00:14.21,Default,,100,100,0,,{\an5}spoken. +Dialogue: 0,0:00:14.21,0:00:14.32,Default,,100,100,0,,{\an5}The +Dialogue: 0,0:00:14.32,0:00:14.62,Default,,100,100,0,,{\an5}voice +Dialogue: 0,0:00:14.62,0:00:14.77,Default,,100,100,0,,{\an5}you +Dialogue: 0,0:00:14.77,0:00:15.65,Default,,100,100,0,,{\an5}hear, +Dialogue: 0,0:00:15.65,0:00:15.76,Default,,100,100,0,,{\an5}and +Dialogue: 0,0:00:15.76,0:00:15.86,Default,,100,100,0,,{\an5}the +Dialogue: 0,0:00:15.86,0:00:16.44,Default,,100,100,0,,{\an5}subtitles +Dialogue: 0,0:00:16.44,0:00:16.58,Default,,100,100,0,,{\an5}you +Dialogue: 0,0:00:16.58,0:00:17.03,Default,,100,100,0,,{\an5}see +Dialogue: 0,0:00:17.03,0:00:17.14,Default,,100,100,0,,{\an5}are +Dialogue: 0,0:00:17.14,0:00:17.44,Default,,100,100,0,,{\an5}both +Dialogue: 0,0:00:17.44,0:00:17.91,Default,,100,100,0,,{\an5}created +Dialogue: 0,0:00:17.91,0:00:18.96,Default,,100,100,0,,{\an5}automatically +Dialogue: 0,0:00:18.96,0:00:19.14,Default,,100,100,0,,{\an5}by +Dialogue: 0,0:00:19.14,0:00:19.64,Default,,100,100,0,,{\an5}Abogen +Dialogue: 0,0:00:19.64,0:00:20.00,Default,,100,100,0,,{\an5}using +Dialogue: 0,0:00:20.00,0:00:21.14,Default,,100,100,0,,{\an5}Kokoro, +Dialogue: 0,0:00:21.14,0:00:21.28,Default,,100,100,0,,{\an5}an +Dialogue: 0,0:00:21.28,0:00:21.88,Default,,100,100,0,,{\an5}open-weight +Dialogue: 0,0:00:21.88,0:00:22.52,Default,,100,100,0,,{\an5}TTS +Dialogue: 0,0:00:22.52,0:00:23.18,Default,,100,100,0,,{\an5}model +Dialogue: 0,0:00:23.18,0:00:23.38,Default,,100,100,0,,{\an5}with +Dialogue: 0,0:00:23.38,0:00:24.01,Default,,100,100,0,,{\an5}82 +Dialogue: 0,0:00:24.01,0:00:24.37,Default,,100,100,0,,{\an5}million +Dialogue: 0,0:00:24.37,0:00:25.98,Default,,100,100,0,,{\an5}parameters. +Dialogue: 0,0:00:25.98,0:00:26.47,Default,,100,100,0,,{\an5}Everything +Dialogue: 0,0:00:26.47,0:00:26.77,Default,,100,100,0,,{\an5}works +Dialogue: 0,0:00:26.77,0:00:27.01,Default,,100,100,0,,{\an5}right +Dialogue: 0,0:00:27.01,0:00:27.11,Default,,100,100,0,,{\an5}on +Dialogue: 0,0:00:27.11,0:00:27.22,Default,,100,100,0,,{\an5}your +Dialogue: 0,0:00:27.22,0:00:28.23,Default,,100,100,0,,{\an5}computer, +Dialogue: 0,0:00:28.23,0:00:28.37,Default,,100,100,0,,{\an5}so +Dialogue: 0,0:00:28.37,0:00:28.53,Default,,100,100,0,,{\an5}your +Dialogue: 0,0:00:28.53,0:00:28.94,Default,,100,100,0,,{\an5}files +Dialogue: 0,0:00:28.94,0:00:29.23,Default,,100,100,0,,{\an5}stay +Dialogue: 0,0:00:29.23,0:00:29.88,Default,,100,100,0,,{\an5}private +Dialogue: 0,0:00:29.88,0:00:30.05,Default,,100,100,0,,{\an5}and +Dialogue: 0,0:00:30.05,0:00:31.16,Default,,100,100,0,,{\an5}secure. +Dialogue: 0,0:00:31.50,0:00:31.63,Default,,100,100,0,,{\an5}The +Dialogue: 0,0:00:31.63,0:00:31.97,Default,,100,100,0,,{\an5}easy +Dialogue: 0,0:00:31.97,0:00:32.54,Default,,100,100,0,,{\an5}interface +Dialogue: 0,0:00:32.54,0:00:32.81,Default,,100,100,0,,{\an5}lets +Dialogue: 0,0:00:32.81,0:00:33.23,Default,,100,100,0,,{\an5}anyone +Dialogue: 0,0:00:33.23,0:00:33.66,Default,,100,100,0,,{\an5}create +Dialogue: 0,0:00:33.66,0:00:34.88,Default,,100,100,0,,{\an5}audiobooks, +Dialogue: 0,0:00:34.88,0:00:35.54,Default,,100,100,0,,{\an5}voiceovers +Dialogue: 0,0:00:35.54,0:00:35.70,Default,,100,100,0,,{\an5}for +Dialogue: 0,0:00:35.70,0:00:36.68,Default,,100,100,0,,{\an5}Instagram, +Dialogue: 0,0:00:36.68,0:00:37.23,Default,,100,100,0,,{\an5}YouTube +Dialogue: 0,0:00:37.23,0:00:37.41,Default,,100,100,0,,{\an5}or +Dialogue: 0,0:00:37.41,0:00:38.52,Default,,100,100,0,,{\an5}TikTok, +Dialogue: 0,0:00:38.52,0:00:38.64,Default,,100,100,0,,{\an5}or +Dialogue: 0,0:00:38.64,0:00:38.89,Default,,100,100,0,,{\an5}just +Dialogue: 0,0:00:38.89,0:00:39.57,Default,,100,100,0,,{\an5}high-quality +Dialogue: 0,0:00:39.57,0:00:40.39,Default,,100,100,0,,{\an5}text-to-speech +Dialogue: 0,0:00:40.39,0:00:40.56,Default,,100,100,0,,{\an5}in +Dialogue: 0,0:00:40.56,0:00:42.03,Default,,100,100,0,,{\an5}seconds. +Dialogue: 0,0:00:42.03,0:00:42.32,Default,,100,100,0,,{\an5}Whether +Dialogue: 0,0:00:42.32,0:00:42.47,Default,,100,100,0,,{\an5}you +Dialogue: 0,0:00:42.47,0:00:42.67,Default,,100,100,0,,{\an5}want +Dialogue: 0,0:00:42.67,0:00:42.77,Default,,100,100,0,,{\an5}to +Dialogue: 0,0:00:42.77,0:00:43.43,Default,,100,100,0,,{\an5}listen, +Dialogue: 0,0:00:43.43,0:00:43.77,Default,,100,100,0,,{\an5}create +Dialogue: 0,0:00:43.77,0:00:44.70,Default,,100,100,0,,{\an5}content, +Dialogue: 0,0:00:44.70,0:00:44.85,Default,,100,100,0,,{\an5}or +Dialogue: 0,0:00:44.85,0:00:45.07,Default,,100,100,0,,{\an5}bring +Dialogue: 0,0:00:45.07,0:00:45.22,Default,,100,100,0,,{\an5}your +Dialogue: 0,0:00:45.22,0:00:45.54,Default,,100,100,0,,{\an5}words +Dialogue: 0,0:00:45.54,0:00:45.67,Default,,100,100,0,,{\an5}to +Dialogue: 0,0:00:45.67,0:00:46.58,Default,,100,100,0,,{\an5}life, +Dialogue: 0,0:00:46.58,0:00:47.10,Default,,100,100,0,,{\an5}Abogen +Dialogue: 0,0:00:47.10,0:00:47.37,Default,,100,100,0,,{\an5}makes +Dialogue: 0,0:00:47.37,0:00:47.53,Default,,100,100,0,,{\an5}it +Dialogue: 0,0:00:47.53,0:00:48.31,Default,,100,100,0,,{\an5}fast, +Dialogue: 0,0:00:48.31,0:00:48.89,Default,,100,100,0,,{\an5}fun, +Dialogue: 0,0:00:48.89,0:00:49.02,Default,,100,100,0,,{\an5}and +Dialogue: 0,0:00:49.02,0:00:50.62,Default,,100,100,0,,{\an5}effortless. +Dialogue: 0,0:00:50.62,0:00:51.35,Default,,100,100,0,,{\an5}Experience +Dialogue: 0,0:00:51.35,0:00:51.47,Default,,100,100,0,,{\an5}your +Dialogue: 0,0:00:51.47,0:00:52.23,Default,,100,100,0,,{\an5}stories, +Dialogue: 0,0:00:52.23,0:00:52.97,Default,,100,100,0,,{\an5}scripts, +Dialogue: 0,0:00:52.97,0:00:53.11,Default,,100,100,0,,{\an5}and +Dialogue: 0,0:00:53.11,0:00:53.64,Default,,100,100,0,,{\an5}ideas +Dialogue: 0,0:00:53.64,0:00:53.73,Default,,100,100,0,,{\an5}in +Dialogue: 0,0:00:53.73,0:00:53.83,Default,,100,100,0,,{\an5}a +Dialogue: 0,0:00:53.83,0:00:54.14,Default,,100,100,0,,{\an5}whole +Dialogue: 0,0:00:54.14,0:00:54.35,Default,,100,100,0,,{\an5}new +Dialogue: 0,0:00:54.35,0:00:54.64,Default,,100,100,0,,{\an5}way +Dialogue: 0,0:00:54.64,0:00:54.79,Default,,100,100,0,,{\an5}with +Dialogue: 0,0:00:54.79,0:00:55.85,Default,,100,100,0,,{\an5}Abogen. diff --git a/demo/demo.srt b/demo/demo.srt deleted file mode 100644 index 40829cd..0000000 --- a/demo/demo.srt +++ /dev/null @@ -1,480 +0,0 @@ -1 -00:00:00,350 --> 00:00:00,962 -Abogen - -2 -00:00:00,962 --> 00:00:01,100 -is - -3 -00:00:01,100 --> 00:00:01,300 -your - -4 -00:00:01,300 --> 00:00:01,887 -all-in-one - -5 -00:00:01,887 --> 00:00:02,250 -tool - -6 -00:00:02,250 --> 00:00:02,375 -for - -7 -00:00:02,375 --> 00:00:02,762 -turning - -8 -00:00:02,762 --> 00:00:03,162 -text - -9 -00:00:03,162 --> 00:00:03,375 -into - -10 -00:00:03,375 --> 00:00:03,725 -something - -11 -00:00:03,725 --> 00:00:03,825 -you - -12 -00:00:03,825 --> 00:00:04,025 -can - -13 -00:00:04,025 --> 00:00:04,312 -see - -14 -00:00:04,312 --> 00:00:04,450 -and - -15 -00:00:04,450 --> 00:00:05,450 -hear. - -16 -00:00:05,450 --> 00:00:05,900 -Easily - -17 -00:00:05,900 --> 00:00:06,337 -convert - -18 -00:00:06,337 --> 00:00:07,187 -ePub, - -19 -00:00:07,187 --> 00:00:08,250 -PDF, - -20 -00:00:08,250 --> 00:00:08,412 -or - -21 -00:00:08,412 --> 00:00:08,800 -text - -22 -00:00:08,800 --> 00:00:09,162 -file - -23 -00:00:09,162 --> 00:00:09,425 -into - -24 -00:00:09,425 --> 00:00:10,037 -audio - -25 -00:00:10,037 --> 00:00:10,237 -with - -26 -00:00:10,237 --> 00:00:10,875 -subtitles - -27 -00:00:10,875 --> 00:00:11,087 -that - -28 -00:00:11,087 --> 00:00:11,287 -light - -29 -00:00:11,287 --> 00:00:11,487 -up - -30 -00:00:11,487 --> 00:00:11,824 -every - -31 -00:00:11,824 --> 00:00:12,175 -word - -32 -00:00:12,175 --> 00:00:12,300 -as - -33 -00:00:12,300 --> 00:00:12,449 -it’s - -34 -00:00:12,449 --> 00:00:13,787 -spoken. - -35 -00:00:13,787 --> 00:00:13,925 -The - -36 -00:00:13,925 --> 00:00:14,275 -voice - -37 -00:00:14,275 --> 00:00:14,425 -you - -38 -00:00:14,425 --> 00:00:15,212 -hear, - -39 -00:00:15,212 --> 00:00:15,337 -and - -40 -00:00:15,337 --> 00:00:15,437 -the - -41 -00:00:15,437 --> 00:00:16,024 -subtitles - -42 -00:00:16,024 --> 00:00:16,162 -you - -43 -00:00:16,162 --> 00:00:16,487 -see - -44 -00:00:16,487 --> 00:00:16,649 -are - -45 -00:00:16,649 --> 00:00:16,975 -both - -46 -00:00:16,975 --> 00:00:17,449 -created - -47 -00:00:17,449 --> 00:00:18,362 -automatically - -48 -00:00:18,362 --> 00:00:18,550 -by - -49 -00:00:18,550 --> 00:00:19,074 -Abogen - -50 -00:00:19,074 --> 00:00:19,425 -using - -51 -00:00:19,425 --> 00:00:20,512 -Kokoro, - -52 -00:00:20,512 --> 00:00:20,637 -a - -53 -00:00:20,637 --> 00:00:21,237 -powerful - -54 -00:00:21,237 --> 00:00:22,050 -text-to-speech - -55 -00:00:22,050 --> 00:00:23,225 -engine. - -56 -00:00:23,225 --> 00:00:23,750 -Everything - -57 -00:00:23,750 --> 00:00:24,062 -works - -58 -00:00:24,062 --> 00:00:24,312 -right - -59 -00:00:24,312 --> 00:00:24,412 -on - -60 -00:00:24,412 --> 00:00:24,524 -your - -61 -00:00:24,524 --> 00:00:25,437 -computer, - -62 -00:00:25,437 --> 00:00:25,625 -so - -63 -00:00:25,625 --> 00:00:25,812 -your - -64 -00:00:25,812 --> 00:00:26,225 -files - -65 -00:00:26,225 --> 00:00:26,524 -stay - -66 -00:00:26,524 --> 00:00:27,112 -private - -67 -00:00:27,112 --> 00:00:27,274 -and - -68 -00:00:27,274 --> 00:00:28,337 -secure. - -69 -00:00:28,725 --> 00:00:28,862 -The - -70 -00:00:28,862 --> 00:00:29,225 -easy - -71 -00:00:29,225 --> 00:00:29,824 -interface - -72 -00:00:29,824 --> 00:00:30,112 -lets - -73 -00:00:30,112 --> 00:00:30,637 -anyone - -74 -00:00:30,637 --> 00:00:31,062 -create - -75 -00:00:31,062 --> 00:00:32,299 -audiobooks, - -76 -00:00:32,299 --> 00:00:33,075 -voiceovers - -77 -00:00:33,075 --> 00:00:33,237 -for - -78 -00:00:33,237 --> 00:00:34,325 -Instagram, - -79 -00:00:34,325 --> 00:00:34,962 -YouTube - -80 -00:00:34,962 --> 00:00:35,137 -or - -81 -00:00:35,137 --> 00:00:35,950 -TikTok, - -82 -00:00:35,950 --> 00:00:36,100 -or - -83 -00:00:36,100 --> 00:00:36,325 -just - -84 -00:00:36,325 --> 00:00:37,024 -high-quality - -85 -00:00:37,024 --> 00:00:37,850 -text-to-speech - -86 -00:00:37,850 --> 00:00:38,000 -in - -87 -00:00:38,000 --> 00:00:39,187 -seconds. - -88 -00:00:39,187 --> 00:00:39,475 -Whether - -89 -00:00:39,475 --> 00:00:39,625 -you - -90 -00:00:39,625 --> 00:00:39,825 -want - -91 -00:00:39,825 --> 00:00:39,924 -to - -92 -00:00:39,924 --> 00:00:40,662 -listen, - -93 -00:00:40,662 --> 00:00:41,000 -create - -94 -00:00:41,000 --> 00:00:41,774 -content, - -95 -00:00:41,774 --> 00:00:41,924 -or - -96 -00:00:41,924 --> 00:00:42,137 -bring - -97 -00:00:42,137 --> 00:00:42,250 -your - -98 -00:00:42,250 --> 00:00:42,575 -words - -99 -00:00:42,575 --> 00:00:42,700 -to - -100 -00:00:42,700 --> 00:00:43,512 -life, - -101 -00:00:43,512 --> 00:00:44,075 -Abogen - -102 -00:00:44,075 --> 00:00:44,350 -makes - -103 -00:00:44,350 --> 00:00:44,525 -it - -104 -00:00:44,525 --> 00:00:45,299 -fast, - -105 -00:00:45,299 --> 00:00:45,825 -fun, - -106 -00:00:45,825 --> 00:00:45,950 -and - -107 -00:00:45,950 --> 00:00:47,275 -effortless. - -108 -00:00:47,275 --> 00:00:47,962 -Experience - -109 -00:00:47,962 --> 00:00:48,075 -your - -110 -00:00:48,075 --> 00:00:48,812 -stories, - -111 -00:00:48,812 --> 00:00:49,350 -scripts, - -112 -00:00:49,350 --> 00:00:49,500 -and - -113 -00:00:49,500 --> 00:00:50,075 -ideas - -114 -00:00:50,075 --> 00:00:50,174 -in - -115 -00:00:50,174 --> 00:00:50,299 -a - -116 -00:00:50,299 --> 00:00:50,662 -whole - -117 -00:00:50,662 --> 00:00:50,850 -new - -118 -00:00:50,850 --> 00:00:51,150 -way - -119 -00:00:51,150 --> 00:00:51,299 -with - -120 -00:00:51,299 --> 00:00:52,424 -Abogen. -