build.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. /*
  2. A simple Javascript program that converts Bad Apple into a Javascript program that plays Bad Apple in the console.
  3. Requires: ffmpeg (binary), glob (node), pngjs (node)
  4. */
  5. const FPS = 1;
  6. const WIDTH = 24;
  7. const HEIGHT = 16;
  8. const os = require("os");
  9. const fs = require("fs");
  10. const path = require("path");
  11. const child_process = require("child_process");
  12. const stream = require("stream");
  13. const PNG = require("pngjs").PNG;
  14. const glob = require("glob");
  15. const VIDEO = "res/video.mp4"; // input
  16. // preliminary checks + utility functions
  17. if (!["Linux", "Darwin"].includes(os.type())) {
  18. throw new Error("file must be run in a unix-based shell");
  19. }
  20. function _execSync(cmd) {
  21. return child_process.execSync(cmd, { encoding: "utf8" }).trim();
  22. }
  23. function _execSyncSilent(cmd) {
  24. child_process.execSync(cmd, { stdio: "ignore" });
  25. }
  26. function mkdirIfNeeded(path) {
  27. if (!fs.existsSync(path)) {
  28. fs.mkdirSync(path);
  29. }
  30. }
  31. function hasFfmpeg() {
  32. return Boolean(_execSync("which ffmpeg"));
  33. }
  34. if (!hasFfmpeg()) {
  35. throw new Error("ffmpeg must be installed");
  36. }
  37. mkdirIfNeeded("tmp");
  38. // calculate video dimensions
  39. function getVideoDims(path) {
  40. let [w, h] = _execSync(
  41. `ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 ${path}`
  42. ).split("x");
  43. return {
  44. width: Number.parseInt(w),
  45. height: Number.parseInt(h),
  46. };
  47. }
  48. let idims = getVideoDims(VIDEO);
  49. let newDims = {
  50. width: WIDTH,
  51. height: HEIGHT,
  52. };
  53. // ffmpeg requires that width and height be divisble by 2
  54. newDims.width -= newDims.width % 2;
  55. newDims.height -= newDims.height % 2;
  56. const SCALED_VIDEO = "tmp/_video.mp4";
  57. let a = _execSync(
  58. `ffmpeg -y -i ${VIDEO} -vf scale=${newDims.width}:${newDims.height} ${SCALED_VIDEO}`
  59. );
  60. function extractFrames(path, fps) {
  61. _execSync(`ffmpeg -i ${path} -vf fps=${fps} tmp/out%d.png`);
  62. }
  63. extractFrames(SCALED_VIDEO, FPS);
  64. console.log("done.");
  65. // scan for frame files and order them
  66. let frameFiles = glob.sync("tmp/out*");
  67. // sort frame files by frame number
  68. frameFiles.sort((a, b) => {
  69. let frameNo = /\d+/g;
  70. let frameA = Number.parseInt(a.match(frameNo));
  71. let frameB = Number.parseInt(b.match(frameNo));
  72. return frameA - frameB;
  73. });
  74. console.log(frameFiles);
  75. let frames = [];
  76. let rawFrameData = new Uint8Array(WIDTH * HEIGHT * frameFiles.length);
  77. for (let i = 0; i < frameFiles.length; i++) {
  78. console.log("processing frame", i);
  79. let imgData = PNG.sync.read(fs.readFileSync(frameFiles[i]));
  80. for (let y = 0; y < imgData.height; y++) {
  81. for (let x = 0; x < imgData.width; x++) {
  82. let idx = (imgData.width * y + x) << 2;
  83. let v = imgData.data[idx];
  84. let fdi = (WIDTH * HEIGHT * i) + (imgData.width * y) + x;
  85. rawFrameData[fdi] = v;
  86. }
  87. }
  88. }
  89. fs.writeFileSync('../assets/frames.dat', Buffer.from(rawFrameData.buffer));