|
@ -1,28 +1,26 @@ |
|
|
import Graph from 'graph-data-structure'; |
|
|
|
|
|
import fs from 'fs'; |
|
|
import fs from 'fs'; |
|
|
import path from 'path'; |
|
|
|
|
|
import util from 'util'; |
|
|
import util from 'util'; |
|
|
|
|
|
|
|
|
import * as api from './api.mjs'; |
|
|
import * as api from './api.mjs'; |
|
|
import {graphToDOT} from './graph.mjs'; |
|
|
import {graphToDOT} from './graph.mjs'; |
|
|
|
|
|
|
|
|
const YOUTUBE_WATCH = 'https://youtu.be/%s'; |
|
|
|
|
|
|
|
|
|
|
|
// Fetch the output path from command line
|
|
|
// Fetch the output path from command line
|
|
|
if (process.argv.length !== 3) |
|
|
|
|
|
|
|
|
if (process.argv.length !== 4) |
|
|
{ |
|
|
{ |
|
|
console.error(`Usage: node explore [output]`); |
|
|
|
|
|
|
|
|
console.error(`Usage: ${process.argv[1]} ROOT DEST
|
|
|
|
|
|
Explore videos linked from ROOT and write the resulting graph to DEST.`);
|
|
|
process.exit(1); |
|
|
process.exit(1); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const dest = process.argv[2]; |
|
|
|
|
|
|
|
|
|
|
|
// Graph of visited videos. Each node is a video which is linked to all the
|
|
|
|
|
|
// videos to which there is a link, either through a card or an endscreen item
|
|
|
|
|
|
const videosGraph = Graph(); |
|
|
|
|
|
|
|
|
const root = process.argv[2]; |
|
|
|
|
|
const dest = process.argv[3]; |
|
|
|
|
|
|
|
|
// Store metadata about each visited video
|
|
|
// Store metadata about each visited video
|
|
|
const videosMeta = Object.create(null); |
|
|
|
|
|
|
|
|
const videosNodes = Object.create(null); |
|
|
|
|
|
|
|
|
|
|
|
// List of videos linked from each video either through a card or an
|
|
|
|
|
|
// endscreen item
|
|
|
|
|
|
const nextVideos = Object.create(null); |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Recursively explore a video and the video linked from it to fill |
|
|
* Recursively explore a video and the video linked from it to fill |
|
@ -33,40 +31,33 @@ const videosMeta = Object.create(null); |
|
|
const exploreVideo = async videoId => |
|
|
const exploreVideo = async videoId => |
|
|
{ |
|
|
{ |
|
|
// Make sure we don’t explore the same video twice
|
|
|
// Make sure we don’t explore the same video twice
|
|
|
if (videoId in videosMeta) |
|
|
|
|
|
|
|
|
if (videoId in videosNodes) |
|
|
{ |
|
|
{ |
|
|
return Promise.resolve(); |
|
|
return Promise.resolve(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const playerConfig = await api.getPlayerConfig(videoId); |
|
|
|
|
|
videosMeta[videoId] = api.getVideoMeta(playerConfig); |
|
|
|
|
|
|
|
|
videosNodes[videoId] = {}; |
|
|
|
|
|
|
|
|
const linkedVideos = [ |
|
|
|
|
|
...api.getEndScreenVideos(playerConfig), |
|
|
|
|
|
...api.getCardVideos(playerConfig), |
|
|
|
|
|
]; |
|
|
|
|
|
|
|
|
const playerConfig = await api.getPlayerConfig(videoId); |
|
|
|
|
|
videosNodes[videoId] = api.getVideoMeta(playerConfig); |
|
|
|
|
|
nextVideos[videoId] = new Set(); |
|
|
|
|
|
|
|
|
// Add links between this video and the linked ones
|
|
|
// Add links between this video and the linked ones
|
|
|
linkedVideos.forEach(id => videosGraph.addEdge(videoId, id)); |
|
|
|
|
|
|
|
|
api.getEndScreenVideos(playerConfig) |
|
|
|
|
|
.forEach(nextId => nextVideos[videoId].add(nextId)); |
|
|
|
|
|
api.getCardVideos(playerConfig) |
|
|
|
|
|
.forEach(nextId => nextVideos[videoId].add(nextId)); |
|
|
|
|
|
|
|
|
// Recurse on linked videos
|
|
|
// Recurse on linked videos
|
|
|
return Promise.all(linkedVideos.map(id => exploreVideo(id))); |
|
|
|
|
|
|
|
|
return Promise.all( |
|
|
|
|
|
Array.from(nextVideos[videoId]) |
|
|
|
|
|
.map(id => exploreVideo(id)) |
|
|
|
|
|
); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// Metadata of the source video
|
|
|
|
|
|
const rootVideoId = 'EZGra6O8ClQ'; |
|
|
|
|
|
console.log('Starting to explore!'); |
|
|
console.log('Starting to explore!'); |
|
|
|
|
|
|
|
|
exploreVideo(rootVideoId).then(() => |
|
|
|
|
|
|
|
|
exploreVideo(root).then(() => |
|
|
{ |
|
|
{ |
|
|
fs.writeFileSync( |
|
|
|
|
|
dest, |
|
|
|
|
|
graphToDOT( |
|
|
|
|
|
videosGraph, |
|
|
|
|
|
id => videosMeta[id].title, |
|
|
|
|
|
id => util.format(YOUTUBE_WATCH, id) |
|
|
|
|
|
) |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
console.log('Finished. Result in ' + dest); |
|
|
|
|
|
|
|
|
fs.writeFileSync(dest, graphToDOT(videosNodes, nextVideos)); |
|
|
|
|
|
console.log(`Finished. Result in ${dest}`); |
|
|
}).catch(console.error); |
|
|
}).catch(console.error); |
|
|