Skip to content
Open
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
72 changes: 70 additions & 2 deletions src/audible_cli/cmds/cmd_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,17 @@ def display_counter():
@click.option(
"--title", "-t",
multiple=True,
help="tile of the audiobook (partial search)"
help="title of the audiobook (partial search)"
)
@click.option(
"--author", "-a",
Copy link
Owner

Choose a reason for hiding this comment

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

The -a option is already used by --asin.

Copy link
Author

Choose a reason for hiding this comment

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

Oops =] I thought I'd looked for that.

All things considered, I think the more natural assumption would be to have the different lists merged before checking -- I just haven't had time to come back to this since then and I'm not sure how soon I can

Copy link
Owner

@mkb79 mkb79 Feb 22, 2024

Choose a reason for hiding this comment

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

I think I'll have time this weekend to rework some things and then merge your request.

multiple=True,
help="author of the audiobook (partial search)"
)
@click.option(
"--series", "-s",
multiple=True,
help="series of the audiobook (partial search)"
)
@click.option(
"--aax",
Expand Down Expand Up @@ -757,6 +767,8 @@ async def cli(session, api_client, **params):
get_all = params.get("all") is True
asins = params.get("asin")
titles = params.get("title")
authors = params.get("author")
series = params.get("series")
if get_all and (asins or titles):
raise click.BadOptionUsage(
"--all",
Expand Down Expand Up @@ -836,7 +848,7 @@ async def cli(session, api_client, **params):
image_sizes=", ".join(cover_sizes),
bunch_size=bunch_size,
response_groups=(
"product_desc, media, product_attrs, relationships, "
"product_desc, media, product_attrs, relationships, contributors, "
"series, customer_rights, pdf_url"
),
start_date=start_date,
Expand Down Expand Up @@ -895,6 +907,62 @@ async def cli(session, api_client, **params):
f"Skip title {title}: Not found in library"
)

for author in authors:
match = library.search_item_by_author(author)
full_match = [i for i in match if i[1] == 100]

if match:
if no_confirm:
[jobs.append(i[0].asin) for i in full_match or match]
else:
choices = []
for i in full_match or match:
a = i[0].asin
t = i[0].full_title
l = ", ".join(a["name"] for a in i[0].authors)
c = questionary.Choice(title=f"{a} # {t} by {l}", value=a)
choices.append(c)

answer = await questionary.checkbox(
f"Found the following matches for '{author}'. Which you want to download?",
choices=choices
).unsafe_ask_async()
if answer is not None:
[jobs.append(i) for i in answer]

else:
logger.error(
f"Skip author {author}: Not found in library"
)

for s in series:
match = library.search_item_by_series(s)
full_match = [i for i in match if i[1] == 100]

if match:
if no_confirm:
[jobs.append(i[0].asin) for i in full_match or match]
else:
choices = []
for i in full_match or match:
a = i[0].asin
t = i[0].full_title
l = ", ".join(s["title"] for s in i[0].series)
c = questionary.Choice(title=f"{a} # {t} in {l}", value=a)
choices.append(c)

answer = await questionary.checkbox(
f"Found the following matches for '{s}'. Which you want to download?",
choices=choices
).unsafe_ask_async()
if answer is not None:
[jobs.append(i) for i in answer]

else:
logger.error(
f"Skip series {s}: Not found in library"
)

# set queue
global QUEUE
QUEUE = asyncio.Queue()
Expand Down
35 changes: 32 additions & 3 deletions src/audible_cli/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
NotDownloadableAsAAX,
ItemNotPublished
)
from .utils import full_response_callback, LongestSubString
from .utils import full_response_callback, substring_in_list_accuracy


logger = logging.getLogger("audible_cli.models")
Expand Down Expand Up @@ -104,13 +104,26 @@ def create_base_filename(self, mode: str):
return base_filename

def substring_in_title_accuracy(self, substring):
match = LongestSubString(substring, self.full_title)
return round(match.percentage, 2)
return substring_in_list_accuracy(substring, [self.full_title])

def substring_in_title(self, substring, p=100):
accuracy = self.substring_in_title_accuracy(substring)
return accuracy >= p

def substring_in_authors_accuracy(self, substring):
if not self.authors:
return 0

authors = [author["name"] for author in self.authors]
return substring_in_list_accuracy(substring, authors)

def substring_in_series_accuracy(self, substring):
if not self.series:
return 0

series = [serie["title"] for serie in self.series]
return substring_in_list_accuracy(substring, series)

def get_cover_url(self, res: Union[str, int] = 500):
images = self.product_images
res = str(res)
Expand Down Expand Up @@ -468,6 +481,22 @@ def search_item_by_title(self, search_title, p=80):

return match

def search_item_by_author(self, search_author, p=80):
match = []
for i in self._data:
accuracy = i.substring_in_authors_accuracy(search_author)
match.append([i, accuracy]) if accuracy >= p else ""

return match

def search_item_by_series(self, search_series, p=80):
match = []
for i in self._data:
accuracy = i.substring_in_series_accuracy(search_series)
match.append([i, accuracy]) if accuracy >= p else ""

return match


class Library(BaseList):
def _prepare_data(self, data: Union[dict, list]) -> list:
Expand Down
7 changes: 7 additions & 0 deletions src/audible_cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,13 @@ def longest_match(self):
def percentage(self):
return self._match.size / len(self._search_for) * 100

def rounded_percentage(self, digits: int = 2):
return round(self.percentage, digits)


def substring_in_list_accuracy(s: str, l: List[str], d: int = 2) -> float:
return max(map(lambda x: LongestSubString(s, x).rounded_percentage(d), l))


def asin_in_library(asin, library):
items = library.get("items") or library
Expand Down