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:
parent
ce257a1f35
commit
02a779b413
|
@ -5,10 +5,24 @@ from typing import Any, Dict
|
||||||
|
|
||||||
import mutagen
|
import mutagen
|
||||||
from libretime_shared.files import compute_md5
|
from libretime_shared.files import compute_md5
|
||||||
|
from mutagen.easyid3 import EasyID3
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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]):
|
def analyze_metadata(filepath_: str, metadata: Dict[str, Any]):
|
||||||
"""
|
"""
|
||||||
Extract audio metadata from tags embedded in the file using mutagen.
|
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):
|
except (AttributeError, KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
extracted_tags_mapping = {
|
extracted_tags_mapping = [
|
||||||
"title": "track_title",
|
("title", "track_title"),
|
||||||
"artist": "artist_name",
|
("artist", "artist_name"),
|
||||||
"album": "album_title",
|
("album", "album_title"),
|
||||||
"bpm": "bpm",
|
("bpm", "bpm"),
|
||||||
"composer": "composer",
|
("composer", "composer"),
|
||||||
"conductor": "conductor",
|
("conductor", "conductor"),
|
||||||
"copyright": "copyright",
|
("copyright", "copyright"),
|
||||||
"comment": "comment",
|
("comment", "comment"),
|
||||||
"encoded_by": "encoder",
|
("comment", "comments"),
|
||||||
"genre": "genre",
|
("comment", "description"),
|
||||||
"isrc": "isrc",
|
("encoded_by", "encoder"),
|
||||||
"label": "label",
|
("genre", "genre"),
|
||||||
"organization": "label",
|
("isrc", "isrc"),
|
||||||
# "length": "length",
|
("label", "label"),
|
||||||
"language": "language",
|
("organization", "label"),
|
||||||
"last_modified": "last_modified",
|
# ("length", "length"),
|
||||||
"mood": "mood",
|
("language", "language"),
|
||||||
"bit_rate": "bit_rate",
|
("last_modified", "last_modified"),
|
||||||
"replay_gain": "replaygain",
|
("mood", "mood"),
|
||||||
# "tracknumber": "track_number",
|
("bit_rate", "bit_rate"),
|
||||||
# "track_total": "track_total",
|
("replay_gain", "replaygain"),
|
||||||
"website": "website",
|
# ("tracknumber", "track_number"),
|
||||||
"date": "year",
|
# ("track_total", "track_total"),
|
||||||
# "mime_type": "mime",
|
("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:
|
try:
|
||||||
metadata[metadata_key] = extracted[extracted_key]
|
metadata[metadata_key] = extracted[extracted_key]
|
||||||
if isinstance(metadata[metadata_key], list):
|
if isinstance(metadata[metadata_key], list):
|
||||||
|
|
|
@ -96,12 +96,18 @@ tags = {
|
||||||
"comment": "Test Comment",
|
"comment": "Test Comment",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mp3Tags = {
|
||||||
|
**tags,
|
||||||
|
"comments": tags["comment"],
|
||||||
|
"description": tags["comment"],
|
||||||
|
}
|
||||||
|
|
||||||
FILES_TAGGED = [
|
FILES_TAGGED = [
|
||||||
FixtureMeta(
|
FixtureMeta(
|
||||||
here / "s1-jointstereo-tagged.mp3",
|
here / "s1-jointstereo-tagged.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**tags,
|
**mp3Tags,
|
||||||
"bit_rate": approx(128000, abs=1e2),
|
"bit_rate": approx(128000, abs=1e2),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -111,7 +117,7 @@ FILES_TAGGED = [
|
||||||
here / "s1-mono-tagged.mp3",
|
here / "s1-mono-tagged.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**tags,
|
**mp3Tags,
|
||||||
"bit_rate": approx(64000, abs=1e2),
|
"bit_rate": approx(64000, abs=1e2),
|
||||||
"channels": 1,
|
"channels": 1,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -121,7 +127,7 @@ FILES_TAGGED = [
|
||||||
here / "s1-stereo-tagged.mp3",
|
here / "s1-stereo-tagged.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**tags,
|
**mp3Tags,
|
||||||
"bit_rate": approx(128000, abs=1e2),
|
"bit_rate": approx(128000, abs=1e2),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -151,7 +157,7 @@ FILES_TAGGED = [
|
||||||
here / "s1-mono-tagged.m4a",
|
here / "s1-mono-tagged.m4a",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**tags,
|
**mp3Tags,
|
||||||
"bit_rate": approx(65000, abs=5e4),
|
"bit_rate": approx(65000, abs=5e4),
|
||||||
"channels": 2, # Weird
|
"channels": 2, # Weird
|
||||||
"mime": "audio/mp4",
|
"mime": "audio/mp4",
|
||||||
|
@ -161,7 +167,7 @@ FILES_TAGGED = [
|
||||||
here / "s1-stereo-tagged.m4a",
|
here / "s1-stereo-tagged.m4a",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**tags,
|
**mp3Tags,
|
||||||
"bit_rate": approx(128000, abs=1e5),
|
"bit_rate": approx(128000, abs=1e5),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp4",
|
"mime": "audio/mp4",
|
||||||
|
@ -228,12 +234,18 @@ tags = {
|
||||||
"comment": "Ł Ą Ż Ę Ć Ń Ś Ź",
|
"comment": "Ł Ą Ż Ę Ć Ń Ś Ź",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mp3Tags = {
|
||||||
|
**tags,
|
||||||
|
"comments": tags["comment"],
|
||||||
|
"description": tags["comment"],
|
||||||
|
}
|
||||||
|
|
||||||
FILES_TAGGED += [
|
FILES_TAGGED += [
|
||||||
FixtureMeta(
|
FixtureMeta(
|
||||||
here / "s1-jointstereo-tagged-utf8.mp3",
|
here / "s1-jointstereo-tagged-utf8.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**tags,
|
**mp3Tags,
|
||||||
"bit_rate": approx(128000, abs=1e2),
|
"bit_rate": approx(128000, abs=1e2),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -243,7 +255,7 @@ FILES_TAGGED += [
|
||||||
here / "s1-mono-tagged-utf8.mp3",
|
here / "s1-mono-tagged-utf8.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**tags,
|
**mp3Tags,
|
||||||
"bit_rate": approx(64000, abs=1e2),
|
"bit_rate": approx(64000, abs=1e2),
|
||||||
"channels": 1,
|
"channels": 1,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -253,7 +265,7 @@ FILES_TAGGED += [
|
||||||
here / "s1-stereo-tagged-utf8.mp3",
|
here / "s1-stereo-tagged-utf8.mp3",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**tags,
|
**mp3Tags,
|
||||||
"bit_rate": approx(128000, abs=1e2),
|
"bit_rate": approx(128000, abs=1e2),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp3",
|
"mime": "audio/mp3",
|
||||||
|
@ -283,7 +295,7 @@ FILES_TAGGED += [
|
||||||
here / "s1-mono-tagged-utf8.m4a",
|
here / "s1-mono-tagged-utf8.m4a",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**tags,
|
**mp3Tags,
|
||||||
"bit_rate": approx(65000, abs=5e4),
|
"bit_rate": approx(65000, abs=5e4),
|
||||||
"channels": 2, # Weird
|
"channels": 2, # Weird
|
||||||
"mime": "audio/mp4",
|
"mime": "audio/mp4",
|
||||||
|
@ -293,7 +305,7 @@ FILES_TAGGED += [
|
||||||
here / "s1-stereo-tagged-utf8.m4a",
|
here / "s1-stereo-tagged-utf8.m4a",
|
||||||
{
|
{
|
||||||
**meta,
|
**meta,
|
||||||
**tags,
|
**mp3Tags,
|
||||||
"bit_rate": approx(128000, abs=1e5),
|
"bit_rate": approx(128000, abs=1e5),
|
||||||
"channels": 2,
|
"channels": 2,
|
||||||
"mime": "audio/mp4",
|
"mime": "audio/mp4",
|
||||||
|
|
|
@ -27,8 +27,8 @@ def test_analyze_metadata(filepath: Path, metadata: dict):
|
||||||
del metadata["length"]
|
del metadata["length"]
|
||||||
del found["length"]
|
del found["length"]
|
||||||
|
|
||||||
# mp3,ogg,flac files does not support comments yet
|
# ogg,flac files does not support comments yet
|
||||||
if not filepath.suffix == ".m4a":
|
if not filepath.suffix == ".m4a" and not filepath.suffix == ".mp3":
|
||||||
if "comment" in metadata:
|
if "comment" in metadata:
|
||||||
del metadata["comment"]
|
del metadata["comment"]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue