Что вы делаете, когда вам нужно сохранить определенное видео с YouTube, чтобы оно не потерялось? Ну, тут логично. Можно в самом YouTube создать плейлист и добавлять туда все, что нужно. Можно просто добавить страницу в закладки. Да много чего можно сделать. А еще скачать видео себе на жесткий диск. Мало ли чего, всякое бывает. А так под рукой и в сохранности. Вот только способов для скачивания становится все меньше и меньше. Давайте попробуем скачать видео с YouTube с помощью питона. Нет, здесь не будет очередного руководства по pytube. Все интереснее и печальнее.
Не так давно промелькнула новость о том, что проект YouTube Vanced закрывается из-за юридического давления со стороны Google. И вроде бы ладно. Сколько еще таких проектов. Тем более, что для скачивания видео с сервиса я пользовался скриптом на питоне, в котором с помощью библиотеки pytube все благополучно скачивалось. Но, через какое-то время после этой новости pytube вдруг перестал работать. Раз и все. Ладно. Я подумал, что это всего лишь ошибка в моей программе. Сейчас поправлю и все заработает. Но, не тут-то было. Ошибок я особо-то и не нашел. А вот pytube не работал даже на простейших примерах, которые у него описаны на странице загрузки. Я решил, что это временные трудности. На время оставил данный проект в покое. Но, вот спустя почти месяц я снова к нему вернулся. И ничего не изменилось.
Тут, скорее всего, все просто. У YouTube слегка изменился код, а следовательно, и алгоритм поиска ссылок у pytube тоже должен было обновиться. Но, этого не произошло. Покопавшись в интернете, я нашел несколько решений. Нужно заменить регулярные выражения в коде модуля и все должно было заработать. Но, у меня не получилось. Хотя, делал все так как сказано, вплоть до строчек кода. Кстати, в самом коде видно, что алгоритм поиска по регулярным выражениям менялся уже не один раз, так как много строк просто закомментировано. В общем и целом, не знаю, «восстанет ли этот проект из пепла», так сказать. Будем надеяться, что да. Хороший был проект. Полезный.
К чему это я веду. А все просто. Время проходит, библиотека не работает, а автоматизировать скачивание надо. Значит придется искать другие решения. Но, что-то с поиском библиотек у меня не задалось. youtube-dl использовать не хочется. Да и не уверен, что он сейчас работает. Вот его не проверял. Каюсь. Впрочем, я просто нашел свое, несколько «костыльное» решение, но оно работает и довольно неплохо. Именно им я и хочу с вами поделиться. Давайте напишем свой «YouTube Downloader» на Python.
И вот какой Франкенштейн у меня получился в итоге…
Что потребуется?
Для отправки запросов нужно установить библиотеку requests, а также я использовал библиотеку tqdm для добавления в скрипт индикатора загрузки. Все же с ней веселее. Установка стандартная. Пишем в терминале:
После установки импортируем библиотеки в наш скрипт. Так же понадобиться импортировать библиотеку os для проверки и объединения путей к папкам и файлам. Вот блок импорта, который должен получиться в итоге:
Как видите, на этот раз кучи библиотек не потребуется. В основном все будет совершаться с помощью запросов. Ими можно довольно много сделать. Нужно только понять, где и что спрашивать )))
Для начала я, конечно же, полез на сам YouTube. И немного поковырявшись в инструментах разработчика накопал довольно интересный запрос, в котором, в виде JSON прилетала самая разнообразная информация о видео, в том числе и ссылки.
Но, наличие ссылок на видео не дало особого результата. Да, по ним можно было открыть видео и даже его скачать. Вот только скачивание происходило со скоростью черепахи, по чайной ложке в час. Я понял, что это не тот вариант, что нужен. Но вот то, что в данном запросе содержалась информация о видео, это могло пригодиться в будущем. Поэтому я взял его себе на заметку.
Тут надо пояснить, что данного запроса, сколько бы я не ковырял код в браузерах, которые у меня установлены для теста, а значит я не пользуюсь ими постоянно, нет.
Немного подумав и поэкспериментировав, я понял, что в появлении данного интересного запроса «виновно» расширение, которое установлено у меня для кастомизации просмотра видео, а именно: Enhancer for YouTube. Именно оно отправляет этот запрос для каких-то своих «злодейских» целей. Но, тем не менее, скачать видео этот запрос не поможет, а значит надо искать дальше.
Я поступил просто. Ввел запрос в поисковике и стал смотреть, как скачивается видео в популярных загрузчиках с сайтов. Большинство ничего внятного не предоставляли. Но, набрел я на сайт Freemake, тот, что разрабатывает Freemake Video Downloader. Оказывается, на нем тоже можно скачать видео с YouTube. Залез я в запросы и понял, это оно. А походив по ссылкам убедился, что это оно еще больше.
Тогда я скопировал cURL данного запроса с помощью правой кнопки мыши. Там нужно выбрать пункт меню: Copy -> Copy as cURL (bash)». Убедился, что это GET-запрос и пошел на сайт curlconverter.com добывать код из скопированного безобразия. Там все просто. Выбираем тип запроса, get или post, вставляем скопированный запрос и получаем код питона. Ну или одного из тех языков, что представлены на этом сайте.
Скопировал полученный код полностью и стал делать функцию загрузки. Причем, хочу обратить внимание на то, что из данного запроса я не убирал вообще ничего. Для начала, я конечно же, попытался сократить заголовки. И до какого-то момента вроде бы все шло хорошо. Но, это пока я не менял идентификатор видео. Как только это происходило, начиналась ерунда. Очевидно, сервер понимал, что его желают налюбить и в ответ отправлял JSON с совершенно случайными видео, которые к требуемому не имели никакого отношения. Так что, немного подумав, я оставил заголовки полностью. Что же, давайте приступим к написанию кода функции.
Я создал функцию с именем get_video_download(vid_id, channel_name), которая на входе получает идентификатор видео, он содержится в ссылке, после знака «=», и имя канала, которое нужно в данном случае для того, чтобы скачиваемое видео помещать в отдельную папку, а не просто создавать папку типа Video Download и кидать все в кучу. Заголовки я пока опущу. Их можно будет увидеть в полном коде функции. Начнем с того момента, где уже и происходит получение JSON и загрузка видео.
Для начала вывожу принт в терминал, чтобы не было скучно, а после отправляю запрос на получение JSON, в котором параметр vid_id получается из ссылки, которую ввел пользователь для загрузки. Далее, уже в полученном JSON нахожу секцию, где указывается качество видео. Данный сайт позволяет скачивать видео в качестве 720р и 360р, именно mp4. Ниже есть еще пара пунктов, но они относятся к форматам 3gp и mp4a. Если все в порядке, и тэг соответствует, получаю название видео. Как вы видели сами, в названии видео содержится большое количество всяческих символов, которые просто не совместимы с тем, чтобы сохранять в операционной системе. А так, как название видео нужно будет именно для того, чтобы не скачивать его в обезличенном виде, а сохранять с тем названием, что и на сервисе, требуется его очистить от всякого мусора, что я и делаю в цикле, перебирая словарь. В теории, туда можно загнать еще больше символов. Так как я загнал только те, с которыми столкнулся. А кто его знает, что будет в голове у автора, когда он будет давать название. Ну и следом получаю ссылку на загрузку.
Двигаемся дальше. Теперь для того, чтобы сохранить видео, я создаю папку, если ее конечно не существует, с названием канала. Где я беру название канала, чуть позже, так как в этом запросе его нет. Ну и вывожу разные принты, чтобы было не скучно.
А теперь, собственно, основная, самая большая часть функции по загрузке видео. На самом деле, загрузка видео тут в несколько строчек кода. Основная часть отведена на различные проверки и запросы информации, если что-то не так у пользователя, чтобы программа просто не закрывалась без объяснения причин. Отправляю запрос на загрузку. Оказывается, у request есть интересный параметр stream. С его помощью можно переписать поведение загрузки тела ответа, которое по умолчанию загружается сразу же, а при указании параметра делает отсрочку загрузки, пока не будет получен доступ к атрибуту content. Это все нужно для того, чтобы реализовать индикатор загрузки, который здесь представлен библиотекой tqdm. Для начала устанавливаем количество заголовков запроса в переменную total. А далее, по мере загрузки контента и получения заголовков, увеличиваем данный параметр на количество заголовков. И выводим в терминал в удобоваримом виде.
Ну, а если нет видео в качестве 720р, то сообщаем об этом пользователю, спрашиваем, желает ли он загрузить видео в том качестве, что есть. Если да, то загружаем. Если нет, прерываем выполнение функции и выводим первоначальное меню. Ну, а если пользователь ввел совсем не то, то посылаем его в незабываемое путешествие по экзотическим странам (перечеркнуть) сообщаем, что он ввел чушь и прерываем работу скрипта.
Однако, как вы понимаете перед тем, как приступить к загрузке видео, нужно получить название канала. А где его взять, вот в чем вопрос. И я вспомнил о том запросе, который отправляло расширение на самой странице с видео. Тогда я так же скопировал его cURL, получил код и выполнил. В ответ прилетел JSON, в котором была информация о видео, в том числе и название канала. Знаю, это выглядит, как если бы я стрелял из пушки по воробьям. Но использовать BeautifulSoup и искать название канала на странице видео не хотелось. Тем более, что вот так вот, с получением JSON оказалось проще. Поэтому, я отправляю запрос на получение. В запросе, в свою очередь, отправляется JSON с кучей параметров, которые нас в общем-то не интересуют. Здесь нужно поменять только идентификатор видео. Меняется он вот тут:
У меня он обозначен как vid_id и передается в функцию при ее вызове. Таким образом я создал функцию get_channel_name(vid_id), которая на входе получает идентификатор, делает запрос. Выковыривает из него название канала. Чистит от «мусора» в виде символов и возвращает очищенное название туда, откуда вызывалась функция.
Не стоит пугаться ее размерами. Большую часть здесь занимает JSON с параметрами, которые передаются с запросом и заголовками. Но избавиться от них не получилось. Потому, чтобы все работало без сбоев, я оставил их без изменения. И использовал так, как они и отправляются в запросе на странице.
И по сути, большая часть скрипта уже готова. Но, меня не оставляло смутное чувство, что я что-то упустил. И да, немного подумав, я понял, что есть еще ведь плейлисты, которые тоже неплохо бы выкачивать полностью, только лишь введя на них ссылку. Но для того, чтобы их выкачать, для начала со страницы плейлиста нужно получить все ссылки на видео, которые содержаться в нем. Но фишка тут в том, что бывают такие плейлисты, количество видео в которых не влезает на одну страницу. И тогда они просто подгружаются по мере прокрутки. То есть, использованием простого запроса тут не обойдешься. Нужно подключать что-то посерьезнее. Но использовать selenium мне не хотелось. Да, с помощью этой библиотеки можно было бы в безголовом режиме прокрутить страницу до конца, собрать все ссылки на видео, сложить в список и вернуть для дальнейшего использования. Но, я подумал, что должен быть другой способ получения ссылок. И он таки нашелся. Стоило ввести запрос на загрузку плейлиста, как нашелся сайт, который и предоставлял этот функционал. Я покопался в его запросах и нашел один, в котором отправляется ссылка на плейлист, а в ответ прилетает JSON уже с готовыми идентификаторами видео. Ну не красота ли.
Тут все по накатанной. Копируется cURL, получается код, делается запрос и забирается JSON, из которого выкорчевываются идентификаторы, помещаются в список и возвращают его туда, откуда запрос был сделан.
Кстати, здесь, прямо на сайте есть специальное поле, куда помещаются ссылки на загрузку видео из плейлиста уже после выполнения запроса. В самом запросе они «медленные», то есть загрузка по ним происходит со скоростью черепахи. А вот на сайте они уже переформатируются в нужный вид и по ним видео загружается хорошо. Но, чтобы забрать их отсюда нужен опять же selenium, а это снова усложняет скрипт. А так, ссылки интересные. Если понять, как преобразуются исходные ссылки именно в такой вид, то можно и самому их конвертировать. Но, пока что я не стал разбираться. Может быть в будущем.
Создаю функцию playlist_item(url). Здесь в запросе так же передается небольшой словарик с параметрами, одним из которых является ссылка на плейлист. Вот ее и получает данная функция на входе и передает в словарик.
Ну, а больше особо и пояснять нечего. Все довольно просто и понятно. Цикл и перебор.
Что же, теперь пришло время для функции, которая обрабатывает пользовательский ввод. Она у меня получилась размером с простыню. Так как нужно было учесть довольно много параметров.
Создаю функцию get_target_path(user_input), которая на входе принимает первоначальный пользовательский ввод и обрабатывает его в зависимости от того, что ввел пользователь.
Думаю, что подробно на этом останавливаться не стоит, так как тут не используется чего-то сверхъестественного. Всего лишь проверки if, elif, else. Ну и, если пользователь ввел что-то не так, в бесконечном цикле запрос правильного параметра. Тут, конечно, я предусмотрел возможность выхода в основное меню, так как пользователь может не понять, что не так и попросту запутаться. А цикл будет долбить его снова и снова. А потому, лучше предоставить ему, то есть пользователю, небольшую лазейку, возможность сбежать из бесконечного цикла. А обрабатываются здесь запросы, которые представлены на скриншоте ниже.
И остается функция main(), в которой и делается первоначальный выбор и осуществляется пользовательский ввод.
Вот, в принципе и все. Если собрать все эти функции в кучку в одном скрипте, то получиться годный загрузчик видео. Данный код работает на обеих платформах. Как на Windows, так и на Linux. На MacOS не проверял, ибо нет у меня этой «заразы». Но, думаю, что и на ней будет работать. А ниже небольшое видео, которое демонстрирует работу скрипта.
Спасибо за внимание. Надеюсь, что данная информация будет кому-нибудь полезной
Не так давно промелькнула новость о том, что проект YouTube Vanced закрывается из-за юридического давления со стороны Google. И вроде бы ладно. Сколько еще таких проектов. Тем более, что для скачивания видео с сервиса я пользовался скриптом на питоне, в котором с помощью библиотеки pytube все благополучно скачивалось. Но, через какое-то время после этой новости pytube вдруг перестал работать. Раз и все. Ладно. Я подумал, что это всего лишь ошибка в моей программе. Сейчас поправлю и все заработает. Но, не тут-то было. Ошибок я особо-то и не нашел. А вот pytube не работал даже на простейших примерах, которые у него описаны на странице загрузки. Я решил, что это временные трудности. На время оставил данный проект в покое. Но, вот спустя почти месяц я снова к нему вернулся. И ничего не изменилось.
Тут, скорее всего, все просто. У YouTube слегка изменился код, а следовательно, и алгоритм поиска ссылок у pytube тоже должен было обновиться. Но, этого не произошло. Покопавшись в интернете, я нашел несколько решений. Нужно заменить регулярные выражения в коде модуля и все должно было заработать. Но, у меня не получилось. Хотя, делал все так как сказано, вплоть до строчек кода. Кстати, в самом коде видно, что алгоритм поиска по регулярным выражениям менялся уже не один раз, так как много строк просто закомментировано. В общем и целом, не знаю, «восстанет ли этот проект из пепла», так сказать. Будем надеяться, что да. Хороший был проект. Полезный.
К чему это я веду. А все просто. Время проходит, библиотека не работает, а автоматизировать скачивание надо. Значит придется искать другие решения. Но, что-то с поиском библиотек у меня не задалось. youtube-dl использовать не хочется. Да и не уверен, что он сейчас работает. Вот его не проверял. Каюсь. Впрочем, я просто нашел свое, несколько «костыльное» решение, но оно работает и довольно неплохо. Именно им я и хочу с вами поделиться. Давайте напишем свой «YouTube Downloader» на Python.
И вот какой Франкенштейн у меня получился в итоге…
Что потребуется?
Для отправки запросов нужно установить библиотеку requests, а также я использовал библиотеку tqdm для добавления в скрипт индикатора загрузки. Все же с ней веселее. Установка стандартная. Пишем в терминале:
Код:
pip install requests
pip install tqdm
После установки импортируем библиотеки в наш скрипт. Так же понадобиться импортировать библиотеку os для проверки и объединения путей к папкам и файлам. Вот блок импорта, который должен получиться в итоге:
Python:
import os.path
import time
import requests
from tqdm import tqdm
Как видите, на этот раз кучи библиотек не потребуется. В основном все будет совершаться с помощью запросов. Ими можно довольно много сделать. Нужно только понять, где и что спрашивать )))
Для начала я, конечно же, полез на сам YouTube. И немного поковырявшись в инструментах разработчика накопал довольно интересный запрос, в котором, в виде JSON прилетала самая разнообразная информация о видео, в том числе и ссылки.
Но, наличие ссылок на видео не дало особого результата. Да, по ним можно было открыть видео и даже его скачать. Вот только скачивание происходило со скоростью черепахи, по чайной ложке в час. Я понял, что это не тот вариант, что нужен. Но вот то, что в данном запросе содержалась информация о видео, это могло пригодиться в будущем. Поэтому я взял его себе на заметку.
Тут надо пояснить, что данного запроса, сколько бы я не ковырял код в браузерах, которые у меня установлены для теста, а значит я не пользуюсь ими постоянно, нет.
Немного подумав и поэкспериментировав, я понял, что в появлении данного интересного запроса «виновно» расширение, которое установлено у меня для кастомизации просмотра видео, а именно: Enhancer for YouTube. Именно оно отправляет этот запрос для каких-то своих «злодейских» целей. Но, тем не менее, скачать видео этот запрос не поможет, а значит надо искать дальше.
Я поступил просто. Ввел запрос в поисковике и стал смотреть, как скачивается видео в популярных загрузчиках с сайтов. Большинство ничего внятного не предоставляли. Но, набрел я на сайт Freemake, тот, что разрабатывает Freemake Video Downloader. Оказывается, на нем тоже можно скачать видео с YouTube. Залез я в запросы и понял, это оно. А походив по ссылкам убедился, что это оно еще больше.
Тогда я скопировал cURL данного запроса с помощью правой кнопки мыши. Там нужно выбрать пункт меню: Copy -> Copy as cURL (bash)». Убедился, что это GET-запрос и пошел на сайт curlconverter.com добывать код из скопированного безобразия. Там все просто. Выбираем тип запроса, get или post, вставляем скопированный запрос и получаем код питона. Ну или одного из тех языков, что представлены на этом сайте.
Скопировал полученный код полностью и стал делать функцию загрузки. Причем, хочу обратить внимание на то, что из данного запроса я не убирал вообще ничего. Для начала, я конечно же, попытался сократить заголовки. И до какого-то момента вроде бы все шло хорошо. Но, это пока я не менял идентификатор видео. Как только это происходило, начиналась ерунда. Очевидно, сервер понимал, что его желают налюбить и в ответ отправлял JSON с совершенно случайными видео, которые к требуемому не имели никакого отношения. Так что, немного подумав, я оставил заголовки полностью. Что же, давайте приступим к написанию кода функции.
Я создал функцию с именем get_video_download(vid_id, channel_name), которая на входе получает идентификатор видео, он содержится в ссылке, после знака «=», и имя канала, которое нужно в данном случае для того, чтобы скачиваемое видео помещать в отдельную папку, а не просто создавать папку типа Video Download и кидать все в кучу. Заголовки я пока опущу. Их можно будет увидеть в полном коде функции. Начнем с того момента, где уже и происходит получение JSON и загрузка видео.
Для начала вывожу принт в терминал, чтобы не было скучно, а после отправляю запрос на получение JSON, в котором параметр vid_id получается из ссылки, которую ввел пользователь для загрузки. Далее, уже в полученном JSON нахожу секцию, где указывается качество видео. Данный сайт позволяет скачивать видео в качестве 720р и 360р, именно mp4. Ниже есть еще пара пунктов, но они относятся к форматам 3gp и mp4a. Если все в порядке, и тэг соответствует, получаю название видео. Как вы видели сами, в названии видео содержится большое количество всяческих символов, которые просто не совместимы с тем, чтобы сохранять в операционной системе. А так, как название видео нужно будет именно для того, чтобы не скачивать его в обезличенном виде, а сохранять с тем названием, что и на сервисе, требуется его очистить от всякого мусора, что я и делаю в цикле, перебирая словарь. В теории, туда можно загнать еще больше символов. Так как я загнал только те, с которыми столкнулся. А кто его знает, что будет в голове у автора, когда он будет давать название. Ну и следом получаю ссылку на загрузку.
Python:
print(f'[+] Получаю название и ссылку на видео...')
response = requests.get(f'https://downloader.freemake.com/api/videoinfo/{vid_id}', headers=headers).json()
if response['qualities'][0]['qualityInfo']['itag'] == 22:
video_title = str(response['metaInfo']['title'])
for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
video_title = video_title.replace(m, "")
url = response['qualities'][0]['url']
Двигаемся дальше. Теперь для того, чтобы сохранить видео, я создаю папку, если ее конечно не существует, с названием канала. Где я беру название канала, чуть позже, так как в этом запросе его нет. Ну и вывожу разные принты, чтобы было не скучно.
Python:
print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
if not os.path.isdir(f'{channel_name}'):
os.mkdir(f'{channel_name}')
print(f'[+] Создаю папку для сохранения видео...\n')
else:
print(f'[+] Папка для сохранения существует...\n')
А теперь, собственно, основная, самая большая часть функции по загрузке видео. На самом деле, загрузка видео тут в несколько строчек кода. Основная часть отведена на различные проверки и запросы информации, если что-то не так у пользователя, чтобы программа просто не закрывалась без объяснения причин. Отправляю запрос на загрузку. Оказывается, у request есть интересный параметр stream. С его помощью можно переписать поведение загрузки тела ответа, которое по умолчанию загружается сразу же, а при указании параметра делает отсрочку загрузки, пока не будет получен доступ к атрибуту content. Это все нужно для того, чтобы реализовать индикатор загрузки, который здесь представлен библиотекой tqdm. Для начала устанавливаем количество заголовков запроса в переменную total. А далее, по мере загрузки контента и получения заголовков, увеличиваем данный параметр на количество заголовков. И выводим в терминал в удобоваримом виде.
Python:
req = requests.get(url=url, headers=headers, stream=True)
total = int(req.headers.get('content-length', 0))
with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
desc=f"{video_title[0:int(len(video_title) / 2)]}...",
total=total,
unit='iB',
unit_scale=True,
unit_divisor=1024,
) as bar:
for data in req.iter_content(chunk_size=1024):
size = file.write(data)
bar.update(size)
print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
else:
user_change = input('\n[+] Нет видео в качестве 720р...\n[+] Загрузить в доступном качестве?:\n'
'\t[1]: Да\n\t[2]: Нет\n\t>>> ')
if user_change == "1":
video_title = str(response['metaInfo']['title'])
for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
video_title = video_title.replace(m, "")
url = response['qualities'][0]['url']
print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
if not os.path.isdir(f'{channel_name}'):
os.mkdir(f'{channel_name}')
print(f'[+] Создаю папку для сохранения видео...\n')
else:
print(f'[+] Папка для сохранения существует...\n')
req = requests.get(url=url, headers=headers, stream=True)
total = int(req.headers.get('content-length', 0))
with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
desc=f"{video_title[0:int(len(video_title) / 2)]}...",
total=total,
unit='iB',
unit_scale=True,
unit_divisor=1024,
) as bar:
for data in req.iter_content(chunk_size=1024):
size = file.write(data)
bar.update(size)
print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
elif user_change == "2":
main()
else:
print('[-] Вы ввели чушь. Закрываю программу...')
exit(0)
Ну, а если нет видео в качестве 720р, то сообщаем об этом пользователю, спрашиваем, желает ли он загрузить видео в том качестве, что есть. Если да, то загружаем. Если нет, прерываем выполнение функции и выводим первоначальное меню. Ну, а если пользователь ввел совсем не то, то посылаем его в незабываемое путешествие по экзотическим странам (перечеркнуть) сообщаем, что он ввел чушь и прерываем работу скрипта.
Python:
def get_video_download(vid_id, channel_name):
headers = {
'authority': 'downloader.freemake.com',
'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Yandex";v="22"',
'dnt': '1',
'x-cf-country': 'RU',
'sec-ch-ua-mobile': '?0',
'x-user-platform': 'Win32',
'accept': 'application/json, text/javascript, */*; q=0.01',
'x-user-browser': 'YaBrowser',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/98.0.4758.141 YaBrowser/22.3.3.852 Yowser/2.5 Safari/537.36',
'x-analytics-header': 'UA-18256617-1',
'x-request-attempt': '1',
'x-user-id': '94119398-e27a-3e13-be17-bbe7fbc25874',
'sec-ch-ua-platform': '"Windows"',
'origin': 'https://www.freemake.com',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'referer': 'https://www.freemake.com/ru/free_video_downloader/',
'accept-language': 'ru,en;q=0.9,uk;q=0.8',
}
print(f'[+] Получаю название и ссылку на видео...')
response = requests.get(f'https://downloader.freemake.com/api/videoinfo/{vid_id}', headers=headers).json()
if response['qualities'][0]['qualityInfo']['itag'] == 22:
video_title = str(response['metaInfo']['title'])
for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
video_title = video_title.replace(m, "")
url = response['qualities'][0]['url']
print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
if not os.path.isdir(f'{channel_name}'):
os.mkdir(f'{channel_name}')
print(f'[+] Создаю папку для сохранения видео...\n')
else:
print(f'[+] Папка для сохранения существует...\n')
req = requests.get(url=url, headers=headers, stream=True)
total = int(req.headers.get('content-length', 0))
with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
desc=f"{video_title[0:int(len(video_title) / 2)]}...",
total=total,
unit='iB',
unit_scale=True,
unit_divisor=1024,
) as bar:
for data in req.iter_content(chunk_size=1024):
size = file.write(data)
bar.update(size)
print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
else:
user_change = input('\n[+] Нет видео в качестве 720р...\n[+] Загрузить в доступном качестве?:\n'
'\t[1]: Да\n\t[2]: Нет\n\t>>> ')
if user_change == "1":
video_title = str(response['metaInfo']['title'])
for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
video_title = video_title.replace(m, "")
url = response['qualities'][0]['url']
print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
if not os.path.isdir(f'{channel_name}'):
os.mkdir(f'{channel_name}')
print(f'[+] Создаю папку для сохранения видео...\n')
else:
print(f'[+] Папка для сохранения существует...\n')
req = requests.get(url=url, headers=headers, stream=True)
total = int(req.headers.get('content-length', 0))
with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
desc=f"{video_title[0:int(len(video_title) / 2)]}...",
total=total,
unit='iB',
unit_scale=True,
unit_divisor=1024,
) as bar:
for data in req.iter_content(chunk_size=1024):
size = file.write(data)
bar.update(size)
print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
elif user_change == "2":
main()
return
else:
print('[-] Вы ввели чушь. Закрываю программу...')
exit(0)
Однако, как вы понимаете перед тем, как приступить к загрузке видео, нужно получить название канала. А где его взять, вот в чем вопрос. И я вспомнил о том запросе, который отправляло расширение на самой странице с видео. Тогда я так же скопировал его cURL, получил код и выполнил. В ответ прилетел JSON, в котором была информация о видео, в том числе и название канала. Знаю, это выглядит, как если бы я стрелял из пушки по воробьям. Но использовать BeautifulSoup и искать название канала на странице видео не хотелось. Тем более, что вот так вот, с получением JSON оказалось проще. Поэтому, я отправляю запрос на получение. В запросе, в свою очередь, отправляется JSON с кучей параметров, которые нас в общем-то не интересуют. Здесь нужно поменять только идентификатор видео. Меняется он вот тут:
Python:
json_data = {
'videoId': vid_id,
'context': {
'client': {
'hl': 'ru',
'gl': 'RU',
'remoteHost': '31.173.242.98',
У меня он обозначен как vid_id и передается в функцию при ее вызове. Таким образом я создал функцию get_channel_name(vid_id), которая на входе получает идентификатор, делает запрос. Выковыривает из него название канала. Чистит от «мусора» в виде символов и возвращает очищенное название туда, откуда вызывалась функция.
Python:
def get_channel_name(vid_id):
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/98.0.4758.141 Safari/537.36',
'accept': '*/*',
}
params = {
'key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
'prettyPrint': 'false',
}
json_data = {
'videoId': vid_id,
'context': {
'client': {
'hl': 'ru',
'gl': 'RU',
'remoteHost': '31.173.242.98',
'deviceMake': '',
'deviceModel': '',
'visitorData': 'CgtrdUNhZ3U2VGNEOCiDndSTBg%3D%3D',
'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/98.0.4758.141 Safari/537.36,gzip(gfe)',
'clientName': 'WEB',
'clientVersion': '2.20220502.01.00',
'osName': 'Windows',
'osVersion': '10.0',
'originalUrl': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
'platform': 'DESKTOP',
'clientFormFactor': 'UNKNOWN_FORM_FACTOR',
'configInfo': {
'appInstallData': 'CIOd1JMGELiLrgUQmN79EhCUj64FEOqQrgUQw_KtBRCY6q0FELfLrQUQ8IKuBRC7ka4FENSDrgUQ6JCu'
'BRCw7q0FEK_yrQUQgub9EhCR-PwSENi-rQU%3D',
},
'userInterfaceTheme': 'USER_INTERFACE_THEME_DARK',
'timeZone': 'Europe/Moscow',
'browserName': 'Chrome',
'browserVersion': '98.0.4758.141',
'screenWidthPoints': 1137,
'screenHeightPoints': 870,
'screenPixelDensity': 1,
'screenDensityFloat': 1,
'utcOffsetMinutes': 360,
'connectionType': 'CONN_CELLULAR_4G',
'memoryTotalKbytes': '8000000',
'mainAppWebInfo': {
'graftUrl': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
'webDisplayMode': 'WEB_DISPLAY_MODE_BROWSER',
'isWebNativeShareAvailable': True,
},
'playerType': 'UNIPLAYER',
'tvAppInfo': {
'livingRoomAppMode': 'LIVING_ROOM_APP_MODE_UNSPECIFIED',
},
'clientScreen': 'WATCH_FULL_SCREEN',
},
'user': {
'lockedSafetyMode': False,
},
'request': {
'useSsl': True,
'internalExperimentFlags': [],
'consistencyTokenJars': [],
},
'adSignalsInfo': {
'params': [
{
'key': 'dt',
'value': '1651838604229',
},
{
'key': 'flash',
'value': '0',
},
{
'key': 'frm',
'value': '0',
},
{
'key': 'u_tz',
'value': '360',
},
{
'key': 'u_his',
'value': '5',
},
{
'key': 'u_h',
'value': '1080',
},
{
'key': 'u_w',
'value': '1920',
},
{
'key': 'u_ah',
'value': '1032',
},
{
'key': 'u_aw',
'value': '1920',
},
{
'key': 'u_cd',
'value': '24',
},
{
'key': 'bc',
'value': '31',
},
{
'key': 'bih',
'value': '870',
},
{
'key': 'biw',
'value': '1121',
},
{
'key': 'brdim',
'value': '43,12,43,12,1920,0,1708,991,1137,870',
},
{
'key': 'vis',
'value': '1',
},
{
'key': 'wgl',
'value': 'true',
},
{
'key': 'ca_type',
'value': 'image',
},
],
},
},
'playbackContext': {
'contentPlaybackContext': {
'html5Preference': 'HTML5_PREF_WANTS',
'lactMilliseconds': '2979',
'referer': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
'signatureTimestamp': 19117,
'autonavState': 'STATE_OFF',
'autoCaptionsDefaultOn': False,
'mdxContext': {},
'playerWidthPixels': 647,
'playerHeightPixels': 364,
},
},
'cpn': 'pwy4NMkpT8PY63hl',
'captionParams': {
'deviceCaptionsOn': True,
},
'attestationRequest': {
'omitBotguardData': True,
},
}
print('\n[+] Получаю название канала...')
channel_name = str(requests.post('https://www.youtube.com/youtubei/v1/player', params=params, headers=headers,
json=json_data).json()['videoDetails']['author'])
for m in ["?", '"', "/", ":", "#", "|", ",", " ?", "?!", "?!", "? ", " / ", " | "]:
channel_name = channel_name.replace(m, " ")
print(f'[+] Название канала получено: "{channel_name}"')
return channel_name
Не стоит пугаться ее размерами. Большую часть здесь занимает JSON с параметрами, которые передаются с запросом и заголовками. Но избавиться от них не получилось. Потому, чтобы все работало без сбоев, я оставил их без изменения. И использовал так, как они и отправляются в запросе на странице.
И по сути, большая часть скрипта уже готова. Но, меня не оставляло смутное чувство, что я что-то упустил. И да, немного подумав, я понял, что есть еще ведь плейлисты, которые тоже неплохо бы выкачивать полностью, только лишь введя на них ссылку. Но для того, чтобы их выкачать, для начала со страницы плейлиста нужно получить все ссылки на видео, которые содержаться в нем. Но фишка тут в том, что бывают такие плейлисты, количество видео в которых не влезает на одну страницу. И тогда они просто подгружаются по мере прокрутки. То есть, использованием простого запроса тут не обойдешься. Нужно подключать что-то посерьезнее. Но использовать selenium мне не хотелось. Да, с помощью этой библиотеки можно было бы в безголовом режиме прокрутить страницу до конца, собрать все ссылки на видео, сложить в список и вернуть для дальнейшего использования. Но, я подумал, что должен быть другой способ получения ссылок. И он таки нашелся. Стоило ввести запрос на загрузку плейлиста, как нашелся сайт, который и предоставлял этот функционал. Я покопался в его запросах и нашел один, в котором отправляется ссылка на плейлист, а в ответ прилетает JSON уже с готовыми идентификаторами видео. Ну не красота ли.
Тут все по накатанной. Копируется cURL, получается код, делается запрос и забирается JSON, из которого выкорчевываются идентификаторы, помещаются в список и возвращают его туда, откуда запрос был сделан.
Кстати, здесь, прямо на сайте есть специальное поле, куда помещаются ссылки на загрузку видео из плейлиста уже после выполнения запроса. В самом запросе они «медленные», то есть загрузка по ним происходит со скоростью черепахи. А вот на сайте они уже переформатируются в нужный вид и по ним видео загружается хорошо. Но, чтобы забрать их отсюда нужен опять же selenium, а это снова усложняет скрипт. А так, ссылки интересные. Если понять, как преобразуются исходные ссылки именно в такой вид, то можно и самому их конвертировать. Но, пока что я не стал разбираться. Может быть в будущем.
Создаю функцию playlist_item(url). Здесь в запросе так же передается небольшой словарик с параметрами, одним из которых является ссылка на плейлист. Вот ее и получает данная функция на входе и передает в словарик.
Python:
params = {
'url': url,
'nextPageToken': '',
}
Ну, а больше особо и пояснять нечего. Все довольно просто и понятно. Цикл и перебор.
Python:
def playlist_item(url):
headers = {
'authority': 'api.youtubemultidownloader.com',
'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Yandex";v="22"',
'accept': 'application/json, text/javascript, */*; q=0.01',
'dnt': '1',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/98.0.4758.141 YaBrowser/22.3.3.852 Yowser/2.5 Safari/537.36',
'sec-ch-ua-platform': '"Windows"',
'origin': 'https://youtubemultidownloader.net',
'sec-fetch-site': 'cross-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'referer': 'https://youtubemultidownloader.net/',
'accept-language': 'ru,en;q=0.9,uk;q=0.8',
}
params = {
'url': url,
'nextPageToken': '',
}
response = requests.get('https://api.youtubemultidownloader.com/playlist', params=params, headers=headers).json()
list_items = []
for item in range(0, len(response['items'])):
list_items.append(response['items'][item]['id'])
return list_items
Что же, теперь пришло время для функции, которая обрабатывает пользовательский ввод. Она у меня получилась размером с простыню. Так как нужно было учесть довольно много параметров.
Создаю функцию get_target_path(user_input), которая на входе принимает первоначальный пользовательский ввод и обрабатывает его в зависимости от того, что ввел пользователь.
Думаю, что подробно на этом останавливаться не стоит, так как тут не используется чего-то сверхъестественного. Всего лишь проверки if, elif, else. Ну и, если пользователь ввел что-то не так, в бесконечном цикле запрос правильного параметра. Тут, конечно, я предусмотрел возможность выхода в основное меню, так как пользователь может не понять, что не так и попросту запутаться. А цикл будет долбить его снова и снова. А потому, лучше предоставить ему, то есть пользователю, небольшую лазейку, возможность сбежать из бесконечного цикла. А обрабатываются здесь запросы, которые представлены на скриншоте ниже.
Python:
def get_target_path(user_input):
if user_input == "1":
vid_id = input('\t[+] Введите ссылку на видео\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
if vid_id == 'ex':
main()
return
while not "https://www.youtube.com" in vid_id:
vid_id = input('\t[+] Введите ссылку на видео\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
if vid_id == 'ex':
main()
return
if '&list' in vid_id:
vid_id = vid_id.split("&")[0].split("=")[-1]
else:
vid_id = vid_id.split("=")[-1]
channel_name = get_channel_name(vid_id)
get_video_download(vid_id, channel_name)
main()
elif user_input == "2":
while not os.path.isfile(user_path := input("\t[+] Введите путь к списку\n\t[+] Для выхода в меню введите: ex\n"
"\t>>> ").replace('"', '')):
if user_path == 'ex':
main()
return
print(f"\n\t[+] Список {user_path} не найден\n")
with open(f'{user_path}', 'r', encoding='utf-8') as file:
video_list = file.readlines()
for video in video_list:
if '&list' in video:
vid_id = video.split("&")[0].split("=")[-1]
else:
vid_id = video.split("=")[-1].strip()
if video.strip() == "":
continue
else:
channel_name = get_channel_name(vid_id)
get_video_download(vid_id, channel_name)
main()
elif user_input == "3":
vid_id = input('\t[+] Введите ссылку на плейлист\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
if vid_id == 'ex':
main()
return
while not "https://www.youtube.com/playlist" in vid_id:
vid_id = input('\t[+] Введите ссылку на плейлист\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
if vid_id == 'ex':
main()
return
list_items = playlist_item(vid_id)
print(f'[+] Видео в плейлисте: {len(list_items)}\n[+] Загружаю плейлист...')
for item in list_items:
channel_name = get_channel_name(item)
get_video_download(item, channel_name)
main()
elif user_input == "4":
exit(0)
else:
main()
И остается функция main(), в которой и делается первоначальный выбор и осуществляется пользовательский ввод.
Python:
def main():
get_target_path(input(f'\n[+] Выберите варианты загрузки:\n\t[1] Загрузить видео\n'
f'\t[2] Загрузить видео из списка\n\t[3] Загрузить плейлист\n\t[4] Выход\n\t>>> '))
Вот, в принципе и все. Если собрать все эти функции в кучку в одном скрипте, то получиться годный загрузчик видео. Данный код работает на обеих платформах. Как на Windows, так и на Linux. На MacOS не проверял, ибо нет у меня этой «заразы». Но, думаю, что и на ней будет работать. А ниже небольшое видео, которое демонстрирует работу скрипта.
Python:
import os.path
import time
import requests
from tqdm import tqdm
def playlist_item(url):
headers = {
'authority': 'api.youtubemultidownloader.com',
'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Yandex";v="22"',
'accept': 'application/json, text/javascript, */*; q=0.01',
'dnt': '1',
'sec-ch-ua-mobile': '?0',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/98.0.4758.141 YaBrowser/22.3.3.852 Yowser/2.5 Safari/537.36',
'sec-ch-ua-platform': '"Windows"',
'origin': 'https://youtubemultidownloader.net',
'sec-fetch-site': 'cross-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'referer': 'https://youtubemultidownloader.net/',
'accept-language': 'ru,en;q=0.9,uk;q=0.8',
}
params = {
'url': url,
'nextPageToken': '',
}
response = requests.get('https://api.youtubemultidownloader.com/playlist', params=params, headers=headers).json()
list_items = []
for item in range(0, len(response['items'])):
list_items.append(response['items'][item]['id'])
return list_items
def get_channel_name(vid_id):
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/98.0.4758.141 Safari/537.36',
'accept': '*/*',
}
params = {
'key': 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8',
'prettyPrint': 'false',
}
json_data = {
'videoId': vid_id,
'context': {
'client': {
'hl': 'ru',
'gl': 'RU',
'remoteHost': '31.173.242.98',
'deviceMake': '',
'deviceModel': '',
'visitorData': 'CgtrdUNhZ3U2VGNEOCiDndSTBg%3D%3D',
'userAgent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/98.0.4758.141 Safari/537.36,gzip(gfe)',
'clientName': 'WEB',
'clientVersion': '2.20220502.01.00',
'osName': 'Windows',
'osVersion': '10.0',
'originalUrl': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
'platform': 'DESKTOP',
'clientFormFactor': 'UNKNOWN_FORM_FACTOR',
'configInfo': {
'appInstallData': 'CIOd1JMGELiLrgUQmN79EhCUj64FEOqQrgUQw_KtBRCY6q0FELfLrQUQ8IKuBRC7ka4FENSDrgUQ6JCu'
'BRCw7q0FEK_yrQUQgub9EhCR-PwSENi-rQU%3D',
},
'userInterfaceTheme': 'USER_INTERFACE_THEME_DARK',
'timeZone': 'Europe/Moskow',
'browserName': 'Chrome',
'browserVersion': '98.0.4758.141',
'screenWidthPoints': 1137,
'screenHeightPoints': 870,
'screenPixelDensity': 1,
'screenDensityFloat': 1,
'utcOffsetMinutes': 360,
'connectionType': 'CONN_CELLULAR_4G',
'memoryTotalKbytes': '8000000',
'mainAppWebInfo': {
'graftUrl': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
'webDisplayMode': 'WEB_DISPLAY_MODE_BROWSER',
'isWebNativeShareAvailable': True,
},
'playerType': 'UNIPLAYER',
'tvAppInfo': {
'livingRoomAppMode': 'LIVING_ROOM_APP_MODE_UNSPECIFIED',
},
'clientScreen': 'WATCH_FULL_SCREEN',
},
'user': {
'lockedSafetyMode': False,
},
'request': {
'useSsl': True,
'internalExperimentFlags': [],
'consistencyTokenJars': [],
},
'adSignalsInfo': {
'params': [
{
'key': 'dt',
'value': '1651838604229',
},
{
'key': 'flash',
'value': '0',
},
{
'key': 'frm',
'value': '0',
},
{
'key': 'u_tz',
'value': '360',
},
{
'key': 'u_his',
'value': '5',
},
{
'key': 'u_h',
'value': '1080',
},
{
'key': 'u_w',
'value': '1920',
},
{
'key': 'u_ah',
'value': '1032',
},
{
'key': 'u_aw',
'value': '1920',
},
{
'key': 'u_cd',
'value': '24',
},
{
'key': 'bc',
'value': '31',
},
{
'key': 'bih',
'value': '870',
},
{
'key': 'biw',
'value': '1121',
},
{
'key': 'brdim',
'value': '43,12,43,12,1920,0,1708,991,1137,870',
},
{
'key': 'vis',
'value': '1',
},
{
'key': 'wgl',
'value': 'true',
},
{
'key': 'ca_type',
'value': 'image',
},
],
},
},
'playbackContext': {
'contentPlaybackContext': {
'html5Preference': 'HTML5_PREF_WANTS',
'lactMilliseconds': '2979',
'referer': 'https://www.youtube.com/watch?v=4MPWVKFaLD8',
'signatureTimestamp': 19117,
'autonavState': 'STATE_OFF',
'autoCaptionsDefaultOn': False,
'mdxContext': {},
'playerWidthPixels': 647,
'playerHeightPixels': 364,
},
},
'cpn': 'pwy4NMkpT8PY63hl',
'captionParams': {
'deviceCaptionsOn': True,
},
'attestationRequest': {
'omitBotguardData': True,
},
}
print('\n[+] Получаю название канала...')
channel_name = str(requests.post('https://www.youtube.com/youtubei/v1/player', params=params, headers=headers,
json=json_data).json()['videoDetails']['author'])
for m in ["?", '"', "/", ":", "#", "|", ",", " ?", "?!", "?!", "? ", " / ", " | "]:
channel_name = channel_name.replace(m, " ")
print(f'[+] Название канала получено: "{channel_name}"')
return channel_name
def get_video_download(vid_id, channel_name):
headers = {
'authority': 'downloader.freemake.com',
'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Yandex";v="22"',
'dnt': '1',
'x-cf-country': 'RU',
'sec-ch-ua-mobile': '?0',
'x-user-platform': 'Win32',
'accept': 'application/json, text/javascript, */*; q=0.01',
'x-user-browser': 'YaBrowser',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/98.0.4758.141 YaBrowser/22.3.3.852 Yowser/2.5 Safari/537.36',
'x-analytics-header': 'UA-18256617-1',
'x-request-attempt': '1',
'x-user-id': '94119398-e27a-3e13-be17-bbe7fbc25874',
'sec-ch-ua-platform': '"Windows"',
'origin': 'https://www.freemake.com',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
'referer': 'https://www.freemake.com/ru/free_video_downloader/',
'accept-language': 'ru,en;q=0.9,uk;q=0.8',
}
print(f'[+] Получаю название и ссылку на видео...')
response = requests.get(f'https://downloader.freemake.com/api/videoinfo/{vid_id}', headers=headers).json()
if response['qualities'][0]['qualityInfo']['itag'] == 22:
video_title = str(response['metaInfo']['title'])
for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
video_title = video_title.replace(m, "")
url = response['qualities'][0]['url']
print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
if not os.path.isdir(f'{channel_name}'):
os.mkdir(f'{channel_name}')
print(f'[+] Создаю папку для сохранения видео...\n')
else:
print(f'[+] Папка для сохранения существует...\n')
req = requests.get(url=url, headers=headers, stream=True)
total = int(req.headers.get('content-length', 0))
with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
desc=f"{video_title[0:int(len(video_title) / 2)]}...",
total=total,
unit='iB',
unit_scale=True,
unit_divisor=1024,
) as bar:
for data in req.iter_content(chunk_size=1024):
size = file.write(data)
bar.update(size)
print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
else:
user_change = input('\n[+] Нет видео в качестве 720р...\n[+] Загрузить в доступном качестве?:\n'
'\t[1]: Да\n\t[2]: Нет\n\t>>> ')
if user_change == "1":
video_title = str(response['metaInfo']['title'])
for m in ["?", '"', "'", "/", ":", "#", "|", ",", " | "]:
video_title = video_title.replace(m, "")
url = response['qualities'][0]['url']
print(f'[+] Название и ссылка получены. Начинаю загрузку: "{video_title}"...')
if not os.path.isdir(f'{channel_name}'):
os.mkdir(f'{channel_name}')
print(f'[+] Создаю папку для сохранения видео...\n')
else:
print(f'[+] Папка для сохранения существует...\n')
req = requests.get(url=url, headers=headers, stream=True)
total = int(req.headers.get('content-length', 0))
with open(f'{os.path.join(channel_name, f"{video_title}.mp4")}', 'wb') as file, tqdm(
desc=f"{video_title[0:int(len(video_title) / 2)]}...",
total=total,
unit='iB',
unit_scale=True,
unit_divisor=1024,
) as bar:
for data in req.iter_content(chunk_size=1024):
size = file.write(data)
bar.update(size)
print(f'\n[+] Видео сохранено в папку: "{channel_name}".\n[+] Загрузка завершена.\n')
elif user_change == "2":
main()
return
else:
print('[-] Вы ввели чушь. Закрываю программу...')
exit(0)
def get_target_path(user_input):
if user_input == "1":
vid_id = input('\t[+] Введите ссылку на видео\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
if vid_id == 'ex':
main()
return
while not "https://www.youtube.com" in vid_id:
vid_id = input('\t[+] Введите ссылку на видео\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
if vid_id == 'ex':
main()
return
if '&list' in vid_id:
vid_id = vid_id.split("&")[0].split("=")[-1]
else:
vid_id = vid_id.split("=")[-1]
channel_name = get_channel_name(vid_id)
get_video_download(vid_id, channel_name)
main()
elif user_input == "2":
while not os.path.isfile(user_path := input("\t[+] Введите путь к списку\n\t[+] Для выхода в меню введите: ex\n"
"\t>>> ").replace('"', '')):
if user_path == 'ex':
main()
return
print(f"\n\t[+] Список {user_path} не найден\n")
with open(f'{user_path}', 'r', encoding='utf-8') as file:
video_list = file.readlines()
for video in video_list:
if '&list' in video:
vid_id = video.split("&")[0].split("=")[-1]
else:
vid_id = video.split("=")[-1].strip()
if video.strip() == "":
continue
else:
channel_name = get_channel_name(vid_id)
get_video_download(vid_id, channel_name)
main()
elif user_input == "3":
vid_id = input('\t[+] Введите ссылку на плейлист\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
if vid_id == 'ex':
main()
return
while not "https://www.youtube.com/playlist" in vid_id:
vid_id = input('\t[+] Введите ссылку на плейлист\n\t[+] Для выхода в меню введите: ex\n\t>>> ')
if vid_id == 'ex':
main()
return
list_items = playlist_item(vid_id)
print(f'[+] Видео в плейлисте: {len(list_items)}\n[+] Загружаю плейлист...')
for item in list_items:
channel_name = get_channel_name(item)
time.sleep(0.3)
get_video_download(item, channel_name)
time.sleep(0.3)
main()
elif user_input == "4":
exit(0)
else:
main()
def main():
get_target_path(input(f'\n[+] Выберите варианты загрузки:\n\t[1] Загрузить видео\n'
f'\t[2] Загрузить видео из списка\n\t[3] Загрузить плейлист\n\t[4] Выход\n\t>>> '))
if __name__ == "__main__":
main()
Спасибо за внимание. Надеюсь, что данная информация будет кому-нибудь полезной
Вложения
Последнее редактирование: