Created
May 27, 2021 09:09
-
-
Save funmaker/d83d7b9ce911a7e20ce1e65bb73f56dd to your computer and use it in GitHub Desktop.
YT 24/7 Radio Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import path from "path"; | |
import fs from "fs"; | |
import fsPromise from "fs/promises"; | |
import cp from "child_process"; | |
import { fileURLToPath } from 'url'; | |
import lame from "@suldashi/lame"; | |
import Canvas from "canvas"; | |
// HOW TO: | |
// 1) choose streaming target, eg yt | |
const STREAMING_TARGET = "rtmp://a.rtmp.youtube.com/live2/<YOUTUBE STREAMING KEY>"; | |
// 2) save https://puu.sh/HK4IB/c39ae67267.png as overlay_template.png | |
// 3) prepare some loop.mp4 video for background loop | |
// 4) put mp3s in playlist folder | |
// 5) (optional) acquire Trebucbd.ttf and uncomment line related to it | |
// 6) npm i --save @suldashi/lame canvas | |
// 7) node server.js | |
let overlayTitle = ""; | |
let overlayDesc = ""; | |
let overlayRefreshing = false; | |
// Uncomment to register bundled font | |
//Canvas.registerFont("Trebucbd.ttf", { family: "Trebuchet MS" }) | |
const overlay = await Canvas.loadImage('overlay_template.png'); | |
await fsPromise.copyFile("overlay_template.png", "overlay.png"); | |
const canvas = Canvas.createCanvas(overlay.width, overlay.height); | |
const ctx = canvas.getContext("2d") | |
async function refreshOverlay() { | |
if(overlayRefreshing) return; | |
overlayRefreshing = true; | |
ctx.clearRect(0, 0, canvas.width, canvas.height) | |
ctx.drawImage(overlay, 0, 0); | |
ctx.font = 'bold 80px Trebuchet MS'; | |
ctx.fillStyle = "white"; | |
ctx.fillText(overlayTitle.toUpperCase(), 377, 945); | |
ctx.save(); | |
ctx.rect(353,975,1337, 64); | |
ctx.clip(); | |
ctx.font = 'bold 40px Trebuchet MS' | |
ctx.fillText(overlayDesc.toUpperCase(), 365, 1023); | |
ctx.restore(); | |
const date = new Date(); | |
const time = date.getHours().toString().padStart(2, "0") + ":" + date.getMinutes().toString().padStart(2, "0"); | |
ctx.font = 'bold 40px Trebuchet MS' | |
ctx.fillText(time, 1737, 1023); | |
const pngStream = canvas.createPNGStream(); | |
const outFile = fs.createWriteStream("overlay.png.tmp"); | |
pngStream.pipe(outFile); | |
await new Promise(res => outFile.once("finish", res)); | |
await fsPromise.rename("overlay.png.tmp", "overlay.png"); | |
overlayRefreshing = false; | |
} | |
setTimeout(() => { | |
refreshOverlay(); | |
setInterval(refreshOverlay, 60000) | |
}, (61 - new Date().getSeconds()) * 1000); | |
function shuffle(array) { | |
for(let pos = 0; pos < array.length; pos++) { | |
const other = Math.floor(Math.random() * (pos + 1)); | |
[array[pos], array[other]] = [array[other], array[pos]]; | |
} | |
} | |
const root = path.dirname(fileURLToPath(import.meta.url)); | |
const playlistDir = path.join(root, "playlist"); | |
const playlist = await fsPromise.readdir(playlistDir); | |
const ffmpeg = cp.spawn( | |
"ffmpeg", | |
( | |
'-threads 4 -y ' + | |
'-re -f s16le -ar 44100 -ac 2 -i pipe:0 ' + | |
'-stream_loop -1 -i loop.mp4 ' + | |
'-loop 1 -f image2 -i overlay.png ' + | |
'-c:v libx264 -c:a aac ' + | |
'-b:v 2M -b:a 128k -preset veryfast -maxrate 4M -bufsize 20M -g 60 ' + | |
'-filter_complex [1][2]overlay[v] ' + | |
'-map 0:a -map [v] -f flv -flvflags no_duration_filesize ' + STREAMING_TARGET | |
).split(" "), | |
{ | |
stdio: ['pipe', process.stdout, process.stderr], | |
}, | |
); | |
ffmpeg.once("exit", () => process.exit()) | |
process.on("exit", () => ffmpeg.kill()); | |
while(true) { | |
console.error("Shuffling the playlist."); | |
shuffle(playlist); | |
for(let id in playlist) { | |
const song = playlist[id]; | |
let basename = song.slice(0, -4); | |
console.error(`Now playing: ${basename}...`); | |
const parts = basename.split(" - "); | |
overlayTitle = parts.slice(1).join(" - "); | |
const meme = [ | |
"**** ***", | |
"LIVE 24/7", | |
"Szkoda strzępić ryja", | |
"Wy kurwa polacy", | |
"Coś się zepsuło", | |
"Nie było mnie słychać", | |
"To powtorzę jeszcze raz", | |
"Cała prawda, całą dobę. ▪ Cała prawda, całą dobę.", | |
]; | |
const mem = meme[Math.floor(Math.random() * meme.length)] | |
const next = playlist[(id + 1) % playlist.length].slice(0, -4); | |
overlayDesc = `Autor: ${parts[0]} ▪ ${mem} ▪ Następne: ${next}`; | |
const file = fs.createReadStream(path.join(playlistDir, song)); | |
const decoder = lame.Decoder({ | |
channels: 2, | |
bitDepth: 16, | |
sampleRate: 44100, | |
mode: lame.STEREO | |
}); | |
file.pipe(decoder); | |
decoder.pipe(ffmpeg.stdin, { end: false }); | |
await Promise.all([ | |
refreshOverlay(), | |
new Promise((res, rej) => { | |
file.on("error", rej); | |
decoder.on("error", rej); | |
decoder.on("end", res); | |
}), | |
]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment