|
import os |
|
import json |
|
import argparse |
|
import requests |
|
from mutagen.flac import FLAC |
|
from langdetect import detect, LangDetectException |
|
from datetime import datetime |
|
|
|
# Change these |
|
MUSIC_DIR = "/my/music/library" # Directory containing the FLAC files |
|
DEEPL_AUTH_KEY = "mysecretkey" # Your DeepL API key (https://www.deepl.com/en/your-account/keys) |
|
TARGET_LANG = "EN" |
|
|
|
DEEPL_API_URL = "https://api-free.deepl.com/v2/translate" |
|
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) |
|
STORE_FILE = os.path.join(SCRIPT_DIR, "translations_store.json") |
|
|
|
def load_store(): |
|
"""Load processed files store from JSON file.""" |
|
if os.path.exists(STORE_FILE): |
|
with open(STORE_FILE, "r") as f: |
|
try: |
|
store = json.load(f) |
|
except json.JSONDecodeError: |
|
store = {} |
|
else: |
|
store = {} |
|
return store |
|
|
|
def save_store(store): |
|
"""Save the processed files store to JSON file.""" |
|
with open(STORE_FILE, "w") as f: |
|
json.dump(store, f, indent=4) |
|
|
|
def translate_text(text, target_lang=TARGET_LANG): |
|
"""Translate given text using DeepL API.""" |
|
payload = { |
|
'auth_key': DEEPL_AUTH_KEY, |
|
'text': text, |
|
'target_lang': target_lang, |
|
} |
|
response = requests.post(DEEPL_API_URL, data=payload) |
|
if response.status_code == 200: |
|
json_data = response.json() |
|
translated_text = json_data['translations'][0]['text'] |
|
return translated_text |
|
else: |
|
print("lyrics: error from deepl api for: unknown file") |
|
return None |
|
|
|
def process_flac_file(file_path, dry_run=False, force=False, store=None): |
|
"""Process a single FLAC file: check lyrics language and translate if necessary.""" |
|
abs_path = os.path.abspath(file_path) |
|
try: |
|
audio = FLAC(file_path) |
|
except Exception as e: |
|
print(f"lyrics: error opening file: {file_path}") |
|
return |
|
|
|
# Check for the lyrics tag (assumed to be "LYRICS") |
|
if "LYRICS" in audio: |
|
original_lyrics = audio["LYRICS"][0] |
|
try: |
|
detected_lang = detect(original_lyrics) |
|
except LangDetectException as e: |
|
print(f"lyrics: could not detect language: {file_path}") |
|
store[abs_path] = {"status": "detection_failed", "timestamp": datetime.now().isoformat()} |
|
return |
|
if detected_lang.lower() != "en": |
|
print(f"lyrics: translating file (detected language {detected_lang.lower()}): {file_path}") |
|
translated = translate_text(original_lyrics) |
|
if translated: |
|
if dry_run: |
|
print(f"lyrics: dry run translation preview: {file_path}") |
|
print(f"lyrics: {translated.lower()}: {file_path}") |
|
else: |
|
audio["LYRICS"] = [translated] |
|
try: |
|
audio.save() |
|
print(f"lyrics: updated with translated lyrics: {file_path}") |
|
except Exception as save_error: |
|
print(f"lyrics: error saving file: {file_path}") |
|
store[abs_path] = {"status": "save_error", "timestamp": datetime.now().isoformat()} |
|
return |
|
store[abs_path] = {"status": "translated", "timestamp": datetime.now().isoformat()} |
|
else: |
|
print(f"lyrics: translation failed for: {file_path}") |
|
store[abs_path] = {"status": "translation_failed", "timestamp": datetime.now().isoformat()} |
|
else: |
|
print(f"lyrics: lyrics already in english: {file_path}") |
|
store[abs_path] = {"status": "english", "timestamp": datetime.now().isoformat()} |
|
else: |
|
print(f"lyrics: no lyrics tag found: {file_path}") |
|
store[abs_path] = {"status": "no_lyrics", "timestamp": datetime.now().isoformat()} |
|
|
|
def iterate_albums(directory, dry_run=False, force=False): |
|
"""Walk through the directory recursively and process each FLAC file.""" |
|
store = load_store() if not force else {} |
|
skipped_count = 0 |
|
|
|
for root, dirs, files in os.walk(directory): |
|
for file in files: |
|
if file.lower().endswith(".flac"): |
|
file_path = os.path.join(root, file) |
|
abs_path = os.path.abspath(file_path) |
|
if not force and abs_path in store: |
|
skipped_count += 1 |
|
continue |
|
process_flac_file(file_path, dry_run=dry_run, force=force, store=store) |
|
|
|
if skipped_count > 0: |
|
print(f"Skipped {skipped_count} paths.") |
|
save_store(store) |
|
|
|
def main(): |
|
parser = argparse.ArgumentParser( |
|
description="Translate lyrics in flac files using DeepL API.\n" |
|
"Keeps track of processed files inside `translations_store.json` which is stored in the script's directory." |
|
) |
|
parser.add_argument("-n", "--dry-run", action="store_true", help="show what would be translated without modifying files.") |
|
parser.add_argument("-f", "--force", action="store_true", help="force rechecking of all files even if processed before.") |
|
parser.add_argument("-d", "--directory", type=str, default=MUSIC_DIR, help="directory containing the flac files.") |
|
args = parser.parse_args() |
|
|
|
iterate_albums(directory=args.directory, dry_run=args.dry_run, force=args.force) |
|
|
|
if __name__ == "__main__": |
|
main() |