initial commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Rename music files in-place using embedded ID3 tags.
|
||||
Format: Artist - Title.ext
|
||||
Handles collisions by appending (1), (2), etc.
|
||||
Run via execute_code in an Hermes session — mutagen is available in the agent environment.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
from mutagen import File
|
||||
|
||||
|
||||
def rename_by_tags(base_dir: str, extensions: tuple = ('.mp3', '.flac', '.m4a', '.ogg')) -> dict:
|
||||
"""Walk base_dir and rename all music files to 'Artist - Title.ext' in-place."""
|
||||
renamed = 0
|
||||
skipped_tag = 0
|
||||
skipped_ok = 0
|
||||
errors = []
|
||||
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for fname in files:
|
||||
if not fname.lower().endswith(extensions):
|
||||
continue
|
||||
|
||||
fullpath = os.path.join(root, fname)
|
||||
|
||||
try:
|
||||
audio = File(fullpath, easy=True)
|
||||
except Exception as e:
|
||||
errors.append(f"READ {fullpath}: {e}")
|
||||
continue
|
||||
|
||||
if audio is None or not audio.tags:
|
||||
skipped_tag += 1
|
||||
continue
|
||||
|
||||
artist = audio.tags.get('artist', [None])[0]
|
||||
title = audio.tags.get('title', [None])[0]
|
||||
|
||||
if not artist or not title:
|
||||
skipped_tag += 1
|
||||
continue
|
||||
|
||||
new_name = f"{artist} - {title}".replace('/', '-').replace('\x00', '')
|
||||
ext = os.path.splitext(fname)[1]
|
||||
new_name += ext
|
||||
new_path = os.path.join(root, new_name)
|
||||
|
||||
if fullpath == new_path:
|
||||
skipped_ok += 1
|
||||
continue
|
||||
|
||||
# Handle collisions
|
||||
counter = 1
|
||||
while os.path.exists(new_path) and new_path != fullpath:
|
||||
name_no_ext = f"{artist} - {title} ({counter})".replace('/', '-')
|
||||
new_path = os.path.join(root, name_no_ext + ext)
|
||||
counter += 1
|
||||
|
||||
try:
|
||||
os.rename(fullpath, new_path)
|
||||
renamed += 1
|
||||
except OSError as e:
|
||||
errors.append(f"RENAME {fullpath}: {e}")
|
||||
|
||||
return {
|
||||
'renamed': renamed,
|
||||
'skipped_tag': skipped_tag,
|
||||
'skipped_ok': skipped_ok,
|
||||
'errors': errors,
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python rename_by_tags.py /path/to/music")
|
||||
sys.exit(1)
|
||||
result = rename_by_tags(sys.argv[1])
|
||||
print(f"Renamed: {result['renamed']}")
|
||||
print(f"Skipped (no tags): {result['skipped_tag']}")
|
||||
print(f"Skipped (already OK): {result['skipped_ok']}")
|
||||
print(f"Errors: {len(result['errors'])}")
|
||||
for e in result['errors'][:10]:
|
||||
print(f" {e}")
|
||||
Reference in New Issue
Block a user