Узнайте, как с помощью Python и алгоритмов сопоставлять ISRC и ISWC, чтобы находить невостребованные роялти. Реализуйте пайплайн разрешения метаданных прямо сейчас.
В глобальной экономике стриминга Spotify, Apple Music и другие платформы ежедневно обрабатывают миллиарды воспроизведений. За этим слоем транзакций скрывается фрагментированная двойная структура авторских прав:
Реестры ISRC и ISWC управляются разными глобальными организациями (IFPI и CISAC), и единого централизованного сопоставления между ними нет. Из-за этого миллионы долларов механических роялти остаются невостребованными в «чёрных ящиках» организаций коллективного управления (CMO). В этой статье мы спроектируем и реализуем высокопроизводительный протокол семантического разрешения сущностей (SERP) для программного устранения этого разрыва.
Сопоставление записей требует многоуровневого классификационного пайплайна. Поскольку ручное сопоставление логистически невозможно, мы реализуем трёхуровневый алгоритмический подход:
┌────────────────────────┐
│ Загрузка записей и │
│ композиций │
└───────────┬────────────┘
│
▼
┌────────────────────────┐
│ 1. Нормализованный │ ──[Сходство < 0.85]──> [Очередь несовпадений]
│ фильтр расстояния │
└───────────┬────────────┘
│ [Сходство >= 0.85]
▼
┌────────────────────────┐
│ 2. Пересечение │ ──[Нет пересечения]──> [Очередь несовпадений]
│ создателей │
└───────────┬────────────┘
│ [Пересечение >= 1]
▼
┌────────────────────────┐
│ 3. Проверка допуска │ ──[Дельта > 4с]──> [Ручная верификация]
│ длительности │
└───────────┬────────────┘
│ [Дельта <= 4с]
▼
┌────────────────────────┐
│ Верифицированная │
│ связь для CMO │
└────────────────────────┘Сравнение названий часто даёт сбои из-за различий в пунктуации, подзаголовках или регистре. Мы нормализуем текст (приводим к нижнему регистру и удаляем неалфавитно-цифровые символы) и выполняем сравнение расстояния Левенштейна, нечувствительного к пунктуации:
Sim(s1, s2) = 1 − [ Levenshtein(s1, s2) / max(|s1|, |s2|) ]
Если показатель сходства превышает 0.85, запись переходит к следующему этапу.
Чтобы избежать сопоставления кавер-версий или семплов с похожими названиями, мы строим матрицу пересечения исполнителей записи и зарегистрированных авторов. Если пересечение создателей больше или равно 1, запись проходит дальше.
Чтобы отличить оригинальные миксы от радио-редакций или акустических вариантов (которые меняют доли и отчисления), мы применяем строгую проверку длительности. Длина аудиозаписи должна совпадать с длительностью композиции в реестре с допуском ±4 секунды:
|D_ISRC − D_ISWC| ≤ 4 секунды
Ниже приведена эталонная реализация с использованием высокопроизводительной библиотеки Polars для выполнения пайплайна над большими наборами данных:
import polars as pl
def resolve_metadata_gap(recordings_df: pl.DataFrame, compositions_df: pl.DataFrame) -> pl.DataFrame:
# 1. Очистка и нормализация идентификаторов (удаление дефисов, точек, пробелов)
cleaned_rec = recordings_df.with_columns([
pl.col("isrc").str.replace_all(r"[\s-]", "").str.to_uppercase(),
pl.col("track_title").str.to_lowercase().str.replace_all(r"[^\w\s]", "").str.strip_chars()
])
cleaned_comp = compositions_df.with_columns([
pl.col("iswc").str.replace_all(r"[\s.-]", "").str.to_uppercase(),
pl.col("work_title").str.to_lowercase().str.replace_all(r"[^\w\s]", "").str.strip_chars()
])
# 2. Соединение наборов данных по нормализованным названиям для сокращения пространства поиска
joined = cleaned_rec.join(cleaned_comp, left_on="track_title", right_on="work_title", how="inner")
# 3. Применение проверки допуска длительности (<= 4 секунды)
matched_candidates = joined.filter(
(pl.col("duration_sec_rec") - pl.col("duration_sec_comp")).abs() <= 4
)
# 4. Проверка пересечения создателей: хотя бы один автор присутствует в исполнителе
matched_candidates = matched_candidates.filter(
pl.struct(["artist_name", "songwriters"]).map_batches(
lambda s: pl.Series([
any(writer.lower() in row["artist_name"].lower() for writer in row["songwriters"])
for row in s.to_list()
])
)
)
return matched_candidates.select(["isrc", "iswc", "track_title", "artist_name", "duration_sec_rec", "duration_sec_comp"])
# Пример тестовых данных
recordings = pl.DataFrame({
"isrc": ["US-RC1-23-00001", "GB-AHT-24-99882"],
"track_title": ["Starlight (Midnight Edit)", "Hold Me Back"],
"artist_name": ["Jane Doe & The Band", "John Smith"],
"duration_sec_rec": [242, 180]
})
compositions = pl.DataFrame({
"iswc": ["T-123.456.789-1", "T-987.654.321-0"],
"work_title": ["Starlight", "Hold Me Back (Live Version)"],
"songwriters": [["Jane Doe", "Alex Wright"], ["John Smith"]],
"duration_sec_comp": [240, 310] # Второй трек не проходит проверку длительности
})
resolution = resolve_metadata_gap(recordings, compositions)
print("Успешно сопоставленные метаданные:")
print(resolution)При работе с каталогами, содержащими миллионы строк данных стриминга и отчётов о выплатах, встроенная строковая обработка медленна. В производственных системах загружают кэши Arrow и используют оркестраторы с состоянием (например, LangGraph и векторные базы данных) для обработки семантических краевых случаев.
Практический вывод: прямо сейчас возьмите приведённый код и адаптируйте его под свои данные: замените тестовые DataFrame на реальные таблицы ISRC и ISWC. Начните с одного альбома, затем масштабируйте.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →