
Sorry for the very big commit that may lead to git log issues and merge conflicts, but it's a major step forward: * Server can be faster at startup because imports() are async and we can easily lazy import big modules * Angular doesn't seem to support ES import (with .js extension), so we had to correctly organize peertube into a monorepo: * Use yarn workspace feature * Use typescript reference projects for dependencies * Shared projects have been moved into "packages", each one is now a node module (with a dedicated package.json/tsconfig.json) * server/tools have been moved into apps/ and is now a dedicated app bundled and published on NPM so users don't have to build peertube cli tools manually * server/tests have been moved into packages/ so we don't compile them every time we want to run the server * Use isolatedModule option: * Had to move from const enum to const (https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums) * Had to explictely specify "type" imports when used in decorators * Prefer tsx (that uses esbuild under the hood) instead of ts-node to load typescript files (tests with mocha or scripts): * To reduce test complexity as esbuild doesn't support decorator metadata, we only test server files that do not import server models * We still build tests files into js files for a faster CI * Remove unmaintained peertube CLI import script * Removed some barrels to speed up execution (less imports)
93 lines
2.3 KiB
TypeScript
93 lines
2.3 KiB
TypeScript
import { FFmpegCommandWrapper, FFmpegCommandWrapperOptions } from './ffmpeg-command-wrapper.js'
|
|
import { getVideoStreamDuration } from './ffprobe.js'
|
|
|
|
export class FFmpegImage {
|
|
private readonly commandWrapper: FFmpegCommandWrapper
|
|
|
|
constructor (options: FFmpegCommandWrapperOptions) {
|
|
this.commandWrapper = new FFmpegCommandWrapper(options)
|
|
}
|
|
|
|
convertWebPToJPG (options: {
|
|
path: string
|
|
destination: string
|
|
}): Promise<void> {
|
|
const { path, destination } = options
|
|
|
|
this.commandWrapper.buildCommand(path)
|
|
.output(destination)
|
|
|
|
return this.commandWrapper.runCommand({ silent: true })
|
|
}
|
|
|
|
processGIF (options: {
|
|
path: string
|
|
destination: string
|
|
newSize: { width: number, height: number }
|
|
}): Promise<void> {
|
|
const { path, destination, newSize } = options
|
|
|
|
this.commandWrapper.buildCommand(path)
|
|
.fps(20)
|
|
.size(`${newSize.width}x${newSize.height}`)
|
|
.output(destination)
|
|
|
|
return this.commandWrapper.runCommand()
|
|
}
|
|
|
|
async generateThumbnailFromVideo (options: {
|
|
fromPath: string
|
|
output: string
|
|
}) {
|
|
const { fromPath, output } = options
|
|
|
|
let duration = await getVideoStreamDuration(fromPath)
|
|
if (isNaN(duration)) duration = 0
|
|
|
|
this.commandWrapper.buildCommand(fromPath)
|
|
.seekInput(duration / 2)
|
|
.videoFilter('thumbnail=500')
|
|
.outputOption('-frames:v 1')
|
|
.output(output)
|
|
|
|
return this.commandWrapper.runCommand()
|
|
}
|
|
|
|
async generateStoryboardFromVideo (options: {
|
|
path: string
|
|
destination: string
|
|
|
|
sprites: {
|
|
size: {
|
|
width: number
|
|
height: number
|
|
}
|
|
|
|
count: {
|
|
width: number
|
|
height: number
|
|
}
|
|
|
|
duration: number
|
|
}
|
|
}) {
|
|
const { path, destination, sprites } = options
|
|
|
|
const command = this.commandWrapper.buildCommand(path)
|
|
|
|
const filter = [
|
|
`setpts=N/round(FRAME_RATE)/TB`,
|
|
`select='not(mod(t,${options.sprites.duration}))'`,
|
|
`scale=${sprites.size.width}:${sprites.size.height}`,
|
|
`tile=layout=${sprites.count.width}x${sprites.count.height}`
|
|
].join(',')
|
|
|
|
command.outputOption('-filter_complex', filter)
|
|
command.outputOption('-frames:v', '1')
|
|
command.outputOption('-q:v', '2')
|
|
command.output(destination)
|
|
|
|
return this.commandWrapper.runCommand()
|
|
}
|
|
}
|