Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
mifi committed Apr 16, 2020
1 parent cb90645 commit 3aaa999
Show file tree
Hide file tree
Showing 25 changed files with 1,769 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": "airbnb-base",
"env": {
"node": true
},
"parserOptions": {
"sourceType": "script"
},
"rules": {
"max-len": 0,
"no-console": 0,
"object-curly-newline": 0,
"no-await-in-loop": 0,
}
}
107 changes: 107 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env node
const meow = require('meow');
const fs = require('fs');
const FileType = require('file-type');
const pMap = require('p-map');
const JSON5 = require('json5');
const assert = require('assert');

const editly = require('.');


const cli = meow(`
Usage
$ editly CLIP1 [CLIP2 [CLIP3 ...]]
where each CLIP can be one of the following:
- A path to a video file
- A path to an image
- A quoted text to show in a title screen, prefixed by "title:"
Or alternatively:
$ editly --json JSON_PATH
where JSON_PATH is the path to an edit spec JSON file, can be a normal JSON or JSON5
Options
--out Out video path (defaults to ./editly-out.mp4) - can also be a .gif
--json Use JSON config, all other options will be ignored
--transition-name Name of default transition to use
--transition-duration Default transition duration in milliseconds
--width Width which all videos will be converted to
--height Height which all videos will be converted to
--fps FPS which all videos will be converted to
--font-path Set default font to a .ttf
--audio-file-path Add an audio track
--fast, -f Fast mode (low resolution and FPS, useful for getting a quick preview)
--verbose
Examples
$ editly title:'My video' clip1.mov clip2.mov title:'My slideshow' img1.jpg img2.jpg title:'THE END' --audio-file-path /path/to/music.mp3 --font-path /path/to/my-favorite-font.ttf
$ editly --json my-editly.json --out output.gif
`, {
flags: {
fast: { type: 'boolean', alias: 'f' },
transitionDuration: { type: 'number' },
width: { type: 'number' },
height: { type: 'number' },
fps: { type: 'number' },
},
});

(async () => {
let params = {
defaults: {},
};
if (cli.flags.json) {
params = JSON5.parse(fs.readFileSync(cli.flags.json, 'utf-8'));
} else {
const clipsIn = cli.input;
if (clipsIn.length < 1) cli.showHelp();

const clips = await pMap(clipsIn, async (clip) => {
const match = clip.match(/^title:(.+)$/);
if (match) return { type: 'title-background', text: match[1] };

const { mime } = await FileType.fromFile(clip);

if (mime.startsWith('video')) return { type: 'video', path: clip };
if (mime.startsWith('image')) return { type: 'image', path: clip };

throw new Error(`Unrecognized clip or file type "${clip}"`);
}, { concurrency: 1 });

assert(clips.length > 0, 'No clips specified');

params.clips = clips.map((clip) => ({ layers: [clip] }));
}

const { verbose, transitionName, transitionDuration, width, height, fps, audioFilePath, fontPath, fast, out: outPath } = cli.flags;

if (transitionName || transitionDuration) {
params.defaults.transition = {
name: transitionName,
duration: transitionDuration,
};
}

if (fontPath) {
params.defaults.layer = {
fontPath,
};
}

if (outPath) params.outPath = outPath;
if (audioFilePath) params.audioFilePath = audioFilePath;
if (width) params.width = width;
if (height) params.height = height;
if (fps) params.fps = fps;

if (fast) params.fast = fast;
if (verbose) params.verbose = verbose;

if (params.verbose) console.log(JSON5.stringify(params, null, 2));

if (!params.outPath) params.outPath = './editly-out.mp4';

await editly(params);
})().catch(console.error);
199 changes: 199 additions & 0 deletions colors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// TODO make separate npm module

// https://stackoverflow.com/a/4382138/6519037
const allColors = [
'hsl(42, 100%, 50%)',
'hsl(310, 34%, 37%)',
'hsl(24, 100%, 50%)',
'hsl(211, 38%, 74%)',
'hsl(350, 100%, 37%)',
'hsl(35, 52%, 59%)',
'hsl(22, 11%, 45%)',
'hsl(145, 100%, 24%)',
'hsl(348, 87%, 71%)',
'hsl(203, 100%, 27%)',
'hsl(11, 100%, 68%)',
'hsl(265, 37%, 34%)',
'hsl(33, 100%, 50%)',
'hsl(342, 63%, 42%)',
'hsl(49, 100%, 47%)',
'hsl(5, 81%, 27%)',
'hsl(68, 100%, 33%)',
'hsl(26, 61%, 21%)',
'hsl(10, 88%, 51%)',
'hsl(84, 33%, 12%)',
];

// https://digitalsynopsis.com/design/beautiful-color-ui-gradients-backgrounds/
const gradientColors = [
[
'#ff9aac',
'#ffa875',
],
[
'#cc2b5e',
'#753a88',
],
[
'#42275a',
'#734b6d',
],
[
'#bdc3c7',
'#2c3e50',
],
[
'#de6262',
'#ffb88c',
],
[
'#eb3349',
'#f45c43',
],
[
'#dd5e89',
'#f7bb97',
],
[
'#56ab2f',
'#a8e063',
],
[
'#614385',
'#516395',
],
[
'#eecda3',
'#ef629f',
],
[
'#eacda3',
'#d6ae7b',
],
[
'#02aab0',
'#00cdac',
],
[
'#d66d75',
'#e29587',
],
[
'#000428',
'#004e92',
],
[
'#ddd6f3',
'#faaca8',
],
[
'#7b4397',
'#dc2430',
],
[
'#43cea2',
'#185a9d',
],
[
'#ba5370',
'#f4e2d8',
],
[
'#ff512f',
'#dd2476',
],
[
'#4568dc',
'#b06ab3',
],
[
'#ec6f66',
'#f3a183',
],
[
'#ffd89b',
'#19547b',
],
[
'#3a1c71',
'#d76d77',
],
[
'#4ca1af',
'#c4e0e5',
],
[
'#ff5f6d',
'#ffc371',
],
[
'#36d1dc',
'#5b86e5',
],
[
'#c33764',
'#1d2671',
],
[
'#141e30',
'#243b55',
],
[
'#ff7e5f',
'#feb47b',
],
[
'#ed4264',
'#ffedbc',
],
[
'#2b5876',
'#4e4376',
],
[
'#ff9966',
'#ff5e62',
],
[
'#aa076b',
'#61045f',
],
];

/* const lightGradients = [
[
'#ee9ca7',
'#ffdde1',
],
[
'#2193b0',
'#6dd5ed',
],
]; */

function getRandomColor(colors = allColors) {
const index = Math.floor(Math.random() * colors.length);
const remainingColors = [...colors];
remainingColors.splice(index, 1);
return { remainingColors, color: colors[index] || allColors[0] };
}

function getRandomColors(num) {
let colors = allColors;
const out = [];
for (let i = 0; i < Math.min(num, allColors.length); i += 1) {
const { remainingColors, color } = getRandomColor(colors);
out.push(color);
colors = remainingColors;
}
return out;
}

function getRandomGradient() {
return gradientColors[Math.floor(Math.random() * gradientColors.length)];
}

module.exports = {
getRandomColors,
getRandomGradient,
};
36 changes: 36 additions & 0 deletions examples/commonFeatures.json5
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
// fast: true,
// width: 2166, height: 1650, fps: 30,
outPath: './out.mp4',
// outPath: './out.gif',
// verbose: true,
// enableFfmpegLog: true,
audioFilePath: './High [NCS Release] - JPB (No Copyright Music)-R8ZRCXy5vhA.m4a',
defaults: {
transition: { name: 'random' },
layer: { fontPath: './Patua_One/PatuaOne-Regular.ttf' },
},
clips: [
{ duration: 3, transition: { name: 'directional-left' }, layers: [{ type: 'title-background', text: 'EDITLY\nVideo editing framework', background: { type: 'linear-gradient', colors: ['#02aab0', '#00cdac'] } }] },
{ duration: 4, transition: { name: 'dreamyzoom' }, layers: [{ type: 'title-background', text: 'Multi-line text with animated linear or radial gradients', background: { type: 'radial-gradient' } }] },
{ duration: 3, transition: { name: 'directional-right' }, layers: [{ type: 'rainbow-colors' }, { type: 'title', text: 'Colorful backgrounds' }] },
{ duration: 3, layers: [{ type: 'pause' }, { type: 'title', text: 'and separators' }] },

{ duration: 3, transition: { name: 'fadegrayscale' }, layers: [{ type: 'title-background', text: 'Image slideshows with Ken Burns effect', background: { type: 'linear-gradient' } }] },
{ duration: 2.5, transition: { name: 'directionalWarp' }, layers: [{ type: 'image', path: './vertical.jpg', zoomDirection: 'out' }] },
{ duration: 3, transition: { name: 'dreamyzoom' }, layers: [{ type: 'image', path: './img1.jpg', duration: 2.5, zoomDirection: 'in' }, { type: 'subtitle', text: 'Indonesia has many spectacular locations. Here is the volcano Kelimutu, which has three lakes in its core, some days with three different colors!' }, { type: 'title', position: 'top', text: 'With text' }] },
{ duration: 3, transition: { name: 'colorphase' }, layers: [{ type: 'image', path: './img2.jpg', zoomDirection: 'out' }, { type: 'subtitle', text: 'Komodo national park is the only home of the endangered Komodo dragons' }] },
{ duration: 2.5, transition: { name: 'simplezoom' }, layers: [{ type: 'image', path: './img3.jpg', zoomDirection: 'in' }] },

{ duration: 1.5, transition: { name: 'crosszoom', duration: 0.3 }, layers: [{ type: 'video', path: '/Users/mifi/Desktop/photos/drone koh lipe/DJI_0402.MOV', cutTo: 58 }, { type: 'title', text: 'Videos' }] },
{ duration: 3, transition: { name: 'fade' }, layers: [{ type: 'video', path: '/Users/mifi/Desktop/photos/drone koh lipe/DJI_0402.MOV', cutFrom: 58 }] },
{ transition: { name: 'fade' }, layers: [{ type: 'video', path: '/Users/mifi/Desktop/photos/drone koh lipe/DJI_0403.MOV', cutTo: 2.5 }] },
{ duration: 1.5, layers: [{ type: 'video', path: '/Users/mifi/Desktop/photos/drone koh lipe/DJI_0401.MOV', cutFrom: 3, cutTo: 30 }] },

{ duration: 3, transition: { name: 'crosszoom' }, layers: [{ type: 'gl', fragmentPath: './shaders/3l23Rh.frag' }, { type: 'title', text: 'OpenGL\nshaders' }] },
{ duration: 3, layers: [{ type: 'gl', fragmentPath: './shaders/MdXyzX.frag' }] },
{ duration: 3, layers: [{ type: 'gl', fragmentPath: './shaders/30daysofshade_010.frag' }] },
{ duration: 3, layers: [{ type: 'gl', fragmentPath: './shaders/wd2yDm.frag', speed: 5 }] },
{ duration: 3, layers: [{ type: 'editly-banner' }] },
],
}
32 changes: 32 additions & 0 deletions examples/customCanvas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const editly = require('..');

async function func({ width, height, canvas }) {
async function onRender(progress) {
const context = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = 40 * (1 + progress * 0.5);

context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'hsl(350, 100%, 37%)';
context.fill();
context.lineWidth = 5;
context.strokeStyle = '#ffffff';
context.stroke();
}

function onClose() {
// Cleanup if you initialized anything
}

return { onRender, onClose };
}

editly({
fast: true,
outPath: './canvas.mp4',
clips: [
{ duration: 2, layers: [{ type: 'canvas', func }] },
],
}).catch(console.error);
Loading

0 comments on commit 3aaa999

Please sign in to comment.