feat: Implement Calibre OPDS import endpoint for downloading books

This commit is contained in:
JB
2025-11-28 19:43:13 -08:00
parent c69e5af54d
commit 5b548cc155
2 changed files with 86 additions and 1 deletions
+11
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import sqlite3
import shutil
import threading
import time
from pathlib import Path
@@ -20,6 +21,16 @@ def _store_path() -> Path:
base_dir = Path(get_internal_cache_path("pronunciations"))
target = base_dir / "pronunciations.db"
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
+75 -1
View File
@@ -1,7 +1,7 @@
from typing import Any, Dict, Mapping, List, Optional
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 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.integrations.audiobookshelf import AudiobookshelfClient, AudiobookshelfConfig
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__)
@@ -140,6 +142,78 @@ def api_calibre_opds_test() -> ResponseReturnValue:
except Exception as e:
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 ---
@api_bp.post("/llm/models")