brella-transition-bin
Version:
Generates the brella transition for OBS Studio.
203 lines (202 loc) • 9 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs"));
const commander_1 = require("commander");
const sanitize_filename_1 = __importDefault(require("sanitize-filename"));
const process_1 = require("process");
const os_1 = require("os");
const path_1 = __importDefault(require("path"));
const brella_transition_1 = __importDefault(require("brella-transition"));
const import_1 = __importDefault(require("@brillout/import"));
const skia_canvas_1 = require("skia-canvas");
const crypto_1 = require("crypto");
commander_1.program
.option("-W, --width <number>", "width of the canvas", "1920")
.option("-H, --height <number>", "height of the canvas", "1080")
.option("-o, --output <string>", "name of output file", "brella.webm")
.option("--output-image", "output every frame as an image file")
.option("--output-dir <string>", "directory to output image files to")
.option("--force-output-dir", "output to directory even if the directory already exists")
.option("--brella <number>", "maximum amount of brella", "30")
.option("--ribs <numbers>", "possible number of ribs, separated by commas", "6,8")
.option("--retries <number>", "maximum retries before choosing to overlap, -1 to allow indefinite retries", "1000000")
.option("-r, --fps <number>", "framerate of the transition", "60")
.option("--attack <number>", "frames of brella opening/closing", "15")
.option("--hold <number>", "frames of brella staying opened", "30")
.option("--rotate <number>", "radian angle to apply to the brella every frame", "0.01")
.option("-h, --hue <numbers>", "HUE angle range in degrees, separated by comma", "0,360")
.option("-s, --saturation <numbers>", "saturation range in percentage, separated by comma", "80,100")
.option("-l, --lightness <numbers>", "lightness range in percentage, separated by comma", "50,50");
const options = commander_1.program.parse().opts();
let attack = parseInt(options.attack);
if (isNaN(attack) || attack < 0) {
console.log("Attack must be a positive integer");
(0, process_1.exit)(1);
}
let hold = parseInt(options.hold);
if (isNaN(hold) || hold < 0) {
console.log("Hold must be a positive integer");
(0, process_1.exit)(1);
}
if (!attack && !hold) {
console.log("One of attack or hold must be positive");
(0, process_1.exit)(1);
}
let rotate = parseFloat(options.rotate);
if (isNaN(rotate)) {
console.log("Rotate must be a number");
(0, process_1.exit)(1);
}
const ribs = options.ribs.split(",").map((x) => parseInt(x));
if (ribs.some(x => isNaN(x) || x < 3)) {
console.log("Ribs must be numbers >= 3");
(0, process_1.exit)(1);
}
let hue = options.hue.split(",").map((x) => parseInt(x));
if (!hue.length || hue.some(x => isNaN(x))) {
console.log("HUE angle range must be numbers");
(0, process_1.exit)(1);
}
hue = hue.map(x => x == 360 || x == -360 ? x : (x < 0 ? (x % 360) + 360 : x % 360)).sort();
let saturation = options.saturation.split(",").map((x) => parseInt(x));
if (!saturation.length || saturation.some(x => isNaN(x))) {
console.log("Saturation range must be numbers");
(0, process_1.exit)(1);
}
saturation = saturation.map(x => x < 0 ? 0 : (x > 100 ? 100 : x)).sort();
let lightness = options.lightness.split(",").map((x) => parseInt(x));
if (!lightness.length || lightness.some(x => isNaN(x))) {
console.log("Lightness range must be numbers");
(0, process_1.exit)(1);
}
lightness = lightness.map(x => x < 0 ? 0 : (x > 100 ? 100 : x)).sort();
const outName = (0, sanitize_filename_1.default)(options.output);
if (outName != options.output) {
console.log("Output file name is not valid");
(0, process_1.exit)(1);
}
const fps = parseInt(options.fps);
if (isNaN(fps)) {
console.log("Frame rate must be a number");
(0, process_1.exit)(1);
}
if (options.outputImage && options.outputDir && fs.existsSync(options.outputDir) && !options.forceOutputDir) {
console.log(`Output directory ${options.outputDir} already exists`);
(0, process_1.exit)(1);
}
let tmpDir;
if (options.outputImage) {
if (options.outputDir) {
fs.mkdirSync(options.outputDir, { recursive: true });
tmpDir = options.outputDir;
}
else
tmpDir = fs.mkdtempSync(path_1.default.join((0, os_1.tmpdir)(), "brella-"));
}
(0, import_1.default)("ffmpeg-stream").then((_a) => __awaiter(void 0, [_a], void 0, function* ({ Converter }) {
const converter = new Converter();
const converterInput = converter.createInputStream({
r: fps,
f: "image2pipe"
});
let realOutName = outName;
const outNameArr = outName.split(".");
while (fs.existsSync(realOutName)) {
outNameArr[outNameArr.length - 1] = Array.from((0, crypto_1.randomBytes)(4)).map(byte => byte.toString(16).padStart(2, "0")).join("");
realOutName = outNameArr.join(".") + ".webm";
}
if (realOutName != outName)
console.log(`${outName} already exists. Will instead output to ${realOutName}`);
converter.createOutputToFile(realOutName, {
lossless: 1,
vcodec: "libvpx-vp9",
pix_fmt: "yuva420p"
});
const converting = converter.run();
const canvas = new skia_canvas_1.Canvas(parseInt(options.width), parseInt(options.height));
const ctx = canvas.getContext("2d");
const transition = new brella_transition_1.default({
brellaMax: parseInt(options.brella),
brellaRetries: parseInt(options.retries),
brellaRibs: ribs,
frameAttack: attack,
frameHold: hold,
frameRotate: rotate,
colorHue: hue,
colorSaturation: saturation,
colorLightness: lightness
});
transition.activate();
const pad = Math.floor(Math.log10(transition.estimatedFrames));
let counter = 0;
// Keep drawing if some brellas are still active
while (transition.isActive()) {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw the frame
transition.render(ctx);
// Get the PNG stream
const png = yield canvas.png;
// Write each frame to a PNG file
if (tmpDir) {
const name = counter.toString().padStart(4, "0");
fs.writeFile(`${tmpDir}/frame-${name}.png`, png, (err) => {
if (err)
console.error(err);
});
}
// Pipe frame to FFMpeg converter
yield new Promise((res, rej) => {
converterInput.write(png, (err) => {
if (err)
rej(err);
else
res();
});
});
counter++;
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
process.stdout.write(`[${Math.floor(counter * 100 / transition.estimatedFrames).toString().padStart(3, "0")}%] Rendered ${counter.toString().padStart(pad, "0")} / ${transition.estimatedFrames} frames`);
}
process.stdout.write("\n");
converterInput.end();
console.log("Finalizing output...");
yield converting;
}));