Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests suite #43

Open
wants to merge 59 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
7303a8d
Add tests for chapter sorting
Dec 5, 2022
e6588c1
Add test for Audnex API call
Dec 5, 2022
0b49b90
Fix tests
Dec 11, 2022
1bc63fd
Remove import
Dec 11, 2022
32e140a
Fix reference name
Dec 11, 2022
5a8dbe6
Add additional test
Dec 11, 2022
2d90c5f
Fix import
Dec 11, 2022
94cce6c
Add test cases
Dec 11, 2022
0205ebd
Add typing for method
Dec 11, 2022
cb9c744
Add alternative method of sorting chapters
Dec 17, 2022
5819874
Add more complicated track matching algorithm
Dec 17, 2022
7112920
Fix return type
Dec 17, 2022
8cc7917
Fix item reference
Dec 17, 2022
ede5e54
Fix index start
Dec 17, 2022
51fc5e2
Add case to matching algorithm
Dec 17, 2022
b9c1009
Fix typo
Dec 17, 2022
34ded23
Add better affix detection
Dec 18, 2022
faeac09
Remove old logic
Dec 19, 2022
d56c46c
Fix case
Dec 19, 2022
9c09135
Fix case where no prefixes are found
Dec 19, 2022
43d091c
Bump version
Feb 16, 2023
7ea60ca
Initialise seed for randomisation
May 20, 2023
b0d4c22
Add two more test cases
May 20, 2023
68dfd6a
Add additional config option
May 20, 2023
8ffbd64
Add method
May 21, 2023
23f4100
Move method
May 21, 2023
ccf22f7
Fix comments
May 21, 2023
498aa35
Add more complex parameterisation
May 21, 2023
e9e78d8
Add function
May 21, 2023
622ee1a
Add tests and fix function
May 21, 2023
31e92bd
Improve chapter matching logic and tests
May 21, 2023
8580b1b
Add comment test markers for convienience
May 21, 2023
9fc0a54
Fix bug with ordering of trusted source orderings
May 21, 2023
60d9722
Refactor methods out
May 22, 2023
a25e599
Rename function
May 22, 2023
85c1732
Add comments for methods
May 22, 2023
70afac8
Rename readme to follow convention
May 22, 2023
11317e5
Re-indent YAML
May 22, 2023
ac55b38
Switch to using configuration to determine chapter algorithm
May 22, 2023
41a3c02
Update pyproject with new README name
May 22, 2023
901cb29
Continue on wrong algorithm specification
May 22, 2023
191aba8
Fix bug with options
May 23, 2023
d058810
Refactor out methods
May 31, 2023
d60d524
Add more tests
May 31, 2023
a338dcd
Fix bug with zero indexed tracks
May 31, 2023
71680fc
Catch error in levenshtein function
May 31, 2023
37a9598
Update README with information
Jun 1, 2023
b6c83b6
Reformat table
Jun 1, 2023
678d143
Remove old test case
Jun 1, 2023
6611bfc
Fix indent
Jun 1, 2023
9938c81
Reformat according to black
Jun 1, 2023
f6b6f99
Remove old tests
Jun 1, 2023
b4dbd78
Fix bug where a single file is named 'chapter 1'
Jun 3, 2023
563e83f
Add narrator field for albums
Jul 2, 2023
2c0fd3b
Remove old option
Jul 2, 2023
e133b5e
Add requirement for testing
Serene-Arc Sep 12, 2023
925960b
Fix issue with unused parameters
Serene-Arc Sep 12, 2023
4b9fdaf
Fix album property
Serene-Arc Sep 26, 2023
5aecb02
Remove source weight from example doc
Serene-Arc Sep 26, 2023
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 .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

repos:
- repo: https://github.com/psf/black
rev: 22.10.0
rev: 23.1.0
hooks:
- id: black

- repo: https://github.com/pycqa/isort
rev: 5.10.1
rev: 5.12.0
hooks:
- id: isort
name: isort (python)
113 changes: 69 additions & 44 deletions readme.md → README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,49 +17,52 @@ This Beets plugin solves both problems.
1. Install via pip: `pip install beets-audible beets-copyartifacts3` (copyartifacts is optional but recommended). See the next section instead if you're running Beets in Docker (highly recommended as it makes it easier to maintain a separate Beets installation dedicated to audiobooks).
2. Use a separate beets config and database for managing audiobooks. This is the recommended Beets config for this plugin:

```yaml
# add audible to the list of plugins
# copyartifacts is optional but recommended if you're manually specifying metadata via metadata.yml, see the "Importing non-audible content" section
# also add the "web" plugin if using the docker image
plugins: audible copyartifacts edit fromfilename scrub

directory: /audiobooks

# Place books in their own folders to be compatible with Booksonic and Audiobookshelf servers
paths:
# For books that belong to a series
"albumtype:audiobook series_name::.+ series_position::.+": $albumartist/%ifdef{series_name}/%ifdef{series_position} - $album%aunique{}/$track - $title
"albumtype:audiobook series_name::.+": $albumartist/%ifdef{series_name}/$album%aunique{}/$track - $title
# Stand-alone books
"albumtype:audiobook": $albumartist/$album%aunique{}/$track - $title
default: $albumartist/$album%aunique{}/$track - $title
singleton: Non-Album/$artist - $title
comp: Compilations/$album%aunique{}/$track - $title
albumtype_soundtrack: Soundtracks/$album/$track $title

# disables musicbrainz lookup, as it doesn't help for audiobooks
musicbrainz:
host: localhost:5123

audible:
# if the number of files in the book is the same as the number of chapters from Audible,
# attempt to match each file to an audible chapter
match_chapters: true
source_weight: 0.0 # disable the source_weight penalty
fetch_art: true # whether to retrieve cover art
include_narrator_in_artists: true # include author and narrator in artist tag. Or just author
keep_series_reference_in_title: true # set to false to remove ", Book X" from end of titles
keep_series_reference_in_subtitle: true # set to false to remove subtitle if it contains the series name and the word book ex. "Book 1 in Great Series", "Great Series, Book 1"

write_description_file: true # output desc.txt
write_reader_file: true # output reader.txt

copyartifacts:
extensions: .yml # so that metadata.yml is copied, see below

scrub:
auto: yes # optional, enabling this is personal preference
```
```yaml
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you re-indent this so that the code is correctly rendered as a code block as part of the list item? Also helps with reducing the size of the diff so that only the significant changes made show up.

# add audible to the list of plugins
# copyartifacts is optional but recommended if you're manually specifying metadata via metadata.yml, see the "Importing non-audible content" section
plugins: audible copyartifacts edit fromfilename scrub

directory: /audiobooks

# Place books in their own folders to be compatible with Booksonic and Audiobookshelf servers
paths:
# For books that belong to a series
"albumtype:audiobook series_name::.+ series_position::.+": $albumartist/%ifdef{series_name}/%ifdef{series_position} - $album%aunique{}/$track - $title
"albumtype:audiobook series_name::.+": $albumartist/%ifdef{series_name}/$album%aunique{}/$track - $title
# Stand-alone books
"albumtype:audiobook": $albumartist/$album%aunique{}/$track - $title
default: $albumartist/$album%aunique{}/$track - $title
singleton: Non-Album/$artist - $title
comp: Compilations/$album%aunique{}/$track - $title
albumtype_soundtrack: Soundtracks/$album/$track $title

# disables musicbrainz lookup, as it doesn't help for audiobooks
musicbrainz:
enabled: no

audible:
# if the number of files in the book is the same as the number of chapters from Audible,
# attempt to match each file to an audible chapter
match_chapters: true
fetch_art: true # whether to retrieve cover art
include_narrator_in_artists: true # include author and narrator in artist tag. Or just author
keep_series_reference_in_title: true # set to false to remove ", Book X" from end of titles
keep_series_reference_in_subtitle: true # set to false to remove subtitle if it contains the series name and the word book ex. "Book 1 in Great Series", "Great Series, Book 1"
write_description_file: true # output desc.txt
write_reader_file: true # output reader.txt
chapter_matching_algorithms:
- single_file
- source_numbering
- starting_numbers
- natural_sort
- chapter_levenshtein

copyartifacts:
extensions: .yml # so that metadata.yml is copied, see below

scrub:
auto: yes # optional, enabling this is personal preference
```

3. Run the `beet --version` command and verify that the audible plugin appears in the list of plugins.

Expand Down Expand Up @@ -203,12 +206,34 @@ George Orwell/

Desc.txt and reader.txt contain the book description and narrator populated from Audible.

## Chapter Matching Algorithms

There are a number of different ways to try and match chapters that appear in audiobooks. These fit the vast majority of cases that can occur with the files of any audiobooks but the process is entirely customisable. There are a number of algorithms, approaches to do this, that are included in this plugin. However if you encounter a situation that these algorithms don't cover, submit it as a bug report so it can be seen and added to the test cases.

Below are descriptions of the different approaches.

- `single_file`
- If the audiobook consists of a single file, then the chapters will simply be the file itself, or the name of the chapters from online if there is only one match returned.
- `source_numbering`
- If the metadata for the chapters are already contains an order that is continuous, then this will be trusted and used as the ordering.
- `starting_numbers`
- If the files start with a consistent series of numbers (with or without a consistent prefix such as 'Chapter') and those numbers are contiguous, then those will be used.
- `natural_sort`
- If the files have only a little difference between them, then they will be sorted as a person sorts them.
- `chapter_levenshtein`
- The distance between the names of the chapters and the online data is computed and each chapter is matched with the closest online version.
- This method should be last as it is the most variable and least likely to work, but is also the only one that rewrites the chapters' data.

Each of these is included in the configuration file. Reordering the lines in the configuration file will change the order in which they are used. Removing a line will prevent that algorithm from being used entirely. This is the way it is possible to customise the plugin to fit your library, or even a specific audiobook.

Note that only the Levenshtein approach changes the names of the chapters. At the most the indexes will be changed with the remained of the algorithms, due to the approaches they take. This is assuming that it is more important for the chapters to be in the right order, and for the book itself to have the right metadata as a whole, rather than the chapters having the same title as the tracks on Audible.

## Tags Written

The plugin writes the following tags:

| ID3 Tag | Audible.com Value |
| ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| `TIT1` (CONTENTGROUP) | Series, Book # |
| `TALB` (ALBUM) | Title |
| `TIT3` (SUBTITLE) | Subtitle |
Expand Down
9 changes: 7 additions & 2 deletions beetsplug/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,18 @@ def search_goodreads(api_key: str, keywords: str) -> ET.Element:


def get_book_info(asin: str) -> Tuple[Book, BookChapters]:
book_response = json.loads(make_request(f"{AUDNEX_ENDPOINT}/books/{asin}"))
chapter_response = json.loads(make_request(f"{AUDNEX_ENDPOINT}/books/{asin}/chapters"))
book_response, chapter_response = call_audnex_for_book_info(asin)
book = Book.from_audnex_book(book_response)
book_chapters = BookChapters.from_audnex_chapter_info(chapter_response)
return book, book_chapters


def call_audnex_for_book_info(asin: str) -> Tuple[Dict, Dict]:
book_response = json.loads(make_request(f"{AUDNEX_ENDPOINT}/books/{asin}"))
chapter_response = json.loads(make_request(f"{AUDNEX_ENDPOINT}/books/{asin}/chapters"))
return book_response, chapter_response


def make_request(url: str) -> bytes:
"""Makes a request to the specified url and returns received response
The request will be retried up to 3 times in case of failure.
Expand Down
Loading