Ranger sa collection de MP3s avec Python et ffprobe
Avec un Python 3.x:
#!/usr/bin/env python
# Petit script quick and dirty utilisant la lib standard et l'utilitaire ffprobe,
# rangeant les MP3s dans des dossiers de la façon suivante:
# a/artiste/album/01-titre.mp3
import subprocess
import os
import sys
import fnmatch
import re
import unicodedata
import logging
import shutil
logger = logging.Logger(__name__)
def slugify(value, allow_unicode=False):
"""
(from Django)
Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
dashes to single dashes. Remove characters that aren't alphanumerics,
underscores, or hyphens. Convert to lowercase. Also strip leading and
trailing whitespace, dashes, and underscores.
"""
value = str(value)
if allow_unicode:
value = unicodedata.normalize("NFKC", value)
else:
value = (
unicodedata.normalize("NFKD", value)
.encode("ascii", "ignore")
.decode("ascii")
)
value = re.sub(r"[^\w\s-]", "", value.lower())
return re.sub(r"[-\s]+", "-", value).strip("-_")
def make_path_from_tags(tags: dict) -> str:
return f"{slugify(tags.get('artist'))[0]}/" \
f"{slugify(tags.get('artist'))}/" \
f"{slugify(tags.get('album'))}/" \
f"{str(tags.get('track')).zfill(2)}-" \
f"{slugify(tags.get('title'))}.mp3"
def data_from_tags(path: str) -> dict:
data = {
"artist": None,
"title": None,
"album": "unknown",
"track": 0,
}
proc = subprocess.Popen(
[
"ffprobe",
"-loglevel",
"error",
"-show_entries",
"format_tags=artist,album,title,track",
"-of",
"default=noprint_wrappers=1",
path,
],
stdout=subprocess.PIPE,
)
output = proc.stdout.read()
output = output.decode("utf8", errors="ignore")
for line in output.split("\n"):
if line.strip():
prop, val = line.split("=")
prop = prop.strip().lower()
val = val.strip()
if prop in ["tag:artist", "tag:track", "tag:title", "tag:album"]:
if prop == "tag:track":
try:
val = val.split("/")[0]
val = int(val)
data["track"] = val
except ValueError:
pass
if prop == "tag:title" and val:
data["title"] = val
if prop == "tag:album" and val:
data["album"] = val
if prop == "tag:artist" and val:
data["artist"] = val
return data
if __name__ == "__main__":
if len(sys.argv) < 3:
sys.exit(f"\nMove mp3s from one dir to another and arrange dirs according to tags.\n\nUsage: {sys.argv[0]} /source/dir/ /dest/dir\n")
if not shutil.which("ffprobe"):
sys.exit("Command-line tool ffprobe is needed but couldn't be found")
for root, _, files in os.walk(sys.argv[1]):
for item in fnmatch.filter(files, "*.mp3"):
og_path = os.path.join(root, item)
tags = data_from_tags(og_path)
if not tags.get("artist") and not tags.get("title"):
logger.warning(f"Not enough tag data, won't move {og_path}")
continue
dest = os.path.join(sys.argv[2], make_path_from_tags(tags))
if os.path.exists(dest):
logger.warning(f"File already exists, not gonna overwrite it: {dest}")
else:
logger.info(f"Moving file to {dest}")
os.makedirs(os.path.dirname(dest), exist_ok=True)
new_dest = shutil.copy2(og_path, dest)
if os.path.exists(dest):
os.remove(og_path)