Archived
feat: pdf reader
This commit is contained in:
@@ -91,13 +91,15 @@ class EBookListSerializer(serializers.ModelSerializer):
|
||||
return None
|
||||
|
||||
def get_started(self, obj):
|
||||
"""True when the user has opened the reader and has a saved EPUB location."""
|
||||
"""True when the user has opened the reader with saved progress."""
|
||||
try:
|
||||
rp = obj.reading_progress
|
||||
except ReadingProgress.DoesNotExist:
|
||||
return False
|
||||
if rp.current_position >= 99:
|
||||
return False
|
||||
if obj.format == "pdf":
|
||||
return rp.last_page > 0 or rp.current_position > 0
|
||||
return bool((rp.epub_location or "").strip())
|
||||
|
||||
|
||||
|
||||
@@ -89,7 +89,69 @@ def _process_epub(file_path: str) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _process_pdf(file_path: str) -> dict[str, Any]:
|
||||
return {"format": "pdf", "page_count": 0, "metadata": {}, "toc": []}
|
||||
from pypdf import PdfReader
|
||||
|
||||
reader = PdfReader(file_path)
|
||||
page_count = len(reader.pages)
|
||||
metadata = _extract_pdf_metadata(reader)
|
||||
toc = _extract_pdf_outline(reader)
|
||||
if not toc and page_count > 0:
|
||||
toc = [
|
||||
{"title": f"Page {i}", "href": f"pdf:page:{i}", "children": []}
|
||||
for i in range(1, page_count + 1)
|
||||
]
|
||||
return {
|
||||
"format": "pdf",
|
||||
"page_count": page_count,
|
||||
"metadata": metadata,
|
||||
"toc": toc,
|
||||
}
|
||||
|
||||
|
||||
def _extract_pdf_metadata(reader) -> dict[str, Any]:
|
||||
metadata: dict[str, Any] = {}
|
||||
info = reader.metadata
|
||||
if not info:
|
||||
return metadata
|
||||
title = getattr(info, "title", None)
|
||||
author = getattr(info, "author", None)
|
||||
if title:
|
||||
metadata["title"] = str(title)
|
||||
if author:
|
||||
metadata["author"] = str(author)
|
||||
return metadata
|
||||
|
||||
|
||||
def _extract_pdf_outline(reader) -> list[dict[str, Any]]:
|
||||
outline = getattr(reader, "outline", None)
|
||||
if not outline:
|
||||
return []
|
||||
return _walk_pdf_outline(reader, outline)
|
||||
|
||||
|
||||
def _walk_pdf_outline(reader, outline: list[Any]) -> list[dict[str, Any]]:
|
||||
entries: list[dict[str, Any]] = []
|
||||
i = 0
|
||||
while i < len(outline):
|
||||
item = outline[i]
|
||||
if isinstance(item, list):
|
||||
if entries:
|
||||
entries[-1]["children"] = _walk_pdf_outline(reader, item)
|
||||
i += 1
|
||||
continue
|
||||
title = getattr(item, "title", None) or "Section"
|
||||
page_num = 1
|
||||
try:
|
||||
page_num = reader.get_destination_page_number(item) + 1
|
||||
except Exception:
|
||||
logger.debug("Could not resolve PDF outline destination", exc_info=True)
|
||||
entries.append({
|
||||
"title": str(title),
|
||||
"href": f"pdf:page:{page_num}",
|
||||
"children": [],
|
||||
})
|
||||
i += 1
|
||||
return entries
|
||||
|
||||
|
||||
def _extract_epub_metadata(book) -> dict[str, Any]:
|
||||
|
||||
@@ -168,18 +168,23 @@ class EBookViewSet(viewsets.ModelViewSet):
|
||||
|
||||
@action(detail=True, methods=["get"])
|
||||
def file(self, request: Request, pk: int | None = None) -> FileResponse | Response:
|
||||
"""Stream the EPUB file for authenticated in-browser reading."""
|
||||
"""Stream the ebook file for authenticated in-browser reading."""
|
||||
ebook = self.get_object()
|
||||
if ebook.format != "epub":
|
||||
content_types = {
|
||||
"epub": "application/epub+zip",
|
||||
"pdf": "application/pdf",
|
||||
}
|
||||
content_type = content_types.get(ebook.format)
|
||||
if not content_type:
|
||||
return Response(
|
||||
{"error": "Reader supports EPUB only."},
|
||||
{"error": "Unsupported format for in-browser reading."},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
if not ebook.file:
|
||||
return Response({"error": "No file found for this e-book."}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return FileResponse(
|
||||
ebook.file.open("rb"),
|
||||
content_type="application/epub+zip",
|
||||
content_type=content_type,
|
||||
filename=ebook.filename(),
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user