Привет хабровцы.
Я думаю не только у меня большой плейлист на 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. Выбираем скачать плейлист:
3. В появившемся окне копируем pls плейлист в буфер обмена:
4. Вставляем этот плейлист в мою утилиту в поле с текстом Insert PLS text here:
5. Жмем кнопочку Download, и видим как начинает скачиваться музыка
6. В рабочем каталоге программы если надо — будет создана папка VKMusic. Имя папки можно поменять:
путь может быть абсолютным.
Если стоит галка в 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