@codellyson/framely-cli 0.1.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/commands/compositions.js +135 -0
- package/commands/preview.js +889 -0
- package/commands/render.js +295 -0
- package/commands/still.js +165 -0
- package/index.js +93 -0
- package/package.json +60 -0
- package/studio/App.css +605 -0
- package/studio/App.jsx +185 -0
- package/studio/CompositionsView.css +399 -0
- package/studio/CompositionsView.jsx +327 -0
- package/studio/PropsEditor.css +195 -0
- package/studio/PropsEditor.tsx +176 -0
- package/studio/RenderDialog.tsx +476 -0
- package/studio/ShareDialog.tsx +200 -0
- package/studio/index.ts +19 -0
- package/studio/player/Player.css +199 -0
- package/studio/player/Player.jsx +355 -0
- package/studio/styles/design-system.css +592 -0
- package/studio/styles/dialogs.css +420 -0
- package/studio/templates/AnimatedGradient.jsx +99 -0
- package/studio/templates/InstagramStory.jsx +172 -0
- package/studio/templates/LowerThird.jsx +139 -0
- package/studio/templates/ProductShowcase.jsx +162 -0
- package/studio/templates/SlideTransition.jsx +211 -0
- package/studio/templates/SocialIntro.jsx +122 -0
- package/studio/templates/SubscribeAnimation.jsx +186 -0
- package/studio/templates/TemplateCard.tsx +58 -0
- package/studio/templates/TemplateFilters.tsx +97 -0
- package/studio/templates/TemplatePreviewDialog.tsx +196 -0
- package/studio/templates/TemplatesMarketplace.css +686 -0
- package/studio/templates/TemplatesMarketplace.tsx +172 -0
- package/studio/templates/TextReveal.jsx +134 -0
- package/studio/templates/UseTemplateDialog.tsx +154 -0
- package/studio/templates/index.ts +45 -0
- package/utils/browser.js +188 -0
- package/utils/codecs.js +200 -0
- package/utils/logger.js +35 -0
- package/utils/props.js +42 -0
- package/utils/render.js +447 -0
- package/utils/validate.js +148 -0
package/utils/codecs.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codec Configurations
|
|
3
|
+
*
|
|
4
|
+
* FFmpeg encoding settings for different video codecs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Supported codec configurations.
|
|
9
|
+
*/
|
|
10
|
+
export const codecs = {
|
|
11
|
+
h264: {
|
|
12
|
+
name: 'H.264',
|
|
13
|
+
ffmpegCodec: 'libx264',
|
|
14
|
+
extension: 'mp4',
|
|
15
|
+
pixelFormat: 'yuv420p',
|
|
16
|
+
supportsCrf: true,
|
|
17
|
+
supportsAudio: true,
|
|
18
|
+
description: 'Most compatible format, good quality/size ratio',
|
|
19
|
+
getArgs: (options) => [
|
|
20
|
+
'-c:v', 'libx264',
|
|
21
|
+
'-pix_fmt', 'yuv420p',
|
|
22
|
+
'-preset', options.preset || 'fast',
|
|
23
|
+
'-crf', String(options.crf || 18),
|
|
24
|
+
'-movflags', '+faststart',
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
h265: {
|
|
29
|
+
name: 'H.265 (HEVC)',
|
|
30
|
+
ffmpegCodec: 'libx265',
|
|
31
|
+
extension: 'mp4',
|
|
32
|
+
pixelFormat: 'yuv420p',
|
|
33
|
+
supportsCrf: true,
|
|
34
|
+
supportsAudio: true,
|
|
35
|
+
description: 'Better compression than H.264, less compatible',
|
|
36
|
+
getArgs: (options) => [
|
|
37
|
+
'-c:v', 'libx265',
|
|
38
|
+
'-pix_fmt', 'yuv420p',
|
|
39
|
+
'-preset', options.preset || 'fast',
|
|
40
|
+
'-crf', String(options.crf || 23),
|
|
41
|
+
'-tag:v', 'hvc1', // For Apple compatibility
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
vp8: {
|
|
46
|
+
name: 'VP8',
|
|
47
|
+
ffmpegCodec: 'libvpx',
|
|
48
|
+
extension: 'webm',
|
|
49
|
+
pixelFormat: 'yuv420p',
|
|
50
|
+
supportsCrf: true,
|
|
51
|
+
supportsAudio: true,
|
|
52
|
+
description: 'WebM format, good for web',
|
|
53
|
+
getArgs: (options) => [
|
|
54
|
+
'-c:v', 'libvpx',
|
|
55
|
+
'-pix_fmt', 'yuv420p',
|
|
56
|
+
'-crf', String(options.crf || 10),
|
|
57
|
+
'-b:v', options.bitrate || '5M',
|
|
58
|
+
'-deadline', 'good',
|
|
59
|
+
'-cpu-used', '2',
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
vp9: {
|
|
64
|
+
name: 'VP9',
|
|
65
|
+
ffmpegCodec: 'libvpx-vp9',
|
|
66
|
+
extension: 'webm',
|
|
67
|
+
pixelFormat: 'yuv420p',
|
|
68
|
+
supportsCrf: true,
|
|
69
|
+
supportsAudio: true,
|
|
70
|
+
description: 'WebM format, better compression than VP8',
|
|
71
|
+
getArgs: (options) => [
|
|
72
|
+
'-c:v', 'libvpx-vp9',
|
|
73
|
+
'-pix_fmt', 'yuv420p',
|
|
74
|
+
'-crf', String(options.crf || 31),
|
|
75
|
+
'-b:v', '0', // Required for CRF mode
|
|
76
|
+
'-deadline', 'good',
|
|
77
|
+
'-cpu-used', '2',
|
|
78
|
+
'-row-mt', '1',
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
prores: {
|
|
83
|
+
name: 'ProRes',
|
|
84
|
+
ffmpegCodec: 'prores_ks',
|
|
85
|
+
extension: 'mov',
|
|
86
|
+
pixelFormat: 'yuva444p10le',
|
|
87
|
+
supportsCrf: false,
|
|
88
|
+
supportsAudio: true,
|
|
89
|
+
supportsAlpha: true,
|
|
90
|
+
description: 'Professional editing format with alpha support',
|
|
91
|
+
profiles: {
|
|
92
|
+
proxy: 0,
|
|
93
|
+
lt: 1,
|
|
94
|
+
standard: 2,
|
|
95
|
+
hq: 3,
|
|
96
|
+
'4444': 4,
|
|
97
|
+
'4444xq': 5,
|
|
98
|
+
},
|
|
99
|
+
getArgs: (options) => {
|
|
100
|
+
const profile = options.profile || 'hq';
|
|
101
|
+
const profileNum = codecs.prores.profiles[profile] != null ? codecs.prores.profiles[profile] : 3;
|
|
102
|
+
return [
|
|
103
|
+
'-c:v', 'prores_ks',
|
|
104
|
+
'-profile:v', String(profileNum),
|
|
105
|
+
'-pix_fmt', options.alpha ? 'yuva444p10le' : 'yuv422p10le',
|
|
106
|
+
'-vendor', 'apl0',
|
|
107
|
+
];
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
gif: {
|
|
112
|
+
name: 'GIF',
|
|
113
|
+
ffmpegCodec: 'gif',
|
|
114
|
+
extension: 'gif',
|
|
115
|
+
pixelFormat: 'rgb8',
|
|
116
|
+
supportsCrf: false,
|
|
117
|
+
supportsAudio: false,
|
|
118
|
+
description: 'Animated GIF, limited colors',
|
|
119
|
+
getArgs: (options) => {
|
|
120
|
+
// GIF needs special handling with palette generation
|
|
121
|
+
return [
|
|
122
|
+
'-filter_complex',
|
|
123
|
+
`[0:v] fps=${options.fps || 15},scale=${options.width || -1}:${options.height || -1}:flags=lanczos,split [a][b];[a] palettegen=max_colors=256:reserve_transparent=0 [p];[b][p] paletteuse=dither=sierra2_4a`,
|
|
124
|
+
'-loop', String(options.loop != null ? options.loop : 0),
|
|
125
|
+
];
|
|
126
|
+
},
|
|
127
|
+
// GIF uses a two-pass approach for better quality
|
|
128
|
+
requiresPalette: true,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get configuration for a codec.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} codecName - Codec identifier
|
|
136
|
+
* @returns {object|null} Codec configuration or null
|
|
137
|
+
*/
|
|
138
|
+
export function getCodecConfig(codecName) {
|
|
139
|
+
return codecs[codecName] || null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get FFmpeg arguments for a codec.
|
|
144
|
+
*
|
|
145
|
+
* @param {string} codecName - Codec identifier
|
|
146
|
+
* @param {object} options - Encoding options
|
|
147
|
+
* @returns {string[]} FFmpeg arguments
|
|
148
|
+
*/
|
|
149
|
+
export function getCodecArgs(codecName, options = {}) {
|
|
150
|
+
const config = getCodecConfig(codecName);
|
|
151
|
+
if (!config) {
|
|
152
|
+
throw new Error(`Unknown codec: ${codecName}`);
|
|
153
|
+
}
|
|
154
|
+
return config.getArgs(options);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get audio encoding arguments.
|
|
159
|
+
*
|
|
160
|
+
* @param {object} options
|
|
161
|
+
* @param {string} [options.codec='aac'] - Audio codec
|
|
162
|
+
* @param {string} [options.bitrate='320k'] - Audio bitrate
|
|
163
|
+
* @param {number} [options.sampleRate=48000] - Sample rate
|
|
164
|
+
* @returns {string[]} FFmpeg audio arguments
|
|
165
|
+
*/
|
|
166
|
+
export function getAudioArgs(options = {}) {
|
|
167
|
+
const codec = options.codec || 'aac';
|
|
168
|
+
const bitrate = options.bitrate || '320k';
|
|
169
|
+
const sampleRate = options.sampleRate || 48000;
|
|
170
|
+
|
|
171
|
+
return [
|
|
172
|
+
'-c:a', codec,
|
|
173
|
+
'-b:a', bitrate,
|
|
174
|
+
'-ar', String(sampleRate),
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* List all available codecs.
|
|
180
|
+
*
|
|
181
|
+
* @returns {Array<{ id: string, name: string, extension: string, description: string }>}
|
|
182
|
+
*/
|
|
183
|
+
export function listCodecs() {
|
|
184
|
+
return Object.entries(codecs).map(([id, config]) => ({
|
|
185
|
+
id,
|
|
186
|
+
name: config.name,
|
|
187
|
+
extension: config.extension,
|
|
188
|
+
description: config.description,
|
|
189
|
+
supportsAudio: config.supportsAudio,
|
|
190
|
+
supportsAlpha: config.supportsAlpha || false,
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export default {
|
|
195
|
+
codecs,
|
|
196
|
+
getCodecConfig,
|
|
197
|
+
getCodecArgs,
|
|
198
|
+
getAudioArgs,
|
|
199
|
+
listCodecs,
|
|
200
|
+
};
|
package/utils/logger.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Utility
|
|
3
|
+
*
|
|
4
|
+
* Simple leveled logger for CLI output.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
const LEVELS = { error: 0, warn: 1, info: 2, verbose: 3 };
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a logger with the specified level.
|
|
13
|
+
* @param {string} [level='info'] - Log level
|
|
14
|
+
* @returns {object} Logger with error, warn, info, verbose methods
|
|
15
|
+
*/
|
|
16
|
+
export function createLogger(level = 'info') {
|
|
17
|
+
const currentLevel = LEVELS[level] ?? LEVELS.info;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
error: (...args) => {
|
|
21
|
+
if (currentLevel >= LEVELS.error) console.error(chalk.red(...args));
|
|
22
|
+
},
|
|
23
|
+
warn: (...args) => {
|
|
24
|
+
if (currentLevel >= LEVELS.warn) console.warn(chalk.yellow(...args));
|
|
25
|
+
},
|
|
26
|
+
info: (...args) => {
|
|
27
|
+
if (currentLevel >= LEVELS.info) console.log(...args);
|
|
28
|
+
},
|
|
29
|
+
verbose: (...args) => {
|
|
30
|
+
if (currentLevel >= LEVELS.verbose) console.log(chalk.gray(...args));
|
|
31
|
+
},
|
|
32
|
+
level: currentLevel,
|
|
33
|
+
isVerbose: currentLevel >= LEVELS.verbose,
|
|
34
|
+
};
|
|
35
|
+
}
|
package/utils/props.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props Loading Utility
|
|
3
|
+
*
|
|
4
|
+
* Shared function for loading input props from JSON string or file.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Load props from JSON string or file path.
|
|
11
|
+
* @param {string} [propsJson] - JSON string
|
|
12
|
+
* @param {string} [propsFile] - Path to JSON file
|
|
13
|
+
* @returns {object} parsed props
|
|
14
|
+
*/
|
|
15
|
+
export function loadProps(propsJson, propsFile) {
|
|
16
|
+
if (propsFile) {
|
|
17
|
+
let content;
|
|
18
|
+
try {
|
|
19
|
+
content = fs.readFileSync(propsFile, 'utf-8');
|
|
20
|
+
} catch (err) {
|
|
21
|
+
if (err.code === 'ENOENT') {
|
|
22
|
+
throw new Error(`Props file not found: ${propsFile}`);
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`Could not read props file ${propsFile}: ${err.message}`);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(content);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
throw new Error(`Invalid JSON in props file ${propsFile}: ${err.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (propsJson) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(propsJson);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
throw new Error(`Invalid JSON in --props argument: ${err.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {};
|
|
42
|
+
}
|