mulmocast 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/actions/movie.d.ts +2 -2
- package/lib/actions/movie.js +18 -7
- package/lib/methods/mulmo_presentation_style.d.ts +146 -0
- package/lib/types/schema.d.ts +1748 -1
- package/lib/types/schema.js +4 -0
- package/lib/types/type.d.ts +2 -1
- package/lib/utils/context.d.ts +730 -0
- package/package.json +5 -5
- package/scripts/test/test_video_filters.json +510 -0
package/lib/actions/movie.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { MulmoStudioContext, MulmoBeat, MulmoTransition, MulmoCanvasDimension, MulmoFillOption } from "../types/index.js";
|
|
1
|
+
import { MulmoStudioContext, MulmoBeat, MulmoTransition, MulmoCanvasDimension, MulmoFillOption, MulmoVideoFilter } from "../types/index.js";
|
|
2
2
|
import { FfmpegContext } from "../utils/ffmpeg_utils.js";
|
|
3
3
|
type VideoId = string | undefined;
|
|
4
|
-
export declare const getVideoPart: (inputIndex: number, isMovie: boolean, duration: number, canvasInfo: MulmoCanvasDimension, fillOption: MulmoFillOption, speed: number) => {
|
|
4
|
+
export declare const getVideoPart: (inputIndex: number, isMovie: boolean, duration: number, canvasInfo: MulmoCanvasDimension, fillOption: MulmoFillOption, speed: number, filters?: MulmoVideoFilter[]) => {
|
|
5
5
|
videoId: string;
|
|
6
6
|
videoPart: string;
|
|
7
7
|
};
|
package/lib/actions/movie.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { GraphAILogger, assert } from "graphai";
|
|
2
|
-
import { mulmoFillOptionSchema } from "../types/index.js";
|
|
2
|
+
import { mulmoFillOptionSchema, } from "../types/index.js";
|
|
3
3
|
import { MulmoPresentationStyleMethods } from "../methods/index.js";
|
|
4
4
|
import { getAudioArtifactFilePath, getOutputVideoFilePath, writingMessage, isFile } from "../utils/file.js";
|
|
5
5
|
import { createVideoFileError, createVideoSourceError } from "../utils/error_cause.js";
|
|
6
6
|
import { FfmpegContextAddInput, FfmpegContextInit, FfmpegContextPushFormattedAudio, FfmpegContextGenerateOutput, } from "../utils/ffmpeg_utils.js";
|
|
7
7
|
import { MulmoStudioContextMethods } from "../methods/mulmo_studio_context.js";
|
|
8
|
+
import { convertVideoFilterToFFmpeg } from "../utils/video_filter.js";
|
|
8
9
|
// const isMac = process.platform === "darwin";
|
|
9
10
|
const videoCodec = "libx264"; // "h264_videotoolbox" (macOS only) is too noisy
|
|
10
|
-
export const getVideoPart = (inputIndex, isMovie, duration, canvasInfo, fillOption, speed) => {
|
|
11
|
+
export const getVideoPart = (inputIndex, isMovie, duration, canvasInfo, fillOption, speed, filters) => {
|
|
11
12
|
const videoId = `v${inputIndex}`;
|
|
12
13
|
const videoFilters = [];
|
|
13
14
|
// Handle different media types
|
|
@@ -41,6 +42,12 @@ export const getVideoPart = (inputIndex, isMovie, duration, canvasInfo, fillOpti
|
|
|
41
42
|
`pad=${canvasInfo.width}:${canvasInfo.height}:(ow-iw)/2:(oh-ih)/2:color=black`);
|
|
42
43
|
}
|
|
43
44
|
videoFilters.push("setsar=1", "format=yuv420p");
|
|
45
|
+
// Apply custom video filters if specified
|
|
46
|
+
if (filters && filters.length > 0) {
|
|
47
|
+
filters.forEach((filter) => {
|
|
48
|
+
videoFilters.push(convertVideoFilterToFFmpeg(filter));
|
|
49
|
+
});
|
|
50
|
+
}
|
|
44
51
|
return {
|
|
45
52
|
videoId,
|
|
46
53
|
videoPart: `[${inputIndex}:v]` + videoFilters.join(",") + `[${videoId}]`,
|
|
@@ -178,10 +185,13 @@ const addTransitionEffects = (ffmpegContext, captionedVideoId, context, transiti
|
|
|
178
185
|
// Cannot apply wipe without first frame
|
|
179
186
|
return prevVideoId;
|
|
180
187
|
}
|
|
181
|
-
//
|
|
182
|
-
// The
|
|
183
|
-
const
|
|
184
|
-
const
|
|
188
|
+
// Calculate the actual duration of the static frames
|
|
189
|
+
// The _last frame was generated with the previous beat's actual duration
|
|
190
|
+
const prevStudioBeat = context.studio.beats[beatIndex - 1];
|
|
191
|
+
const prevBeatActualDuration = Math.max(prevStudioBeat.duration + getExtraPadding(context, beatIndex - 1), prevStudioBeat.movieDuration ?? 0);
|
|
192
|
+
// xfade offset must be non-negative and within the first video's duration
|
|
193
|
+
// Start the transition at the end of the first video minus the transition duration
|
|
194
|
+
const xfadeOffset = Math.max(0, prevBeatActualDuration - duration);
|
|
185
195
|
// Apply xfade with explicit pixel format
|
|
186
196
|
const xfadeOutputId = `${transitionVideoId}_xfade`;
|
|
187
197
|
ffmpegContext.filterComplex.push(`[${transitionVideoId}]format=yuv420p[${transitionVideoId}_fmt]`);
|
|
@@ -345,7 +355,8 @@ export const createVideo = async (audioArtifactFilePath, outputVideoPath, contex
|
|
|
345
355
|
studioBeat.movieFile ||
|
|
346
356
|
MulmoPresentationStyleMethods.getImageType(context.presentationStyle, beat) === "movie");
|
|
347
357
|
const speed = beat.movieParams?.speed ?? 1.0;
|
|
348
|
-
const
|
|
358
|
+
const filters = beat.movieParams?.filters;
|
|
359
|
+
const { videoId, videoPart } = getVideoPart(inputIndex, isMovie, duration, canvasInfo, getFillOption(context, beat), speed, filters);
|
|
349
360
|
ffmpegContext.filterComplex.push(videoPart);
|
|
350
361
|
// for transition
|
|
351
362
|
const needFirst = needsFirstFrame[index]; // This beat has slidein
|
|
@@ -25,6 +25,152 @@ export declare const MulmoPresentationStyleMethods: {
|
|
|
25
25
|
type: "fade" | "slideout_left" | "slideout_right" | "slideout_up" | "slideout_down" | "slidein_left" | "slidein_right" | "slidein_up" | "slidein_down" | "wipeleft" | "wiperight" | "wipeup" | "wipedown" | "wipetl" | "wipetr" | "wipebl" | "wipebr";
|
|
26
26
|
duration: number;
|
|
27
27
|
} | undefined;
|
|
28
|
+
filters?: ({
|
|
29
|
+
type: "mono";
|
|
30
|
+
} | {
|
|
31
|
+
type: "sepia";
|
|
32
|
+
} | {
|
|
33
|
+
type: "brightness_contrast";
|
|
34
|
+
brightness: number;
|
|
35
|
+
contrast: number;
|
|
36
|
+
} | {
|
|
37
|
+
type: "hue";
|
|
38
|
+
hue: number;
|
|
39
|
+
saturation: number;
|
|
40
|
+
brightness: number;
|
|
41
|
+
} | {
|
|
42
|
+
type: "colorbalance";
|
|
43
|
+
rs: number;
|
|
44
|
+
gs: number;
|
|
45
|
+
bs: number;
|
|
46
|
+
rm: number;
|
|
47
|
+
gm: number;
|
|
48
|
+
bm: number;
|
|
49
|
+
rh: number;
|
|
50
|
+
gh: number;
|
|
51
|
+
bh: number;
|
|
52
|
+
} | {
|
|
53
|
+
type: "vibrance";
|
|
54
|
+
intensity: number;
|
|
55
|
+
} | {
|
|
56
|
+
type: "negate";
|
|
57
|
+
negate_alpha: boolean;
|
|
58
|
+
} | {
|
|
59
|
+
type: "colorhold";
|
|
60
|
+
color: string;
|
|
61
|
+
similarity: number;
|
|
62
|
+
blend: number;
|
|
63
|
+
} | {
|
|
64
|
+
type: "colorkey";
|
|
65
|
+
color: string;
|
|
66
|
+
similarity: number;
|
|
67
|
+
blend: number;
|
|
68
|
+
} | {
|
|
69
|
+
type: "blur";
|
|
70
|
+
radius: number;
|
|
71
|
+
power: number;
|
|
72
|
+
} | {
|
|
73
|
+
type: "gblur";
|
|
74
|
+
sigma: number;
|
|
75
|
+
} | {
|
|
76
|
+
type: "avgblur";
|
|
77
|
+
sizeX: number;
|
|
78
|
+
sizeY: number;
|
|
79
|
+
} | {
|
|
80
|
+
type: "unsharp";
|
|
81
|
+
luma_msize_x: number;
|
|
82
|
+
luma_msize_y: number;
|
|
83
|
+
luma_amount: number;
|
|
84
|
+
chroma_msize_x: number;
|
|
85
|
+
chroma_msize_y: number;
|
|
86
|
+
chroma_amount: number;
|
|
87
|
+
} | {
|
|
88
|
+
type: "edgedetect";
|
|
89
|
+
low: number;
|
|
90
|
+
high: number;
|
|
91
|
+
mode: "wires" | "colormix" | "canny";
|
|
92
|
+
} | {
|
|
93
|
+
type: "sobel";
|
|
94
|
+
planes: number;
|
|
95
|
+
scale: number;
|
|
96
|
+
delta: number;
|
|
97
|
+
} | {
|
|
98
|
+
type: "emboss";
|
|
99
|
+
} | {
|
|
100
|
+
type: "glitch";
|
|
101
|
+
intensity: number;
|
|
102
|
+
style: "blend" | "noise";
|
|
103
|
+
} | {
|
|
104
|
+
type: "grain";
|
|
105
|
+
intensity: number;
|
|
106
|
+
} | {
|
|
107
|
+
type: "hflip";
|
|
108
|
+
} | {
|
|
109
|
+
type: "vflip";
|
|
110
|
+
} | {
|
|
111
|
+
type: "rotate";
|
|
112
|
+
angle: number;
|
|
113
|
+
fillcolor: string;
|
|
114
|
+
} | {
|
|
115
|
+
type: "transpose";
|
|
116
|
+
dir: "cclock" | "clock" | "cclock_flip" | "clock_flip";
|
|
117
|
+
} | {
|
|
118
|
+
type: "vignette";
|
|
119
|
+
angle: number;
|
|
120
|
+
mode: "forward" | "backward";
|
|
121
|
+
x0?: number | undefined;
|
|
122
|
+
y0?: number | undefined;
|
|
123
|
+
} | {
|
|
124
|
+
type: "fade";
|
|
125
|
+
mode: "in" | "out";
|
|
126
|
+
start_frame: number;
|
|
127
|
+
nb_frames: number;
|
|
128
|
+
alpha: boolean;
|
|
129
|
+
color: string;
|
|
130
|
+
} | {
|
|
131
|
+
type: "pixelize";
|
|
132
|
+
width: number;
|
|
133
|
+
height: number;
|
|
134
|
+
mode: "avg" | "min" | "max";
|
|
135
|
+
} | {
|
|
136
|
+
type: "pseudocolor";
|
|
137
|
+
preset: "magma" | "inferno" | "plasma" | "viridis" | "turbo" | "cividis" | "range1" | "range2" | "shadows" | "highlights" | "solar" | "nominal" | "preferred" | "total";
|
|
138
|
+
} | {
|
|
139
|
+
type: "tmix";
|
|
140
|
+
frames: number;
|
|
141
|
+
weights?: string | undefined;
|
|
142
|
+
} | {
|
|
143
|
+
type: "lagfun";
|
|
144
|
+
decay: number;
|
|
145
|
+
planes: number;
|
|
146
|
+
} | {
|
|
147
|
+
type: "threshold";
|
|
148
|
+
planes: number;
|
|
149
|
+
} | {
|
|
150
|
+
type: "elbg";
|
|
151
|
+
codebook_length: number;
|
|
152
|
+
} | {
|
|
153
|
+
type: "lensdistortion";
|
|
154
|
+
k1: number;
|
|
155
|
+
k2: number;
|
|
156
|
+
} | {
|
|
157
|
+
type: "chromashift";
|
|
158
|
+
cbh: number;
|
|
159
|
+
cbv: number;
|
|
160
|
+
crh: number;
|
|
161
|
+
crv: number;
|
|
162
|
+
edge: "smear" | "wrap";
|
|
163
|
+
} | {
|
|
164
|
+
type: "deflicker";
|
|
165
|
+
size: number;
|
|
166
|
+
mode: "gm" | "am" | "hm" | "qm" | "cm" | "pm" | "median";
|
|
167
|
+
} | {
|
|
168
|
+
type: "dctdnoiz";
|
|
169
|
+
sigma: number;
|
|
170
|
+
} | {
|
|
171
|
+
type: "custom";
|
|
172
|
+
filter: string;
|
|
173
|
+
})[] | undefined;
|
|
28
174
|
speed?: number | undefined;
|
|
29
175
|
};
|
|
30
176
|
keyName: string;
|