3
51
b65318d7-8a9a-425f-92b9-9feba609ef39
https://www.youtube.com
234DF17B-418C-4FDC-9DFE-CD0C586D2E76
4
Youtube v4.4
515
1
700
0
701
-1
702
-1
517
578-720,722-1080,482-576,402-480,322-400,202-320,0-200
518
1
512
1
530
#define mpiTokensInfo 591 // Пароль, где хранится информация о токенах (при сохранении hdf эти данные в файле не сохраняются)
#define mpiAuthorization 594 // Авторизация пользователя: 1 - включена, 2 - выключена, 0 - наследуется
THmsScriptMediaItem PodcastItem = FolderItem; string MediaResourceLink = '';
///////////////////////////////////////////////////////////////////////////////
// Г Л О Б А Л Ь Н Ы Е П Е Р Е М Е Н Н Ы Е //
int gnTotalItems = 0; // Глобальный счетчик созданных ссылок
string gsUrlBase = 'https://www.youtube.com';
string gsHeaders = 'Accept-Encoding: gzip, deflate\r\n';
string gsPodcastName = 'HMS Youtube v4.4'; // Заголовок видео-сообщения при ошибках
string gsAPIKey = '';
string gsClientId = '';
string gsClientSecret= '';
string gsLanguage = 'ru-RU';
string gsRegion = 'RU';
TDateTime gStart = Now;
Variant gREQUEST; // Для хранения созданного OLE объекта WinHttp.WinHttpRequest.5.1
THmsScriptMediaItem gRoot = GetRoot(); // Корневой элемент подкаста, содержащий информацию о токенах
TStringList gACCESS = LoadAccessInfo(); // Создание объекта хранения информации о токенах
///////////////////////////////////////////////////////////////////////////////
// Ф У Н К Ц И И //
///////////////////////////////////////////////////////////////////////////////
// Поиск корневой папки подкаста, где мы будем хранить в mpiAccessToken
THmsScriptMediaItem GetRoot() {
gRoot = PodcastItem; // Начиная с текущего элемента, ищется создержащий срипт или информацию о токенах
while ((Trim(gRoot[mpiTokensInfo])=='') && (Trim(gRoot[550])=='') && (gRoot[532]!='1') && (gRoot.ItemParent!=nil)) gRoot=gRoot.ItemParent;
return gRoot;
}
///////////////////////////////////////////////////////////////////////////////
// Загрузка данных о токенах доступа
TStringList LoadAccessInfo() {
gACCESS = TStringList.Create();
if (Pos('=', Trim(gRoot[mpiTokensInfo]))>0)
gACCESS.Text = gRoot[mpiTokensInfo];
else
gACCESS.Text = mpPodcastAuthorizationPassword;
return gACCESS;
}
///////////////////////////////////////////////////////////////////////////////
// Сохранение данных о токенах доступа
void SaveAccessInfo() {
gRoot[mpiTokensInfo ] = gACCESS.Text;
gRoot[mpiAuthorization] = '1'; // Включение авторизации пользователя, чтобы данные сохранялись после перезагрузки
}
///////////////////////////////////////////////////////////////////////////////
// Запрос к API
string APIRequest(string sObject, string sParam) {
string sData, sLink, sLang, sErr, sHeaderName, sHeaderValue; int i;
sLang = Lowercase(gsRegion);
if (sObject=='search') {
gsAPIKey = 'AIzaSyCPxJ1HKZznL9WEFay70tLjyzDrry0DiEw';
gsHeaders = '';
}
sLink = 'https://www.googleapis.com/youtube/v3/'+sObject+'?key='+gsAPIKey+'&maxResults=50&gl='+sLang+'®ionCode='+sLang+'&hl='+sLang+'&'+sParam;
// И всё этот бред с CreateOleObject для того, чтобы была возможность получить тело ответа при коде ответа отличных от 200 для анализа ошибок
try {
if (VarType(gREQUEST)!=9) gREQUEST = CreateOleObject('WinHttp.WinHttpRequest.5.1'); // Если ранее не создавался объект, то создаём
gREQUEST.open('GET', sLink, false);
for (i=1; i <= WordCount(gsHeaders, '\r\n') ; i++) {
if (!HmsRegExMatch2('^(.*?):(.*)', ExtractWord(i, gsHeaders, '\r\n'), sHeaderName, sHeaderValue)) continue;
gREQUEST.SetRequestHeader(sHeaderName, sHeaderValue);
}
gREQUEST.send();
sData = gREQUEST.ResponseText;
if (gREQUEST.Status!=200) {
HmsRegExMatch('"message"\\s*:\\s*"(.*?)"', sData, sErr);
HmsLogMessage(2, "Ошибка API: "+HmsHtmlToText(HmsJsonDecode(sErr)));
}
} except {
sData = HmsDownloadUrl(sLink, gsHeaders, true);
if (sData=='') HmsLogMessage(2, "Ошибка API: запрос вернул пустой результат. "+sLink);
}
return sData;
}
///////////////////////////////////////////////////////////////////////////////
// Показ информации, сохранённой в ссылке
void ShowInfo() {
string sTitle, sCateg, sInfo, sDescr, sImg, prefix='youtubev3';
TStrings INFO = TStringList.Create();
INFO.Text = PodcastItem[1001001];
sTitle = INFO.Values['Title' ];
sCateg = INFO.Values['Categ' ];
sInfo = INFO.Values['Info' ];
sDescr = INFO.Values['Descr' ];
sImg = INFO.Values['Poster'];
INFO.Free();
MediaResourceLink = GenerateVideoInfo(prefix, sTitle, sInfo, sCateg, sDescr, sImg);
}
///////////////////////////////////////////////////////////////////////////////
// Генерирование картинок и строки для ffmpeg
char GenerateVideoInfo(char prefix, char sTitle, char sInfo='', char sCateg='', char sDescr='', char sImg='', int mode=0, char sDirID='') {
char sHtml, sPost, sVal, sCol, sLink, sData, sPos, sImagesDir;
int i, n, nSeconds, nSecDel, nMaxImages, nMaxTextLength, nPage=0; double nH, nW;
char sFileImage, sCmd, sColor, sTime; TRegExpr reChannel, reCast;
int xMargin=15, yMargin=15;
nSeconds = 0; nMaxImages = 4; nMaxTextLength = 0;
if (HmsRegExMatch('--xmargin=(\\d+)', mpPodcastParameters, sVal)) xMargin=StrToInt(sVal);
if (HmsRegExMatch('--ymargin=(\\d+)', mpPodcastParameters, sVal)) yMargin=StrToInt(sVal);
if (Pos('--lq', mpPodcastParameters)>0) {
nH = cfgTranscodingScreenHeight / 2;
nW = cfgTranscodingScreenWidth / 2;
} else if (HmsRegExMatch2('--pr=(\\d+)x(\\d+)', mpPodcastParameters, sVal, sPos)) {
nH = StrToInt(sPos);
nW = StrToInt(sVal);
} else {
nH = cfgTranscodingScreenHeight;
nW = cfgTranscodingScreenWidth;
}
if (sDirID=='') sDirID = 'info_'+PodcastItem.ItemID;
sImagesDir = IncludeTrailingBackslash(ExtractShortPathName(HmsTempDirectory))+Trim(prefix);
sImagesDir = IncludeTrailingBackslash(sImagesDir)+sDirID;
ForceDirectories(sImagesDir);
sFileImage = IncludeTrailingBackslash(sImagesDir)+'info_';
sPost = '';
TStrings INFO = TStringList.Create();
INFO.Values['prfx' ] = prefix;
INFO.Values['title'] = sTitle;
INFO.Values['info' ] = sInfo;
INFO.Values['categ'] = sCateg;
INFO.Values['descr'] = sDescr;
INFO.Values['xpic' ] = '15';
INFO.Values['ypic' ] = '15';
INFO.Values['w' ] = IntToStr(Round(nW));
INFO.Values['h' ] = IntToStr(Round(nH));
INFO.Values['xm' ] = IntToStr(xMargin);
INFO.Values['ym' ] = IntToStr(yMargin);
INFO.Values['fz' ] = '3';
INFO.Values['fzdescr'] = IntToStr(Round(nH/22));
INFO.Values['fztitle'] = IntToStr(Round(nH/18));
INFO.Values['fzinfo' ] = IntToStr(Round(nH/25));
if (mode==1) {
INFO.Values['fzdescr'] = IntToStr(Round(nH/16));
INFO.Values['fztitle'] = IntToStr(Round(nH/10));
INFO.Values['fzinfo' ] = IntToStr(Round(nH/14));
INFO.Values['xm'] = IntToStr(Round(nH/14));
} else if ((mode==2) || (mode==3)) {
INFO.Values['ct' ] = '000';
INFO.Values['ctitle'] = '822';
INFO.Values['cinfo' ] = '223';
INFO.Values['ccateg'] = '255';
INFO.Values['fz'] = '6';
INFO.Values['ns'] = '1';
INFO.Values['bg'] = './backgrounds/white.png';
INFO.Values['wpic' ] = IntToStr(Round(nH/3));
if (mode==2) INFO.Values['wpic'] = IntToStr(Round(nH/2));
}
if (sImg !='') {
INFO.Values['urlpic'] = sImg;
INFO.Values['wpic' ] = IntToStr(Round(nH/2));
}
n = 0; TStrings FILELIST = TStringList.Create();
try {
HmsGetFileList(sImagesDir, FILELIST, '*.jpg');
n = FILELIST.Count;
} finally { FILELIST.Free(); }
if (n<4) {
// Получаем ссылки на сформированные картинки, пока в параметрах встречаем lastpos=N
sPos = '0'; nPage = 0;
do {
sColor = ''; sTime = ''; nPage++;
if (nPage>1) {
sData = Copy(sDescr, 1, StrToInt(sPos)); // Блок, успевший попасть на экран
// Определяем последний использующийся цвет
HmsRegExMatch('.*(\\d{2}:\\d{2}\\s*?-\\s*?\\d{2}:\\d{2})', sData, sTime, 1, PCRE_SINGLELINE);
sTime += ' ... ';
}
sDescr = sColor+sTime+Trim(Copy(sDescr, StrToInt(sPos), 999999));
INFO.Values['descr'] = sDescr;
sPost = '';
for (n=0; n<INFO.Count; n++) sPost += '&'+Trim(INFO.Names[n])+'='+HmsHttpEncode(INFO.Values[INFO.Names[n]]);
sLink = HmsSendRequestEx('wonky.lostcut.net', '/videopreview.php', 'POST',
'application/x-www-form-urlencoded', '', sPost, 80, 0, '', true);
if (LeftCopy(sLink, 4)!='http') {HmsLogMessage(2, 'Ошибка получения картинки видеоинформации'); return;}
HmsDownloadURLToFile(sLink, sFileImage);
for (n=0; n<5; n++) { nSeconds++; CopyFile(sFileImage, sFileImage+Format('%.3d.jpg', [nSeconds]), false); }
if (nPage>=nMaxImages) break;
} while (HmsRegExMatch2('^(.*?)\\?lastpos=(\\d+)', sLink, sLink, sPos));
}
INFO.Free();
char sFileMP3 = ExtractShortPathName(HmsTempDirectory)+'\\silent.mp3';
try {
if (!FileExists(sFileMP3)) HmsDownloadURLToFile('http://wonky.lostcut.net/mp3/silent.mp3', sFileMP3);
sFileMP3 = '-i "'+sFileMP3+'"';
} except { sFileMP3=''; }
sLink = Format('%s -f image2 -r 1 -i "%s" -c:v libx264 -r 30 -pix_fmt yuv420p ', [sFileMP3, sFileImage+'%03d.jpg']);
PodcastItem[mpiTimeLength] = HmsTimeFormat(nSeconds)+'.000';
return sLink;
}
///////////////////////////////////////////////////////////////////////////////
// Создание информационной ссылки
void InfoItem(string sMsg) {
THmsScriptMediaItem Item = HmsCreateMediaItem('Info'+Str(PodcastItem.ChildCount), PodcastItem.ItemID);
Item[mpiTitle ] = sMsg;
Item[mpiThumbnail ] = 'http://wonky.lostcut.net/vids/info.jpg';
Item[mpiCreateDate] = IncTime(gStart, 0, -gnTotalItems, 0, 0); gnTotalItems++;
HmsLogMessage(2, sMsg);
}
///////////////////////////////////////////////////////////////////////////////
// Запрос авторизации для нового устройства
void RequestNewCode() {
string sPost, sData, sRet; TJsonObject JSON = TJsonObject.Create();
try {
sPost = 'scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube&client_id='+gsClientId;
sData = HmsSendRequestEx('accounts.google.com', '/o/oauth2/device/code', 'POST', 'application/x-www-form-urlencoded', '', sPost, 443, 0, sRet, true);
JSON.LoadFromString(sData);
gACCESS.Values['code' ] = JSON.S['device_code'];
gACCESS.Values['verification_url'] = JSON.S['verification_url'];
gACCESS.Values['user_code' ] = JSON.S['user_code'];
gACCESS.Values['expires_date' ] = '';
SaveAccessInfo();
InfoItem('Перейдите по адресу '+JSON.S['verification_url']);
InfoItem('Введите код: ' +JSON.S['user_code' ]);
InfoItem('Потом обновите раздел.');
} finally { JSON.Free; }
}
///////////////////////////////////////////////////////////////////////////////
// Авторизация
bool Login(bool bSilent=false) {
TJsonObject JSON; string sData, sPost, sRet; bool bResult=false;
JSON = TJsonObject.Create();
try {
if (TimeStamp1970ToDateTime(StrToIntDef(gACCESS.Values['expires_date'], 0), false)>Now) { bResult=true; return true; }
if (gACCESS.Values['code']!='') {
if (bSilent) return false;
sPost = 'client_secret='+gsClientSecret+'&code='+gACCESS.Values['code']+'&client_id='+gsClientId+'&grant_type=http%3A%2F%2Foauth.net%2Fgrant_type%2Fdevice%2F1.0';
sData = HmsSendRequestEx('www.googleapis.com', '/oauth2/v4/token', 'POST', 'application/x-www-form-urlencoded', '', sPost, 443, 0, sRet, true);
JSON.LoadFromString(sData);
if (JSON.S['access_token']!='') {
gACCESS.Values['access_token' ] = JSON.S['access_token' ];
gACCESS.Values['refresh_token'] = JSON.S['refresh_token'];
gACCESS.Values['expires_date' ] = Str(DateTimeToTimeStamp1970(Now, false)+JSON.I['expires_in']);
gACCESS.Values['code'] = '';
SaveAccessInfo();
} else if (Pos('428 Precondition Required', sRet)>0) {
if (!bSilent) {
InfoItem('Ожидание подтверждения авторизации');
InfoItem('По адресу '+gACCESS.Values['verification_url']);
InfoItem('Ввести код: '+gACCESS.Values['user_code']);
}
return false;
}
}
if (gACCESS.Values['access_token']=='') { if (!bSilent) RequestNewCode(); return false; }
// Проверка работоспособности токена
sData = HmsDownloadURL('https://www.googleapis.com/oauth2/v3/tokeninfo?access_token='+gACCESS.Values['access_token']);
JSON.LoadFromString(sData);
if (!JSON.B['azp']) {
sPost = 'client_id='+gsClientId+'&client_secret='+gsClientSecret+'&refresh_token='+gACCESS.Values['refresh_token']+'&grant_type=refresh_token';
sData = HmsSendRequestEx('www.googleapis.com', '/oauth2/v4/token', 'POST', 'application/x-www-form-urlencoded', '', sPost, 443, 0, sRet, true);
JSON.LoadFromString(sData);
gACCESS.Values['access_token'] = JSON.S['access_token'];
}
gACCESS.Values['expires_date'] = Str(DateTimeToTimeStamp1970(Now, false)+JSON.I['expires_in']);
SaveAccessInfo();
bResult = (gACCESS.Values['access_token']!='');
} finally {
JSON.Free;
if (bResult) {
gsHeaders += 'Authorization: Bearer '+gACCESS.Values['access_token']+'\r\n';
}
}
return bResult;
}
///////////////////////////////////////////////////////////////////////////////
// Получение ссылки на Youtube
bool GetLink_Youtube33(string sLink) {
string sData, sVideoID='', sAudio='', sSubtitlesLanguage='ru',
sSubtitlesUrl, sFile, sVal, sMsg, sConfig, sHeaders, hlsUrl, subsUrl, jsUrl,
streamMap, algorithm, sType, itag, sig, alg, s, sp; TStringList QLIST;
int i, n, w, num, height, priority, minPriority = 90, selHeight, maxHeight = 1080, audioQual;
TJsonObject JSON, PLAYER_RESPONSE, VIDEO; TJsonArray FORMATS, SUBS; TRegExpr RegEx;
sHeaders = 'Referer: '+sLink+'\r\n'+
'User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36\r\n'+
'Origin: https://www.youtube.com\r\n';
if (HmsRegExMatch('--maxheight=(\\d+)', mpPodcastParameters, sVal)) maxHeight = StrToInt(sVal);
HmsRegExMatch('--sublanguage=(\\w{2})', mpPodcastParameters, sSubtitlesLanguage);
bool bSubtitles = Pos('--subtitles' , mpPodcastParameters)>0;
bool bAdaptive = Pos('--adaptive' , mpPodcastParameters)>0;
bool bQualLog = Pos('--qualitylog', mpPodcastParameters)>0;
if (!HmsRegExMatch('[\\?&]v=([^&]+)' , sLink, sVideoID))
if (!HmsRegExMatch('youtu.be/([^&]+)' , sLink, sVideoID))
HmsRegExMatch('/(?:embed|v)/([^\\?]+)', sLink, sVideoID);
if (sVideoID=='') return;
sLink = gsUrlBase+'/watch?v='+sVideoID+'&hl=ru';
sData = HmsDownloadURL(sLink, sHeaders, true);
sData = HmsRemoveLineBreaks(sData);
if (!HmsRegExMatch('player.config\\s*=\\s*({.*?});', sData, sConfig, 1, PCRE_SINGLELINE)) {
HmsLogMessage(2 , mpTitle+': No player.config data in loaded page.');
return;
}
JSON = TJsonObject.Create();
PLAYER_RESPONSE = TJsonObject.Create();
QLIST = TStringList.Create();
try {
JSON.LoadFromString(sConfig);
PLAYER_RESPONSE.LoadFromString(JSON.S['args\\player_response']);
// Если есть субтитры и в дополнительных параметрах указано их показывать - загружаем
if (bSubtitles && PLAYER_RESPONSE.B['captions\\playerCaptionsTracklistRenderer\\captionTracks']) {
string sTime1, sTime2, engSubs; float nStart, nDur;
SUBS = PLAYER_RESPONSE.O['captions\\playerCaptionsTracklistRenderer\\captionTracks'].AsArray;
for (i=0; i < SUBS.Length; i++) {
if (SUBS[i].S['languageCode']==sSubtitlesLanguage) subsUrl = SUBS[i].S['baseUrl'];
if (SUBS[i].S['languageCode']=='en' ) engSubs = SUBS[i].S['baseUrl'];
}
if ((subsUrl=='') && (engSubs!='')) subsUrl = engSubs+'&tlang='+sSubtitlesLanguage;
if (subsUrl!='') {
sData = HmsDownloadURL(subsUrl+'&fmt=srv3', sHeaders, true);
sMsg = ''; i = 0;
RegEx = TRegExpr.Create('(<(text|p).*?</(text|p)>)', PCRE_SINGLELINE); // Convert to srt format
try {
if (RegEx.Search(sData)) do {
if (HmsRegExMatch('start="([\\d\\.]+)', RegEx.Match, sVal)) nStart = StrToFloat(ReplaceStr(sVal, '.', ','))*1000;
else if (HmsRegExMatch('t="(\\d+)' , RegEx.Match, sVal)) nStart = StrToFloat(sVal);
if (HmsRegExMatch('dur="([\\d\\.]+)' , RegEx.Match, sVal)) nDur = StrToFloat(ReplaceStr(sVal, '.', ','))*1000;
else if (HmsRegExMatch('d="(\\d+)' , RegEx.Match, sVal)) nDur = StrToFloat(sVal);
sTime1 = HmsTimeFormat(Int(nStart/1000))+','+RightCopy(Str(nStart), 3);
sTime2 = HmsTimeFormat(Int((nStart+nDur)/1000))+','+RightCopy(Str(nStart+nDur), 3);
sMsg += Format("%d\n%s --> %s\n%s\n\n", [i, sTime1, sTime2, HmsHtmlToText(HmsHtmlToText(RegEx.Match(0), 65001))]);
i++;
} while (RegEx.SearchAgain());
} finally { RegEx.Free(); }
sFile = HmsSubtitlesDirectory+'\\Youtube\\'+PodcastItem.ItemID+'.'+sSubtitlesLanguage+'.srt';
HmsStringToFile(sMsg, sFile);
if (Trim(PodcastItem[mpiSubtitleLanguage])!='') bAdaptive = false;
PodcastItem[mpiSubtitleLanguage] = sFile;
}
}
hlsUrl = PLAYER_RESPONSE.S['streamingData\\hlsManifestUrl'];
jsUrl = JSON.S['assets\\js'];
if (hlsUrl!='') {
MediaResourceLink = ' '+hlsUrl;
bAdaptive = false;
sData = HmsDownloadUrl(hlsUrl, sHeaders, true);
RegEx = TRegExpr.Create('BANDWIDTH=(\\d+).*?RESOLUTION=(\\d+)x(\\d+).*?(http[^#]*)', PCRE_SINGLELINE);
try {
if (RegEx.Search(sData)) do {
sLink = '' + RegEx.Match(4);
height = StrToIntDef(RegEx.Match(3), 0);
if (mpPodcastMediaFormats!='') {
priority = HmsMediaFormatPriority(height, mpPodcastMediaFormats);
if ((priority>=0) && (priority>minPriority)) {
MediaResourceLink = sLink; minPriority = priority;
}
} else if ((height > selHeight) && (height <= maxHeight)) {
MediaResourceLink = sLink; selHeight = height;
}
QLIST.Values[Format('%.4d', [height])] = sLink;
} while (RegEx.SearchAgain());
} finally { RegEx.Free(); }
} else if (PLAYER_RESPONSE.B['streamingData\\formats']) {
FORMATS = PLAYER_RESPONSE.O['streamingData\\formats'].AsArray;
if (FORMATS[0].B['signatureCipher'])
algorithm = HmsDownloadURL('https://hms.lostcut.net/yt/getalgo.php?jsurl='+HmsHttpEncode(jsUrl));
for(i=0; i<FORMATS.Length; i++) {
VIDEO = FORMATS[i];
if (VIDEO.B['signatureCipher']) {
sLink = HmsHttpDecode(ExtractParam(VIDEO.S['signatureCipher'], 'url', '', '&'));
sig = HmsHttpDecode(ExtractParam(VIDEO.S['signatureCipher'], 's' , '', '&'));
sp = HmsHttpDecode(ExtractParam(VIDEO.S['signatureCipher'], 'sp' , '', '&'));
if (sig!='') {
for (w=1; w<=WordCount(algorithm, ' '); w++) {
alg = ExtractWord(w, algorithm, ' ');
if (Length(alg)<1) continue;
if (Length(alg)>1) TryStrToInt(Copy(alg, 2, 4), num);
if (alg[1]=='r') {s=''; for(n=Length(sig); n>0; n--) s+=sig[n]; sig = s; } // Reverse
if (alg[1]=='s') {sig = Copy(sig, num+1, Length(sig)); } // Clone
if (alg[1]=='w') {n = (num-Trunc(num/Length(sig)))+1; Swap(sig[1], sig[n]);} // Swap
}
}
sLink += '&'+sp+'='+sig;
} else {
sLink = VIDEO.S['url'];
}
height = VIDEO.I['height'];
if (height>0) QLIST.Values[Format('%.4d', [height])] = sLink;
if (mpPodcastMediaFormats!='') {
priority = HmsMediaFormatPriority(height, mpPodcastMediaFormats);
if ((priority>=0) || (priority<minPriority)) {
MediaResourceLink = sLink; minPriority = priority; selHeight = height;
}
} else if ((height>selHeight) && (height<= maxHeight)) {
MediaResourceLink = sLink; selHeight = height;
} else if ((height>=selHeight) && (height<= maxHeight) && (VIDEO.I['itag'] in ([18,22,37,38,82,83,84,85]))) {
// Если высота такая же, но формат MP4 - то выбираем именно его (делаем приоритет MP4)
MediaResourceLink = sLink; selHeight = height;
}
}
}
if (bAdaptive || (MediaResourceLink=='') && PLAYER_RESPONSE.B['streamingData\\adaptiveFormats']) {
FORMATS = PLAYER_RESPONSE.O['streamingData\\adaptiveFormats'].AsArray;
if (FORMATS[0].B['cipher'] && (algorithm==''))
algorithm = HmsDownloadURL('https://hms.lostcut.net/yt/getalgo.php?jsurl='+HmsHttpEncode(jsUrl));
for(i=0; i<FORMATS.Length; i++) {
VIDEO = FORMATS[i];
if (VIDEO.B['cipher']) {
sLink = HmsHttpDecode(ExtractParam(VIDEO.S['cipher'], 'url', '', '&'));
sig = HmsHttpDecode(ExtractParam(VIDEO.S['cipher'], 's' , '', '&'));
sp = HmsHttpDecode(ExtractParam(VIDEO.S['cipher'], 'sp' , '', '&'));
if (sig!='') {
for (w=1; w<=WordCount(algorithm, ' '); w++) {
alg = ExtractWord(w, algorithm, ' ');
if (Length(alg)<1) continue;
if (Length(alg)>1) TryStrToInt(Copy(alg, 2, 4), num);
if (alg[1]=='r') {s=''; for(n=Length(sig); n>0; n--) s+=sig[n]; sig = s; } // Reverse
if (alg[1]=='s') {sig = Copy(sig, num+1, Length(sig)); } // Clone
if (alg[1]=='w') {n = (num-Trunc(num/Length(sig)))+1; Swap(sig[1], sig[n]);} // Swap
}
}
sLink += '&'+sp+'='+sig;
} else {
sLink = VIDEO.S['url'];
}
if (VIDEO.B['audioSampleRate'] && (audioQual < VIDEO.I['bitrate'])) {
sAudio = sLink; audioQual = VIDEO.I['bitrate']; continue;
}
height = VIDEO.I['height'];
if (height>0) QLIST.Values[Format('%.4d', [height])] = sLink;
if (mpPodcastMediaFormats!='') {
priority = HmsMediaFormatPriority(height, mpPodcastMediaFormats);
if ((priority>=0) || (priority<minPriority)) {
MediaResourceLink = sLink; minPriority = priority; selHeight = height;
}
} else if ((height>selHeight) && (height<= maxHeight)) {
MediaResourceLink = sLink; selHeight = height;
}
}
sVal = ""; if (Trim(mpTimeStart)!="") sVal = " -ss "+mpTimeStart;
if (bAdaptive && (sAudio!='')) {
if (Pos('--downloadaudio', mpPodcastParameters)>0) {
sFile = HmsTempDirectory+'\\'+PodcastItem.ItemID+'.webm';
if (!FileExists(sFile))
HmsDownloadURLToFile(sAudio, sFile, sHeaders, true);
} else sFile = sAudio;
MediaResourceLink = '-hide_banner -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2 -fflags +genpts -i "'+Trim(MediaResourceLink)+'"'+sVal+' -i "'+Trim(sFile)+'"';
}
}
// Если еще не установлена реальная длительность видео - устанавливаем
if ((Trim(mpTimeLength)=='') || (RightCopy(mpTimeLength, 6)=='00.000') && (hlsUrl=='')) {
PodcastItem[mpiTimeLength] = HmsTimeFormat(PLAYER_RESPONSE.I['videoDetails\\lengthSeconds']);
}
if (bQualLog) {
QLIST.Sort();
sMsg = 'Доступное качество: ';
for (i = 0; i < QLIST.Count; i++) {
if (i>0) sMsg += ', ';
sMsg += IntToStr(StrToInt(QLIST.Names[i])); // Обрезаем лидирующие нули
}
sMsg += '. Выбрано: ' + Str(selHeight);
HmsLogMessage(1, mpTitle+'. '+sMsg);
}
} finally { JSON.Free; PLAYER_RESPONSE.Free; QLIST.Free; }
}
///////////////////////////////////////////////////////////////////////////////
// Показ видео сообщения
bool VideoMessage(string sMsg) {
string sFileMP3 = HmsTempDirectory+'\\sa.mp3';
string sFileImg = HmsTempDirectory+'\\youtubemsg_';
sMsg = HmsHtmlToText(HmsJsonDecode(sMsg));
int nH = cfgTranscodingScreenHeight;
int nW = cfgTranscodingScreenWidth;
HmsLogMessage(2, mpTitle+': '+sMsg);
string sLink = Format('http://wonky.lostcut.net/videomessage.php?h=%d&w=%d&captsize=%d&fontsize=%d&caption=%s&msg=%s', [nH, nW, Round(nH/8), Round(nH/17), gsPodcastName, HmsHttpEncode(sMsg)]);
HmsDownloadURLToFile(sLink, sFileImg);
for (int i=1; i<=7; i++) CopyFile(sFileImg, sFileImg+Format('%.3d.jpg', [i]), false);
try {
if (!FileExists(sFileMP3)) HmsDownloadURLToFile('http://wonky.lostcut.net/mp3/sa.mp3', sFileMP3);
sFileMP3 = '-i "'+sFileMP3+'" ';
} except { sFileMP3 = ''; }
MediaResourceLink = Format('%s-f image2 -r 1 -i "%s" -c:v libx264 -r 30 -pix_fmt yuv420p ', [sFileMP3, sFileImg+'%03d.jpg']);
return false;
}
///////////////////////////////////////////////////////////////////////////////
// Создание ссылки-ошибки
void ErrorItem(string sMsg) {
THmsScriptMediaItem Item = HmsCreateMediaItem('Err', PodcastItem.ItemID);
Item[mpiTitle ] = sMsg;
Item[mpiThumbnail] = 'http://wonky.lostcut.net/icons/symbol-error.png';
HmsLogMessage(2, sMsg);
}
///////////////////////////////////////////////////////////////////////////////
// Создание видео-ссылки
THmsScriptMediaItem CreateVideo(string sID, string sTitle, string sImg, string sTime, string sDate, string sComment='') {
if (LeftCopy(sID, 4)!='http') sID = gsUrlBase+'/watch?v='+Trim(sID);
THmsScriptMediaItem Item = HmsCreateMediaItem(sID, PodcastItem.ItemID);
Item[mpiTitle ] = sTitle;
Item[mpiThumbnail ] = sImg;
Item[mpiTimeLength ] = sTime;
Item[mpiCreateDate ] = sDate;
Item[mpiComment ] = sComment;
gnTotalItems++;
return Item;
}
///////////////////////////////////////////////////////////////////////////////
// Создание папки или обновляемого подкаста
THmsScriptMediaItem CreateFolder(string sName, string sLink, string sImg='') {
THmsScriptMediaItem Item = PodcastItem.AddFolder(sLink); // Создаём папку с указанной ссылкой
Item[mpiTitle ] = sName; // Присваиваем наименование
Item[mpiThumbnail ] = sImg; // Картинка папки
Item[mpiCreateDate ] = IncTime(gStart, 0, -gnTotalItems, 0, 0); gnTotalItems++;
return Item;
}
///////////////////////////////////////////////////////////////////////////////
// Перевод некоторых английских фраз из информации о плейлистах
string Translate(string sText) {
string sVal, sResult=sText; TStrings DICT = TStringList.Create();
DICT.Values['Best of YouTube'] = 'Лучшее на YouTube';
DICT.Values['Paid channels' ] = 'Платные каналы';
DICT.Values['Top YouTube Collections'] = 'Лучшие коллекции YouTube';
DICT.Values['Popular Artists'] = 'Популярные исполнители';
if (Trim(DICT.Values[sText])!='') sResult = DICT.Values[sText];
DICT.Free();
if (HmsRegExMatch('(Popular Artist Mixes)', sResult, sVal)) sResult = ReplaceStr(sResult, sVal, 'Миксы популярных исполнителей');
return sResult;
}
///////////////////////////////////////////////////////////////////////////////
// Получение безопасного валидного наименования
string SafeName(string sName) {
sName = ReplaceStr(sName, '/' , '-');
sName = ReplaceStr(sName, '\\', '-');
sName = Translate(sName);
return sName;
}
///////////////////////////////////////////////////////////////////////////////
// Получение длительности для HMS из формата youtube
string ConvertTime(string sTime) {
string sVal; int nSeconds = 0;
if (HmsRegExMatch('(\\d+)H', sTime, sVal)) nSeconds += StrToInt(sVal)*3600;
if (HmsRegExMatch('(\\d+)M', sTime, sVal)) nSeconds += StrToInt(sVal)*60;
if (HmsRegExMatch('(\\d+)S', sTime, sVal)) nSeconds += StrToInt(sVal);
if (nSeconds==0) nSeconds = 600;
return HmsTimeFormat(nSeconds)+'.000';
}
///////////////////////////////////////////////////////////////////////////////
// Получение даты создания для HMS из формата youtube
string ConvertDate(string sDate) {
string sY, sM, sD, sTime;
HmsRegExMatch3('(\\d{4}).(\\d{2}).(\\d{2})', sDate, sY, sM, sD);
HmsRegExMatch ('T(\\d{2}:\\d{2}:\\d{2})' , sDate, sTime );
return Format('%s.%s.%s %s', [sD, sM, sY, sTime]);
}
///////////////////////////////////////////////////////////////////////////////
// Универсальная функция создания элементов видео (или сбора информации)
string CreateItems(string sPath, string sObject, string sParam, string sFiltr='') {
string sLink, sData, sName, sID, sImg, sCh, sVal, sResult='', sTime;
int i, nLevel; TJsonObject JSON, ITEM; TJsonArray ITEMS;
if (HmsRegExMatch('pageToken=([\\w-_]+)', mpFilePath, sVal)) sParam += '&pageToken='+sVal;
if (HmsRegExMatch('&level=(\\d+)' , mpFilePath, sVal)) nLevel = StrToInt(sVal);
sData = APIRequest(sObject, sParam);
JSON = TJsonObject.Create();
try {
JSON.LoadFromString(sData);
sID = JSON.S('nextPageToken');
if ((sID!='') && (nLevel<3)) {
if (sObject=='search') mpFilePath = '-search='+sParam;
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
if (HmsRegExMatch('(&level=\\d+)' , mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
sCh = '?'; if (Pos('?', mpFilePath)>0) sCh = '&';
CreateFolder('Следующая страница', mpFilePath+sCh+'pageToken='+sID+'&level='+Str(nLevel+1));
}
ITEMS = JSON.A('items');
if (ITEMS != nil) {
for (i=0; i<ITEMS.Length; i++) {
ITEM = ITEMS[i];
// Хоть playlistItems и возвращает список видео, но там нет длительности
// Поэтому собираем ID видео, чтобы потом вызвать videos со списком этих ID
if (sObject=='playlistItems') {
if (sResult!='') sResult += ',';
sResult += ITEM.S('contentDetails\\videoId');
continue;
} else if (sObject=='activities') {
if (sFiltr!='') if (ITEM.S[sFiltr]=='') continue;
if (sResult!='') sResult += ',';
sID = ITEM.S('contentDetails\\recommendation\\resourceId\\videoId');
if (sID=='') sID = ITEM.S('contentDetails\\playlistItem\\resourceId\\videoId');
if (sID=='') sID = ITEM.S('contentDetails\\upload\\videoId');
if (sID!='') sResult += sID;
continue;
} else if (sObject=='search') {
sVal = ITEM.S('id\\kind');
if (sVal=='youtube#playlist') {
sID = ITEM.S('id\\playlistId');
sPath = gsUrlBase+'/playlist?list=';
} else if (sVal=='youtube#channel') {
sID = ITEM.S('id\\channelId');
sPath = gsUrlBase+'/channel/';
} else {
sID = ITEM.S('id\\videoId');
sPath = 'video';
if (sResult!='') sResult += ',';
if (sID!='') sResult += sID;
continue;
}
} else if (sObject=='videoCategories') {
if (!ITEM.B('snippet\\assignable')) continue;
sID = ITEM.S('id');
if (sID=='1') sID = '30';
} else if (sObject=='subscriptions') {
sID = ITEM.S('snippet\\resourceId\\channelId');
} else {
sID = ITEM.S('id');
}
if (sID=='') continue;
if (LeftCopy(sPath, 6)=='getids') { if (sResult!='') sResult+=','; sResult += sID; continue; }
sName = ITEM.S('snippet\\localized\\title');
if (sName=='') sName = ITEM.S('snippet\\title');
sImg = ITEM.S('snippet\\thumbnails\\medium\\url');
sName = SafeName(HmsUtf8Decode(sName));
sVal = ITEM.S('contentDetails\\itemCount'); if (sVal=='0') continue;
if (sVal!='') sName += ' ['+sVal+']';
if (Pos(sPath, '%s')>0) sLink = Format(sPath, [sID]);
else sLink = sPath + sID;
if (sPath=='video' ) {
if (ITEM.S('snippet\\liveBroadcastContent')=='live') sTime = '04:00:00.000';
else sTime = ConvertTime(ITEM.S('contentDetails\\duration'));
if (Pos('DE', ITEM.S('contentDetails\\regionRestriction\\blocked'))>0) sID += '¬de=1';
}
if (sPath=='video' ) CreateVideo(sID, sName, sImg, sTime, ConvertDate(ITEM.S('snippet\\publishedAt')), HmsUtf8Decode(ITEM.S('snippet\\description')));
else CreateFolder(sName, sLink, sImg);
}
}
} finally { JSON.Free(); }
return sResult;
}
///////////////////////////////////////////////////////////////////////////////
// Создание каналов конкретной категории
void CreateChannels(string sID) {
Login(true);
CreateItems(gsUrlBase+'/channel/', 'channels', 'part=snippet,contentDetails&categoryId='+sID);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка категорий назначенных самим youtube автоматически
void CreateGuideCategories() {
Login(true);
CreateItems(gsUrlBase+'/channels/', 'guideCategories', 'part=snippet');
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка категорий
void CreateVideoCategories() {
Login(true);
CreateItems('-category=', 'videoCategories', 'part=snippet');
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео по выбранной категории
void CreateVideosByCategory(string sID) {
Login(true);
CreateItems('video', 'videos', 'part=snippet,contentDetails&chart=mostPopular&videoCategoryId='+sID);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео выбранного плейлиста
void CreatePlaylistVideos(string sID) {
string sIDs, sVal;
Login(true);
sIDs = CreateItems('getids', 'playlistItems', 'part=contentDetails&fields=nextPageToken,items(contentDetails%2FvideoId)&playlistId='+sID);
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
///////////////////////////////////////////////////////////////////////////////
// Создание подкастов каналов в родительской папке
void CreateMySubscriptions() {
if (!Login()) return;
TJsonObject SUBS = TJsonObject.Create();
try {
SUBS.LoadFromString(APIRequest('subscriptions', 'part=snippet&mine=true'));
if (SUBS["items"]==nil) return;
for (int n=0; n < SUBS["items"].AsArray.Length; n++) {
TJsonObject OBJECT = SUBS["items"].AsArray[n];
string ch = OBJECT.S['snippet\\resourceId\\channelId']; if (ch=='') continue;
THmsScriptMediaItem Item = PodcastItem.AddFolder(gsUrlBase+'/channel/'+ch);
Item[mpiTitle ] = HmsUtf8Decode(OBJECT.S['snippet\\title']);
Item[mpiComment ] = HmsUtf8Decode(OBJECT.S['snippet\\description']);
Item[mpiThumbnail ] = OBJECT.S['snippet\\thumbnails\\medium\\url'];
Item[mpiCreateDate] = ConvertDate(OBJECT.S['snippet\\publishedAt']); gnTotalItems++;
}
} finally { SUBS.Free; }
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка подписчиков
void CreateMySubscribers() {
if (!Login()) return;
CreateItems(gsUrlBase+'/channel/', 'subscriptions', 'part=snippet,contentDetails&mySubscribers=true');
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео по ключевому слову названия плейлиста ("WL", "likes"...)
void CreateMyPlaylist(string sKey) {
string sData, sID;
if (!Login()) return;
sData = APIRequest('channels', 'part=snippet,contentDetails&mine=true');
if (HmsRegExMatch('"'+sKey+'":\\s*?"(.*?)"', sData, sID)) CreatePlaylistVideos(sID);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео "Рекомендации"
void CreateMyRecommendations() {
string sIDs, sVal;
if (!Login()) return;
sIDs = CreateItems('getids', 'activities', 'part=contentDetails&home=true');
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
///////////////////////////////////////////////////////////////////////////////
// Получение списка ID через запятую по переданной json строке
string GetIDs(string sData, string sPath) {
string sID, sIDs;
TJsonObject JSON = TJsonObject.Create();
int nCount = 0;
try {
JSON.LoadFromString(sData);
if (JSON["items"].AsArray!=nil) {
for (int i=0; i < JSON["items"].AsArray.Length; i++) {
sID = JSON["items"].AsArray[i].S[sPath]; if (sID=='') continue;
if (sIDs=='') sIDs = sID; else sIDs += ','+sID;
nCount++; if (nCount >= 50) break;
}
}
} finally { JSON.Free; }
return sIDS;
}
///////////////////////////////////////////////////////////////////////////////
// Список новых видео в подписках (НОВЫЙ СПОСОБ)
void NewVideos() {
string sID, sIDs, sData, sName, sTime, sDesc, sChan; TJsonArray VIDEOS;
if (!Login()) return;
TJsonObject SUBS = TJsonObject.Create();
TJsonObject JSON = TJsonObject.Create();
TStringList LIST = TStringList.Create();
int nCount = 0;
try {
sData = APIRequest('subscriptions', 'part=snippet&mine=true');
SUBS.LoadFromString(sData);
if (SUBS["items"] != nil) {
for (int n=0; n < SUBS["items"].AsArray.Length; n++) {
sChan = SUBS["items"].AsArray[n].S['snippet\\resourceId\\channelId']; if (sChan=='') continue;
sData = APIRequest('activities', 'part=contentDetails&channelId='+sChan);
sIDs = GetIDs(sData, 'contentDetails\\upload\\videoId');
sData = APIRequest('videos', 'part=snippet,contentDetails&id='+sIDs);
JSON.LoadFromString(sData);
if (JSON["items"]==nil) continue;
for (int i=0; i < JSON["items"].AsArray.Length; i++) {
sID = JSON["items"].AsArray[i].S['id'];
sData = JSON["items"].AsArray[i].S['snippet\\publishedAt'];
sTime = JSON["items"].AsArray[i].S['contentDetails\\duration'];
sName = HmsUtf8Decode(JSON["items"].AsArray[i].S['snippet\\title']);
sDesc = HmsUtf8Decode(JSON["items"].AsArray[i].S['snippet\\description']);
LIST.Add(sData+'='+sID+'|'+sName+'|'+sTime+'|'+sDesc);
}
}
CreateVideosFromList(LIST);
}
} finally { SUBS.Free; JSON.Free; LIST.Free; }
}
///////////////////////////////////////////////////////////////////////////////
// Создание видео из переданного списка TStringList
void CreateVideosFromList(TStringList LIST) {
string sID, sLink, sName, sTime, sDate, sDesc, sImg;
if (LIST.Count==0) return;
LIST.Sort();
for (int i=LIST.Count-1; i>=0; i--) {
HmsRegExMatch2('^(.*?)=(.*?)\\|' , LIST[i], sDate, sID);
HmsRegExMatch3('\\|(.*?)\\|(.*?)\\|(.*)', LIST[i], sName, sTime, sDesc);
sLink = gsUrlBase+'/watch?v='+sID;
THmsScriptMediaItem Item = HmsCreateMediaItem(sLink, PodcastItem.ItemID);
Item[mpiTitle ] = sName;
Item[mpiComment ] = sDesc;
Item[mpiThumbnail ] = 'https://i.ytimg.com/vi/'+sID+'/mqdefault.jpg';
Item[mpiTimeLength] = ConvertTime(sTime);
Item[mpiCreateDate] = ConvertDate(sDate); gnTotalItems++;
}
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка новых видео в подписках
void CreateMyNewVideos() {
string sIDs, sDate, sID, sLink, sData, sPlaylistIDs, sName, sImg, sTime='10', sVal;
int i, n, nCount, nMaxPages; TJsonObject JSON, VIDEO; TJsonArray VIDEOS; TStringList LIST;
if (!Login()) return;
sPlaylistIDs = CreateItems('getids', 'subscriptions', 'part=snippet&mine=true');
LIST = TStringList.Create();
JSON = TJsonObject.Create();
for (i=1; i<=WordCount(sPlaylistIDs, ','); i++) {
sID = ExtractWord(i, sPlaylistIDs, ','); // ИД плейлиста, на который мы подписаны
sData = APIRequest('search', 'part=snippet&order=date&channelId='+sID);
JSON.LoadFromString(sData);
VIDEOS = JSON["items"].AsArray;
if (VIDEOS!=nil) {
for (n=0; n < VIDEOS.Length; n++) {
VIDEO = VIDEOS[n];
if (VIDEO.S['id\\kind']!='youtube#video') continue;
sID = VIDEO.S['id\\videoId'];
sDate = VIDEO.S['snippet\\publishedAt'];
sName = VIDEO.S['snippet\\title'];
LIST.Add(sDate+';'+sID+';'+HmsUtf8Decode(sName));
}
}
}
LIST.Sort();
nMaxPages = 1;
if (HmsRegExMatch('--pages=(\\d+)', mpPodcastParameters, sVal)) nMaxPages = StrToInt(sVal);
i=LIST.Count-1;
for (n=0; n<nMaxPages; n++) {
nCount=0; sIDs='';
while (i>=0) {
HmsRegExMatch3('(.*?);(.*?);(.*)', LIST[i], sDate, sID, sName);
if (sIDs!='') sIDs += ','; sIDs += sID;
i--; nCount++;
if (nCount >= 50) break;
}
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
LIST.Free(); JSON.Free();
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео выбранного канала
void CreateChannelVideos(string sChannelID) {
Login(true);
SearchVideosByParam('&q=&part=snippet&type=video&order=date&channelId='+sChannelID);
}
///////////////////////////////////////////////////////////////////////////////
// Поиск видео с выбранными параметрами
void SearchVideosByParam(string sParam) {
string sIDs, sVal;
Login(true);
sIDs = CreateItems('video', 'search', sParam);
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео выбранного региона
void CreateVideosByRegion(string sRegion) {
string sVal, sParam;
Login(true);
gsRegion = sRegion;
sParam = '&q=&part=snippet&type=video&order=rating®ionCode='+sRegion+'&relevanceLanguage='+sRegion;
if (HmsRegExMatch('category=([\\w-_]+)', mpFilePath, sVal)) sParam += '&videoCategoryId=' + sVal;
SearchVideosByParam(sParam);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка последних обновлений
void CreateMyActivities() {
string sIDs, sVal;
if (!Login()) return;
sIDs = CreateItems('getids', 'activities', 'part=contentDetails&mine=true');
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео конкретного канала (по его id)
void CreateChannelPlaylists(string sChannelID) {
Login(true);
CreateItems(gsUrlBase+'/playlist?list=', 'playlists', 'part=snippet,contentDetails&channelId='+sChannelID);
}
///////////////////////////////////////////////////////////////////////////////
// Создание секций конкретного канала ("Загруженные", "Плейлисты" и проч.)
void CreateChannelSections(string sChannelID, bool bForUser=false) {
string sLink, sData, sIDs='', sType, sID, sName, sChannelData, sVal;
int i, nCnt, nMax; TJsonObject JSON, ITEM; TJsonArray ITEMS; TStrings CHANNELS;
Login(true);
if (bForUser)
sChannelData = APIRequest('channels', 'part=snippet,contentDetails&forUsername='+sChannelID);
else
sChannelData = APIRequest('channels', 'part=snippet,contentDetails&id='+sChannelID);
HmsRegExMatch('"id":\\s*?"(.*?)"', sChannelData, sChannelID);
if ((Pos('--translate', mpPodcastParameters)>0) && HmsRegExMatch('"country"\\s*:\\s*"(.*?)"', sChannelData, sVal) && (sVal!=gsRegion)) {
if ((Pos('--subtitles', mpPodcastParameters)<1)) PodcastItem[mpiPodcastParameters] += ' --subtitles';
}
sData = APIRequest('channelSections', 'part=snippet,contentDetails&fields=items(id,snippet/type,snippet/title,snippet/localized/title,contentDetails)&channelId='+sChannelID);
JSON = TJsonObject.Create();
CHANNELS = TStringList.Create();
try {
JSON.LoadFromString(sData);
ITEMS = JSON.A('items');
if (ITEMS != nil) {
for (i=0; i<ITEMS.Length; i++) {
ITEM = ITEMS[i];
sType = ITEM.S('snippet\\type');
if (sType=='singlePlaylist') {
sID = ITEM.S('contentDetails\\playlists[0]');
CHANNELS.Values[sID] = '1';
} else if ((sType=='multiplePlaylists')||(sType=='multipleChannels')) {
sID = ITEM.S('id');
sName = ITEM.S('snippet\\localized\\title');
if (sName=='') sName = ITEM.S('snippet\\title');
sName = SafeName(HmsUtf8Decode(sName));
CreateFolder(sName, '-channelSection='+sID);
}
}
}
} finally { JSON.Free(); }
// Create single playlists
nCnt = 0; nMax = 4;
for (i=0; i<CHANNELS.Count; i++) {
sID = CHANNELS.Names[i];
nCnt++; if (nCnt>=50) {
CreateItems(gsUrlBase+'/playlist?list=', 'playlists', 'part=snippet,contentDetails&id='+sIDs);
nCnt = 1; sIDs = sID; nMax --; if (nMax<=0) break;
}
if (sIDs!='') sIDs += ','; sIDs += sID;
}
if (sIDs!='') CreateItems(gsUrlBase+'/playlist?list=', 'playlists', 'part=snippet,contentDetails&id='+sIDs);
CHANNELS.Free();
CreateFolder('Плейлисты', '-channelPlaylists='+sChannelID);
sLink = gsUrlBase+'/playlist?list=';
if (HmsRegExMatch('"uploads":\\s*?"(.*?)"' , sChannelData, sID)) CreateFolder('Загруженные видео', sLink+sID);
if (HmsRegExMatch('"likes":\\s*?"(.*?)"' , sChannelData, sID)) CreateFolder('Понравившиеся' , sLink+sID);
if (HmsRegExMatch('"favorites":\\s*?"(.*?)"', sChannelData, sID)) CreateFolder('Любимые' , sLink+sID);
}
///////////////////////////////////////////////////////////////////////////////
// Создание видео конкретной секции канала
void CreateByChannelSection(string sSectionID) {
string sLink, sData, sIDs='', sType, sID, sName;
int i; TJsonObject JSON, ITEM; TJsonArray ITEMS;
Login(true);
sData = APIRequest('channelSections', 'part=contentDetails&id='+sSectionID);
JSON = TJsonObject.Create();
try {
JSON.LoadFromString(sData);
sLink = gsUrlBase+'/channel/';
sIDs = JSON.S('items[0]\\contentDetails\\channels');
HmsRegExMatch('\\[(.*?)\\]', sIDs, sIDs);
sIDs = ReplaceStr(sIDs, '"', '');
if (WordCount(sIDs, ',')>50) {
sID = Copy(sIDs, 1, WordPosition(51, sIDs, ',')-2);
CreateItems(sLink, 'channels', 'part=snippet,contentDetails&id='+sID);
sIDs = Copy(sIDs, WordPosition(51, sIDs, ','), 99999);
if (WordCount(sIDs, ',')>50) sIDs = Copy(sIDs, 1, WordPosition(51, sIDs, ',')-2);
}
if (sIDs!='') CreateItems(sLink, 'channels', 'part=snippet,contentDetails&id='+sIDs);
sLink = gsUrlBase+'/playlist?list=';
sIDs = JSON.S('items[0]\\contentDetails\\playlists');
HmsRegExMatch('\\[(.*?)\\]', sIDs, sIDs);
sIDs = ReplaceStr(sIDs, '"', '');
if (WordCount(sIDs, ',')>50) {
sID = Copy(sIDs, 1, WordPosition(51, sIDs, ',')-2);
CreateItems(sLink, 'playlists', 'part=snippet,contentDetails&id='+sID);
sIDs = Copy(sIDs, WordPosition(51, sIDs, ','), 99999);
if (WordCount(sIDs, ',')>50) sIDs = Copy(sIDs, 1, WordPosition(51, sIDs, ',')-2);
}
if (sIDs!='') CreateItems(sLink, 'playlists', 'part=snippet,contentDetails&id='+sIDs);
} finally { JSON.Free(); }
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка своих собственных плейлистов
void CreateMyPlaylists() {
string sData, sLink, sID;
if (!Login()) return;
CreateItems(gsUrlBase+'/playlist?list=', 'playlists', 'part=snippet,contentDetails&mine=true');
}
///////////////////////////////////////////////////////////////////////////////
// Поиск видео по названию
void SearchVideos(string sName, bool bContinue=false) {
string sVal='', sParam = '', sIDs, sPodcastParams, sKey, sPossibleKeys; int i, nCnt;
if (!bContinue) {
sPodcastParams = mpFilePath + ' ' + PodcastItem.ItemParent[mpiFilePath] + ' ' + mpPodcastParameters;
sPossibleKeys = 'channelId,channelType,eventType,location,locationRadius,maxResults,order,publishedAfter,publishedBefore,regionCode,relevanceLanguage,safeSearch,topicId,type,videoCaption,videoCategoryId,videoDefinition,videoDimension,videoDuration,videoEmbeddable,videoLicense,videoSyndicated,videoType';
for (i=1; i<=WordCount(sPossibleKeys, ','); i++) {
sKey = ExtractWord(i, sPossibleKeys, ',');
if (HmsRegExMatch('-'+sKey+'=([\\w_\\-\\.,:]+)', sPodcastParams, sVal)) sParam += '&'+sKey+'=' +sVal;
}
if (PodcastItem.ItemParent[mpiComment]=='+') sName = PodcastItem.ItemParent[mpiTitle] + ' ' + Trim(sName);
if (ProgramVersion>'3.0') sIDs = CreateItems('video', 'search', 'part=snippet&q='+HmsHttpEncode(sName)+sParam);
else sIDs = CreateItems('video', 'search', 'part=snippet&q='+HmsHttpEncode(HmsUtf8Encode(sName))+sParam);
} else {
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', sName, sVal)) sName = ReplaceStr(sName, sVal, '');
sIDs = CreateItems('video', 'search', sName);
}
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
///////////////////////////////////////////////////////////////////////////////
// Создание структуры подкаста - папок меню
void CreateMyFolders() {
THmsScriptMediaItem Item;
CreateFolder('Новые видео в подписках', '-newVideos');
CreateFolder('Мои подписки' , gsUrlBase+'/feed/subscriptions');
CreateFolder('Плейлисты' , '-playlists');
//CreateFolder('Посмотреть позже' , gsUrlBase+'/playlist?list=WL');
CreateFolder('Загруженные видео', '-uploads');
CreateFolder('Понравившиеся' , '-likes');
CreateFolder('Просмотренные' , gsUrlBase+'/feed/history');
//CreateFolder('Рекомендации' , '-recommend');
//CreateFolder('Мои подписчики' , '-mySubscribers');
}
///////////////////////////////////////////////////////////////////////////////
// Г Л А В Н А Я П Р О Ц Е Д У Р А //
// ----------------------------------------------------------------------------
{
string s;
HmsRegExMatch('--language=(\\w+)' , mpPodcastParameters, gsLanguage);
HmsRegExMatch('--regionCode=([\\w-]+)', mpPodcastParameters, gsRegion );
// Проверка на упоротых, которые пытаются запустить "Обновление подкастов" для всего подкаста разом
if (InteractiveMode && (HmsCurrentMediaTreeItem.ItemClassName=='TVideoPodcastsFolderItem')) {
HmsCurrentMediaTreeItem.DeleteChildItems(); // Дабы обновления всех подкастов не запустилось - удаляем их.
ShowMessage('Обновление всех разделов разом запрещено!\nДля восстановления структуры\nзапустите "Создать ленты подкастов".');
return;
}
HmsRegExMatch('APIKey="(.*?)"' , mpPodcastParameters, gsAPIKey );
HmsRegExMatch('ClientId="(.*?)"' , mpPodcastParameters, gsClientId );
HmsRegExMatch('ClientSecret="(.*?)"', mpPodcastParameters, gsClientSecret);
if (PodcastItem.IsFolder) {
PodcastItem.DeleteChildItems(); // Удаление существующих ссылок
if ((gsAPIKey=='') || (gsClientId=='') || (gsClientSecret=='')) {
s = 'Не все параметры подкаста установлены (--APIKey, --ClientId или --ClientSecret)';
HmsLogMessage(2, s);
ErrorItem(s);
return;
}
// Анализ текущей ссылки
if (HmsRegExMatch('-newVideos' , mpFilePath, s)) CreateMyNewVideos(); //NewVideos();
else if (HmsRegExMatch('/feed/subscriptions', mpFilePath, s)) CreateMySubscriptions();
else if (HmsRegExMatch('-MyChannel' , mpFilePath, s)) CreateMyFolders();
else if (HmsRegExMatch('-playlists' , mpFilePath, s)) CreateMyPlaylists();
else if (HmsRegExMatch('/playlist\\?list=WL', mpFilePath, s)) CreateMyPlaylist('watchLater');
else if (HmsRegExMatch('-likes' , mpFilePath, s)) CreateMyPlaylist('likes');
else if (HmsRegExMatch('-favorites' , mpFilePath, s)) CreateMyPlaylist('favorites');
else if (HmsRegExMatch('-uploads' , mpFilePath, s)) CreateMyPlaylist('uploads');
else if (HmsRegExMatch('/feed/history' , mpFilePath, s)) CreateMyPlaylist('watchHistory');
else if (HmsRegExMatch('/feed/music' , mpFilePath, s)) CreateMyPlaylist('music');
else if (HmsRegExMatch('-mySubscribers' , mpFilePath, s)) CreateMySubscribers();
else if (HmsRegExMatch('-recommend' , mpFilePath, s)) CreateMyRecommendations();
else if (HmsRegExMatch('-activities' , mpFilePath, s)) CreateMyActivities();
else if (HmsRegExMatch('-videosByRegion=([\\w-_]+)', mpFilePath, s)) CreateVideosByRegion(s);
else if (HmsRegExMatch('youtube.com/channels$' , mpFilePath, s)) CreateGuideCategories();
else if (HmsRegExMatch('-videoCategories' , mpFilePath, s)) CreateVideoCategories();
else if (HmsRegExMatch('-category=(.*)' , mpFilePath, s)) CreateVideosByCategory(s);
else if (HmsRegExMatch('-channelSection=(.*)' , mpFilePath, s)) CreateByChannelSection(s);
else if (HmsRegExMatch('-channelPlaylists=(.*)', mpFilePath, s)) CreateChannelPlaylists(s);
else if (HmsRegExMatch('/channels/([\\w-_]+)' , mpFilePath, s)) CreateChannels(s);
else if (HmsRegExMatch('/channel/([\\w-_]+)/videos', mpFilePath, s)) CreateChannelVideos(s);
else if (HmsRegExMatch('/channel/([\\w-_]+)' , mpFilePath, s)) CreateChannelSections(s);
else if (HmsRegExMatch('/user/([\\w-_]+)' , mpFilePath, s)) CreateChannelSections(s, true);
else if (HmsRegExMatch('\\?list=([\\w-_]+)' , mpFilePath, s)) CreatePlaylistVideos(s);
else if (HmsRegExMatch('-search="(.*?)"' , mpFilePath, s)) SearchVideos(s); // Просто поиск значения
else if (HmsRegExMatch('-search=(.*)' , mpFilePath, s)) SearchVideos(s, true); // Продожение поиска (техническая ссылка, создаётся сама)
else if (!HmsRegExMatch('^http', mpFilePath, s)) SearchVideos(mpTitle); // Иначе, если в ссылке не http адрес - поиск названия
else ErrorItem('Не умею обрабатывать ссылку :(');
HmsLogMessage(1, mpTitle+': Создано ссылок - '+IntToStr(gnTotalItems));
} else {
// Устанавливаем значение goRoot как корневой элемент подкаста
if (LeftCopy(mpFilePath, 4)=='Info') { ShowInfo(); return; }
if (HmsRegExMatch('youtu.?be', mpFilePath, s)) GetLink_Youtube33(mpFilePath);
else MediaResourceLink = mpFilePath;
}
}
531
C++Script
532
1
553
1
522
0
570
1
245
b65318d7-8a9a-425f-92b9-9feba609ef39
93
42160,1342670833
55
http://cdn.bgr.com/2012/04/youtube.jpg
550
#define mpiTokensInfo 591 // Пароль, где хранится информация о токенах (при сохранении hdf эти данные в файле не сохраняются)
#define mpiAuthorization 594 // Авторизация пользователя: 1 - включена, 2 - выключена, 0 - наследуется
//THmsScriptMediaItem PodcastItem = FolderItem; string MediaResourceLink = '';
///////////////////////////////////////////////////////////////////////////////
// Г Л О Б А Л Ь Н Ы Е П Е Р Е М Е Н Н Ы Е //
int gnTotalItems = 0; // Глобальный счетчик созданных ссылок
string gsUrlBase = 'https://www.youtube.com';
string gsHeaders = 'Accept-Encoding: gzip, deflate\r\n';
string gsPodcastName = 'HMS Youtube v4.4'; // Заголовок видео-сообщения при ошибках
string gsAPIKey = '';
string gsClientId = '';
string gsClientSecret= '';
string gsLanguage = 'ru-RU';
string gsRegion = 'RU';
TDateTime gStart = Now;
Variant gREQUEST; // Для хранения созданного OLE объекта WinHttp.WinHttpRequest.5.1
THmsScriptMediaItem gRoot = GetRoot(); // Корневой элемент подкаста, содержащий информацию о токенах
TStringList gACCESS = LoadAccessInfo(); // Создание объекта хранения информации о токенах
///////////////////////////////////////////////////////////////////////////////
// Ф У Н К Ц И И //
///////////////////////////////////////////////////////////////////////////////
// Поиск корневой папки подкаста, где мы будем хранить в mpiAccessToken
THmsScriptMediaItem GetRoot() {
gRoot = PodcastItem; // Начиная с текущего элемента, ищется создержащий срипт или информацию о токенах
while ((Trim(gRoot[mpiTokensInfo])=='') && (Trim(gRoot[550])=='') && (gRoot[532]!='1') && (gRoot.ItemParent!=nil)) gRoot=gRoot.ItemParent;
return gRoot;
}
///////////////////////////////////////////////////////////////////////////////
// Загрузка данных о токенах доступа
TStringList LoadAccessInfo() {
gACCESS = TStringList.Create();
if (Pos('=', Trim(gRoot[mpiTokensInfo]))>0)
gACCESS.Text = gRoot[mpiTokensInfo];
else
gACCESS.Text = mpPodcastAuthorizationPassword;
return gACCESS;
}
///////////////////////////////////////////////////////////////////////////////
// Сохранение данных о токенах доступа
void SaveAccessInfo() {
gRoot[mpiTokensInfo ] = gACCESS.Text;
gRoot[mpiAuthorization] = '1'; // Включение авторизации пользователя, чтобы данные сохранялись после перезагрузки
}
///////////////////////////////////////////////////////////////////////////////
// Запрос к API
string APIRequest(string sObject, string sParam) {
string sData, sLink, sLang, sErr, sHeaderName, sHeaderValue; int i;
sLang = Lowercase(gsRegion);
if (sObject=='search') {
gsAPIKey = 'AIzaSyCPxJ1HKZznL9WEFay70tLjyzDrry0DiEw';
gsHeaders = '';
}
sLink = 'https://www.googleapis.com/youtube/v3/'+sObject+'?key='+gsAPIKey+'&maxResults=50&gl='+sLang+'®ionCode='+sLang+'&hl='+sLang+'&'+sParam;
// И всё этот бред с CreateOleObject для того, чтобы была возможность получить тело ответа при коде ответа отличных от 200 для анализа ошибок
try {
if (VarType(gREQUEST)!=9) gREQUEST = CreateOleObject('WinHttp.WinHttpRequest.5.1'); // Если ранее не создавался объект, то создаём
gREQUEST.open('GET', sLink, false);
for (i=1; i <= WordCount(gsHeaders, '\r\n') ; i++) {
if (!HmsRegExMatch2('^(.*?):(.*)', ExtractWord(i, gsHeaders, '\r\n'), sHeaderName, sHeaderValue)) continue;
gREQUEST.SetRequestHeader(sHeaderName, sHeaderValue);
}
gREQUEST.send();
sData = gREQUEST.ResponseText;
if (gREQUEST.Status!=200) {
HmsRegExMatch('"message"\\s*:\\s*"(.*?)"', sData, sErr);
HmsLogMessage(2, "Ошибка API: "+HmsHtmlToText(HmsJsonDecode(sErr)));
}
} except {
sData = HmsDownloadUrl(sLink, gsHeaders, true);
if (sData=='') HmsLogMessage(2, "Ошибка API: запрос вернул пустой результат. "+sLink);
}
return sData;
}
///////////////////////////////////////////////////////////////////////////////
// Показ информации, сохранённой в ссылке
void ShowInfo() {
string sTitle, sCateg, sInfo, sDescr, sImg, prefix='youtubev3';
TStrings INFO = TStringList.Create();
INFO.Text = PodcastItem[1001001];
sTitle = INFO.Values['Title' ];
sCateg = INFO.Values['Categ' ];
sInfo = INFO.Values['Info' ];
sDescr = INFO.Values['Descr' ];
sImg = INFO.Values['Poster'];
INFO.Free();
MediaResourceLink = GenerateVideoInfo(prefix, sTitle, sInfo, sCateg, sDescr, sImg);
}
///////////////////////////////////////////////////////////////////////////////
// Генерирование картинок и строки для ffmpeg
char GenerateVideoInfo(char prefix, char sTitle, char sInfo='', char sCateg='', char sDescr='', char sImg='', int mode=0, char sDirID='') {
char sHtml, sPost, sVal, sCol, sLink, sData, sPos, sImagesDir;
int i, n, nSeconds, nSecDel, nMaxImages, nMaxTextLength, nPage=0; double nH, nW;
char sFileImage, sCmd, sColor, sTime; TRegExpr reChannel, reCast;
int xMargin=15, yMargin=15;
nSeconds = 0; nMaxImages = 4; nMaxTextLength = 0;
if (HmsRegExMatch('--xmargin=(\\d+)', mpPodcastParameters, sVal)) xMargin=StrToInt(sVal);
if (HmsRegExMatch('--ymargin=(\\d+)', mpPodcastParameters, sVal)) yMargin=StrToInt(sVal);
if (Pos('--lq', mpPodcastParameters)>0) {
nH = cfgTranscodingScreenHeight / 2;
nW = cfgTranscodingScreenWidth / 2;
} else if (HmsRegExMatch2('--pr=(\\d+)x(\\d+)', mpPodcastParameters, sVal, sPos)) {
nH = StrToInt(sPos);
nW = StrToInt(sVal);
} else {
nH = cfgTranscodingScreenHeight;
nW = cfgTranscodingScreenWidth;
}
if (sDirID=='') sDirID = 'info_'+PodcastItem.ItemID;
sImagesDir = IncludeTrailingBackslash(ExtractShortPathName(HmsTempDirectory))+Trim(prefix);
sImagesDir = IncludeTrailingBackslash(sImagesDir)+sDirID;
ForceDirectories(sImagesDir);
sFileImage = IncludeTrailingBackslash(sImagesDir)+'info_';
sPost = '';
TStrings INFO = TStringList.Create();
INFO.Values['prfx' ] = prefix;
INFO.Values['title'] = sTitle;
INFO.Values['info' ] = sInfo;
INFO.Values['categ'] = sCateg;
INFO.Values['descr'] = sDescr;
INFO.Values['xpic' ] = '15';
INFO.Values['ypic' ] = '15';
INFO.Values['w' ] = IntToStr(Round(nW));
INFO.Values['h' ] = IntToStr(Round(nH));
INFO.Values['xm' ] = IntToStr(xMargin);
INFO.Values['ym' ] = IntToStr(yMargin);
INFO.Values['fz' ] = '3';
INFO.Values['fzdescr'] = IntToStr(Round(nH/22));
INFO.Values['fztitle'] = IntToStr(Round(nH/18));
INFO.Values['fzinfo' ] = IntToStr(Round(nH/25));
if (mode==1) {
INFO.Values['fzdescr'] = IntToStr(Round(nH/16));
INFO.Values['fztitle'] = IntToStr(Round(nH/10));
INFO.Values['fzinfo' ] = IntToStr(Round(nH/14));
INFO.Values['xm'] = IntToStr(Round(nH/14));
} else if ((mode==2) || (mode==3)) {
INFO.Values['ct' ] = '000';
INFO.Values['ctitle'] = '822';
INFO.Values['cinfo' ] = '223';
INFO.Values['ccateg'] = '255';
INFO.Values['fz'] = '6';
INFO.Values['ns'] = '1';
INFO.Values['bg'] = './backgrounds/white.png';
INFO.Values['wpic' ] = IntToStr(Round(nH/3));
if (mode==2) INFO.Values['wpic'] = IntToStr(Round(nH/2));
}
if (sImg !='') {
INFO.Values['urlpic'] = sImg;
INFO.Values['wpic' ] = IntToStr(Round(nH/2));
}
n = 0; TStrings FILELIST = TStringList.Create();
try {
HmsGetFileList(sImagesDir, FILELIST, '*.jpg');
n = FILELIST.Count;
} finally { FILELIST.Free(); }
if (n<4) {
// Получаем ссылки на сформированные картинки, пока в параметрах встречаем lastpos=N
sPos = '0'; nPage = 0;
do {
sColor = ''; sTime = ''; nPage++;
if (nPage>1) {
sData = Copy(sDescr, 1, StrToInt(sPos)); // Блок, успевший попасть на экран
// Определяем последний использующийся цвет
HmsRegExMatch('.*(\\d{2}:\\d{2}\\s*?-\\s*?\\d{2}:\\d{2})', sData, sTime, 1, PCRE_SINGLELINE);
sTime += ' ... ';
}
sDescr = sColor+sTime+Trim(Copy(sDescr, StrToInt(sPos), 999999));
INFO.Values['descr'] = sDescr;
sPost = '';
for (n=0; n<INFO.Count; n++) sPost += '&'+Trim(INFO.Names[n])+'='+HmsHttpEncode(INFO.Values[INFO.Names[n]]);
sLink = HmsSendRequestEx('wonky.lostcut.net', '/videopreview.php', 'POST',
'application/x-www-form-urlencoded', '', sPost, 80, 0, '', true);
if (LeftCopy(sLink, 4)!='http') {HmsLogMessage(2, 'Ошибка получения картинки видеоинформации'); return;}
HmsDownloadURLToFile(sLink, sFileImage);
for (n=0; n<5; n++) { nSeconds++; CopyFile(sFileImage, sFileImage+Format('%.3d.jpg', [nSeconds]), false); }
if (nPage>=nMaxImages) break;
} while (HmsRegExMatch2('^(.*?)\\?lastpos=(\\d+)', sLink, sLink, sPos));
}
INFO.Free();
char sFileMP3 = ExtractShortPathName(HmsTempDirectory)+'\\silent.mp3';
try {
if (!FileExists(sFileMP3)) HmsDownloadURLToFile('http://wonky.lostcut.net/mp3/silent.mp3', sFileMP3);
sFileMP3 = '-i "'+sFileMP3+'"';
} except { sFileMP3=''; }
sLink = Format('%s -f image2 -r 1 -i "%s" -c:v libx264 -r 30 -pix_fmt yuv420p ', [sFileMP3, sFileImage+'%03d.jpg']);
PodcastItem[mpiTimeLength] = HmsTimeFormat(nSeconds)+'.000';
return sLink;
}
///////////////////////////////////////////////////////////////////////////////
// Создание информационной ссылки
void InfoItem(string sMsg) {
THmsScriptMediaItem Item = HmsCreateMediaItem('Info'+Str(PodcastItem.ChildCount), PodcastItem.ItemID);
Item[mpiTitle ] = sMsg;
Item[mpiThumbnail ] = 'http://wonky.lostcut.net/vids/info.jpg';
Item[mpiCreateDate] = IncTime(gStart, 0, -gnTotalItems, 0, 0); gnTotalItems++;
HmsLogMessage(2, sMsg);
}
///////////////////////////////////////////////////////////////////////////////
// Запрос авторизации для нового устройства
void RequestNewCode() {
string sPost, sData, sRet; TJsonObject JSON = TJsonObject.Create();
try {
sPost = 'scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fyoutube&client_id='+gsClientId;
sData = HmsSendRequestEx('accounts.google.com', '/o/oauth2/device/code', 'POST', 'application/x-www-form-urlencoded', '', sPost, 443, 0, sRet, true);
JSON.LoadFromString(sData);
gACCESS.Values['code' ] = JSON.S['device_code'];
gACCESS.Values['verification_url'] = JSON.S['verification_url'];
gACCESS.Values['user_code' ] = JSON.S['user_code'];
gACCESS.Values['expires_date' ] = '';
SaveAccessInfo();
InfoItem('Перейдите по адресу '+JSON.S['verification_url']);
InfoItem('Введите код: ' +JSON.S['user_code' ]);
InfoItem('Потом обновите раздел.');
} finally { JSON.Free; }
}
///////////////////////////////////////////////////////////////////////////////
// Авторизация
bool Login(bool bSilent=false) {
TJsonObject JSON; string sData, sPost, sRet; bool bResult=false;
JSON = TJsonObject.Create();
try {
if (TimeStamp1970ToDateTime(StrToIntDef(gACCESS.Values['expires_date'], 0), false)>Now) { bResult=true; return true; }
if (gACCESS.Values['code']!='') {
if (bSilent) return false;
sPost = 'client_secret='+gsClientSecret+'&code='+gACCESS.Values['code']+'&client_id='+gsClientId+'&grant_type=http%3A%2F%2Foauth.net%2Fgrant_type%2Fdevice%2F1.0';
sData = HmsSendRequestEx('www.googleapis.com', '/oauth2/v4/token', 'POST', 'application/x-www-form-urlencoded', '', sPost, 443, 0, sRet, true);
JSON.LoadFromString(sData);
if (JSON.S['access_token']!='') {
gACCESS.Values['access_token' ] = JSON.S['access_token' ];
gACCESS.Values['refresh_token'] = JSON.S['refresh_token'];
gACCESS.Values['expires_date' ] = Str(DateTimeToTimeStamp1970(Now, false)+JSON.I['expires_in']);
gACCESS.Values['code'] = '';
SaveAccessInfo();
} else if (Pos('428 Precondition Required', sRet)>0) {
if (!bSilent) {
InfoItem('Ожидание подтверждения авторизации');
InfoItem('По адресу '+gACCESS.Values['verification_url']);
InfoItem('Ввести код: '+gACCESS.Values['user_code']);
}
return false;
}
}
if (gACCESS.Values['access_token']=='') { if (!bSilent) RequestNewCode(); return false; }
// Проверка работоспособности токена
sData = HmsDownloadURL('https://www.googleapis.com/oauth2/v3/tokeninfo?access_token='+gACCESS.Values['access_token']);
JSON.LoadFromString(sData);
if (!JSON.B['azp']) {
sPost = 'client_id='+gsClientId+'&client_secret='+gsClientSecret+'&refresh_token='+gACCESS.Values['refresh_token']+'&grant_type=refresh_token';
sData = HmsSendRequestEx('www.googleapis.com', '/oauth2/v4/token', 'POST', 'application/x-www-form-urlencoded', '', sPost, 443, 0, sRet, true);
JSON.LoadFromString(sData);
gACCESS.Values['access_token'] = JSON.S['access_token'];
}
gACCESS.Values['expires_date'] = Str(DateTimeToTimeStamp1970(Now, false)+JSON.I['expires_in']);
SaveAccessInfo();
bResult = (gACCESS.Values['access_token']!='');
} finally {
JSON.Free;
if (bResult) {
gsHeaders += 'Authorization: Bearer '+gACCESS.Values['access_token']+'\r\n';
}
}
return bResult;
}
///////////////////////////////////////////////////////////////////////////////
// Получение ссылки на Youtube
bool GetLink_Youtube33(string sLink) {
string sData, sVideoID='', sAudio='', sSubtitlesLanguage='ru',
sSubtitlesUrl, sFile, sVal, sMsg, sConfig, sHeaders, hlsUrl, subsUrl, jsUrl,
streamMap, algorithm, sType, itag, sig, alg, s, sp; TStringList QLIST;
int i, n, w, num, height, priority, minPriority = 90, selHeight, maxHeight = 1080, audioQual;
TJsonObject JSON, PLAYER_RESPONSE, VIDEO; TJsonArray FORMATS, SUBS; TRegExpr RegEx;
sHeaders = 'Referer: '+sLink+'\r\n'+
'User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36\r\n'+
'Origin: https://www.youtube.com\r\n';
if (HmsRegExMatch('--maxheight=(\\d+)', mpPodcastParameters, sVal)) maxHeight = StrToInt(sVal);
HmsRegExMatch('--sublanguage=(\\w{2})', mpPodcastParameters, sSubtitlesLanguage);
bool bSubtitles = Pos('--subtitles' , mpPodcastParameters)>0;
bool bAdaptive = Pos('--adaptive' , mpPodcastParameters)>0;
bool bQualLog = Pos('--qualitylog', mpPodcastParameters)>0;
if (!HmsRegExMatch('[\\?&]v=([^&]+)' , sLink, sVideoID))
if (!HmsRegExMatch('youtu.be/([^&]+)' , sLink, sVideoID))
HmsRegExMatch('/(?:embed|v)/([^\\?]+)', sLink, sVideoID);
if (sVideoID=='') return;
sLink = gsUrlBase+'/watch?v='+sVideoID+'&hl=ru';
sData = HmsDownloadURL(sLink, sHeaders, true);
sData = HmsRemoveLineBreaks(sData);
if (!HmsRegExMatch('player.config\\s*=\\s*({.*?});', sData, sConfig, 1, PCRE_SINGLELINE)) {
HmsLogMessage(2 , mpTitle+': No player.config data in loaded page.');
return;
}
JSON = TJsonObject.Create();
PLAYER_RESPONSE = TJsonObject.Create();
QLIST = TStringList.Create();
try {
JSON.LoadFromString(sConfig);
PLAYER_RESPONSE.LoadFromString(JSON.S['args\\player_response']);
// Если есть субтитры и в дополнительных параметрах указано их показывать - загружаем
if (bSubtitles && PLAYER_RESPONSE.B['captions\\playerCaptionsTracklistRenderer\\captionTracks']) {
string sTime1, sTime2, engSubs; float nStart, nDur;
SUBS = PLAYER_RESPONSE.O['captions\\playerCaptionsTracklistRenderer\\captionTracks'].AsArray;
for (i=0; i < SUBS.Length; i++) {
if (SUBS[i].S['languageCode']==sSubtitlesLanguage) subsUrl = SUBS[i].S['baseUrl'];
if (SUBS[i].S['languageCode']=='en' ) engSubs = SUBS[i].S['baseUrl'];
}
if ((subsUrl=='') && (engSubs!='')) subsUrl = engSubs+'&tlang='+sSubtitlesLanguage;
if (subsUrl!='') {
sData = HmsDownloadURL(subsUrl+'&fmt=srv3', sHeaders, true);
sMsg = ''; i = 0;
RegEx = TRegExpr.Create('(<(text|p).*?</(text|p)>)', PCRE_SINGLELINE); // Convert to srt format
try {
if (RegEx.Search(sData)) do {
if (HmsRegExMatch('start="([\\d\\.]+)', RegEx.Match, sVal)) nStart = StrToFloat(ReplaceStr(sVal, '.', ','))*1000;
else if (HmsRegExMatch('t="(\\d+)' , RegEx.Match, sVal)) nStart = StrToFloat(sVal);
if (HmsRegExMatch('dur="([\\d\\.]+)' , RegEx.Match, sVal)) nDur = StrToFloat(ReplaceStr(sVal, '.', ','))*1000;
else if (HmsRegExMatch('d="(\\d+)' , RegEx.Match, sVal)) nDur = StrToFloat(sVal);
sTime1 = HmsTimeFormat(Int(nStart/1000))+','+RightCopy(Str(nStart), 3);
sTime2 = HmsTimeFormat(Int((nStart+nDur)/1000))+','+RightCopy(Str(nStart+nDur), 3);
sMsg += Format("%d\n%s --> %s\n%s\n\n", [i, sTime1, sTime2, HmsHtmlToText(HmsHtmlToText(RegEx.Match(0), 65001))]);
i++;
} while (RegEx.SearchAgain());
} finally { RegEx.Free(); }
sFile = HmsSubtitlesDirectory+'\\Youtube\\'+PodcastItem.ItemID+'.'+sSubtitlesLanguage+'.srt';
HmsStringToFile(sMsg, sFile);
if (Trim(PodcastItem[mpiSubtitleLanguage])!='') bAdaptive = false;
PodcastItem[mpiSubtitleLanguage] = sFile;
}
}
hlsUrl = PLAYER_RESPONSE.S['streamingData\\hlsManifestUrl'];
jsUrl = JSON.S['assets\\js'];
if (hlsUrl!='') {
MediaResourceLink = ' '+hlsUrl;
bAdaptive = false;
sData = HmsDownloadUrl(hlsUrl, sHeaders, true);
RegEx = TRegExpr.Create('BANDWIDTH=(\\d+).*?RESOLUTION=(\\d+)x(\\d+).*?(http[^#]*)', PCRE_SINGLELINE);
try {
if (RegEx.Search(sData)) do {
sLink = '' + RegEx.Match(4);
height = StrToIntDef(RegEx.Match(3), 0);
if (mpPodcastMediaFormats!='') {
priority = HmsMediaFormatPriority(height, mpPodcastMediaFormats);
if ((priority>=0) && (priority>minPriority)) {
MediaResourceLink = sLink; minPriority = priority;
}
} else if ((height > selHeight) && (height <= maxHeight)) {
MediaResourceLink = sLink; selHeight = height;
}
QLIST.Values[Format('%.4d', [height])] = sLink;
} while (RegEx.SearchAgain());
} finally { RegEx.Free(); }
} else if (PLAYER_RESPONSE.B['streamingData\\formats']) {
FORMATS = PLAYER_RESPONSE.O['streamingData\\formats'].AsArray;
if (FORMATS[0].B['signatureCipher'])
algorithm = HmsDownloadURL('https://hms.lostcut.net/yt/getalgo.php?jsurl='+HmsHttpEncode(jsUrl));
for(i=0; i<FORMATS.Length; i++) {
VIDEO = FORMATS[i];
if (VIDEO.B['signatureCipher']) {
sLink = HmsHttpDecode(ExtractParam(VIDEO.S['signatureCipher'], 'url', '', '&'));
sig = HmsHttpDecode(ExtractParam(VIDEO.S['signatureCipher'], 's' , '', '&'));
sp = HmsHttpDecode(ExtractParam(VIDEO.S['signatureCipher'], 'sp' , '', '&'));
if (sig!='') {
for (w=1; w<=WordCount(algorithm, ' '); w++) {
alg = ExtractWord(w, algorithm, ' ');
if (Length(alg)<1) continue;
if (Length(alg)>1) TryStrToInt(Copy(alg, 2, 4), num);
if (alg[1]=='r') {s=''; for(n=Length(sig); n>0; n--) s+=sig[n]; sig = s; } // Reverse
if (alg[1]=='s') {sig = Copy(sig, num+1, Length(sig)); } // Clone
if (alg[1]=='w') {n = (num-Trunc(num/Length(sig)))+1; Swap(sig[1], sig[n]);} // Swap
}
}
sLink += '&'+sp+'='+sig;
} else {
sLink = VIDEO.S['url'];
}
height = VIDEO.I['height'];
if (height>0) QLIST.Values[Format('%.4d', [height])] = sLink;
if (mpPodcastMediaFormats!='') {
priority = HmsMediaFormatPriority(height, mpPodcastMediaFormats);
if ((priority>=0) || (priority<minPriority)) {
MediaResourceLink = sLink; minPriority = priority; selHeight = height;
}
} else if ((height>selHeight) && (height<= maxHeight)) {
MediaResourceLink = sLink; selHeight = height;
} else if ((height>=selHeight) && (height<= maxHeight) && (VIDEO.I['itag'] in ([18,22,37,38,82,83,84,85]))) {
// Если высота такая же, но формат MP4 - то выбираем именно его (делаем приоритет MP4)
MediaResourceLink = sLink; selHeight = height;
}
}
}
if (bAdaptive || (MediaResourceLink=='') && PLAYER_RESPONSE.B['streamingData\\adaptiveFormats']) {
FORMATS = PLAYER_RESPONSE.O['streamingData\\adaptiveFormats'].AsArray;
if (FORMATS[0].B['cipher'] && (algorithm==''))
algorithm = HmsDownloadURL('https://hms.lostcut.net/yt/getalgo.php?jsurl='+HmsHttpEncode(jsUrl));
for(i=0; i<FORMATS.Length; i++) {
VIDEO = FORMATS[i];
if (VIDEO.B['cipher']) {
sLink = HmsHttpDecode(ExtractParam(VIDEO.S['cipher'], 'url', '', '&'));
sig = HmsHttpDecode(ExtractParam(VIDEO.S['cipher'], 's' , '', '&'));
sp = HmsHttpDecode(ExtractParam(VIDEO.S['cipher'], 'sp' , '', '&'));
if (sig!='') {
for (w=1; w<=WordCount(algorithm, ' '); w++) {
alg = ExtractWord(w, algorithm, ' ');
if (Length(alg)<1) continue;
if (Length(alg)>1) TryStrToInt(Copy(alg, 2, 4), num);
if (alg[1]=='r') {s=''; for(n=Length(sig); n>0; n--) s+=sig[n]; sig = s; } // Reverse
if (alg[1]=='s') {sig = Copy(sig, num+1, Length(sig)); } // Clone
if (alg[1]=='w') {n = (num-Trunc(num/Length(sig)))+1; Swap(sig[1], sig[n]);} // Swap
}
}
sLink += '&'+sp+'='+sig;
} else {
sLink = VIDEO.S['url'];
}
if (VIDEO.B['audioSampleRate'] && (audioQual < VIDEO.I['bitrate'])) {
sAudio = sLink; audioQual = VIDEO.I['bitrate']; continue;
}
height = VIDEO.I['height'];
if (height>0) QLIST.Values[Format('%.4d', [height])] = sLink;
if (mpPodcastMediaFormats!='') {
priority = HmsMediaFormatPriority(height, mpPodcastMediaFormats);
if ((priority>=0) || (priority<minPriority)) {
MediaResourceLink = sLink; minPriority = priority; selHeight = height;
}
} else if ((height>selHeight) && (height<= maxHeight)) {
MediaResourceLink = sLink; selHeight = height;
}
}
sVal = ""; if (Trim(mpTimeStart)!="") sVal = " -ss "+mpTimeStart;
if (bAdaptive && (sAudio!='')) {
if (Pos('--downloadaudio', mpPodcastParameters)>0) {
sFile = HmsTempDirectory+'\\'+PodcastItem.ItemID+'.webm';
if (!FileExists(sFile))
HmsDownloadURLToFile(sAudio, sFile, sHeaders, true);
} else sFile = sAudio;
MediaResourceLink = '-hide_banner -reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2 -fflags +genpts -i "'+Trim(MediaResourceLink)+'"'+sVal+' -i "'+Trim(sFile)+'"';
}
}
// Если еще не установлена реальная длительность видео - устанавливаем
if ((Trim(mpTimeLength)=='') || (RightCopy(mpTimeLength, 6)=='00.000') && (hlsUrl=='')) {
PodcastItem[mpiTimeLength] = HmsTimeFormat(PLAYER_RESPONSE.I['videoDetails\\lengthSeconds']);
}
if (bQualLog) {
QLIST.Sort();
sMsg = 'Доступное качество: ';
for (i = 0; i < QLIST.Count; i++) {
if (i>0) sMsg += ', ';
sMsg += IntToStr(StrToInt(QLIST.Names[i])); // Обрезаем лидирующие нули
}
sMsg += '. Выбрано: ' + Str(selHeight);
HmsLogMessage(1, mpTitle+'. '+sMsg);
}
} finally { JSON.Free; PLAYER_RESPONSE.Free; QLIST.Free; }
}
///////////////////////////////////////////////////////////////////////////////
// Показ видео сообщения
bool VideoMessage(string sMsg) {
string sFileMP3 = HmsTempDirectory+'\\sa.mp3';
string sFileImg = HmsTempDirectory+'\\youtubemsg_';
sMsg = HmsHtmlToText(HmsJsonDecode(sMsg));
int nH = cfgTranscodingScreenHeight;
int nW = cfgTranscodingScreenWidth;
HmsLogMessage(2, mpTitle+': '+sMsg);
string sLink = Format('http://wonky.lostcut.net/videomessage.php?h=%d&w=%d&captsize=%d&fontsize=%d&caption=%s&msg=%s', [nH, nW, Round(nH/8), Round(nH/17), gsPodcastName, HmsHttpEncode(sMsg)]);
HmsDownloadURLToFile(sLink, sFileImg);
for (int i=1; i<=7; i++) CopyFile(sFileImg, sFileImg+Format('%.3d.jpg', [i]), false);
try {
if (!FileExists(sFileMP3)) HmsDownloadURLToFile('http://wonky.lostcut.net/mp3/sa.mp3', sFileMP3);
sFileMP3 = '-i "'+sFileMP3+'" ';
} except { sFileMP3 = ''; }
MediaResourceLink = Format('%s-f image2 -r 1 -i "%s" -c:v libx264 -r 30 -pix_fmt yuv420p ', [sFileMP3, sFileImg+'%03d.jpg']);
return false;
}
///////////////////////////////////////////////////////////////////////////////
// Создание ссылки-ошибки
void ErrorItem(string sMsg) {
THmsScriptMediaItem Item = HmsCreateMediaItem('Err', PodcastItem.ItemID);
Item[mpiTitle ] = sMsg;
Item[mpiThumbnail] = 'http://wonky.lostcut.net/icons/symbol-error.png';
HmsLogMessage(2, sMsg);
}
///////////////////////////////////////////////////////////////////////////////
// Создание видео-ссылки
THmsScriptMediaItem CreateVideo(string sID, string sTitle, string sImg, string sTime, string sDate, string sComment='') {
if (LeftCopy(sID, 4)!='http') sID = gsUrlBase+'/watch?v='+Trim(sID);
THmsScriptMediaItem Item = HmsCreateMediaItem(sID, PodcastItem.ItemID);
Item[mpiTitle ] = sTitle;
Item[mpiThumbnail ] = sImg;
Item[mpiTimeLength ] = sTime;
Item[mpiCreateDate ] = sDate;
Item[mpiComment ] = sComment;
gnTotalItems++;
return Item;
}
///////////////////////////////////////////////////////////////////////////////
// Создание папки или обновляемого подкаста
THmsScriptMediaItem CreateFolder(string sName, string sLink, string sImg='') {
THmsScriptMediaItem Item = PodcastItem.AddFolder(sLink); // Создаём папку с указанной ссылкой
Item[mpiTitle ] = sName; // Присваиваем наименование
Item[mpiThumbnail ] = sImg; // Картинка папки
Item[mpiCreateDate ] = IncTime(gStart, 0, -gnTotalItems, 0, 0); gnTotalItems++;
return Item;
}
///////////////////////////////////////////////////////////////////////////////
// Перевод некоторых английских фраз из информации о плейлистах
string Translate(string sText) {
string sVal, sResult=sText; TStrings DICT = TStringList.Create();
DICT.Values['Best of YouTube'] = 'Лучшее на YouTube';
DICT.Values['Paid channels' ] = 'Платные каналы';
DICT.Values['Top YouTube Collections'] = 'Лучшие коллекции YouTube';
DICT.Values['Popular Artists'] = 'Популярные исполнители';
if (Trim(DICT.Values[sText])!='') sResult = DICT.Values[sText];
DICT.Free();
if (HmsRegExMatch('(Popular Artist Mixes)', sResult, sVal)) sResult = ReplaceStr(sResult, sVal, 'Миксы популярных исполнителей');
return sResult;
}
///////////////////////////////////////////////////////////////////////////////
// Получение безопасного валидного наименования
string SafeName(string sName) {
sName = ReplaceStr(sName, '/' , '-');
sName = ReplaceStr(sName, '\\', '-');
sName = Translate(sName);
return sName;
}
///////////////////////////////////////////////////////////////////////////////
// Получение длительности для HMS из формата youtube
string ConvertTime(string sTime) {
string sVal; int nSeconds = 0;
if (HmsRegExMatch('(\\d+)H', sTime, sVal)) nSeconds += StrToInt(sVal)*3600;
if (HmsRegExMatch('(\\d+)M', sTime, sVal)) nSeconds += StrToInt(sVal)*60;
if (HmsRegExMatch('(\\d+)S', sTime, sVal)) nSeconds += StrToInt(sVal);
if (nSeconds==0) nSeconds = 600;
return HmsTimeFormat(nSeconds)+'.000';
}
///////////////////////////////////////////////////////////////////////////////
// Получение даты создания для HMS из формата youtube
string ConvertDate(string sDate) {
string sY, sM, sD, sTime;
HmsRegExMatch3('(\\d{4}).(\\d{2}).(\\d{2})', sDate, sY, sM, sD);
HmsRegExMatch ('T(\\d{2}:\\d{2}:\\d{2})' , sDate, sTime );
return Format('%s.%s.%s %s', [sD, sM, sY, sTime]);
}
///////////////////////////////////////////////////////////////////////////////
// Универсальная функция создания элементов видео (или сбора информации)
string CreateItems(string sPath, string sObject, string sParam, string sFiltr='') {
string sLink, sData, sName, sID, sImg, sCh, sVal, sResult='', sTime;
int i, nLevel; TJsonObject JSON, ITEM; TJsonArray ITEMS;
if (HmsRegExMatch('pageToken=([\\w-_]+)', mpFilePath, sVal)) sParam += '&pageToken='+sVal;
if (HmsRegExMatch('&level=(\\d+)' , mpFilePath, sVal)) nLevel = StrToInt(sVal);
sData = APIRequest(sObject, sParam);
JSON = TJsonObject.Create();
try {
JSON.LoadFromString(sData);
sID = JSON.S('nextPageToken');
if ((sID!='') && (nLevel<3)) {
if (sObject=='search') mpFilePath = '-search='+sParam;
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
if (HmsRegExMatch('(&level=\\d+)' , mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
sCh = '?'; if (Pos('?', mpFilePath)>0) sCh = '&';
CreateFolder('Следующая страница', mpFilePath+sCh+'pageToken='+sID+'&level='+Str(nLevel+1));
}
ITEMS = JSON.A('items');
if (ITEMS != nil) {
for (i=0; i<ITEMS.Length; i++) {
ITEM = ITEMS[i];
// Хоть playlistItems и возвращает список видео, но там нет длительности
// Поэтому собираем ID видео, чтобы потом вызвать videos со списком этих ID
if (sObject=='playlistItems') {
if (sResult!='') sResult += ',';
sResult += ITEM.S('contentDetails\\videoId');
continue;
} else if (sObject=='activities') {
if (sFiltr!='') if (ITEM.S[sFiltr]=='') continue;
if (sResult!='') sResult += ',';
sID = ITEM.S('contentDetails\\recommendation\\resourceId\\videoId');
if (sID=='') sID = ITEM.S('contentDetails\\playlistItem\\resourceId\\videoId');
if (sID=='') sID = ITEM.S('contentDetails\\upload\\videoId');
if (sID!='') sResult += sID;
continue;
} else if (sObject=='search') {
sVal = ITEM.S('id\\kind');
if (sVal=='youtube#playlist') {
sID = ITEM.S('id\\playlistId');
sPath = gsUrlBase+'/playlist?list=';
} else if (sVal=='youtube#channel') {
sID = ITEM.S('id\\channelId');
sPath = gsUrlBase+'/channel/';
} else {
sID = ITEM.S('id\\videoId');
sPath = 'video';
if (sResult!='') sResult += ',';
if (sID!='') sResult += sID;
continue;
}
} else if (sObject=='videoCategories') {
if (!ITEM.B('snippet\\assignable')) continue;
sID = ITEM.S('id');
if (sID=='1') sID = '30';
} else if (sObject=='subscriptions') {
sID = ITEM.S('snippet\\resourceId\\channelId');
} else {
sID = ITEM.S('id');
}
if (sID=='') continue;
if (LeftCopy(sPath, 6)=='getids') { if (sResult!='') sResult+=','; sResult += sID; continue; }
sName = ITEM.S('snippet\\localized\\title');
if (sName=='') sName = ITEM.S('snippet\\title');
sImg = ITEM.S('snippet\\thumbnails\\medium\\url');
sName = SafeName(HmsUtf8Decode(sName));
sVal = ITEM.S('contentDetails\\itemCount'); if (sVal=='0') continue;
if (sVal!='') sName += ' ['+sVal+']';
if (Pos(sPath, '%s')>0) sLink = Format(sPath, [sID]);
else sLink = sPath + sID;
if (sPath=='video' ) {
if (ITEM.S('snippet\\liveBroadcastContent')=='live') sTime = '04:00:00.000';
else sTime = ConvertTime(ITEM.S('contentDetails\\duration'));
if (Pos('DE', ITEM.S('contentDetails\\regionRestriction\\blocked'))>0) sID += '¬de=1';
}
if (sPath=='video' ) CreateVideo(sID, sName, sImg, sTime, ConvertDate(ITEM.S('snippet\\publishedAt')), HmsUtf8Decode(ITEM.S('snippet\\description')));
else CreateFolder(sName, sLink, sImg);
}
}
} finally { JSON.Free(); }
return sResult;
}
///////////////////////////////////////////////////////////////////////////////
// Создание каналов конкретной категории
void CreateChannels(string sID) {
Login(true);
CreateItems(gsUrlBase+'/channel/', 'channels', 'part=snippet,contentDetails&categoryId='+sID);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка категорий назначенных самим youtube автоматически
void CreateGuideCategories() {
Login(true);
CreateItems(gsUrlBase+'/channels/', 'guideCategories', 'part=snippet');
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка категорий
void CreateVideoCategories() {
Login(true);
CreateItems('-category=', 'videoCategories', 'part=snippet');
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео по выбранной категории
void CreateVideosByCategory(string sID) {
Login(true);
CreateItems('video', 'videos', 'part=snippet,contentDetails&chart=mostPopular&videoCategoryId='+sID);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео выбранного плейлиста
void CreatePlaylistVideos(string sID) {
string sIDs, sVal;
Login(true);
sIDs = CreateItems('getids', 'playlistItems', 'part=contentDetails&fields=nextPageToken,items(contentDetails%2FvideoId)&playlistId='+sID);
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
///////////////////////////////////////////////////////////////////////////////
// Создание подкастов каналов в родительской папке
void CreateMySubscriptions() {
if (!Login()) return;
TJsonObject SUBS = TJsonObject.Create();
try {
SUBS.LoadFromString(APIRequest('subscriptions', 'part=snippet&mine=true'));
if (SUBS["items"]==nil) return;
for (int n=0; n < SUBS["items"].AsArray.Length; n++) {
TJsonObject OBJECT = SUBS["items"].AsArray[n];
string ch = OBJECT.S['snippet\\resourceId\\channelId']; if (ch=='') continue;
THmsScriptMediaItem Item = PodcastItem.AddFolder(gsUrlBase+'/channel/'+ch);
Item[mpiTitle ] = HmsUtf8Decode(OBJECT.S['snippet\\title']);
Item[mpiComment ] = HmsUtf8Decode(OBJECT.S['snippet\\description']);
Item[mpiThumbnail ] = OBJECT.S['snippet\\thumbnails\\medium\\url'];
Item[mpiCreateDate] = ConvertDate(OBJECT.S['snippet\\publishedAt']); gnTotalItems++;
}
} finally { SUBS.Free; }
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка подписчиков
void CreateMySubscribers() {
if (!Login()) return;
CreateItems(gsUrlBase+'/channel/', 'subscriptions', 'part=snippet,contentDetails&mySubscribers=true');
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео по ключевому слову названия плейлиста ("WL", "likes"...)
void CreateMyPlaylist(string sKey) {
string sData, sID;
if (!Login()) return;
sData = APIRequest('channels', 'part=snippet,contentDetails&mine=true');
if (HmsRegExMatch('"'+sKey+'":\\s*?"(.*?)"', sData, sID)) CreatePlaylistVideos(sID);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео "Рекомендации"
void CreateMyRecommendations() {
string sIDs, sVal;
if (!Login()) return;
sIDs = CreateItems('getids', 'activities', 'part=contentDetails&home=true');
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
///////////////////////////////////////////////////////////////////////////////
// Получение списка ID через запятую по переданной json строке
string GetIDs(string sData, string sPath) {
string sID, sIDs;
TJsonObject JSON = TJsonObject.Create();
int nCount = 0;
try {
JSON.LoadFromString(sData);
if (JSON["items"].AsArray!=nil) {
for (int i=0; i < JSON["items"].AsArray.Length; i++) {
sID = JSON["items"].AsArray[i].S[sPath]; if (sID=='') continue;
if (sIDs=='') sIDs = sID; else sIDs += ','+sID;
nCount++; if (nCount >= 50) break;
}
}
} finally { JSON.Free; }
return sIDS;
}
///////////////////////////////////////////////////////////////////////////////
// Список новых видео в подписках (НОВЫЙ СПОСОБ)
void NewVideos() {
string sID, sIDs, sData, sName, sTime, sDesc, sChan; TJsonArray VIDEOS;
if (!Login()) return;
TJsonObject SUBS = TJsonObject.Create();
TJsonObject JSON = TJsonObject.Create();
TStringList LIST = TStringList.Create();
int nCount = 0;
try {
sData = APIRequest('subscriptions', 'part=snippet&mine=true');
SUBS.LoadFromString(sData);
if (SUBS["items"] != nil) {
for (int n=0; n < SUBS["items"].AsArray.Length; n++) {
sChan = SUBS["items"].AsArray[n].S['snippet\\resourceId\\channelId']; if (sChan=='') continue;
sData = APIRequest('activities', 'part=contentDetails&channelId='+sChan);
sIDs = GetIDs(sData, 'contentDetails\\upload\\videoId');
sData = APIRequest('videos', 'part=snippet,contentDetails&id='+sIDs);
JSON.LoadFromString(sData);
if (JSON["items"]==nil) continue;
for (int i=0; i < JSON["items"].AsArray.Length; i++) {
sID = JSON["items"].AsArray[i].S['id'];
sData = JSON["items"].AsArray[i].S['snippet\\publishedAt'];
sTime = JSON["items"].AsArray[i].S['contentDetails\\duration'];
sName = HmsUtf8Decode(JSON["items"].AsArray[i].S['snippet\\title']);
sDesc = HmsUtf8Decode(JSON["items"].AsArray[i].S['snippet\\description']);
LIST.Add(sData+'='+sID+'|'+sName+'|'+sTime+'|'+sDesc);
}
}
CreateVideosFromList(LIST);
}
} finally { SUBS.Free; JSON.Free; LIST.Free; }
}
///////////////////////////////////////////////////////////////////////////////
// Создание видео из переданного списка TStringList
void CreateVideosFromList(TStringList LIST) {
string sID, sLink, sName, sTime, sDate, sDesc, sImg;
if (LIST.Count==0) return;
LIST.Sort();
for (int i=LIST.Count-1; i>=0; i--) {
HmsRegExMatch2('^(.*?)=(.*?)\\|' , LIST[i], sDate, sID);
HmsRegExMatch3('\\|(.*?)\\|(.*?)\\|(.*)', LIST[i], sName, sTime, sDesc);
sLink = gsUrlBase+'/watch?v='+sID;
THmsScriptMediaItem Item = HmsCreateMediaItem(sLink, PodcastItem.ItemID);
Item[mpiTitle ] = sName;
Item[mpiComment ] = sDesc;
Item[mpiThumbnail ] = 'https://i.ytimg.com/vi/'+sID+'/mqdefault.jpg';
Item[mpiTimeLength] = ConvertTime(sTime);
Item[mpiCreateDate] = ConvertDate(sDate); gnTotalItems++;
}
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка новых видео в подписках
void CreateMyNewVideos() {
string sIDs, sDate, sID, sLink, sData, sPlaylistIDs, sName, sImg, sTime='10', sVal;
int i, n, nCount, nMaxPages; TJsonObject JSON, VIDEO; TJsonArray VIDEOS; TStringList LIST;
if (!Login()) return;
sPlaylistIDs = CreateItems('getids', 'subscriptions', 'part=snippet&mine=true');
LIST = TStringList.Create();
JSON = TJsonObject.Create();
for (i=1; i<=WordCount(sPlaylistIDs, ','); i++) {
sID = ExtractWord(i, sPlaylistIDs, ','); // ИД плейлиста, на который мы подписаны
sData = APIRequest('search', 'part=snippet&order=date&channelId='+sID);
JSON.LoadFromString(sData);
VIDEOS = JSON["items"].AsArray;
if (VIDEOS!=nil) {
for (n=0; n < VIDEOS.Length; n++) {
VIDEO = VIDEOS[n];
if (VIDEO.S['id\\kind']!='youtube#video') continue;
sID = VIDEO.S['id\\videoId'];
sDate = VIDEO.S['snippet\\publishedAt'];
sName = VIDEO.S['snippet\\title'];
LIST.Add(sDate+';'+sID+';'+HmsUtf8Decode(sName));
}
}
}
LIST.Sort();
nMaxPages = 1;
if (HmsRegExMatch('--pages=(\\d+)', mpPodcastParameters, sVal)) nMaxPages = StrToInt(sVal);
i=LIST.Count-1;
for (n=0; n<nMaxPages; n++) {
nCount=0; sIDs='';
while (i>=0) {
HmsRegExMatch3('(.*?);(.*?);(.*)', LIST[i], sDate, sID, sName);
if (sIDs!='') sIDs += ','; sIDs += sID;
i--; nCount++;
if (nCount >= 50) break;
}
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
LIST.Free(); JSON.Free();
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео выбранного канала
void CreateChannelVideos(string sChannelID) {
Login(true);
SearchVideosByParam('&q=&part=snippet&type=video&order=date&channelId='+sChannelID);
}
///////////////////////////////////////////////////////////////////////////////
// Поиск видео с выбранными параметрами
void SearchVideosByParam(string sParam) {
string sIDs, sVal;
Login(true);
sIDs = CreateItems('video', 'search', sParam);
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео выбранного региона
void CreateVideosByRegion(string sRegion) {
string sVal, sParam;
Login(true);
gsRegion = sRegion;
sParam = '&q=&part=snippet&type=video&order=rating®ionCode='+sRegion+'&relevanceLanguage='+sRegion;
if (HmsRegExMatch('category=([\\w-_]+)', mpFilePath, sVal)) sParam += '&videoCategoryId=' + sVal;
SearchVideosByParam(sParam);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка последних обновлений
void CreateMyActivities() {
string sIDs, sVal;
if (!Login()) return;
sIDs = CreateItems('getids', 'activities', 'part=contentDetails&mine=true');
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка видео конкретного канала (по его id)
void CreateChannelPlaylists(string sChannelID) {
Login(true);
CreateItems(gsUrlBase+'/playlist?list=', 'playlists', 'part=snippet,contentDetails&channelId='+sChannelID);
}
///////////////////////////////////////////////////////////////////////////////
// Создание секций конкретного канала ("Загруженные", "Плейлисты" и проч.)
void CreateChannelSections(string sChannelID, bool bForUser=false) {
string sLink, sData, sIDs='', sType, sID, sName, sChannelData, sVal;
int i, nCnt, nMax; TJsonObject JSON, ITEM; TJsonArray ITEMS; TStrings CHANNELS;
Login(true);
if (bForUser)
sChannelData = APIRequest('channels', 'part=snippet,contentDetails&forUsername='+sChannelID);
else
sChannelData = APIRequest('channels', 'part=snippet,contentDetails&id='+sChannelID);
HmsRegExMatch('"id":\\s*?"(.*?)"', sChannelData, sChannelID);
if ((Pos('--translate', mpPodcastParameters)>0) && HmsRegExMatch('"country"\\s*:\\s*"(.*?)"', sChannelData, sVal) && (sVal!=gsRegion)) {
if ((Pos('--subtitles', mpPodcastParameters)<1)) PodcastItem[mpiPodcastParameters] += ' --subtitles';
}
sData = APIRequest('channelSections', 'part=snippet,contentDetails&fields=items(id,snippet/type,snippet/title,snippet/localized/title,contentDetails)&channelId='+sChannelID);
JSON = TJsonObject.Create();
CHANNELS = TStringList.Create();
try {
JSON.LoadFromString(sData);
ITEMS = JSON.A('items');
if (ITEMS != nil) {
for (i=0; i<ITEMS.Length; i++) {
ITEM = ITEMS[i];
sType = ITEM.S('snippet\\type');
if (sType=='singlePlaylist') {
sID = ITEM.S('contentDetails\\playlists[0]');
CHANNELS.Values[sID] = '1';
} else if ((sType=='multiplePlaylists')||(sType=='multipleChannels')) {
sID = ITEM.S('id');
sName = ITEM.S('snippet\\localized\\title');
if (sName=='') sName = ITEM.S('snippet\\title');
sName = SafeName(HmsUtf8Decode(sName));
CreateFolder(sName, '-channelSection='+sID);
}
}
}
} finally { JSON.Free(); }
// Create single playlists
nCnt = 0; nMax = 4;
for (i=0; i<CHANNELS.Count; i++) {
sID = CHANNELS.Names[i];
nCnt++; if (nCnt>=50) {
CreateItems(gsUrlBase+'/playlist?list=', 'playlists', 'part=snippet,contentDetails&id='+sIDs);
nCnt = 1; sIDs = sID; nMax --; if (nMax<=0) break;
}
if (sIDs!='') sIDs += ','; sIDs += sID;
}
if (sIDs!='') CreateItems(gsUrlBase+'/playlist?list=', 'playlists', 'part=snippet,contentDetails&id='+sIDs);
CHANNELS.Free();
CreateFolder('Плейлисты', '-channelPlaylists='+sChannelID);
sLink = gsUrlBase+'/playlist?list=';
if (HmsRegExMatch('"uploads":\\s*?"(.*?)"' , sChannelData, sID)) CreateFolder('Загруженные видео', sLink+sID);
if (HmsRegExMatch('"likes":\\s*?"(.*?)"' , sChannelData, sID)) CreateFolder('Понравившиеся' , sLink+sID);
if (HmsRegExMatch('"favorites":\\s*?"(.*?)"', sChannelData, sID)) CreateFolder('Любимые' , sLink+sID);
}
///////////////////////////////////////////////////////////////////////////////
// Создание видео конкретной секции канала
void CreateByChannelSection(string sSectionID) {
string sLink, sData, sIDs='', sType, sID, sName;
int i; TJsonObject JSON, ITEM; TJsonArray ITEMS;
Login(true);
sData = APIRequest('channelSections', 'part=contentDetails&id='+sSectionID);
JSON = TJsonObject.Create();
try {
JSON.LoadFromString(sData);
sLink = gsUrlBase+'/channel/';
sIDs = JSON.S('items[0]\\contentDetails\\channels');
HmsRegExMatch('\\[(.*?)\\]', sIDs, sIDs);
sIDs = ReplaceStr(sIDs, '"', '');
if (WordCount(sIDs, ',')>50) {
sID = Copy(sIDs, 1, WordPosition(51, sIDs, ',')-2);
CreateItems(sLink, 'channels', 'part=snippet,contentDetails&id='+sID);
sIDs = Copy(sIDs, WordPosition(51, sIDs, ','), 99999);
if (WordCount(sIDs, ',')>50) sIDs = Copy(sIDs, 1, WordPosition(51, sIDs, ',')-2);
}
if (sIDs!='') CreateItems(sLink, 'channels', 'part=snippet,contentDetails&id='+sIDs);
sLink = gsUrlBase+'/playlist?list=';
sIDs = JSON.S('items[0]\\contentDetails\\playlists');
HmsRegExMatch('\\[(.*?)\\]', sIDs, sIDs);
sIDs = ReplaceStr(sIDs, '"', '');
if (WordCount(sIDs, ',')>50) {
sID = Copy(sIDs, 1, WordPosition(51, sIDs, ',')-2);
CreateItems(sLink, 'playlists', 'part=snippet,contentDetails&id='+sID);
sIDs = Copy(sIDs, WordPosition(51, sIDs, ','), 99999);
if (WordCount(sIDs, ',')>50) sIDs = Copy(sIDs, 1, WordPosition(51, sIDs, ',')-2);
}
if (sIDs!='') CreateItems(sLink, 'playlists', 'part=snippet,contentDetails&id='+sIDs);
} finally { JSON.Free(); }
}
///////////////////////////////////////////////////////////////////////////////
// Создание списка своих собственных плейлистов
void CreateMyPlaylists() {
string sData, sLink, sID;
if (!Login()) return;
CreateItems(gsUrlBase+'/playlist?list=', 'playlists', 'part=snippet,contentDetails&mine=true');
}
///////////////////////////////////////////////////////////////////////////////
// Поиск видео по названию
void SearchVideos(string sName, bool bContinue=false) {
string sVal='', sParam = '', sIDs, sPodcastParams, sKey, sPossibleKeys; int i, nCnt;
if (!bContinue) {
sPodcastParams = mpFilePath + ' ' + PodcastItem.ItemParent[mpiFilePath] + ' ' + mpPodcastParameters;
sPossibleKeys = 'channelId,channelType,eventType,location,locationRadius,maxResults,order,publishedAfter,publishedBefore,regionCode,relevanceLanguage,safeSearch,topicId,type,videoCaption,videoCategoryId,videoDefinition,videoDimension,videoDuration,videoEmbeddable,videoLicense,videoSyndicated,videoType';
for (i=1; i<=WordCount(sPossibleKeys, ','); i++) {
sKey = ExtractWord(i, sPossibleKeys, ',');
if (HmsRegExMatch('-'+sKey+'=([\\w_\\-\\.,:]+)', sPodcastParams, sVal)) sParam += '&'+sKey+'=' +sVal;
}
if (PodcastItem.ItemParent[mpiComment]=='+') sName = PodcastItem.ItemParent[mpiTitle] + ' ' + Trim(sName);
if (ProgramVersion>'3.0') sIDs = CreateItems('video', 'search', 'part=snippet&q='+HmsHttpEncode(sName)+sParam);
else sIDs = CreateItems('video', 'search', 'part=snippet&q='+HmsHttpEncode(HmsUtf8Encode(sName))+sParam);
} else {
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', sName, sVal)) sName = ReplaceStr(sName, sVal, '');
sIDs = CreateItems('video', 'search', sName);
}
if (HmsRegExMatch('(.pageToken=[\\w-_]+)', mpFilePath, sVal)) mpFilePath = ReplaceStr(mpFilePath, sVal, '');
CreateItems('video', 'videos', 'part=snippet,contentDetails&id='+sIDs);
}
///////////////////////////////////////////////////////////////////////////////
// Создание структуры подкаста - папок меню
void CreateMyFolders() {
THmsScriptMediaItem Item;
CreateFolder('Новые видео в подписках', '-newVideos');
CreateFolder('Мои подписки' , gsUrlBase+'/feed/subscriptions');
CreateFolder('Плейлисты' , '-playlists');
//CreateFolder('Посмотреть позже' , gsUrlBase+'/playlist?list=WL');
CreateFolder('Загруженные видео', '-uploads');
CreateFolder('Понравившиеся' , '-likes');
CreateFolder('Просмотренные' , gsUrlBase+'/feed/history');
//CreateFolder('Рекомендации' , '-recommend');
//CreateFolder('Мои подписчики' , '-mySubscribers');
}
///////////////////////////////////////////////////////////////////////////////
// Г Л А В Н А Я П Р О Ц Е Д У Р А //
// ----------------------------------------------------------------------------
{
string s;
HmsRegExMatch('--language=(\\w+)' , mpPodcastParameters, gsLanguage);
HmsRegExMatch('--regionCode=([\\w-]+)', mpPodcastParameters, gsRegion );
// Проверка на упоротых, которые пытаются запустить "Обновление подкастов" для всего подкаста разом
if (InteractiveMode && (HmsCurrentMediaTreeItem.ItemClassName=='TVideoPodcastsFolderItem')) {
HmsCurrentMediaTreeItem.DeleteChildItems(); // Дабы обновления всех подкастов не запустилось - удаляем их.
ShowMessage('Обновление всех разделов разом запрещено!\nДля восстановления структуры\nзапустите "Создать ленты подкастов".');
return;
}
HmsRegExMatch('APIKey="(.*?)"' , mpPodcastParameters, gsAPIKey );
HmsRegExMatch('ClientId="(.*?)"' , mpPodcastParameters, gsClientId );
HmsRegExMatch('ClientSecret="(.*?)"', mpPodcastParameters, gsClientSecret);
if (PodcastItem.IsFolder) {
PodcastItem.DeleteChildItems(); // Удаление существующих ссылок
if ((gsAPIKey=='') || (gsClientId=='') || (gsClientSecret=='')) {
s = 'Не все параметры подкаста установлены (--APIKey, --ClientId или --ClientSecret)';
HmsLogMessage(2, s);
ErrorItem(s);
return;
}
// Анализ текущей ссылки
if (HmsRegExMatch('-newVideos' , mpFilePath, s)) CreateMyNewVideos(); //NewVideos();
else if (HmsRegExMatch('/feed/subscriptions', mpFilePath, s)) CreateMySubscriptions();
else if (HmsRegExMatch('-MyChannel' , mpFilePath, s)) CreateMyFolders();
else if (HmsRegExMatch('-playlists' , mpFilePath, s)) CreateMyPlaylists();
else if (HmsRegExMatch('/playlist\\?list=WL', mpFilePath, s)) CreateMyPlaylist('watchLater');
else if (HmsRegExMatch('-likes' , mpFilePath, s)) CreateMyPlaylist('likes');
else if (HmsRegExMatch('-favorites' , mpFilePath, s)) CreateMyPlaylist('favorites');
else if (HmsRegExMatch('-uploads' , mpFilePath, s)) CreateMyPlaylist('uploads');
else if (HmsRegExMatch('/feed/history' , mpFilePath, s)) CreateMyPlaylist('watchHistory');
else if (HmsRegExMatch('/feed/music' , mpFilePath, s)) CreateMyPlaylist('music');
else if (HmsRegExMatch('-mySubscribers' , mpFilePath, s)) CreateMySubscribers();
else if (HmsRegExMatch('-recommend' , mpFilePath, s)) CreateMyRecommendations();
else if (HmsRegExMatch('-activities' , mpFilePath, s)) CreateMyActivities();
else if (HmsRegExMatch('-videosByRegion=([\\w-_]+)', mpFilePath, s)) CreateVideosByRegion(s);
else if (HmsRegExMatch('youtube.com/channels$' , mpFilePath, s)) CreateGuideCategories();
else if (HmsRegExMatch('-videoCategories' , mpFilePath, s)) CreateVideoCategories();
else if (HmsRegExMatch('-category=(.*)' , mpFilePath, s)) CreateVideosByCategory(s);
else if (HmsRegExMatch('-channelSection=(.*)' , mpFilePath, s)) CreateByChannelSection(s);
else if (HmsRegExMatch('-channelPlaylists=(.*)', mpFilePath, s)) CreateChannelPlaylists(s);
else if (HmsRegExMatch('/channels/([\\w-_]+)' , mpFilePath, s)) CreateChannels(s);
else if (HmsRegExMatch('/channel/([\\w-_]+)/videos', mpFilePath, s)) CreateChannelVideos(s);
else if (HmsRegExMatch('/channel/([\\w-_]+)' , mpFilePath, s)) CreateChannelSections(s);
else if (HmsRegExMatch('/user/([\\w-_]+)' , mpFilePath, s)) CreateChannelSections(s, true);
else if (HmsRegExMatch('\\?list=([\\w-_]+)' , mpFilePath, s)) CreatePlaylistVideos(s);
else if (HmsRegExMatch('-search="(.*?)"' , mpFilePath, s)) SearchVideos(s); // Просто поиск значения
else if (HmsRegExMatch('-search=(.*)' , mpFilePath, s)) SearchVideos(s, true); // Продожение поиска (техническая ссылка, создаётся сама)
else if (!HmsRegExMatch('^http', mpFilePath, s)) SearchVideos(mpTitle); // Иначе, если в ссылке не http адрес - поиск названия
else ErrorItem('Не умею обрабатывать ссылку :(');
HmsLogMessage(1, mpTitle+': Создано ссылок - '+IntToStr(gnTotalItems));
} else {
// Устанавливаем значение goRoot как корневой элемент подкаста
if (LeftCopy(mpFilePath, 4)=='Info') { ShowInfo(); return; }
if (HmsRegExMatch('youtu.?be', mpFilePath, s)) GetLink_Youtube33(mpFilePath);
else MediaResourceLink = mpFilePath;
}
}
551
C++Script
571
// 2020.01.27
/////////////////////// Создание структуры подкаста /////////////////////////
#define mpiScriptAlt3 510 // Cкрипт по Alt+3
///////////////////////////////////////////////////////////////////////////////
// Г Л О Б А Л Ь Н Ы Е П Е Р Е М Е Н Н Ы Е //
THmsScriptMediaItem Podcast = GetRoot(); // Главная папка подкаста
int gnTotalItems = 0;
TDateTime gStart = Now;
///////////////////////////////////////////////////////////////////////////////
// Ф У Н К Ц И И //
///////////////////////////////////////////////////////////////////////////////
// Установка переменной Podcast: поиск родительской папки, содержащий скрипт
THmsScriptMediaItem GetRoot() {
Podcast = FolderItem; // Начиная с текущего элемента, ищется создержащий срипт
while ((Trim(Podcast[550])=='') && (Podcast[532]!='1') && (Podcast.ItemParent!=nil))
Podcast=Podcast.ItemParent;
return Podcast;
}
///////////////////////////////////////////////////////////////////////////////
// Создание папки или подкаста
THmsScriptMediaItem CreateFolder(THmsScriptMediaItem Parent, string sName, string sLink, string sParams='', bool bForceFolder=false, bool bCreateStruct=false) {
THmsScriptMediaItem Item = Parent.AddFolder(sLink, bForceFolder); // Создаём папку с указанной ссылкой
Item[mpiTitle ] = sName; // Присваиваем наименование
Item[mpiCreateDate] = IncTime(gStart,0,-gnTotalItems,0,0); gnTotalItems++;
Item[mpiPodcastParameters] = sParams;
Item[mpiFolderSortOrder ] = "-mpCreateDate";
if (bCreateStruct) Item[570] = 2;
return Item;
}
///////////////////////////////////////////////////////////////////////////////
// Функция создания динамической папки с указанным скриптом
THmsScriptMediaItem CreateDynamicItem(THmsScriptMediaItem prntItem, string sTitle, string sLink, string &sScript='') {
THmsScriptMediaItem Folder = prntItem.AddFolder(sLink, false, 32);
Folder[mpiTitle ] = sTitle;
Folder[mpiCreateDate] = VarToStr(IncTime(Now,0,-prntItem.ChildCount,0,0));
Folder[200] = 5; // mpiFolderType
Folder[500] = sScript; // mpiDynamicScript
Folder[501] = 'JScript'; // mpiDynamicSyntaxType
Folder[mpiFolderSortOrder] = -mpiCreateDate;
return Folder;
}
///////////////////////////////////////////////////////////////////////////////
// Замена в тексте загруженного скрипта значения текстовой переменной
void ReplaceVarValue(string &sText, string sVarName, string sNewVal) {
char sVal, sVal2;
if (HmsRegExMatch2("("+sVarName+"\\s*?=.*?';)", sText, sVal, sVal2, PCRE_SINGLELINE)) {
HmsRegExMatch(sVarName+"\\s*?=\\s*?'(.*)'", sVal, sVal2);
sText = ReplaceStr(sText, sVal, ReplaceStr(sVal , sVal2, sNewVal));
}
}
///////////////////////////////////////////////////////////////////////////////
// Создание папки ПОИСК (с загрузкой скрипта с форума homemediaserver.ru)
void CreateSearchFolder(THmsScriptMediaItem prntItem, string sTitle) {
string sScript='', sHtml; THmsScriptMediaItem Folder;
// Да да, загружаем скрипт с сайта форума HMS
sHtml = HmsUtf8Decode(HmsDownloadURL('http://homemediaserver.ru/forum/viewtopic.php?f=15&t=2793&p=17395'));
HmsRegExMatch('BeginSearchJScript\\*/(.*?)/\\*EndSearchJScript', sHtml, sScript, 1, PCRE_SINGLELINE);
sScript = HmsHtmlToText(sScript, 1251);
sScript = ReplaceStr(sScript, #160, ' ');
// И меняем значения переменных на свои
//ReplaceVarValue(sScript, 'gsSuggestUrl', gsAPIUrl+'videos?q=');
//ReplaceVarValue(sScript, 'gsSuggestResultCut', '');
//ReplaceVarValue(sScript, 'gsSuggestRegExp', '"name":"(.*?)"');
//ReplaceVarValue(sScript, 'gsSuggestMethod' , 'POST');
//sScript = ReplaceStr(sScript, 'gnSuggestNoUTFEnc = 0', 'gnSuggestNoUTFEnc = 1');
Folder = prntItem.AddFolder(sTitle, true);
Folder[mpiCreateDate ] = VarToStr(IncTime(gStart,0,-gnTotalItems,0,0));
Folder[mpiFolderSortOrder] = "-mpCreateDate";
gnTotalItems++;
CreateDynamicItem(Folder, '"Набрать текст"', '-SearchCommands', sScript);
CreateFolder(Folder, 'Каналы' , '-type=channel' , '', true);
CreateFolder(Folder, 'Плейлисты' , '-type=playlist', '', true);
CreateFolder(Folder, 'Видео' , '-type=video' , '', true);
CreateFolder(Folder, 'Мультфильмы', '-type=video' , '', true);
CreateFolder(Folder, 'Фильмы' , '-type=video -videoType=movie' , '', true);
CreateFolder(Folder, 'Шоу' , '-type=video -videoType=episode', '', true);
}
///////////////////////////////////////////////////////////////////////////////
// Г Л А В Н А Я П Р О Ц Е Д У Р А //
// ----------------------------------------------------------------------------
{
THmsScriptMediaItem Folder, Folder2, Item;
FolderItem.DeleteChildItems(); // Удаление существующих ссылок
CreateFolder(FolderItem, 'Новые видео в подписках', '-newVideos', '--subtitles --pages=4');
CreateSearchFolder(FolderItem, 'Поиск');
Folder = CreateFolder(FolderItem, 'Мой канал', '-MyChannel', '', true);
CreateFolder(Folder, 'Новые видео в подписках', '-newVideos');
CreateFolder(Folder, 'Мои подписки', 'https://www.youtube.com/feed/subscriptions');
//Folder2 = CreateFolder(Folder, 'Мои подписки', '-MySubscriptions', '', true);
//CreateFolder(Folder2, 'Обновить список каналов', 'https://www.youtube.com/feed/subscriptions');
// Folder2 = CreateFolder(Folder, 'Плейлисты' , '-playlists', '', true);
// CreateFolder(Folder2, 'Обновить список плейлистов', '-UpdateMyPlaylists');
CreateFolder(Folder, 'Мои плейлисты', '-playlists');
//CreateFolder(Folder, 'Посмотреть позже' , 'https://www.youtube.com/playlist?list=WL');
CreateFolder(Folder, 'Загруженные видео', '-uploads');
CreateFolder(Folder, 'Понравившиеся' , '-likes');
//CreateFolder(Folder, 'Просмотренные' , 'http://www.youtube.com/feed/history');
//CreateFolder(Folder, 'Рекомендации' , '-recommend');
//CreateFolder(FolderItem, 'Каталог каналов', 'http://www.youtube.com/channels');
//CreateFolder(FolderItem, 'Категории', '-videoCategories');
//CreateFolder(FolderItem, 'Русский Youtube', '-videosByRegion=ru', '');
//CreateFolder(FolderItem, 'Русская музыка Youtube', '-videosByRegion=ru -category=10', '');
Folder = CreateFolder(FolderItem, 'Прямые трансляции', '-search="" -type=video -eventType=live -order=viewCount -relevanceLanguage=ru');
Folder[mpiTranscodingProfile] = 'Фильмы (основной) - FFMPEG';
Folder = CreateFolder(FolderItem, 'Музыка', 'Музыка', '', true);
CreateFolder(Folder, 'Музыка - лучшее в Аргентине', 'https://www.youtube.com/playlist?list=PLFgquLnL59amyKLSulnqkHkLf-GZRVmtN');
CreateFolder(Folder, 'Популярная музыка - Россия', 'https://www.youtube.com/playlist?list=PLgMaGEI-ZiiaHUw-Swt12WOtknrO9UK8G');
CreateFolder(Folder, 'Boiler Room', 'https://www.youtube.com/user/brtvofficial');
CreateFolder(Folder, 'Новые Official Video', '-search="Official Video" -type=video -videoCategoryId=10 -order=date');
Folder = CreateDynamicItem(FolderItem, 'Настройки', '-SettingsFolder', FolderItem[mpiScriptAlt3]);
Folder[501] = 'C++Script'; // mpiDynamicSyntaxType
}
572
C++Script
215
-mpCreateDate
511
Нет скрипта
510
///////////////////////////////////////////////////////////////////////////////
// Г Л О Б А Л Ь Н Ы Е П Е Р Е М Е Н Н Ы Е //
THmsScriptMediaItem Root = FolderItem; // Корневая папка настроек
string gsRootPath = '-SettingsFolder', // Значение поля путь (ссылка) корневой папки настроек
gsKey = '', // Ключ (определяется ниже)
gsValue = ''; // Значение ключа (устанавливается ниже)
TDateTime gTimeStart = Now; // Время запуска скрипта
TStrings SETTINGS; // Объект TStrings для хранения описания настроек
int
mpiAccessToken = 101315, // Коды идентификаторов параметра медиа-ресурса
mpiRefreshToken= 101316, // для хранения токенов в корневой папке подкаста
gnTotalItems = 0, // Глобальный счетчик
// Константы параметров этой динамической папки
mpiFolderType = 200, // Тип папки
mpiDynamicScript = 500, // Скрипт
mpiDynamicSyntaxType = 501, // Язык скрипта
mpiPreviousItemID = 200104, // Предыдущий ItemID
;
///////////////////////////////////////////////////////////////////////////////
// Ф У Н К Ц И И //
// ----------------------------------------------------- Структура настроек ---
void SettingsStructure() {
// Указываем ключи и их наименования
// Также указываем ключ и далее знак ':' и после - значение ключа (или '+' - добавить, '-' - удалить ключ)
//SETTINGS.Values["--chkupdates"] = "Проверять обновления подкаста";
//SETTINGS.Values["--chkupdates:-"] = 'Не проверять';
//SETTINGS.Values["--chkupdates:+"] = 'Проверять';
SETTINGS.Values["--maxheight"] = "Максимальная высота кадра (качество)";
SETTINGS.Values["--maxheight:240" ] = '240';
SETTINGS.Values["--maxheight:320" ] = '320';
SETTINGS.Values["--maxheight:480" ] = '480';
SETTINGS.Values["--maxheight:640" ] = '640';
SETTINGS.Values["--maxheight:1080"] = '1080';
SETTINGS.Values["--maxheight:2048"] = '2048';
SETTINGS.Values["--maxheight:4096"] = '4096';
SETTINGS.Values["--maxheight:4320"] = '4320';
SETTINGS.Values["--subtitles"] = "Показ субтитров";
SETTINGS.Values["--subtitles:-"] = 'Не показывать субтитры';
SETTINGS.Values["--subtitles:+"] = 'Показывать субтитры';
SETTINGS.Values["--adaptive"] = "Адаптивные медиа-потоки (adaptive)";
SETTINGS.Values["--adaptive:-"] = 'Выключить adaptive';
SETTINGS.Values["--adaptive:+"] = 'Включить adaptive';
SETTINGS.Values["--sublanguage"] = "Язык субтитров";
SETTINGS.Values["--sublanguage:ru"] = 'Русский';
SETTINGS.Values["--sublanguage:en"] = 'Английский';
SETTINGS.Values["--sublanguage:hy"] = 'Армянский';
SETTINGS.Values["--sublanguage:be"] = 'Белорусский';
SETTINGS.Values["--sublanguage:kk"] = 'Казахский';
SETTINGS.Values["--sublanguage:lv"] = 'Латышский';
SETTINGS.Values["--sublanguage:pl"] = 'Польский';
SETTINGS.Values["--sublanguage:uk"] = 'Украинский';
SETTINGS.Values["--sublanguage:fr"] = 'Французский';
SETTINGS.Values["--sublanguage:cs"] = 'Чешский';
SETTINGS.Values["--sublanguage:et"] = 'Эстонский';
//SETTINGS.Values["--alert"] = "Вывод окна сообщения авторизации";
//SETTINGS.Values["--alert:-"] = 'Не выводить окно';
//SETTINGS.Values["--alert:+"] = 'Выводить окно';
}
// ----------------------------------------------------------------------------
void ClearAllTokens(THmsScriptMediaItem Folder) {
THmsScriptMediaItem Item;
Folder[mpiAccessToken ] = '';
Folder[mpiRefreshToken] = '';
for (int i=0; i<Folder.ChildCount; i++) {
Item = Folder.ChildItems[i]; if (!Item.IsFolder) continue;
Item[mpiAccessToken ] = '';
Item[mpiRefreshToken] = '';
if (Item.HasChildItems) ClearAllTokens(Item);
}
}
// ---------------------------------- Проверка текущего состояния настройки ---
bool CheckKeyState(string sVal) {
string PARAMS = Root.ItemParent[mpiPodcastParameters]+' '; // Строка установленных параметров подкаста
bool bExist = (Pos(gsKey+' ', PARAMS)>0); // Проверяем, указан ли уже ключ
if (sVal=='-') return !bExist; // Если проверяемое значение '-' - ключ не должен быть указан
else if (sVal=='+') return bExist; // Если проверяемое значение '+' - указан ли ключ
else return (Pos(gsKey+'='+sVal+' ', PARAMS)>0); // Иначе проверяем, установлено ли в параметрах заданное значение
}
// ---------------------------------------------- Создание ссылки-сообщения ---
void ShowMessageLink(string sMsg) {
THmsScriptMediaItem Item = HmsCreateMediaItem(sMsg, FolderItem.ItemID);
Item[mpiThumbnail] = 'http://wonky.lostcut.net/icons/ok.png';
}
// ---------- Функция создания динамической папки с унаследованным скриптом ---
void CreateItem(string sTitle, string sLink) {
THmsScriptMediaItem Folder = FolderItem.AddFolder(sLink, true);
Folder[mpiTitle ] = sTitle;
Folder[mpiCreateDate] = VarToStr(IncTime(gTimeStart,0,-gnTotalItems,0,0)); gnTotalItems++;
Folder.CopyProperties(FolderItem, [mpiFolderType, mpiDynamicScript, mpiDynamicSyntaxType, mpiFolderSortOrder]);
}
///////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------- Создание списка настроек ---
void CreateMainMenu() {
for (int i=0; i<SETTINGS.Count; i++) { // Обходим в цикле все настройки
string sKey = SETTINGS.Names[i]; // Получаем ключ настроек
if (Pos(':', sKey)>0) continue; // Если там есть ':', то это вариант настройки - пропускаем
CreateItem(SETTINGS.Values[sKey], '-showValues='+sKey); // Создаём пункт настройки
}
//CreateItem('Удалить токены для смены кода доступа', '-clearTokens');
}
// ------------------------------------ Создание списка вариантов настройки ---
void CreateValuesList() {
int i; string sVal, sKey, sName, sState; // При входе в процедуру в gsKey уже сам ключ
for (i=0; i<SETTINGS.Count; i++) { // Обходим в цикле все настройки
sKey = SETTINGS.Names[i]; // Получаем ключ настроек
sName = SETTINGS.Values[sKey]; // Наименование варианта настройки
if (!HmsRegExMatch('^'+gsKey+':(.*)', sKey, sVal)) continue; // Если не получили значение нашего ключа, пропускаем
if (CheckKeyState(sVal)) sState='[v]'; else sState='[ ]'; // Проверка: установлено ли данное значение, ставим пометку
CreateItem(sState+' '+sName, '-key='+gsKey+' -value='+sVal); // Создаём пункт варианта настройки
}
}
// ------------------------ Включение/Отключение настройки или его значения ---
void ApplyKeyValue() {
// При входе в процедуру уже в gsKey - ключ, в gsValue - его значение (может быть '+' или '-', это установить или удалить параметр)
bool bExist; string sOldVal=gsKey, sNewVal, PARAMS; // По-умолчанию в sOldVal сам ключ
PARAMS = Root.ItemParent[mpiPodcastParameters]+' '; // Строка установленных параметров подкаста
HmsRegExMatch('('+gsKey+'=.*?)\\s', PARAMS, sOldVal); // Вылавливаем в sOldVal установленное значение
bExist = (Pos(sOldVal, PARAMS)>0); // Устанавливаем флаг присутсвия ключа в параметрах
if (gsValue=='-') sNewVal = ''; // Замена на пустое значение = удалению
else if (gsValue=='+') sNewVal = gsKey; // Просто устанавливаем ключ
else sNewVal = gsKey+'='+gsValue; // Устанавливаем ключ с новым значением
if (bExist) PARAMS = ReplaceStr(PARAMS, sOldVal, sNewVal); // Если ключ уже присутствует - заменяем
else PARAMS += sNewVal; // Иначе просто добавляем
ShowMessageLink('ВЫБРАНО: '+Copy(mpTitle, 5, 99)); // Пропускаем '[ ] ' в mpTitle и сообщаем о выбранном варианте
Root.ItemParent[mpiPodcastParameters] = Trim(ReplaceStr(PARAMS, ' ', ' ')); // Сохраняем параметры подкаста
}
// ----------------------------------------------------------------------------
// Проверка значения ссылки текущей папки и извлечение группировок регулярного выражения в gsKey и gsValue
bool CheckPath(string sPattern) { return HmsRegExMatch2(sPattern, mpFilePath, gsKey, gsValue); }
///////////////////////////////////////////////////////////////////////////////
// Г Л А В Н А Я П Р О Ц Е Д У Р А //
// ----------------------------------------------------------------------------
{
// Поиск корневой динамической папки (ибо этот скрипт может выполнятся и в подпапках)
while ((Root[mpiFilePath]!=gsRootPath) && (Root.ItemParent!=nil)) Root = Root.ItemParent;
if (Root[mpiFilePath]!=gsRootPath) { ShowMessageLink('Не найдена папка настроек с путём '+gsRootPath); return; }
// Если это повторный вызов, смены папки не произошло - ничего не делаем
if ((FolderItem.ItemID==Root[mpiPreviousItemID]) && !DebugMode && (FolderItem[mpiFilePath]!=gsRootPath)) return;
FolderItem.DeleteChildItems(); Root[mpiPreviousItemID] = FolderItem.ItemID;
SETTINGS = TStringList.Create();
try {
SettingsStructure();
if (CheckPath(gsRootPath)) CreateMainMenu(); // Если это корень - создаём список настроек
else if (CheckPath('-showValues=(.*)')) CreateValuesList(); // Зашли в настройку - показываем список вариантов значений
else if (CheckPath('-key=(.*?) -value=(.*)')) ApplyKeyValue(); // Зашли в вариант значения настройки - применяем этот вариант
else if (CheckPath('-clearTokens')) {
ClearAllTokens(Root.ItemParent); // 'Удалить токены для смены кода доступа'
ShowMessageLink('Токены очищены!');
}
} finally { SETTINGS.Free(); }
HmsIncSystemUpdateID(); // Говорим устройству об обновлении содержания
}
-
53
c3f67e87aa15ae314e32cb7beac8cb09
-newVideos
b65318d7-8a9a-425f-92b9-9feba609ef39
515
2
512
2
532
2
700
2
553
2
42
3
4
Новые видео в подписках
35
43862,9389074884
527
--subtitles --pages=4
215
-mpCreateDate
93
43862,9389201157
525
44131,9830998032
-
51
01fa37188cfa11d74fb14e3d31d87c11
Поиск
b65318d7-8a9a-425f-92b9-9feba609ef39
515
2
512
2
532
2
700
2
553
2
42
3
35
43862,9382175926
215
-mpCreateDate
93
43862,9389201157
-
32
3add83ac4a34b90ab195fa4950187302
-SearchCommands
01fa37188cfa11d74fb14e3d31d87c11
515
2
512
2
532
2
700
2
553
2
42
3
4
"Набрать текст"
35
43862,9382175926
200
5
500
mpiPreviousItemID = 200104;
mpiDoNothing = 201100;
Root = GetRoot();
gsTextSearch = HmsGetUserSearchText();
gsMsg = '';
gsSuggestUrl = 'http://www.google.ru/complete/search?sclient=psy-ab&q=';
gsSuggestResultCut = '';
gsSuggestRegExp = '\\["(.*?)",';
gsSuggestMethod = 'GET';
gnSuggestNoUTFEnc = 0;
///////////////////////////////////////////////////////////////////////////////
function GetRoot() {
folder = FolderItem;
while ((folder[mpiFilePath]!='-SearchCommands') && (folder.ItemParent!=nil)) folder=folder.ItemParent;
return folder;
}
///////////////////////////////////////////////////////////////////////////////
function CreateLink(folder, title, img) {
Item = HmsCreateMediaItem(folder.ItemID, folder.ItemID);
Item[mpiTitle ] = title;
Item[mpiThumbnail] = img;
}
///////////////////////////////////////////////////////////////////////////////
function CreateFolder(title, link) {
if (Trim(title)=='') title = 'Пробел';
folder = FolderItem.AddFolder(link, true);
folder[mpiTitle] = title;
folder.CopyProperties(FolderItem, [200, 500, 501]); // Копируем тип папки, скрипт, язык срипта
}
///////////////////////////////////////////////////////////////////////////////
function AddPodcastSearch(itemID) {
if (Trim(gsTextSearch)=='') { gsMsg = 'Текст поиска не набран! Добавлять нечего.'; return; }
if (LowerCase(gsTextSearch)==gsTextSearch) gsTextSearch = NameCase(gsTextSearch);
if (Trim(itemID)!='') Folder = Root.ItemParent.FindItemByProperty(mpiItemID, itemID);
else Folder = Root.ItemParent;
Item = Folder.AddFolder(gsTextSearch, false);
Item[mpiFilePath ] = Format('search="%s"', [gsTextSearch]);
Item[mpiTitle ] = gsTextSearch;
HmsDatabaseAutoSave(false);
gsMsg = Format('Подкаст "%s" добавлен в "%s"', [gsTextSearch, Folder[mpiTitle]]);
}
///////////////////////////////////////////////////////////////////////////////
function CreateSearchCommands() {
CreateFolder('#', '-Chars=!:');
CreateFolder('A-Z', '-Chars=AZ');
CreateFolder('А-Я', '-Chars=АЯ');
CreateFolder('Очистить текст поиска', '-Clear');
CreateFolder('Добавить в папку '+Root.ItemParent[mpiTitle], '-Save=');
for(i=0; i<Root.ItemParent.ChildCount; i++) {
Item = Root.ItemParent.ChildItems[i];
if (Item.ItemClassID==51) CreateFolder('Добавить в папку '+Item[mpiTitle], '-Save='+Item[mpiItemID]);
}
CreateFolder('Очистить историю поиска в папке '+Root.ItemParent[mpiTitle], '-ClearHistory=');
for(i=0; i<Root.ItemParent.ChildCount; i++) {
Item = Root.ItemParent.ChildItems[i];
if (Item.ItemClassID==51) CreateFolder('Очистить историю поиска в папке '+Item[mpiTitle], '-ClearHistory='+Item[mpiItemID]);
}
}
///////////////////////////////////////////////////////////////////////////////
function ClearSearchHistory(itemID) {
if (Trim(itemID)!='') Folder = Root.ItemParent.FindItemByProperty(mpiItemID, itemID);
else Folder = Root.ItemParent;
for(i=0; i<Folder.ChildCount; i++) {
Item = Folder.ChildItems[i]; // Ищем все элементы, у которых значение mpiFilePath начиначется с 'search'
if (LeftCopy(Item[mpiFilePath], 6)=='search') { Item.Delete(); i--; }
}
gsMsg = 'История поиска в папке "'+Folder[mpiTitle]+'" очищена';
}
///////////////////////////////////////////////////////////////////////////////
function CreateCharFolders(chars) {
ch1 = Copy(chars, 1, 1); ch2 = Copy(chars, 2, 1);
CreateFolder('Удалить последний символ', '-Char=Delete'); // Вначале - команда удаления символа
CreateFolder(' ', '-Char= '); // Пробел
for (i=ord(ch1); i<=ord(ch2); i++) CreateFolder(Chr(i), '-Char='+Chr(i));
}
///////////////////////////////////////////////////////////////////////////////
function LoadSuggestions() {
// Suggestions ------ Блок работы с подсказками -------
if ((LeftCopy(gsTextSearch, 1)==' ') || (Trim(gsSuggestUrl)=='') || (Length(gsTextSearch)<2)) return;
nPort = 80; if (LeftCopy(gsSuggestUrl, 5)=="https") nPort = 443;
text = gsTextSearch; if (gnSuggestNoUTFEnc==0) text = HmsUtf8Encode(text); // Если не указано не кодировать в UTF - кодируем
text = HmsPercentEncode(text);
// Если есть ключ <TEXT> в запросе - заменяем его на значение набранного текста, иначе просто добавляем в конец
if (Pos('<TEXT>', gsSuggestUrl)>0) gsSuggestUrl = ReplaceStr(gsSuggestUrl, '<TEXT>', text);
else gsSuggestUrl += text;
HmsRegExMatch3('(https?://(.*?))(/.*)', gsSuggestUrl, urlBase, server, page);
if (gsSuggestMethod=='POST') HmsRegExMatch2('^(.*?)\\?(.*)', page, page, post);
headers = urlBase+'/\r\n'+
'Accept-Encoding: gzip, deflate\r\n'+
'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:13.0) Gecko/20100101 Firefox/13.0\r\n'+
'Connection: Keep-Alive\r\n'+
'Origin: '+urlBase+'/\r\n'+
'Accept: application/json, text/javascript, */*; q=0.01\r\n'; // Для включения возможности gzip в запросах
text = HmsSendRequestEx(server, page, gsSuggestMethod, 'application/x-www-form-urlencoded; Charset=UTF-8', headers, post, nPort, 0x10, retHeaders, true);
text = HmsUtf8Decode(text);
if (gsSuggestResultCut!='') HmsRegExMatch(gsSuggestResultCut, text, text);// Если есть выражение обрезки - обрезаем
text = HmsJsonDecode(text); TRegExpr t = TRegExpr.Create('(<[^>]+>)'); // Избавляемся от тегов в середине слов подсказки
TRegExpr reSearch = TRegExpr.Create(gsSuggestRegExp, PCRE_SINGLELINE);
if (reSearch.Search(text)) do {
s = reSearch.Match; gnTotalItems=0;
if (t.Search(s)) do s=ReplaceStr(s, t.Match, ''); while (t.SearchAgain());// (функция HmsHtmlToText не подходит т.к. ставит пробел в середине слова)
if (HmsRegExMatch('^(.*?)[/\\(\\|]', s, sCh)) { // Обрезаем подсказку до знаков /, ( или |
if (Pos(LowerCase(gsTextSearch), LowerCase(sCh))>0) s = sCh; // Если после этого в подсказке встречается набранный текст - то так и оставляем
}
if (Pos(LowerCase(gsTextSearch), LowerCase(s))<1) continue;
if (LowerCase(s)==s) s = NameCase(s); // Если подсказки - все маленькие буквы, делаем NameCase
// Если в подсказке больше одного слова - дополнительно создаём сначала подсказки из слов, которые содержат набранный текст (выделяем слова отдельно)
if (WordCount(s, ' ')>1) {
nCnt = WordCount(s, ' ');
for (i=1; i<=nCnt; i++) {
sCh=ExtractWord(i, s, ' '); if (Trim(sCh)=='') continue;
if (Pos(LowerCase(gsTextSearch), LowerCase(sCh))<1) continue;
if (LowerCase(gsTextSearch)==LowerCase(sCh)) continue;
sCh = ReplaceStr(ReplaceStr(ReplaceStr(sCh, '\\', ''), ',', ''), ':', '');
CreateFolder('Вариант: '+sCh, '-SetText='+sCh);
}
} gnTotalItems++;
// Создаём папку с предложением варианта (подсказку)
if (LowerCase(s)!=LowerCase(gsTextSearch)) CreateFolder('Вариант: '+s, '-SetText='+s);
if (gnTotalItems>100) break; // Ограничиваем количество создаваемых элементов = 100
} while (reSearch.SearchAgain());
}
///////////////////////////////////////////////////////////////////////////////
{
// Если это повторный вызов, смены папки не произошло - ничего не делаем
if ((FolderItem.ItemID==Root[mpiPreviousItemID]) && !DebugMode && (FolderItem[mpiFilePath]!='-SearchCommands')) return;
FolderItem.DeleteChildItems(); Root[mpiPreviousItemID] = FolderItem.ItemID;
if (Root[mpiDoNothing]=='1') Root[mpiDoNothing] = ''; // Флаг "Ничего не делать" - например, при возврате в команду набирания буквы из подпапки варианта
else if (mpFilePath=='-SearchCommands') CreateSearchCommands();
else if (mpFilePath=='-Clear' ) gsTextSearch = '';
else if (mpFilePath=='-Char=Delete' ) gsTextSearch = LeftCopy(gsTextSearch, Length(gsTextSearch)-1); // Удаление последнего символа
else if (HmsRegExMatch('-Save=(.*)' , mpFilePath, chars)) AddPodcastSearch (chars);
else if (HmsRegExMatch('-ClearHistory=(.*)', mpFilePath, chars)) ClearSearchHistory(chars);
else if (HmsRegExMatch('-Char=(.+)' , mpFilePath, chars)) { if (chars=='') chars =' '; gsTextSearch += chars; }
else if (HmsRegExMatch('-Chars=(.+)' , mpFilePath, chars)) CreateCharFolders(chars);
else if (HmsRegExMatch('-SetText=(.+)', mpFilePath, chars)) { gsTextSearch = chars; Root[mpiDoNothing]='1'; } // включаем флаг не выполнять команду при возврате из этой папки
HmsSetUserSearchText(gsTextSearch);
if (gsMsg!='') CreateLink(FolderItem, gsMsg, 'http://wonky.lostcut.net/icons/ok.png');
else CreateLink(FolderItem, 'Текст поиска: '+gsTextSearch, 'http://wonky.lostcut.net/icons/search-icon1.jpg');
CreateLink(Root, 'Текст поиска: '+gsTextSearch, 'http://wonky.lostcut.net/icons/search-icon1.jpg');
if (HmsRegExMatch('-(Char)=', mpFilePath, '')) LoadSuggestions();
HmsIncSystemUpdateID(); // Говорим устройству об обновлении содержания
}
501
JScript
215
-35
93
43862,9389201157
200104
fbcf01f870e0415c04d349145b5f5828
-
51
fba19edb9bcb3950235ddab9934e57e0
-type=channel
01fa37188cfa11d74fb14e3d31d87c11
515
2
512
2
532
2
700
2
553
2
42
3
4
Каналы
35
43862,9375185995
215
-mpCreateDate
93
43862,9389201157
-
51
6932f1eeab1d1c0774d989db66644070
-type=playlist
01fa37188cfa11d74fb14e3d31d87c11
515
2
512
2
532
2
700
2
553
2
42
3
4
Плейлисты
35
43862,9368241551
215
-mpCreateDate
93
43862,9389201157
-
51
3712a0d2ffe2b1408d8931c7a01aea35
-type=video
01fa37188cfa11d74fb14e3d31d87c11
515
2
512
2
532
2
700
2
553
2
42
3
4
Мультфильмы
35
43862,9354352662
215
-mpCreateDate
93
43862,9389201157
-
51
6f66bade291e964d76288d8bbc4c9576
-type=video -videoType=movie
01fa37188cfa11d74fb14e3d31d87c11
515
2
512
2
532
2
700
2
553
2
42
3
4
Фильмы
35
43862,9347408218
215
-mpCreateDate
93
43862,9389201157
-
51
9c98763f2815c6b7741901f9d8902417
-type=video -videoType=episode
01fa37188cfa11d74fb14e3d31d87c11
515
2
512
2
532
2
700
2
553
2
42
3
4
Шоу
35
43862,9340463773
215
-mpCreateDate
93
43862,9389201157
-
51
2cf7fb56b648f64b80acf8e87eb77744
-MyChannel
b65318d7-8a9a-425f-92b9-9feba609ef39
515
2
512
2
532
2
700
2
553
2
42
3
4
Мой канал
35
43862,9333519329
215
-mpCreateDate
93
43862,9389201273
-
53
a9a2f5b4c7fd7753d712e04deba25a5e
-newVideos
2cf7fb56b648f64b80acf8e87eb77744
515
2
512
2
532
2
700
2
553
2
42
3
4
Новые видео в подписках
35
43862,9326574884
215
-mpCreateDate
93
43862,9389201273
525
44009,7970188426
-
53
a10ef0cbb9ef5923c7367f98cab2620c
https://www.youtube.com/feed/subscriptions
2cf7fb56b648f64b80acf8e87eb77744
515
2
512
2
532
2
700
2
553
2
42
3
4
Мои подписки
35
43862,931963044
215
-mpCreateDate
93
43862,9389201273
525
44131,9833118981
-
53
1627de2c22f8b74a59d214c03764da66
-playlists
2cf7fb56b648f64b80acf8e87eb77744
515
2
512
2
532
2
700
2
553
2
42
3
4
Мои плейлисты
35
43862,9312685995
215
-mpCreateDate
93
43862,9389201273
-
53
d0a13ca18b252520c63a280648f3c7df
-uploads
2cf7fb56b648f64b80acf8e87eb77744
515
2
512
2
532
2
700
2
553
2
42
3
4
Загруженные видео
35
43862,9305741551
215
-mpCreateDate
93
43862,9389201273
-
53
c5ba2c6b1675584fa744199f1a98605c
-likes
2cf7fb56b648f64b80acf8e87eb77744
515
2
512
2
532
2
700
2
553
2
42
3
4
Понравившиеся
35
43862,9298797106
215
-mpCreateDate
93
43862,9389201273
525
43906,926959456
-
53
2ceff415550aa0cd3747752595a5fa8a
-search="" -type=video -eventType=live -order=viewCount -relevanceLanguage=ru
b65318d7-8a9a-425f-92b9-9feba609ef39
515
2
512
2
532
2
700
2
553
2
42
3
4
Прямые трансляции
35
43862,9291852662
215
-mpCreateDate
50
Фильмы (основной) - FFMPEG
93
43862,9389201273
701
-1
702
-1
517
578-720,722-1080,482-576,402-480,322-400,202-320,0-200
518
0
522
0
245
2ceff415550aa0cd3747752595a5fa8a
525
44131,9597086921
FFMPEG
Transcoders\ffmpeg.exe
http://www.ffmpeg.org/
http://www.ffmpeg.org/
FFMPEG
Нет скрипта
Транскодирование с перекодированием видео
3
begin
if SameText(cfgTranscodingFileFormat, 'MPEG (DVD)') then
FileExt := 'mpg'
else if Pos('MPEGTS', cfgTranscodingFileFormat) > 0 then
FileExt := 'ts'
else if Pos('ASF', cfgTranscodingFileFormat) > 0 then
FileExt := 'wmv'
else if SameText(cfgTranscodingFileFormat, 'MP4') then
FileExt := 'mp4'
else if SameText(cfgTranscodingFileFormat, 'MPEG1') then
FileExt := 'mpeg'
else
FileExt := '';
if FileExt <> '' then
MimeType := HmsGetMimeType(FileExt)
else
MimeType := ''
end.
PascalScript
0
{
TranscodingParams = HmsTranscodingInputParams + HmsTranscodingVideoParams + HmsTranscodingMapParams(mpAudioStreamNo);
If (HmsRegExMatch('(-vstreamid \\d+)', TranscodingParams, gsUserVariable1))
TranscodingParams = ReplaceStr(TranscodingParams, gsUserVariable1, '');
If (HmsRegExMatch('(-astreamid \\d+)', TranscodingParams, gsUserVariable1))
TranscodingParams = ReplaceStr(TranscodingParams, gsUserVariable1, '');
}
JScript
Фильмы (основной) - FFMPEG
-1
4
b15ffc44-3c1b-4f7d-9c19-fbd75ab5b042
-
51
38ab1d14429c099ff644c137f59287fd
Музыка
b65318d7-8a9a-425f-92b9-9feba609ef39
515
2
512
2
532
2
700
2
553
2
42
3
35
43862,9284908218
215
-mpCreateDate
93
43862,9389201273
-
53
5709a22b5fa60fddc609feada7390802
https://www.youtube.com/playlist?list=PLFgquLnL59amyKLSulnqkHkLf-GZRVmtN
38ab1d14429c099ff644c137f59287fd
515
2
512
2
532
2
700
2
553
2
42
3
4
Музыка - лучшее в Аргентине
35
43862,9277963773
215
-mpCreateDate
93
43862,9389201273
-
53
f04522c18ce5a18c313858426dd065da
https://www.youtube.com/playlist?list=PLgMaGEI-ZiiaHUw-Swt12WOtknrO9UK8G
38ab1d14429c099ff644c137f59287fd
515
2
512
2
532
2
700
2
553
2
42
3
4
Популярная музыка - Россия
35
43862,9271019329
215
-mpCreateDate
93
43862,9389201273
-
53
22ced0d85c4a266423969b5450067135
https://www.youtube.com/user/brtvofficial
38ab1d14429c099ff644c137f59287fd
515
2
512
2
532
2
700
2
553
2
42
3
4
Boiler Room
35
43862,9264074884
215
-mpCreateDate
93
43862,9389201273
-
53
dada9df18d7b10f3291bf10aecbbfb88
-search="Official Video" -type=video -videoCategoryId=10 -order=date
38ab1d14429c099ff644c137f59287fd
515
2
512
2
532
2
700
2
553
2
42
3
4
Новые Official Video
35
43862,925713044
215
-mpCreateDate
93
43862,9389201273
-
32
a87bec1bafc9b3a26e07c374e16b29bb
-SettingsFolder
b65318d7-8a9a-425f-92b9-9feba609ef39
515
2
512
2
532
2
700
2
553
2
42
3
4
Настройки
35
43862,9347453704
200
5
500
///////////////////////////////////////////////////////////////////////////////
// Г Л О Б А Л Ь Н Ы Е П Е Р Е М Е Н Н Ы Е //
THmsScriptMediaItem Root = FolderItem; // Корневая папка настроек
string gsRootPath = '-SettingsFolder', // Значение поля путь (ссылка) корневой папки настроек
gsKey = '', // Ключ (определяется ниже)
gsValue = ''; // Значение ключа (устанавливается ниже)
TDateTime gTimeStart = Now; // Время запуска скрипта
TStrings SETTINGS; // Объект TStrings для хранения описания настроек
int
mpiAccessToken = 101315, // Коды идентификаторов параметра медиа-ресурса
mpiRefreshToken= 101316, // для хранения токенов в корневой папке подкаста
gnTotalItems = 0, // Глобальный счетчик
// Константы параметров этой динамической папки
mpiFolderType = 200, // Тип папки
mpiDynamicScript = 500, // Скрипт
mpiDynamicSyntaxType = 501, // Язык скрипта
mpiPreviousItemID = 200104, // Предыдущий ItemID
;
///////////////////////////////////////////////////////////////////////////////
// Ф У Н К Ц И И //
// ----------------------------------------------------- Структура настроек ---
void SettingsStructure() {
// Указываем ключи и их наименования
// Также указываем ключ и далее знак ':' и после - значение ключа (или '+' - добавить, '-' - удалить ключ)
//SETTINGS.Values["--chkupdates"] = "Проверять обновления подкаста";
//SETTINGS.Values["--chkupdates:-"] = 'Не проверять';
//SETTINGS.Values["--chkupdates:+"] = 'Проверять';
SETTINGS.Values["--maxheight"] = "Максимальная высота кадра (качество)";
SETTINGS.Values["--maxheight:240" ] = '240';
SETTINGS.Values["--maxheight:320" ] = '320';
SETTINGS.Values["--maxheight:480" ] = '480';
SETTINGS.Values["--maxheight:640" ] = '640';
SETTINGS.Values["--maxheight:1080"] = '1080';
SETTINGS.Values["--maxheight:2048"] = '2048';
SETTINGS.Values["--maxheight:4096"] = '4096';
SETTINGS.Values["--maxheight:4320"] = '4320';
SETTINGS.Values["--subtitles"] = "Показ субтитров";
SETTINGS.Values["--subtitles:-"] = 'Не показывать субтитры';
SETTINGS.Values["--subtitles:+"] = 'Показывать субтитры';
SETTINGS.Values["--adaptive"] = "Адаптивные медиа-потоки (adaptive)";
SETTINGS.Values["--adaptive:-"] = 'Выключить adaptive';
SETTINGS.Values["--adaptive:+"] = 'Включить adaptive';
SETTINGS.Values["--sublanguage"] = "Язык субтитров";
SETTINGS.Values["--sublanguage:ru"] = 'Русский';
SETTINGS.Values["--sublanguage:en"] = 'Английский';
SETTINGS.Values["--sublanguage:hy"] = 'Армянский';
SETTINGS.Values["--sublanguage:be"] = 'Белорусский';
SETTINGS.Values["--sublanguage:kk"] = 'Казахский';
SETTINGS.Values["--sublanguage:lv"] = 'Латышский';
SETTINGS.Values["--sublanguage:pl"] = 'Польский';
SETTINGS.Values["--sublanguage:uk"] = 'Украинский';
SETTINGS.Values["--sublanguage:fr"] = 'Французский';
SETTINGS.Values["--sublanguage:cs"] = 'Чешский';
SETTINGS.Values["--sublanguage:et"] = 'Эстонский';
//SETTINGS.Values["--alert"] = "Вывод окна сообщения авторизации";
//SETTINGS.Values["--alert:-"] = 'Не выводить окно';
//SETTINGS.Values["--alert:+"] = 'Выводить окно';
}
// ----------------------------------------------------------------------------
void ClearAllTokens(THmsScriptMediaItem Folder) {
THmsScriptMediaItem Item;
Folder[mpiAccessToken ] = '';
Folder[mpiRefreshToken] = '';
for (int i=0; i<Folder.ChildCount; i++) {
Item = Folder.ChildItems[i]; if (!Item.IsFolder) continue;
Item[mpiAccessToken ] = '';
Item[mpiRefreshToken] = '';
if (Item.HasChildItems) ClearAllTokens(Item);
}
}
// ---------------------------------- Проверка текущего состояния настройки ---
bool CheckKeyState(string sVal) {
string PARAMS = Root.ItemParent[mpiPodcastParameters]+' '; // Строка установленных параметров подкаста
bool bExist = (Pos(gsKey+' ', PARAMS)>0); // Проверяем, указан ли уже ключ
if (sVal=='-') return !bExist; // Если проверяемое значение '-' - ключ не должен быть указан
else if (sVal=='+') return bExist; // Если проверяемое значение '+' - указан ли ключ
else return (Pos(gsKey+'='+sVal+' ', PARAMS)>0); // Иначе проверяем, установлено ли в параметрах заданное значение
}
// ---------------------------------------------- Создание ссылки-сообщения ---
void ShowMessageLink(string sMsg) {
THmsScriptMediaItem Item = HmsCreateMediaItem(sMsg, FolderItem.ItemID);
Item[mpiThumbnail] = 'http://wonky.lostcut.net/icons/ok.png';
}
// ---------- Функция создания динамической папки с унаследованным скриптом ---
void CreateItem(string sTitle, string sLink) {
THmsScriptMediaItem Folder = FolderItem.AddFolder(sLink, true);
Folder[mpiTitle ] = sTitle;
Folder[mpiCreateDate] = VarToStr(IncTime(gTimeStart,0,-gnTotalItems,0,0)); gnTotalItems++;
Folder.CopyProperties(FolderItem, [mpiFolderType, mpiDynamicScript, mpiDynamicSyntaxType, mpiFolderSortOrder]);
}
///////////////////////////////////////////////////////////////////////////////
// ----------------------------------------------- Создание списка настроек ---
void CreateMainMenu() {
for (int i=0; i<SETTINGS.Count; i++) { // Обходим в цикле все настройки
string sKey = SETTINGS.Names[i]; // Получаем ключ настроек
if (Pos(':', sKey)>0) continue; // Если там есть ':', то это вариант настройки - пропускаем
CreateItem(SETTINGS.Values[sKey], '-showValues='+sKey); // Создаём пункт настройки
}
//CreateItem('Удалить токены для смены кода доступа', '-clearTokens');
}
// ------------------------------------ Создание списка вариантов настройки ---
void CreateValuesList() {
int i; string sVal, sKey, sName, sState; // При входе в процедуру в gsKey уже сам ключ
for (i=0; i<SETTINGS.Count; i++) { // Обходим в цикле все настройки
sKey = SETTINGS.Names[i]; // Получаем ключ настроек
sName = SETTINGS.Values[sKey]; // Наименование варианта настройки
if (!HmsRegExMatch('^'+gsKey+':(.*)', sKey, sVal)) continue; // Если не получили значение нашего ключа, пропускаем
if (CheckKeyState(sVal)) sState='[v]'; else sState='[ ]'; // Проверка: установлено ли данное значение, ставим пометку
CreateItem(sState+' '+sName, '-key='+gsKey+' -value='+sVal); // Создаём пункт варианта настройки
}
}
// ------------------------ Включение/Отключение настройки или его значения ---
void ApplyKeyValue() {
// При входе в процедуру уже в gsKey - ключ, в gsValue - его значение (может быть '+' или '-', это установить или удалить параметр)
bool bExist; string sOldVal=gsKey, sNewVal, PARAMS; // По-умолчанию в sOldVal сам ключ
PARAMS = Root.ItemParent[mpiPodcastParameters]+' '; // Строка установленных параметров подкаста
HmsRegExMatch('('+gsKey+'=.*?)\\s', PARAMS, sOldVal); // Вылавливаем в sOldVal установленное значение
bExist = (Pos(sOldVal, PARAMS)>0); // Устанавливаем флаг присутсвия ключа в параметрах
if (gsValue=='-') sNewVal = ''; // Замена на пустое значение = удалению
else if (gsValue=='+') sNewVal = gsKey; // Просто устанавливаем ключ
else sNewVal = gsKey+'='+gsValue; // Устанавливаем ключ с новым значением
if (bExist) PARAMS = ReplaceStr(PARAMS, sOldVal, sNewVal); // Если ключ уже присутствует - заменяем
else PARAMS += sNewVal; // Иначе просто добавляем
ShowMessageLink('ВЫБРАНО: '+Copy(mpTitle, 5, 99)); // Пропускаем '[ ] ' в mpTitle и сообщаем о выбранном варианте
Root.ItemParent[mpiPodcastParameters] = Trim(ReplaceStr(PARAMS, ' ', ' ')); // Сохраняем параметры подкаста
}
// ----------------------------------------------------------------------------
// Проверка значения ссылки текущей папки и извлечение группировок регулярного выражения в gsKey и gsValue
bool CheckPath(string sPattern) { return HmsRegExMatch2(sPattern, mpFilePath, gsKey, gsValue); }
///////////////////////////////////////////////////////////////////////////////
// Г Л А В Н А Я П Р О Ц Е Д У Р А //
// ----------------------------------------------------------------------------
{
// Поиск корневой динамической папки (ибо этот скрипт может выполнятся и в подпапках)
while ((Root[mpiFilePath]!=gsRootPath) && (Root.ItemParent!=nil)) Root = Root.ItemParent;
if (Root[mpiFilePath]!=gsRootPath) { ShowMessageLink('Не найдена папка настроек с путём '+gsRootPath); return; }
// Если это повторный вызов, смены папки не произошло - ничего не делаем
if ((FolderItem.ItemID==Root[mpiPreviousItemID]) && !DebugMode && (FolderItem[mpiFilePath]!=gsRootPath)) return;
FolderItem.DeleteChildItems(); Root[mpiPreviousItemID] = FolderItem.ItemID;
SETTINGS = TStringList.Create();
try {
SettingsStructure();
if (CheckPath(gsRootPath)) CreateMainMenu(); // Если это корень - создаём список настроек
else if (CheckPath('-showValues=(.*)')) CreateValuesList(); // Зашли в настройку - показываем список вариантов значений
else if (CheckPath('-key=(.*?) -value=(.*)')) ApplyKeyValue(); // Зашли в вариант значения настройки - применяем этот вариант
else if (CheckPath('-clearTokens')) {
ClearAllTokens(Root.ItemParent); // 'Удалить токены для смены кода доступа'
ShowMessageLink('Токены очищены!');
}
} finally { SETTINGS.Free(); }
HmsIncSystemUpdateID(); // Говорим устройству об обновлении содержания
}
501
C++Script
215
-mpCreateDate
93
43862,9389201273
200104
c107bbfaf1aa594f1bad2f164057fecb
245
a87bec1bafc9b3a26e07c374e16b29bb