Signed-off-by: mbrucedogs <mbrucedogs@gmail.com>
This commit is contained in:
parent
c48c1d3696
commit
d18ac54476
465
fix_artist_name_format.py
Normal file
465
fix_artist_name_format.py
Normal file
@ -0,0 +1,465 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fix artist name formatting for Let's Sing Karaoke channel.
|
||||
|
||||
This script specifically targets the "Last Name, First Name" format and converts it to
|
||||
"First Name Last Name" format in ID3 tags. It only processes entries where there is exactly one comma
|
||||
followed by exactly 2 words, to avoid affecting multi-artist entries.
|
||||
|
||||
Usage:
|
||||
python fix_artist_name_format.py --preview # Show what would be changed
|
||||
python fix_artist_name_format.py --apply # Actually make the changes
|
||||
python fix_artist_name_format.py --external "D:\Karaoke\Karaoke\MP4\Let's Sing Karaoke" # Use external directory
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
|
||||
# Try to import mutagen for ID3 tag manipulation
|
||||
try:
|
||||
from mutagen.mp4 import MP4
|
||||
MUTAGEN_AVAILABLE = True
|
||||
except ImportError:
|
||||
MUTAGEN_AVAILABLE = False
|
||||
print("⚠️ mutagen not available - install with: pip install mutagen")
|
||||
|
||||
|
||||
def is_lastname_firstname_format(artist_name: str) -> bool:
|
||||
"""
|
||||
Check if artist name is in "Last Name, First Name" format.
|
||||
|
||||
Args:
|
||||
artist_name: The artist name to check
|
||||
|
||||
Returns:
|
||||
True if the name matches "Last Name, First Name" format with exactly 2 words after comma
|
||||
"""
|
||||
if ',' not in artist_name:
|
||||
return False
|
||||
|
||||
# Split by comma
|
||||
parts = artist_name.split(',', 1)
|
||||
if len(parts) != 2:
|
||||
return False
|
||||
|
||||
last_name = parts[0].strip()
|
||||
first_name_part = parts[1].strip()
|
||||
|
||||
# Check if there are exactly 2 words after the comma
|
||||
words_after_comma = first_name_part.split()
|
||||
if len(words_after_comma) != 2:
|
||||
return False
|
||||
|
||||
# Additional check: make sure it's not a multi-artist entry
|
||||
# If there are more than 2 words total in the artist name, it might be multi-artist
|
||||
total_words = len(artist_name.split())
|
||||
if total_words > 4: # Last, First Name (4 words max for single artist)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def convert_to_firstname_lastname(artist_name: str) -> str:
|
||||
"""
|
||||
Convert "Last Name, First Name" to "First Name Last Name".
|
||||
|
||||
Args:
|
||||
artist_name: Artist name in "Last Name, First Name" format
|
||||
|
||||
Returns:
|
||||
Artist name in "First Name Last Name" format
|
||||
"""
|
||||
parts = artist_name.split(',', 1)
|
||||
last_name = parts[0].strip()
|
||||
first_name_part = parts[1].strip()
|
||||
|
||||
# Split the first name part into words
|
||||
words = first_name_part.split()
|
||||
if len(words) == 2:
|
||||
first_name = words[0]
|
||||
middle_name = words[1]
|
||||
return f"{first_name} {middle_name} {last_name}"
|
||||
else:
|
||||
# Fallback - just reverse the parts
|
||||
return f"{first_name_part} {last_name}"
|
||||
|
||||
|
||||
def extract_artist_title_from_filename(filename: str) -> Tuple[str, str]:
|
||||
"""
|
||||
Extract artist and title from a filename.
|
||||
|
||||
Args:
|
||||
filename: MP4 filename (without extension)
|
||||
|
||||
Returns:
|
||||
Tuple of (artist, title)
|
||||
"""
|
||||
# Remove .mp4 extension
|
||||
if filename.endswith('.mp4'):
|
||||
filename = filename[:-4]
|
||||
|
||||
# Look for " - " separator
|
||||
if " - " in filename:
|
||||
parts = filename.split(" - ", 1)
|
||||
return parts[0].strip(), parts[1].strip()
|
||||
|
||||
return "", filename
|
||||
|
||||
|
||||
def update_id3_tags(file_path: str, new_artist: str, apply_changes: bool = False) -> bool:
|
||||
"""
|
||||
Update the ID3 tags in an MP4 file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the MP4 file
|
||||
new_artist: New artist name to set
|
||||
apply_changes: Whether to actually apply changes or just preview
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
if not MUTAGEN_AVAILABLE:
|
||||
print(f"⚠️ mutagen not available - cannot update ID3 tags for {file_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
mp4 = MP4(file_path)
|
||||
|
||||
if apply_changes:
|
||||
# Update the artist tag
|
||||
mp4["\xa9ART"] = new_artist
|
||||
mp4.save()
|
||||
print(f"📝 Updated ID3 tag: {os.path.basename(file_path)} → Artist: '{new_artist}'")
|
||||
else:
|
||||
# Just preview what would be changed
|
||||
current_artist = mp4.get("\xa9ART", ["Unknown"])[0] if "\xa9ART" in mp4 else "Unknown"
|
||||
print(f"📝 Would update ID3 tag: {os.path.basename(file_path)} → Artist: '{current_artist}' → '{new_artist}'")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Failed to update ID3 tags for {file_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def scan_external_directory(directory_path: str) -> List[Dict]:
|
||||
"""
|
||||
Scan external directory for MP4 files with "Last Name, First Name" format in ID3 tags.
|
||||
|
||||
Args:
|
||||
directory_path: Path to the external directory
|
||||
|
||||
Returns:
|
||||
List of files that need ID3 tag updates
|
||||
"""
|
||||
if not os.path.exists(directory_path):
|
||||
print(f"❌ Directory not found: {directory_path}")
|
||||
return []
|
||||
|
||||
if not MUTAGEN_AVAILABLE:
|
||||
print("❌ mutagen not available - cannot scan ID3 tags")
|
||||
return []
|
||||
|
||||
files_to_update = []
|
||||
|
||||
# Scan for MP4 files
|
||||
for file_path in Path(directory_path).glob("*.mp4"):
|
||||
try:
|
||||
mp4 = MP4(str(file_path))
|
||||
current_artist = mp4.get("\xa9ART", ["Unknown"])[0] if "\xa9ART" in mp4 else "Unknown"
|
||||
|
||||
if current_artist and is_lastname_firstname_format(current_artist):
|
||||
new_artist = convert_to_firstname_lastname(current_artist)
|
||||
|
||||
files_to_update.append({
|
||||
'file_path': str(file_path),
|
||||
'filename': file_path.name,
|
||||
'old_artist': current_artist,
|
||||
'new_artist': new_artist
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Could not read ID3 tags from {file_path.name}: {e}")
|
||||
|
||||
return files_to_update
|
||||
|
||||
|
||||
def update_tracking_file(tracking_file: str, channel_name: str = "Let's Sing Karaoke", apply_changes: bool = False) -> Tuple[int, List[Dict]]:
|
||||
"""
|
||||
Update the karaoke tracking file to fix artist name formatting.
|
||||
|
||||
Args:
|
||||
tracking_file: Path to the tracking JSON file
|
||||
channel_name: Channel name to target (default: Let's Sing Karaoke)
|
||||
apply_changes: Whether to actually apply changes or just preview
|
||||
|
||||
Returns:
|
||||
Tuple of (number of changes made, list of changed entries)
|
||||
"""
|
||||
if not os.path.exists(tracking_file):
|
||||
print(f"❌ Tracking file not found: {tracking_file}")
|
||||
return 0, []
|
||||
|
||||
# Load the tracking data
|
||||
with open(tracking_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
changes_made = 0
|
||||
changed_entries = []
|
||||
|
||||
# Process songs
|
||||
for song_key, song_data in data.get('songs', {}).items():
|
||||
if song_data.get('channel_name') != channel_name:
|
||||
continue
|
||||
|
||||
artist = song_data.get('artist', '')
|
||||
if not artist or not is_lastname_firstname_format(artist):
|
||||
continue
|
||||
|
||||
# Convert the artist name
|
||||
new_artist = convert_to_firstname_lastname(artist)
|
||||
|
||||
if apply_changes:
|
||||
# Update the tracking data
|
||||
song_data['artist'] = new_artist
|
||||
|
||||
# Update the video title if it exists and contains the old artist name
|
||||
video_title = song_data.get('video_title', '')
|
||||
if video_title and artist in video_title:
|
||||
song_data['video_title'] = video_title.replace(artist, new_artist)
|
||||
|
||||
# Update the file path if it exists
|
||||
file_path = song_data.get('file_path', '')
|
||||
if file_path and artist in file_path:
|
||||
song_data['file_path'] = file_path.replace(artist, new_artist)
|
||||
|
||||
changes_made += 1
|
||||
changed_entries.append({
|
||||
'song_key': song_key,
|
||||
'old_artist': artist,
|
||||
'new_artist': new_artist,
|
||||
'title': song_data.get('title', ''),
|
||||
'file_path': song_data.get('file_path', '')
|
||||
})
|
||||
|
||||
print(f"🔄 {'Updated' if apply_changes else 'Would update'}: '{artist}' → '{new_artist}' ({song_data.get('title', '')})")
|
||||
|
||||
# Save the updated data
|
||||
if apply_changes and changes_made > 0:
|
||||
# Create backup
|
||||
backup_file = f"{tracking_file}.backup"
|
||||
shutil.copy2(tracking_file, backup_file)
|
||||
print(f"💾 Created backup: {backup_file}")
|
||||
|
||||
# Save updated file
|
||||
with open(tracking_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
print(f"💾 Updated tracking file: {tracking_file}")
|
||||
|
||||
return changes_made, changed_entries
|
||||
|
||||
|
||||
def update_songlist_tracking(songlist_file: str, channel_name: str = "Let's Sing Karaoke", apply_changes: bool = False) -> Tuple[int, List[Dict]]:
|
||||
"""
|
||||
Update the songlist tracking file to fix artist name formatting.
|
||||
|
||||
Args:
|
||||
songlist_file: Path to the songlist tracking JSON file
|
||||
channel_name: Channel name to target (default: Let's Sing Karaoke)
|
||||
apply_changes: Whether to actually apply changes or just preview
|
||||
|
||||
Returns:
|
||||
Tuple of (number of changes made, list of changed entries)
|
||||
"""
|
||||
if not os.path.exists(songlist_file):
|
||||
print(f"❌ Songlist tracking file not found: {songlist_file}")
|
||||
return 0, []
|
||||
|
||||
# Load the songlist data
|
||||
with open(songlist_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
changes_made = 0
|
||||
changed_entries = []
|
||||
|
||||
# Process songlist entries
|
||||
for song_key, song_data in data.items():
|
||||
artist = song_data.get('artist', '')
|
||||
if not artist or not is_lastname_firstname_format(artist):
|
||||
continue
|
||||
|
||||
# Convert the artist name
|
||||
new_artist = convert_to_firstname_lastname(artist)
|
||||
|
||||
if apply_changes:
|
||||
# Update the songlist data
|
||||
song_data['artist'] = new_artist
|
||||
|
||||
changes_made += 1
|
||||
changed_entries.append({
|
||||
'song_key': song_key,
|
||||
'old_artist': artist,
|
||||
'new_artist': new_artist,
|
||||
'title': song_data.get('title', '')
|
||||
})
|
||||
|
||||
print(f"🔄 {'Updated' if apply_changes else 'Would update'} songlist: '{artist}' → '{new_artist}' ({song_data.get('title', '')})")
|
||||
|
||||
# Save the updated data
|
||||
if apply_changes and changes_made > 0:
|
||||
# Create backup
|
||||
backup_file = f"{songlist_file}.backup"
|
||||
shutil.copy2(songlist_file, backup_file)
|
||||
print(f"💾 Created backup: {backup_file}")
|
||||
|
||||
# Save updated file
|
||||
with open(songlist_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
print(f"💾 Updated songlist file: {songlist_file}")
|
||||
|
||||
return changes_made, changed_entries
|
||||
|
||||
|
||||
def update_id3_tags_for_files(files_to_update: List[Dict], apply_changes: bool = False) -> int:
|
||||
"""
|
||||
Update ID3 tags for a list of files.
|
||||
|
||||
Args:
|
||||
files_to_update: List of files to update
|
||||
apply_changes: Whether to actually apply changes or just preview
|
||||
|
||||
Returns:
|
||||
Number of files successfully updated
|
||||
"""
|
||||
updated_count = 0
|
||||
|
||||
for file_info in files_to_update:
|
||||
file_path = file_info['file_path']
|
||||
new_artist = file_info['new_artist']
|
||||
|
||||
if update_id3_tags(file_path, new_artist, apply_changes):
|
||||
updated_count += 1
|
||||
|
||||
return updated_count
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run the artist name fix script."""
|
||||
parser = argparse.ArgumentParser(description="Fix artist name formatting in ID3 tags for Let's Sing Karaoke")
|
||||
parser.add_argument('--preview', action='store_true', help='Show what would be changed without making changes')
|
||||
parser.add_argument('--apply', action='store_true', help='Actually apply the changes')
|
||||
parser.add_argument('--external', type=str, help='Path to external karaoke directory')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Default to preview mode if no action specified
|
||||
if not args.preview and not args.apply:
|
||||
args.preview = True
|
||||
|
||||
print("🎤 Artist Name Format Fix Script (ID3 Tags Only)")
|
||||
print("=" * 60)
|
||||
print("This script will fix 'Last Name, First Name' format to 'First Name Last Name'")
|
||||
print("Only targeting Let's Sing Karaoke channel to avoid affecting other channels.")
|
||||
print("Focusing on ID3 tags only - filenames will not be changed.")
|
||||
print()
|
||||
|
||||
if not MUTAGEN_AVAILABLE:
|
||||
print("❌ mutagen library not available!")
|
||||
print("Please install it with: pip install mutagen")
|
||||
return
|
||||
|
||||
if args.preview:
|
||||
print("🔍 PREVIEW MODE - No changes will be made")
|
||||
else:
|
||||
print("⚡ APPLY MODE - Changes will be made")
|
||||
print()
|
||||
|
||||
# File paths
|
||||
tracking_file = "data/karaoke_tracking.json"
|
||||
songlist_file = "data/songlist_tracking.json"
|
||||
|
||||
# Process external directory if specified
|
||||
if args.external:
|
||||
print(f"📁 Scanning external directory: {args.external}")
|
||||
external_files = scan_external_directory(args.external)
|
||||
|
||||
if external_files:
|
||||
print(f"\n📋 Found {len(external_files)} files with 'Last Name, First Name' format in ID3 tags:")
|
||||
for file_info in external_files:
|
||||
print(f" • {file_info['filename']}: '{file_info['old_artist']}' → '{file_info['new_artist']}'")
|
||||
|
||||
if args.apply:
|
||||
print(f"\n📝 Updating ID3 tags in external files...")
|
||||
updated_count = update_id3_tags_for_files(external_files, apply_changes=True)
|
||||
print(f"✅ Updated ID3 tags in {updated_count} external files")
|
||||
else:
|
||||
print(f"\n📝 Would update ID3 tags in {len(external_files)} external files")
|
||||
else:
|
||||
print("✅ No files with 'Last Name, First Name' format found in ID3 tags")
|
||||
|
||||
# Process tracking files (only if they exist in current project)
|
||||
if os.path.exists(tracking_file):
|
||||
print(f"\n📊 Processing karaoke tracking file...")
|
||||
tracking_changes, tracking_entries = update_tracking_file(tracking_file, apply_changes=args.apply)
|
||||
else:
|
||||
print(f"\n⚠️ Tracking file not found: {tracking_file}")
|
||||
tracking_changes = 0
|
||||
|
||||
if os.path.exists(songlist_file):
|
||||
print(f"\n📊 Processing songlist tracking file...")
|
||||
songlist_changes, songlist_entries = update_songlist_tracking(songlist_file, apply_changes=args.apply)
|
||||
else:
|
||||
print(f"\n⚠️ Songlist tracking file not found: {songlist_file}")
|
||||
songlist_changes = 0
|
||||
|
||||
# Process local downloads directory ID3 tags
|
||||
downloads_dir = "downloads"
|
||||
local_id3_updates = 0
|
||||
if os.path.exists(downloads_dir) and tracking_changes > 0:
|
||||
print(f"\n📝 Processing ID3 tags in local downloads directory...")
|
||||
# Scan local downloads for files that need ID3 tag updates
|
||||
local_files = []
|
||||
for entry in tracking_entries:
|
||||
file_path = entry.get('file_path', '')
|
||||
if file_path and os.path.exists(file_path.replace('\\', '/')):
|
||||
local_files.append({
|
||||
'file_path': file_path.replace('\\', '/'),
|
||||
'filename': os.path.basename(file_path),
|
||||
'old_artist': entry['old_artist'],
|
||||
'new_artist': entry['new_artist']
|
||||
})
|
||||
|
||||
if local_files:
|
||||
local_id3_updates = update_id3_tags_for_files(local_files, apply_changes=args.apply)
|
||||
|
||||
total_changes = tracking_changes + songlist_changes
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("📋 Summary:")
|
||||
print(f" • Tracking file changes: {tracking_changes}")
|
||||
print(f" • Songlist file changes: {songlist_changes}")
|
||||
print(f" • Local ID3 tag updates: {local_id3_updates}")
|
||||
print(f" • Total changes: {total_changes}")
|
||||
|
||||
if args.external:
|
||||
external_count = len(scan_external_directory(args.external)) if args.preview else len(external_files)
|
||||
print(f" • External ID3 tag updates: {external_count}")
|
||||
|
||||
if total_changes > 0 or (args.external and external_count > 0):
|
||||
if args.apply:
|
||||
print("\n✅ Artist name formatting in ID3 tags has been fixed!")
|
||||
print("💾 Backups have been created for all modified files.")
|
||||
print("🔄 You may need to re-run your karaoke downloader to update any cached data.")
|
||||
else:
|
||||
print("\n🔍 Preview complete. Use --apply to make these changes.")
|
||||
else:
|
||||
print("\n✅ No changes needed! All artist names are already in the correct format.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
295
fix_artist_name_format_simple.py
Normal file
295
fix_artist_name_format_simple.py
Normal file
@ -0,0 +1,295 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fix artist name formatting for Let's Sing Karaoke channel.
|
||||
|
||||
This script specifically targets the "Last Name, First Name" format and converts it to
|
||||
"First Name Last Name" format in ID3 tags. It only processes entries where there is exactly one comma
|
||||
followed by exactly 2 words, to avoid affecting multi-artist entries.
|
||||
|
||||
Usage:
|
||||
python fix_artist_name_format_simple.py --preview # Show what would be changed
|
||||
python fix_artist_name_format_simple.py --apply # Actually make the changes
|
||||
python fix_artist_name_format_simple.py --external "D:\Karaoke\Karaoke\MP4\Let's Sing Karaoke" # Use external directory
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
|
||||
# Try to import mutagen for ID3 tag manipulation
|
||||
try:
|
||||
from mutagen.mp4 import MP4
|
||||
MUTAGEN_AVAILABLE = True
|
||||
except ImportError:
|
||||
MUTAGEN_AVAILABLE = False
|
||||
print("WARNING: mutagen not available - install with: pip install mutagen")
|
||||
|
||||
|
||||
def is_lastname_firstname_format(artist_name: str) -> bool:
|
||||
"""
|
||||
Check if artist name is in "Last Name, First Name" format.
|
||||
|
||||
Args:
|
||||
artist_name: The artist name to check
|
||||
|
||||
Returns:
|
||||
True if the name matches "Last Name, First Name" format with exactly 1 or 2 words after comma
|
||||
"""
|
||||
if ',' not in artist_name:
|
||||
return False
|
||||
|
||||
# Split by comma
|
||||
parts = artist_name.split(',', 1)
|
||||
if len(parts) != 2:
|
||||
return False
|
||||
|
||||
last_name = parts[0].strip()
|
||||
first_name_part = parts[1].strip()
|
||||
|
||||
# Check if there are exactly 1 or 2 words after the comma
|
||||
words_after_comma = first_name_part.split()
|
||||
if len(words_after_comma) not in [1, 2]:
|
||||
return False
|
||||
|
||||
# Additional check: make sure it's not a multi-artist entry
|
||||
# If there are more than 4 words total in the artist name, it might be multi-artist
|
||||
total_words = len(artist_name.split())
|
||||
if total_words > 4: # Last, First Name (4 words max for single artist)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def convert_lastname_firstname(artist_name: str) -> str:
|
||||
"""
|
||||
Convert "Last Name, First Name" to "First Name Last Name".
|
||||
|
||||
Args:
|
||||
artist_name: The artist name to convert
|
||||
|
||||
Returns:
|
||||
The converted artist name
|
||||
"""
|
||||
if ',' not in artist_name:
|
||||
return artist_name
|
||||
|
||||
parts = artist_name.split(',', 1)
|
||||
if len(parts) != 2:
|
||||
return artist_name
|
||||
|
||||
last_name = parts[0].strip()
|
||||
first_name = parts[1].strip()
|
||||
|
||||
return f"{first_name} {last_name}"
|
||||
|
||||
|
||||
def process_artist_name(artist_name: str) -> str:
|
||||
"""
|
||||
Process an artist name, handling both single artists and multiple artists separated by "&".
|
||||
|
||||
Args:
|
||||
artist_name: The artist name to process
|
||||
|
||||
Returns:
|
||||
The processed artist name
|
||||
"""
|
||||
if '&' in artist_name:
|
||||
# Split by "&" and process each artist individually
|
||||
artists = [artist.strip() for artist in artist_name.split('&')]
|
||||
processed_artists = []
|
||||
|
||||
for artist in artists:
|
||||
if is_lastname_firstname_format(artist):
|
||||
processed_artist = convert_lastname_firstname(artist)
|
||||
processed_artists.append(processed_artist)
|
||||
else:
|
||||
processed_artists.append(artist)
|
||||
|
||||
# Rejoin with "&"
|
||||
return ' & '.join(processed_artists)
|
||||
else:
|
||||
# Single artist
|
||||
if is_lastname_firstname_format(artist_name):
|
||||
return convert_lastname_firstname(artist_name)
|
||||
else:
|
||||
return artist_name
|
||||
|
||||
|
||||
def update_id3_tags(file_path: str, new_artist: str, apply_changes: bool = False) -> bool:
|
||||
"""
|
||||
Update the ID3 tags in an MP4 file.
|
||||
|
||||
Args:
|
||||
file_path: Path to the MP4 file
|
||||
new_artist: New artist name to set
|
||||
apply_changes: Whether to actually apply changes or just preview
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
if not MUTAGEN_AVAILABLE:
|
||||
print(f"WARNING: mutagen not available - cannot update ID3 tags for {file_path}")
|
||||
return False
|
||||
|
||||
try:
|
||||
mp4 = MP4(file_path)
|
||||
|
||||
if apply_changes:
|
||||
# Update the artist tag
|
||||
mp4["\xa9ART"] = new_artist
|
||||
mp4.save()
|
||||
print(f"UPDATED ID3 tag: {os.path.basename(file_path)} -> Artist: '{new_artist}'")
|
||||
else:
|
||||
# Just preview what would be changed
|
||||
current_artist = mp4.get("\xa9ART", ["Unknown"])[0] if "\xa9ART" in mp4 else "Unknown"
|
||||
print(f"WOULD UPDATE ID3 tag: {os.path.basename(file_path)} -> Artist: '{current_artist}' -> '{new_artist}'")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: Failed to update ID3 tags for {file_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def scan_external_directory(directory_path: str, debug: bool = False) -> List[Dict]:
|
||||
"""
|
||||
Scan external directory for MP4 files with "Last Name, First Name" format in ID3 tags.
|
||||
|
||||
Args:
|
||||
directory_path: Path to the external directory
|
||||
debug: Whether to show debug information
|
||||
|
||||
Returns:
|
||||
List of files that need ID3 tag updates
|
||||
"""
|
||||
if not os.path.exists(directory_path):
|
||||
print(f"ERROR: Directory not found: {directory_path}")
|
||||
return []
|
||||
|
||||
if not MUTAGEN_AVAILABLE:
|
||||
print("ERROR: mutagen not available - cannot scan ID3 tags")
|
||||
return []
|
||||
|
||||
files_to_update = []
|
||||
total_files = 0
|
||||
files_with_artist_tags = 0
|
||||
|
||||
# Scan for MP4 files
|
||||
for file_path in Path(directory_path).glob("*.mp4"):
|
||||
total_files += 1
|
||||
try:
|
||||
mp4 = MP4(str(file_path))
|
||||
current_artist = mp4.get("\xa9ART", ["Unknown"])[0] if "\xa9ART" in mp4 else "Unknown"
|
||||
|
||||
if current_artist != "Unknown":
|
||||
files_with_artist_tags += 1
|
||||
|
||||
if debug:
|
||||
print(f"DEBUG: {file_path.name} -> Artist: '{current_artist}'")
|
||||
|
||||
# Process the artist name to handle multiple artists
|
||||
processed_artist = process_artist_name(current_artist)
|
||||
|
||||
if processed_artist != current_artist:
|
||||
files_to_update.append({
|
||||
'file_path': str(file_path),
|
||||
'filename': file_path.name,
|
||||
'old_artist': current_artist,
|
||||
'new_artist': processed_artist
|
||||
})
|
||||
|
||||
if debug:
|
||||
print(f"DEBUG: MATCH FOUND - {file_path.name}: '{current_artist}' -> '{processed_artist}'")
|
||||
|
||||
except Exception as e:
|
||||
if debug:
|
||||
print(f"WARNING: Could not read ID3 tags from {file_path.name}: {e}")
|
||||
|
||||
print(f"INFO: Scanned {total_files} MP4 files, {files_with_artist_tags} had artist tags, {len(files_to_update)} need updates")
|
||||
return files_to_update
|
||||
|
||||
|
||||
def update_id3_tags_for_files(files_to_update: List[Dict], apply_changes: bool = False) -> int:
|
||||
"""
|
||||
Update ID3 tags for a list of files.
|
||||
|
||||
Args:
|
||||
files_to_update: List of files to update
|
||||
apply_changes: Whether to actually apply changes or just preview
|
||||
|
||||
Returns:
|
||||
Number of files successfully updated
|
||||
"""
|
||||
updated_count = 0
|
||||
|
||||
for file_info in files_to_update:
|
||||
file_path = file_info['file_path']
|
||||
new_artist = file_info['new_artist']
|
||||
|
||||
if update_id3_tags(file_path, new_artist, apply_changes):
|
||||
updated_count += 1
|
||||
|
||||
return updated_count
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function to run the artist name fix script."""
|
||||
parser = argparse.ArgumentParser(description="Fix artist name formatting in ID3 tags for Let's Sing Karaoke")
|
||||
parser.add_argument('--preview', action='store_true', help='Show what would be changed without making changes')
|
||||
parser.add_argument('--apply', action='store_true', help='Actually apply the changes')
|
||||
parser.add_argument('--external', type=str, help='Path to external karaoke directory')
|
||||
parser.add_argument('--debug', action='store_true', help='Show debug information')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Default to preview mode if no action specified
|
||||
if not args.preview and not args.apply:
|
||||
args.preview = True
|
||||
|
||||
print("Artist Name Format Fix Script (ID3 Tags Only)")
|
||||
print("=" * 60)
|
||||
print("This script will fix 'Last Name, First Name' format to 'First Name Last Name'")
|
||||
print("Only targeting Let's Sing Karaoke channel to avoid affecting other channels.")
|
||||
print("Focusing on ID3 tags only - filenames will not be changed.")
|
||||
print()
|
||||
|
||||
if not MUTAGEN_AVAILABLE:
|
||||
print("ERROR: mutagen library not available!")
|
||||
print("Please install it with: pip install mutagen")
|
||||
return
|
||||
|
||||
if args.preview:
|
||||
print("PREVIEW MODE - No changes will be made")
|
||||
else:
|
||||
print("APPLY MODE - Changes will be made")
|
||||
print()
|
||||
|
||||
# Process external directory if specified
|
||||
if args.external:
|
||||
print(f"Scanning external directory: {args.external}")
|
||||
external_files = scan_external_directory(args.external, debug=args.debug)
|
||||
|
||||
if external_files:
|
||||
print(f"\nFound {len(external_files)} files with 'Last Name, First Name' format in ID3 tags:")
|
||||
for file_info in external_files:
|
||||
print(f" * {file_info['filename']}: '{file_info['old_artist']}' -> '{file_info['new_artist']}'")
|
||||
|
||||
if args.apply:
|
||||
print(f"\nUpdating ID3 tags in external files...")
|
||||
updated_count = update_id3_tags_for_files(external_files, apply_changes=True)
|
||||
print(f"SUCCESS: Updated ID3 tags in {updated_count} external files")
|
||||
else:
|
||||
print(f"\nWould update ID3 tags in {len(external_files)} external files")
|
||||
else:
|
||||
print("SUCCESS: No files with 'Last Name, First Name' format found in ID3 tags")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Summary complete.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user