@marcuth/movie.js 0.1.3 โ 0.2.1
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/README.md +164 -0
- package/dist/clips/video-clip.d.ts +3 -1
- package/dist/clips/video-clip.js +8 -2
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Movie.js
|
|
2
|
+
|
|
3
|
+
**Movie.js** is a library built on top of `fluent-ffmpeg` designed to create templates for generating standardized videos with dataโwhich can be static or dynamic. It simplifies the process of sequencing video, image, and audio clips into a final composition.
|
|
4
|
+
|
|
5
|
+
## ๐ฆ Installation
|
|
6
|
+
|
|
7
|
+
Installation is straightforward; just use your preferred package manager. Here is an example using NPM:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i @marcuth/movie.js
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> **Note:** You must have FFMPEG installed on your system for this library to work.
|
|
14
|
+
|
|
15
|
+
## ๐ Usage
|
|
16
|
+
|
|
17
|
+
<a href="https://www.buymeacoffee.com/marcuth">
|
|
18
|
+
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" width="200">
|
|
19
|
+
</a>
|
|
20
|
+
|
|
21
|
+
### Template
|
|
22
|
+
|
|
23
|
+
The foundation for creating videos in Movie.js is the `Template`. This is where you define your clips and configuration.
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import movie from "@marcuth/movie.js"
|
|
27
|
+
|
|
28
|
+
type RenderData = {
|
|
29
|
+
heroImage: string
|
|
30
|
+
backgroundVideo: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const template = movie.template<RenderData>({
|
|
34
|
+
config: {
|
|
35
|
+
format: "mp4",
|
|
36
|
+
fps: 30,
|
|
37
|
+
outputOptions: ["-preset ultrafast"] // optional ffmpeg output options
|
|
38
|
+
},
|
|
39
|
+
clips: [
|
|
40
|
+
// ... your clips here
|
|
41
|
+
]
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Render the template with data
|
|
45
|
+
const result = await template.render({
|
|
46
|
+
heroImage: "/path/to/image.png",
|
|
47
|
+
backgroundVideo: "/path/to/video.mp4"
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Save the result
|
|
51
|
+
await result.toFile("output.mp4")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### Clips
|
|
57
|
+
|
|
58
|
+
Clips are the building blocks of your video. They represent individual media segments like videos, images, or audio tracks.
|
|
59
|
+
|
|
60
|
+
Most file paths in clips support dynamic resolution:
|
|
61
|
+
```ts
|
|
62
|
+
path: ({ data, index }) => data.myPath
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### VideoClip
|
|
66
|
+
|
|
67
|
+
To insert a video segment:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
movie.video({
|
|
71
|
+
path: "assets/intro.mp4",
|
|
72
|
+
fadeIn: 1, // seconds
|
|
73
|
+
fadeOut: 1, // seconds
|
|
74
|
+
subClip: [0, 5] // [start, duration] - Clip from 0s to 5s
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
#### ImageClip
|
|
79
|
+
|
|
80
|
+
To insert an image with optional scrolling/Ken Burns effect:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
movie.image({
|
|
84
|
+
path: ({ data }) => data.heroImage,
|
|
85
|
+
duration: 5, // seconds
|
|
86
|
+
width: 1920, // force resize width
|
|
87
|
+
height: 1080, // force resize height
|
|
88
|
+
scroll: {
|
|
89
|
+
axis: "y", // "x", "y", or "auto"
|
|
90
|
+
direction: "forward",
|
|
91
|
+
easing: "easeInOut"
|
|
92
|
+
},
|
|
93
|
+
fadeIn: 0.5
|
|
94
|
+
})
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### AudioClip
|
|
98
|
+
|
|
99
|
+
To add audio (background music, sound effects):
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
movie.audio({
|
|
103
|
+
path: "assets/music.mp3",
|
|
104
|
+
volume: 0.5,
|
|
105
|
+
fadeIn: 2,
|
|
106
|
+
fadeOut: 2
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### Structural Clips
|
|
113
|
+
|
|
114
|
+
#### RepeatClip
|
|
115
|
+
|
|
116
|
+
If you need to loop over an array of data to generate clips:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
movie.repeat({
|
|
120
|
+
each: ({ data }) => data.items, // Array of items
|
|
121
|
+
clip: (item, index) => movie.video({
|
|
122
|
+
path: item.videoPath
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### CompositionClip
|
|
128
|
+
|
|
129
|
+
Group multiple clips together. Useful for organizing sequences:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
movie.composition({
|
|
133
|
+
clips: [
|
|
134
|
+
movie.video({ ... }),
|
|
135
|
+
movie.video({ ... })
|
|
136
|
+
]
|
|
137
|
+
})
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
#### ConcatenationClip
|
|
141
|
+
|
|
142
|
+
Explicitly concatenate a list of clips:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
movie.concatenation({
|
|
146
|
+
clips: [ ... ]
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## ๐ค Contributing
|
|
153
|
+
|
|
154
|
+
Want to contribute? Follow these steps:
|
|
155
|
+
|
|
156
|
+
1. Fork the repository.
|
|
157
|
+
2. Create a new branch (`git checkout -b feature-new`).
|
|
158
|
+
3. Commit your changes (`git commit -m 'Add new feature'`).
|
|
159
|
+
4. Push to the branch (`git push origin feature-new`).
|
|
160
|
+
5. Open a Pull Request.
|
|
161
|
+
|
|
162
|
+
## ๐ License
|
|
163
|
+
|
|
164
|
+
This project is licensed under the MIT License.
|
|
@@ -6,12 +6,14 @@ export type VideoClipOptions<RenderData> = {
|
|
|
6
6
|
path: Path<RenderData>;
|
|
7
7
|
fadeIn?: number;
|
|
8
8
|
fadeOut?: number;
|
|
9
|
+
subClip?: [number, number];
|
|
9
10
|
};
|
|
10
11
|
export declare class VideoClip<RenderData> extends Clip<RenderData> {
|
|
11
12
|
readonly path: Path<RenderData>;
|
|
12
13
|
readonly fadeIn?: number;
|
|
13
14
|
readonly fadeOut?: number;
|
|
14
|
-
|
|
15
|
+
readonly subClip?: [number, number];
|
|
16
|
+
constructor({ path, fadeIn, fadeOut, subClip }: VideoClipOptions<RenderData>);
|
|
15
17
|
protected getInput(path: string, inputIndex: number): FFmpegInput;
|
|
16
18
|
getDuration(path: string): Promise<number>;
|
|
17
19
|
build(data: RenderData, context: RenderContext): Promise<void>;
|
package/dist/clips/video-clip.js
CHANGED
|
@@ -5,11 +5,12 @@ const fluent_ffmpeg_1 = require("fluent-ffmpeg");
|
|
|
5
5
|
const resolve_path_1 = require("../utils/resolve-path");
|
|
6
6
|
const clip_1 = require("./clip");
|
|
7
7
|
class VideoClip extends clip_1.Clip {
|
|
8
|
-
constructor({ path, fadeIn, fadeOut }) {
|
|
8
|
+
constructor({ path, fadeIn, fadeOut, subClip }) {
|
|
9
9
|
super();
|
|
10
10
|
this.path = path;
|
|
11
11
|
this.fadeIn = fadeIn;
|
|
12
12
|
this.fadeOut = fadeOut;
|
|
13
|
+
this.subClip = subClip;
|
|
13
14
|
}
|
|
14
15
|
getInput(path, inputIndex) {
|
|
15
16
|
return {
|
|
@@ -37,8 +38,13 @@ class VideoClip extends clip_1.Clip {
|
|
|
37
38
|
const input = this.getInput(path, context.inputIndex);
|
|
38
39
|
let currentVideoOutput = input.aliases.video;
|
|
39
40
|
let currentAudioOutput = input.aliases.audio;
|
|
40
|
-
|
|
41
|
+
let duration = await this.getDuration(path);
|
|
41
42
|
context.command.input(path);
|
|
43
|
+
if (this.subClip) {
|
|
44
|
+
const [start, subDuration] = this.subClip;
|
|
45
|
+
context.command.inputOptions([`-ss ${start}`, `-t ${subDuration}`]);
|
|
46
|
+
duration = subDuration;
|
|
47
|
+
}
|
|
42
48
|
if (this.fadeIn !== undefined && this.fadeIn > 0) {
|
|
43
49
|
const fadeInOutput = `[fadeIn${context.inputIndex}]`;
|
|
44
50
|
const fadeInAudioOutput = `[fadeInAudio${context.inputIndex}]`;
|