mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-21 00:11:27 -07:00
feat: sync animated anki images to sentence audio
This commit is contained in:
@@ -24,6 +24,33 @@ import { createLogger } from './logger';
|
||||
|
||||
const log = createLogger('media');
|
||||
|
||||
export function buildAnimatedImageVideoFilter(options: {
|
||||
fps?: number;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
leadingStillDuration?: number;
|
||||
}): string {
|
||||
const { fps = 10, maxWidth = 640, maxHeight, leadingStillDuration = 0 } = options;
|
||||
const clampedFps = Math.max(1, Math.min(60, fps));
|
||||
const vfParts: string[] = [];
|
||||
|
||||
if (leadingStillDuration > 0) {
|
||||
vfParts.push(`tpad=start_duration=${leadingStillDuration}:start_mode=clone`);
|
||||
}
|
||||
|
||||
vfParts.push(`fps=${clampedFps}`);
|
||||
|
||||
if (maxWidth && maxWidth > 0 && maxHeight && maxHeight > 0) {
|
||||
vfParts.push(`scale=w=${maxWidth}:h=${maxHeight}:force_original_aspect_ratio=decrease`);
|
||||
} else if (maxWidth && maxWidth > 0) {
|
||||
vfParts.push(`scale=w=${maxWidth}:h=-2`);
|
||||
} else if (maxHeight && maxHeight > 0) {
|
||||
vfParts.push(`scale=w=-2:h=${maxHeight}`);
|
||||
}
|
||||
|
||||
return vfParts.join(',');
|
||||
}
|
||||
|
||||
export class MediaGenerator {
|
||||
private tempDir: string;
|
||||
private notifyIconDir: string;
|
||||
@@ -289,25 +316,15 @@ export class MediaGenerator {
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
crf?: number;
|
||||
leadingStillDuration?: number;
|
||||
} = {},
|
||||
): Promise<Buffer> {
|
||||
const start = Math.max(0, startTime - padding);
|
||||
const duration = endTime - startTime + 2 * padding;
|
||||
const { fps = 10, maxWidth = 640, maxHeight, crf = 35 } = options;
|
||||
const { fps = 10, maxWidth = 640, maxHeight, crf = 35, leadingStillDuration = 0 } = options;
|
||||
|
||||
const clampedFps = Math.max(1, Math.min(60, fps));
|
||||
const clampedCrf = Math.max(0, Math.min(63, crf));
|
||||
|
||||
const vfParts: string[] = [];
|
||||
vfParts.push(`fps=${clampedFps}`);
|
||||
if (maxWidth && maxWidth > 0 && maxHeight && maxHeight > 0) {
|
||||
vfParts.push(`scale=w=${maxWidth}:h=${maxHeight}:force_original_aspect_ratio=decrease`);
|
||||
} else if (maxWidth && maxWidth > 0) {
|
||||
vfParts.push(`scale=w=${maxWidth}:h=-2`);
|
||||
} else if (maxHeight && maxHeight > 0) {
|
||||
vfParts.push(`scale=w=-2:h=${maxHeight}`);
|
||||
}
|
||||
|
||||
const av1Encoder = await this.detectAv1Encoder();
|
||||
if (!av1Encoder) {
|
||||
throw new Error(
|
||||
@@ -338,7 +355,12 @@ export class MediaGenerator {
|
||||
'-i',
|
||||
videoPath,
|
||||
'-vf',
|
||||
vfParts.join(','),
|
||||
buildAnimatedImageVideoFilter({
|
||||
fps,
|
||||
maxWidth,
|
||||
maxHeight,
|
||||
leadingStillDuration,
|
||||
}),
|
||||
...encoderArgs,
|
||||
'-y',
|
||||
outputPath,
|
||||
|
||||
Reference in New Issue
Block a user