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

VorbisComment Recommendation and Chapter Support #969

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
75 changes: 72 additions & 3 deletions cozy/media/tag_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, uri: str, discoverer_info: GstPbutils.DiscovererInfo):
self.discoverer_info = discoverer_info

self.tags: Gst.TagList = discoverer_info.get_tags()

self._is_ogg = self.uri.lower().endswith(("opus", "ogg", "flac"))
if not self.tags:
raise ValueError("Failed to retrieve tags from discoverer_info")

Expand Down Expand Up @@ -53,15 +53,23 @@ def _get_book_name_fallback(self):
return unquote(directory)

def _get_author(self):
authors = self._get_string_list(Gst.TAG_COMPOSER)
authors = (
self._get_string_list(Gst.TAG_ARTIST)
if self._is_ogg
else self._get_string_list(Gst.TAG_COMPOSER)
)

if len(authors) > 0 and authors[0]:
return "; ".join(authors)
else:
return _("Unknown")

def _get_reader(self):
readers = self._get_string_list(Gst.TAG_ARTIST)
readers = (
self._get_string_list(Gst.TAG_PERFORMER)
if self._is_ogg
else self._get_string_list(Gst.TAG_ARTIST)
)

if len(readers) > 0 and readers[0]:
return "; ".join(readers)
Expand Down Expand Up @@ -92,6 +100,8 @@ def _get_chapters(self):
if self.uri.lower().endswith("m4b") and self._mutagen_supports_chapters():
mutagen_tags = self._parse_with_mutagen()
return self._get_m4b_chapters(mutagen_tags)
elif self._is_ogg:
return self._get_ogg_chapters()
else:
return self._get_single_chapter()

Expand Down Expand Up @@ -162,6 +172,55 @@ def _get_m4b_chapters(self, mutagen_tags: MP4) -> list[Chapter]:

return chapters

def _get_ogg_chapters(self) -> list[Chapter]:
comment_list: list[str] = self._get_string_list("extended-comment")
chapter_dict: dict[int, list[float | str | None]] = {}
for comment in comment_list:
comment_split = comment.split("=", 1)
if len(comment_split) != 2: # Is a tag set in this comment
continue
if (
len(comment_split[0]) not in (10, 14)
or comment_split[0][:7].lower() != "chapter"
or not comment_split[0][7:10].isdecimal()
):
continue # Is the tag in the form chapter + 3 numbers + maybe name
try:
chapter_num = int(comment_split[0][7:10], 10) # get number from 3 chars
except ValueError:
continue
if chapter_num not in chapter_dict:
chapter_dict[chapter_num] = [None, None]
if len(comment_split[0]) == 14 and comment_split[0][10:14].lower() == "name":
chapter_dict[chapter_num][1] = comment_split[1]
elif len(comment_split[0]) == 10:
chapter_dict[chapter_num][0] = self._vorbis_timestamp_to_secs(comment_split[1])
if 0 not in chapter_dict or chapter_dict[0][0] is None or chapter_dict[0][1] is None:
return self._get_single_chapter()
i = 1
chapter_list: list[Chapter] = []
while (
i in chapter_dict and chapter_dict[i][0] is not None and chapter_dict[i][1] is not None
):
chapter_list.append(
Chapter(
name=chapter_dict[i - 1][1],
position=int(chapter_dict[i - 1][0] * NS_TO_SEC),
length=chapter_dict[i][0] - chapter_dict[i - 1][0],
number=i,
)
)
i += 1
chapter_list.append(
Chapter(
name=chapter_dict[i - 1][1],
position=int(chapter_dict[i - 1][0] * NS_TO_SEC),
length=self._get_length_in_seconds() - chapter_dict[i - 1][0],
number=i,
)
)
return chapter_list

def _parse_with_mutagen(self) -> MP4:
path = unquote(urlparse(self.uri).path)
mutagen_mp4 = MP4(path)
Expand All @@ -174,3 +233,13 @@ def _mutagen_supports_chapters() -> bool:
return True

return mutagen.version[0] == 1 and mutagen.version[1] >= 45

@staticmethod
def _vorbis_timestamp_to_secs(timestamp: str) -> float | None:
elems = timestamp.split(":")
if len(elems) != 3:
return None
try:
return int(elems[0], 10) * 3600 + int(elems[1], 10) * 60 + float(elems[2])
except ValueError:
return None