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
4 changes: 2 additions & 2 deletions internal/service/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ func TestSortEntries(t *testing.T) {
func TestExtractMetadata(t *testing.T) {
t.Run("Extract EPUB", func(t *testing.T) {
path := filepath.Join("testdata", "mybook", "mybook.epub")
title, author, coverPath, description, series, seriesIndex := extractEpubMetadata(path)
t.Logf("EPUB Title: %q, Author: %q, CoverPath: %q, Description: %q, Series: %q, SeriesIndex: %q", title, author, coverPath, description, series, seriesIndex)
title, author, coverPath, description, series, seriesIndex, subjects := extractEpubMetadata(path)
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) {
Expand Down
50 changes: 33 additions & 17 deletions internal/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ type CatalogEntry struct {
Description string
Series string
SeriesIndex string
Subjects []string
}

type IsDirer interface {
Expand Down Expand Up @@ -206,7 +207,7 @@ func (s OPDS) Scan(fPath string, urlPath string, page int) (*Catalog, error) {

if s.ExtractMetadata && !entry.IsDir() {
idx := len(catalog.Entries) - 1
title, author, coverPath, description, series, seriesIndex := extractMetadata(entryPath)
title, author, coverPath, description, series, seriesIndex, subjects := extractMetadata(entryPath)
if title != "" {
catalog.Entries[idx].Title = title
}
Expand All @@ -225,6 +226,9 @@ func (s OPDS) Scan(fPath string, urlPath string, page int) (*Catalog, error) {
if seriesIndex != "" {
catalog.Entries[idx].SeriesIndex = seriesIndex
}
if len(subjects) > 0 {
catalog.Entries[idx].Subjects = subjects
}
}
}

Expand Down Expand Up @@ -261,22 +265,22 @@ func (s OPDS) Scan(fPath string, urlPath string, page int) (*Catalog, error) {
return catalog, nil
}

func extractMetadata(path string) (string, string, string, string, string, string) {
func extractMetadata(path string) (string, string, string, string, string, string, []string) {
ext := strings.ToLower(filepath.Ext(path))
switch ext {
case ".epub":
return extractEpubMetadata(path)
case ".pdf":
title, author := extractPdfMetadata(path)
return title, author, "", "", "", ""
return title, author, "", "", "", "", nil
}
return "", "", "", "", "", ""
return "", "", "", "", "", "", nil
}

func extractEpubMetadata(path string) (string, string, string, string, string, string) {
func extractEpubMetadata(path string) (string, string, string, string, string, string, []string) {
r, err := zip.OpenReader(path)
if err != nil {
return "", "", "", "", "", ""
return "", "", "", "", "", "", nil
}
defer r.Close()

Expand All @@ -289,25 +293,26 @@ func extractEpubMetadata(path string) (string, string, string, string, string, s
}

if opfPath == "" {
return "", "", "", "", "", ""
return "", "", "", "", "", "", nil
}

f, err := r.Open(opfPath)
if err != nil {
return "", "", "", "", "", ""
return "", "", "", "", "", "", nil
}
defer f.Close()

opfContent, err := io.ReadAll(f)
if err != nil {
return "", "", "", "", "", ""
return "", "", "", "", "", "", nil
}

var opf struct {
Metadata struct {
Title string `xml:"title"`
Creator string `xml:"creator"`
Description string `xml:"description"`
Title string `xml:"title"`
Creator string `xml:"creator"`
Description string `xml:"description"`
Subject []string `xml:"subject"`
Meta []struct {
Name string `xml:"name,attr"`
Content string `xml:"content,attr"`
Expand All @@ -324,7 +329,7 @@ func extractEpubMetadata(path string) (string, string, string, string, string, s

decoder := xml.NewDecoder(bytes.NewReader(opfContent))
if err := decoder.Decode(&opf); err != nil {
return "", "", "", "", "", ""
return "", "", "", "", "", "", nil
}

// If standard unmarshal fails to get values due to namespaces
Expand All @@ -333,9 +338,10 @@ func extractEpubMetadata(path string) (string, string, string, string, string, s
decoder.DefaultSpace = "http://purl.org/dc/elements/1.1/"
var opf2 struct {
Metadata struct {
Title string `xml:"title"`
Creator string `xml:"creator"`
Description string `xml:"description"`
Title string `xml:"title"`
Creator string `xml:"creator"`
Description string `xml:"description"`
Subject []string `xml:"subject"`
Meta []struct {
Name string `xml:"name,attr"`
Content string `xml:"content,attr"`
Expand All @@ -352,6 +358,9 @@ func extractEpubMetadata(path string) (string, string, string, string, string, s
if opf2.Metadata.Description != "" {
opf.Metadata.Description = opf2.Metadata.Description
}
if len(opf2.Metadata.Subject) > 0 && len(opf.Metadata.Subject) == 0 {
opf.Metadata.Subject = opf2.Metadata.Subject
}
if len(opf2.Metadata.Meta) > 0 && len(opf.Metadata.Meta) == 0 {
opf.Metadata.Meta = opf2.Metadata.Meta
}
Expand All @@ -370,7 +379,7 @@ func extractEpubMetadata(path string) (string, string, string, string, string, s
// Find cover image in manifest
coverPath := findEpubCover(r, opf.Manifest.Items, opfPath)

return opf.Metadata.Title, opf.Metadata.Creator, coverPath, opf.Metadata.Description, series, seriesIndex
return opf.Metadata.Title, opf.Metadata.Creator, coverPath, opf.Metadata.Description, series, seriesIndex, opf.Metadata.Subject
}

func findEpubCover(r *zip.ReadCloser, items []struct {
Expand Down Expand Up @@ -1008,6 +1017,13 @@ func (s OPDS) makeFeed(catalog *Catalog, req *http.Request) opds.Feed {
Build())
}

for _, subject := range entry.Subjects {
entryBuilder = entryBuilder.AddCategory(opds.Category{
Term: subject,
Label: subject,
})
}

feedBuilder = feedBuilder.AddEntry(entryBuilder.Build())
}
return feedBuilder.Build()
Expand Down
28 changes: 17 additions & 11 deletions opds/atom.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,24 @@ type Feed struct {
Opds string `xml:"xmlns:opds,attr,omitempty"`
}

// Entry is an Atom entry.
type Entry struct {
Title string `xml:"title"`
ID string `xml:"id"`
Link []Link `xml:"link"`
Published TimeStr `xml:"published"`
Updated TimeStr `xml:"updated"`
Author *Person `xml:"author"`
Summary *Text `xml:"summary"`
Content *Text `xml:"content"`
DcSeries string `xml:"dc:series,omitempty"`
DcSeriesPosition string `xml:"dc:seriesPosition,omitempty"`
Title string `xml:"title"`
ID string `xml:"id"`
Link []Link `xml:"link"`
Published TimeStr `xml:"published"`
Updated TimeStr `xml:"updated"`
Author *Person `xml:"author"`
Summary *Text `xml:"summary"`
Content *Text `xml:"content"`
DcSeries string `xml:"dc:series,omitempty"`
DcSeriesPosition string `xml:"dc:seriesPosition,omitempty"`
Categories []Category `xml:"category"`
}

type Category struct {
Term string `xml:"term,attr"`
Label string `xml:"label,attr,omitempty"`
Scheme string `xml:"scheme,attr,omitempty"`
}

// Link is an Atom link with optional OPDS 1.2 facet attributes.
Expand Down
4 changes: 4 additions & 0 deletions opds/entry_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ func (e entryBuilder) SeriesPosition(pos string) entryBuilder {
return builder.Set(e, "DcSeriesPosition", pos).(entryBuilder)
}

func (e entryBuilder) AddCategory(category Category) entryBuilder {
return builder.Append(e, "Categories", category).(entryBuilder)
}

func (e entryBuilder) Build() Entry {
return builder.GetStruct(e).(Entry)
}
Expand Down
Loading