diff --git a/server/src/Core/Config.ts b/server/src/Core/Config.ts index 4fc4cc3d..b64daf75 100644 --- a/server/src/Core/Config.ts +++ b/server/src/Core/Config.ts @@ -2,6 +2,8 @@ import { debugLog } from "@/Helpers/Console"; import { execSimple, GetRunningProcesses } from "@/Helpers/Execute"; import { is_docker } from "@/Helpers/System"; import { isNumber } from "@/Helpers/Types"; +import { TwitchHelper } from "@/Providers/Twitch"; +import { YouTubeHelper } from "@/Providers/YouTube"; import type { SettingField } from "@common/Config"; import { settingsFields } from "@common/ServerConfig"; import type { AxiosResponse } from "axios"; @@ -13,8 +15,6 @@ import minimist from "minimist"; import crypto from "node:crypto"; import fs from "node:fs"; import path from "node:path"; -import { TwitchHelper } from "@/Providers/Twitch"; -import { YouTubeHelper } from "@/Providers/YouTube"; import { AppRoot, BaseConfigCacheFolder, @@ -572,6 +572,15 @@ export class Config { // no blocks in testing // if (process.env.NODE_ENV === "test") return; + if (Config.getInstance().cfg("storage.no_watch_files", false)) { + log( + LOGLEVEL.DEBUG, + "config.startWatchingConfig", + `Not watching config file due to 'storage.no_watch_files' setting` + ); + return false; + } + // monitor config for external changes this.watcher = fs.watch( BaseConfigPath.config, @@ -634,7 +643,7 @@ export class Config { console.log( chalk.green( "Client is built: " + - path.join(BaseConfigFolder.client, "index.html") + path.join(BaseConfigFolder.client, "index.html") ) ); } @@ -680,12 +689,12 @@ export class Config { console.log( chalk.green( "Chat dumper is built: " + - path.join( - AppRoot, - "twitch-chat-dumper", - "build", - "index.js" - ) + path.join( + AppRoot, + "twitch-chat-dumper", + "build", + "index.js" + ) ) ); } diff --git a/server/src/Core/Providers/Twitch/TwitchChannel.ts b/server/src/Core/Providers/Twitch/TwitchChannel.ts index 36f639e9..41857cdf 100644 --- a/server/src/Core/Providers/Twitch/TwitchChannel.ts +++ b/server/src/Core/Providers/Twitch/TwitchChannel.ts @@ -3124,6 +3124,15 @@ export class TwitchChannel extends BaseChannel { return false; // don't watch if no channel folders are enabled } + if (Config.getInstance().cfg("storage.no_watch_files", false)) { + log( + LOGLEVEL.DEBUG, + "tw.channel.startWatching", + `Not watching files for ${this.internalName} due to setting being enabled` + ); + return false; + } + const folders = [Helper.vodFolder(this.internalName)]; // if (this.login && fs.existsSync(path.join(BaseConfigDataFolder.saved_clips, "scheduler", this.login))) diff --git a/server/src/Core/Providers/Twitch/TwitchVOD.ts b/server/src/Core/Providers/Twitch/TwitchVOD.ts index fcad33fd..327f9499 100644 --- a/server/src/Core/Providers/Twitch/TwitchVOD.ts +++ b/server/src/Core/Providers/Twitch/TwitchVOD.ts @@ -2868,6 +2868,15 @@ export class TwitchVOD extends BaseVOD { // no blocks in testing // if (process.env.NODE_ENV === "test") return false; + if (Config.getInstance().cfg("storage.no_watch_files", false)) { + log( + LOGLEVEL.DEBUG, + "vod.watch", + `Not watching files for ${this.basename} due to config setting` + ); + return false; + } + const files = this.associatedFiles.map((f) => path.join(this.directory, f) ); diff --git a/server/src/Helpers/Video.ts b/server/src/Helpers/Video.ts index a1e5a4ce..651b21b2 100644 --- a/server/src/Helpers/Video.ts +++ b/server/src/Helpers/Video.ts @@ -33,120 +33,109 @@ export interface RemuxReturn { * @param metadata_file * @returns */ -export function remuxFile( +export async function remuxFile( input: string, output: string, overwrite = false, metadata_file?: string ): Promise { - return new Promise((resolve, reject) => { - const ffmpegPath = Helper.path_ffmpeg(); + const ffmpegPath = Helper.path_ffmpeg(); - if (!ffmpegPath) { - reject(new Error("Failed to find ffmpeg")); - return; - } + if (!ffmpegPath) { + reject(new Error("Failed to find ffmpeg")); + return; + } - const emptyFile = - fs.existsSync(output) && fs.statSync(output).size == 0; + const emptyFile = fs.existsSync(output) && fs.statSync(output).size == 0; - if (!overwrite && fs.existsSync(output) && !emptyFile) { - log( - LOGLEVEL.ERROR, - "video.remux", - `Output file ${output} already exists` - ); - reject(new Error(`Output file ${output} already exists`)); - } + if (!overwrite && fs.existsSync(output) && !emptyFile) { + log( + LOGLEVEL.ERROR, + "video.remux", + `Output file ${output} already exists` + ); + reject(new Error(`Output file ${output} already exists`)); + } - if (emptyFile) { - fs.unlinkSync(output); - } + if (emptyFile) { + fs.unlinkSync(output); + } - // ffmpeg seems to make ts cfr into vfr, don't know why + // ffmpeg seems to make ts cfr into vfr, don't know why - const opts: string[] = []; - // "-r", parseInt(info.video.FrameRate).toString(), - // "-vsync", "cfr", - opts.push("-i", input); + const opts: string[] = []; + // "-r", parseInt(info.video.FrameRate).toString(), + // "-vsync", "cfr", + opts.push("-i", input); - // write metadata to file - if (metadata_file) { - if (fs.existsSync(metadata_file)) { - opts.push("-i", metadata_file); - opts.push("-map_metadata", "1"); - } else { - log( - LOGLEVEL.ERROR, - "video.remux", - `Metadata file ${metadata_file} does not exist for remuxing ${input}` - ); - } + // write metadata to file + if (metadata_file) { + if (fs.existsSync(metadata_file)) { + opts.push("-i", metadata_file); + opts.push("-map_metadata", "1"); + } else { + log( + LOGLEVEL.ERROR, + "video.remux", + `Metadata file ${metadata_file} does not exist for remuxing ${input}` + ); } + } - // "-map", "0", - // "-analyzeduration", + // "-map", "0", + // "-analyzeduration", - opts.push("-c", "copy"); // copy all streams + opts.push("-c", "copy"); // copy all streams - if (!output.endsWith(Config.AudioContainer)) { - opts.push("-bsf:a", "aac_adtstoasc"); // audio bitstream filter? - } + if (!output.endsWith(Config.AudioContainer)) { + opts.push("-bsf:a", "aac_adtstoasc"); // audio bitstream filter? + } - if (output.endsWith(".mp4")) { - opts.push("-movflags", "faststart"); // make streaming possible, not sure if this is a good idea - } + if (output.endsWith(".mp4")) { + opts.push("-movflags", "faststart"); // make streaming possible, not sure if this is a good idea + } - // "-r", parseInt(info.video.FrameRate).toString(), - // "-vsync", "cfr", - // ...ffmpeg_options, - // output, + // "-r", parseInt(info.video.FrameRate).toString(), + // "-vsync", "cfr", + // ...ffmpeg_options, + // output, - if (overwrite || emptyFile) { - opts.push("-y"); - } + if (overwrite || emptyFile) { + opts.push("-y"); + } - if (Config.getInstance().cfg("app_verbose")) { - opts.push("-loglevel", "repeat+level+verbose"); - } + if (Config.getInstance().cfg("app_verbose")) { + opts.push("-loglevel", "repeat+level+verbose"); + } - if (Config.getInstance().cfg("debug")) { - // opts.push("-report"); // can't set output file - opts.push( - "-progress", - path.join( - BaseConfigDataFolder.logs_software, - "ffmpeg_progress.log" - ) - ); - opts.push("-vstats"); - opts.push( - "-vstats_file", - path.join( - BaseConfigDataFolder.logs_software, - "ffmpeg_vstats.log" - ) - ); - } + if (Config.getInstance().cfg("debug")) { + // opts.push("-report"); // can't set output file + opts.push( + "-progress", + path.join(BaseConfigDataFolder.logs_software, "ffmpeg_progress.log") + ); + opts.push("-vstats"); + opts.push( + "-vstats_file", + path.join(BaseConfigDataFolder.logs_software, "ffmpeg_vstats.log") + ); + } - opts.push(output); + opts.push(output); - log(LOGLEVEL.INFO, "video.remux", `Remuxing ${input} to ${output}`); + log(LOGLEVEL.INFO, "video.remux", `Remuxing ${input} to ${output}`); - const job = startJob(`remux_${path.basename(input)}`, ffmpegPath, opts); + let currentSeconds = 0; + let totalSeconds = 0; - if (!job || !job.process) { - reject( - new Error( - `Failed to start job for remuxing ${input} to ${output}` - ) - ); - return; - } + // const job = startJob(`remux_${path.basename(input)}`, ffmpegPath, opts); - let currentSeconds = 0; - let totalSeconds = 0; - job.on("log", (stream: string, data: string) => { + const job = await exec( + ffmpegPath, + opts, + {}, + `remux_${path.basename(input)}`, + (stream: string, data: string) => { const totalDurationMatch = data.match( /Duration: (\d+):(\d+):(\d+)/ ); @@ -161,13 +150,22 @@ export function remuxFile( )}: ${totalSeconds}` ); } + if (data.match(/moving the moov atom/)) { + console.log( + `Create MOOV atom for ${path.basename( + input + )} (this usually takes a while)` + ); + } + }, + (data: string) => { const currentTimeMatch = data.match(/time=(\d+):(\d+):(\d+)/); if (currentTimeMatch && totalSeconds > 0) { currentSeconds = parseInt(currentTimeMatch[1]) * 3600 + parseInt(currentTimeMatch[2]) * 60 + parseInt(currentTimeMatch[3]); - job.setProgress(currentSeconds / totalSeconds); + // console.debug(`Remux current time: ${currentSeconds}/${totalSeconds}`); progressOutput( `🎞 Remuxing ${path.basename( @@ -176,75 +174,66 @@ export function remuxFile( (currentSeconds / totalSeconds) * 100 )}%)` ); + return currentSeconds / totalSeconds; } - if (data.match(/moving the moov atom/)) { - console.log( - `Create MOOV atom for ${path.basename( - input - )} (this usually takes a while)` - ); - } - }); + } + ); - job.process.on("error", (err) => { + job.process.on("error", (err) => { + log( + LOGLEVEL.ERROR, + "video.remux", + `Process ${process.pid} error: ${err.message}` + ); + // reject({ code: -1, success: false, stdout: job.stdout, stderr: job.stderr }); + reject(new Error(`Process ${process.pid} error: ${err.message}`)); + }); + + job.process.on("close", (code) => { + if (job) { + job.clear(); + } + void LiveStreamDVR.getInstance().updateFreeStorageDiskSpace(); + // const out_log = ffmpeg.stdout.read(); + const success = fs.existsSync(output) && fs.statSync(output).size > 0; + if (success) { + log( + LOGLEVEL.SUCCESS, + "video.remux", + `Remuxed ${input} to ${output}` + ); + resolve({ + code: code || -1, + success, + stdout: job.stdout, + stderr: job.stderr, + }); + } else { log( LOGLEVEL.ERROR, "video.remux", - `Process ${process.pid} error: ${err.message}` + `Failed to remux '${input}' to '${output}'` ); - // reject({ code: -1, success: false, stdout: job.stdout, stderr: job.stderr }); - reject(new Error(`Process ${process.pid} error: ${err.message}`)); - }); + // reject({ code, success, stdout: job.stdout, stderr: job.stderr }); - job.process.on("close", (code) => { - if (job) { - job.clear(); + let message = "Unknown error"; + const errorSearch = job.stderr.join("").match(/\[error\] (.*)/g); + if (errorSearch && errorSearch.length > 0) { + message = errorSearch.slice(1).join(", "); } - void LiveStreamDVR.getInstance().updateFreeStorageDiskSpace(); - // const out_log = ffmpeg.stdout.read(); - const success = - fs.existsSync(output) && fs.statSync(output).size > 0; - if (success) { - log( - LOGLEVEL.SUCCESS, - "video.remux", - `Remuxed ${input} to ${output}` - ); - resolve({ - code: code || -1, - success, - stdout: job.stdout, - stderr: job.stderr, - }); - } else { - log( - LOGLEVEL.ERROR, - "video.remux", - `Failed to remux '${input}' to '${output}'` - ); - // reject({ code, success, stdout: job.stdout, stderr: job.stderr }); - let message = "Unknown error"; - const errorSearch = job.stderr - .join("") - .match(/\[error\] (.*)/g); - if (errorSearch && errorSearch.length > 0) { - message = errorSearch.slice(1).join(", "); - } - - if (fs.existsSync(output) && fs.statSync(output).size == 0) { - fs.unlinkSync(output); - } - - // for (const err of errorSearch) { - // message = err[1]; - reject( - new Error( - `Failed to remux '${input}' to '${output}': ${message}` - ) - ); + if (fs.existsSync(output) && fs.statSync(output).size == 0) { + fs.unlinkSync(output); } - }); + + // for (const err of errorSearch) { + // message = err[1]; + reject( + new Error( + `Failed to remux '${input}' to '${output}': ${message}` + ) + ); + } }); }