Generate visual representations of the networks underlying video-gamebooks on YouTube https://youtube-maze.delab.re/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

86 lines
2.5 KiB

  1. import util from 'util';
  2. import * as api from './api.mjs';
  3. import { WATCH_BASE, escapeQuotes } from './util.mjs';
  4. const GRAPH_NODE = ' "%s" [label="%s", URL="%s", fontcolor=blue]';
  5. const GRAPH_LINK = ' "%s" -> "%s"';
  6. /**
  7. * Explore the video graph starting from the given root.
  8. *
  9. * @async
  10. * @param string videoId Source video identifier.
  11. * @param function onUpdate Callback run every time a new video is explored.
  12. * @return Array.<Object, Object> Nodes and set of neighbors of each node.
  13. */
  14. export const exploreVideos = async (videoId, onUpdate) =>
  15. {
  16. // Store metadata about each visited video
  17. const videosNodes = Object.create(null);
  18. // List of videos linked from each video either through a card or an
  19. // endscreen item
  20. const nextVideos = Object.create(null);
  21. // Videos that still need to be explored
  22. const queue = [videoId];
  23. while (queue.length > 0)
  24. {
  25. const currentId = queue.shift();
  26. if (!(currentId in videosNodes))
  27. {
  28. const metadata = await api.getMetadata(currentId);
  29. const info = api.getVideoInfo(metadata);
  30. videosNodes[currentId] = info;
  31. nextVideos[currentId] = new Set();
  32. // Add links between this video and the linked ones
  33. api.getEndScreenVideos(metadata)
  34. .forEach(nextId => nextVideos[info.videoId].add(nextId));
  35. api.getCardVideos(metadata)
  36. .forEach(nextId => nextVideos[info.videoId].add(nextId));
  37. for (let nextId of nextVideos[info.videoId])
  38. {
  39. queue.push(nextId);
  40. }
  41. }
  42. onUpdate(Object.keys(videosNodes).length, queue.length);
  43. }
  44. return [videosNodes, nextVideos];
  45. };
  46. /**
  47. * Convert a video graph to the GraphViz format.
  48. *
  49. * @param Object videosNodes Videos of the graph.
  50. * @param Object nextVideos For each video, list of next videos.
  51. * @return string GraphViz representation of the graph.
  52. */
  53. export const toGraphviz = (videosNodes, nextVideos) =>
  54. {
  55. // Convert videosNodes
  56. const nodesStr = Object.entries(videosNodes).map(([id, {title}]) =>
  57. {
  58. const url = util.format(WATCH_BASE, id);
  59. return util.format(GRAPH_NODE, id, escapeQuotes(title), url);
  60. }).join('\n');
  61. // Convert edges
  62. const nextStr = Object.entries(nextVideos).map(([id, neighbors]) =>
  63. Array.from(neighbors)
  64. .map(neighbor => util.format(GRAPH_LINK, id, neighbor))
  65. .join('\n')
  66. ).join('\n');
  67. return `digraph "" {
  68. ${nodesStr}
  69. ${nextStr}}`;
  70. };