Files
music-tools/make_folders.py
2026-04-13 17:24:42 +02:00

909 lines
37 KiB
Python
Executable File

import mutagen
from eyed3.id3.frames import ImageFrame
from mutagen.flac import FLAC
from mutagen.mp3 import MP3
from mutagen.id3 import ID3
from mutagen.wave import WAVE
from mutagen.oggvorbis import OggVorbis
from mutagen.id3 import ID3, TIT2, TALB, TPE1, TPE2, COMM, TCOM, TCON, TDRC, TDRL, TRCK, TPUB, POPM, APIC
from mutagen.easyid3 import EasyID3
import os
from os import listdir
from os.path import isfile, join
import psutil
import datetime as dt
from icrawler.builtin import GoogleImageCrawler
from datetime import datetime
import spotify_search
import requests
import logging
logging.basicConfig(
level=logging.INFO,
format="{asctime} - {levelname} - [{funcName}:{lineno}] - {message}",
style="{",
datefmt="%Y-%m-%d %H:%M",
)
# TODO check if title is correct (not like "artist - name")
def switch_ID3_flag_tag(audio, ID3_tag, flac_tag):
try:
audio[flac_tag] = str(audio[ID3_tag][0].text[0])
logging.info("switched ID3 tag " + ID3_tag + " to flac tag " + flac_tag + " with value " + str(audio[flac_tag]) + ", type " + str(type(audio[flac_tag])))
audio.pop(ID3_tag, None)
except:
logging.info("could not switch ID3 tag " + ID3_tag + " to flac tag " + flac_tag)
def remove_flac_ID3_tags(audio, x: str):
if x.endswith(".flac"):
logging.info("switching ID3 tags to flac tags for file " + x)
switch_ID3_flag_tag(audio,"TPE2","BAND")
switch_ID3_flag_tag(audio, "TPE1", "ARTIST")
switch_ID3_flag_tag(audio, "TIT2", "TITLE")
switch_ID3_flag_tag(audio, "TALB", "ALBUM")
switch_ID3_flag_tag(audio, "COMM", "COMMENT")
switch_ID3_flag_tag(audio, "TCOM", "COMPOSER")
switch_ID3_flag_tag(audio, "TCON", "GENRE")
switch_ID3_flag_tag(audio, "TRCK", "TRACKNUMBER")
switch_ID3_flag_tag(audio, "TDRC", "DATE")
switch_ID3_flag_tag(audio, "TPUB", "PUBLISHER")
audio.pop("POPM", None)
audio.pop("APIC", None)
def make_folder(foldername):
try:
logging.info("Creating folder " + foldername)
if "/" in foldername:
folders = foldername.split('/')
pos = "."
for fold in folders:
pos = join(pos,fold)
if not os.path.exists(pos):
os.mkdir(pos)
else:
os.mkdir(foldername)
except Exception as err:
logging.error("could not create folder " + foldername)
logging.error(err)
def search_google_images_and_save(x: str, audio):
if x.endswith(".flac"):
remove_flac_ID3_tags(audio,x)
audio.save(x)
found_image = False
# Try album art search first
if (check_tag(audio, x, "TALB","album")):
if x.endswith(".flac"):
songpath = join(".",str(audio["artist"]),str(audio["album"]))
else:
artist_folder = str(audio.get("TPE2", audio.get("TPE1", "Unknown Artist")))
songpath = join(".", artist_folder, str(audio["TALB"]))
if "\x00" in songpath:
songpath = songpath.replace("\x00",", ")
make_folder(songpath)
os.replace(join(".",x),join(songpath,x))
if x.endswith(".flac"):
artist_val = str(audio.get("artist", "Unknown Artist"))
album_val = str(audio.get("album", "Unknown Album"))
else:
artist_val = str(audio.get("TPE2", audio.get("TPE1", "Unknown Artist")))
album_val = str(audio.get("TALB", "Unknown Album"))
# Skip Google search if artist or album is unknown
if "Unknown Artist" in artist_val or "Unknown Album" in album_val:
logging.info("Artist or album is unknown, skipping Google image search")
else:
google_keyword = artist_val + " " + album_val + " album"
logging.info("Moved file! Now searching for album art... keyword is " + google_keyword)
google_Crawler = GoogleImageCrawler(storage = {'root_dir': songpath})
try:
result = google_Crawler.crawl(keyword = google_keyword, max_num = 1)
found_image = True
except Exception as e:
logging.info(f"could not find Google result by album, searching by track and artist: {e}")
# Fallback: if no image found, try searching by song and artist
if not found_image or not any(f.split('.')[-1].lower() in ["jpg","png"] for f in listdir(songpath)):
song_keyword = artist_val + " " + str(audio.get("TIT2", ""))
logging.info("Fallback: searching for song art... keyword is " + song_keyword)
try:
google_Crawler.crawl(keyword = song_keyword, max_num = 1)
found_image = True
except Exception as e:
logging.info(f"could not find Google result by song: {e}")
# Rename cover art file if found
for f in listdir(songpath):
if (isfile(join(songpath,f)) and f.split(".")[-1].lower() in ["jpg","png"]):
os.replace(join(songpath,f),join(songpath,"Cover." + f.split(".")[-1].lower()))
logging.info("Done!")
else:
# search for song name and artist
songpath = join(".",str(audio.get("TPE2", "Unknown Artist")),str(audio.get("TIT2", "Unknown Title")))
make_folder(songpath)
os.replace(join(".",x),join(songpath,x))
artist_keyword = str(audio.get("TPE2", "Unknown Artist"))
title_keyword = str(audio.get("TIT2", "Unknown Title"))
# Skip Google search if artist or title is unknown
if "Unknown Artist" in artist_keyword or "Unknown Title" in title_keyword:
logging.info("Artist or title is unknown, skipping Google image search")
else:
song_keyword = artist_keyword + " " + title_keyword
logging.info("Moved file! Now searching for album art... keyword is " + song_keyword)
google_Crawler = GoogleImageCrawler(storage = {'root_dir': songpath})
try:
google_Crawler.crawl(keyword = song_keyword, max_num = 1)
found_image = True
except Exception as e:
logging.info(f"could not find Google result by track and artist: {e}")
for f in listdir(songpath):
if (isfile(join(songpath,f)) and f.split(".")[-1].lower() in ["jpg","png"]):
os.replace(join(songpath,f),join(songpath,"Cover." + f.split(".")[-1].lower()))
logging.info("Done!")
# TIT2 = title,
# TPE1 = artist,
# TPE2 = band,
# TALB = album,
# COMM = comment,
# TCOM = composer,
# TCON = genre,
# TRCK = number,
# TDRC = year,
# TPUB = publisher
def create_ID3_tag(audio, tagname: str, textvalue: str):
logging.info("creating ID3 tag " + tagname + " with value " + textvalue)
if tagname == "TALB":
audio[tagname] = TALB(encoding=3,text=textvalue)
elif tagname == "TIT2":
audio[tagname] = TIT2(encoding=3,text=textvalue)
elif tagname == "TPE1":
audio[tagname] = TPE1(encoding=3,text=textvalue)
elif tagname == "TPE2":
audio[tagname] = TPE2(encoding=3,text=textvalue)
elif tagname == "COMM":
audio[tagname] = COMM(encoding=3,text=textvalue)
elif tagname == "TCOM":
audio[tagname] = TCOM(encoding=3,text=textvalue)
elif tagname == "TCON":
audio[tagname] = TCON(encoding=3,text=textvalue)
elif tagname == "TRCK":
audio[tagname] = TRCK(encoding=3,text=textvalue)
elif tagname == "TDRC":
try:
audio[tagname] = TDRC(encoding=3,text=textvalue)
except:
pass
elif tagname == "TDRL":
try:
audio[tagname] = TDRL(encoding=3,text=textvalue)
except:
pass
elif tagname == "TPUB":
audio[tagname] = TPUB(encoding=3,text=textvalue)
def check_tag(audio, filename: str, ID3_tag: str, normal_tag) -> bool:
res = False
# check if the ID3 tag exists
if (ID3_tag in audio.keys() and len(str(audio[ID3_tag])) != 0):
logging.info(ID3_tag + " ID3 tag found! " + str(audio[ID3_tag]))
# apply it to the general album tag
if audio[ID3_tag] is not str and filename.endswith(".mp3"):
audio[normal_tag] = audio[ID3_tag]
else:
audio[normal_tag] = audio[ID3_tag]
logging.info("Set " + normal_tag + " to " + str(audio[normal_tag]))
res = True
# check if general tag exists
elif (normal_tag in audio.keys() and len(str(audio[normal_tag])) != 0):
logging.info(normal_tag + " normal tag found! " + str(audio[normal_tag]))
if audio[normal_tag] is not str:
audio[normal_tag] = audio[normal_tag][0]
logging.info("normal tag is not str, set it to " + str(audio[normal_tag][0]))
if (not filename.endswith(".flac")):
#apply it to the ID3 tag
if audio[normal_tag] is not str:
create_ID3_tag(audio, ID3_tag,audio[normal_tag][0])
else:
create_ID3_tag(audio, ID3_tag,audio[normal_tag])
else:
logging.debug(filename + " is a flac file, not creating ID3 tag")
res = True
return res
def check_title_songname(x: str, audio):
logging.info("checking title by name " + x)
extension = x.split(".")[-1].lower()
if (extension in ["mp3","flac","ogg","wav","m4a","mp4"]):
x = x.rsplit(".",1)[0] # remove the file extension
logging.info("file has extension " + extension + ". removing it from title. New title: " + x)
if (" - " in x):
items = x.split(" - ")
# If the format is 'artist - Topic - title', remove 'Topic'
if len(items) > 2 and items[1].strip().lower() == "topic":
logging.info("Detected ' - Topic - ' in name, removing 'Topic'.")
# Rebuild items without 'Topic'
items = [items[0]] + items[2:]
# Set artist and title tags robustly
if len(items) == 2:
artist, title = items[0].strip(), items[1].strip()
elif len(items) > 2:
artist, title = items[0].strip(), items[1].strip()
else:
artist, title = x.strip(), x.strip()
# Set both TPE1 (song artist) and TPE2 (album artist)
audio["TPE1"] = TPE1(encoding=3, text=artist)
audio["TPE2"] = TPE2(encoding=3, text=artist)
# Only set 'artist' as a string for FLAC, not for MP3
if hasattr(audio, 'mime') and audio.mime and 'flac' in audio.mime[0].lower():
audio["artist"] = artist
# Set title tags
audio["TIT2"] = TIT2(encoding=3, text=title)
if hasattr(audio, 'mime') and audio.mime and 'flac' in audio.mime[0].lower():
audio["title"] = title
logging.info(f"Set artist: {artist}, title: {title}")
else:
logging.info("no - found in title, setting full name as title: " + x)
if ("TIT2" not in audio.keys()):
song_title = x.strip().rstrip()
logging.info("TIT2 tag not found, creating it. Using song title: " + song_title)
audio["TIT2"] = TIT2(encoding=3,text=song_title)
audio["title"] = TIT2(encoding=3,text=song_title)
def check_for_multiple_artists(audio, filename: str, name: str):
logging.info("checking for multiple artists for name " + name)
artists = []
if (" x " in name):
artists = name.split(" x ")
elif (" X " in name):
artists = name.split(" X ")
elif ("," in name):
artists = name.split(",")
elif ("/" in name):
artists = name.split("/")
elif ("\x00" in name):
artists = name.split("\x00")
if (len(artists) > 0):
logging.info("multiple artists: " + str(artists))
if filename.endswith(".flac"):
audio["artist"] = TPE2(encoding=3,text=["\0".join(artists)])
else:
audio["TPE1"] = TPE2(encoding=3,text=["\0".join(artists)])
else:
logging.info("no multiple artists found in name " + name + ", setting artist to " + name)
if filename.endswith(".flac"):
audio["artist"] = TPE2(encoding=3,text=name)
else:
audio["TPE1"] = TPE2(encoding=3,text=name)
# checks for any artist from the song name. If it exists it sets the properties of the file
def check_artist_songname(x: str, audio):
items = x.split(" - ")
logging.info("Checking artist by name. items: " + str(items))
# Remove 'Topic' if present
if len(items) > 2 and items[1].strip().lower() == "topic":
logging.info("Detected ' - Topic - ' in name, removing 'Topic'.")
items = [items[0]] + items[2:]
artist = items[0].strip()
# Set both TPE1 (song artist) and TPE2 (album artist)
audio["TPE1"] = TPE1(encoding=3, text=artist)
audio["TPE2"] = TPE2(encoding=3, text=artist)
# Only set 'artist' as a string for FLAC, not for MP3
if hasattr(audio, 'mime') and audio.mime and 'flac' in audio.mime[0].lower():
audio["artist"] = artist
logging.info(f"Set artist tags TPE1 and TPE2 to {artist}")
def check_artist(audio, filename: str) -> bool:
res = False
# check if the ID3 artist tag exists
check_tag(audio, filename, "TPE1","artist")
check_tag(audio, filename, "TPE2","artist")
if ("TPE1" in audio.keys()):
if (len(str(audio["TPE1"])) != 0):
logging.info("TPE1 tag was found! " + str(audio["TPE1"]))
# apply it to general artist tag
audio["TPE2"] = TPE2(encoding=3,text=str(audio["TPE1"]))
audio["artist"] = audio["TPE1"]
logging.info("checking for multiple artists")
check_for_multiple_artists(audio, filename ,str(audio["TPE1"]))
res = True
# if no TPE1, check if the ID3 band tag exists
elif ("TPE2" in audio.keys()):
if (len(str(audio["TPE2"])) != 0):
logging.info("TPE2 tag was found! " + str(audio["TPE2"]))
# apply it to TPE1 and general artist tags
audio["TPE1"] = TPE1(encoding=3,text=str(audio["TPE2"]))
audio["artist"] = audio["TPE1"]
check_for_multiple_artists(audio,filename,str(audio["TPE2"]))
res = True
# check if artist audio tag exists
elif ("artist" in audio.keys()):
if (len(str(audio["artist"])) != 0):
logging.info("artist tag was found! " + str(audio["artist"]))
artist = ""
if (audio["artist"] is not str):
artist = audio["artist"][0]
else:
artist = audio["artist"]
logging.info("artist: " + artist)
# apply to both ID3 artist tags
audio["TPE1"] = TPE1(encoding=3,text=artist)
audio["TPE2"] = TPE2(encoding=3,text=artist)
if (audio["TPE2"][0].text is not str):
audio["TPE2"][0].text = str(audio["TPE2"][0].text[0])
if (audio["TPE1"] is not str):
audio["TPE1"] = audio["TPE1"][0]
logging.info("Set TPE1 and TPE2 to " + str(audio["TPE1"][0]) + " and " + str(audio["TPE2"][0]))
check_for_multiple_artists(audio,filename,artist)
res = True
return res
def set_genre_tag(genres, audio):
"""Apply genre tags to audio file from Spotify genres list."""
genre = ""
if (len(genres) > 0):
if (len(genres) == 1):
audio["TCON"] = TCON(encoding=3,text=str(genres[0]))
else:
for i in range(len(genres)):
if (i == 0):
genre = str(genres[i])
else:
genre += "," + str(genres[i])
logging.info("genre set to " + genre)
audio["TCON"] = TCON(encoding=3,text=genre)
audio["genre"] = audio["TCON"]
def embed_music_file(audiostr: str, coverfile: str):
try:
new_audio = ID3(audiostr)
with open(coverfile,'rb') as albumart:
new_audio.add(APIC(
encoding=3,
mime='image/jpeg',
type=3, desc=u'Cover image',
data=albumart.read()
))
new_audio.save(audiostr)
logging.info("Finished!")
except:
logging.info("could not embed music file")
def save_album_from_spotify(spotify, audio, x: str, spotify_data: dict) -> bool:
"""
Save audio file with metadata and cover art from Spotify album data.
Args:
spotify: Spotify client instance
audio: Audio file object
x: Filename
spotify_data: Dict with album data from spotify_search.search_album()
Returns:
True if successful, False otherwise
"""
if not spotify_data or not spotify_data.get('found'):
logging.info("No Spotify album data provided")
return False
logging.info("Applying Spotify album data to file...")
# Set artist
album_artist = spotify_data['artist']
if x.endswith(".flac"):
try:
if str(audio.get("album_artist", "")) != album_artist:
audio["album_artist"] = album_artist
except:
audio["album_artist"] = album_artist
else:
if str(audio.get("TPE2", "")) != album_artist:
audio["TPE2"] = TPE2(encoding=3, text=album_artist)
# Set album
album_name = spotify_data['album']
if x.endswith(".flac"):
if str(audio.get("album", "")) != album_name:
audio["album"] = album_name
elif str(audio.get("TALB", "")) != album_name:
audio["TALB"] = TALB(encoding=3, text=album_name)
# Parse and set release date
release_date = spotify_data['release_date']
try:
year = str(datetime.strptime(release_date, '%Y-%m-%d').year)
except:
try:
year = str(datetime.strptime(release_date, '%Y-%m').year)
except:
try:
year = str(datetime.strptime(release_date, '%Y').year)
except:
year = str(release_date)
if x.endswith(".flac"):
audio["year"] = year
audio["date"] = release_date
else:
audio["TDRC"] = TDRC(encoding=3, text=year)
audio["TDRL"] = TDRL(encoding=3, text=release_date)
# Set genres
logging.info("genres: " + str(spotify_data['genres']))
set_genre_tag(spotify_data['genres'], audio)
# Set comment
comment = "Spotify ID: {0}. Release date precision: {1}, total tracks in album: {2}. This album has {3} version(s)".format(
spotify_data['album_id'],
spotify_data['release_date_precision'],
spotify_data['total_tracks'],
spotify_data['versions_count']
)
logging.info("Comment: " + comment)
if x.endswith(".flac"):
audio["comment"] = audio.get("comment", "") + comment
else:
audio["COMM"] = COMM(encoding=3, text=comment + str(audio.get("COMM", "")))
# Save tags
if x.endswith(".flac"):
remove_flac_ID3_tags(audio, x)
audio.save(x)
# Create folder structure
if x.endswith(".flac"):
artist_path = str(audio["artist"][0])
album_path = str(audio["album"][0])
else:
if "/" in str(audio["TPE2"]):
audio["TPE2"] = str(audio["TPE2"]).replace("/", "")
artist_path = str(audio["TPE2"])
album_path = str(audio["TALB"])
songpath = join(".", artist_path, album_path)
make_folder(join(".", artist_path))
# Handle albums with / in the name
if not x.endswith(".flac") and "/" in album_path:
logging.info("album contains /")
folders = album_path.split('/')
logging.info(folders)
pos = join(".", artist_path)
for fold in folders:
make_folder(join(pos, fold))
pos = join(pos, fold)
logging.info(pos)
make_folder(songpath)
os.replace(join(".", x), join(songpath, x))
logging.info("moved song file, now downloading cover art")
# Download and save cover art
if spotify_data['image_url']:
img_data = requests.get(spotify_data['image_url']).content
with open(join(songpath, "Cover.jpg"), 'wb') as handler:
handler.write(img_data)
logging.info("done getting cover art!")
logging.info("now setting cover art..")
embed_music_file(join(songpath, x), join(songpath, "Cover.jpg"))
return True
def save_track_from_spotify(spotify, audio, x: str, spotify_data: dict) -> bool:
"""
Save audio file with metadata and cover art from Spotify track data.
Args:
spotify: Spotify client instance
audio: Audio file object
x: Filename
spotify_data: Dict with track data from spotify_search.search_track()
Returns:
True if successful, False otherwise
"""
if not spotify_data or not spotify_data.get('found'):
logging.info("No Spotify track data provided")
return False
logging.info("Applying Spotify track data to file...")
# Get current artist value for comparison
if x.endswith(".flac"):
current_artist = str(audio.get("artist", [""])[0]) if not isinstance(audio.get("artist", ""), str) else str(audio.get("artist", ""))
else:
if "artist" in audio:
current_artist = str(audio["artist"][0]) if not isinstance(audio["artist"], str) else str(audio["artist"])
elif "TPE2" in audio:
current_artist = str(audio["TPE2"][0]) if not isinstance(audio["TPE2"], str) else str(audio["TPE2"])
else:
current_artist = "Unknown Artist"
# Set artist if different
found_artist = spotify_data['artist']
if found_artist != current_artist:
logging.info("Changing album artist from " + current_artist + " to " + found_artist)
if x.endswith(".flac"):
audio["album_artist"] = found_artist
else:
audio["TPE2"] = TPE2(encoding=3, text=found_artist)
# Set album
found_album = spotify_data['album']
logging.info("found album name: " + found_album)
if len(found_album) > 0:
if x.endswith(".flac"):
audio["album"] = found_album
else:
audio["TALB"] = TALB(encoding=3, text=found_album)
else:
# set album to title if no album found
if x.endswith(".flac"):
audio["album"] = audio["title"][0]
else:
audio["TALB"] = TALB(encoding=3, text=str(audio["TIT2"]))
# Add system info to comment
now = dt.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
cpu_percent = psutil.cpu_percent(interval=1)
ram_percent = psutil.virtual_memory().percent
sysinfo = f"This album was downloaded on {now}. The server was using {cpu_percent}% CPU and {ram_percent}% RAM."
# Build comment from album metadata
album_label = spotify_data.get('label', '')
album_desc = f"Label: {album_label}. " if album_label else ""
comment = "Spotify ID: {0}. This album was released on: {1}, total tracks in album: {2}. This album has {3} version(s). {4} {5}".format(
spotify_data['album_id'],
spotify_data['release_date'],
spotify_data['total_tracks'],
spotify_data['versions_count'],
album_desc,
sysinfo
)
logging.info("Comment: " + comment)
if x.endswith(".flac"):
audio["comment"] = comment
else:
audio["COMM"] = COMM(encoding=3, text=comment)
# Parse and set release date
release_date = spotify_data['release_date']
try:
year = str(datetime.strptime(release_date, '%Y-%m-%d').year)
except:
try:
year = str(datetime.strptime(release_date, '%Y-%m').year)
except:
try:
year = str(datetime.strptime(release_date, '%Y').year)
except Exception as err:
logging.info(err)
year = str(release_date)
if x.endswith(".flac"):
audio["year"] = year
audio["date"] = release_date
else:
audio["TDRC"] = TDRC(encoding=3, text=year)
audio["TDRL"] = TDRL(encoding=3, text=release_date)
# Set track number
if x.endswith(".flac"):
audio["TRACKNUMBER"] = str(spotify_data['track_number']) + "/" + str(spotify_data['total_tracks'])
else:
audio["TRCK"] = TRCK(encoding=3, text=str(spotify_data['track_number']) + "/" + str(spotify_data['total_tracks']))
# Set popularity
if x.endswith(".flac"):
audio["popularity"] = str(spotify_data['popularity'])
else:
audio["POPM"] = POPM(encoding=3, text=str(spotify_data['popularity']))
# Set genres
logging.info("genres: " + str(spotify_data['genres']))
set_genre_tag(spotify_data['genres'], audio)
# Save tags
remove_flac_ID3_tags(audio, x)
audio.save(x)
# Create folder structure
if x.endswith(".flac"):
artist_path = str(audio["artist"][0])
else:
if audio["TPE2"] is not str:
artist_path = str(audio["TPE2"][0])
else:
artist_path = str(audio["TPE2"])
logging.info("artist path: " + artist_path)
if x.endswith(".flac"):
songpath = join(".", artist_path, str(audio["ALBUM"][0]))
else:
songpath = join(".", artist_path, str(audio["TALB"]))
logging.info("song path: " + songpath)
make_folder(join(".", artist_path))
# Handle albums with / in the name
if not x.endswith(".flac") and "/" in str(audio["TALB"]):
logging.info("album contains /")
folders = str(audio["TALB"]).split('/')
logging.info(folders)
pos = join(".", str(audio["TPE2"]))
for fold in folders:
make_folder(join(pos, fold))
pos = join(pos, fold)
logging.info(pos)
make_folder(songpath)
os.replace(join(".", x), join(songpath, x))
logging.info("moved song file, now downloading cover art")
# Download and save cover art
if spotify_data['image_url']:
img_data = requests.get(spotify_data['image_url']).content
with open(join(songpath, "Cover.jpg"), 'wb') as handler:
handler.write(img_data)
logging.info("done getting cover art!")
logging.info("now setting cover art..")
embed_music_file(join(songpath, x), join(songpath, "Cover.jpg"))
return True
def main():
# Preprocess: rename files with '- Topic -' in the name to 'artist - title'
for fname in [f for f in listdir(".") if isfile(join(".",f)) and "- Topic -" in f]:
parts = fname.rsplit("- Topic -", 1)
if len(parts) == 2:
artist = parts[0].strip().rstrip("- ")
title = parts[1].rsplit('.', 1)[0].strip()
ext = fname.rsplit('.', 1)[-1]
new_name = f"{artist} - {title}.{ext}"
if not os.path.exists(new_name):
logging.info(f"Renaming file '{fname}' to '{new_name}'")
os.rename(fname, new_name)
else:
logging.warning(f"Target filename '{new_name}' already exists. Skipping rename for '{fname}'")
# for spotipy to be able to log in, the environment variables SPOTIPY_CLIENT_ID and SPOTIPY_CLIENT_SECRET have to be set
# these can be obtained from the spotify developer dashboard
# they are defined in /etc/profile.d/spotipy.sh
spotify = spotify_search.init_spotify_client()
onlyfiles = [f for f in listdir(".") if (isfile(join(".",f)) and f.split(".")[-1] in ['mp3','mp4','ogg','wav','flac','m4a','MP3','FLAC','OGG','MP4','WAV','M4A'])]
# TIT2 = title,
# TPE1 = artist,
# TPE2 = band,
# TALB = album,
# COMM = comment,
# TCOM = composer,
# TCON = genre,
# TRCK = number,
# TDRC = year,
# TPUB = publisher
# use: audio["TRCK"] = TRCK(encoding=3, text=u'track_number') and replace the tags with appropriate values
for x in onlyfiles:
logging.info("------------------------------------------------")
logging.info(x)
# try to open tags, if the file has none, create a new ID3 object
try:
audio = mutagen.File(x)
except mutagen.mp3.HeaderNotFoundError as err:
logging.info(err)
logging.info("header not found")
audio = mutagen.File(x,easy=True)
audio.add_tags()
except mutagen.id3.ID3NoHeaderError:
logging.info("no header")
audio = mutagen.File(x,easy=True)
audio.add_tags()
except:
logging.info("opening as ID3")
audio = ID3(x)
audio.add_tags()
logging.info(type(audio))
try:
if (audio.tags == None):
logging.info("audio has no tags")
audio.add_tags()
except:
pass
has_valid_artist = check_artist(audio,x)
has_valid_album = check_tag(audio,x,"TALB","album")
has_valid_title = check_tag(audio,x,"TIT2","title")
if (has_valid_title):
if x.endswith(".flac"):
logging.info("Found valid title in title tag: " + str(audio["TITLE"]))
if audio["TITLE"] is not str:
check_title_songname(audio["TITLE"][0],audio)
else:
check_title_songname(audio["TITLE"],audio)
else:
logging.info("Found valid title in TTI2 tag: " + str(audio["TIT2"]) + ". type: " + str(type(audio["TIT2"])))
if not isinstance(audio["TIT2"], str):
if (isinstance(audio["TIT2"][0], str)):
check_title_songname(audio["TIT2"][0],audio)
else:
logging.info(type(audio["TIT2"][0]))
check_title_songname(audio["TIT2"][0].text[0],audio)
else:
check_title_songname(audio["TIT2"].text[0],audio)
else:
logging.info("No valid title found in TTI2 tag, using name " + x)
check_title_songname(x,audio)
has_valid_title = True
if (has_valid_artist == False):
logging.info("No valid artist found, checking for artist by songname of the file (" + x + ")")
if (" - " in x):
check_artist_songname(x, audio)
has_valid_artist = True
if (has_valid_artist == False and has_valid_title):
logging.info("No valid artist found but valid title found, checking for multiple artists")
check_for_multiple_artists(audio,x,str(audio["TIT2"]))
has_valid_artist = check_artist(audio, x) # check again
check_tag(audio,x,"COMM","comment")
check_tag(audio,x,"TCOM","composer")
has_genre = check_tag(audio,x,"TCON","genre")
if (has_genre):
if x.endswith(".flac"):
audio["genre"] = str(audio["genre"]).replace(" & ",",")
else:
audio["TCON"] = TCON(encoding=3, text=str(audio["TCON"]).replace(" & ",",")) # convert genres like Hip-Hop & Rap to Hip-Hop,Rap
check_tag(audio,x,"TRCK","track")
check_tag(audio,x,"TDRC","year")
check_tag(audio,x,"TPUB","publisher")
if (has_valid_artist and has_valid_title):
found = False
# Extract artist and title for search
artist = ""
track = ""
if x.endswith(".flac"):
if audio["artist"] is not str:
artist = str(audio["artist"][0])
else:
artist = str(audio["artist"])
if audio["title"] is not str:
track = str(audio["title"][0])
else:
track = str(audio["title"])
else:
# Prefer 'artist' and 'title' tags if available, fallback to TPE2/TIT2
if "artist" in audio:
if audio["artist"] is not str:
artist = str(audio["artist"][0])
else:
artist = str(audio["artist"])
elif "TPE2" in audio:
if audio["TPE2"] is not str:
artist = str(audio["TPE2"][0])
else:
artist = str(audio["TPE2"])
else:
artist = "Unknown Artist"
if "title" in audio:
if audio["title"] is not str:
track = str(audio["title"][0])
else:
track = str(audio["title"])
elif "TIT2" in audio:
if audio["TIT2"] is not str:
track = str(audio["TIT2"][0])
else:
track = str(audio["TIT2"])
else:
track = "Unknown Title"
# Search Spotify for the track
try:
spotify_data = spotify_search.search_track(spotify, artist, track)
if spotify_data:
found = save_track_from_spotify(spotify, audio, x, spotify_data)
except Exception as err:
logging.error("could not find track on spotify: " + str(err))
logging.error(err.with_traceback)
found = False
if (found == False):
if (x.endswith(".flac")):
logging.info("valid artist found. making folder for artist " + str(audio["artist"][0]))
make_folder(join(".",str(audio["artist"][0])))
else:
# Use TPE2 if available, otherwise fallback to TPE1
artist_folder = str(audio.get("TPE2", audio.get("TPE1", "Unknown Artist")))
logging.info("valid artist found. making folder for artist " + artist_folder)
if "/" in artist_folder:
artist_folder = artist_folder.replace("/","")
make_folder(join(".", artist_folder))
if (has_valid_album):
if (x.endswith(".flac")):
make_folder(join(".",str(audio["artist"][0]),str(audio["album"][0])))
else:
make_folder(join(".",str(audio["TPE2"]),str(audio["TALB"])))
logging.info("spotify did not find artist and track, searching for album...")
if (has_valid_album):
# Extract artist and album for search
search_artist = ""
search_album = ""
if x.endswith(".flac"):
search_artist = str(audio["artist"])
search_album = str(audio["album"])
else:
search_artist = str(audio["TPE2"])
search_album = str(audio["TALB"])
# Search Spotify for the album
album_data = spotify_search.search_album(spotify, search_artist, search_album)
if album_data:
album_found = save_album_from_spotify(spotify, audio, x, album_data)
else:
album_found = False
if (album_found == False):
logging.info("Nothing found on spotify, searching Google Images...")
search_google_images_and_save(x, audio)
else:
# Only set album to title if TALB is not already set (i.e., not found from Spotify)
if x.endswith(".flac"):
if not audio.get("album"):
audio["album"] = audio["title"][0]
else:
if not audio.get("TALB") or (isinstance(audio["TALB"], TALB) and not audio["TALB"].text):
audio["TALB"] = TALB(encoding=3,text=str(audio["TIT2"]))
search_google_images_and_save(x, audio)
logging.info("------------------------------------------------")
if __name__ == "__main__":
main()