Editly

Slick, declarative command line video editing & API

README

undefined
Discord NPM version Build status PayPal

Editly is a tool and framework for declarative NLE (non-linear video editing) using Node.js and ffmpeg. Editly allows you to easily and programmatically create a video from a set of clips, images, audio and titles, with smooth transitions and music overlaid.

Editly has a simple CLI for quickly assembling a video from a set of clips or images, or you can use its more flexible JavaScript API.

Inspired by ffmpeg-concat, editly is much faster and doesn't require much storage because it usesstreaming editing. Editly aims to be very extensible and feature rich with a pluggable interface for adding new dynamic content.
demo

This GIF / YouTube was created with this command: "editly commonFeatures.json5". See more examples here.

Features


- Edit videos with code! 🤓
- Declarative API with fun defaults
- Create colorful videos with random colors generated from aesthetically pleasing palettes and random effects
- Supports any input size, e.g. 4K video and DSLR photos
- Can output to any dimensions and aspect ratio, e.g. Instagram post (1:1), Instagram story (9:16), YouTube (16:9), or any other dimensions you like.
- Content is scaled and letterboxed automatically, even if the input aspect ratio is not the same and the framerate will be converted.
- Speed up / slow down videos automatically to match the cutFrom/cutTo segment length with each clip's duration
- Overlay text and subtitles on videos, images or backgrounds
- Accepts custom HTML5 Canvas / Fabric.js JavaScript code for custom screens or dynamic overlays
- Render custom GL shaders (for example from shadertoy)
- Can output GIF
- Overlay transparent images or even videos with alpha channel
- Show different sub-clips for parts of a clips duration (B-roll)
- Picture-in-picture
- Vignette
- Preserve/mix multiple audio sources
- Automatic audio crossfading
- Automatic audio ducking and normalization

Use cases


- Create a slideshow from a set of pictures with text overlay
- Create a fast-paced trailer or promo video
- Create a tutorial video with help text
- Create news stories
- Create an animated GIF
- Resize video to any size or framerate and with automatic letterboxing/cropping (e.g. if you need to upload a video somewhere but the site complains Video must be 1337x1000 30fps)
- Create a podcast with multiple mixed tracks


Requirements


- Windows, MacOS or Linux
- Node.js installed (Use of the latest LTS version is recommended, v12.16.2 or newer on MacOS.)
- ffmpeg (and ffprobe) installed and available inPATH
- (Linux) may require some extra steps. See headless-gl.
- Editly is now ESM only

Installing


npm i -g editly

Usage: Command line video editor


Run editly --help for usage

Create a simple randomized video edit from videos, images and text with an audio track:

  1. ```sh
  2. editly \
  3.   title:'My video' \
  4.   clip1.mov \
  5.   clip2.mov \
  6.   title:'My slideshow' \
  7.   img1.jpg \
  8.   img2.jpg \
  9.   title:'THE END' \
  10.   --fast \
  11.   --audio-file-path /path/to/music.mp3
  12. ```

Or create an MP4 (or GIF) from a JSON or JSON5 edit spec (JSON5 is just a more user friendly JSON format):

  1. ```sh
  2. editly my-spec.json5 --fast --keep-source-audio --out output.gif
  3. ```

For examples of how to make a JSON edit spec, see below or examples.

Without --fast, it will default to using the width, height and frame rate from the first input video. All other clips will be converted to these dimensions. You can of course override any or all of these parameters.

- TIP: Use this tool in conjunction with LosslessCut
- TIP: If you need catchy music for your video, have a look at this YouTube or the YouTube audio library. Then use youtube-dl to download the video, and then point--audio-file-path at the video file. Be sure to respect their license!

JavaScript library


  1. ``` js
  2. import editly from 'editly';

  3. // See editSpec documentation
  4. await editly(editSpec)
  5. ```

Edit spec


Edit specs are JavaScript / JSON objects describing the whole edit operation with the following structure:

  1. ``` js
  2. {
  3.   outPath,
  4.   width,
  5.   height,
  6.   fps,
  7.   allowRemoteRequests: false,
  8.   defaults: {
  9.     duration: 4,
  10.     transition: {
  11.       duration: 0.5,
  12.       name: 'random',
  13.       audioOutCurve: 'tri',
  14.       audioInCurve: 'tri',
  15.     },
  16.     layer: {
  17.       fontPath,
  18.       // ...more layer defaults
  19.     },
  20.     layerType: {
  21.       'fill-color': {
  22.         color: '#ff6666',
  23.       }
  24.       // ...more per-layer-type defaults
  25.     },
  26.   },
  27.   clips: [
  28.     {
  29.       transition,
  30.       duration,
  31.       layers: [
  32.         {
  33.           type,
  34.           // ...more layer-specific options
  35.         }
  36.         // ...more layers
  37.       ],
  38.     }
  39.     // ...more clips
  40.   ],
  41.   audioFilePath,
  42.   loopAudio: false,
  43.   keepSourceAudio: false,
  44.   clipsAudioVolume: 1,
  45.   outputVolume: 1,
  46.   audioTracks: [
  47.     {
  48.       path,
  49.       mixVolume: 1,
  50.       cutFrom: 0,
  51.       cutTo,
  52.       start: 0,
  53.     },
  54.     // ...more audio tracks
  55.   ],
  56.   audioNorm: {
  57.     enable: false,
  58.     gaussSize: 5,
  59.     maxGain: 30,
  60.   }

  61.   // Testing options:
  62.   enableFfmpegLog: false,
  63.   verbose: false,
  64.   fast: false,
  65. }
  66. ```

Parameters


ParameterCLIDescriptionDefault|
|-|-|-|-|-|
`outPath``--out`Output|
`width``--width`Width`640`|
`height``--height`Heightauto|
`fps``--fps`FPSFirst|
`customOutputArgs`|auto|
`allowRemoteRequests``--allow-remote-requests`Allow`false`|
`fast``--fast`,Fast`false`|
`defaults.layer.fontPath``--font-path`SetSystem|
`defaults.layer.*`||
`defaults.duration``--clip-duration`Set`4`sec
`defaults.transition`||
`defaults.transition.duration``--transition-duration`Default`0.5`sec
`defaults.transition.name``--transition-name`Default`random`|
`defaults.transition.audioOutCurve`|`tri`|
`defaults.transition.audioInCurve`|`tri`|
`clips[]`||
`clips[].duration`|`defaults.duration`|
`clips[].transition`|`defaults.transition`|
`clips[].layers[]`||
`clips[].layers[].type`||
`clips[].layers[].start`||
`clips[].layers[].stop`||
`audioTracks[]`|`[]`|
`audioFilePath``--audio-file-path`Set|
`loopAudio``--loop-audio`Loop`false`|
`keepSourceAudio``--keep-source-audio`Keep`false`|
`clipsAudioVolume`|`1`|
`outputVolume``--output-volume`Adjust`1`e.g.
`audioNorm.enable`|`false`|
`audioNorm.gaussSize`|`5`|
`audioNorm.maxGain`|`30`|

Transition types


transition.name can be any of gl-transitions, or any of the following:directional-left, directional-right, directional-up, directional-down, random or dummy.

Layer types



Layer type 'video'


For video layers, if parent clip.duration is specified, the video will be slowed/sped-up to match clip.duration. If cutFrom/cutTo is set, the resulting segment (cutTo-cutFrom) will be slowed/sped-up to fit clip.duration. If the layer has audio, it will be kept (and mixed with other audio layers if present.)

ParameterDescriptionDefault|
|-|-|-|-|
`path`Path|
`resizeMode`See|
`cutFrom`Time`0`sec
`cutTo`Time*endsec
`width`Width`1``0`
`height`Height`1``0`
`left`X-position`0``0`
`top`Y-position`0``0`
`originX`X`left``left`
`originY`Y`top``top`
`mixVolume`Relative`1`|

Layer type 'audio'


Audio layers will be mixed together. If cutFrom/cutTo is set, the resulting segment (cutTo-cutFrom) will be slowed/sped-up to fit clip.duration. The slow down/speed-up operation is limited to values between 0.5x and 100x.

ParameterDescriptionDefault|
|-|-|-|-|
`path`Path|
`cutFrom`Time`0`sec
`cutTo`Time`clip.duration`sec
`mixVolume`Relative`1`|

Layer type 'detached-audio'


This is a special case of audioTracks that makes it easier to start the audio relative to clips start times without having to calculate global start times.

detached-audio has the exact same properties as audioTracks, exceptstart time is relative to the clip's start.


Layer type 'image'


Full screen image

ParameterDescriptionDefault|
|-|-|-|-|
`path`Path|
`resizeMode`See|

See also See Ken Burns parameters.

Layer type 'image-overlay'


Image overlay with a custom position and size on the screen. NOTE: If you want to use animated GIFs use video instead.

ParameterDescriptionDefault|
|-|-|-|-|
`path`Path|
`position`See|
`width`Width|
`height`Height|


Layer type 'title'

- fontPath - See defaults.layer.fontPath
- text - Title text to show, keep it short
- textColor - default #ffffff
- position - See Position parameter


Layer type 'subtitle'

- fontPath - See defaults.layer.fontPath
- text - Subtitle text to show
- textColor - default #ffffff

Layer type 'title-background'


Title with background

- text - See type title
- textColor - See type title
- background - { type, ... } - See type radial-gradient, linear-gradient or fill-color
- fontPath - See type title

Layer type 'news-title'

- fontPath - See defaults.layer.fontPath
- text
- textColor - default #ffffff
- backgroundColor - default #d02a42
- position - See Position parameter

Layer type 'slide-in-text'

- fontPath - See defaults.layer.fontPath
- text
- fontSize
- charSpacing
- color
- position - See Position parameter

Layer type 'fill-color', 'pause'

- color - Color to fill background, default: randomize

Layer type 'radial-gradient'

- colors - Array of two colors, default: randomize

Layer type 'linear-gradient'

- colors - Array of two colors, default: randomize

Layer type 'rainbow-colors'


🌈🌈🌈

Layer type 'canvas'



- func - Custom JavaScript function

Layer type 'fabric'



- func - Custom JavaScript function

Layer type 'gl'


Loads a GLSL shader. See gl.json5 and rainbow-colors.frag

- fragmentPath
- vertexPath (optional)

Arbitrary audio tracks


audioTracks property can optionally contain a list of objects which specify audio tracks that can be started at arbitrary times in the final video. These tracks will be mixed together (mixVolume specifying a relative number for how loud each track is compared to the other tracks). Because audio from clips will be mixed separately from audioTracks, clipsAudioVolume specifies the volume of the combined audio from clips relative to the volume of each of the audio tracks from audioTracks.

ParameterDescriptionDefault|
|-|-|-|-|
`audioTracks[].path`File|
`audioTracks[].mixVolume`Relative`1`|
`audioTracks[].cutFrom`Time`0`sec
`audioTracks[].cutTo`Time|
`audioTracks[].start`How`0`sec

The difference between audioTracks and Layer type 'audio' is that audioTracks will continue to play across multiple clips and can start and stop whenever needed.

See audioTracks example

See also Layer type 'detached-audio'.

Audio normalization


You can enable audio normalization of the final output audio. This is useful if you want to achieve Audio Ducking (e.g. automatically lower volume of all other tracks when voice-over speaks).

audioNorm parameters are documented here.


Resize modes


resizeMode - How to fit image to screen. Can be one of:
- contain - All the video will be contained within the frame and letterboxed
- contain-blur - Like contain, but with a blurred copy as the letterbox
- cover - Video be cropped to cover the whole screen (aspect ratio preserved)
- stretch - Video will be stretched to cover the whole screen (aspect ratio ignored).

Default contain-blur.

See:

Position parameter


Certain layers support the position parameter

position can be one of either:
  - top, bottom center, top-left, top-right, center-left, center-right, bottom-left, bottom-right
  - An object { x, y, originX = 'left', originY = 'top' }, where { x: 0, y: 0 } is the upper left corner of the screen, and { x: 1, y: 1 } is the lower right corner, x is relative to video width, y to video height. originX and originY are optional, and specify the position's origin (anchor position) of the object.


Ken Burns parameters


ParameterDescriptionDefault|
|-|-|-|-|
`zoomDirection`Zoom|
`zoomAmount`Zoom`0.1`|

Docker


This should help you use editly as a containerized CLI, without worrying about
getting all the right versions of dependencies on your system.

  1. ``` sh
  2. $ git clone https://github.com/mifi/editly.git
  3. $ cd editly/examples
  4. $ git clone https://github.com/mifi/editly-assets.git assets
  5. $ cd ..
  6. $ docker-compose up
  7. $ docker-compose run editly bash -c "cd examples && editly audio1.json5 --out /outputs/audio1.mp4"
  8. $ docker cp editly:/outputs/audio1.mp4 .
  9. ```

Troubleshooting


- If you get Error: The specified module could not be found., try: npm un -g editly && npm i -g --build-from-source editly (see #15)
- If you get an error about gl returning null, see Requirements.
- If you get an error /bin/sh: pkg-config: command not found, try to use newest Node.js LTS version

Donate 🙏


This project is maintained by me alone. The project will always remain free and open source, but if it's useful for you, consider supporting me. :) It will give me extra motivation to improve it.


Thanks


This package would not exist without the support and help from all the contributors and sponsors!

Special thanks to:

- Patrick Connolly - docker-compose support
- Skayo - Typescript support

See also


- https://github.com/JonnyBurger/remotion - Awesome React based alternative to editly (not open source)
- https://github.com/pankod/canvas2video - Similar project based on Cairo
- https://github.com/h2non/videoshow - Inspired editly
- https://github.com/transitive-bullshit/ffmpeg-concat - Inspired editly
- http://www.quasimondo.com/BoxBlurForCanvas/FastBlurDemo.html - Fast blur effect used in editly
- https://github.com/transitive-bullshit/awesome-ffmpeg
- https://github.com/sjfricke/awesome-webgl
- https://www.mltframework.org/docs/melt/
- Icon made by Freepik from www.flaticon.com

Videos made by you


Submit a PR if you want to share your videos or project created with editly here.

- https://www.youtube.com/channel/UCDUauYxuY5Cv1Z6AuD3UGDw?view_as=subscriber


Made with ❤️ in 🇳🇴


Follow me on GitHub, YouTube, IG, Twitter for more awesome content!