все заметки
[ru / en]

Как в браузере скачать mp4 из YouTube

2026.02.07

Копаемся в коде YouTube в попытке получить ссылки на оригинальные mp4.

Для тех, кому побыстрее, то инструкция ниже.

Разбор

Если открывать YouTube на десктопе, то видео грузится частями webm потоком.

На странице ролика (именно ролика) в ytInitialPlayerResponse.streamingData.formats есть массив данных, где есть ссылка на mp4 в низком качестве 360p. Она не работает.

Но используется URL поток из ytInitialPlayerResponse.streamingData.serverAbrStreamingUrl.

И есть список всех доступных форматов (webm, mp4) в ytInitialPlayerResponse.streamingData.adaptiveFormats, но URL там нет.

А все ссылки в adaptiveFormats появляются как только зайти с мобильного на страницу ролика. Кто не знает, то в DevTools есть возможность эмулировать разные устройства (менять размер экрана и подсовывать другой UserAgent).

В старых видео url находится в поле url, а вот относительно новые уже находятся в поле signatureCipher которое имеет вид (соединено амперсандом):

s=EJE4JURJJKKLHFM75uutnjuyuyu8899oiooyioyGy-iyoAc6t6IIYOYOyoo...\nsp=sig\nurl=https://rr3.googlevideo.com/videoplayback?expire=...
  • s - подпись для url
  • sp - в нем название параметра подписи, то есть sig
  • url - URL

Добавление &sig=<s> к url не помогает. Потому что там есть некоторая манипуляция в подписи:

  • меняется местами пара символов
  • вырезается пара символов
  • reverse строки

Так как разработчики могут одним пальцем поменять метод, то нет смысла использовать эти алгоритмы. Не долго будет жить. Проверено во время написания этой заметки.

Основная мысль, просто отсеять все webm, оставить mp4, и подсунуть плееру.

Но все таки хочется меньше движений, эти переключения в десктоп/мобильный режим - утомляют. Что же делать?

Если попробовать поделиться видео, то открывает iframe с url https://www.youtube.com/embed/<video_id>. А там происходит POST запрос на https://www.youtube.com/youtubei/v1/player, в ответе которого есть все ссылки на всё.

Это самый нужный набор данных которые нужны YouTube, чтобы выдать данные о видео:

{\n\tvideoId: <video_id>,\n\tcontext: {\n\t\tclient: <client_info>,\n\t\tthirdParty: {\n\t\t\t"embedUrl": "https://www.youtube.com/watch?v=" + <video_id>,\n\t\t},\n\t},\n\tplaybackContext: {\n\t\tcontentPlaybackContext: {\n\t\t\tsignatureTimestamp: 20486\n\t\t}\n\t}\n}
  • video_id - id видео, которое в параметре v в url. логично, да.
  • client_info - данные о клиенте (о нас), находятся в ytcfg.data_.INNERTUBE_CONTEXT.client

Делаем запрос, а там нет ссылок. Чудеса. Просто нужно поменять в client_info.clientName на WEB_EMBEDDED_PLAYER, там изначально WEB.

Ссылки есть. Теперь осталось подсовывать плееру только mp4.

Сначала фильтруем:

if (e.mimeType.startsWith('video/mp4')) {\n\tmp4.push(e);\n}

Обновляем конфиг плеера

window.ytplayer.config = {args: {raw_player_response: <new_video_info>}};

Обнуляем область плеера #ytd-player > #container

player.innerHTML = '';

Плеер с новыми данными создаем с помощью:

createPlayer = window.yt && window.yt.player && window.yt.player.Application \n\t&& (window.yt.player.Application.createAlternate || window.yt.player.Application.create)

Все конфигурации плеера доступны в ytcfg.data_.WEB_PLAYER_CONTEXT_CONFIGS, нужный это WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH:

createPlayer(\n\tplayer,\n\twindow.ytplayer.config,\n\tytcfg.data_.WEB_PLAYER_CONTEXT_CONFIGS.WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH\n);

Инструкция

Внимание При работе плеера с mp4 не работает переключение качества. Для получения каждой ссылки нужно пересоздавать плеер с одним нужным качеством.

  • На странице ролика (в адресе присутствует параметр v) нажимаем ctrl+shift+i - попадаем в консоль DevTools
  • Вставляем данный код в консоль, и в верхнем левом углу плеера появится download со ссылкой на mp4
(function () {\n\tlet video_id = (new URLSearchParams(location.search)).get('v');\n\tif (!video_id)\n\t\tthrow new Error('video_id not found');\n\n\tlet client = ytcfg.data_.INNERTUBE_CONTEXT.client;\n\tclient.clientName = 'WEB_EMBEDDED_PLAYER';\n\n\tlet data = {\n\t\tvideoId: video_id,\n\t\tcontext: {\n\t\t\tclient: ytcfg.data_.INNERTUBE_CONTEXT.client,\n\t\t\tthirdParty: {\n\t\t\t\t'embedUrl': 'https://www.youtube.com/watch?v=' + video_id,\n\t\t\t},\n\t\t},\n\t\tplaybackContext: {\n\t\t\tcontentPlaybackContext: {\n\t\t\t\tsignatureTimestamp: 20486\n\t\t\t}\n\t\t}\n\t};\n\n\tfetch('/youtubei/v1/player', {\n\t\tmethod: 'POST',\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t},\n\t\tbody: JSON.stringify(data)\n\t}).then(function (response) {\n\t\tif (!response.ok)\n\t\t\tthrow new Error(`HTTP error: ${response.status}`);\n\n\t\treturn response.json();\n\t}).then(function (data) {\n\n\t\tlet mp4 = [];\n\t\tdata.streamingData.adaptiveFormats.forEach(e => {\n\t\t\tif (e.mimeType.startsWith('video/mp4')) {\n\t\t\t\tmp4.push(e);\n\t\t\t}\n\t\t});\n\t\tif (!mp4.length)\n\t\t\tthrow new Error('mp4 links not found');\n\n\t\tdata.streamingData.adaptiveFormats = mp4;\n\t\tdata.streamingData.formats = mp4;\n\t\twindow.ytplayer.config = {\n\t\t\targs: {\n\t\t\t\traw_player_response: data\n\t\t\t}\n\t\t};\n\n\t\tlet player = document.querySelector('#ytd-player > #container');\n\t\tplayer.innerText = '';\n\n\t\tlet createPlayer = window.yt && window.yt.player\n\t\t\t&& window.yt.player.Application\n\t\t\t&& (window.yt.player.Application.createAlternate || window.yt.player.Application.create);\n\n\t\tcreatePlayer(\n\t\t\tplayer,\n\t\t\twindow.ytplayer.config,\n\t\t\tytcfg.data_.WEB_PLAYER_CONTEXT_CONFIGS.WEB_PLAYER_CONTEXT_CONFIG_ID_KEVLAR_WATCH\n\t\t);\n\n\t\tlet link = document.createElement('a');\n\t\tlink.setAttribute('target', '_blank');\n\t\tlink.style.cssText = `\n\t\t\tposition: absolute;\n\t\t\tz-index: 1;\n\t\t\tbackground: #fff;\n\t\t\tcolor: #000;\n\t\t`;\n\t\tlink.innerText = 'download';\n\n\t\tlet video = document.querySelector('video');\n\t\tplayer.previousElementSibling && player.previousElementSibling.remove();\n\t\tif (video.getAttribute('src').startsWith('https://')) {\n\t\t\tlink.setAttribute('href', video.getAttribute('src'));\n\t\t\tplayer.before(link);\n\t\t}\n\n\t}).catch(error => {\n\t\tconsole.error('Fetch error:', error.message);\n\t});\n\n})();

Результат

mp4 ссылка в Youtube
еще по темам: как скачать?, javascript