Skip to content

Instantly share code, notes, and snippets.

@colevandersWands
Last active January 12, 2021 15:59
Show Gist options
  • Save colevandersWands/f8b7f636bc8f7b64f88139d6c7d8b302 to your computer and use it in GitHub Desktop.
Save colevandersWands/f8b7f636bc8f7b64f88139d6c7d8b302 to your computer and use it in GitHub Desktop.
BBB Screenshare extractor CLI
'use strict';
/* generate mp4's of the screenshare + audio from a BBB hosted recording
$ node this-file.js meeting-id name-of-output-file keep
- meeting-id (required) a big blue button meeting id, you can find this in the URL for recorded sessions.
the script will exit early if this is not provided
- name-of-output-file (optional) the output file name should not include an extension, just the name.
defaults to class-recording.mp4
- keep (optional) can be anything, the script just checks for a 4th cli arg
if present, the original videos will not be removed
test it out with this short video:
- video link: https://meet.openknowledge.be/playback/presentation/2.0/playback.html?meetingId=05594ce10542676cd7f00b5d118cb2f367054196-1606646031832
- cli input: $ node this-file.js 05594ce10542676cd7f00b5d118cb2f367054196-1606646031832 test-download
heads-up: you will need https://www.ffmpeg.org/ installed on your computer for this to work
possible improvement: do everything in memory without writing temp files?
credits: adapted from https://github.com/bermarte/HYF-small-video-utility
*/
const fs = require('fs');
const path = require('path');
const util = require('util');
const writeFilePromise = util.promisify(fs.writeFile);
const unlinkPromise = util.promisify(fs.unlink);
const ffmpeg = require('fluent-ffmpeg');
const fetch = require('node-fetch');
// exit early if there is no meeting id
const meetingId = process.argv[2];
if (!meetingId) {
console.log('no meeting id provided');
process.exit(0);
}
// configurations
const mediaExtension = '.mp4';
const domain = 'https://meet.openknowledge.be/';
const uniquifier = Math.random()
.toString(16)
.substr(2, 12);
// read in the file name and generate a temporary output path
const outputFileName = `${process.argv[3] ||
`class-recording-${uniquifier}`}${mediaExtension}`;
const outputPath = path.join(__dirname, outputFileName);
// did the user ask to keep the temp files?
const keepTemps = process.argv[4];
// generate fetch URL and temporary output path for the silent screenshare video
const deskshareUrl = `${domain}presentation/${meetingId}/deskshare/deskshare${mediaExtension}`;
const deskshareFileName = 'temp-deskshare-' + uniquifier;
const desksharePath = path.join(
__dirname,
`${deskshareFileName}${mediaExtension}`
);
// generate fetch URL and temporary output path for the webcams with audio
const webcamsUrl = `${domain}presentation/${meetingId}/video/webcams${mediaExtension}`;
const webcamsFileName = 'temp-webcams-' + uniquifier;
const webcamsPath = path.join(__dirname, `${webcamsFileName}${mediaExtension}`);
// generate temporary audio output path
const audioFileName = `temp-audio-${uniquifier}.aac`;
const audioPath = path.join(__dirname, audioFileName);
// main script
(async () => {
try {
console.log('--- downloading videos ---');
await Promise.all([
// downloadVideo is hoisted from the bottom of the file
downloadVideo(deskshareUrl, desksharePath),
downloadVideo(webcamsUrl, webcamsPath),
]);
console.log('--- extracting audio from webcams ---');
await new Promise((res, rej) => {
ffmpeg(webcamsPath)
.outputOptions(['-vn', '-acodec copy'])
.save(audioPath)
.on('end', () => (console.log('mixing done'), res()))
.on('error', err => (console.error(err), rej()));
});
console.log('--- combining audio with deskshare video ---');
await new Promise((res, rej) => {
ffmpeg()
.input(audioPath)
.input(desksharePath)
.output(outputPath)
// .outputOptions('-c', 'copy')
.on('end', () => (console.log('mixing done'), res()))
.on('error', err => (console.error(err), rej()))
.run();
});
if (!keepTemps) {
console.log('--- clearing temporary files ---');
await Promise.all([
unlinkPromise(desksharePath),
unlinkPromise(webcamsPath),
unlinkPromise(audioPath),
]);
}
console.log('all done');
} catch (err) {
console.error(err);
}
})();
async function downloadVideo(url, filePath) {
try {
console.log(`... fetching ${url}`);
const response = await fetch(url);
console.log(`... buffering ${url}`);
const buffer = await response.buffer();
console.log(`... writing ${url}`);
await writeFilePromise(filePath, buffer);
console.log(`finished downloading ${url}`);
} catch (err) {
console.error(err);
}
}
{
"description": "A little script used to convert the lesson of HYF available on [openknowledge.be](https://meet.openknowledge.be/playback/presentation/2.0/playback.html?meetingId=48966e92bc14f80c53d450f9e59dc77e812b2f8b-1605437686426)",
"dependencies": {
"ffmpeg-extract-audio": "^1.0.2",
"fluent-ffmpeg": "^2.1.2",
"node-fetch": "^2.6.1"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment