gl-transitions
Version:
The open collection of GL Transitions
72 lines (54 loc) • 3.68 kB
Markdown
# Video tools
## editly (works today)
[editly](https://github.com/mifi/editly) depends on `gl-transitions` and accepts any transition name between clips:
```js
import editly from "editly";
await editly({
outPath: "out.mp4",
defaults: { transition: { name: "crosswarp", duration: 0.5 } },
clips: [
{ layers: [{ type: "video", path: "a.mp4" }] },
{ transition: { name: "cube", duration: 1, params: { persp: 0.5 } }, layers: [{ type: "image", path: "b.jpg" }] },
],
});
```
- `transition` on a clip applies between that clip and the next one.
- `params` sets transition parameters (undocumented in editly but supported); omitted ones keep their defaults.
- Also `random`, `dummy` (none), and `directional-left|right|up|down`.
## FFmpeg (third-party filter)
FFmpeg has no gl-transitions support of its own (its `xfade` filter has a separate, fixed set). The [`ffmpeg-gl-transition`](https://github.com/transitive-bullshit/ffmpeg-gl-transition) filter runs a `.glsl` file, but it needs FFmpeg rebuilt with the filter and an OpenGL context (GLFW or EGL for headless servers):
```sh
ffmpeg -i a.mp4 -i b.mp4 -filter_complex "gltransition=duration=1:offset=2:source=crosswarp.glsl" -y out.mp4
```
The filter sets `progress` and `ratio` but **no parameters**: their uniforms stay 0. For a transition with parameters, bake the values in before writing the `.glsl` file, turning each parameter uniform into a constant:
```js
import transitions from "gl-transitions";
// "uniform float persp; // = 0.7" -> "const float persp = 0.7;" (user values, else defaults)
function bakeParams(transition, values = {}) {
const float = (v) => (Number.isInteger(v) ? v.toFixed(1) : String(v)); // GLSL ES 1.00 needs "1.0", not "1"
const literal = (type, v) =>
Array.isArray(v) ? `${type}(${v.join(", ")})` : type === "float" ? float(Number(v)) : String(v);
let glsl = transition.glsl;
for (const [name, p] of Object.entries(transition.params)) {
// Remove the name from its uniform declaration (it may declare several names, and /* = default */ comments).
glsl = glsl.replace(new RegExp(`uniform\\s+${p.type}\\s+([^;]*);`, "g"), (decl, names) => {
const rest = names
.replace(/\/\*[\s\S]*?\*\//g, "")
.split(",")
.map((n) => n.trim())
.filter((n) => n !== name);
return rest.length ? `uniform ${p.type} ${rest.join(", ")};` : "";
});
glsl = `const ${p.type} ${name} = ${literal(p.type, values[name] ?? p.default)};\n` + glsl;
}
return glsl;
}
```
The constants go at the top of the file, so they are defined before any function uses them. Transitions with extra textures (`displacement`, `luma`) do not work with this filter.
Without a custom FFmpeg build, render the transition frames yourself (WebGL in headless Chrome with `references/webgl.md`, or CanvasKit in Node with `references/skia.md`) and encode them with FFmpeg, or use editly, which does exactly that.
## Remotion (planned)
There is no gl-transitions presentation for `@remotion/transitions` yet (a `@gl-transitions/remotion` package is planned). For images and videos, a component can do it today:
- render a `<canvas>` and draw with the WebGL recipe from `references/webgl.md`;
- compute `progress` from `useCurrentFrame()` (e.g. `interpolate(frame, [start, start + duration], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" })`);
- load images with `delayRender` / `continueRender` so frames are not captured before textures are ready, and pass `preserveDrawingBuffer: true` to `getContext("webgl")`.
Transitions between arbitrary React scenes are not possible this way: the scenes would have to be rasterized first.