moving id3 tags and headers to dataclass implementation
This commit is contained in:
parent
bc24d6378a
commit
84f8563671
@ -1,31 +1,129 @@
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from appdirs import user_config_dir
|
||||
from dataclasses import dataclass, asdict
|
||||
from mutagen.id3._frames import (
|
||||
TIT2,
|
||||
TPE1,
|
||||
TALB,
|
||||
TRCK,
|
||||
TDRC,
|
||||
# TYER,
|
||||
TLEN,
|
||||
TCON,
|
||||
TPOS,
|
||||
COMM,
|
||||
TPE2,
|
||||
TCOM,
|
||||
TPE3,
|
||||
TPE4,
|
||||
TCOP,
|
||||
WOAR,
|
||||
USLT,
|
||||
APIC,
|
||||
TENC,
|
||||
TBPM,
|
||||
TKEY,
|
||||
# TDAT,
|
||||
TIME,
|
||||
TSSE,
|
||||
TOPE,
|
||||
TEXT,
|
||||
TOLY,
|
||||
TORY,
|
||||
TPUB,
|
||||
WCOM,
|
||||
WCOP,
|
||||
WOAS,
|
||||
WORS,
|
||||
WPAY,
|
||||
WPUB,
|
||||
)
|
||||
|
||||
|
||||
mutagen_id3_tag_mapping = {
|
||||
"title": TIT2, # Title/song name/content description
|
||||
"artist": TPE1, # Lead performer(s)/Soloist(s)
|
||||
"album": TALB, # Album/Movie/Show title
|
||||
"album_artist": TPE2, # Band/orchestra/accompaniment
|
||||
"genre": TCON, # Content type
|
||||
"album_date": TDRC,
|
||||
# "year": TYER, # Year of recording
|
||||
# "date": TDAT, # Date
|
||||
"lyrics": USLT, # Unsynchronized lyric/text transcription
|
||||
"track_number": TRCK, # Track number/Position in set
|
||||
"album_cover": APIC, # Attached picture
|
||||
"composer": TCOM, # Composer
|
||||
"conductor": TPE3, # Conductor/performer refinement
|
||||
"remixed_by": TPE4, # Interpreted, remixed, or otherwise modified by
|
||||
"part_of_a_set": TPOS, # Part of a set
|
||||
"comments": COMM, # Comments
|
||||
"copyright": TCOP, # Copyright message
|
||||
"url_artist": WOAR, # Official artist/performer webpage
|
||||
"encoded_by": TENC, # Encoded by
|
||||
"bpm": TBPM, # BPM (beats per minute)
|
||||
"initial_key": TKEY, # Initial key
|
||||
"time": TIME, # Time
|
||||
"encoding_settings": TSSE, # Software/Hardware and settings used for encoding
|
||||
"original_artist": TOPE, # Original artist(s)/performer(s)
|
||||
"lyricist": TEXT, # Lyricist/Text writer
|
||||
"original_lyricist": TOLY, # Original lyricist(s)/text writer(s)
|
||||
"original_release_year": TORY, # Original release year
|
||||
"publisher": TPUB, # Publisher
|
||||
"commercial_info": WCOM, # Commercial information
|
||||
"copyright_info": WCOP, # Copyright information
|
||||
"official_audio_source_url": WOAS, # Official audio source webpage
|
||||
"official_internet_radio_station_homepage": WORS, # Official Internet radio station homepage
|
||||
"payment_url": WPAY, # Payment (URL)
|
||||
"publishers_official_webpage": WPUB, # Publishers official webpage
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class SQLiteMap:
|
||||
title: str | None = None
|
||||
artist: str | None = None
|
||||
album: str | None = None
|
||||
album_artist: str | None = None
|
||||
track_number: str | None = None
|
||||
genre: str | None = None
|
||||
length_seconds: str | None = None
|
||||
album_date: str | None = None
|
||||
title: str | TIT2 | None = None
|
||||
artist: str | TPE1 | None = None
|
||||
album: str | TALB | None = None
|
||||
album_artist: str | TPE2 | None = None
|
||||
track_number: str | TRCK | None = None
|
||||
genre: str | TCON | None = None
|
||||
length_ms: str | TLEN | None = None
|
||||
album_date: str | TDRC | None = None
|
||||
codec: str | None = None
|
||||
filepath: str | None = None
|
||||
bitrate: str | None = None
|
||||
# lyrics: str | USLT | None = None
|
||||
# album_art: str | APIC | None = None
|
||||
|
||||
@dataclass
|
||||
class ID3Field:
|
||||
db: str # e.g., "title"
|
||||
gui: str # e.g., "Title"
|
||||
frame_class: Optional[type] = None # e.g., TPE1
|
||||
frame_id: Optional[str] = None # e.g., "TPE1"
|
||||
|
||||
|
||||
"""
|
||||
db names are called FIELDS (e.g., title, track_number, length_seconds)
|
||||
gui names are called HEADERS (e.g., title, track, length, year)
|
||||
id3 names are called TAGS (e.g., TIT2, TPE1, TALB)
|
||||
|
||||
is dataclasses rly worth it?
|
||||
"""
|
||||
class HeaderTags2:
|
||||
def __init__(self):
|
||||
self.headers = [
|
||||
ID3Field(frame_class=TIT2, frame_id="TIT2", db="title", gui="Title"),
|
||||
ID3Field(frame_class=TPE1, frame_id="TPE1", db="artist", gui="Artist"),
|
||||
ID3Field(frame_class=TALB, frame_id="TALB", db="album", gui="Album"),
|
||||
ID3Field(frame_class=TPE2, frame_id="TPE2", db="album_artist", gui="Album Artist"),
|
||||
ID3Field(frame_class=TRCK, frame_id="TRCK", db="track_number", gui="Track"),
|
||||
ID3Field(frame_class=TCON, frame_id="TCON", db="genre", gui="Genre"),
|
||||
ID3Field(frame_class=TLEN, frame_id="TLEN", db="length_ms", gui="Time"),
|
||||
ID3Field(frame_class=TDRC, frame_id="TDRC", db="album_date", gui="Year"),
|
||||
ID3Field(db="codec", gui="Codec"),
|
||||
ID3Field(db="filepath", gui="Filepath"),
|
||||
ID3Field(db="bitrate", gui="Bitrate"),
|
||||
]
|
||||
# Lookup dicts
|
||||
# - Usage example: frame_id['TPE1'].db # => "artist"
|
||||
self.frame_id = {f.frame_id: f for f in self.headers}
|
||||
self.db = {f.db: f for f in self.headers}
|
||||
self.gui = {f.gui: f for f in self.headers}
|
||||
|
||||
|
||||
class HeaderTags:
|
||||
@ -69,7 +167,7 @@ class HeaderTags:
|
||||
genre="genre",
|
||||
album_date="year",
|
||||
codec="codec",
|
||||
length_seconds="length",
|
||||
length_ms="length",
|
||||
filepath="path",
|
||||
bitrate="bitrate",
|
||||
)
|
||||
|
||||
@ -11,6 +11,6 @@ from .CreatePlaylistWindow import CreatePlaylistWindow
|
||||
from .PlaylistsPane import PlaylistsPane
|
||||
from .ExportPlaylistWindow import ExportPlaylistWindow
|
||||
from .QuestionBoxDetails import QuestionBoxDetails
|
||||
from .HeaderTags import HeaderTags
|
||||
from .HeaderTags import HeaderTags, HeaderTags2
|
||||
from .MediaPlayer import MediaPlayer
|
||||
from .SearchLineEdit import SearchLineEdit
|
||||
|
||||
3
main.py
3
main.py
@ -4,7 +4,6 @@ import logging
|
||||
from PyQt5 import QtCore
|
||||
import typing
|
||||
import DBA
|
||||
import qdarktheme
|
||||
from subprocess import run
|
||||
# from pyqtgraph import mkBrush
|
||||
from mutagen.id3 import ID3
|
||||
@ -737,7 +736,7 @@ if __name__ == "__main__":
|
||||
clipboard = app.clipboard()
|
||||
# Dark theme >:3
|
||||
# qdarktheme.setup_theme()
|
||||
qdarktheme.setup_theme("auto") # this is supposed to work but doesnt
|
||||
# qdarktheme.setup_theme("auto") # this is supposed to work but doesnt
|
||||
# Show the UI
|
||||
ui = ApplicationWindow(clipboard)
|
||||
# window size
|
||||
|
||||
122
utils/set_tag.py
122
utils/set_tag.py
@ -1,118 +1,32 @@
|
||||
from logging import debug, error, warning
|
||||
from components import ErrorDialog
|
||||
from components.HeaderTags import HeaderTags
|
||||
|
||||
# from utils import convert_date_str_to_tyer_tdat_id3_tag
|
||||
from components.HeaderTags import HeaderTags2
|
||||
from mutagen.id3 import ID3
|
||||
from mutagen.id3._util import ID3NoHeaderError
|
||||
from mutagen.id3._frames import (
|
||||
Frame,
|
||||
TIT2,
|
||||
TPE1,
|
||||
TALB,
|
||||
TRCK,
|
||||
TDRC,
|
||||
# TYER,
|
||||
TCON,
|
||||
TPOS,
|
||||
COMM,
|
||||
TPE2,
|
||||
TCOM,
|
||||
TPE3,
|
||||
TPE4,
|
||||
TCOP,
|
||||
WOAR,
|
||||
USLT,
|
||||
APIC,
|
||||
TENC,
|
||||
TBPM,
|
||||
TKEY,
|
||||
# TDAT,
|
||||
TIME,
|
||||
TSSE,
|
||||
TOPE,
|
||||
TEXT,
|
||||
TOLY,
|
||||
TORY,
|
||||
TPUB,
|
||||
WCOM,
|
||||
WCOP,
|
||||
WOAS,
|
||||
WORS,
|
||||
WPAY,
|
||||
WPUB,
|
||||
)
|
||||
from mutagen.id3._frames import USLT, Frame
|
||||
|
||||
mutagen_id3_tag_mapping = {
|
||||
"title": TIT2, # Title/song name/content description
|
||||
"artist": TPE1, # Lead performer(s)/Soloist(s)
|
||||
"album": TALB, # Album/Movie/Show title
|
||||
"album_artist": TPE2, # Band/orchestra/accompaniment
|
||||
"genre": TCON, # Content type
|
||||
"album_date": TDRC,
|
||||
# "year": TYER, # Year of recording
|
||||
# "date": TDAT, # Date
|
||||
"lyrics": USLT, # Unsynchronized lyric/text transcription
|
||||
"track_number": TRCK, # Track number/Position in set
|
||||
"album_cover": APIC, # Attached picture
|
||||
"composer": TCOM, # Composer
|
||||
"conductor": TPE3, # Conductor/performer refinement
|
||||
"remixed_by": TPE4, # Interpreted, remixed, or otherwise modified by
|
||||
"part_of_a_set": TPOS, # Part of a set
|
||||
"comments": COMM, # Comments
|
||||
"copyright": TCOP, # Copyright message
|
||||
"url_artist": WOAR, # Official artist/performer webpage
|
||||
"encoded_by": TENC, # Encoded by
|
||||
"bpm": TBPM, # BPM (beats per minute)
|
||||
"initial_key": TKEY, # Initial key
|
||||
"time": TIME, # Time
|
||||
"encoding_settings": TSSE, # Software/Hardware and settings used for encoding
|
||||
"original_artist": TOPE, # Original artist(s)/performer(s)
|
||||
"lyricist": TEXT, # Lyricist/Text writer
|
||||
"original_lyricist": TOLY, # Original lyricist(s)/text writer(s)
|
||||
"original_release_year": TORY, # Original release year
|
||||
"publisher": TPUB, # Publisher
|
||||
"commercial_info": WCOM, # Commercial information
|
||||
"copyright_info": WCOP, # Copyright information
|
||||
"official_audio_source_url": WOAS, # Official audio source webpage
|
||||
"official_internet_radio_station_homepage": WORS, # Official Internet radio station homepage
|
||||
"payment_url": WPAY, # Payment (URL)
|
||||
"publishers_official_webpage": WPUB, # Publishers official webpage
|
||||
}
|
||||
|
||||
|
||||
def set_tag(filepath: str, tag_name: str, value: str):
|
||||
def set_tag(filepath: str, db_column: str, value: str):
|
||||
"""
|
||||
Sets the ID3 tag for a file given a filepath, tag_name, and a value for the tag
|
||||
Sets the ID3 tag for a file given a filepath, db_column, and a value for the tag
|
||||
|
||||
Args:
|
||||
filepath: path to the mp3 file
|
||||
tag_name: common name of the ID3 tag
|
||||
db_column: db column name of the ID3 tag
|
||||
value: value to set for the tag
|
||||
|
||||
Returns:
|
||||
True / False
|
||||
"""
|
||||
headers = HeaderTags()
|
||||
debug(f"filepath: {filepath} | tag_name: {tag_name} | value: {value}")
|
||||
headers = HeaderTags2()
|
||||
debug(f"filepath: {filepath} | db_column: {db_column} | value: {value}")
|
||||
|
||||
try:
|
||||
try: # Load existing tags
|
||||
audio_file = ID3(filepath)
|
||||
except ID3NoHeaderError: # Create new tags if none exist
|
||||
audio_file = ID3()
|
||||
# Date handling - TDRC vs TYER+TDAT
|
||||
# if tag_name == "album_date":
|
||||
# tyer_tag, tdat_tag = convert_date_str_to_tyer_tdat_id3_tag(value)
|
||||
# # always update TYER
|
||||
# audio_file.add(tyer_tag)
|
||||
# if tdat_tag:
|
||||
# # update TDAT if we have it
|
||||
# audio_file.add(tdat_tag)
|
||||
|
||||
# Lyrics
|
||||
# if tag_name == "lyrics" or tag_name == "USLT":
|
||||
if tag_name == "lyrics":
|
||||
# Lyrics get handled differently
|
||||
if db_column == "lyrics":
|
||||
try:
|
||||
audio = ID3(filepath)
|
||||
except Exception as e:
|
||||
@ -123,17 +37,15 @@ def set_tag(filepath: str, tag_name: str, value: str):
|
||||
audio.add(frame)
|
||||
audio.save()
|
||||
return True
|
||||
# Convert ID3 tag nice name (that i chose) into into the Mutagen Frame object
|
||||
if tag_name in mutagen_id3_tag_mapping: # Tag accounted for
|
||||
tag_class = mutagen_id3_tag_mapping[tag_name]
|
||||
if issubclass(tag_class, Frame):
|
||||
frame = tag_class(encoding=3, text=[value])
|
||||
audio_file.add(frame) # Add the tag
|
||||
else:
|
||||
pass
|
||||
# DB Tag into Mutagen Frame Class
|
||||
if db_column in headers.db:
|
||||
frame_class = headers.db[db_column].frame_class
|
||||
assert frame_class is not None # ooo scary
|
||||
if issubclass(frame_class, Frame):
|
||||
frame = frame_class(encoding=3, text=[value])
|
||||
audio_file.add(frame)
|
||||
else:
|
||||
warning('nice name tag not found - skipping updating id3 tag')
|
||||
pass
|
||||
warning(f'Tag "{db_column}" not found - ID3 tag update skipped')
|
||||
audio_file.save(filepath)
|
||||
return True
|
||||
except Exception as e:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user