first commit
This commit is contained in:
commit
010598a8be
15 changed files with 1128 additions and 0 deletions
35
misc/fix_dates_deezer_dl.sh
Normal file
35
misc/fix_dates_deezer_dl.sh
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#!/bin/bash
|
||||
|
||||
BASE=/tmp/deezer-downloader/albums/
|
||||
cd "$BASE" || exit 1
|
||||
|
||||
shopt -s nullglob
|
||||
|
||||
for dir in */ ; do
|
||||
DATES=$(~/bin/exiftool/exiftool -Date "$dir"/*.flac "$dir"/*.mp3 2>/dev/null \
|
||||
| awk -F': ' '{if($2!="") print $2}' | sort -u)
|
||||
|
||||
COUNT=$(echo "$DATES" | wc -l)
|
||||
|
||||
if [ "$COUNT" -le 1 ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
NEWDATE=$(echo "$DATES" | sort -nr | head -n1)
|
||||
|
||||
echo "🔧 $dir → Fixing inconsistent dates to newest: $NEWDATE"
|
||||
echo "$DATES" | sed 's/^/ - /'
|
||||
|
||||
for f in "$dir"/*.flac; do
|
||||
[ -f "$f" ] || continue
|
||||
metaflac --remove-tag=DATE "$f"
|
||||
metaflac --set-tag=DATE="$NEWDATE" "$f"
|
||||
done
|
||||
|
||||
for f in "$dir"/*.mp3; do
|
||||
[ -f "$f" ] || continue
|
||||
id3v2 -T "$NEWDATE" "$f" >/dev/null 2>&1
|
||||
done
|
||||
|
||||
echo
|
||||
done
|
||||
63
misc/fix_singles.py
Normal file
63
misc/fix_singles.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import os
|
||||
import shutil
|
||||
from mutagen import File
|
||||
|
||||
# Path where your music files are
|
||||
MUSIC_DIR = "/home/fucksophie/OrpheusDL/downloads"
|
||||
|
||||
# First, collect all .lrc files
|
||||
lrc_files = {}
|
||||
for filename in os.listdir(MUSIC_DIR):
|
||||
if filename.lower().endswith(".lrc"):
|
||||
key = os.path.splitext(filename)[0] # filename without extension
|
||||
lrc_files[key] = os.path.join(MUSIC_DIR, filename)
|
||||
|
||||
# Loop through all items in the directory
|
||||
for filename in os.listdir(MUSIC_DIR):
|
||||
filepath = os.path.join(MUSIC_DIR, filename)
|
||||
|
||||
# Only process files, ignore directories
|
||||
if not os.path.isfile(filepath):
|
||||
continue
|
||||
|
||||
# Only process .mp3 and .flac files
|
||||
if not (filename.lower().endswith(".mp3") or filename.lower().endswith(".flac")):
|
||||
continue
|
||||
|
||||
# Load file with mutagen
|
||||
audio = File(filepath, easy=True)
|
||||
if not audio:
|
||||
print(f"Skipping {filename}, can't read metadata")
|
||||
continue
|
||||
|
||||
# Try to get the album artist tag; fallback to artist if missing
|
||||
artist = None
|
||||
if "albumartist" in audio:
|
||||
artist = audio["albumartist"][0]
|
||||
elif "artist" in audio:
|
||||
artist = audio["artist"][0]
|
||||
else:
|
||||
print(f"No artist tag found for {filename}, skipping")
|
||||
continue
|
||||
|
||||
# Clean up artist name for folder creation
|
||||
safe_artist = artist.replace("/", "_").replace("\\", "_")
|
||||
folder_name = f"{safe_artist} (singles)"
|
||||
target_folder = os.path.join(MUSIC_DIR, folder_name)
|
||||
|
||||
# Create folder if it doesn't exist
|
||||
os.makedirs(target_folder, exist_ok=True)
|
||||
|
||||
# Move file into folder
|
||||
target_path = os.path.join(target_folder, filename)
|
||||
print(f"Moving '{filename}' to '{target_folder}'")
|
||||
shutil.move(filepath, target_path)
|
||||
|
||||
# Also move corresponding .lrc file if it exists
|
||||
base_name = os.path.splitext(filename)[0]
|
||||
if base_name in lrc_files:
|
||||
lrc_target = os.path.join(target_folder, os.path.basename(lrc_files[base_name]))
|
||||
print(f"Moving lyrics '{lrc_files[base_name]}' to '{target_folder}'")
|
||||
shutil.move(lrc_files[base_name], lrc_target)
|
||||
|
||||
print("Done!")
|
||||
27
misc/fix_utf8_filenames_only.py
Normal file
27
misc/fix_utf8_filenames_only.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
ROOT = "/home/fucksophie/media/Music/axxturel loosies"
|
||||
SUPPORTED_EXTS = {".mp3", ".m4a"} # add other formats as needed
|
||||
|
||||
def sanitize_filename(filename: str) -> str:
|
||||
# Keep ASCII printable characters; replace anything else with "_"
|
||||
return re.sub(r"[^\x20-\x7E]", "_", filename)
|
||||
|
||||
def safe_print(s: str):
|
||||
# Encode with backslashreplace to avoid crashing on surrogates
|
||||
print(s.encode("utf-8", errors="backslashreplace").decode("utf-8"))
|
||||
|
||||
for root, _, files in os.walk(ROOT):
|
||||
for f in files:
|
||||
ext = os.path.splitext(f)[1].lower()
|
||||
if ext not in SUPPORTED_EXTS:
|
||||
continue
|
||||
|
||||
old_path = os.path.join(root, f)
|
||||
new_name = sanitize_filename(f)
|
||||
new_path = os.path.join(root, new_name)
|
||||
if old_path != new_path:
|
||||
safe_print(f"[DRY RUN] Would rename: {old_path} → {new_path}")
|
||||
# To apply changes, uncomment the next line:
|
||||
os.rename(old_path, new_path)
|
||||
75
misc/fix_utf8_tags.py
Normal file
75
misc/fix_utf8_tags.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import os
|
||||
from typing import Optional
|
||||
from mutagen.id3 import ID3, ID3NoHeaderError, TALB, TIT2
|
||||
|
||||
MUSIC_ROOT = "/home/fucksophie/media/Music"
|
||||
SUPPORTED_EXTS = {".mp3"}
|
||||
DRY_RUN = False
|
||||
|
||||
def deduce_album_from_dir(dirname: str) -> Optional[str]:
|
||||
# "[Facy] - Night school HOSTED BY BRAILLED"
|
||||
if " - " in dirname:
|
||||
return dirname.split(" - ", 1)[1].strip()
|
||||
return None
|
||||
|
||||
def is_singles_dir(dirname: str) -> bool:
|
||||
return dirname.lower().endswith("(singles)")
|
||||
|
||||
def title_from_filename(filename: str) -> str:
|
||||
return os.path.splitext(filename)[0].strip()
|
||||
|
||||
for root, _, files in os.walk(MUSIC_ROOT):
|
||||
dirname = os.path.basename(root)
|
||||
|
||||
album_name = deduce_album_from_dir(dirname)
|
||||
singles = is_singles_dir(dirname)
|
||||
|
||||
for file in files:
|
||||
if os.path.splitext(file)[1].lower() not in SUPPORTED_EXTS:
|
||||
continue
|
||||
|
||||
path = os.path.join(root, file)
|
||||
|
||||
try:
|
||||
try:
|
||||
tags = ID3(path)
|
||||
except ID3NoHeaderError:
|
||||
tags = ID3()
|
||||
|
||||
# -------- Albums --------
|
||||
if album_name:
|
||||
existing_album = tags.get("TALB")
|
||||
if not (existing_album and existing_album.text and existing_album.text[0].strip()):
|
||||
print(f"[DRY RUN][ALBUM] {path}")
|
||||
print(f" → set album = '{album_name}'")
|
||||
|
||||
if not DRY_RUN:
|
||||
tags.add(TALB(encoding=3, text=album_name))
|
||||
tags.save(path)
|
||||
|
||||
# -------- Singles --------
|
||||
if singles:
|
||||
title = title_from_filename(file)
|
||||
|
||||
existing_title = tags.get("TIT2")
|
||||
existing_album = tags.get("TALB")
|
||||
|
||||
needs_title = not (existing_title and existing_title.text and existing_title.text[0].strip())
|
||||
needs_album = not (existing_album and existing_album.text and existing_album.text[0].strip())
|
||||
|
||||
if needs_title or needs_album:
|
||||
print(f"[DRY RUN][SINGLE] {path}")
|
||||
if needs_title:
|
||||
print(f" → set title = '{title}'")
|
||||
if needs_album:
|
||||
print(" → set album = 'singles'")
|
||||
|
||||
if not DRY_RUN:
|
||||
if needs_title:
|
||||
tags.add(TIT2(encoding=3, text=title))
|
||||
if needs_album:
|
||||
tags.add(TALB(encoding=3, text="singles"))
|
||||
tags.save(path)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] {path}: {e}")
|
||||
131
misc/lrcput+txt.py
Normal file
131
misc/lrcput+txt.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
import os
|
||||
import shutil
|
||||
import argparse
|
||||
from mutagen.flac import FLAC
|
||||
from mutagen.mp4 import MP4
|
||||
import eyed3
|
||||
from tqdm import tqdm
|
||||
|
||||
def has_embedded_lyrics(audio):
|
||||
if isinstance(audio, FLAC):
|
||||
return 'LYRICS' in audio
|
||||
elif isinstance(audio, MP4):
|
||||
return '\xa9lyr' in audio.tags
|
||||
elif isinstance(audio, eyed3.core.AudioFile):
|
||||
return audio.tag.lyrics is not None
|
||||
return False
|
||||
|
||||
def embed_lrc(directory, skip_existing, reduce_lrc, recursive):
|
||||
embedded_lyrics_files = 0
|
||||
failed_files = []
|
||||
|
||||
audio_files = []
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
if file.endswith('.flac') or file.endswith('.mp3') or file.endswith('.m4a'):
|
||||
audio_files.append(os.path.join(root, file))
|
||||
|
||||
with tqdm(total=len(audio_files), desc='Embedding LRC files', unit='file') as pbar:
|
||||
for audio_path in audio_files:
|
||||
file = os.path.basename(audio_path)
|
||||
base = os.path.splitext(file)[0]
|
||||
|
||||
lrc_path = os.path.join(os.path.dirname(audio_path), base + '.lrc')
|
||||
txt_path = os.path.join(os.path.dirname(audio_path), base + '.txt')
|
||||
|
||||
lyrics_path = None
|
||||
if os.path.exists(lrc_path):
|
||||
lyrics_path = lrc_path
|
||||
elif os.path.exists(txt_path):
|
||||
lyrics_path = txt_path
|
||||
|
||||
if not lyrics_path:
|
||||
pbar.set_postfix({"status": "no lrc/txt"})
|
||||
pbar.update(1)
|
||||
continue
|
||||
|
||||
if skip_existing:
|
||||
audio = None
|
||||
if file.endswith('.flac'):
|
||||
audio = FLAC(audio_path)
|
||||
elif file.endswith('.mp3'):
|
||||
audio = eyed3.load(audio_path)
|
||||
elif file.endswith('.m4a'):
|
||||
audio = MP4(audio_path)
|
||||
if has_embedded_lyrics(audio):
|
||||
pbar.set_postfix({"status": "skipped"})
|
||||
pbar.update(1)
|
||||
continue
|
||||
|
||||
try:
|
||||
lyrics = open(lyrics_path, 'r', encoding='utf-8').read()
|
||||
|
||||
if file.endswith('.flac'):
|
||||
audio = FLAC(audio_path)
|
||||
audio['LYRICS'] = lyrics
|
||||
audio.save()
|
||||
elif file.endswith('.mp3'):
|
||||
audio = eyed3.load(audio_path)
|
||||
tag = audio.tag
|
||||
tag.lyrics.set(lyrics)
|
||||
tag.save(version=eyed3.id3.ID3_V2_3)
|
||||
elif file.endswith('.m4a'):
|
||||
audio = MP4(audio_path)
|
||||
audio.tags['\xa9lyr'] = lyrics
|
||||
audio.save()
|
||||
|
||||
embedded_lyrics_files += 1
|
||||
pbar.set_postfix({"status": f"embedded: {file}"})
|
||||
pbar.update(1)
|
||||
pbar.refresh()
|
||||
|
||||
if reduce_lrc:
|
||||
os.remove(lyrics_path)
|
||||
pbar.set_postfix({"status": f"embedded, reduced: {file}"})
|
||||
pbar.refresh()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error embedding lyrics for {file}: {str(e)}")
|
||||
pbar.set_postfix({"status": f"error: {file}"})
|
||||
pbar.update(1)
|
||||
pbar.refresh()
|
||||
failed_files.append(file)
|
||||
if os.path.exists(lyrics_path):
|
||||
shutil.move(lyrics_path, lyrics_path + ".failed")
|
||||
continue
|
||||
|
||||
return len(audio_files), embedded_lyrics_files, failed_files
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description='Embed LRC files into audio files (FLAC, MP3, and M4A) and optionally reduce LRC files.')
|
||||
parser.add_argument('-d', '--directory', required=True, help='Directory containing audio and LRC files')
|
||||
parser.add_argument('-s', '--skip', action='store_true', help='Skip files that already have embedded lyrics')
|
||||
parser.add_argument('-r', '--reduce', action='store_true', help='Reduce (delete) LRC files after embedding')
|
||||
parser.add_argument('-R', '--recursive', action='store_true', help='Recursively process subdirectories')
|
||||
args = parser.parse_args()
|
||||
|
||||
banner = """
|
||||
██╗ ██████╗ ██████╗██████╗ ██╗ ██╗████████╗
|
||||
██║ ██╔══██╗██╔════╝██╔══██╗██║ ██║╚══██╔══╝
|
||||
██║ ██████╔╝██║ ██████╔╝██║ ██║ ██║
|
||||
██║ ██╔══██╗██║ ██╔═══╝ ██║ ██║ ██║
|
||||
███████╗██║ ██║╚██████╗██║ ╚██████╔╝ ██║
|
||||
╚══════╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═════╝ ╚═╝
|
||||
Scripted by TheRedSpy15, .txt support by Sophie"""
|
||||
print(banner)
|
||||
|
||||
directory_path = args.directory
|
||||
skip_existing = args.skip
|
||||
reduce_lrc = args.reduce
|
||||
recursive = args.recursive
|
||||
total, embedded, failed = embed_lrc(directory_path, skip_existing, reduce_lrc, recursive)
|
||||
percentage = (embedded / total) * 100 if total > 0 else 0
|
||||
|
||||
print(f"Total audio files: {total}")
|
||||
print(f"Embedded lyrics in {embedded} audio files.")
|
||||
print(f"Percentage of audio files with embedded lyrics: {percentage:.2f}%")
|
||||
|
||||
if failed:
|
||||
print("\nFailed to embed LRC for the following files:")
|
||||
for file in failed:
|
||||
print(file)
|
||||
Loading…
Add table
Add a link
Reference in a new issue