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 }