Vellum/internal/modules/comics/cover.go
mithral 5a377a2198 PDF reading via pdf.js
- PdfReader renders PDFs to canvas with page navigation and page-number
  progress, reusing the same integer progress model as comics.
- Reader routes by file container, not media kind, so a PDF in a comics
  library and a PDF ebook both open in the PDF reader.
- pdf.js is dynamically imported: the main bundle stays 443 kB and the 474 kB
  pdf chunk plus 1.26 MB worker load only when a PDF is actually opened.
- comics cover extractor now only handles zip archives. A PDF in a comics
  library is a valid item but not a zip, so it returns no cover instead of
  logging an error on every request.
- fixed the page scale to leave slack for the vertical scrollbar, which was
  otherwise forcing a horizontal scrollbar on tall pages.

Verified live: a 3-page PDF renders, navigates, and reports 33% progress.
2026-10-09 21:43:43 -04:00

62 lines
1.4 KiB
Go

package comics
import (
"context"
"database/sql"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"github.com/mithral/vellum/internal/media"
)
func init() {
media.RegisterCoverExtractor(Kind, ExtractCover)
}
// ExtractCover writes the first page of the archive into the cache and returns
// its path. A comic's first page is its cover by convention.
//
// Only zip-based archives are handled. A PDF in a comics library is a valid
// item but not a zip, so it returns no cover rather than logging an error on
// every request — the grid falls back to a placeholder.
func ExtractCover(ctx context.Context, db *sql.DB, item media.Item, absPath, cacheDir string) (string, error) {
switch strings.ToLower(filepath.Ext(item.Path)) {
case ".cbz", ".zip":
default:
return "", nil
}
pages, err := ListPages(absPath)
if err != nil {
return "", err
}
dir, err := media.CoverCacheDir(cacheDir, Kind)
if err != nil {
return "", err
}
ext := strings.ToLower(path.Ext(pages[0].Name))
if ext == "" {
ext = ".jpg"
}
outPath := filepath.Join(dir, fmt.Sprintf("%d%s", item.ID, ext))
data, err := ReadPage(absPath, pages[0].Name)
if err != nil {
return "", err
}
tmp := outPath + ".tmp"
if err := os.WriteFile(tmp, data, 0o640); err != nil {
return "", fmt.Errorf("comics: write cover: %w", err)
}
if err := os.Rename(tmp, outPath); err != nil {
_ = os.Remove(tmp)
return "", err
}
return outPath, nil
}