From 0570210262ae48bc93d356437bf77a330092e0e8 Mon Sep 17 00:00:00 2001 From: Richard Garber Date: Mon, 18 Nov 2024 21:16:57 -0500 Subject: [PATCH 1/6] Added parsing of VorbisComment field recommendations: https://xiph.org/vorbis/doc/v-comment.html --- cozy/media/tag_reader.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/cozy/media/tag_reader.py b/cozy/media/tag_reader.py index ef855678..24f9a45f 100644 --- a/cozy/media/tag_reader.py +++ b/cozy/media/tag_reader.py @@ -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") @@ -53,7 +53,11 @@ 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) @@ -61,7 +65,11 @@ def _get_author(self): 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) From 71a340ad5af9766d30cc4a971eb69e8496cc1e78 Mon Sep 17 00:00:00 2001 From: Richard Garber Date: Mon, 18 Nov 2024 21:19:28 -0500 Subject: [PATCH 2/6] Added VorbisComment Chapter Extension parsing: https://wiki.xiph.org/Chapter_Extension --- cozy/media/tag_reader.py | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/cozy/media/tag_reader.py b/cozy/media/tag_reader.py index 24f9a45f..c653e891 100644 --- a/cozy/media/tag_reader.py +++ b/cozy/media/tag_reader.py @@ -170,6 +170,48 @@ 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: + if len(comment) < 12 or comment[:7] not in {"CHAPTER", "chapter"}: + continue + try: + chapter_num = int(comment[7:10], 10) + except ValueError: + continue + if chapter_num not in chapter_dict: + chapter_dict[chapter_num] = [None, None] + if len(comment) > 15 and comment[10:14] in {"NAME", "name"}: + chapter_dict[chapter_num][1] = comment[15:] + else: + chapter_dict[chapter_num][0] = self._vorbis_timestamp_to_secs(comment[11:]) + 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) @@ -182,3 +224,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 From 5d0953d20e1bd33fcf04fecccdbc3c12f89065ec Mon Sep 17 00:00:00 2001 From: Richard Garber Date: Mon, 18 Nov 2024 22:12:06 -0500 Subject: [PATCH 3/6] Changed keyword detection in ogg chapter function to be faster --- cozy/media/tag_reader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cozy/media/tag_reader.py b/cozy/media/tag_reader.py index c653e891..19e492be 100644 --- a/cozy/media/tag_reader.py +++ b/cozy/media/tag_reader.py @@ -174,7 +174,7 @@ 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: - if len(comment) < 12 or comment[:7] not in {"CHAPTER", "chapter"}: + if len(comment) < 12 or comment[:7].lower() != "chapter": continue try: chapter_num = int(comment[7:10], 10) @@ -182,7 +182,7 @@ def _get_ogg_chapters(self) -> list[Chapter]: continue if chapter_num not in chapter_dict: chapter_dict[chapter_num] = [None, None] - if len(comment) > 15 and comment[10:14] in {"NAME", "name"}: + if len(comment) > 15 and comment[10:14].lower() != "name": chapter_dict[chapter_num][1] = comment[15:] else: chapter_dict[chapter_num][0] = self._vorbis_timestamp_to_secs(comment[11:]) From 689eae681680d534598a57523b6881a7f8ea4454 Mon Sep 17 00:00:00 2001 From: Richard Garber Date: Mon, 25 Nov 2024 17:06:54 -0500 Subject: [PATCH 4/6] Changed Ogg Chapter parsing to be more resilient with fewer assumptions --- cozy/media/tag_reader.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/cozy/media/tag_reader.py b/cozy/media/tag_reader.py index 19e492be..a6c38598 100644 --- a/cozy/media/tag_reader.py +++ b/cozy/media/tag_reader.py @@ -174,18 +174,26 @@ 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: - if len(comment) < 12 or comment[:7].lower() != "chapter": + 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[7:10], 10) + 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) > 15 and comment[10:14].lower() != "name": - chapter_dict[chapter_num][1] = comment[15:] - else: - chapter_dict[chapter_num][0] = self._vorbis_timestamp_to_secs(comment[11:]) + 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]) + print(chapter_dict) 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 From c614d1eaa938000cc3a0a5f4afe620b022216822 Mon Sep 17 00:00:00 2001 From: Richard Garber Date: Mon, 25 Nov 2024 17:07:43 -0500 Subject: [PATCH 5/6] Actually make the _get_chapters() function call the ogg code --- cozy/media/tag_reader.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cozy/media/tag_reader.py b/cozy/media/tag_reader.py index a6c38598..614d8e6f 100644 --- a/cozy/media/tag_reader.py +++ b/cozy/media/tag_reader.py @@ -100,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() From 3d51867949dd553077b2bc90d12b4bd2596c769b Mon Sep 17 00:00:00 2001 From: Richard Garber Date: Mon, 25 Nov 2024 17:08:48 -0500 Subject: [PATCH 6/6] Remove debug print --- cozy/media/tag_reader.py | 1 - 1 file changed, 1 deletion(-) diff --git a/cozy/media/tag_reader.py b/cozy/media/tag_reader.py index 614d8e6f..1dfdb1e9 100644 --- a/cozy/media/tag_reader.py +++ b/cozy/media/tag_reader.py @@ -195,7 +195,6 @@ def _get_ogg_chapters(self) -> list[Chapter]: 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]) - print(chapter_dict) 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