mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
Improved EPUB chapter detection
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
# 1.1.6 (pre-release)
|
# 1.1.6 (pre-release)
|
||||||
- Fixed SRT subtitle numbering issue, mentioned by @page-muncher in #41
|
- Fixed SRT subtitle numbering issue, mentioned by @page-muncher in #41
|
||||||
|
- 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
|
||||||
- Windows installer script now prompts the user to install the CUDA version of PyTorch even if no NVIDIA GPU is detected.
|
- 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.
|
- Abogen now includes Mandarin Chinese (misaki[zh]) by default; manual installation is no longer required.
|
||||||
|
|
||||||
|
|||||||
+57
-54
@@ -324,14 +324,29 @@ 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 +531,12 @@ 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 +598,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 +637,13 @@ 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(base_href, doc_order, doc_order_decoded)
|
||||||
doc_key = None
|
if not doc_key:
|
||||||
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(
|
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 +718,13 @@ 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(base_href, doc_order, doc_order_decoded)
|
||||||
doc_key = None
|
|
||||||
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 +736,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,11 +754,6 @@ class HandlerDialog(QDialog):
|
|||||||
current_entry_node["children"],
|
current_entry_node["children"],
|
||||||
find_position_func,
|
find_position_func,
|
||||||
)
|
)
|
||||||
|
|
||||||
if title and (
|
|
||||||
current_entry_node.get("has_content", False)
|
|
||||||
or current_entry_node["children"]
|
|
||||||
):
|
|
||||||
tree_structure_list.append(current_entry_node)
|
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):
|
||||||
|
|||||||
Reference in New Issue
Block a user