ffmpeg-framecraft 1.0.0
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/LICENSE +15 -0
- package/README.md +467 -0
- package/index.js +29 -0
- package/package.json +43 -0
- package/src/engine.js +365 -0
- package/src/executor.js +158 -0
- package/src/filters.js +173 -0
- package/src/presets/presets.js +105 -0
- package/src/presets/shorts.js +28 -0
- package/src/presets/transitions.js +49 -0
- package/src/utils.js +16 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform and workflow presets for video processing.
|
|
3
|
+
* Use these with FramecraftEngine for consistent output across YouTube Shorts, TikTok, etc.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* const { youtubeShortPreset } = require('./presets/presets');
|
|
7
|
+
* engine.slicesWithTransitions(input, output, {
|
|
8
|
+
* slices: [...],
|
|
9
|
+
* transition: youtubeShortPreset.transition
|
|
10
|
+
* });
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** @constant {object} YouTube Shorts preset */
|
|
14
|
+
const youtubeShortPreset = {
|
|
15
|
+
aspectRatio: '9:16',
|
|
16
|
+
resolution: '1080x1920',
|
|
17
|
+
width: 1080,
|
|
18
|
+
height: 1920,
|
|
19
|
+
subtitles: true,
|
|
20
|
+
watermark: true,
|
|
21
|
+
transition: 'fadeblack',
|
|
22
|
+
transitionDuration: 1,
|
|
23
|
+
videoCodec: 'libx264',
|
|
24
|
+
audioCodec: 'aac',
|
|
25
|
+
crf: 23,
|
|
26
|
+
audioBitrate: '128k',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** @constant {object} TikTok preset */
|
|
30
|
+
const tiktokPreset = {
|
|
31
|
+
aspectRatio: '9:16',
|
|
32
|
+
resolution: '1080x1920',
|
|
33
|
+
width: 1080,
|
|
34
|
+
height: 1920,
|
|
35
|
+
subtitles: true,
|
|
36
|
+
watermark: false,
|
|
37
|
+
transition: 'fade',
|
|
38
|
+
transitionDuration: 0.5,
|
|
39
|
+
videoCodec: 'libx264',
|
|
40
|
+
audioCodec: 'aac',
|
|
41
|
+
crf: 23,
|
|
42
|
+
audioBitrate: '128k',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** @constant {object} Instagram Reels preset */
|
|
46
|
+
const instagramReelsPreset = {
|
|
47
|
+
aspectRatio: '9:16',
|
|
48
|
+
resolution: '1080x1920',
|
|
49
|
+
width: 1080,
|
|
50
|
+
height: 1920,
|
|
51
|
+
subtitles: true,
|
|
52
|
+
watermark: false,
|
|
53
|
+
transition: 'dissolve',
|
|
54
|
+
transitionDuration: 0.5,
|
|
55
|
+
videoCodec: 'libx264',
|
|
56
|
+
audioCodec: 'aac',
|
|
57
|
+
crf: 23,
|
|
58
|
+
audioBitrate: '128k',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** @constant {object} Generic vertical/shorts preset (720p, baseline compatibility) */
|
|
62
|
+
const shortsPresetConfig = {
|
|
63
|
+
aspectRatio: '9:16',
|
|
64
|
+
resolution: '720x1280',
|
|
65
|
+
width: 720,
|
|
66
|
+
height: 1280,
|
|
67
|
+
subtitles: true,
|
|
68
|
+
watermark: false,
|
|
69
|
+
transition: 'fade',
|
|
70
|
+
transitionDuration: 0.5,
|
|
71
|
+
videoCodec: 'libx264',
|
|
72
|
+
profile: 'baseline',
|
|
73
|
+
level: '3.0',
|
|
74
|
+
audioCodec: 'aac',
|
|
75
|
+
crf: 23,
|
|
76
|
+
audioBitrate: '128k',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** All platform presets keyed by name */
|
|
80
|
+
const PLATFORM_PRESETS = {
|
|
81
|
+
youtubeShort: youtubeShortPreset,
|
|
82
|
+
tiktok: tiktokPreset,
|
|
83
|
+
instagramReels: instagramReelsPreset,
|
|
84
|
+
shorts: shortsPresetConfig,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get a platform preset by name.
|
|
89
|
+
* @param {string} name - Preset name (youtubeShort, tiktok, instagramReels, shorts)
|
|
90
|
+
* @returns {object}
|
|
91
|
+
*/
|
|
92
|
+
function getPreset(name) {
|
|
93
|
+
const p = PLATFORM_PRESETS[name];
|
|
94
|
+
if (!p) throw new Error(`Unknown preset: ${name}. Use: ${Object.keys(PLATFORM_PRESETS).join(', ')}`);
|
|
95
|
+
return { ...p };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
youtubeShortPreset,
|
|
100
|
+
tiktokPreset,
|
|
101
|
+
instagramReelsPreset,
|
|
102
|
+
shortsPresetConfig,
|
|
103
|
+
PLATFORM_PRESETS,
|
|
104
|
+
getPreset,
|
|
105
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shorts preset: 720x1280, H.264 baseline, AAC.
|
|
3
|
+
* Optimized for YouTube Shorts / TikTok / vertical video platforms.
|
|
4
|
+
*/
|
|
5
|
+
function outputOptions() {
|
|
6
|
+
return [
|
|
7
|
+
'-c:v',
|
|
8
|
+
'libx264',
|
|
9
|
+
'-preset',
|
|
10
|
+
'medium',
|
|
11
|
+
'-crf',
|
|
12
|
+
'23',
|
|
13
|
+
'-profile:v',
|
|
14
|
+
'baseline',
|
|
15
|
+
'-level',
|
|
16
|
+
'3.0',
|
|
17
|
+
'-movflags',
|
|
18
|
+
'+faststart',
|
|
19
|
+
'-c:a',
|
|
20
|
+
'aac',
|
|
21
|
+
'-b:a',
|
|
22
|
+
'128k',
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
outputOptions,
|
|
28
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transition presets for use with slicesWithTransitions.
|
|
3
|
+
* Maps preset names to FFmpeg xfade transition types and default duration.
|
|
4
|
+
*
|
|
5
|
+
* @see https://ffmpeg.org/ffmpeg-filters.html#xfade
|
|
6
|
+
*/
|
|
7
|
+
const PRESETS = {
|
|
8
|
+
fade: { type: 'fade', duration: 0.5 },
|
|
9
|
+
fadeLong: { type: 'fade', duration: 1 },
|
|
10
|
+
wipeleft: { type: 'wipeleft', duration: 0.5 },
|
|
11
|
+
wiperight: { type: 'wiperight', duration: 0.5 },
|
|
12
|
+
wipeup: { type: 'wipeup', duration: 0.5 },
|
|
13
|
+
wipedown: { type: 'wipedown', duration: 0.5 },
|
|
14
|
+
slideleft: { type: 'slideleft', duration: 0.5 },
|
|
15
|
+
slideright: { type: 'slideright', duration: 0.5 },
|
|
16
|
+
slideup: { type: 'slideup', duration: 0.5 },
|
|
17
|
+
slidedown: { type: 'slidedown', duration: 0.5 },
|
|
18
|
+
circleopen: { type: 'circleopen', duration: 0.5 },
|
|
19
|
+
circleclose: { type: 'circleclose', duration: 0.5 },
|
|
20
|
+
rectcrop: { type: 'rectcrop', duration: 0.5 },
|
|
21
|
+
distance: { type: 'distance', duration: 0.5 },
|
|
22
|
+
fadeblack: { type: 'fadeblack', duration: 0.5 },
|
|
23
|
+
fadewhite: { type: 'fadewhite', duration: 0.5 },
|
|
24
|
+
radial: { type: 'radial', duration: 0.5 },
|
|
25
|
+
dissolve: { type: 'dissolve', duration: 0.5 },
|
|
26
|
+
pixelize: { type: 'pixelize', duration: 0.5 },
|
|
27
|
+
zoomin: { type: 'zoomin', duration: 0.5 },
|
|
28
|
+
zoomout: { type: 'zoomout', duration: 0.5 },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function getTransition(presetOrObject) {
|
|
32
|
+
if (typeof presetOrObject === 'string') {
|
|
33
|
+
const p = PRESETS[presetOrObject];
|
|
34
|
+
if (!p) throw new Error(`Unknown transition preset: ${presetOrObject}`);
|
|
35
|
+
return { ...p };
|
|
36
|
+
}
|
|
37
|
+
if (presetOrObject && typeof presetOrObject.type === 'string') {
|
|
38
|
+
return {
|
|
39
|
+
type: presetOrObject.type,
|
|
40
|
+
duration: presetOrObject.duration ?? 0.5,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { type: 'fade', duration: 0.5 };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
PRESETS,
|
|
48
|
+
getTransition,
|
|
49
|
+
};
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const timeStringToSeconds = (timeString) => {
|
|
2
|
+
const [hours, minutes, seconds] = timeString.split(':').map(Number).map(num => num.toString().padStart(2, '0'));
|
|
3
|
+
return parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(seconds);
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const secondsToTimeString = (seconds) => {
|
|
7
|
+
const hours = Math.floor(seconds / 3600);
|
|
8
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
9
|
+
const remainingSeconds = seconds % 60;
|
|
10
|
+
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
timeStringToSeconds,
|
|
15
|
+
secondsToTimeString,
|
|
16
|
+
};
|