Взлом YouTube для быстрого скачивания видео

в 9:00, , рубрики: drm, ruvds_статьи, YouTube, yt-dl, yt-dlp, ytdl-sub, альтернативные клиенты, скачивание файлов, скрапинг
Взлом YouTube для быстрого скачивания видео - 1Мы неоднократно рассказывали о специальном софте, который позволяет скачивать видео с YouTube. Это в первую очередь youtube-dl (yt-dl) и его клоны, такие как yt-dlp и yt-dlc. С ними воюют могучие копирасты, пытаются удалить их из поисковой выдачи, с хостингов, с Github и так далее, по известной схеме «тотальная война».

Но главным врагом для этих программ остаются даже не правообладатели, а злобная корпорация Google, которая постоянно вносит изменения в YouTube API, чтобы прекратить злоупотребления сервисом помешать людям скачивать файлы, ведь от этого Google никакой прибыли.

Сами программки — просто технические инструменты для скачивания общедоступного контента, они ничего не воруют и не пиратят. Просто доступ происходит нестандартным способом, который не предусмотрен официально. Посмотрим, как это делается.

Исходный код yt-dl и самого популярного клона yt-dlp написаны на Python и опубликованы на Github. Все инструменты используют стандартный интерфейс YouTube API, который постоянно меняется для борьбы как раз с этими сервисами.

Ниже приведены примеры обращения к YouTube API из командной строки. Примерно такие же «хакерские» способы используются в yt-dl и yt-dlp.

▍ Как получить URL файла для скачивания

У каждого видео YouTube есть уникальный идентификатор, который отображается в командной строке. Например, взять лекцию по введению в нейросети с популярного стэнфордского курса CS229 («Машинное обучение»):

https://www.youtube.com/watch?v=MfIjxPh6Pys

Здесь это MfIjxPh6Pys.

Чтобы из консоли получить ссылку на видеофайл для встроенного плеера, обращаемся к YouTube API установкой правильного контекста, который проверяется API. В данном случае проверяется отправитель /youtubei/v1/player. Под Linux с установленным веб-сервером http установка контекста, запрос URL и получение результата выглядит таким образом:

$ echo -n '{"videoId":"MfIjxPh6Pys","context":{"client":{"clientName":"WEB","clientVersion":"2.20230810.05.00"}}}' | 
  http post 'https://www.youtube.com/youtubei/v1/player' |
  jq -r '.streamingData.adaptiveFormats[0].url'

Получаем результат:

https://rr1---sn-cxauxaxjvh-hn9ee.googlevideo.com/videoplayback?expire=1696595317&ei=FakfZfXlEtGlv_IPxNaV2AU&ip=37.214.30.90&id=o-AMiXDDvzOJHVfbTdoHu7xOdk9fvOq9m84I3Wzk753Yu5&itag=137&aitags=133%2C134%2C135%2C136%2C137%2C160%2C242%2C243%2C244%2C247%2C248%2C278&source=youtube&requiressl=yes&mh=Rz&mm=31%2C26&mn=sn-cxauxaxjvh-hn9ee%2Csn-4g5edndd&ms=au%2Conr&mv=m&mvi=1&pl=23&initcwndbps=1002500&spc=UWF9fzcbe7KrcWzhYWx5WsJlt_lA6NVDpNGO3Cm8sw&vprv=1&svpuc=1&mime=video%2Fmp4&ns=P1X5iZALtEvoq7t3wbLWUZ4P&gir=yes&clen=948670888&dur=4813.466&lmt=1587563951213289&mt=1696573267&fvip=4&keepalive=yes&fexp=24007246&beids=24350018&c=WEB&txp=5316222&n=eCX-4hI1kOARp7xm&sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cspc%2Cvprv%2Csvpuc%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt&sig=AGM4YrMwRAIgT-QJYv9PrBilbApceUYThL_ioh_3bimU0gGWaMXw2HACIAZMSJ8EB_eiu_TJDm77Z0PQUFRuKSJlqX-RDXsQzxoK&lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps&lsig=AK1ks_kwRAIgSFJb5d1DNHo8RRoocYbq_YW_QDeKOp_BVnEfD3sHNoMCIB5lV1AM1hiZ75rOHfnY5DRk71AVcE-K3Bo2kMxi6_6x

Взлом YouTube для быстрого скачивания видео - 2

Адрес можно также взять с помощью Developer Tools на открытой веб-странице.

Это и есть искомый URL. Формат его следующий:

Protocol: https
Hostname: rr1---sn-cxauxaxjvh-hn9ee.googlevideo.com
Path name: /videoplayback
Query Parameters:
  expire: 1696595317
  ei: FakfZfXlEtGlv_IPxNaV2AU
  ip: 37.214.30.90
  id: o-AMiXDDvzOJHVfbTdoHu7xOdk9fvOq9m84I3Wzk753Yu5
  itag: 137
  aitags: 133,134,135,136,137,160,242,243,244,247,248,278
  source: youtube
  requiressl: yes
  mh: Rz
  mm: 31,26
  mn: sn-cxauxaxjvh-hn9ee/Csn-4g5edndd
  ms: au
  mv: m
  mvi: 1
  pl: 23
  initcwndbps: 1002500
  spc: UWF9fzcbe7KrcWzhYWx5WsJlt_lA6NVDpNGO3Cm8sw
  vprv: 1
  svpuc: 1
  mime: video/mp4
  ns: P1X5iZALtEvoq7t3wbLWUZ4P
  gir: yes
  clen: 948670888
  dur: 4813.466
  lmt: 1587563951213289
  mt: 1696573267
  fvip: 4
  keepalive: yes
  fexp: 24007246
  beids: 24350018
  c: WEB
  txp: 5316222
  n: eCX-4hI1kOARp7xm
  sparams: expire,ei,ip,id,aitags,source,requiressl,spc,vprv,svpuc,mime,ns,gir,clen,dur,lmt
  sig: AGM4YrMwRAIgT-QJYv9PrBilbApceUYThL_ioh_3bimU0gGWaMXw2HACIAZMSJ8EB_eiu_TJDm77Z0PQUFRuKSJlqX-RDXsQzxoK
  lsparams: mh,mm,mn,ms,mv,mvi,pl,initcwndbps
  lsig: AK1ks_kwRAIgSFJb5d1DNHo8RRoocYbq_YW_QDeKOp_BVnEfD3sHNoMCIB5lV1AM1hiZ75rOHfnY5DRk71AVcE-K3Bo2kMxi6_6x

Некоторые из этих параметров YouTube API описаны в документации YouTube yt-dlp, но нам они вроде бы не нужны. Казалось бы, берём URL и скачиваем, у нас есть валидный адрес. Но не всё так просто.

▍ Ограничение скорости

Дело в том, что YouTube (Google) всячески мешает альтернативным клиентам скачивать видеофайлы на максимальной скорости, расценивая это как нелегальное использование своих ресурсов. Для этого реализовано специальное ограничение скорости.

Например, попробуем скачать вышеуказанный URL без преобразования. Из консоли:

$ http --print=b --download 'https://rr1---sn-cxauxaxjvh-hn9ee.googlevideo.com/videoplayback?expire=1696595317&ei=FakfZ[и т. д.]'

И мы увидим, что скорость скачивания никогда не превышает 30−50 КБ/с:

Взлом YouTube для быстрого скачивания видео - 3

Но на такой скорости наша лекция продолжительностью 1 ч 20 мин будет скачиваться примерно пять с половиной часов. Налицо явное несоответствие.

Похоже, в YouTube заложен лимит на скорость скачивания видео при запросе через API, а браузер обходит это ограничение. И наши любимые программки вроде yt-dlp тоже его обходят. Каким образом? Всё очень просто: оказывается, наш URL «ненастоящий», обманка, а для скачивания на нормальной скорости его требуется немножко изменить.

▍ Взлом защиты

Как объясняет хакер 0x7D0 (вероятно, он входит в число контрибьюторов yt-dlp и/или NewPipe), в 2021 году в YouTube API появилась поддержка специального параметра n в вышеуказанном URL (n: eCX-4hI1kOARp7xm). Значение параметра должно быть преобразовано с помощью JS-алгоритма, приведённого в файле base.js на веб-странице со встроенным плеером (мы же указали контекст, что выдаём себя за YouTube-плеер).

Параметр представляет собой своего рода DRM, то есть проверку валидности сайта, на котором установлен плеер. Если URL не проходит проверку, то YouTube обрезает скорость скачивания, как в нашем примере.

Так что для скачивания файлов с YouTube на нормальной скорости без файла base.js не обойтись. Поскольку вышеупомянутый JS-алгоритм (DRM) обфусцирован и часто меняется, файл нужно постоянно скачивать с сайта YouTube или обновлять его в своей локальной версии, когда на сервере меняется проверка. Как вариант, 0x7D0 публикует JS-код для автоматического скачивания файла base.js, извлечения кода алгоритма, передачи ему параметра n и вычисления результата. Такой скрипт нужно запускать каждый раз, чтобы получить новый URL и скачать файл на нормальной скорости.

Скрипт для вычисления n

import axios from 'axios';
import vm from 'vm'

const videoId = 'aqz-KE-bpKQ';

/**
 * From the Youtube API, retrieve metadata about the video (title, video format and audio format)
 */
async function retrieveMetadata(videoId) {
    const response = await axios.post('https://www.youtube.com/youtubei/v1/player', {
        "videoId": videoId,
        "context": {
            "client": { "clientName": "WEB", "clientVersion": "2.20230810.05.00" }
        }
    });

    const formats = response.data.streamingData.adaptiveFormats;

    return [
        response.data.videoDetails.title,
        formats.filter(w => w.mimeType.startsWith("video/webm"))[0], 
        formats.filter(w => w.mimeType.startsWith("audio/webm"))[0],
    ];
}

/**
 * From the Youtube Web Page, retrieve the challenge algorithm for the n query parameter
 */
async function retrieveChallenge(video_id){

    /**
     * Find the URL of the javascript file for the current player version
     */
    async function retrieve_player_url(video_id) {
        let response = await axios.get('https://www.youtube.com/embed/' + video_id);
        let player_hash = //s/player/(w+)/player_ias.vflset/w+/base.js/.exec(response.data)[1]
        return `https://www.youtube.com/s/player/${player_hash}/player_ias.vflset/en_US/base.js`
    }

    const player_url = await retrieve_player_url(video_id);

    const response = await axios.get(player_url);
    let challenge_name = /.get("n"))&&(b=([a-zA-Z0-9$]+)(?:[(d+)])?([a-zA-Z0-9])/.exec(response.data)[1];
    challenge_name = new RegExp(`var ${challenge_name}\s*=\s*\[(.+?)\]\s*[,;]`).exec(response.data)[1];

    const challenge = new RegExp(`${challenge_name}\s*=\s*function\s*\(([\w$]+)\)\s*{(.+?}\s*return\ [\w$]+.join\(""\))};`, "s").exec(response.data)[2];

    return challenge;
}

/**
 * Solve the challenge and replace the n query parameter from the url
 */
function solveChallenge(challenge, formatUrl) {
    const url = new URL(formatUrl);

    const n = url.searchParams.get("n");
    const n_transformed = vm.runInNewContext(`((a) => {${challenge}})('${n}')`);

    url.searchParams.set("n", n_transformed);
    return url.toString();
}


const [title, video, audio] = await retrieveMetadata(videoId);
const challenge = await retrieveChallenge(videoId);

video.url = solveChallenge(challenge, video.url);
audio.url = solveChallenge(challenge, audio.url);

console.log(video.url);

Примерно так же делает yt-dlp. Для решения этой задачи в программу встроен маленький интерпретатор JavaScript.

После вычисления мы получаем новый «быстрый» URL. Интересно, что даже для него действуют ограничения скорости YouTube, чтобы она не превышала необходимой скорости для файла данного размера и качества, но yt-dlp успешно взламывает и это второе ограничение. Это делается простым добавлением к запросу параметра &range=start-end. Файл разбивается на части, которые скачиваются одновременно. Простой параметр сразу кардинально увеличивает скорость, обычно до максимально возможной для вашего канала.

Более подробно эта тема обсуждается в репозитории yt-dlp (issue 6400). Там обсуждение ведётся по-партизански: участники поясняют, что Google следит и за самим обсуждением, и за обновлениями yt-dlp, и своевременно вносит изменения в работу YouTube API, чтобы противодействовать методам обхода ограничений.

Потом скачанные фрагменты склеиваем в единое целое, при необходимости объединяя видео и звук. Вот почему yt-dlp не работает без установленного ffmpeg (универсальная консольная утилита для обработки медиафайлов, см. «Безграничные возможности FFmpeg на примерах»). В документации FFmpeg написано, как склеить фрагменты одной командой.

Проделав все эти шаги, мы можем скачивать видео с YouTube на высокой скорости даже без использования yt-dlp и др.

▍ Три года гонений

Как уже было сказано, юристы правообладателей пытаются всячески помешать работе альтернативных клиентов для YouTube. Хуже всего, когда изымаются домены, потому что тогда программа не может нормально обновиться или ломается какая-нибудь функциональность типа сообщений о багах. Остаётся только репозиторий на Github, который принадлежит Microsoft, так что хакерский софт там на птичьих правах.

Представим, что YouTube поменял API, в результате yt-dl скатился до зарезанной скорости. Пользователь идёт на официальный сайт за новой версией — и видит там такую картину:

Взлом YouTube для быстрого скачивания видео - 4
Конфискованный домен yt-dl.org. Он уже давно в таком состоянии

Юридическая атака на youtube-dl началась ровно три года назад, когда RIAA прислала DMCA-запрос на удаление репозитория на Github (его сначала удалили, а потом восстановили). И с тех пор youtube-dl прессуют на всех фронтах.

В данный момент битву против youtube-dl правообладатели могут при желании считать выигранной, они могут составить победные отчёты и получить заслуженные бонусы: количество пользователей и интенсивность разработки проекта упали. Но они не понимают, что всё это произошло, потому что вышел более продвинутый форк yt-dlp, который хостится на Github с множеством собственных форков. С 1200-ми контрибуторами он настолько активно поддерживается, что даже оригинальный yt-dl бэкпортирует у него большинство своих исправлений. Другими словами, этот форк уже обогнал по популярности своего родителя (там 790 контрибуторов). Возможно, такое развитие событий напрямую связано с гонениями на оригинальную версию.

Взлом YouTube для быстрого скачивания видео - 5
Сравнение коммитов в мастер yt-dl (вверху) и yt-dlp (внизу)

Как уже было сказано, исходный код youtube-dl и yt-dlp написан на Python. Поэтому программы работают в питоновской среде даже без компиляции. Они также распространяются через родной питоновский менеджер пакетов pip. Хотя проще всего взять бинарники из официального репозитория на том же GitHub и использовать их без всяких зависимостей.

▍ Дежа вю

Эта война копирастов из YouTube (Google) с якобы «пиратским» софтом выглядит довольно забавно, ведь на заре своего существования сам YouTube считался абсолютно пиратским. Как и российский стартап ВКонтакте, сразу после запуска он быстро набрал популярность за счёт того, что пользователи могли безнаказанно публиковать там нелицензионный контент: фильмы, сериалы, мультики и пр. И они там долго висели.

Например, в 2006 году редакторы Википедии обсуждали предложение удалить из энциклопедии все ссылки на YouTube, поскольку «львиная доля контента там является пиратским». Тогда YouTube особо не спешил бороться с этим явлением. Но спустя двадцать лет уже сам вошёл в число рьяных копирастов. Ничего не поделаешь, это капитализм. Ну а с ВК вообще всё понятно, сейчас нелицензионный контент в РФ почти официально разрешён. Говорят, в прошлом году даже в кинотеатрах крутили пиратки (только без рекламы 1xbet), может и сейчас где-то крутят, никто за это уже не будет особо карать.

▍ Выводы

В случае с нынешней историей основной вывод таков: программы для скачивания на самом деле реализуют базовые функции YouTube API, просто в таком виде, какой не нравится правообладателям. Но те ничего не могут поделать, потому что код программ открыт и способ реализации понятен, так что такие инструменты может сделать каждый и использовать их никто не запретит.

Наличие кучи «сервисов по загрузке видео» доказывает, что реализовать такой функционал довольно легко. Правда, есть нюансы, главный из которых — ограничение на скорость скачивания. Но всё можно решить. И в принципе можно спокойно обойтись даже без специальных инструментов вроде yt-dlp (но только с ограничением по скорости). Вот чтобы быстро скачивать без посторонних инструментов, придётся немножко заморочиться. В целом же, скачивать файлы с YouTube можно и самостоятельно.

Автор: Анатолий Ализар

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js