Skip to content

Commit

Permalink
match local books by info.txt
Browse files Browse the repository at this point in the history
  • Loading branch information
jvatic committed Sep 10, 2023
1 parent 6706f24 commit b4510f8
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 41 deletions.
30 changes: 22 additions & 8 deletions audible/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package audible

import (
"context"
"crypto/sha1"
"encoding/hex"
"fmt"
"image"
"io"
Expand Down Expand Up @@ -37,12 +35,11 @@ type Book struct {
}

func (b *Book) ID() string {
h := sha1.New()
for _, name := range b.Authors {
io.WriteString(h, name)
parts := strings.Split(b.AudibleURL, "/")
if len(parts) == 0 {
return b.AudibleURL
}
io.WriteString(h, b.Title)
return hex.EncodeToString(h.Sum(nil))
return parts[len(parts)-1]
}

func (b *Book) WriteInfo(w io.Writer) error {
Expand Down Expand Up @@ -220,7 +217,7 @@ func (c *Client) getLibraryPage(ctx context.Context, pageURL string) (*Page, err
if u, err := url.Parse(htmlquery.SelectAttr(a, "href")); err == nil {
u = resp.Request.URL.ResolveReference(u)
u.RawQuery = "" // query is unnecessary baggage
book.AudibleURL = u.String()
book.AudibleURL = strings.Split(u.String(), "?")[0]
}
}
book.Title = strings.TrimSpace(htmlquery.InnerText(node))
Expand Down Expand Up @@ -283,6 +280,23 @@ func (c *Client) getLibraryPage(ctx context.Context, pageURL string) (*Page, err
addDownloadURL(a)
}

if book.AudibleURL == "" && len(book.DownloadURLs) > 0 {
for _, du := range book.DownloadURLs {
log.Debugf("using download URL (%s) for AudibleURL", du)
if u, err := url.Parse(du); err == nil {
q := u.Query()
if asin := q.Get("asin"); asin != "" {
book.AudibleURL = fmt.Sprintf("https://audible.com/pd/%s", asin)
} else {
log.Debugf("unable to find asin in url(%s)", du)
}
} else {
log.Debugf("error parsing url(%s): %v", du, err)
}
break
}
}

page.Books = append(page.Books, book)
}

Expand Down
7 changes: 5 additions & 2 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (cli *CLI) GetNewBooks(books []*audible.Book) ([]*audible.Book, []*audible.
localBooksByID[b.ID()] = b
}

newBooks := make([]*audible.Book, 0, len(books)-len(localBooks))
newBooks := make([]*audible.Book, 0)
downloadedBooks := make([]*audible.Book, 0, len(localBooks))
for _, b := range books {
if dlb, ok := localBooksByID[b.ID()]; ok {
Expand Down Expand Up @@ -165,7 +165,10 @@ func (cli *CLI) DownloadLibrary(ctx context.Context, c *audible.Client) error {

getDstPath := PromptPathTemplate()
for _, b := range books {
b.LocalPath = filepath.Join(cli.DstDir, getDstPath(b))
p := filepath.Join(cli.DstDir, getDstPath(b))
if b.LocalPath == "" {
b.LocalPath = p
}
}

loop:
Expand Down
3 changes: 3 additions & 0 deletions gui/library/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ func SetSelectedDir(uri fyne.ListableURI) Action {
if err != nil {
log.Errorf("Error discovering downloaded books: %s", err)
}
log.Debugf("Found %d local books", len(localBooks))
n := s.numSelected
for _, b := range localBooks {
if bi, ok := s.GetBookIndexForID(b.ID()); ok {
Expand All @@ -237,6 +238,8 @@ func SetSelectedDir(uri fyne.ListableURI) Action {
ch <- components.CheckboxActionSetChecked(false)
ch <- components.CheckboxActionDisable()
}
} else {
log.Debugf("unable to match local book: %s (%s)", b.ID(), b.Title)
}
}
s.SetNumSelected(n)
Expand Down
163 changes: 132 additions & 31 deletions internal/common/common.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package common

import (
"bufio"
"bytes"
"context"
"fmt"
"image"
"io"
"os"
"os/signal"
"path/filepath"
Expand Down Expand Up @@ -81,14 +84,62 @@ func CompilePathTemplate(t string, subs ...PathTemplateSub) func(b *audible.Book
}
}

func ListDownloadedBooks(dir string) ([]*audible.Book, error) {
parseAuthors := func(str string) []string {
parts := strings.Split(str, ",")
for i, p := range parts {
parts[i] = strings.TrimSpace(p)
type bookInfo struct {
Title string
Authors []string
Narrators []string
URL string
}

func parseInfoTxt(data io.Reader) (*bookInfo, error) {
info := &bookInfo{}

s := bufio.NewScanner(data)

// the first line is the title
s.Scan()
info.Title = s.Text()

for s.Scan() {
line := s.Text()
if strings.HasPrefix(line, "Written by:") {
info.Authors = strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "Written by:")), ", ")
continue
}
if strings.HasPrefix(line, "Narrated by:") {
info.Narrators = strings.Split(strings.TrimSpace(strings.TrimPrefix(line, "Narrated by:")), ", ")
continue
}
if strings.HasPrefix(line, "URL:") {
info.URL = strings.TrimSpace(strings.TrimPrefix(line, "URL:"))
continue
}
return parts
}

if len(info.Title) == 0 {
return nil, fmt.Errorf("invalid info.txt: missing book title")
}

if len(info.Authors) == 0 {
return nil, fmt.Errorf("invalid info.txt: missing book authors")
}

if len(info.Narrators) == 0 {
return nil, fmt.Errorf("invalid info.txt: missing book narrators")
}

if len(info.URL) == 0 {
return nil, fmt.Errorf("invalid info.txt: missing book URL")
}

if err := s.Err(); err != nil {
return nil, err
}

return info, nil
}

func ListDownloadedBooks(dir string) ([]*audible.Book, error) {
booksByID := make(map[string]*audible.Book)
books := make([]*audible.Book, 0)
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
Expand All @@ -98,41 +149,56 @@ func ListDownloadedBooks(dir string) ([]*audible.Book, error) {
if info.IsDir() {
return nil
}
isMP4 := filepath.Ext(path) == ".mp4"

// identify existing books by their info.txt files
if filepath.Base(path) != "info.txt" {
return nil
}

file, err := os.Open(path)
if err != nil {
if isMP4 {
log.Warnf("Unable to read %s: %s", path, err)
}
return nil
log.Warnf("Unable to read %s: %s", path, err)
return err
}
defer file.Close()
if _, _, err := tag.Identify(file); err != nil {
if isMP4 {
log.Warnf("Unable to identify %s: %s", path, err)
}
return nil
bookInfo, err := parseInfoTxt(file)
if err != nil {
log.Warnf("Unable to parse book info (%s): %s", path, err)
return err
}

b := &audible.Book{
Title: bookInfo.Title,
Authors: bookInfo.Authors,
Narrators: bookInfo.Narrators,
AudibleURL: bookInfo.URL,
}
meta, err := tag.ReadAtoms(file)

isMP4 := false
exts := []string{".mp4", ".aax"}
m, err := filepath.Glob(filepath.Join(filepath.Dir(path), "*"))
if err != nil {
if isMP4 {
log.Warnf("Unable to read tag data %s: %s", path, err)
log.Debugf("unable to glob %s: %v", path, err)
return err
}
for _, e := range m {
for _, ext := range exts {
if strings.Contains(e, ext) {
if filepath.Ext(e) == ".mp4" {
isMP4 = true
}
e = strings.TrimSuffix(e, ".icloud")
b.LocalPath = e
log.Debugf("setting local path for book(%s): %s", b.ID(), e)
}
}
return nil
}
if strings.ToLower(meta.Genre()) != "audiobook" {

if b.LocalPath == "" {
log.Warnf("unable to find audio file for %s", path)
return nil
}
var thumbImg image.Image
if p := meta.Picture(); p != nil {
thumbImg, _, _ = image.Decode(bytes.NewReader(p.Data))
}
b := &audible.Book{
Title: meta.Title(),
Authors: parseAuthors(meta.Artist()),
ThumbImage: thumbImg,
LocalPath: path,
}

if eb := booksByID[b.ID()]; eb != nil {
// don't overwrite an mp4 entry (e.g. with an aax one)
if filepath.Ext(eb.LocalPath) == ".mp4" {
Expand All @@ -141,6 +207,41 @@ func ListDownloadedBooks(dir string) ([]*audible.Book, error) {
}
books = append(books, b)
booksByID[b.ID()] = b

if !isMP4 {
return nil
}

mp4File, err := os.Open(b.LocalPath)
if err != nil {
log.Warnf("Unable to read %s: %s", path, err)
return nil
}
defer mp4File.Close()
if _, _, err := tag.Identify(mp4File); err != nil {
log.Warnf("Unable to identify %s: %s", path, err)
return nil
}

meta, err := tag.ReadAtoms(mp4File)
if err != nil {
log.Warnf("Unable to read tag data %s: %s", path, err)
return nil
}
if strings.ToLower(meta.Genre()) != "audiobook" {
return nil
}

if title := meta.Title(); title != "" {
b.Title = title
}

var thumbImg image.Image
if p := meta.Picture(); p != nil {
thumbImg, _, _ = image.Decode(bytes.NewReader(p.Data))
}
b.ThumbImage = thumbImg

return nil
})
if err != nil {
Expand Down

0 comments on commit b4510f8

Please sign in to comment.