mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-03-19 00:18:34 +01:00
Add jsdocs for Ffmpeg and tools controller
This commit is contained in:
parent
91cca2e358
commit
1e6dd0e3e0
@ -2,9 +2,17 @@ const Logger = require('../Logger')
|
|||||||
const Database = require('../Database')
|
const Database = require('../Database')
|
||||||
|
|
||||||
class ToolsController {
|
class ToolsController {
|
||||||
constructor() { }
|
constructor() {}
|
||||||
|
|
||||||
// POST: api/tools/item/:id/encode-m4b
|
/**
|
||||||
|
* POST: /api/tools/item/:id/encode-m4b
|
||||||
|
* Start an audiobook merge to m4b task
|
||||||
|
*
|
||||||
|
* @this import('../routers/ApiRouter')
|
||||||
|
*
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
*/
|
||||||
async encodeM4b(req, res) {
|
async encodeM4b(req, res) {
|
||||||
if (req.libraryItem.isMissing || req.libraryItem.isInvalid) {
|
if (req.libraryItem.isMissing || req.libraryItem.isInvalid) {
|
||||||
Logger.error(`[MiscController] encodeM4b: library item not found or invalid ${req.params.id}`)
|
Logger.error(`[MiscController] encodeM4b: library item not found or invalid ${req.params.id}`)
|
||||||
@ -27,7 +35,15 @@ class ToolsController {
|
|||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE: api/tools/item/:id/encode-m4b
|
/**
|
||||||
|
* DELETE: /api/tools/item/:id/encode-m4b
|
||||||
|
* Cancel a running m4b merge task
|
||||||
|
*
|
||||||
|
* @this import('../routers/ApiRouter')
|
||||||
|
*
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
*/
|
||||||
async cancelM4bEncode(req, res) {
|
async cancelM4bEncode(req, res) {
|
||||||
const workerTask = this.abMergeManager.getPendingTaskByLibraryItemId(req.params.id)
|
const workerTask = this.abMergeManager.getPendingTaskByLibraryItemId(req.params.id)
|
||||||
if (!workerTask) return res.sendStatus(404)
|
if (!workerTask) return res.sendStatus(404)
|
||||||
@ -37,7 +53,15 @@ class ToolsController {
|
|||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: api/tools/item/:id/embed-metadata
|
/**
|
||||||
|
* POST: /api/tools/item/:id/embed-metadata
|
||||||
|
* Start audiobook embed task
|
||||||
|
*
|
||||||
|
* @this import('../routers/ApiRouter')
|
||||||
|
*
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
*/
|
||||||
async embedAudioFileMetadata(req, res) {
|
async embedAudioFileMetadata(req, res) {
|
||||||
if (req.libraryItem.isMissing || !req.libraryItem.hasAudioFiles || !req.libraryItem.isBook) {
|
if (req.libraryItem.isMissing || !req.libraryItem.hasAudioFiles || !req.libraryItem.isBook) {
|
||||||
Logger.error(`[ToolsController] Invalid library item`)
|
Logger.error(`[ToolsController] Invalid library item`)
|
||||||
@ -57,7 +81,15 @@ class ToolsController {
|
|||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: api/tools/batch/embed-metadata
|
/**
|
||||||
|
* POST: /api/tools/batch/embed-metadata
|
||||||
|
* Start batch audiobook embed task
|
||||||
|
*
|
||||||
|
* @this import('../routers/ApiRouter')
|
||||||
|
*
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
*/
|
||||||
async batchEmbedMetadata(req, res) {
|
async batchEmbedMetadata(req, res) {
|
||||||
const libraryItemIds = req.body.libraryItemIds || []
|
const libraryItemIds = req.body.libraryItemIds || []
|
||||||
if (!libraryItemIds.length) {
|
if (!libraryItemIds.length) {
|
||||||
@ -99,6 +131,12 @@ class ToolsController {
|
|||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import('express').Request} req
|
||||||
|
* @param {import('express').Response} res
|
||||||
|
* @param {import('express').NextFunction} next
|
||||||
|
*/
|
||||||
async middleware(req, res, next) {
|
async middleware(req, res, next) {
|
||||||
if (!req.user.isAdminOrUp) {
|
if (!req.user.isAdminOrUp) {
|
||||||
Logger.error(`[LibraryItemController] Non-root user attempted to access tools route`, req.user)
|
Logger.error(`[LibraryItemController] Non-root user attempted to access tools route`, req.user)
|
||||||
@ -120,4 +158,4 @@ class ToolsController {
|
|||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = new ToolsController()
|
module.exports = new ToolsController()
|
||||||
|
498
server/libs/fluentFfmpeg/index.d.ts
vendored
Normal file
498
server/libs/fluentFfmpeg/index.d.ts
vendored
Normal file
@ -0,0 +1,498 @@
|
|||||||
|
/// <reference types="node" />
|
||||||
|
|
||||||
|
import * as events from "events";
|
||||||
|
import * as stream from "stream";
|
||||||
|
|
||||||
|
declare namespace Ffmpeg {
|
||||||
|
interface FfmpegCommandLogger {
|
||||||
|
error(...data: any[]): void;
|
||||||
|
warn(...data: any[]): void;
|
||||||
|
info(...data: any[]): void;
|
||||||
|
debug(...data: any[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FfmpegCommandOptions {
|
||||||
|
logger?: FfmpegCommandLogger | undefined;
|
||||||
|
niceness?: number | undefined;
|
||||||
|
priority?: number | undefined;
|
||||||
|
presets?: string | undefined;
|
||||||
|
preset?: string | undefined;
|
||||||
|
stdoutLines?: number | undefined;
|
||||||
|
timeout?: number | undefined;
|
||||||
|
source?: string | stream.Readable | undefined;
|
||||||
|
cwd?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FilterSpecification {
|
||||||
|
filter: string;
|
||||||
|
inputs?: string | string[] | undefined;
|
||||||
|
outputs?: string | string[] | undefined;
|
||||||
|
options?: any | string | any[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PresetFunction = (command: FfmpegCommand) => void;
|
||||||
|
|
||||||
|
interface Filter {
|
||||||
|
description: string;
|
||||||
|
input: string;
|
||||||
|
multipleInputs: boolean;
|
||||||
|
output: string;
|
||||||
|
multipleOutputs: boolean;
|
||||||
|
}
|
||||||
|
interface Filters {
|
||||||
|
[key: string]: Filter;
|
||||||
|
}
|
||||||
|
type FiltersCallback = (err: Error, filters: Filters) => void;
|
||||||
|
|
||||||
|
interface Codec {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
canDecode: boolean;
|
||||||
|
canEncode: boolean;
|
||||||
|
drawHorizBand?: boolean | undefined;
|
||||||
|
directRendering?: boolean | undefined;
|
||||||
|
weirdFrameTruncation?: boolean | undefined;
|
||||||
|
intraFrameOnly?: boolean | undefined;
|
||||||
|
isLossy?: boolean | undefined;
|
||||||
|
isLossless?: boolean | undefined;
|
||||||
|
}
|
||||||
|
interface Codecs {
|
||||||
|
[key: string]: Codec;
|
||||||
|
}
|
||||||
|
type CodecsCallback = (err: Error, codecs: Codecs) => void;
|
||||||
|
|
||||||
|
interface Encoder {
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
frameMT: boolean;
|
||||||
|
sliceMT: boolean;
|
||||||
|
experimental: boolean;
|
||||||
|
drawHorizBand: boolean;
|
||||||
|
directRendering: boolean;
|
||||||
|
}
|
||||||
|
interface Encoders {
|
||||||
|
[key: string]: Encoder;
|
||||||
|
}
|
||||||
|
type EncodersCallback = (err: Error, encoders: Encoders) => void;
|
||||||
|
|
||||||
|
interface Format {
|
||||||
|
description: string;
|
||||||
|
canDemux: boolean;
|
||||||
|
canMux: boolean;
|
||||||
|
}
|
||||||
|
interface Formats {
|
||||||
|
[key: string]: Format;
|
||||||
|
}
|
||||||
|
type FormatsCallback = (err: Error, formats: Formats) => void;
|
||||||
|
|
||||||
|
interface FfprobeData {
|
||||||
|
streams: FfprobeStream[];
|
||||||
|
format: FfprobeFormat;
|
||||||
|
chapters: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FfprobeStream {
|
||||||
|
[key: string]: any;
|
||||||
|
index: number;
|
||||||
|
codec_name?: string | undefined;
|
||||||
|
codec_long_name?: string | undefined;
|
||||||
|
profile?: number | undefined;
|
||||||
|
codec_type?: string | undefined;
|
||||||
|
codec_time_base?: string | undefined;
|
||||||
|
codec_tag_string?: string | undefined;
|
||||||
|
codec_tag?: string | undefined;
|
||||||
|
width?: number | undefined;
|
||||||
|
height?: number | undefined;
|
||||||
|
coded_width?: number | undefined;
|
||||||
|
coded_height?: number | undefined;
|
||||||
|
has_b_frames?: number | undefined;
|
||||||
|
sample_aspect_ratio?: string | undefined;
|
||||||
|
display_aspect_ratio?: string | undefined;
|
||||||
|
pix_fmt?: string | undefined;
|
||||||
|
level?: string | undefined;
|
||||||
|
color_range?: string | undefined;
|
||||||
|
color_space?: string | undefined;
|
||||||
|
color_transfer?: string | undefined;
|
||||||
|
color_primaries?: string | undefined;
|
||||||
|
chroma_location?: string | undefined;
|
||||||
|
field_order?: string | undefined;
|
||||||
|
timecode?: string | undefined;
|
||||||
|
refs?: number | undefined;
|
||||||
|
id?: string | undefined;
|
||||||
|
r_frame_rate?: string | undefined;
|
||||||
|
avg_frame_rate?: string | undefined;
|
||||||
|
time_base?: string | undefined;
|
||||||
|
start_pts?: number | undefined;
|
||||||
|
start_time?: number | undefined;
|
||||||
|
duration_ts?: string | undefined;
|
||||||
|
duration?: string | undefined;
|
||||||
|
bit_rate?: string | undefined;
|
||||||
|
max_bit_rate?: string | undefined;
|
||||||
|
bits_per_raw_sample?: string | undefined;
|
||||||
|
nb_frames?: string | undefined;
|
||||||
|
nb_read_frames?: string | undefined;
|
||||||
|
nb_read_packets?: string | undefined;
|
||||||
|
sample_fmt?: string | undefined;
|
||||||
|
sample_rate?: number | undefined;
|
||||||
|
channels?: number | undefined;
|
||||||
|
channel_layout?: string | undefined;
|
||||||
|
bits_per_sample?: number | undefined;
|
||||||
|
disposition?: FfprobeStreamDisposition | undefined;
|
||||||
|
rotation?: string | number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FfprobeStreamDisposition {
|
||||||
|
[key: string]: any;
|
||||||
|
default?: number | undefined;
|
||||||
|
dub?: number | undefined;
|
||||||
|
original?: number | undefined;
|
||||||
|
comment?: number | undefined;
|
||||||
|
lyrics?: number | undefined;
|
||||||
|
karaoke?: number | undefined;
|
||||||
|
forced?: number | undefined;
|
||||||
|
hearing_impaired?: number | undefined;
|
||||||
|
visual_impaired?: number | undefined;
|
||||||
|
clean_effects?: number | undefined;
|
||||||
|
attached_pic?: number | undefined;
|
||||||
|
timed_thumbnails?: number | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FfprobeFormat {
|
||||||
|
[key: string]: any;
|
||||||
|
filename?: string | undefined;
|
||||||
|
nb_streams?: number | undefined;
|
||||||
|
nb_programs?: number | undefined;
|
||||||
|
format_name?: string | undefined;
|
||||||
|
format_long_name?: string | undefined;
|
||||||
|
start_time?: number | undefined;
|
||||||
|
duration?: number | undefined;
|
||||||
|
size?: number | undefined;
|
||||||
|
bit_rate?: number | undefined;
|
||||||
|
probe_score?: number | undefined;
|
||||||
|
tags?: Record<string, string | number> | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ScreenshotsConfig {
|
||||||
|
count?: number | undefined;
|
||||||
|
folder?: string | undefined;
|
||||||
|
filename?: string | undefined;
|
||||||
|
timemarks?: number[] | string[] | undefined;
|
||||||
|
timestamps?: number[] | string[] | undefined;
|
||||||
|
fastSeek?: boolean | undefined;
|
||||||
|
size?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AudioVideoFilter {
|
||||||
|
filter: string;
|
||||||
|
options: string | string[] | {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// static methods
|
||||||
|
function setFfmpegPath(path: string): FfmpegCommand;
|
||||||
|
function setFfprobePath(path: string): FfmpegCommand;
|
||||||
|
function setFlvtoolPath(path: string): FfmpegCommand;
|
||||||
|
function availableFilters(callback: FiltersCallback): void;
|
||||||
|
function getAvailableFilters(callback: FiltersCallback): void;
|
||||||
|
function availableCodecs(callback: CodecsCallback): void;
|
||||||
|
function getAvailableCodecs(callback: CodecsCallback): void;
|
||||||
|
function availableEncoders(callback: EncodersCallback): void;
|
||||||
|
function getAvailableEncoders(callback: EncodersCallback): void;
|
||||||
|
function availableFormats(callback: FormatsCallback): void;
|
||||||
|
function getAvailableFormats(callback: FormatsCallback): void;
|
||||||
|
|
||||||
|
class FfmpegCommand extends events.EventEmitter {
|
||||||
|
constructor(options?: FfmpegCommandOptions);
|
||||||
|
constructor(input?: string | stream.Readable, options?: FfmpegCommandOptions);
|
||||||
|
|
||||||
|
// options/inputs
|
||||||
|
mergeAdd(source: string | stream.Readable): FfmpegCommand;
|
||||||
|
addInput(source: string | stream.Readable): FfmpegCommand;
|
||||||
|
input(source: string | stream.Readable): FfmpegCommand;
|
||||||
|
withInputFormat(format: string): FfmpegCommand;
|
||||||
|
inputFormat(format: string): FfmpegCommand;
|
||||||
|
fromFormat(format: string): FfmpegCommand;
|
||||||
|
withInputFps(fps: number): FfmpegCommand;
|
||||||
|
withInputFPS(fps: number): FfmpegCommand;
|
||||||
|
withFpsInput(fps: number): FfmpegCommand;
|
||||||
|
withFPSInput(fps: number): FfmpegCommand;
|
||||||
|
inputFPS(fps: number): FfmpegCommand;
|
||||||
|
inputFps(fps: number): FfmpegCommand;
|
||||||
|
fpsInput(fps: number): FfmpegCommand;
|
||||||
|
FPSInput(fps: number): FfmpegCommand;
|
||||||
|
nativeFramerate(): FfmpegCommand;
|
||||||
|
withNativeFramerate(): FfmpegCommand;
|
||||||
|
native(): FfmpegCommand;
|
||||||
|
setStartTime(seek: string | number): FfmpegCommand;
|
||||||
|
seekInput(seek: string | number): FfmpegCommand;
|
||||||
|
loop(duration?: string | number): FfmpegCommand;
|
||||||
|
|
||||||
|
// options/audio
|
||||||
|
withNoAudio(): FfmpegCommand;
|
||||||
|
noAudio(): FfmpegCommand;
|
||||||
|
withAudioCodec(codec: string): FfmpegCommand;
|
||||||
|
audioCodec(codec: string): FfmpegCommand;
|
||||||
|
withAudioBitrate(bitrate: string | number): FfmpegCommand;
|
||||||
|
audioBitrate(bitrate: string | number): FfmpegCommand;
|
||||||
|
withAudioChannels(channels: number): FfmpegCommand;
|
||||||
|
audioChannels(channels: number): FfmpegCommand;
|
||||||
|
withAudioFrequency(freq: number): FfmpegCommand;
|
||||||
|
audioFrequency(freq: number): FfmpegCommand;
|
||||||
|
withAudioQuality(quality: number): FfmpegCommand;
|
||||||
|
audioQuality(quality: number): FfmpegCommand;
|
||||||
|
withAudioFilter(filters: string | string[] | AudioVideoFilter[]): FfmpegCommand;
|
||||||
|
withAudioFilters(filters: string | string[] | AudioVideoFilter[]): FfmpegCommand;
|
||||||
|
audioFilter(filters: string | string[] | AudioVideoFilter[]): FfmpegCommand;
|
||||||
|
audioFilters(filters: string | string[] | AudioVideoFilter[]): FfmpegCommand;
|
||||||
|
|
||||||
|
// options/video;
|
||||||
|
withNoVideo(): FfmpegCommand;
|
||||||
|
noVideo(): FfmpegCommand;
|
||||||
|
withVideoCodec(codec: string): FfmpegCommand;
|
||||||
|
videoCodec(codec: string): FfmpegCommand;
|
||||||
|
withVideoBitrate(bitrate: string | number, constant?: boolean): FfmpegCommand;
|
||||||
|
videoBitrate(bitrate: string | number, constant?: boolean): FfmpegCommand;
|
||||||
|
withVideoFilter(filters: string | string[] | AudioVideoFilter[]): FfmpegCommand;
|
||||||
|
withVideoFilters(filters: string | string[] | AudioVideoFilter[]): FfmpegCommand;
|
||||||
|
videoFilter(filters: string | string[] | AudioVideoFilter[]): FfmpegCommand;
|
||||||
|
videoFilters(filters: string | string[] | AudioVideoFilter[]): FfmpegCommand;
|
||||||
|
withOutputFps(fps: number): FfmpegCommand;
|
||||||
|
withOutputFPS(fps: number): FfmpegCommand;
|
||||||
|
withFpsOutput(fps: number): FfmpegCommand;
|
||||||
|
withFPSOutput(fps: number): FfmpegCommand;
|
||||||
|
withFps(fps: number): FfmpegCommand;
|
||||||
|
withFPS(fps: number): FfmpegCommand;
|
||||||
|
outputFPS(fps: number): FfmpegCommand;
|
||||||
|
outputFps(fps: number): FfmpegCommand;
|
||||||
|
fpsOutput(fps: number): FfmpegCommand;
|
||||||
|
FPSOutput(fps: number): FfmpegCommand;
|
||||||
|
fps(fps: number): FfmpegCommand;
|
||||||
|
FPS(fps: number): FfmpegCommand;
|
||||||
|
takeFrames(frames: number): FfmpegCommand;
|
||||||
|
withFrames(frames: number): FfmpegCommand;
|
||||||
|
frames(frames: number): FfmpegCommand;
|
||||||
|
|
||||||
|
// options/videosize
|
||||||
|
keepPixelAspect(): FfmpegCommand;
|
||||||
|
keepDisplayAspect(): FfmpegCommand;
|
||||||
|
keepDisplayAspectRatio(): FfmpegCommand;
|
||||||
|
keepDAR(): FfmpegCommand;
|
||||||
|
withSize(size: string): FfmpegCommand;
|
||||||
|
setSize(size: string): FfmpegCommand;
|
||||||
|
size(size: string): FfmpegCommand;
|
||||||
|
withAspect(aspect: string | number): FfmpegCommand;
|
||||||
|
withAspectRatio(aspect: string | number): FfmpegCommand;
|
||||||
|
setAspect(aspect: string | number): FfmpegCommand;
|
||||||
|
setAspectRatio(aspect: string | number): FfmpegCommand;
|
||||||
|
aspect(aspect: string | number): FfmpegCommand;
|
||||||
|
aspectRatio(aspect: string | number): FfmpegCommand;
|
||||||
|
applyAutopadding(pad?: boolean, color?: string): FfmpegCommand;
|
||||||
|
applyAutoPadding(pad?: boolean, color?: string): FfmpegCommand;
|
||||||
|
applyAutopad(pad?: boolean, color?: string): FfmpegCommand;
|
||||||
|
applyAutoPad(pad?: boolean, color?: string): FfmpegCommand;
|
||||||
|
withAutopadding(pad?: boolean, color?: string): FfmpegCommand;
|
||||||
|
withAutoPadding(pad?: boolean, color?: string): FfmpegCommand;
|
||||||
|
withAutopad(pad?: boolean, color?: string): FfmpegCommand;
|
||||||
|
withAutoPad(pad?: boolean, color?: string): FfmpegCommand;
|
||||||
|
autoPad(pad?: boolean, color?: string): FfmpegCommand;
|
||||||
|
autopad(pad?: boolean, color?: string): FfmpegCommand;
|
||||||
|
|
||||||
|
// options/output
|
||||||
|
addOutput(target: string | stream.Writable, pipeopts?: { end?: boolean | undefined }): FfmpegCommand;
|
||||||
|
output(target: string | stream.Writable, pipeopts?: { end?: boolean | undefined }): FfmpegCommand;
|
||||||
|
seekOutput(seek: string | number): FfmpegCommand;
|
||||||
|
seek(seek: string | number): FfmpegCommand;
|
||||||
|
withDuration(duration: string | number): FfmpegCommand;
|
||||||
|
setDuration(duration: string | number): FfmpegCommand;
|
||||||
|
duration(duration: string | number): FfmpegCommand;
|
||||||
|
toFormat(format: string): FfmpegCommand;
|
||||||
|
withOutputFormat(format: string): FfmpegCommand;
|
||||||
|
outputFormat(format: string): FfmpegCommand;
|
||||||
|
format(format: string): FfmpegCommand;
|
||||||
|
map(spec: string): FfmpegCommand;
|
||||||
|
updateFlvMetadata(): FfmpegCommand;
|
||||||
|
flvmeta(): FfmpegCommand;
|
||||||
|
|
||||||
|
// options/custom
|
||||||
|
addInputOption(options: string[]): FfmpegCommand;
|
||||||
|
addInputOption(...options: string[]): FfmpegCommand;
|
||||||
|
addInputOptions(options: string[]): FfmpegCommand;
|
||||||
|
addInputOptions(...options: string[]): FfmpegCommand;
|
||||||
|
withInputOption(options: string[]): FfmpegCommand;
|
||||||
|
withInputOption(...options: string[]): FfmpegCommand;
|
||||||
|
withInputOptions(options: string[]): FfmpegCommand;
|
||||||
|
withInputOptions(...options: string[]): FfmpegCommand;
|
||||||
|
inputOption(options: string[]): FfmpegCommand;
|
||||||
|
inputOption(...options: string[]): FfmpegCommand;
|
||||||
|
inputOptions(options: string[]): FfmpegCommand;
|
||||||
|
inputOptions(...options: string[]): FfmpegCommand;
|
||||||
|
addOutputOption(options: string[]): FfmpegCommand;
|
||||||
|
addOutputOption(...options: string[]): FfmpegCommand;
|
||||||
|
addOutputOptions(options: string[]): FfmpegCommand;
|
||||||
|
addOutputOptions(...options: string[]): FfmpegCommand;
|
||||||
|
addOption(options: string[]): FfmpegCommand;
|
||||||
|
addOption(...options: string[]): FfmpegCommand;
|
||||||
|
addOptions(options: string[]): FfmpegCommand;
|
||||||
|
addOptions(...options: string[]): FfmpegCommand;
|
||||||
|
withOutputOption(options: string[]): FfmpegCommand;
|
||||||
|
withOutputOption(...options: string[]): FfmpegCommand;
|
||||||
|
withOutputOptions(options: string[]): FfmpegCommand;
|
||||||
|
withOutputOptions(...options: string[]): FfmpegCommand;
|
||||||
|
withOption(options: string[]): FfmpegCommand;
|
||||||
|
withOption(...options: string[]): FfmpegCommand;
|
||||||
|
withOptions(options: string[]): FfmpegCommand;
|
||||||
|
withOptions(...options: string[]): FfmpegCommand;
|
||||||
|
outputOption(options: string[]): FfmpegCommand;
|
||||||
|
outputOption(...options: string[]): FfmpegCommand;
|
||||||
|
outputOptions(options: string[]): FfmpegCommand;
|
||||||
|
outputOptions(...options: string[]): FfmpegCommand;
|
||||||
|
filterGraph(
|
||||||
|
spec: string | FilterSpecification | Array<string | FilterSpecification>,
|
||||||
|
map?: string[] | string,
|
||||||
|
): FfmpegCommand;
|
||||||
|
complexFilter(
|
||||||
|
spec: string | FilterSpecification | Array<string | FilterSpecification>,
|
||||||
|
map?: string[] | string,
|
||||||
|
): FfmpegCommand;
|
||||||
|
|
||||||
|
// options/misc
|
||||||
|
usingPreset(preset: string | PresetFunction): FfmpegCommand;
|
||||||
|
preset(preset: string | PresetFunction): FfmpegCommand;
|
||||||
|
|
||||||
|
// processor
|
||||||
|
renice(niceness: number): FfmpegCommand;
|
||||||
|
kill(signal: string): FfmpegCommand;
|
||||||
|
_getArguments(): string[];
|
||||||
|
|
||||||
|
// capabilities
|
||||||
|
setFfmpegPath(path: string): FfmpegCommand;
|
||||||
|
setFfprobePath(path: string): FfmpegCommand;
|
||||||
|
setFlvtoolPath(path: string): FfmpegCommand;
|
||||||
|
availableFilters(callback: FiltersCallback): void;
|
||||||
|
getAvailableFilters(callback: FiltersCallback): void;
|
||||||
|
availableCodecs(callback: CodecsCallback): void;
|
||||||
|
getAvailableCodecs(callback: CodecsCallback): void;
|
||||||
|
availableEncoders(callback: EncodersCallback): void;
|
||||||
|
getAvailableEncoders(callback: EncodersCallback): void;
|
||||||
|
availableFormats(callback: FormatsCallback): void;
|
||||||
|
getAvailableFormats(callback: FormatsCallback): void;
|
||||||
|
|
||||||
|
// ffprobe
|
||||||
|
ffprobe(callback: (err: any, data: FfprobeData) => void): void;
|
||||||
|
ffprobe(index: number, callback: (err: any, data: FfprobeData) => void): void;
|
||||||
|
ffprobe(options: string[], callback: (err: any, data: FfprobeData) => void): void; // tslint:disable-line unified-signatures
|
||||||
|
ffprobe(index: number, options: string[], callback: (err: any, data: FfprobeData) => void): void;
|
||||||
|
|
||||||
|
// event listeners
|
||||||
|
/**
|
||||||
|
* Emitted just after ffmpeg has been spawned.
|
||||||
|
*
|
||||||
|
* @event FfmpegCommand#start
|
||||||
|
* @param {String} command ffmpeg command line
|
||||||
|
*/
|
||||||
|
on(event: "start", listener: (command: string) => void): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when ffmpeg reports progress information
|
||||||
|
*
|
||||||
|
* @event FfmpegCommand#progress
|
||||||
|
* @param {Object} progress progress object
|
||||||
|
* @param {Number} progress.frames number of frames transcoded
|
||||||
|
* @param {Number} progress.currentFps current processing speed in frames per second
|
||||||
|
* @param {Number} progress.currentKbps current output generation speed in kilobytes per second
|
||||||
|
* @param {Number} progress.targetSize current output file size
|
||||||
|
* @param {String} progress.timemark current video timemark
|
||||||
|
* @param {Number} [progress.percent] processing progress (may not be available depending on input)
|
||||||
|
*/
|
||||||
|
on(
|
||||||
|
event: "progress",
|
||||||
|
listener: (progress: {
|
||||||
|
frames: number;
|
||||||
|
currentFps: number;
|
||||||
|
currentKbps: number;
|
||||||
|
targetSize: number;
|
||||||
|
timemark: string;
|
||||||
|
percent?: number | undefined;
|
||||||
|
}) => void,
|
||||||
|
): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when ffmpeg outputs to stderr
|
||||||
|
*
|
||||||
|
* @event FfmpegCommand#stderr
|
||||||
|
* @param {String} line stderr output line
|
||||||
|
*/
|
||||||
|
on(event: "stderr", listener: (line: string) => void): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when ffmpeg reports input codec data
|
||||||
|
*
|
||||||
|
* @event FfmpegCommand#codecData
|
||||||
|
* @param {Object} codecData codec data object
|
||||||
|
* @param {String} codecData.format input format name
|
||||||
|
* @param {String} codecData.audio input audio codec name
|
||||||
|
* @param {String} codecData.audio_details input audio codec parameters
|
||||||
|
* @param {String} codecData.video input video codec name
|
||||||
|
* @param {String} codecData.video_details input video codec parameters
|
||||||
|
*/
|
||||||
|
on(
|
||||||
|
event: "codecData",
|
||||||
|
listener: (codecData: {
|
||||||
|
format: string;
|
||||||
|
audio: string;
|
||||||
|
audio_details: string;
|
||||||
|
video: string;
|
||||||
|
video_details: string;
|
||||||
|
}) => void,
|
||||||
|
): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when an error happens when preparing or running a command
|
||||||
|
*
|
||||||
|
* @event FfmpegCommand#error
|
||||||
|
* @param {Error} error error object, with optional properties 'inputStreamError' / 'outputStreamError' for errors on their respective streams
|
||||||
|
* @param {String|null} stdout ffmpeg stdout, unless outputting to a stream
|
||||||
|
* @param {String|null} stderr ffmpeg stderr
|
||||||
|
*/
|
||||||
|
on(event: "error", listener: (error: Error, stdout: string | null, stderr: string | null) => void): this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when a command finishes processing
|
||||||
|
*
|
||||||
|
* @event FfmpegCommand#end
|
||||||
|
* @param {Array|String|null} [filenames|stdout] generated filenames when taking screenshots, ffmpeg stdout when not outputting to a stream, null otherwise
|
||||||
|
* @param {String|null} stderr ffmpeg stderr
|
||||||
|
*/
|
||||||
|
on(event: "end", listener: (filenames: string[] | string | null, stderr: string | null) => void): this;
|
||||||
|
|
||||||
|
// recipes
|
||||||
|
saveToFile(output: string): FfmpegCommand;
|
||||||
|
save(output: string): FfmpegCommand;
|
||||||
|
writeToStream(stream: stream.Writable, options?: { end?: boolean | undefined }): stream.Writable;
|
||||||
|
pipe(stream?: stream.Writable, options?: { end?: boolean | undefined }): stream.Writable | stream.PassThrough;
|
||||||
|
stream(stream: stream.Writable, options?: { end?: boolean | undefined }): stream.Writable;
|
||||||
|
takeScreenshots(config: number | ScreenshotsConfig, folder?: string): FfmpegCommand;
|
||||||
|
thumbnail(config: number | ScreenshotsConfig, folder?: string): FfmpegCommand;
|
||||||
|
thumbnails(config: number | ScreenshotsConfig, folder?: string): FfmpegCommand;
|
||||||
|
screenshot(config: number | ScreenshotsConfig, folder?: string): FfmpegCommand;
|
||||||
|
screenshots(config: number | ScreenshotsConfig, folder?: string): FfmpegCommand;
|
||||||
|
mergeToFile(target: string | stream.Writable, tmpFolder: string): FfmpegCommand;
|
||||||
|
concatenate(target: string | stream.Writable, options?: { end?: boolean | undefined }): FfmpegCommand;
|
||||||
|
concat(target: string | stream.Writable, options?: { end?: boolean | undefined }): FfmpegCommand;
|
||||||
|
clone(): FfmpegCommand;
|
||||||
|
run(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ffprobe(file: string, callback: (err: any, data: FfprobeData) => void): void;
|
||||||
|
function ffprobe(file: string, index: number, callback: (err: any, data: FfprobeData) => void): void;
|
||||||
|
function ffprobe(file: string, options: string[], callback: (err: any, data: FfprobeData) => void): void; // tslint:disable-line unified-signatures
|
||||||
|
function ffprobe(
|
||||||
|
file: string,
|
||||||
|
index: number,
|
||||||
|
options: string[],
|
||||||
|
callback: (err: any, data: FfprobeData) => void,
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
declare function Ffmpeg(options?: Ffmpeg.FfmpegCommandOptions): Ffmpeg.FfmpegCommand;
|
||||||
|
declare function Ffmpeg(input?: string | stream.Readable, options?: Ffmpeg.FfmpegCommandOptions): Ffmpeg.FfmpegCommand;
|
||||||
|
|
||||||
|
export = Ffmpeg;
|
@ -3,29 +3,53 @@ const fs = require('../libs/fsExtra')
|
|||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const TaskManager = require('./TaskManager')
|
const TaskManager = require('./TaskManager')
|
||||||
const Task = require('../objects/Task')
|
const Task = require('../objects/Task')
|
||||||
const { writeConcatFile } = require('../utils/ffmpegHelpers')
|
|
||||||
const ffmpegHelpers = require('../utils/ffmpegHelpers')
|
const ffmpegHelpers = require('../utils/ffmpegHelpers')
|
||||||
const Ffmpeg = require('../libs/fluentFfmpeg')
|
const Ffmpeg = require('../libs/fluentFfmpeg')
|
||||||
const SocketAuthority = require('../SocketAuthority')
|
const SocketAuthority = require('../SocketAuthority')
|
||||||
const { isWritable, copyToExisting } = require('../utils/fileUtils')
|
const { isWritable, copyToExisting } = require('../utils/fileUtils')
|
||||||
const TrackProgressMonitor = require('../objects/TrackProgressMonitor')
|
const TrackProgressMonitor = require('../objects/TrackProgressMonitor')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef AbMergeEncodeOptions
|
||||||
|
* @property {string} codec
|
||||||
|
* @property {string} channels
|
||||||
|
* @property {string} bitrate
|
||||||
|
*/
|
||||||
|
|
||||||
class AbMergeManager {
|
class AbMergeManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.itemsCacheDir = Path.join(global.MetadataPath, 'cache/items')
|
this.itemsCacheDir = Path.join(global.MetadataPath, 'cache/items')
|
||||||
|
|
||||||
|
/** @type {Task[]} */
|
||||||
this.pendingTasks = []
|
this.pendingTasks = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} libraryItemId
|
||||||
|
* @returns {Task|null}
|
||||||
|
*/
|
||||||
getPendingTaskByLibraryItemId(libraryItemId) {
|
getPendingTaskByLibraryItemId(libraryItemId) {
|
||||||
return this.pendingTasks.find((t) => t.task.data.libraryItemId === libraryItemId)
|
return this.pendingTasks.find((t) => t.task.data.libraryItemId === libraryItemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel and fail running task
|
||||||
|
*
|
||||||
|
* @param {Task} task
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
cancelEncode(task) {
|
cancelEncode(task) {
|
||||||
task.setFailed('Task canceled by user')
|
task.setFailed('Task canceled by user')
|
||||||
return this.removeTask(task, true)
|
return this.removeTask(task, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import('../objects/user/User')} user
|
||||||
|
* @param {import('../objects/LibraryItem')} libraryItem
|
||||||
|
* @param {AbMergeEncodeOptions} [options={}]
|
||||||
|
*/
|
||||||
async startAudiobookMerge(user, libraryItem, options = {}) {
|
async startAudiobookMerge(user, libraryItem, options = {}) {
|
||||||
const task = new Task()
|
const task = new Task()
|
||||||
|
|
||||||
@ -63,6 +87,12 @@ class AbMergeManager {
|
|||||||
this.runAudiobookMerge(libraryItem, task, options || {})
|
this.runAudiobookMerge(libraryItem, task, options || {})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {import('../objects/LibraryItem')} libraryItem
|
||||||
|
* @param {Task} task
|
||||||
|
* @param {AbMergeEncodeOptions} encodingOptions
|
||||||
|
*/
|
||||||
async runAudiobookMerge(libraryItem, task, encodingOptions) {
|
async runAudiobookMerge(libraryItem, task, encodingOptions) {
|
||||||
// Make sure the target directory is writable
|
// Make sure the target directory is writable
|
||||||
if (!(await isWritable(libraryItem.path))) {
|
if (!(await isWritable(libraryItem.path))) {
|
||||||
@ -178,6 +208,12 @@ class AbMergeManager {
|
|||||||
Logger.info(`[AbMergeManager] Ab task finished ${task.id}`)
|
Logger.info(`[AbMergeManager] Ab task finished ${task.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove ab merge task
|
||||||
|
*
|
||||||
|
* @param {Task} task
|
||||||
|
* @param {boolean} [removeTempFilepath=false]
|
||||||
|
*/
|
||||||
async removeTask(task, removeTempFilepath = false) {
|
async removeTask(task, removeTempFilepath = false) {
|
||||||
Logger.info('[AbMergeManager] Removing task ' + task.id)
|
Logger.info('[AbMergeManager] Removing task ' + task.id)
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
const EventEmitter = require('events')
|
const EventEmitter = require('events')
|
||||||
const Path = require('path')
|
const Path = require('path')
|
||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
@ -46,7 +45,7 @@ class Stream extends EventEmitter {
|
|||||||
}
|
}
|
||||||
get episode() {
|
get episode() {
|
||||||
if (!this.isPodcast) return null
|
if (!this.isPodcast) return null
|
||||||
return this.libraryItem.media.episodes.find(ep => ep.id === this.episodeId)
|
return this.libraryItem.media.episodes.find((ep) => ep.id === this.episodeId)
|
||||||
}
|
}
|
||||||
get libraryItemId() {
|
get libraryItemId() {
|
||||||
return this.libraryItem.id
|
return this.libraryItem.id
|
||||||
@ -76,21 +75,10 @@ class Stream extends EventEmitter {
|
|||||||
return this.tracks[0].codec
|
return this.tracks[0].codec
|
||||||
}
|
}
|
||||||
get mimeTypesToForceAAC() {
|
get mimeTypesToForceAAC() {
|
||||||
return [
|
return [AudioMimeType.FLAC, AudioMimeType.OPUS, AudioMimeType.WMA, AudioMimeType.AIFF, AudioMimeType.WEBM, AudioMimeType.WEBMA, AudioMimeType.AWB, AudioMimeType.CAF]
|
||||||
AudioMimeType.FLAC,
|
|
||||||
AudioMimeType.OPUS,
|
|
||||||
AudioMimeType.WMA,
|
|
||||||
AudioMimeType.AIFF,
|
|
||||||
AudioMimeType.WEBM,
|
|
||||||
AudioMimeType.WEBMA,
|
|
||||||
AudioMimeType.AWB,
|
|
||||||
AudioMimeType.CAF
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
get codecsToForceAAC() {
|
get codecsToForceAAC() {
|
||||||
return [
|
return ['alac']
|
||||||
'alac'
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
get userToken() {
|
get userToken() {
|
||||||
return this.user.token
|
return this.user.token
|
||||||
@ -109,7 +97,7 @@ class Stream extends EventEmitter {
|
|||||||
}
|
}
|
||||||
get numSegments() {
|
get numSegments() {
|
||||||
var numSegs = Math.floor(this.totalDuration / this.segmentLength)
|
var numSegs = Math.floor(this.totalDuration / this.segmentLength)
|
||||||
if (this.totalDuration - (numSegs * this.segmentLength) > 0) {
|
if (this.totalDuration - numSegs * this.segmentLength > 0) {
|
||||||
numSegs++
|
numSegs++
|
||||||
}
|
}
|
||||||
return numSegs
|
return numSegs
|
||||||
@ -135,7 +123,7 @@ class Stream extends EventEmitter {
|
|||||||
clientPlaylistUri: this.clientPlaylistUri,
|
clientPlaylistUri: this.clientPlaylistUri,
|
||||||
startTime: this.startTime,
|
startTime: this.startTime,
|
||||||
segmentStartNumber: this.segmentStartNumber,
|
segmentStartNumber: this.segmentStartNumber,
|
||||||
isTranscodeComplete: this.isTranscodeComplete,
|
isTranscodeComplete: this.isTranscodeComplete
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +131,7 @@ class Stream extends EventEmitter {
|
|||||||
const segStartTime = segNum * this.segmentLength
|
const segStartTime = segNum * this.segmentLength
|
||||||
if (this.segmentStartNumber > segNum) {
|
if (this.segmentStartNumber > segNum) {
|
||||||
Logger.warn(`[STREAM] Segment #${segNum} Request is before starting segment number #${this.segmentStartNumber} - Reset Transcode`)
|
Logger.warn(`[STREAM] Segment #${segNum} Request is before starting segment number #${this.segmentStartNumber} - Reset Transcode`)
|
||||||
await this.reset(segStartTime - (this.segmentLength * 5))
|
await this.reset(segStartTime - this.segmentLength * 5)
|
||||||
return segStartTime
|
return segStartTime
|
||||||
} else if (this.isTranscodeComplete) {
|
} else if (this.isTranscodeComplete) {
|
||||||
return false
|
return false
|
||||||
@ -153,7 +141,7 @@ class Stream extends EventEmitter {
|
|||||||
const distanceFromFurthestSegment = segNum - this.furthestSegmentCreated
|
const distanceFromFurthestSegment = segNum - this.furthestSegmentCreated
|
||||||
if (distanceFromFurthestSegment > 10) {
|
if (distanceFromFurthestSegment > 10) {
|
||||||
Logger.info(`Segment #${segNum} requested is ${distanceFromFurthestSegment} segments from latest (${secondsToTimestamp(segStartTime)}) - Reset Transcode`)
|
Logger.info(`Segment #${segNum} requested is ${distanceFromFurthestSegment} segments from latest (${secondsToTimestamp(segStartTime)}) - Reset Transcode`)
|
||||||
await this.reset(segStartTime - (this.segmentLength * 5))
|
await this.reset(segStartTime - this.segmentLength * 5)
|
||||||
return segStartTime
|
return segStartTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,7 +205,7 @@ class Stream extends EventEmitter {
|
|||||||
else chunks.push(`${current_chunk[0]}-${current_chunk[current_chunk.length - 1]}`)
|
else chunks.push(`${current_chunk[0]}-${current_chunk[current_chunk.length - 1]}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
var perc = (this.segmentsCreated.size * 100 / this.numSegments).toFixed(2) + '%'
|
var perc = ((this.segmentsCreated.size * 100) / this.numSegments).toFixed(2) + '%'
|
||||||
Logger.info('[STREAM-CHECK] Check Files', this.segmentsCreated.size, 'of', this.numSegments, perc, `Furthest Segment: ${this.furthestSegmentCreated}`)
|
Logger.info('[STREAM-CHECK] Check Files', this.segmentsCreated.size, 'of', this.numSegments, perc, `Furthest Segment: ${this.furthestSegmentCreated}`)
|
||||||
// Logger.debug('[STREAM-CHECK] Chunks', chunks.join(', '))
|
// Logger.debug('[STREAM-CHECK] Chunks', chunks.join(', '))
|
||||||
|
|
||||||
@ -251,6 +239,7 @@ class Stream extends EventEmitter {
|
|||||||
async start() {
|
async start() {
|
||||||
Logger.info(`[STREAM] START STREAM - Num Segments: ${this.numSegments}`)
|
Logger.info(`[STREAM] START STREAM - Num Segments: ${this.numSegments}`)
|
||||||
|
|
||||||
|
/** @type {import('../libs/fluentFfmpeg/index').FfmpegCommand} */
|
||||||
this.ffmpeg = Ffmpeg()
|
this.ffmpeg = Ffmpeg()
|
||||||
this.furthestSegmentCreated = 0
|
this.furthestSegmentCreated = 0
|
||||||
|
|
||||||
@ -289,24 +278,8 @@ class Stream extends EventEmitter {
|
|||||||
audioCodec = 'aac'
|
audioCodec = 'aac'
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ffmpeg.addOption([
|
this.ffmpeg.addOption([`-loglevel ${logLevel}`, '-map 0:a', `-c:a ${audioCodec}`])
|
||||||
`-loglevel ${logLevel}`,
|
const hlsOptions = ['-f hls', '-copyts', '-avoid_negative_ts make_non_negative', '-max_delay 5000000', '-max_muxing_queue_size 2048', `-hls_time 6`, `-hls_segment_type ${this.hlsSegmentType}`, `-start_number ${this.segmentStartNumber}`, '-hls_playlist_type vod', '-hls_list_size 0', '-hls_allow_cache 0']
|
||||||
'-map 0:a',
|
|
||||||
`-c:a ${audioCodec}`
|
|
||||||
])
|
|
||||||
const hlsOptions = [
|
|
||||||
'-f hls',
|
|
||||||
"-copyts",
|
|
||||||
"-avoid_negative_ts make_non_negative",
|
|
||||||
"-max_delay 5000000",
|
|
||||||
"-max_muxing_queue_size 2048",
|
|
||||||
`-hls_time 6`,
|
|
||||||
`-hls_segment_type ${this.hlsSegmentType}`,
|
|
||||||
`-start_number ${this.segmentStartNumber}`,
|
|
||||||
"-hls_playlist_type vod",
|
|
||||||
"-hls_list_size 0",
|
|
||||||
"-hls_allow_cache 0"
|
|
||||||
]
|
|
||||||
if (this.hlsSegmentType === 'fmp4') {
|
if (this.hlsSegmentType === 'fmp4') {
|
||||||
hlsOptions.push('-strict -2')
|
hlsOptions.push('-strict -2')
|
||||||
var fmp4InitFilename = Path.join(this.streamPath, 'init.mp4')
|
var fmp4InitFilename = Path.join(this.streamPath, 'init.mp4')
|
||||||
@ -369,7 +342,6 @@ class Stream extends EventEmitter {
|
|||||||
|
|
||||||
Logger.info(`[STREAM] ${this.id} notifying client that stream is ready`)
|
Logger.info(`[STREAM] ${this.id} notifying client that stream is ready`)
|
||||||
this.clientEmit('stream_open', this.toJSON())
|
this.clientEmit('stream_open', this.toJSON())
|
||||||
|
|
||||||
}
|
}
|
||||||
this.isTranscodeComplete = true
|
this.isTranscodeComplete = true
|
||||||
this.ffmpeg = null
|
this.ffmpeg = null
|
||||||
@ -387,11 +359,14 @@ class Stream extends EventEmitter {
|
|||||||
this.ffmpeg.kill('SIGKILL')
|
this.ffmpeg.kill('SIGKILL')
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.remove(this.streamPath).then(() => {
|
await fs
|
||||||
Logger.info('Deleted session data', this.streamPath)
|
.remove(this.streamPath)
|
||||||
}).catch((err) => {
|
.then(() => {
|
||||||
Logger.error('Failed to delete session data', err)
|
Logger.info('Deleted session data', this.streamPath)
|
||||||
})
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
Logger.error('Failed to delete session data', err)
|
||||||
|
})
|
||||||
|
|
||||||
if (errorMessage) this.clientEmit('stream_error', { id: this.id, error: (errorMessage || '').trim() })
|
if (errorMessage) this.clientEmit('stream_error', { id: this.id, error: (errorMessage || '').trim() })
|
||||||
else this.clientEmit('stream_closed', this.id)
|
else this.clientEmit('stream_closed', this.id)
|
||||||
|
@ -40,6 +40,7 @@ class ApiRouter {
|
|||||||
/** @type {import('../Auth')} */
|
/** @type {import('../Auth')} */
|
||||||
this.auth = Server.auth
|
this.auth = Server.auth
|
||||||
this.playbackSessionManager = Server.playbackSessionManager
|
this.playbackSessionManager = Server.playbackSessionManager
|
||||||
|
/** @type {import('../managers/AbMergeManager')} */
|
||||||
this.abMergeManager = Server.abMergeManager
|
this.abMergeManager = Server.abMergeManager
|
||||||
/** @type {import('../managers/BackupManager')} */
|
/** @type {import('../managers/BackupManager')} */
|
||||||
this.backupManager = Server.backupManager
|
this.backupManager = Server.backupManager
|
||||||
@ -47,6 +48,7 @@ class ApiRouter {
|
|||||||
this.watcher = Server.watcher
|
this.watcher = Server.watcher
|
||||||
/** @type {import('../managers/PodcastManager')} */
|
/** @type {import('../managers/PodcastManager')} */
|
||||||
this.podcastManager = Server.podcastManager
|
this.podcastManager = Server.podcastManager
|
||||||
|
/** @type {import('../managers/AudioMetadataManager')} */
|
||||||
this.audioMetadataManager = Server.audioMetadataManager
|
this.audioMetadataManager = Server.audioMetadataManager
|
||||||
this.rssFeedManager = Server.rssFeedManager
|
this.rssFeedManager = Server.rssFeedManager
|
||||||
this.cronManager = Server.cronManager
|
this.cronManager = Server.cronManager
|
||||||
|
@ -53,6 +53,7 @@ async function extractCoverArt(filepath, outputpath) {
|
|||||||
await fs.ensureDir(dirname)
|
await fs.ensureDir(dirname)
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
/** @type {import('../libs/fluentFfmpeg/index').FfmpegCommand} */
|
||||||
var ffmpeg = Ffmpeg(filepath)
|
var ffmpeg = Ffmpeg(filepath)
|
||||||
ffmpeg.addOption(['-map 0:v', '-frames:v 1'])
|
ffmpeg.addOption(['-map 0:v', '-frames:v 1'])
|
||||||
ffmpeg.output(outputpath)
|
ffmpeg.output(outputpath)
|
||||||
@ -76,6 +77,7 @@ module.exports.extractCoverArt = extractCoverArt
|
|||||||
//This should convert based on the output file extension as well
|
//This should convert based on the output file extension as well
|
||||||
async function resizeImage(filePath, outputPath, width, height) {
|
async function resizeImage(filePath, outputPath, width, height) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
/** @type {import('../libs/fluentFfmpeg/index').FfmpegCommand} */
|
||||||
var ffmpeg = Ffmpeg(filePath)
|
var ffmpeg = Ffmpeg(filePath)
|
||||||
ffmpeg.addOption(['-vf', `scale=${width || -1}:${height || -1}`])
|
ffmpeg.addOption(['-vf', `scale=${width || -1}:${height || -1}`])
|
||||||
ffmpeg.addOutput(outputPath)
|
ffmpeg.addOutput(outputPath)
|
||||||
@ -111,6 +113,7 @@ module.exports.downloadPodcastEpisode = (podcastEpisodeDownload) => {
|
|||||||
})
|
})
|
||||||
if (!response) return resolve(false)
|
if (!response) return resolve(false)
|
||||||
|
|
||||||
|
/** @type {import('../libs/fluentFfmpeg/index').FfmpegCommand} */
|
||||||
const ffmpeg = Ffmpeg(response.data)
|
const ffmpeg = Ffmpeg(response.data)
|
||||||
ffmpeg.addOption('-loglevel debug') // Debug logs printed on error
|
ffmpeg.addOption('-loglevel debug') // Debug logs printed on error
|
||||||
ffmpeg.outputOptions('-c:a', 'copy', '-map', '0:a', '-metadata', 'podcast=1')
|
ffmpeg.outputOptions('-c:a', 'copy', '-map', '0:a', '-metadata', 'podcast=1')
|
||||||
@ -251,7 +254,7 @@ module.exports.writeFFMetadataFile = writeFFMetadataFile
|
|||||||
* @param {number} track - The track number to embed in the audio file.
|
* @param {number} track - The track number to embed in the audio file.
|
||||||
* @param {string} mimeType - The MIME type of the audio file.
|
* @param {string} mimeType - The MIME type of the audio file.
|
||||||
* @param {function(number): void|null} progressCB - A callback function to report progress.
|
* @param {function(number): void|null} progressCB - A callback function to report progress.
|
||||||
* @param {Ffmpeg} ffmpeg - The Ffmpeg instance to use (optional). Used for dependency injection in tests.
|
* @param {import('../libs/fluentFfmpeg/index').FfmpegCommand} ffmpeg - The Ffmpeg instance to use (optional). Used for dependency injection in tests.
|
||||||
* @param {function(string, string): Promise<void>} copyFunc - The function to use for copying files (optional). Used for dependency injection in tests.
|
* @param {function(string, string): Promise<void>} copyFunc - The function to use for copying files (optional). Used for dependency injection in tests.
|
||||||
* @returns {Promise<void>} A promise that resolves if the operation is successful, rejects otherwise.
|
* @returns {Promise<void>} A promise that resolves if the operation is successful, rejects otherwise.
|
||||||
*/
|
*/
|
||||||
@ -392,9 +395,9 @@ module.exports.getFFMetadataObject = getFFMetadataObject
|
|||||||
* @param {number} duration - The total duration of the audio tracks.
|
* @param {number} duration - The total duration of the audio tracks.
|
||||||
* @param {string} itemCachePath - The path to the item cache.
|
* @param {string} itemCachePath - The path to the item cache.
|
||||||
* @param {string} outputFilePath - The path to the output file.
|
* @param {string} outputFilePath - The path to the output file.
|
||||||
* @param {Object} encodingOptions - The options for encoding the audio.
|
* @param {import('../managers/AbMergeManager').AbMergeEncodeOptions} encodingOptions - The options for encoding the audio.
|
||||||
* @param {Function} [progressCB=null] - The callback function to track the progress of the merge.
|
* @param {Function} [progressCB=null] - The callback function to track the progress of the merge.
|
||||||
* @param {Object} [ffmpeg=Ffmpeg()] - The FFmpeg instance to use for merging.
|
* @param {import('../libs/fluentFfmpeg/index').FfmpegCommand} [ffmpeg=Ffmpeg()] - The FFmpeg instance to use for merging.
|
||||||
* @returns {Promise<void>} A promise that resolves when the audio files are merged successfully.
|
* @returns {Promise<void>} A promise that resolves when the audio files are merged successfully.
|
||||||
*/
|
*/
|
||||||
async function mergeAudioFiles(audioTracks, duration, itemCachePath, outputFilePath, encodingOptions, progressCB = null, ffmpeg = Ffmpeg()) {
|
async function mergeAudioFiles(audioTracks, duration, itemCachePath, outputFilePath, encodingOptions, progressCB = null, ffmpeg = Ffmpeg()) {
|
||||||
|
Loading…
Reference in New Issue
Block a user