После установки Node.js
Устанавливаем puppeteer без Chromium (350 МВ)sudo dnf module list nodejs sudo dnf module enable nodejs:12 sudo dnf install nodejs
При запуске puppeteer указываем путь к исполняемому файлу Google Chromeecho "export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true && npm install i puppeteer" | sudo sh
Выключаем CORSexecutablePath: '/opt/google/chrome/google-chrome',
Из интересного для сохранения «.m3u8» и «.ts» используем page.evaluate, далее fetch в контексте браузера, затем передаем содержимое файла в puppeteer'--disable-web-security', '--disable-site-isolation-trials',
и сохраняем файл определенной с помощью page.exposeFunction функцией writefile.let dataString = '[' + (new Uint8Array(data)).toString() + ']';
Еще чуток бэкенда на php'use strict'; const puppeteer = require('puppeteer'); const fs = require('fs'); (async function main() { var browser; try { var myArgs = process.argv.slice(2); if (!myArgs.length) { throw('need dir'); } console.log('dir: ', myArgs[0]); browser = await puppeteer.launch({ headless: true, slowMo: 250, // slow down by 250ms // devtools: true, executablePath: '/opt/google/chrome/google-chrome', defaultViewport: null, // иначе не работает --window-size=1920,1080 args:[ '--window-size=1920,1080', '--disable-setuid-sandbox', '--no-sandbox', '--disable-infobars', '--disable-web-security', '--disable-site-isolation-trials', ], }); /* var lessonUrl = myArgs[0] const crypto = require('crypto'); const hash = crypto.createHash('sha256'); hash.update(lessonUrl); var dir = 'fdb' + '/' + hash.digest('hex'); if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } var dirOriginal = dir + '/' + 'original'; if (!fs.existsSync(dirOriginal)) { fs.mkdirSync(dirOriginal); } */ var lessonUrl = myArgs[1]; var dir = myArgs[0]; var dirOriginal = dir + '/' + 'original'; const regUrl = 'https://zzz.ru/user/registration' const page = await browser.newPage() page.on('console', msg => console.log('PAGE LOG:', msg.text())); // await page.setDefaultNavigationTimeout(60000); await page.goto(regUrl, { waitUntil: 'load' }) await page.waitForTimeout(1000); await page.screenshot({ path: dir + '/' + 'screenshot01.png' }) let checkUrl = await page.evaluate(() => location.href); if (regUrl == checkUrl) { await page.click('[data-qa="__authEmailButton"]') await page.type('#email', 'username@mail.ru') await page.type('#password', '12wdnj4ss7ty') await page.screenshot({ path: dir + '/' + 'screenshot02.png' }) await Promise.all([ page.click('[data-qa="__authEmailSubmitButton"]'), page.waitForNavigation(), // if "await page.waitForNavigation" then "TimeoutError: Navigation timeout of 30000 ms exceeded", причем на ноде поновее всё работало ]); } await page.waitForTimeout(2000); await page.screenshot({ path: dir + '/' + 'screenshot1.png' }) console.log(lessonUrl); await page.goto(lessonUrl, { waitUntil: 'load' }) await page.waitForTimeout(2000); await page.screenshot({ path: dir + '/' + 'screenshot2.png' }) /* // forEach и async не очень дружат var videoFrame; page.frames().forEach(async (frame) => { let tt = await frame.$('#shaka-video'); if (tt) { videoFrame = frame; } }); */ var videoFrame; for (const frame of page.frames()) { let tt = await frame.$('#shaka-video'); if (tt) { videoFrame = frame; } } if (!videoFrame) { throw('#shaka-video not found!'); } if (videoFrame) { console.log(videoFrame.url()); await page.goto(videoFrame.url(), { waitUntil: 'load' }) await page.waitForTimeout(5000); await page.screenshot({ path: dir + '/' + 'screenshot3.png' }) const filesUrls = await page.evaluate(() => { var urls = []; var ui0 = document.getElementById('shaka-player').ui; urls.push(ui0.b.Fa); // "https://storage.svc.services/api/v2/backends/yandex/sets/hls.webinar::198a2db0-e74b-44c8-ac22-7cec61972e8a/objects/long.v2.yandex.master.m3u8" // ссылка после редиректа получаем подписанные (pre-signed) URL // ui0.b.s.K // "https://storage-lb.services/hls.webinar/198a2db0-e74b-44c8-ac22-7cec61972e8a.long.v2.yandex.master.m3u8?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=DNmtuhzj5w4VFKS-9BUA%2F20201228%2Fru-central1%2Fs3%2Faws4_request&X-Amz-Date=20201228T142459Z&X-Amz-Expires=300&X-Amz-Signature=0a10db545d16abdfd81c988d7f6e488dfc36502aab60456da78afa9f1ed5bac8&X-Amz-SignedHeaders=host" var a0=v0=undefined; // select audio and video streams ui0.b.s.a.forEach((v,k)=>{ if (!a0 && v.stream.type=='audio') {a0=v;} if (v.stream.type=='video' && (!v0 || v.stream.width > v0.stream.width)) {v0=v;} }); // load and save selected audio and video segments description files urls.push(a0.Wc); urls.push(v0.Wc); // ["https://storage.svc.services/api/v2/backends/yandex/sets/hls.webinar::198a2db0-e74b-44c8-ac22-7cec61972e8a/objects/long.audio.32kbps.1.1608056896.ts" length: 1 __proto__: Array(0) a0.stream.segmentIndex.a.forEach((v,k)=>{ urls = urls.concat(v.c()); }); v0.stream.segmentIndex.a.forEach((v,k)=>{ urls = urls.concat(v.c()); }); return urls; }); console.log(filesUrls[0], filesUrls[1], filesUrls[2], filesUrls[3], filesUrls.length); var filesUrlsSaved = 0; await page.exposeFunction('writefile', async (filePath, data) => { // let dataBuffer = new TextEncoder().encode(data); filesUrlsSaved++; let dataBuffer = new Uint8Array(JSON.parse(data)); return new Promise((resolve, reject) => { fs.writeFile(filePath, dataBuffer, {encoding: null}, (err) => { if (err) reject(err); else resolve(); }); }); }); if (filesUrls.length) { fs.writeFileSync(dirOriginal + '/loading', filesUrls[1] + '\n' + filesUrls[2] + '\n', {flag: 'w'}); } for (var loadFile of filesUrls) { await page.evaluate(async (dirOriginal, loadFile) => { let fileName = loadFile.match('[^/]*$')[0].match('^[^#?]*'); await fetch(loadFile, { method: "GET", redirect: 'follow', }).then((response) => { console.log('url with auth', response.url); return response.arrayBuffer(); }).then(async function(data) { // let dataView = new Uint8Array(data); // let dataString = new TextDecoder().decode(data); let dataString = '[' + (new Uint8Array(data)).toString() + ']'; await window.writefile(dirOriginal + '/' + fileName, dataString); }); }, dirOriginal, loadFile); /* // error 403 var viewSource = await page.goto(loadFile); fs.writeFile(dirOriginal + '/' + fileName, await viewSource.buffer(), function (err) { if (err) { return console.log(err); } }); */ } // if (filesUrls.length) { // fs.writeFileSync(dirOriginal + '/loaded', '', {flag: 'w'}); // } if (filesUrls.length != filesUrlsSaved) { console.error('filesUrls:', filesUrls.length, filesUrlsSaved); throw('Not all files saved!'); } } browser.close() console.log('See screenshot in: ' + dir) } catch (err) { console.error(err); browser.close() } })();
['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; $proc = proc_open($cmd . "\n", $descriptorspec, $pipes); foreach ($pipes as $pipe) { stream_set_blocking($pipe, false); } fwrite($pipes[0], $input); fclose($pipes[0]); // $stdout = stream_get_contents($pipes[1]); // $stderr = stream_get_contents($pipes[2]); $stdout = ''; $stderr = ''; while (!feof($pipes[1])) $stdout .= fgets($pipes[1],1024); while (!feof($pipes[2])) $stderr .= fgets($pipes[2],1024); fclose($pipes[1]); fclose($pipes[2]); $return = proc_close($proc); return [ 'stdout' => $stdout, 'stderr' => $stderr, 'return' => $return ]; } ... // копируем все ts в один файл output.mp4 без преобразования, очень быстро // при склейке обычно "убегает" звук, "-itsoffset 0.5", сдвигает видео дорожку на 500 мс $command = sprintf('/usr/bin/ffmpeg -loglevel repeat+level+error -nostats -y -i "%s" -itsoffset 0.147978 -i "%s" -vcodec copy -c:a copy "output.mp4"', $m3u8files[0], $m3u8files[1]); $ret = exec_return($command); file_put_contents("{$forConvert->full}/converted/error", $ret['stderr']); chmod("{$forConvert->full}/converted/error", 0666); file_put_contents("{$forConvert->full}/converted/converted", $ret['stdout']); chmod("{$forConvert->full}/converted/converted", 0666); ...