UNPKG

lw-ffmpeg-node

Version:

`lw-ffmpeg-node` is a lightweight Node.js library for manipulating video files using FFmpeg. It provides various methods to retrieve information about video files, compress videos, create video slices, crop videos, change video size and frame rate, and sa

178 lines (177 loc) 7.25 kB
"use strict"; /** * A class that allows support for using FFMPEG by providing a path to it. */ 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; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FFMPEGPathModel = exports.YTDLPModel = void 0; const cli_1 = __importStar(require("./cli")); class YTDLPModel { constructor(ytdlpPath) { this.ytdlpPath = ytdlpPath; this.cmd = this .ytdlpPath ? cli_1.default.cmd.bind(null, this.ytdlpPath) : this.linuxCmd; ytdlpPath && cli_1.FileManager.exists(ytdlpPath).then((exists) => { if (!exists) { throw new Error(`yt-dlp path does not exist. Path provided: ${ytdlpPath}`); } }); } async linuxCmd(command, options) { return (await cli_1.default.linux(`yt-dlp ${command}`, options)); } async getVersion() { return await this.cmd("--version"); } async downloadVideo(url, destination, options) { const { ytUrl } = YTDLPModel.getYoutubeId(url); const qualityOption = options?.quality === "high" ? `-f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best'` : ""; const overwriteOption = options?.shouldOverwrite ? "--force-overwrites" : ""; const stdout = await this.cmd(`${overwriteOption} ${qualityOption} ${ytUrl}`, { cwd: destination, ...options?.cliOptions, }); return stdout; } static getYoutubeId(url) { const youtubeVideoRegex = /https:\/\/www\.youtube\.com\/watch\?v=((\w|-)+)/; let ytUrl = null; ytUrl = youtubeVideoRegex.exec(url)[0]; if (!ytUrl) { throw new Error("Invalid Youtube URL"); } const videoId = ytUrl.split("=")[1]; return { videoId, ytUrl, }; } } exports.YTDLPModel = YTDLPModel; class FFMPEGPathModel { constructor(ffmpegPath, ffprobePath) { this.ffmpegPath = ffmpegPath; this.ffprobePath = ffprobePath; this.cmd = cli_1.default.cmd.bind(null, this.ffmpegPath); this.ffprobe = new FFPROBEModel(this.ffprobePath); } async getVersion() { return await this.cmd("-version"); } async compress(inputPath, outputPath) { const stdout = await this.cmd(`-y -i ${inputPath} -vcodec libx264 -crf 28 -acodec copy ${outputPath}`); return stdout; } async createVideoSlice(input_path, output_path, inpoint, outpoint, options) { if (inpoint >= outpoint) { throw new Error("inpoint must be less than outpoint"); } if (inpoint < 0) { throw new Error("inpoint must be greater than or equal to 0"); } const duration = await this.ffprobe.getVideoDuration(input_path); if (outpoint > duration || inpoint > duration) { throw new Error("outpoint must be less than the video duration"); } if (options?.convertToMp4) { await this.cmd(`-y -ss ${inpoint} -t ${outpoint - inpoint} -i ${input_path} -vcodec libx264 -acodec copy ${output_path}`); } else { await this.cmd(`-y -ss ${inpoint} -t ${outpoint - inpoint} -i ${input_path} -c copy ${output_path}`); } return output_path; } async cropVideo(inputPath, outputPath, x, y, width, height) { await this.cmd(`-y -i ${inputPath} -vf "crop=${width}:${height}:${x}:${y}" ${outputPath}`); } async changeSize(inputPath, outputPath, width, height) { await this.cmd(`-y -i ${inputPath} -s ${width}x${height} ${outputPath}`); } async changeFramerate(inputPath, outputPath, frameRate) { await this.cmd(`-y -i ${inputPath} -r ${frameRate} ${outputPath}`); } async saveThumbnail(inputPath, outputPath, time) { await this.cmd(`-y -ss ${time} -i ${inputPath} -vframes 1 ${outputPath}`); } } exports.FFMPEGPathModel = FFMPEGPathModel; class FFPROBEModel { constructor(ffprobePath) { this.ffprobePath = ffprobePath; this.cmd = cli_1.default.cmd.bind(null, this.ffprobePath); } async getVersion() { return await this.cmd("-version"); } async getVideoInfo(filepath) { const part1 = "-v error -print_format json -select_streams v:0 -show_format -show_streams"; const part2 = "-show_entries stream=codec_name,width,height,bit_rate,r_frame_rate"; const part3 = "-show_entries format=duration,filename,nb_streams,size"; const stdout = await this.cmd(`${part1} ${part2} ${part3} ${filepath}`); const data = JSON.parse(stdout); const info = { codec_name: data.streams[0].codec_name, width: Number(data.streams[0].width), height: Number(data.streams[0].height), bit_rate: Number(data.streams[0].bit_rate), r_frame_rate: this.convertFractionStringToNumber(data.streams[0].r_frame_rate), filename: data.format.filename, duration: Number(data.format.duration), nb_streams: data.format.nb_streams, size: Number(data.format.size), }; return info; } async getVideoDuration(filePath) { const command = `-v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 ${filePath}`; const duration = await this.cmd(command); return Number(duration); } async getVideoFrameRate(filePath) { try { const command = `-v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1 ${filePath}`; const framerate = await this.cmd(command); return this.convertFractionStringToNumber(framerate); } catch (e) { console.error("Error getting frame rate:", e); return 30; } } convertFractionStringToNumber(fractionString) { const [numerator, denominator] = fractionString.split("/").map(Number); if (isNaN(numerator) || isNaN(denominator) || denominator === 0) { throw new Error("Invalid fraction string"); } return numerator / denominator; } }