1
0
mirror of https://github.com/snobu/destreamer.git synced 2026-04-16 23:31:46 +00:00

Merge pull request #7 from shirj/dev-list-download

Argument parser and multiple video download
This commit is contained in:
Adrian Calinescu
2020-02-15 20:56:01 +02:00
committed by GitHub
3 changed files with 65 additions and 42 deletions

View File

@@ -33,12 +33,23 @@ Destreamer takes a [honeybadger](https://www.youtube.com/watch?v=4r7wHMg5Yjg) ap
* Edit `destreamer.ts` and replace the username const with your own, you may still need to enter your password or go through 2FA if you don't have the STS cookie saved in Chrome. If you do (i.e. you usually log in to Microsoft Stream with Chrome), then you may try turning `headless: false` to `true` for a truly headless experience) * Edit `destreamer.ts` and replace the username const with your own, you may still need to enter your password or go through 2FA if you don't have the STS cookie saved in Chrome. If you do (i.e. you usually log in to Microsoft Stream with Chrome), then you may try turning `headless: false` to `true` for a truly headless experience)
======= =======
* Edit `destreamer.ts` (`.js` if using the vanilla JS master branch) and replace the username const with your own, you may still need to enter your password or go through 2FA if you don't have the STS cookie saved in Chrome. If you do (i.e. you usually log in to Microsoft Stream with Chrome), then you may try turning `headless: false` to `true` for a truly headless experience) * Edit `destreamer.ts` (`.js` if using the vanilla JS master branch) and replace the username const with your own, you may still need to enter your password or go through 2FA if you don't have the STS cookie saved in Chrome. If you do (i.e. you usually log in to Microsoft Stream with Chrome), then you may try turning `headless: false` to `true` for a truly headless experience)
* `npm install` to restore packages * `npm install` to restore packages* `npm install` to restore packages
* `npm start <URL of the video>` * `npm run -s build` to transpile TypeScript to JavaScript
* `node ./destreamer.js`
```
Options:
--help Show help [boolean]
--version Show version number [boolean]
--videoUrls [array] [required]
--username [string] [required]
--outputDirectory [string] [default: "videos"]
```
* `node destreamer.js --username username@example.com --outputDirectory "C:\videos" --videoUrls "https://web.microsoftstream.com/video/VIDEO-1" "https://web.microsoftstream.com/video/VIDEO-2" "https://web.microsoftstream.com/video/VIDEO-3"`
### To download a list of videos ### To download a list of videos
There's no implementation that does that (yet). There's some work happening to support this, give it some time. ~~There's no implementation that does that (yet). There's some work happening to support this, give it some time.~~
See usage above.
## EXPECTED OUTPUT ## EXPECTED OUTPUT

View File

@@ -4,13 +4,21 @@ import { terminal as term } from 'terminal-kit';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { BrowserTests } from './BrowserTests'; import { BrowserTests } from './BrowserTests';
import yargs = require('yargs');
// Type in your username here (the one you use to // Type in your username here (the one you use to
// login to Microsoft Stream). // login to Microsoft Stream).
const username: string = 'somebody@example.com'; const args: string[] = process.argv.slice(2); // TODO: Remove this
const args: string[] = process.argv.slice(2);
const videoUrl: string = args[0]; const argv = yargs.options({
const outputDirectory: string = 'videos'; videoUrls: { type: 'array', demandOption: true },
username: { type: 'string', demandOption: true },
outputDirectory: { type: 'string', default: 'videos' }
}).argv;
console.info('Video URLs: %s', argv.videoUrls);
console.info('Username: %s', argv.username);
console.info('Output Directory: %s', argv.outputDirectory);
function sanityChecks() { function sanityChecks() {
try { try {
@@ -31,10 +39,10 @@ function sanityChecks() {
console.error('FFmpeg is missing. You need a fairly recent release of FFmpeg in $PATH.'); console.error('FFmpeg is missing. You need a fairly recent release of FFmpeg in $PATH.');
} }
if (!fs.existsSync(outputDirectory)){ if (!fs.existsSync(argv.outputDirectory)){
console.log('Creating output directory: ' + console.log('Creating output directory: ' +
process.cwd() + path.sep + outputDirectory); process.cwd() + path.sep + argv.outputDirectory);
fs.mkdirSync(outputDirectory); fs.mkdirSync(argv.outputDirectory);
} }
if (args[0] == null || args[0].length < 10) { if (args[0] == null || args[0].length < 10) {
@@ -44,7 +52,7 @@ function sanityChecks() {
} }
} }
async function rentVideoForLater() { async function rentVideoForLater(videoUrls: string[], username: string, outputDirectory: string) {
console.log('Launching headless Chrome to perform the OpenID Connect dance...'); console.log('Launching headless Chrome to perform the OpenID Connect dance...');
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
// Switch to false if you need to login interactively // Switch to false if you need to login interactively
@@ -56,7 +64,7 @@ async function rentVideoForLater() {
// This breaks on slow connections, needs more reliable logic // This breaks on slow connections, needs more reliable logic
//const oidcUrl = "https://login.microsoftonline.com/common/oauth2/authorize?client_id=cf53fce8-def6-4aeb-8d30-b158e7b1cf83&response_mode=form_post&response_type=code+id_token&scope=openid+profile&state=OpenIdConnect.AuthenticationProperties%3d1VtrsKV5QUHtzn8cDWL4wJmacu-VHH_DfpPxMQBhnfbar-_e8X016GGJDPfqfvcyUK3F3vBoiFwUpahR2ANfrzHE469vcw7Mk86wcAqBGXCvAUmv59MDU_OZFHpSL360oVRBo84GfVXAKYdhCjhPtelRHLHEM_ADiARXeMdVTAO3SaTiVQMhw3c9vLWuXqrKKevpI7E5esCQy5V_dhr2Q7kKrlW3gHX0232b8UWAnSDpc-94&nonce=636832485747560726.NzMyOWIyYWQtM2I3NC00MmIyLTg1NTMtODBkNDIwZTI1YjAxNDJiN2JkNDMtMmU5Ni00OTc3LWFkYTQtNTNlNmUwZmM1NTVl&nonceKey=OpenIdConnect.nonce.F1tPks6em0M%2fWMwvatuGWfFM9Gj83LwRKLvbx9rYs5M%3d&site_id=500453&redirect_uri=https%3a%2f%2fmsit.microsoftstream.com%2f&post_logout_redirect_uri=https%3a%2f%2fproducts.office.com%2fmicrosoft-stream&msafed=0"; //const oidcUrl = "https://login.microsoftonline.com/common/oauth2/authorize?client_id=cf53fce8-def6-4aeb-8d30-b158e7b1cf83&response_mode=form_post&response_type=code+id_token&scope=openid+profile&state=OpenIdConnect.AuthenticationProperties%3d1VtrsKV5QUHtzn8cDWL4wJmacu-VHH_DfpPxMQBhnfbar-_e8X016GGJDPfqfvcyUK3F3vBoiFwUpahR2ANfrzHE469vcw7Mk86wcAqBGXCvAUmv59MDU_OZFHpSL360oVRBo84GfVXAKYdhCjhPtelRHLHEM_ADiARXeMdVTAO3SaTiVQMhw3c9vLWuXqrKKevpI7E5esCQy5V_dhr2Q7kKrlW3gHX0232b8UWAnSDpc-94&nonce=636832485747560726.NzMyOWIyYWQtM2I3NC00MmIyLTg1NTMtODBkNDIwZTI1YjAxNDJiN2JkNDMtMmU5Ni00OTc3LWFkYTQtNTNlNmUwZmM1NTVl&nonceKey=OpenIdConnect.nonce.F1tPks6em0M%2fWMwvatuGWfFM9Gj83LwRKLvbx9rYs5M%3d&site_id=500453&redirect_uri=https%3a%2f%2fmsit.microsoftstream.com%2f&post_logout_redirect_uri=https%3a%2f%2fproducts.office.com%2fmicrosoft-stream&msafed=0";
await page.goto(videoUrl, { waitUntil: 'networkidle2' }); await page.goto(videoUrls[0], { waitUntil: 'networkidle2' });
await page.waitForSelector('input[type="email"]'); await page.waitForSelector('input[type="email"]');
await page.keyboard.type(username); await page.keyboard.type(username);
await page.click('input[type="submit"]'); await page.click('input[type="submit"]');
@@ -66,6 +74,7 @@ async function rentVideoForLater() {
await sleep(1500); await sleep(1500);
console.log('Sorry, i mean "you".'); console.log('Sorry, i mean "you".');
for (let videoUrl of videoUrls) {
await page.goto(videoUrl, { waitUntil: 'networkidle2' }); await page.goto(videoUrl, { waitUntil: 'networkidle2' });
await sleep(2000); await sleep(2000);
// try this instead of hardcoding sleep // try this instead of hardcoding sleep
@@ -89,19 +98,20 @@ async function rentVideoForLater() {
console.log(`Video title is: ${title}`); console.log(`Video title is: ${title}`);
console.log("At this point Chrome's job is done, shutting it down...");
await browser.close();
console.log('Constructing HLS URL...'); console.log('Constructing HLS URL...');
const hlsUrl = amsUrl.substring(0, amsUrl.lastIndexOf('/')) + '/manifest(format=m3u8-aapl)'; const hlsUrl = amsUrl.substring(0, amsUrl.lastIndexOf('/')) + '/manifest(format=m3u8-aapl)';
console.log('Spawning youtube-dl with cookie and HLS URL...'); console.log('Spawning youtube-dl with cookie and HLS URL...');
const youtubedlCmd = 'youtube-dl --no-call-home --no-progress --no-warnings ' + const youtubedlCmd = 'youtube-dl --no-call-home --no-warnings ' +
`--output "${outputDirectory}/${title}.mp4" --add-header Cookie:"${cookie}" "${hlsUrl}"`; `--output "${outputDirectory}/${title}.mp4" --add-header Cookie:"${cookie}" "${hlsUrl}"`;
// console.log(`\n\n[DEBUG] Invoking youtube-dl: ${youtubedlCmd}\n\n`); // console.log(`\n\n[DEBUG] Invoking youtube-dl: ${youtubedlCmd}\n\n`);
var result = execSync(youtubedlCmd, { stdio: 'inherit' }); var result = execSync(youtubedlCmd, { stdio: 'inherit' });
} }
console.log("At this point Chrome's job is done, shutting it down...");
await browser.close();
}
function sleep(ms: number) { function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
@@ -134,5 +144,5 @@ if (args[0] === 'test')
else { else {
sanityChecks(); sanityChecks();
rentVideoForLater(); rentVideoForLater(argv.videoUrls as string[], argv.username, argv.outputDirectory);
} }

View File

@@ -18,11 +18,13 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/puppeteer": "^1.20.0", "@types/puppeteer": "^1.20.0",
"@types/terminal-kit": "^1.28.0" "@types/terminal-kit": "^1.28.0",
"@types/yargs": "^15.0.3"
}, },
"dependencies": { "dependencies": {
"puppeteer": "^1.20.0", "puppeteer": "^1.20.0",
"terminal-kit": "^1.26.10", "terminal-kit": "^1.26.10",
"typescript": "^3.6.3" "typescript": "^3.6.3",
"yargs": "^15.0.3"
} }
} }