Corrections to code logic

This commit is contained in:
Deniz Şafak
2025-04-27 05:09:52 +03:00
parent 52f2dbbec1
commit d67cfce896
8 changed files with 190 additions and 163 deletions
-9
View File
@@ -1,9 +0,0 @@
from abogen.utils import get_version
# Program Information
PROGRAM_NAME = "abogen"
PROGRAM_DESCRIPTION = (
"Generate audiobooks from EPUBs, PDFs and text with synchronized captions."
)
GITHUB_URL = "https://github.com/denizsafak/abogen"
VERSION = get_version()
+1 -1
View File
@@ -21,7 +21,7 @@ from PyQt5.QtWidgets import (
QMenu, QMenu,
) )
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from abogen.utils import clean_text, calculate_text_length from utils import clean_text, calculate_text_length
import os import os
+117
View File
@@ -0,0 +1,117 @@
from utils import get_version, get_user_config_path
# Program Information
PROGRAM_NAME = "abogen"
PROGRAM_DESCRIPTION = (
"Generate audiobooks from EPUBs, PDFs and text with synchronized captions."
)
GITHUB_URL = "https://github.com/denizsafak/abogen"
VERSION = get_version()
# Language description mapping
LANGUAGE_DESCRIPTIONS = {
"a": "American English",
"b": "British English",
"e": "Spanish",
"f": "French",
"h": "Hindi",
"i": "Italian",
"j": "Japanese",
"p": "Brazilian Portuguese",
"z": "Mandarin Chinese",
}
# Supported languages for subtitle generation
# Currently, only 'a (American English)' and 'b (British English)' are supported for subtitle generation.
# This is because tokens that contain timestamps are not generated for other languages in the Kokoro pipeline.
# Please refer to: https://github.com/hexgrad/kokoro/blob/6d87f4ae7abc2d14dbc4b3ef2e5f19852e861ac2/kokoro/pipeline.py
# 383 English processing (unchanged)
# 384 if self.lang_code in 'ab':
SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION = [
"a",
"b",
]
# Voice and sample text constants
VOICES_INTERNAL = [
"af_alloy",
"af_aoede",
"af_bella",
"af_heart",
"af_jessica",
"af_kore",
"af_nicole",
"af_nova",
"af_river",
"af_sarah",
"af_sky",
"am_adam",
"am_echo",
"am_eric",
"am_fenrir",
"am_liam",
"am_michael",
"am_onyx",
"am_puck",
"am_santa",
"bf_alice",
"bf_emma",
"bf_isabella",
"bf_lily",
"bm_daniel",
"bm_fable",
"bm_george",
"bm_lewis",
"ef_dora",
"em_alex",
"em_santa",
"ff_siwis",
"hf_alpha",
"hf_beta",
"hm_omega",
"hm_psi",
"if_sara",
"im_nicola",
"jf_alpha",
"jf_gongitsune",
"jf_nezumi",
"jf_tebukuro",
"jm_kumo",
"pf_dora",
"pm_alex",
"pm_santa",
"zf_xiaobei",
"zf_xiaoni",
"zf_xiaoxiao",
"zf_xiaoyi",
"zm_yunjian",
"zm_yunxi",
"zm_yunxia",
"zm_yunyang",
]
# Voice and sample text mapping
SAMPLE_VOICE_TEXTS = {
"a": "This is a sample of the selected voice.",
"b": "This is a sample of the selected voice.",
"e": "Este es una muestra de la voz seleccionada.",
"f": "Ceci est un exemple de la voix sélectionnée.",
"h": "यह चयनित आवाज़ का एक नमूना है।",
"i": "Questo è un esempio della voce selezionata.",
"j": "これは選択した声のサンプルです。",
"p": "Este é um exemplo da voz selecionada.",
"z": "这是所选语音的示例。",
}
# flags mapping for voice display
FLAGS = {
"a": "🇺🇸",
"b": "🇬🇧",
"e": "🇪🇸",
"f": "🇫🇷",
"h": "🇮🇳",
"i": "🇮🇹",
"j": "🇯🇵",
"p": "🇧🇷",
"z": "🇨🇳",
}
+3 -2
View File
@@ -5,8 +5,8 @@ import time
from PyQt5.QtCore import QThread, pyqtSignal, Qt from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import QCheckBox, QVBoxLayout, QDialog, QLabel, QDialogButtonBox from PyQt5.QtWidgets import QCheckBox, QVBoxLayout, QDialog, QLabel, QDialogButtonBox
import soundfile as sf import soundfile as sf
from abogen.utils import clean_text, SAMPLE_VOICE_TEXTS, LANGUAGE_DESCRIPTIONS from utils import clean_text
from abogen import PROGRAM_NAME from constants import PROGRAM_NAME, LANGUAGE_DESCRIPTIONS, SAMPLE_VOICE_TEXTS
def get_sample_voice_text(lang_code): def get_sample_voice_text(lang_code):
@@ -688,6 +688,7 @@ class PlayAudioThread(QThread):
try: try:
import pygame import pygame
import time as _time import time as _time
pygame.mixer.init() pygame.mixer.init()
pygame.mixer.music.load(self.wav_path) pygame.mixer.music.load(self.wav_path)
pygame.mixer.music.play() pygame.mixer.music.play()
+13 -7
View File
@@ -46,7 +46,7 @@ from PyQt5.QtGui import (
QColor, QColor,
QMovie, QMovie,
) )
from abogen.utils import ( from utils import (
load_config, load_config,
save_config, save_config,
get_gpu_acceleration, get_gpu_acceleration,
@@ -56,14 +56,19 @@ from abogen.utils import (
calculate_text_length, calculate_text_length,
get_resource_path, get_resource_path,
LoadPipelineThread, LoadPipelineThread,
)
from conversion import ConversionThread, VoicePreviewThread, PlayAudioThread
from book_handler import HandlerDialog
from constants import (
PROGRAM_NAME,
VERSION,
GITHUB_URL,
PROGRAM_DESCRIPTION,
LANGUAGE_DESCRIPTIONS,
FLAGS, FLAGS,
VOICES_INTERNAL, VOICES_INTERNAL,
SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION, SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION,
LANGUAGE_DESCRIPTIONS,
) )
from abogen.conversion import ConversionThread, VoicePreviewThread, PlayAudioThread
from abogen.book_handler import HandlerDialog
from abogen import PROGRAM_NAME, VERSION, GITHUB_URL, PROGRAM_DESCRIPTION
from threading import Thread from threading import Thread
# Import ctypes for Windows-specific taskbar icon # Import ctypes for Windows-specific taskbar icon
@@ -1271,6 +1276,7 @@ class abogen(QWidget):
if self.preview_playing: if self.preview_playing:
try: try:
import pygame import pygame
pygame.mixer.music.stop() pygame.mixer.music.stop()
except Exception: except Exception:
pass pass
@@ -1470,7 +1476,7 @@ class abogen(QWidget):
def show_chapter_options_dialog(self, chapter_count): def show_chapter_options_dialog(self, chapter_count):
"""Show dialog to ask user about chapter processing options when chapters are detected in a .txt file""" """Show dialog to ask user about chapter processing options when chapters are detected in a .txt file"""
from abogen.conversion import ChapterOptionsDialog from conversion import ChapterOptionsDialog
dialog = ChapterOptionsDialog(chapter_count, parent=self) dialog = ChapterOptionsDialog(chapter_count, parent=self)
dialog.setWindowModality(Qt.ApplicationModal) dialog.setWindowModality(Qt.ApplicationModal)
@@ -1539,7 +1545,7 @@ class abogen(QWidget):
def reveal_config_in_explorer(self): def reveal_config_in_explorer(self):
"""Open the configuration file location in file explorer.""" """Open the configuration file location in file explorer."""
from abogen.utils import get_user_config_path from utils import get_user_config_path
try: try:
config_path = get_user_config_path() config_path = get_user_config_path()
+11 -4
View File
@@ -3,9 +3,12 @@ import sys
import platform import platform
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from abogen.gui import abogen
from abogen.utils import get_resource_path # Add the directory to Python path
from abogen import PROGRAM_NAME, VERSION sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
from gui import abogen
from utils import get_resource_path
from constants import PROGRAM_NAME, VERSION
# Ensure sys.stdout and sys.stderr are valid in GUI mode # Ensure sys.stdout and sys.stderr are valid in GUI mode
if sys.stdout is None: if sys.stdout is None:
@@ -28,7 +31,11 @@ if platform.system() == "Windows":
if platform.system() == "Linux": if platform.system() == "Linux":
xdg_session = os.environ.get("XDG_SESSION_TYPE", "").lower() xdg_session = os.environ.get("XDG_SESSION_TYPE", "").lower()
desktop = os.environ.get("XDG_CURRENT_DESKTOP", "").lower() desktop = os.environ.get("XDG_CURRENT_DESKTOP", "").lower()
if "gnome" in desktop and xdg_session == "wayland" and "QT_QPA_PLATFORM" not in os.environ: if (
"gnome" in desktop
and xdg_session == "wayland"
and "QT_QPA_PLATFORM" not in os.environ
):
os.environ["QT_QPA_PLATFORM"] = "wayland" os.environ["QT_QPA_PLATFORM"] = "wayland"
+2 -112
View File
@@ -10,114 +10,6 @@ from threading import Thread
os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1" os.environ["HF_HUB_DISABLE_SYMLINKS_WARNING"] = "1"
warnings.filterwarnings("ignore") warnings.filterwarnings("ignore")
# Language description mapping
LANGUAGE_DESCRIPTIONS = {
"a": "American English",
"b": "British English",
"e": "Spanish",
"f": "French",
"h": "Hindi",
"i": "Italian",
"j": "Japanese",
"p": "Brazilian Portuguese",
"z": "Mandarin Chinese",
}
# Supported languages for subtitle generation
# Currently, only 'a (American English)' and 'b (British English)' are supported for subtitle generation.
# This is because tokens that contain timestamps are not generated for other languages in the Kokoro pipeline.
# Please refer to: https://github.com/hexgrad/kokoro/blob/6d87f4ae7abc2d14dbc4b3ef2e5f19852e861ac2/kokoro/pipeline.py
# 383 English processing (unchanged)
# 384 if self.lang_code in 'ab':
SUPPORTED_LANGUAGES_FOR_SUBTITLE_GENERATION = [
"a",
"b",
]
# Voice and sample text constants
VOICES_INTERNAL = [
"af_alloy",
"af_aoede",
"af_bella",
"af_heart",
"af_jessica",
"af_kore",
"af_nicole",
"af_nova",
"af_river",
"af_sarah",
"af_sky",
"am_adam",
"am_echo",
"am_eric",
"am_fenrir",
"am_liam",
"am_michael",
"am_onyx",
"am_puck",
"am_santa",
"bf_alice",
"bf_emma",
"bf_isabella",
"bf_lily",
"bm_daniel",
"bm_fable",
"bm_george",
"bm_lewis",
"ef_dora",
"em_alex",
"em_santa",
"ff_siwis",
"hf_alpha",
"hf_beta",
"hm_omega",
"hm_psi",
"if_sara",
"im_nicola",
"jf_alpha",
"jf_gongitsune",
"jf_nezumi",
"jf_tebukuro",
"jm_kumo",
"pf_dora",
"pm_alex",
"pm_santa",
"zf_xiaobei",
"zf_xiaoni",
"zf_xiaoxiao",
"zf_xiaoyi",
"zm_yunjian",
"zm_yunxi",
"zm_yunxia",
"zm_yunyang",
]
# Voice and sample text mapping
SAMPLE_VOICE_TEXTS = {
"a": "This is a sample of the selected voice.",
"b": "This is a sample of the selected voice.",
"e": "Este es una muestra de la voz seleccionada.",
"f": "Ceci est un exemple de la voix sélectionnée.",
"h": "यह चयनित आवाज़ का एक नमूना है।",
"i": "Questo è un esempio della voce selezionata.",
"j": "これは選択した声のサンプルです。",
"p": "Este é um exemplo da voz selecionada.",
"z": "这是所选语音的示例。",
}
# flags mapping for voice display
FLAGS = {
"a": "🇺🇸",
"b": "🇬🇧",
"e": "🇪🇸",
"f": "🇫🇷",
"h": "🇮🇳",
"i": "🇮🇹",
"j": "🇯🇵",
"p": "🇧🇷",
"z": "🇨🇳",
}
def get_resource_path(package, resource): def get_resource_path(package, resource):
""" """
@@ -174,8 +66,6 @@ def get_user_config_path():
return os.path.join(config_dir, "config.json") return os.path.join(config_dir, "config.json")
CONFIG_PATH = get_user_config_path()
_sleep_procs = {"Darwin": None, "Linux": None} # Store sleep prevention processes _sleep_procs = {"Darwin": None, "Linux": None} # Store sleep prevention processes
@@ -193,7 +83,7 @@ def clean_text(text):
def load_config(): def load_config():
try: try:
with open(CONFIG_PATH, "r", encoding="utf-8") as f: with open(get_user_config_path(), "r", encoding="utf-8") as f:
return json.load(f) return json.load(f)
except Exception: except Exception:
return {} return {}
@@ -201,7 +91,7 @@ def load_config():
def save_config(config): def save_config(config):
try: try:
with open(CONFIG_PATH, "w", encoding="utf-8") as f: with open(get_user_config_path(), "w", encoding="utf-8") as f:
json.dump(config, f, indent=2) json.dump(config, f, indent=2)
except Exception: except Exception:
pass pass
+29 -14
View File
@@ -13,23 +13,27 @@ import re
import sys import sys
from datetime import timedelta from datetime import timedelta
def parse_time(s): def parse_time(s):
# For ASS format # For ASS format
if ':' in s and '.' in s: if ":" in s and "." in s:
h, m, s = s.split(':') h, m, s = s.split(":")
sec, cs = s.split('.') sec, cs = s.split(".")
return timedelta(hours=int(h), minutes=int(m), seconds=int(sec), milliseconds=int(cs) * 10) return timedelta(
hours=int(h), minutes=int(m), seconds=int(sec), milliseconds=int(cs) * 10
)
# For SRT format (00:00:00,000) # For SRT format (00:00:00,000)
elif ':' in s and ',' in s: elif ":" in s and "," in s:
parts = s.split(':') parts = s.split(":")
h = int(parts[0]) h = int(parts[0])
m = int(parts[1]) m = int(parts[1])
sec_parts = parts[2].split(',') sec_parts = parts[2].split(",")
sec = int(sec_parts[0]) sec = int(sec_parts[0])
ms = int(sec_parts[1]) ms = int(sec_parts[1])
return timedelta(hours=h, minutes=m, seconds=sec, milliseconds=ms) return timedelta(hours=h, minutes=m, seconds=sec, milliseconds=ms)
return None return None
def format_time(t): def format_time(t):
total_seconds = int(t.total_seconds()) total_seconds = int(t.total_seconds())
cs = int((t.total_seconds() - total_seconds) * 100) cs = int((t.total_seconds() - total_seconds) * 100)
@@ -38,6 +42,7 @@ def format_time(t):
s = total_seconds % 60 s = total_seconds % 60
return f"{h}:{m:02}:{s:02}.{cs:02}" return f"{h}:{m:02}:{s:02}.{cs:02}"
# Desired script info and style # Desired script info and style
DESIRED_SCRIPT_INFO = """[Script Info] DESIRED_SCRIPT_INFO = """[Script Info]
Title: Centered Subs Title: Centered Subs
@@ -51,8 +56,9 @@ Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour,
Style: Default,Arial,60,&H00FFFFFF,&H000000FF,&H00000000,&H64000000,-1,0,0,0,100,100,0,0,1,2,0,5,10,10,10,1 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): def process_subtitle_file(input_file):
output_file = input_file.replace('.ass', '_demo.ass').replace('.srt', '_demo.ass') output_file = input_file.replace(".ass", "_demo.ass").replace(".srt", "_demo.ass")
if output_file == input_file: if output_file == input_file:
output_file = f"{input_file}_demo.ass" output_file = f"{input_file}_demo.ass"
@@ -67,7 +73,7 @@ def process_subtitle_file(input_file):
sys.exit(1) sys.exit(1)
# Check if it's an SRT file # Check if it's an SRT file
is_srt = input_file.lower().endswith('.srt') is_srt = input_file.lower().endswith(".srt")
if is_srt: if is_srt:
return convert_srt_to_ass(input_file, output_file, lines) return convert_srt_to_ass(input_file, output_file, lines)
@@ -124,9 +130,12 @@ def process_subtitle_file(input_file):
if end > next_start: if end > next_start:
end = next_start # cut current subtitle to stop at next one's start end = next_start # cut current subtitle to stop at next one's start
fixed_line = re.sub(r"Dialogue: \d,[^,]+,[^,]+,", fixed_line = re.sub(
f"Dialogue: 0,{format_time(start)},{format_time(end)},", r"Dialogue: \d,[^,]+,[^,]+,",
line, count=1) f"Dialogue: 0,{format_time(start)},{format_time(end)},",
line,
count=1,
)
fixed_lines.append(fixed_line) fixed_lines.append(fixed_line)
try: try:
@@ -138,6 +147,7 @@ def process_subtitle_file(input_file):
print(f"❌ Error writing output file: {e}") print(f"❌ Error writing output file: {e}")
sys.exit(1) sys.exit(1)
def convert_srt_to_ass(input_file, output_file, lines): def convert_srt_to_ass(input_file, output_file, lines):
"""Convert SRT format to ASS format.""" """Convert SRT format to ASS format."""
events = [] events = []
@@ -167,7 +177,9 @@ def convert_srt_to_ass(input_file, output_file, lines):
timing_line = block[1] timing_line = block[1]
# Parse timing information # Parse timing information
timing_match = re.match(r'(\d+:\d+:\d+,\d+)\s+-->\s+(\d+:\d+:\d+,\d+)', timing_line) timing_match = re.match(
r"(\d+:\d+:\d+,\d+)\s+-->\s+(\d+:\d+:\d+,\d+)", timing_line
)
if not timing_match: if not timing_match:
continue continue
@@ -187,7 +199,9 @@ def convert_srt_to_ass(input_file, output_file, lines):
f.write(DESIRED_SCRIPT_INFO) f.write(DESIRED_SCRIPT_INFO)
f.write(DESIRED_STYLE) f.write(DESIRED_STYLE)
f.write("[Events]\n") f.write("[Events]\n")
f.write("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n") f.write(
"Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n"
)
f.writelines(events) f.writelines(events)
print(f"✅ Successfully converted SRT to ASS. Output file: {output_file}") print(f"✅ Successfully converted SRT to ASS. Output file: {output_file}")
return True return True
@@ -195,6 +209,7 @@ def convert_srt_to_ass(input_file, output_file, lines):
print(f"❌ Error writing output file: {e}") print(f"❌ Error writing output file: {e}")
sys.exit(1) sys.exit(1)
if __name__ == "__main__": if __name__ == "__main__":
if len(sys.argv) < 2: if len(sys.argv) < 2:
print("❌ Error: No input file specified") print("❌ Error: No input file specified")