Всем привет! Пишу впервые, хоть и читаю Хабр с 2012 года. Буду рад любой обратной связи. Поехали.

Предыстория

VK — хранилище интересного мне видеоконтента. Относительно недавно просмотр видео на бюджетных устройствах (особенно в приложении) стал невозможен по ряду причин:

  • Постоянные ошибки

  • Низкая частота кадров при качестве 720p и выше

  • Графические артефакты при качестве 480p и ниже

Я решил воспользоваться безотказным методом — скачивать видео и смотреть их локально. Однако, найти ссылки на mp4 файлы в исходниках desktop-версии VK мне не удалось. Несколько лет назад — удавалось. Видимо теперь всё работает через более хитрый стриминг. Возможно по этой же причине более не работают сторонние сервисы для скачивания видео.

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

Реализация

Для начала, создадим manifest.json — файл, содержащий информацию о расширении. Помимо обязательных полей, добавим в него поле content_scripts, содержащее следующее правило: при переходе на любую страницу на домене m.vk.com и полной загрузке DOM исполнить код, находящийся в файле content-script.js. Такой код называется встраиваемым скриптом. Манифест может содержать огромное количество разнообразных полей. Подробная документация по этому поводу — здесь.

// manifest.json

{
  "manifest_version": 3,
  "name": "VK Video Downloader",
  "version": "1.0.0",
  "action": {},
  "content_scripts": [
    {
      "js": ["content-script.js"],
      "matches": ["https://m.vk.com/*"],
      "run_at": "document_idle"
    }
  ]
}
// content-script.js

alert('Hello, World!');
Загружаем расширение в браузер. Переходим по нужному URL. Довольствуемся результатом.
Загружаем расширение в браузер. Переходим по нужному URL. Довольствуемся результатом.

Перейдя на страницу любого видео и взглянув на HTML-дерево, мы увидим примерно следующее:

<div class="VideoPage__video">
  <video class="vv_inline_video">
    <source src="https://vkvd97.mycdn.me/video.m3u8?..." type="application/vnd.apple.mpegurl">
    <source src="https://vkvd97.mycdn.me/...type=1&amp;..." type="video/mp4">
    <source src="https://vkvd97.mycdn.me/...type=0&amp;..." type="video/mp4">
    <source src="https://vkvd97.mycdn.me/...type=4&amp;..." type="video/mp4">
  </video>
</div>

Нас интересуют значения аттрибутов src тегов <source>, отвечающих за mp4 файлы. И я не просто так отметил url-параметр type в каждом из них. Именно он ответственен за качество видео. Единожды скачав все возможные вариации одного видео, я вывел таблицу соответствия:

Значение параметра type

Качество видео

0

240p

1

360p

2

480p

3

720p

4

144p

5

1080p

6

1440p

7

2160p

Да, 144p выбивается из общей логики и имеет тип 4. Возможно отголоски какого-то легаси, а возможно я просто чего-то не знаю и кто-нибудь просветит меня в комментариях :)

Реализуем функцию, собирающую и возвращающую нужные нам данные в удобном для дальнейшей обработки формате:

// content-script.js

function getVideoSources() {
  const sourceTags = document.querySelectorAll(
    'video source[type="video/mp4"]'
  );
  let videoSources = {};

  for (const tag of sourceTags) {
    if (tag.src.includes('&type=4')) {
      videoSources['144p'] = tag.src;
    } else if (tag.src.includes('&type=0')) {
      videoSources['240p'] = tag.src;
    } else if (tag.src.includes('&type=1')) {
      videoSources['360p'] = tag.src;
    } else if (tag.src.includes('&type=2')) {
      videoSources['480p'] = tag.src;
    } else if (tag.src.includes('&type=3')) {
      videoSources['720p'] = tag.src;
    } else if (tag.src.includes('&type=5')) {
      videoSources['1080p'] = tag.src;
    } else if (tag.src.includes('&type=6')) {
      videoSources['1440p'] = tag.src;
    } else if (tag.src.includes('&type=7')) {
      videoSources['2160p'] = tag.src;
    }
  }

  return videoSources;
}

console.log(getVideoSources());

Теперь, перезагрузив расширение и открыв страницу любого видео, мы увидим в консоли браузера нечто подобное (количество полей будет варьироваться в зависимости от максимального качества видео):

{
  "144p": "https://vkvd97.mycdn.me/...type=4&amp;...",
  "240p": "https://vkvd97.mycdn.me/...type=0&amp;...",
  "360p": "https://vkvd97.mycdn.me/...type=1&amp;..."
}

Казалось бы, задача практически решена, однако именно на этом этапе разработки, я заметил нюанс, заставивший меня поломать голову.

При вводе URL страницы с видео в адресную строку и нажатии Enter скрипт срабатывал. При переходе на страницу с другой страницы VK — не срабатывал, однако стоило её перезагрузить — он всё же срабатывал.

Насколько мне удалось выяснить, дело в том, что при навигации по VK далеко не всегда происходит полноценный переход на новую страницу. Во многих случаях просто перестраивается DOM, что не является условием срабатывания встраиваемого скрипта.

Придётся действовать иначе. Будем отслеживать изменения DOM в реальном времени и извлекать данные лишь в том случае, если находимся на странице видео (путь начинается с /video-) и заметили появление в документе контейнера видеоплеера (имеет класс VideoPage__video). Поможет нам в этом такая замечательная вещь, как MutationObserver. Также, стоит учитывать то, что отрисоваться контейнер может далеко не сразу. Поэтому, при переходе на страницу с видео, будем искать его каждые 100 мс, пока не найдём.

// content-script.js

let lastUrl = location.href;

new MutationObserver(() => {
  if (location.href !== lastUrl) {
    lastUrl = location.href;
  }
  if (location.pathname.includes('/video-')) {
    const checker = setInterval(() => {
      if (
        document.querySelector('div.VideoPage__video')
      ) {
        clearInterval(checker);
        getVideoSources();
      }
    }, 100);
  }
}).observe(document, { subtree: true, childList: true });

function getVideoSources() {...}

Замечательно. Теперь, стоит плееру появиться на странице (неважно каким образом вы на эту страницу попали), в консоль выводятся нужные нам данные. Давайте реализуем функционал для отображения их непосредственно на странице, немного подогнав под вёрстку VK:

// content-script.js

function createDownloadPanel(videoSources) {
  const label = document.createElement('span');
  label.innerText = 'Скачать:';
  label.style.marginRight = '2px';

  const panel = document.createElement('div');
  panel.id = 'vkVideoDownloaderPanel';
  panel.style.margin = '8px 12px';
  panel.appendChild(label);

  for (const [quality, url] of Object.entries(videoSources)) {
    const aTag = document.createElement('a');
    aTag.href = url;
    aTag.innerText = quality;
    aTag.style.margin = '0 2px';
    panel.appendChild(aTag);
  }

  return panel;
}

function showPanel(panel) {
  document.querySelector('div.VideoPage__video').after(panel);
}
Совместив весь код выше получаем такой вот результат
Совместив весь код выше получаем такой вот результат

Чуть позже я немного доработал расширение:

  • Добавил обработку видео, встроенных со сторонних сайтов (в контейнере плеера в таком случае находится не <video>, а <iframe>)

  • Исправил баги, из-за которых, функция отвечающая за отрисовку панели для скачивания n-ое количество раз отрабатывала вхолостую, ничего не отрисовывая

  • Добавил иконки

  • Дополнил manifest.json

Взглянуть на финальную версию и воспользоваться ей можно перейдя на мой GitHub. Уже получил несколько благодарностей на почту за публикацию этого расширения. Может будет полезно и вам.

Спасибо, что дочитали до конца.

Комментарии (25)


  1. VADemon
    00.00.0000 00:00
    +5

    Это бы время да на наисания модуля для youtube-dl (yt-dlp) Который, кажется, и так уже нормально работает :) И для картинок еще gallery-dl. Хотя для обычного пользователя будет проще расширение, чтобы не заниматься экспортом кук.


    1. habraabr
      00.00.0000 00:00
      +1

      У меня вот кстати youtube-dl/yt-dlp криво качает с мейлрушных ресурсов (VK/OK/Video Mail.ru). Файлы вечно битые, чего нет если качать условно с Reddit/Youtube итп.

      Модуль формально-то есть, но он кривой. И тут вы правы что руки нужны в проекте, который уже дефакто стандарт (или попросту швейцарский нож для скачивания видео).


  1. chernish2
    00.00.0000 00:00

    Классная статья, спасибо, поставил плюс.

    А как Вы из мобильного приложения ссылки вытаскивали?


    1. JustKappaMan Автор
      00.00.0000 00:00
      +1

      Спасибо. Про приложение речь в статье идёт лишь в том контексте, что оно превратилось в раздутое нечто, в котором обычный просмотр видео на устройстве трёхлетней давности - пытка :) Все ссылки вытаскиваются из веб-версии ВК.


  1. hurtavy
    00.00.0000 00:00

    вот если бы расширение само вытаскивало ссылки из мобильной версии при открытии обычной... :)


    1. JustKappaMan Автор
      00.00.0000 00:00

      Вроде как можно реализовать такое. Но тогда, так как встраиваемый скрипт работает в так называемом изолированном окружении, нужно запариваться с инъекцией, чтобы получить доступ к существующим на момент загрузки страницы JS-объектам.

      Чисто для практики сделал такое же расширение для Одноклассников, вот оно работает даже на десктоп-версии :D Так как там вся информация о плеере, включая ссылки на все исходники, хранится одной длиннющей строкой в одном атрибуте тега.


  1. Nyptus
    00.00.0000 00:00
    +1

    Буквально вчера писал тг-бота для скачивания видео с вк, работает, но наградил дичи со скачиванием чанков и сшиванием. Оказывается можно было решить намного проще! Спасибо за статью! Пойду переписывать бота :)


    1. JustKappaMan Автор
      00.00.0000 00:00
      +4

      Пожалуйста.

      Да, с мобильной веб-версией всё (пока что) действительно проще. Хотя, есть у меня опасения, что скоро и её перелопатят. Ишь чего, в <video> просто лежат ссылки на видео. Не порядок, не хайпово.

      Пользователь должен переходить на страницу, на сервер должен уходить AJAX-запрос с характеристиками устройства. В зависимости от его мощности в кастомный плеер будет чанками стримиться видеопоток. Желательно в зашифрованом виде и расшифровываться на клиенте.

      Вот такое мы "уважаем", это нам "надо" :)


      1. Goron_Dekar
        00.00.0000 00:00

        Кроме того, что такое поведение мешает пользователям скачивать фильмы, оно ещё мешает автоматическим системам анализа контента искать запреёщнку.

        Хочется верить, что этот переход сделан как ответ на введение автомата поиска от РКН.


      1. vadimk91
        00.00.0000 00:00
        +1

        Пару лет назад они таким образом музыку порезали на кусочки, раньше можно было просто скачать, теперь фигвам.


  1. nin-jin
    00.00.0000 00:00

    А что за артефакты на низком разрешении? Если речь о чём-то таком, то это похоже в Хроме что-то сломали. На Ютубе наблюдаю такое при переключении вкладок.


    1. JustKappaMan Автор
      00.00.0000 00:00
      +1

      Не, не о таком. О пикселизации, обрезании части картинки и зелёных полосах по краям видео. Но это касается лишь плеера в мобильном приложении.


    1. Astus
      00.00.0000 00:00

      Было такое (или очень похожее) с гифками/видео в Хроме, лечилось то ли отключением, то ли включением «аппаратного ускорения».


  1. imjustwatching
    00.00.0000 00:00
    +2

    VideoDownloadHelper для FireFox, уже лет 10 наверное любой самый замаскированный видеопоток тянет, даже когда формат .ts и тому подобные и раздробленные на 10 секундные фрагменты может заджойнить. Но вы конечно тоже большой молодец. Проделали свою отдельную работу чтоб качать видеоролики с интернета для хранения на своем компьютере.


    1. JustKappaMan Автор
      00.00.0000 00:00

      Благодарю! Насчёт VideoDownloadHelper не был в курсе. Обязательно гляну.


  1. pahenor
    00.00.0000 00:00
    +1

    а какую роль выполняет vkvd97 mycdn me? Просто как то страшно пользоваться расширением при авторизованном аккаунте вк, когда твоя инфа может отправляться на сторонний сайт


    1. JustKappaMan Автор
      00.00.0000 00:00

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

      Такая в ВК (и не только там) особенность архитектуры хранения данных: видео, фото, документы - так называемая статика, раскиданы по куче разных доменов. Сайт то огромный. Можете почитать про CDN.

      Для примера - открыл сейчас в новом окне аватарку сообщества Хабр в ВК - она лежит на домене sun3.userapi.com.


      1. kesig
        00.00.0000 00:00

        такой весьма странный домен действительно висит за odnoklassniki.ru



  1. S1re10k_4f99
    00.00.0000 00:00
    +1

    А 4к получается нельзя качать?


    1. JustKappaMan Автор
      00.00.0000 00:00

      Интересный вопрос, сегодня гляну как с этим дело обстоит. Но если честно - не припомню 4К видео в ВК.


    1. JustKappaMan Автор
      00.00.0000 00:00

      Обновил расширение. Теперь можно скачивать видео в QHD и 4K. Совсем забыл что в ВК есть эти форматы. Также обновил таблицу и код в статье. Спасибо за напоминание :)


  1. Klestofer
    00.00.0000 00:00
    +1

    Разрешение activeTab в данном случае не нужно.


    1. JustKappaMan Автор
      00.00.0000 00:00

      Действительно, осталось со времён прототипирования, когда ссылки под видео появлялись после клика по иконке расширения. Обновил: убрал ненужное разрешение и заодно добавил возможность скачивать видео в форматах QHD и 4K. Спасибо за обратную связь!