SaveFrom.net + костыль, или загрузка PLS плейлистов

в 11:44, , рубрики: Delphi, Вконтакте, музыка, Социальные сети и сообщества, метки: , ,

SaveFrom.net + костыль, или загрузка PLS плейлистов Привет хабровцы.
Я думаю не только у меня большой плейлист на vk.com. Совсем недавно я озадачился тем, как бы выкачать музыку, чтобы можно было слушать её оффлайн. Вскоре я нашел удобный сервис SaveFrom.net. И все бы ничего, да только выкачивать сразу всю музыку он не умеет. Он предлагает нам варинаты:
1. Дать нам кучу url, чтобы мы вставили все это дело в менеджер загрузок, типа DownloadManager
2. Сохранить плейлист в m3u/pls, которые играют по урл

В первом случае у нас имена файлов будут вида 3ee56ab0933e.mp3. ID3 tag будет на месте, но согласитесь, это неудобно, открывать каждую композицию чтобы посмотреть что это
Во втором случае — у нас чисто url-ы в плейлистах, но зато title песни есть сразу.

Поскольку наблюдать файлы вида 3ee56ab0933e.mp3 мне не хотелось, а так же не хотелось вручную все это именовать — я набросал на коленке тулзу, которая умеет читать pls плейлисты, и скачивать музыку в 10 коннектов.

Бинарник + исходник

Откомпилированный экзешник + исходный код (на Delphi) лежит тут

Как пользоваться

1. Прокручиваем наш плейлист вконтакте до самого низа, чтобы SaveFrom.net мог получить полный pls
2. Выбираем скачать плейлист:
SaveFrom.net + костыль, или загрузка PLS плейлистов
3. В появившемся окне копируем pls плейлист в буфер обмена:
SaveFrom.net + костыль, или загрузка PLS плейлистов
4. Вставляем этот плейлист в мою утилиту в поле с текстом Insert PLS text here:
SaveFrom.net + костыль, или загрузка PLS плейлистов
5. Жмем кнопочку Download, и видим как начинает скачиваться музыка
6. В рабочем каталоге программы если надо — будет создана папка VKMusic. Имя папки можно поменять:
SaveFrom.net + костыль, или загрузка PLS плейлистов
путь может быть абсолютным.

Если стоит галка в Allow skip files, то программа перед скачиванием будет проверять, если есть на диске файл с таким же именем, то качать его она не будет.

Очень краткий экскурс в исходный код

Пишу в первую очередь для тех, кто захочет изменить код под себя.
Программа использует Virtual Treeview и Indy.
untPLSparser.pas — парсер PLS файлов. В интерфейсе модуля одна единственная функция:

function Parse(const text: string): TPLSItems;

На входе — PLS плейлист, возвращает массив структур описывающих аудиозаписи (заголовок + урл + зачастую длительность трека)

Все это дело конвертируется в другой массив TMusicList описанный в untDownloadList.pas.
TMusicList состоит из TMusicItem и нужен для того, чтобы хранить информацию о статусе скачивания, а так же возникших ошибках.

Далее начинается процесс загрузки. 10 заранее созданных потоков TDLThread, реализация которых находится в untDownloadThread.pas начинают скачивать композиции.
Скачивание запускается через TfrmMain.RunFreeDownload в untMain.pas.
Сначала получаем индекс записи, которую надо скачать с помощью метода TfrmMain.GetFreeItem. Затем проверяем, если у нас стоит галка пропускать файлы, то смотрим есть ли такой файл, и если есть — пропускаем.

В процессе скачивания в коллбеки

DLProgress, DLError, DLComplete, DLTerminate

прилетают евенты где мы собственно обновляем информацию в TMusicItem, начинаем новую загрузку или сообщаем об ошибке.

Скачивающи потоки работают следующим образом.
После создания поток ждет задачи на скачивание в цикле:

      while (Url = '') do
      begin
        if Terminated then Exit;
        FInWork := False;
        Sleep(1);
      end;

как только есть Url для скачивания — он начинает загрузку. Поскольку Indy — библиотека на блокирующих сокетах — то должен быть механизм, чтобы остановить загруку. Я использую специальное исключение EStopTask в коллбеках TidHTTP.OnWorkBegin, TidHTTP.OnWork, TidHTTP.OnWorkEnd, что позволяет мне фактически прервать загрузку в любой момент.

Если что-то пошло не так, но мы можем корректно обработать это, и продолжить работу, мы обрабатываем, недокачанный файл прибиваем. Если в потоке возникло исключение, которое мы не можем обработать (например AV), то согласно стандартному механизму — поток будет прибит, а в FatalException будет лежать объект исключения. Поэтому в untMain.pas я обрабатываю OnTerminate, проверяю объект исключения потока, и если он есть — то бросаю исключение уже в главной нити.

В заключение

Вот собственно и все, спасибо за чтение. Рад если мой «костыль» вдруг оказался для вас удобным.

Автор: MrShoor

Источник


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


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