mirror of
https://github.com/denizsafak/abogen.git
synced 2026-07-18 13:40:27 +02:00
feat: Implement Calibre OPDS import endpoint for downloading books
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import shutil
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -20,6 +21,16 @@ def _store_path() -> Path:
|
|||||||
base_dir = Path(get_internal_cache_path("pronunciations"))
|
base_dir = Path(get_internal_cache_path("pronunciations"))
|
||||||
target = base_dir / "pronunciations.db"
|
target = base_dir / "pronunciations.db"
|
||||||
target.parent.mkdir(parents=True, exist_ok=True)
|
target.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
if not target.exists():
|
||||||
|
try:
|
||||||
|
legacy_dir = Path(get_internal_cache_path("pronunciations"))
|
||||||
|
legacy_path = legacy_dir / "pronunciations.db"
|
||||||
|
if legacy_path.exists() and legacy_path != target:
|
||||||
|
shutil.move(str(legacy_path), target)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
return target
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Any, Dict, Mapping, List, Optional
|
from typing import Any, Dict, Mapping, List, Optional
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
from flask import Blueprint, request, jsonify, send_file
|
from flask import Blueprint, request, jsonify, send_file, url_for
|
||||||
from flask.typing import ResponseReturnValue
|
from flask.typing import ResponseReturnValue
|
||||||
|
|
||||||
from abogen.web.routes.utils.settings import (
|
from abogen.web.routes.utils.settings import (
|
||||||
@@ -24,6 +24,8 @@ from abogen.llm_client import list_models, LLMClientError
|
|||||||
from abogen.kokoro_text_normalization import normalize_for_pipeline
|
from abogen.kokoro_text_normalization import normalize_for_pipeline
|
||||||
from abogen.integrations.audiobookshelf import AudiobookshelfClient, AudiobookshelfConfig
|
from abogen.integrations.audiobookshelf import AudiobookshelfClient, AudiobookshelfConfig
|
||||||
from abogen.integrations.calibre_opds import CalibreOPDSClient
|
from abogen.integrations.calibre_opds import CalibreOPDSClient
|
||||||
|
from abogen.web.routes.utils.service import get_service
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
api_bp = Blueprint("api", __name__)
|
api_bp = Blueprint("api", __name__)
|
||||||
|
|
||||||
@@ -140,6 +142,78 @@ def api_calibre_opds_test() -> ResponseReturnValue:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({"error": str(e)}), 400
|
return jsonify({"error": str(e)}), 400
|
||||||
|
|
||||||
|
@api_bp.post("/integrations/calibre-opds/import")
|
||||||
|
def api_calibre_opds_import() -> ResponseReturnValue:
|
||||||
|
if not request.is_json:
|
||||||
|
return jsonify({"error": "Expected JSON payload."}), 400
|
||||||
|
|
||||||
|
payload = request.get_json(force=True, silent=True) or {}
|
||||||
|
href = payload.get("href")
|
||||||
|
|
||||||
|
if not href:
|
||||||
|
return jsonify({"error": "Download URL (href) is required."}), 400
|
||||||
|
|
||||||
|
settings = load_settings()
|
||||||
|
integrations = settings.get("integrations", {})
|
||||||
|
calibre_settings = integrations.get("calibre_opds", {})
|
||||||
|
|
||||||
|
# We need to download the file
|
||||||
|
# For now, let's just return a success message as a placeholder
|
||||||
|
# In a real implementation, this would trigger a download job
|
||||||
|
|
||||||
|
# Re-using the logic from books.py if possible, or implementing it here
|
||||||
|
# It seems books.py had a placeholder for this too.
|
||||||
|
|
||||||
|
# Let's try to actually download it using the service
|
||||||
|
try:
|
||||||
|
service = get_service()
|
||||||
|
|
||||||
|
client = CalibreOPDSClient(
|
||||||
|
base_url=calibre_settings.get("base_url", ""),
|
||||||
|
username=calibre_settings.get("username"),
|
||||||
|
password=calibre_settings.get("password"),
|
||||||
|
verify=calibre_settings.get("verify_ssl", True),
|
||||||
|
)
|
||||||
|
|
||||||
|
with client._open_client() as http_client:
|
||||||
|
response = http_client.get(href, follow_redirects=True)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Try to get filename from content-disposition
|
||||||
|
filename = "downloaded_book.epub"
|
||||||
|
if "content-disposition" in response.headers:
|
||||||
|
import re
|
||||||
|
fname = re.findall("filename=(.+)", response.headers["content-disposition"])
|
||||||
|
if fname:
|
||||||
|
filename = fname[0].strip('"')
|
||||||
|
|
||||||
|
filename = secure_filename(filename)
|
||||||
|
|
||||||
|
# Save to uploads folder
|
||||||
|
uploads_dir = service._uploads_root
|
||||||
|
target_path = uploads_dir / filename
|
||||||
|
|
||||||
|
# Ensure unique filename
|
||||||
|
stem = target_path.stem
|
||||||
|
suffix = target_path.suffix
|
||||||
|
counter = 1
|
||||||
|
while target_path.exists():
|
||||||
|
target_path = uploads_dir / f"{stem}_{counter}{suffix}"
|
||||||
|
counter += 1
|
||||||
|
|
||||||
|
with open(target_path, "wb") as f:
|
||||||
|
for chunk in response.iter_bytes(chunk_size=8192):
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"success": True,
|
||||||
|
"message": "Book downloaded successfully.",
|
||||||
|
"redirect": url_for("main.prepare_page", filename=target_path.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
# --- LLM Routes ---
|
# --- LLM Routes ---
|
||||||
|
|
||||||
@api_bp.post("/llm/models")
|
@api_bp.post("/llm/models")
|
||||||
|
|||||||
Reference in New Issue
Block a user