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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ point is tag `upstream-baseline`.
- `Config/pin_max_runtime_delta_min`: optional hard limit on the runtime mismatch of a pinned ASIN (default off).

### Fixed
- `disk N/` and `part N/` folders, which are grouped into one book since the multi-disc fix below, were filed
into a flat `Author/Title/` folder, so `Disk 1/01.mp3` and `Disk 2/01.mp3` collided and the second was
silently skipped. They now get a `disc_folder` subfolder like `cd N/` and `disc N/` always did.
- Multi-disc releases (`cd1/`, `Disc 01/`, `part 2/`) were grouped by the disc folder. Two downloads that both
used `cd1/` became one book (files from both hardlinked to the first match) and a single release's discs were
matched separately with one-disc runtime (the wrong edition could win). Grouping now walks past disc parents
Expand Down
2 changes: 1 addition & 1 deletion CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ A copy of default_config.cfg can be found under the /templates folder. It is re
| | multi_author | How to handle the Author folder for multi-author books: first_author, authors, "", "Static folder name" | first_author |
| | in_series | Format of the generated tree for books in a series | {author}/{series}/{series} #{part} - {title} |
| | no_series | Format of the generated tree for books that are NOT in a series | {author}/{title} |
| | disc_folder | Format of the folder name for multi-disc books | {title} {disc} |
| | disc_folder | Format of the folder name for multi-disc books (files whose parent folder is `cd N`, `disc N`, `disk N` or `part N`) | {title} {disc} |
| tokens | | | |
| | skip_series | Used when fixid3 is true and an alt Title is generated from the id3-series data | 0 |
| | kw_ignore | Characters ignored when generating keywords for search | | [".", ":", "_", "[", "]", "{", "}", ",", ";", "(", ")"] |
Expand Down
9 changes: 8 additions & 1 deletion myx_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,8 +434,15 @@ def loadFromCache(key, category, cfg):
except (OSError, ValueError):
return None

#the disc-folder vocabulary must cover everything myx_names.DISC_FOLDER groups into one book (cd/disc/disk/part N),
#or two grouped discs with the same file names would be filed into one flat folder and the second silently skipped.
#cd/disc stay unanchored as upstream had them ("Title Disc 2" is a disc folder too); part needs a word boundary
#so that a title such as "Counterpart 2" is not mistaken for one.
MULTI_CD = re.compile(r"(?:cd|disc|disk)\s?\d+|\bpart\s?\d+", re.IGNORECASE)


def isMultiCD(parent):
return re.search(r"disc\s?\d+", parent.lower()) or re.search(r"cd\s?\d+", parent.lower())
return MULTI_CD.search(parent) is not None

def isGraphicAudio(author):
m = re.search(r"graphic[\s]?audio[\s]?(llc[.]?)*", author.lower())
Expand Down
27 changes: 27 additions & 0 deletions tests/test_names.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import unittest

import myx_names as N
from tests.support import FakeConfig


def parse(name, known=(), file_name=None):
Expand Down Expand Up @@ -288,3 +289,29 @@ def test_last_scan_of_one_new_disc_pulls_in_the_older_disc(self):
finally:
import shutil
shutil.rmtree(src)


class DiscFolderTest(unittest.TestCase):
def test_every_folder_grouped_as_a_disc_is_filed_into_a_disc_subfolder(self):
import myx_utilities
for folder in ("cd1", "CD 2", "Disc 1", "disc01", "disk 1", "Disk2", "Part 2", "part 10", "Title Disc 2"):
self.assertTrue(N.DISC_FOLDER.match(folder) is None or myx_utilities.isMultiCD(folder), folder)
for folder in ("cd1", "Disk 1", "Part 2", "Title Disc 2"):
self.assertTrue(myx_utilities.isMultiCD(folder), folder)
for folder in ("Counterpart 2", "Rampart 5", "Some Book", "Chapter 3", "1984"):
self.assertFalse(myx_utilities.isMultiCD(folder), folder)

def test_grouped_discs_get_distinct_target_folders(self):
# Disk 1/01.mp3 and Disk 2/01.mp3 are one book since #15; they must not both target Author/Title/01.mp3
import myx_classes
book = myx_classes.Book(asin="B000000001", title="Title")
book.authors = [myx_classes.Contributor("Author")]
cfg = FakeConfig("/tmp", **{"Config/target_path/no_series": "{author}/{title}",
"Config/target_path/disc_folder": "{title} {disc}"})
targets = set()
for disc in ("Disk 1", "Disk 2", "Part 1", "Part 2"):
bf = myx_classes.BookFile(f"Title/{disc}/01.mp3", f"/dl/Title/{disc}/01.mp3", "/dl", "/lib")
targets.add(bf.getConfigTargetPath(cfg, book))
self.assertEqual(len(targets), 4)
self.assertIn("/lib/Author/Title/Title Disk 1", targets)

Loading