diff options
Diffstat (limited to 'shared/ffmpeg/ffmpeg-images.ts')
-rw-r--r-- | shared/ffmpeg/ffmpeg-images.ts | 61 |
1 files changed, 47 insertions, 14 deletions
diff --git a/shared/ffmpeg/ffmpeg-images.ts b/shared/ffmpeg/ffmpeg-images.ts index 2db63bd8b..618fac7d1 100644 --- a/shared/ffmpeg/ffmpeg-images.ts +++ b/shared/ffmpeg/ffmpeg-images.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { FFmpegCommandWrapper, FFmpegCommandWrapperOptions } from './ffmpeg-command-wrapper' | 1 | import { FFmpegCommandWrapper, FFmpegCommandWrapperOptions } from './ffmpeg-command-wrapper' |
2 | import { getVideoStreamDuration } from './ffprobe' | ||
2 | 3 | ||
3 | export class FFmpegImage { | 4 | export class FFmpegImage { |
4 | private readonly commandWrapper: FFmpegCommandWrapper | 5 | private readonly commandWrapper: FFmpegCommandWrapper |
@@ -36,24 +37,56 @@ export class FFmpegImage { | |||
36 | 37 | ||
37 | async generateThumbnailFromVideo (options: { | 38 | async generateThumbnailFromVideo (options: { |
38 | fromPath: string | 39 | fromPath: string |
39 | folder: string | 40 | output: string |
40 | imageName: string | ||
41 | }) { | 41 | }) { |
42 | const { fromPath, folder, imageName } = options | 42 | const { fromPath, output } = options |
43 | 43 | ||
44 | const pendingImageName = 'pending-' + imageName | 44 | let duration = await getVideoStreamDuration(fromPath) |
45 | if (isNaN(duration)) duration = 0 | ||
45 | 46 | ||
46 | const thumbnailOptions = { | 47 | this.commandWrapper.buildCommand(fromPath) |
47 | filename: pendingImageName, | 48 | .seekInput(duration / 2) |
48 | count: 1, | 49 | .videoFilter('thumbnail=500') |
49 | folder | 50 | .outputOption('-frames:v 1') |
51 | .output(output) | ||
52 | |||
53 | return this.commandWrapper.runCommand() | ||
54 | } | ||
55 | |||
56 | async generateStoryboardFromVideo (options: { | ||
57 | path: string | ||
58 | destination: string | ||
59 | |||
60 | sprites: { | ||
61 | size: { | ||
62 | width: number | ||
63 | height: number | ||
64 | } | ||
65 | |||
66 | count: { | ||
67 | width: number | ||
68 | height: number | ||
69 | } | ||
70 | |||
71 | duration: number | ||
50 | } | 72 | } |
73 | }) { | ||
74 | const { path, destination, sprites } = options | ||
75 | |||
76 | const command = this.commandWrapper.buildCommand(path) | ||
51 | 77 | ||
52 | return new Promise<string>((res, rej) => { | 78 | const filter = [ |
53 | this.commandWrapper.buildCommand(fromPath) | 79 | `setpts=N/round(FRAME_RATE)/TB`, |
54 | .on('error', rej) | 80 | `select='not(mod(t,${options.sprites.duration}))'`, |
55 | .on('end', () => res(imageName)) | 81 | `scale=${sprites.size.width}:${sprites.size.height}`, |
56 | .thumbnail(thumbnailOptions) | 82 | `tile=layout=${sprites.count.width}x${sprites.count.height}` |
57 | }) | 83 | ].join(',') |
84 | |||
85 | command.outputOption('-filter_complex', filter) | ||
86 | command.outputOption('-frames:v', '1') | ||
87 | command.outputOption('-q:v', '2') | ||
88 | command.output(destination) | ||
89 | |||
90 | return this.commandWrapper.runCommand() | ||
58 | } | 91 | } |
59 | } | 92 | } |