music-library-tools/misc/lrcput+txt.py
2025-12-20 12:02:36 +02:00

131 lines
5.4 KiB
Python

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)