mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 21:50:28 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
395cd75038 | ||
|
|
e60a1f7e42 | ||
|
|
1fe5a73c98 | ||
|
|
37db948d2f | ||
|
|
63679405a9 | ||
|
|
156fa75c76 |
+8
-1
@@ -1,3 +1,10 @@
|
|||||||
|
# 1.1.6
|
||||||
|
- Improved EPUB chapter detection: Now reliably detects chapters from NAV HTML (TOC) files, even in non-standard EPUBs, fixes the issue mentioned by @jefro108 in #33
|
||||||
|
- Fixed SRT subtitle numbering issue, mentioned by @page-muncher in #41
|
||||||
|
- Fixed missing chapter contents issue in some EPUB files.
|
||||||
|
- Windows installer script now prompts the user to install the CUDA version of PyTorch even if no NVIDIA GPU is detected.
|
||||||
|
- Abogen now includes Mandarin Chinese (misaki[zh]) by default; manual installation is no longer required.
|
||||||
|
|
||||||
# 1.1.5
|
# 1.1.5
|
||||||
- Changed the temporary directory path to user's cache directory, which is more appropriate for storing cache files and avoids issues with unintended cleanup.
|
- Changed the temporary directory path to user's cache directory, which is more appropriate for storing cache files and avoids issues with unintended cleanup.
|
||||||
- Fixed the isssue where extra metadata information was not being saved to M4B files when they have no chapters, ensuring that all metadata is correctly written to the output file.
|
- Fixed the isssue where extra metadata information was not being saved to M4B files when they have no chapters, ensuring that all metadata is correctly written to the output file.
|
||||||
@@ -23,7 +30,7 @@
|
|||||||
- Added a new option: `Reset to default settings`, allowing users to reset all settings to their default values.
|
- Added a new option: `Reset to default settings`, allowing users to reset all settings to their default values.
|
||||||
- Added a new option: `Disable Kokoro's internet access`. This lets you prevent Kokoro from downloading models or voices from HuggingFace Hub, which can help avoid long waiting times if your computer is offline.
|
- Added a new option: `Disable Kokoro's internet access`. This lets you prevent Kokoro from downloading models or voices from HuggingFace Hub, which can help avoid long waiting times if your computer is offline.
|
||||||
- HuggingFace Hub telemetry is now disabled by default for improved privacy. (HuggingFace Hub is used by Kokoro to download its models)
|
- HuggingFace Hub telemetry is now disabled by default for improved privacy. (HuggingFace Hub is used by Kokoro to download its models)
|
||||||
- Potential fix for #37 and #38, where the program was becoming slow while processing large files.
|
- cPotential fix for #37 and #38, where the program was becoming slow while processing large files.
|
||||||
- Fixed `Open folder` and `Open file` buttons in the queue manager GUI.
|
- Fixed `Open folder` and `Open file` buttons in the queue manager GUI.
|
||||||
- Improvements in code structure.
|
- Improvements in code structure.
|
||||||
|
|
||||||
|
|||||||
+22
-3
@@ -1,5 +1,5 @@
|
|||||||
@echo off
|
@echo off
|
||||||
setlocal
|
setlocal EnableDelayedExpansion
|
||||||
cd /d "%~dp0"
|
cd /d "%~dp0"
|
||||||
|
|
||||||
:: Set misaki language
|
:: Set misaki language
|
||||||
@@ -23,7 +23,6 @@ set CUDA_VERSION=128
|
|||||||
for /f "delims=: tokens=*" %%A in ('findstr /b ::: "%~f0"') do @echo(%%A
|
for /f "delims=: tokens=*" %%A in ('findstr /b ::: "%~f0"') do @echo(%%A
|
||||||
|
|
||||||
set CURRENT_DIR="%CD%"
|
set CURRENT_DIR="%CD%"
|
||||||
setlocal enabledelayedexpansion
|
|
||||||
set NAME=abogen
|
set NAME=abogen
|
||||||
set PROJECTFOLDER=abogen
|
set PROJECTFOLDER=abogen
|
||||||
set RUN=python_embedded\Scripts\abogen.exe
|
set RUN=python_embedded\Scripts\abogen.exe
|
||||||
@@ -276,6 +275,7 @@ if "%MISAKI_LANG%" NEQ "en" (
|
|||||||
for /f %%i in ('%PYTHON_CONSOLE_PATH% -c "from abogen.is_nvidia import check; print(check())"') do set IS_NVIDIA=%%i
|
for /f %%i in ('%PYTHON_CONSOLE_PATH% -c "from abogen.is_nvidia import check; print(check())"') do set IS_NVIDIA=%%i
|
||||||
|
|
||||||
:: Check if torch is installed with CUDA support
|
:: Check if torch is installed with CUDA support
|
||||||
|
echo.
|
||||||
echo Checking CUDA availability...
|
echo Checking CUDA availability...
|
||||||
if /I "%IS_NVIDIA%"=="true" (
|
if /I "%IS_NVIDIA%"=="true" (
|
||||||
for /f %%i in ('%PYTHON_CONSOLE_PATH% -c "from torch.cuda import is_available; print(is_available())"') do set cuda_available=%%i
|
for /f %%i in ('%PYTHON_CONSOLE_PATH% -c "from torch.cuda import is_available; print(is_available())"') do set cuda_available=%%i
|
||||||
@@ -293,7 +293,26 @@ if /I "%IS_NVIDIA%"=="true" (
|
|||||||
echo CUDA is available on NVIDIA GPU.
|
echo CUDA is available on NVIDIA GPU.
|
||||||
)
|
)
|
||||||
) else (
|
) else (
|
||||||
echo GPU is not NVIDIA. Skipping PyTorch CUDA installation.
|
echo.
|
||||||
|
echo Unable to detect an NVIDIA GPU in your system.
|
||||||
|
echo.
|
||||||
|
echo Do you want to install PyTorch anyway?
|
||||||
|
echo.
|
||||||
|
echo If you DO have an NVIDIA GPU, please press Y.
|
||||||
|
echo If you DO NOT have an NVIDIA GPU, please press N.
|
||||||
|
echo.
|
||||||
|
choice /C YN /M "Y=Yes, N=No"
|
||||||
|
if errorlevel 2 (
|
||||||
|
echo Skipping PyTorch installation.
|
||||||
|
) else (
|
||||||
|
echo Installing PyTorch with CUDA %CUDA_VERSION% support...
|
||||||
|
%PYTHON_CONSOLE_PATH% -m pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu%CUDA_VERSION% --no-warn-script-location
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo Failed to install PyTorch.
|
||||||
|
pause
|
||||||
|
exit /b
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
:: Ask user if they want to create a desktop shortcut
|
:: Ask user if they want to create a desktop shortcut
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1.1.5
|
1.1.6
|
||||||
+66
-55
@@ -324,14 +324,31 @@ class HandlerDialog(QDialog):
|
|||||||
f"Found NCX item via ITEM_NAVIGATION: {ncx_in_nav.get_name()}"
|
f"Found NCX item via ITEM_NAVIGATION: {ncx_in_nav.get_name()}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 3. If still no nav_item, check for ITEM_NCX directly
|
# 3. If still no nav_item, check for NCX or fallback to NAV HTML in all ITEM_DOCUMENTs
|
||||||
if not nav_item:
|
ncx_constant = getattr(epub, "ITEM_NCX", None)
|
||||||
ncx_items = list(self.book.get_items_of_type(ebooklib.ITEM_NCX))
|
if not nav_item and ncx_constant is not None:
|
||||||
|
ncx_items = list(self.book.get_items_of_type(ncx_constant))
|
||||||
if ncx_items:
|
if ncx_items:
|
||||||
nav_item = ncx_items[0] # Take the first one
|
nav_item = ncx_items[0]
|
||||||
nav_type = "ncx"
|
nav_type = "ncx"
|
||||||
logging.info(f"Found NCX item via ITEM_NCX: {ncx_items[0].get_name()}")
|
logging.info(f"Found NCX item via ITEM_NCX: {nav_item.get_name()}")
|
||||||
|
# Fallback: search all ITEM_DOCUMENTs for a NAV HTML with <nav epub:type="toc">
|
||||||
|
if not nav_item:
|
||||||
|
for item in self.book.get_items_of_type(ebooklib.ITEM_DOCUMENT):
|
||||||
|
try:
|
||||||
|
html_content = item.get_content().decode("utf-8", errors="ignore")
|
||||||
|
if "<nav" in html_content and 'epub:type="toc"' in html_content:
|
||||||
|
soup = BeautifulSoup(html_content, "html.parser")
|
||||||
|
nav_tag = soup.find("nav", attrs={"epub:type": "toc"})
|
||||||
|
if nav_tag:
|
||||||
|
nav_item = item
|
||||||
|
nav_type = "html"
|
||||||
|
logging.info(
|
||||||
|
f"Found NAV HTML with TOC in: {item.get_name()}"
|
||||||
|
)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
continue
|
||||||
# 4. If no navigation item found by any method, trigger fallback
|
# 4. If no navigation item found by any method, trigger fallback
|
||||||
if not nav_item or not nav_type:
|
if not nav_item or not nav_type:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
@@ -516,10 +533,14 @@ class HandlerDialog(QDialog):
|
|||||||
slice_html += self.doc_content.get(intermediate_doc_href, "")
|
slice_html += self.doc_content.get(intermediate_doc_href, "")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
# Fallback: if slice_html is empty, try to get the whole file's text
|
||||||
|
if not slice_html.strip() and current_doc_html:
|
||||||
|
logging.warning(
|
||||||
|
f"No content found for src '{current_src}', using full file as fallback."
|
||||||
|
)
|
||||||
|
slice_html = current_doc_html
|
||||||
if slice_html.strip():
|
if slice_html.strip():
|
||||||
slice_soup = BeautifulSoup(slice_html, "html.parser")
|
slice_soup = BeautifulSoup(slice_html, "html.parser")
|
||||||
# Add double newlines after <p> and <div> tags
|
|
||||||
for tag in slice_soup.find_all(["p", "div"]):
|
for tag in slice_soup.find_all(["p", "div"]):
|
||||||
tag.append("\n\n")
|
tag.append("\n\n")
|
||||||
for tag in slice_soup.find_all(["sup", "sub"]):
|
for tag in slice_soup.find_all(["sup", "sub"]):
|
||||||
@@ -581,6 +602,23 @@ class HandlerDialog(QDialog):
|
|||||||
f"Finished processing EPUB navigation. Found {len(self.content_texts)} content sections linked to TOC."
|
f"Finished processing EPUB navigation. Found {len(self.content_texts)} content sections linked to TOC."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _find_doc_key(self, base_href, doc_order, doc_order_decoded):
|
||||||
|
"""Find the best matching doc_key for a given base_href using robust matching."""
|
||||||
|
candidates = [
|
||||||
|
base_href,
|
||||||
|
urllib.parse.unquote(base_href),
|
||||||
|
]
|
||||||
|
base_name = os.path.basename(base_href).lower()
|
||||||
|
for k in list(doc_order.keys()) + list(doc_order_decoded.keys()):
|
||||||
|
if os.path.basename(k).lower() == base_name:
|
||||||
|
candidates.append(k)
|
||||||
|
for candidate in candidates:
|
||||||
|
if candidate in doc_order:
|
||||||
|
return candidate, doc_order[candidate]
|
||||||
|
elif candidate in doc_order_decoded:
|
||||||
|
return candidate, doc_order_decoded[candidate]
|
||||||
|
return None, None
|
||||||
|
|
||||||
def _parse_ncx_navpoint(
|
def _parse_ncx_navpoint(
|
||||||
self,
|
self,
|
||||||
nav_point,
|
nav_point,
|
||||||
@@ -603,27 +641,15 @@ class HandlerDialog(QDialog):
|
|||||||
|
|
||||||
if src:
|
if src:
|
||||||
base_href, fragment = src.split("#", 1) if "#" in src else (src, None)
|
base_href, fragment = src.split("#", 1) if "#" in src else (src, None)
|
||||||
# Try both original and decoded hrefs
|
doc_key, doc_idx = self._find_doc_key(
|
||||||
doc_key = None
|
base_href, doc_order, doc_order_decoded
|
||||||
if base_href in doc_order:
|
)
|
||||||
doc_key = base_href
|
if not doc_key:
|
||||||
doc_idx = doc_order[base_href]
|
|
||||||
elif urllib.parse.unquote(base_href) in doc_order:
|
|
||||||
doc_key = urllib.parse.unquote(base_href)
|
|
||||||
doc_idx = doc_order[doc_key]
|
|
||||||
elif base_href in doc_order_decoded:
|
|
||||||
doc_key = base_href
|
|
||||||
doc_idx = doc_order_decoded[base_href]
|
|
||||||
elif urllib.parse.unquote(base_href) in doc_order_decoded:
|
|
||||||
doc_key = urllib.parse.unquote(base_href)
|
|
||||||
doc_idx = doc_order_decoded[doc_key]
|
|
||||||
else:
|
|
||||||
logging.warning(
|
logging.warning(
|
||||||
f"Navigation entry '{title}' points to '{base_href}', which is not in the spine or document list."
|
f"Navigation entry '{title}' points to '{base_href}', which is not in the spine or document list (even after basename fallback)."
|
||||||
)
|
)
|
||||||
current_entry_node["has_content"] = False
|
current_entry_node["has_content"] = False
|
||||||
doc_key = None
|
else:
|
||||||
if doc_key is not None:
|
|
||||||
position = find_position_func(doc_key, fragment)
|
position = find_position_func(doc_key, fragment)
|
||||||
entry_data = {
|
entry_data = {
|
||||||
"src": src,
|
"src": src,
|
||||||
@@ -698,28 +724,15 @@ class HandlerDialog(QDialog):
|
|||||||
current_entry_node["title"] = title
|
current_entry_node["title"] = title
|
||||||
current_entry_node["src"] = src
|
current_entry_node["src"] = src
|
||||||
|
|
||||||
|
doc_key = None
|
||||||
|
doc_idx = None
|
||||||
|
position = 0
|
||||||
|
fragment = None
|
||||||
if src:
|
if src:
|
||||||
base_href, fragment = src.split("#", 1) if "#" in src else (src, None)
|
base_href, fragment = src.split("#", 1) if "#" in src else (src, None)
|
||||||
# Try both original and decoded hrefs
|
doc_key, doc_idx = self._find_doc_key(
|
||||||
doc_key = None
|
base_href, doc_order, doc_order_decoded
|
||||||
if base_href in doc_order:
|
)
|
||||||
doc_key = base_href
|
|
||||||
doc_idx = doc_order[base_href]
|
|
||||||
elif urllib.parse.unquote(base_href) in doc_order:
|
|
||||||
doc_key = urllib.parse.unquote(base_href)
|
|
||||||
doc_idx = doc_order[doc_key]
|
|
||||||
elif base_href in doc_order_decoded:
|
|
||||||
doc_key = base_href
|
|
||||||
doc_idx = doc_order_decoded[base_href]
|
|
||||||
elif urllib.parse.unquote(base_href) in doc_order_decoded:
|
|
||||||
doc_key = urllib.parse.unquote(base_href)
|
|
||||||
doc_idx = doc_order_decoded[doc_key]
|
|
||||||
else:
|
|
||||||
logging.warning(
|
|
||||||
f"Navigation entry '{title}' points to '{base_href}', which is not in the spine or document list."
|
|
||||||
)
|
|
||||||
current_entry_node["has_content"] = False
|
|
||||||
doc_key = None
|
|
||||||
if doc_key is not None:
|
if doc_key is not None:
|
||||||
position = find_position_func(doc_key, fragment)
|
position = find_position_func(doc_key, fragment)
|
||||||
entry_data = {
|
entry_data = {
|
||||||
@@ -731,13 +744,16 @@ class HandlerDialog(QDialog):
|
|||||||
}
|
}
|
||||||
ordered_entries.append(entry_data)
|
ordered_entries.append(entry_data)
|
||||||
current_entry_node["has_content"] = True
|
current_entry_node["has_content"] = True
|
||||||
|
else:
|
||||||
|
logging.warning(
|
||||||
|
f"Navigation entry '{title}' points to '{base_href}', which is not in the spine or document list (even after basename fallback)."
|
||||||
|
)
|
||||||
|
current_entry_node["has_content"] = False
|
||||||
else:
|
else:
|
||||||
current_entry_node["has_content"] = False
|
current_entry_node["has_content"] = False
|
||||||
|
|
||||||
child_ol = li_element.find("ol", recursive=False)
|
for child_ol in li_element.find_all("ol", recursive=False):
|
||||||
if child_ol:
|
|
||||||
for child_li in child_ol.find_all("li", recursive=False):
|
for child_li in child_ol.find_all("li", recursive=False):
|
||||||
# Pass find_position_func down recursively
|
|
||||||
self._parse_html_nav_li(
|
self._parse_html_nav_li(
|
||||||
child_li,
|
child_li,
|
||||||
ordered_entries,
|
ordered_entries,
|
||||||
@@ -746,12 +762,7 @@ class HandlerDialog(QDialog):
|
|||||||
current_entry_node["children"],
|
current_entry_node["children"],
|
||||||
find_position_func,
|
find_position_func,
|
||||||
)
|
)
|
||||||
|
tree_structure_list.append(current_entry_node)
|
||||||
if title and (
|
|
||||||
current_entry_node.get("has_content", False)
|
|
||||||
or current_entry_node["children"]
|
|
||||||
):
|
|
||||||
tree_structure_list.append(current_entry_node)
|
|
||||||
|
|
||||||
def _find_position_robust(self, doc_href, fragment_id):
|
def _find_position_robust(self, doc_href, fragment_id):
|
||||||
if doc_href not in self.doc_content:
|
if doc_href not in self.doc_content:
|
||||||
|
|||||||
+18
-12
@@ -434,8 +434,8 @@ class ConversionThread(QThread):
|
|||||||
)
|
)
|
||||||
# Only check for files with allowed extensions (extension without dot, case-insensitive)
|
# Only check for files with allowed extensions (extension without dot, case-insensitive)
|
||||||
clash = any(
|
clash = any(
|
||||||
os.path.splitext(fname)[0] == f"{base_name}{suffix}" and
|
os.path.splitext(fname)[0] == f"{base_name}{suffix}"
|
||||||
os.path.splitext(fname)[1][1:].lower() in allowed_exts
|
and os.path.splitext(fname)[1][1:].lower() in allowed_exts
|
||||||
for fname in os.listdir(parent_dir)
|
for fname in os.listdir(parent_dir)
|
||||||
)
|
)
|
||||||
if not os.path.exists(chapters_out_dir_candidate) and not clash:
|
if not os.path.exists(chapters_out_dir_candidate) and not clash:
|
||||||
@@ -467,6 +467,8 @@ class ConversionThread(QThread):
|
|||||||
{"chapter": chapter[0], "start": 0.0, "end": 0.0}
|
{"chapter": chapter[0], "start": 0.0, "end": 0.0}
|
||||||
for chapter in chapters
|
for chapter in chapters
|
||||||
]
|
]
|
||||||
|
# SRT numbering fix: use a global counter
|
||||||
|
merged_srt_index = 1 # SRT numbering for merged file
|
||||||
# Prepare output file/ffmpeg process for merged output
|
# Prepare output file/ffmpeg process for merged output
|
||||||
if self.output_format in ["wav", "mp3", "flac"]:
|
if self.output_format in ["wav", "mp3", "flac"]:
|
||||||
merged_out_file = sf.SoundFile(
|
merged_out_file = sf.SoundFile(
|
||||||
@@ -483,8 +485,8 @@ class ConversionThread(QThread):
|
|||||||
merged_out_file = None
|
merged_out_file = None
|
||||||
ffmpeg_proc = None
|
ffmpeg_proc = None
|
||||||
metadata_options = (
|
metadata_options = (
|
||||||
self._extract_and_add_metadata_tags_to_ffmpeg_cmd()
|
self._extract_and_add_metadata_tags_to_ffmpeg_cmd()
|
||||||
)
|
)
|
||||||
# Prepare ffmpeg command for m4b output
|
# Prepare ffmpeg command for m4b output
|
||||||
cmd = [
|
cmd = [
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
@@ -599,6 +601,7 @@ class ConversionThread(QThread):
|
|||||||
{"chapter": chapter[0], "start": 0.0, "end": 0.0}
|
{"chapter": chapter[0], "start": 0.0, "end": 0.0}
|
||||||
for chapter in chapters
|
for chapter in chapters
|
||||||
]
|
]
|
||||||
|
srt_index = 1 # SRT numbering fix for chapter-only mode
|
||||||
# Instead of processing the whole text, process by chapter
|
# Instead of processing the whole text, process by chapter
|
||||||
for chapter_idx, (chapter_name, chapter_text) in enumerate(chapters, 1):
|
for chapter_idx, (chapter_name, chapter_text) in enumerate(chapters, 1):
|
||||||
chapter_out_path = None
|
chapter_out_path = None
|
||||||
@@ -677,6 +680,9 @@ class ConversionThread(QThread):
|
|||||||
continue
|
continue
|
||||||
# Open chapter subtitle file for incremental writing if needed
|
# Open chapter subtitle file for incremental writing if needed
|
||||||
chapter_subtitle_file = None
|
chapter_subtitle_file = None
|
||||||
|
chapter_srt_index = (
|
||||||
|
1 # Initialize SRT numbering for this chapter file
|
||||||
|
)
|
||||||
if self.subtitle_mode != "Disabled":
|
if self.subtitle_mode != "Disabled":
|
||||||
subtitle_format = getattr(self, "subtitle_format", "srt")
|
subtitle_format = getattr(self, "subtitle_format", "srt")
|
||||||
file_extension = "ass" if "ass" in subtitle_format else "srt"
|
file_extension = "ass" if "ass" in subtitle_format else "srt"
|
||||||
@@ -821,12 +827,12 @@ class ConversionThread(QThread):
|
|||||||
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,,{merged_subtitle_alignment_tag}{text}\n"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
for i, (start, end, text) in enumerate(
|
for entry in new_entries:
|
||||||
new_entries, 1
|
start, end, text = entry
|
||||||
):
|
|
||||||
merged_subtitle_file.write(
|
merged_subtitle_file.write(
|
||||||
f"{i}\n{self._srt_time(start)} --> {self._srt_time(end)}\n{text}\n\n"
|
f"{merged_srt_index}\n{self._srt_time(start)} --> {self._srt_time(end)}\n{text}\n\n"
|
||||||
)
|
)
|
||||||
|
merged_srt_index += 1
|
||||||
# Per-chapter subtitle processing for both file and ffmpeg_proc
|
# Per-chapter subtitle processing for both file and ffmpeg_proc
|
||||||
if chapter_out_file or chapter_ffmpeg_proc:
|
if chapter_out_file or chapter_ffmpeg_proc:
|
||||||
new_chapter_entries = []
|
new_chapter_entries = []
|
||||||
@@ -848,12 +854,12 @@ class ConversionThread(QThread):
|
|||||||
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,,{chapter_subtitle_alignment_tag}{text}\n"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
for i, (start, end, text) in enumerate(
|
for entry in new_chapter_entries:
|
||||||
new_chapter_entries, 1
|
start, end, text = entry
|
||||||
):
|
|
||||||
chapter_subtitle_file.write(
|
chapter_subtitle_file.write(
|
||||||
f"{i}\n{self._srt_time(start)} --> {self._srt_time(end)}\n{text}\n\n"
|
f"{chapter_srt_index}\n{self._srt_time(start)} --> {self._srt_time(end)}\n{text}\n\n"
|
||||||
)
|
)
|
||||||
|
chapter_srt_index += 1
|
||||||
if merge_chapters_at_end:
|
if merge_chapters_at_end:
|
||||||
current_time += chunk_dur
|
current_time += chunk_dur
|
||||||
if chapter_out_file or chapter_ffmpeg_proc:
|
if chapter_out_file or chapter_ffmpeg_proc:
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ os.environ["MIOPEN_CONV_PRECISE_ROCM_TUNING"] = "0"
|
|||||||
# Reset sleep states
|
# Reset sleep states
|
||||||
atexit.register(prevent_sleep_end)
|
atexit.register(prevent_sleep_end)
|
||||||
|
|
||||||
|
|
||||||
# Also handle signals (Ctrl+C, kill, etc.)
|
# Also handle signals (Ctrl+C, kill, etc.)
|
||||||
def _cleanup_sleep(signum, frame):
|
def _cleanup_sleep(signum, frame):
|
||||||
prevent_sleep_end()
|
prevent_sleep_end()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, _cleanup_sleep)
|
signal.signal(signal.SIGINT, _cleanup_sleep)
|
||||||
signal.signal(signal.SIGTERM, _cleanup_sleep)
|
signal.signal(signal.SIGTERM, _cleanup_sleep)
|
||||||
|
|
||||||
@@ -50,6 +52,7 @@ if sys.stderr is None:
|
|||||||
if platform.system() == "Darwin" and platform.processor() == "arm":
|
if platform.system() == "Darwin" and platform.processor() == "arm":
|
||||||
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
|
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
|
||||||
|
|
||||||
|
|
||||||
# Custom message handler to filter out specific Qt warnings
|
# Custom message handler to filter out specific Qt warnings
|
||||||
def qt_message_handler(mode, context, message):
|
def qt_message_handler(mode, context, message):
|
||||||
if "Wayland does not support QWindow::requestActivate()" in message:
|
if "Wayland does not support QWindow::requestActivate()" in message:
|
||||||
|
|||||||
+11
-3
@@ -74,24 +74,32 @@ def get_user_config_path():
|
|||||||
if os.path.exists(custom_dir):
|
if os.path.exists(custom_dir):
|
||||||
config_dir = custom_dir
|
config_dir = custom_dir
|
||||||
else:
|
else:
|
||||||
config_dir = user_config_dir("abogen", appauthor=False, roaming=True, ensure_exists=True)
|
config_dir = user_config_dir(
|
||||||
|
"abogen", appauthor=False, roaming=True, ensure_exists=True
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# Windows and fallback case
|
# Windows and fallback case
|
||||||
config_dir = user_config_dir("abogen", appauthor=False, roaming=True, ensure_exists=True)
|
config_dir = user_config_dir(
|
||||||
|
"abogen", appauthor=False, roaming=True, ensure_exists=True
|
||||||
|
)
|
||||||
|
|
||||||
return os.path.join(config_dir, "config.json")
|
return os.path.join(config_dir, "config.json")
|
||||||
|
|
||||||
|
|
||||||
# Define cache path
|
# Define cache path
|
||||||
def get_user_cache_path(folder=None):
|
def get_user_cache_path(folder=None):
|
||||||
from platformdirs import user_cache_dir
|
from platformdirs import user_cache_dir
|
||||||
|
|
||||||
cache_dir = user_cache_dir("abogen", appauthor=False, opinion=True, ensure_exists=True)
|
cache_dir = user_cache_dir(
|
||||||
|
"abogen", appauthor=False, opinion=True, ensure_exists=True
|
||||||
|
)
|
||||||
if folder:
|
if folder:
|
||||||
cache_dir = os.path.join(cache_dir, folder)
|
cache_dir = os.path.join(cache_dir, folder)
|
||||||
# Ensure the directory exists
|
# Ensure the directory exists
|
||||||
os.makedirs(cache_dir, exist_ok=True)
|
os.makedirs(cache_dir, exist_ok=True)
|
||||||
return cache_dir
|
return cache_dir
|
||||||
|
|
||||||
|
|
||||||
_sleep_procs = {"Darwin": None, "Linux": None} # Store sleep prevention processes
|
_sleep_procs = {"Darwin": None, "Linux": None} # Store sleep prevention processes
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ keywords = ["audiobook", "epub", "pdf", "text-to-speech", "subtitle", "tts", "ko
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"PyQt5>=5.15.11",
|
"PyQt5>=5.15.11",
|
||||||
"kokoro>=0.9.4",
|
"kokoro>=0.9.4",
|
||||||
|
"misaki[zh]>=0.9.4",
|
||||||
"ebooklib>=0.19",
|
"ebooklib>=0.19",
|
||||||
"beautifulsoup4>=4.13.4",
|
"beautifulsoup4>=4.13.4",
|
||||||
"PyMuPDF>=1.25.5",
|
"PyMuPDF>=1.25.5",
|
||||||
|
|||||||
Reference in New Issue
Block a user