Не так давно написал скрипт, скачивающий выбранный плейлист с YouTube, в своей работе он использует разделение задач пусть на примитивные, но всё же потоки, и распределяет оные по ядрам процессора. Хотелось бы его усовершенствовать и добавить автоматическое определение количества ядер (скрипт запускается на разных ПК с абсолютно неидентичным железом), но проблема в том, что я не знаю, как это реализовать. Подозреваю, что силами WMIC, но я не силён в нём. Надеюсь на помощь форумчан.
На всякий случай привожу сам скрипт:
читать дальше »
Код:
![Выделить весь код](images/misc/selectcode.png)
@Echo Off
SetLocal EnableDelayedExpansion
:: YTDownloader v1.4
:: Anonymous, 2013. Ad majorem Applejack gloriam
:: Distributed under WTFPL license (http://www.wtfpl.net/)
:: Скрипт, скачивающий выбранный плейлист с ютуба в виде mp3
:: Работа с YouTube ведется через собственный API (тоже на батниках). Документация по API: http://ponyasha.tk/api/
:: Зависимости: wget (http://gnuwin32.sourceforge.net/packages/wget.htm) aria2 (http://aria2.sourceforge.net/)
:: Почему две утилиты для одной и той же цели? Aria не умеет писать данные в stdout, а wget скачивает в один поток
:: Да-да, реализована многопоточность и распределение процессов по ядрам. inb4: месье знает толк в извращениях
::==================================
:: Настройки
::==================================
:: Число потоков (обычно вполне хватает пяти)
Set Threads=5
:: Число используемых ядер процессора (не больше четырех)
Set Cores=4
:: Домен, на котором расположены используемые API
Set APIBaseURL=http://ponyasha.tk
::==================================
:: Обработчик параметров командной строки
::==================================
If "%~1"=="" Echo USAGE: %~nx0 ^<YouTube playlist ID^>&Exit /B
If "%~1"=="thread" GoTo Thread
::==================================
:: Работа с данными плейлиста
::==================================
:: Подготовка
If Not Exist audio MD audio
For /F "delims=" %%A In ('Dir "%Temp%\thread*.lock" /B 2^>nul') Do (Del "%Temp%\%%A")
If Exist "%Temp%\ytplaylist.txt" Del "%Temp%\ytplaylist.txt"
:: Получение ID видео
Set i=0
For /F "eol= delims=" %%A In ('wget "!APIBaseURL!/api/ytplist.bat?list=%~1" -q -O- 2^>nul') Do (
For %%B In (%%A) Do (
If "%%~B"=="Error:" Echo ERROR: Ivalid playlist ID or empty playlist&Exit /B
Echo %%~B>>"%Temp%\ytplaylist.txt"
Set /A i+=1
)
)
::==================================
:: Балансировка нагрузки
::==================================
:: Считаем количество заданий на поток
Set /A JobsPerThread=i/Threads
:: Проверяем на деление без остатка и распределяем диапазоны
Set /A Mod=JobsPerThread*Threads
For /L %%A In (1,1,!Threads!) Do (
Set /A Prev=%%A-1
Set /A Thread[%%A]min=Thread[!Prev!]max+1
Set /A Thread[%%A]max=JobsPerThread*%%A
)
:: Если поровну между потоками не делится, добавляем остаток к последнему
If Not "!Mod!"=="!i!" Set /A Thread[%Threads%]max=i-Mod+!Thread[%Threads%]max!
:: Если заданий меньше чем потоков, принудительно переводим в однопоточный режим
If !i! LSS !Threads! (
Set Thread[1]min=1
Set Thread[1]max=!i!
Set Threads=1
)
:: Распределяем потоки по ядрам (само собой, вместе с дочерними процессами)
If !Cores! GTR 4 Set Cores=4
Set Core=1&Set CoreID=1
For /L %%A In (1,1,!Threads!) Do (
Set Thread[%%A]core=!CoreID!
Call :AffinityMap
)
:: Вызываем потоки (копии самого себя, работающие в том же окне)
Echo YTDownload started
Echo INFO: !i! videos ready to processing
Echo v:0>"%Temp%\progress.data"
For /L %%A In (1,1,!Threads!) Do (
Start /B /affinity !Thread[%%A]core! %~nx0 thread !Thread[%%A]min!:!Thread[%%A]max! %%A !i!
)
:: Ожидание окончания всех потоков (интервал проверки - ~2 секунды)
:ThreadWatcher
Set AllDone=true
Ping -n 3 127.0.0.1>nul
For /L %%A In (1,1,!Threads!) Do (If Exist "%Temp%\thread%%A.lock" Set AllDone=false)
If "!AllDone!"=="false" GoTo ThreadWatcher
:: Завершение работы
Echo INFO: All threads finished
Del "%Temp%\ytplaylist.txt"
Exit /B
::==================================
:: Отдельный поток
::==================================
:Thread
:: Разбираем аргументы
Set ThreadNum=%~3
Set Videos=%~4
For /F "tokens=1,2 delims=:" %%A In ("%~2") Do (Set Min=%%A&Set Max=%%B)
:: Ставим локфайл
Echo.>"%Temp%\thread!ThreadNum!.lock"
:: Получаем только принадлежащие потоку данные
Set i=0
For /F "usebackq eol= delims=" %%A In ("%Temp%\ytplaylist.txt") Do (
Set /A i+=1
If !i! GEQ !Min! If !i! LEQ !Max! (
Set Err=0
For /F "eol= delims=" %%B In ('wget "!APIBaseURL!/api/yt2mp3.bat?v=%%A" -q -O- 2^>nul') Do (Set AudioURL=%%B)
:: Вывод сообщения о ошибке, генерируемого самим API
If "!AudioURL:~,6!"=="Error:" Echo ERROR: [%%A]!AudioURL:~6!&Set Err=1
If !Err!==0 (
:: Получение имени скачиваемого файла
For /F "eol= delims=" %%A In ("!AudioURL:/=\!") Do Set AudioName=%%~nxA
:: Сокращение отображаемых данных
Set DisplayedURL=!AudioURL!
If Not "!DisplayedURL!"=="!DisplayedURL:~-40!" Set DisplayedURL=!DisplayedURL:~,25!...!DisplayedURL:~-10!
If Not "!AudioName!"=="!AudioName:~-40!" Set AudioName=!AudioName:~,25!...!AudioName:~-10!
:: Инкрементация счетчика обработанных видео
For /F "usebackq tokens=2 delims=:" %%A In ("%Temp%\progress.data") Do (
Set /A Progress=%%A+1
Echo v:!Progress!>"%Temp%\progress.data"
Set /A Percentage=!Progress!*100/!Videos!
Title [!Progress!/!Videos!] !Percentage!%%
)
:: Многопоточное скачивание
Echo [!Progress!/!Videos!] !Percentage!%% Thread #!ThreadNum!: Downloading !DisplayedURL!
aria2c -j 10 -x 10 -s 10 --allow-overwrite=true -D -d "audio" "!AudioURL!">nul
Echo Thread #!ThreadNum!: Succesfuly downloaded: !AudioName!
)
)
)
:: Убираем локфайл и убиваем поток
Del "%Temp%\thread!ThreadNum!.lock"
Echo INFO: Thread !ThreadNum! finished
Exit
::==================================
:: Вспомогательные функции
::==================================
:AffinityMap
:: Получаем ID следующего ядра
If !Cores!==1 Set CoreID=1&Exit /B
Set /A CoreID="1<<Core"
Set /A Core+=1
If !Core!==!Cores! Set Core=0
Exit /B