feat(analyzer): parse comment fields from mp3 files (#3082)

### Description

Upload comments from mp3 files into libretime `comments` and
`description` fields.

**This is a new feature**:

Yes

**I have updated the documentation to reflect these changes**:

No none required

### Testing Notes

**What I did:**

I uploaded tracks that contained comments into LibreTime and checked the
database to ensure that the `comments` and `description` fields were
correctly populated. I then went to the UI and confirmed that the
description field had the MP3 comment in it inside of the metadata
editor. I then uploaded some files that did not have comments to make
sure I did not break any existing functionality.

**How you can replicate my testing:**

Follow the steps in what I did

### **Links**

Fixes #526

---------

Co-authored-by: Kyle Robbertze <paddatrapper@users.noreply.github.com>
This commit is contained in:
dakriy 2024-11-22 10:28:06 -08:00 committed by GitHub
parent ce257a1f35
commit 02a779b413
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 67 additions and 39 deletions

View File

@ -5,10 +5,24 @@ from typing import Any, Dict
import mutagen
from libretime_shared.files import compute_md5
from mutagen.easyid3 import EasyID3
logger = logging.getLogger(__name__)
def flatten(xss):
return [x for xs in xss for x in xs]
def comment_get(id3, _):
comments = [v.text for k, v in id3.items() if "COMM" in k or "comment" in k]
return flatten(comments)
EasyID3.RegisterKey("comment", comment_get)
def analyze_metadata(filepath_: str, metadata: Dict[str, Any]):
"""
Extract audio metadata from tags embedded in the file using mutagen.
@ -71,34 +85,36 @@ def analyze_metadata(filepath_: str, metadata: Dict[str, Any]):
except (AttributeError, KeyError, IndexError):
pass
extracted_tags_mapping = {
"title": "track_title",
"artist": "artist_name",
"album": "album_title",
"bpm": "bpm",
"composer": "composer",
"conductor": "conductor",
"copyright": "copyright",
"comment": "comment",
"encoded_by": "encoder",
"genre": "genre",
"isrc": "isrc",
"label": "label",
"organization": "label",
# "length": "length",
"language": "language",
"last_modified": "last_modified",
"mood": "mood",
"bit_rate": "bit_rate",
"replay_gain": "replaygain",
# "tracknumber": "track_number",
# "track_total": "track_total",
"website": "website",
"date": "year",
# "mime_type": "mime",
}
extracted_tags_mapping = [
("title", "track_title"),
("artist", "artist_name"),
("album", "album_title"),
("bpm", "bpm"),
("composer", "composer"),
("conductor", "conductor"),
("copyright", "copyright"),
("comment", "comment"),
("comment", "comments"),
("comment", "description"),
("encoded_by", "encoder"),
("genre", "genre"),
("isrc", "isrc"),
("label", "label"),
("organization", "label"),
# ("length", "length"),
("language", "language"),
("last_modified", "last_modified"),
("mood", "mood"),
("bit_rate", "bit_rate"),
("replay_gain", "replaygain"),
# ("tracknumber", "track_number"),
# ("track_total", "track_total"),
("website", "website"),
("date", "year"),
# ("mime_type", "mime"),
]
for extracted_key, metadata_key in extracted_tags_mapping.items():
for extracted_key, metadata_key in extracted_tags_mapping:
try:
metadata[metadata_key] = extracted[extracted_key]
if isinstance(metadata[metadata_key], list):

View File

@ -96,12 +96,18 @@ tags = {
"comment": "Test Comment",
}
mp3Tags = {
**tags,
"comments": tags["comment"],
"description": tags["comment"],
}
FILES_TAGGED = [
FixtureMeta(
here / "s1-jointstereo-tagged.mp3",
{
**meta,
**tags,
**mp3Tags,
"bit_rate": approx(128000, abs=1e2),
"channels": 2,
"mime": "audio/mp3",
@ -111,7 +117,7 @@ FILES_TAGGED = [
here / "s1-mono-tagged.mp3",
{
**meta,
**tags,
**mp3Tags,
"bit_rate": approx(64000, abs=1e2),
"channels": 1,
"mime": "audio/mp3",
@ -121,7 +127,7 @@ FILES_TAGGED = [
here / "s1-stereo-tagged.mp3",
{
**meta,
**tags,
**mp3Tags,
"bit_rate": approx(128000, abs=1e2),
"channels": 2,
"mime": "audio/mp3",
@ -151,7 +157,7 @@ FILES_TAGGED = [
here / "s1-mono-tagged.m4a",
{
**meta,
**tags,
**mp3Tags,
"bit_rate": approx(65000, abs=5e4),
"channels": 2, # Weird
"mime": "audio/mp4",
@ -161,7 +167,7 @@ FILES_TAGGED = [
here / "s1-stereo-tagged.m4a",
{
**meta,
**tags,
**mp3Tags,
"bit_rate": approx(128000, abs=1e5),
"channels": 2,
"mime": "audio/mp4",
@ -228,12 +234,18 @@ tags = {
"comment": "Ł Ą Ż Ę Ć Ń Ś Ź",
}
mp3Tags = {
**tags,
"comments": tags["comment"],
"description": tags["comment"],
}
FILES_TAGGED += [
FixtureMeta(
here / "s1-jointstereo-tagged-utf8.mp3",
{
**meta,
**tags,
**mp3Tags,
"bit_rate": approx(128000, abs=1e2),
"channels": 2,
"mime": "audio/mp3",
@ -243,7 +255,7 @@ FILES_TAGGED += [
here / "s1-mono-tagged-utf8.mp3",
{
**meta,
**tags,
**mp3Tags,
"bit_rate": approx(64000, abs=1e2),
"channels": 1,
"mime": "audio/mp3",
@ -253,7 +265,7 @@ FILES_TAGGED += [
here / "s1-stereo-tagged-utf8.mp3",
{
**meta,
**tags,
**mp3Tags,
"bit_rate": approx(128000, abs=1e2),
"channels": 2,
"mime": "audio/mp3",
@ -283,7 +295,7 @@ FILES_TAGGED += [
here / "s1-mono-tagged-utf8.m4a",
{
**meta,
**tags,
**mp3Tags,
"bit_rate": approx(65000, abs=5e4),
"channels": 2, # Weird
"mime": "audio/mp4",
@ -293,7 +305,7 @@ FILES_TAGGED += [
here / "s1-stereo-tagged-utf8.m4a",
{
**meta,
**tags,
**mp3Tags,
"bit_rate": approx(128000, abs=1e5),
"channels": 2,
"mime": "audio/mp4",

View File

@ -27,8 +27,8 @@ def test_analyze_metadata(filepath: Path, metadata: dict):
del metadata["length"]
del found["length"]
# mp3,ogg,flac files does not support comments yet
if not filepath.suffix == ".m4a":
# ogg,flac files does not support comments yet
if not filepath.suffix == ".m4a" and not filepath.suffix == ".mp3":
if "comment" in metadata:
del metadata["comment"]