├── .gitignore ├── package.json ├── README.md └── src └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | input/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "faviconfuckery", 3 | "version": "1.0.0", 4 | "description": "gaming", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cd src && node ." 8 | }, 9 | "keywords": [], 10 | "author": "Antonio32A <~@antonio32a.com> (antonio32a.com)", 11 | "license": "MIT", 12 | "dependencies": { 13 | "ffmpeg-extract-frames": "^2.0.2", 14 | "minecraft-protocol": "^1.25.0", 15 | "sharp": "^0.28.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # faviconfuckery 2 | Simple tool which displays videos in the Minecraft server list using the favicon and description. 3 | 4 | This does not produce audio. 5 | 6 | ## Demos 7 | - https://i.antonio32a.com/CPmNBw.mp4 8 | - https://i.antonio32a.com/OIYDrW.mp4 9 | - https://i.antonio32a.com/9KRlNG.mp4 10 | 11 | ## Requirements 12 | - ffmpeg 13 | - node 14 14 | 15 | ## Usage 16 | - clone this repository and cd into it 17 | - run `npm i` 18 | - create a directory called `input` with a second directory called `images` in it 19 | - download a video from youtube which has subtitles and download captions for it using [savesubs.com](https://savesubs.com), captions must be in JSON format 20 | - move the video to the `input` directory, rename it to `in.mp4`, move the captions as well and name them `in.json` 21 | - run `npm start` 22 | - wait for the server to start, you will see a `Ready!` in the console 23 | - add `localhost` to your Minecraft servers (use version 1.8.9) and spam the refresh button (using an autoclicker is recommended) 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const { exec } = require("child_process"); 2 | const fs = require("fs").promises; 3 | const mc = require("minecraft-protocol"); 4 | const extractFrames = require("ffmpeg-extract-frames"); 5 | const sharp = require("sharp"); 6 | 7 | const videoName = "in"; 8 | const fps = 30; 9 | 10 | (async () => { 11 | let subtitles = JSON.parse(await fs.readFile("../input/" + videoName + ".json")) 12 | .map(i => ({ [parseInt(i.start * 1000)]: i.lines.join("\n") })); 13 | subtitles = Object.assign({}, ...subtitles); 14 | 15 | if ((await fs.readdir("../input/images")).length === 0) { 16 | console.log("No images found, extracting frames..."); 17 | await extractFrames({ 18 | input: "../input/" + videoName + ".mp4", 19 | output: "../input/images/%d.jpg", 20 | fps 21 | }); 22 | console.log("Finished extracting frames."); 23 | 24 | console.log("Resizing images..."); 25 | for (const image of (await fs.readdir("../input/images"))) { 26 | await sharp("../input/images/" + image) 27 | .png() 28 | .resize(64, 64) 29 | .toFile("../input/images/" + image.split(".")[0] + ".png"); 30 | await fs.rm("../input/images/" + image); // remove the jpg one 31 | } 32 | console.log("Finished resizing images."); 33 | } 34 | 35 | console.log("Preparing images."); 36 | const images = []; 37 | const imageNames = (await fs.readdir("../input/images")).sort((a, b) => 38 | parseInt(a.split(".")[0]) - parseInt(b.split(".")[0]) 39 | ); 40 | 41 | for (const image of imageNames) 42 | images.push(await fs.readFile("../input/images/" + image, "base64")); 43 | console.log("Finished preparing images."); 44 | 45 | console.log("Ready!"); 46 | let startTime; 47 | mc.createServer({ 48 | "online-mode": false, 49 | host: "0.0.0.0", 50 | port: 25565, 51 | version: "1.8.9", 52 | beforePing: result => { 53 | if (!startTime) 54 | startTime = Date.now(); 55 | 56 | let frame = Math.floor((Date.now() - startTime) / (1000 / fps)); 57 | if (frame >= images.length) 58 | frame = images.length - 1; 59 | 60 | const timestamp = Date.now() - startTime; 61 | let keys = Object.keys(subtitles).map(i => parseInt(i)); 62 | keys.push(timestamp); 63 | keys.sort((a, b) => a - b); 64 | const subtitleIndex = keys.indexOf(timestamp) - 1; 65 | 66 | result.favicon = "data:image/png;base64," + images[frame]; 67 | result.description = Object.values(subtitles)[subtitleIndex] ?? "antonio32a.com"; 68 | result.players.online = frame; 69 | result.players.max = images.length - 1; 70 | 71 | // quick and simple autoclicker using xdotool, adjust speed depending on how long it takes to process 72 | // new Promise(resolve => setTimeout(resolve, 1)).then(() => exec("xdotool click --repeat 2 1")); 73 | return result; 74 | } 75 | }); 76 | })(); 77 | --------------------------------------------------------------------------------