LRC: караоке в MP3
Случайно узнал, что программы-плееры и носимые устройства, чуть дороже базового уровня, отображают текст в mp3 файлах.
И, если его разметить временными блоками в начале каждой строки, выделяют эту строку, как в караоке.
Такой формат называется LRC (видимо, от слова lyric) и понимается достаточно большим количеством клиентов.
Хотите, расскажу где взять эту разметку?
Есть сайты с большими базами готовых текстов, поиск по которым даёт впечатляющие результаты:
- lrclib.net — бесплатная база данных с внятным поиском и собственным клиентом
- megalobiz.com — большая база с неустойчивым соединением (может это только мне так повезло)
- netease — азиатский поисковик, тоже временами отваливается
- musixmatch.com — эти ещё и денег по подписке хотят
В общем, идея интересная, но для большой аудио коллекции хлопотная в реализации. А что, если доверить поиск программе…
Можно автоматизировать поиск LRC?
Разумеется, можно. Я пробовал на Python 3 (в Windows). И вот, что получилось:
Для начала установим библиотеки обработки mp3 и поиска по lrc базам. В cmd введём:
pip install mutagen syncedlyrics
и подтвердим — Enter. А когда процесс завершится, напишем простенький скрипт пакетной обработки (кликните на название файла):
main.py
import configparser
import os
import sys
import time
from mutagen.id3 import ID3, USLT, ID3NoHeaderError
import syncedlyrics
def get_base_path():
"""Получает путь к папке, где лежит exe или py файл."""
if getattr(sys, "frozen", False):
return os.path.dirname(sys.executable)
return os.path.dirname(os.path.abspath(__file__))
def load_config():
base_path = get_base_path()
config_path = os.path.join(base_path, "config.ini")
if not os.path.exists(config_path):
print(f"[Ошибка] Файл конфигурации не найден: {config_path}")
input("\nНажмите Enter для выхода...")
sys.exit(1)
config = configparser.ConfigParser()
config.read(config_path, encoding="utf-8")
try:
settings = config["Settings"]
music_dir = settings.get("music_dir", "").strip()
overwrite_existing = settings.getboolean(
"overwrite_existing", fallback=False
)
delay_seconds = settings.getfloat("delay_seconds", fallback=1.5)
providers_raw = settings.get("providers", "lrclib, netease, megalobiz")
providers = [p.strip() for p in providers_raw.split(",") if p.strip()]
return music_dir, overwrite_existing, delay_seconds, providers
except Exception as e:
print(f"[Ошибка] Сбой при чтении config.ini: {e}")
input("\nНажмите Enter для выхода...")
sys.exit(1)
def process_folder(music_dir, overwrite_existing, delay_seconds, providers):
if not os.path.exists(music_dir):
print(f"[Ошибка] Указанная папка с музыкой не найдена: {music_dir}")
return
print("=" * 60)
print(f" Папка: {music_dir}")
print(
f" Перезапись существующего USLT: {'ВКЛ' if overwrite_existing else 'ВЫКЛ (пропуск)'}"
)
print(f" Провайдеры: {', '.join(providers)}")
print(f" Пауза между запросами: {delay_seconds} сек.")
print("=" * 60)
for root, _, files in os.walk(music_dir):
for file in files:
if not file.lower().endswith(".mp3"):
continue
mp3_path = os.path.join(root, file)
try:
tags = ID3(mp3_path)
artist = tags.get("TPE1", [""])[0]
title = tags.get("TIT2", [""])[0]
uslt_tags = tags.getall("USLT")
except ID3NoHeaderError:
tags = ID3()
artist, title = "", ""
uslt_tags = []
# Пропуск, если тег уже есть и перезапись отключена
if uslt_tags and not overwrite_existing:
print(f"[Пропуск - USLT есть]: {file}")
continue
search_query = (
f"{artist} - {title}"
if artist and title
else os.path.splitext(file)[0]
)
print(f"\n[Ищем]: {search_query}")
try:
lrc_text = syncedlyrics.search(
search_query,
providers=providers,
synced_only=True,
)
if lrc_text:
tags.setall(
"USLT",
[
USLT(
encoding=3, # UTF-8
lang="eng",
desc="",
text=lrc_text,
)
],
)
tags.save(mp3_path, v2_version=3)
print(f" [OK] Вшито в тег USLT: {file}")
else:
print(f" [Пропуск] LRC с таймкодами не найден.")
except Exception as e:
print(f" [Ошибка]: {e}")
time.sleep(delay_seconds)
if __name__ == "__main__":
music_dir, overwrite, delay, providers = load_config()
process_folder(music_dir, overwrite, delay, providers)
print("\n" + "=" * 60)
print("Обработка завершена!")
input("Нажмите Enter для закрытия программы...")
В отдельный файл конфигурации вынесем
- путь к обрабатываемой папке с медиатекой,
- перечень провайдеров, у которых будем искать lrc-тексты
- режим обработки существующих данных (перезаписать или пропустить), и
- задержку между запросами для параноидальных защит.
config.ini
[Settings]
# Путь к папке с Вашей музыкой
music_dir = E:\Music
# Режим перезаписи: true — перезаписывать все, false — только где нет USLT
overwrite_existing = false
# Задержка между запросами к API (в секундах)
delay_seconds = 1.5
# Провайдеры через запятую (lrclib, netease, megalobiz, musixmatch)
providers = lrclib, netease, megalobiz, musixmatch
Сложим это «счастье» рядышком, и запустим компиллятор.
Программа просканирует папку; для каждого файла mp3 по названию трека и артисту сделает запрос на серверы и, при получении данных, запишет их в поле USLT исходного mp3 файла.
Откуда плеер их потом благополучно и считает.
У меня нет Python-а. Без него LRC пакетом не найти?
Почему же — и без пайтона можно:
Скачайте архив с файлообменника, распакуйте, отредактируйте config.ini под свои нужды и запустите lyricator.exe
Как только выскочит сообщение про то, что программа справилась — нажмите Enter для закрытия окна.
В отличие от клиента lrcget, эта программа пакетно обработает папку с музыкой, и во вложенные папки заглянет, ищет сразу по нескольким провайдерам и не переспрашивает Вас, что ей делать.Пусть она и выглядит неказисто, но работает шустрее ручного поиска в разы.
Делал для себя, делюсь безвозмездно, претензии по порче имущества не принимаю. Если опасаетесь сразу всю медиатеку пакетом сканировать — правильно делаете.
Для начала скопируйте несколько файлов во временную папку, «натравите» на неё скрипт и посмотрите, что получится. Так и нервы убережёте, и конфигурацию настраивать научитесь.
В общем, пробуйте, дерзайте и… пойте на здоровье.
P. S. Да, и напишите в комментариях, стоит ли поделиться с Вами редактором, который из простого текста синхронизированный в несколько кликов делает?










