Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.25.3
require (
github.com/lann/builder v0.0.0-20150808151131-f22ce00fd939
github.com/stretchr/testify v1.7.0
rsc.io/pdf v0.1.1
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
8 changes: 4 additions & 4 deletions internal/service/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ func TestExtractMetadata(t *testing.T) {
t.Logf("EPUB Title: %q, Author: %q, CoverPath: %q, Description: %q, Series: %q, SeriesIndex: %q, Subjects: %v", title, author, coverPath, description, series, seriesIndex, subjects)
})

t.Run("Parse PDF value", func(t *testing.T) {
line := "/Title (The Great Gatsby) /Author (F. Scott Fitzgerald)"
assert.Equal(t, "The Great Gatsby", parsePdfValue(line, "/Title"))
assert.Equal(t, "F. Scott Fitzgerald", parsePdfValue(line, "/Author"))
t.Run("Extract PDF", func(t *testing.T) {
path := filepath.Join("testdata", "mybook", "mybook.pdf")
title, author, description, subjects := extractPdfMetadata(path)
t.Logf("PDF Title: %q, Author: %q, Description: %q, Subjects: %v", title, author, description, subjects)
})
}

Expand Down
59 changes: 22 additions & 37 deletions internal/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package service

import (
"archive/zip"
"bufio"
"bytes"
"crypto/sha256"
"encoding/hex"
Expand All @@ -26,6 +25,7 @@ import (
"time"

"github.com/dubyte/dir2opds/opds"
"rsc.io/pdf"
)

func init() {
Expand Down Expand Up @@ -271,8 +271,8 @@ func extractMetadata(path string) (string, string, string, string, string, strin
case ".epub":
return extractEpubMetadata(path)
case ".pdf":
title, author := extractPdfMetadata(path)
return title, author, "", "", "", "", nil
title, author, description, subjects := extractPdfMetadata(path)
return title, author, "", description, "", "", subjects
}
return "", "", "", "", "", "", nil
}
Expand Down Expand Up @@ -437,46 +437,31 @@ func findEpubCover(r *zip.ReadCloser, items []struct {
return ""
}

func extractPdfMetadata(path string) (string, string) {
f, err := os.Open(path)
func extractPdfMetadata(path string) (string, string, string, []string) {
reader, err := pdf.Open(path)
if err != nil {
return "", ""
return "", "", "", nil
}
defer f.Close()

scanner := bufio.NewScanner(f)
var title, author string
// Only scan first 4KB to keep it fast
maxLines := 100
for i := 0; i < maxLines && scanner.Scan(); i++ {
line := scanner.Text()
if title == "" && strings.Contains(line, "/Title") {
title = parsePdfValue(line, "/Title")
}
if author == "" && strings.Contains(line, "/Author") {
author = parsePdfValue(line, "/Author")
}
if title != "" && author != "" {
break
}
info := reader.Trailer().Key("Info")
if info.IsNull() {
return "", "", "", nil
}
return title, author
}

func parsePdfValue(line, key string) string {
idx := strings.Index(line, key)
if idx == -1 {
return ""
}
start := strings.Index(line[idx:], "(")
if start == -1 {
return ""
}
end := strings.Index(line[idx+start:], ")")
if end == -1 {
return ""
title := info.Key("Title").Text()
author := info.Key("Author").Text()
description := info.Key("Subject").Text()

var subjects []string
if kw := info.Key("Keywords").Text(); kw != "" {
for _, s := range strings.Split(kw, ",") {
if s = strings.TrimSpace(s); s != "" {
subjects = append(subjects, s)
}
}
}
return line[idx+start+1 : idx+start+end]

return title, author, description, subjects
}

func (s OPDS) sortEntries(entries []CatalogEntry) {
Expand Down
Loading