commit bde2e319bd5305a16c563e02462dcc501fa4d1ec Author: mattmcw Date: Sat May 8 12:53:26 2021 -0400 Created repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27774a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +videos +output +node/node_modules \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b1f337e --- /dev/null +++ b/README.md @@ -0,0 +1,222 @@ +--- +header-includes: + - \hypersetup{colorlinks=true, + allbordercolors={0 0 1}, + pdfborderstyle={/S/U/W 1}} +--- + +# UNIX FOR ARTISTS +### Fun with FFMPEG + +## Requirements + +The following applications and runtime environments are needed to run the scripts used in the workshop. +Installation instructions are available below, broken down by operating system. + +* [Bash](https://www.gnu.org/software/bash/) - *You probably have it already!* +* [FFMPEG](https://www.ffmpeg.org/) +* [ImageMagick](https://imagemagick.org/index.php) +* [Youtube-dl](https://ytdl-org.github.io/youtube-dl/index.html) +* [Golang](https://golang.org/) +* \*[Python3](https://www.python.org/downloads/) (*optional*) +* \*[Node.js](https://nodejs.org/en/) (*optional*) + +It's very helpful to have a text editor that you can comfortably read and edit scripts in. +This selection of text editors ranges from free and open source to "free to use" and "free to try". +You can also use TextEdit or Notepad or any text editing application you prefer as long as it's capable of editing in plaintext. + +* [Atom](https://atom.io/) - Free. *macOS, Windows and Linux.* +* [GNU Emacs](https://www.gnu.org/software/emacs/download.html) - Free. *Linux, macOS and Windows.* +* [Visual Studio Code](https://code.visualstudio.com/download) - Free. *macOS, Windows and Linux.* +* [Sublime Text 3](https://www.sublimetext.com/3) - Free to evaluate. *macOS, Windows and Linux.* +* [Notepad++](https://notepad-plus-plus.org/downloads/) - Free to use. *Windows only.* +* [BBEdit](http://www.barebones.com/products/bbedit/download.html) - Freemium. *macOS only.* + +------- + +## macOS + +![The macOS Terminal](img/mac_terminal.png "The macOS Terminal") + +Open the Terminal app located in your Applications folder. +Bash 3.2 is available by default on macOS. + + +##### Install Homebrew + +Go to [https://brew.sh](https://brew.sh) and run the command under the "Install Homebrew" heading after pasting it into your Terminal. + +You will be asked to confirm a few things while Homebrew installs. + +Once the installation process is complete you can install the applications we will be using in this workshop by running the following commands: + +```bash +brew install ffmpeg imagemagick youtube-dl +``` + +Optionally, you can install Node.js for a single example that we will be covering in the workshop. + +```bash +brew install node +``` + +Make sure that you have a compatible version of Python installed by running the following command: + +```bash +python3 --version +``` + +It should print out a value similar to `Python 3.8.2`. +Anything higher than 3.7.0 will work. + +##### Install Golang on macOS + +Go to the [Golang download site](https://golang.org/doc/install). + +Select the "Mac" tab under "2. Go install." if not already selected and download the .pkg installer using the "Download Go for Mac" button. + +Open the installer and follow the instructions. + +Confirm that Go is installed by running the following command in Terminal: + +```bash +go version +``` + +If Golang has been installed correctly you can now install `primitive`. + +```bash +go get -u github.com/fogleman/primitive +``` + +------- + +## Windows 10 + +Windows has its own command-line interpreter, but in order to keep code and examples consistent across different operating systems you will need to install the Windows Subsystem for Linux (WSL). + +##### Enable WSL + +Open the "Turn Windows Features on or off" dialog by searching in your command bar. + +![Turn Windows features on or off](img/windows_10_turn_windows_features.png "Turn Windows features on or off") + +Enable "Virtual Machine Platform" and "Windows Subsystem for Linux" by selecting the checkboxes next to the items and hit "OK". + +![Enable WSL and Virtual Machine Platform](img/windows_10_enable.png "Enable WSL and Virtual Machine Platform") + +Restart your computer for changes to apply. + + +##### Install Ubuntu 20.04 LTS + +Open the Microsoft Store and search for "Ubuntu". +For the latest LTS version, install `Ubuntu 20.04`. + +![Install Ubuntu 20.04](img/windows_10_install.png "Install Ubuntu 20.04 through the Microsoft Store") + +Other versions of Ubuntu or openSUSE may work, but will be subject to differences in behavior. + +![Select the Ubuntu shell](img/windows_10_ubuntu.png "Select the Ubuntu shell") + +Once you've installed Ubuntu, you can open the shell and begin running commands using Bash. + + +##### Install Requirements on Windows 10 + +![The Ubuntu shell on Windows 10](img/windows_10_terminal.png "The Ubuntu shell on Windows 10") + +You can now run the following commands to install the applications for this workshop: + +```bash +sudo apt update +sudo apt install -y ffmpeg imagemagick youtube-dl +``` + +Optionally install Node.js. + +```bash +sudo apt install -y nodejs npm +``` + + +##### Install Golang on Windows 10 + +Go to the [Golang download site](https://golang.org/doc/install). + +Select the "Windows" tab under "2. Go install." if not already selected and download the .msi installer by using the "Download Go for Windows" button. + +Confirm that Go is installed by running the following command in your shell: + +```bash +go version +``` + +If Golang has been installed correctly you can now install `primitive`. + +```bash +go get -u github.com/fogleman/primitive +``` + +------- + +Additional tutorials for installing Bash on Windows 10. + +* [How to Install Linux Bash Shell on Windows 10](https://itsfoss.com/install-bash-on-windows/) +* [Install Bash on Windows 10](https://bayton.org/docs/linux/ubuntu/install-bash-on-windows-10-build-14316/) + +------- + +## Linux + +Open up your preferred terminal application and install the required applications on a Debian-based system. + +```bash +sudo apt install -y ffmpeg imagemagick youtube-dl +``` + +Or, if your distribution uses the `yum` package manager. + +```bash +yum install ffmpeg imagemagick youtube-dl +``` + +Optionally install Node.js. + +```bash +sudo apt install -y nodejs npm +``` +Or using yum. + +``` +yum install nodejs +``` + +##### Install Golang on Linux + +Go to the [Golang download site](https://golang.org/doc/install). + +Select the "Linux" tab under "2. Go install." if not already selected and download the .tar.gz archive using the "Download Go for Linux" button. + +Navigate to the folder you downloaded the archive into. +For the following commands, `go1.16.3.linux-amd64.tar.gz` can be replaced by the filename of the version that you just downloaded. + +```bash +sudo rm -rf /usr/local/go +sudo tar -C /usr/local -xzf go1.16.3.linux-amd64.tar.gz +export PATH=$PATH:/usr/local/go/bin +``` + +Either restart your terminal or run the command `source $HOME/.profile` to apply the changes just made to your system. + +Confirm that Go is installed by running the following command: + +```bash +go version +``` + +If Golang has been correctly installed you can now install `primitive`. + +```bash +go get -u github.com/fogleman/primitive +``` \ No newline at end of file diff --git a/audio/spring.mp3 b/audio/spring.mp3 new file mode 100644 index 0000000..77fb94f Binary files /dev/null and b/audio/spring.mp3 differ diff --git a/bash/arguments.sh b/bash/arguments.sh new file mode 100644 index 0000000..04455f0 --- /dev/null +++ b/bash/arguments.sh @@ -0,0 +1,22 @@ +#!/bin/bash -e + +##################################################### +# +# Arguments are provided to commands and scripts usually +# separated by a space. They can be read and used within +# bash scripts by referencing them with the "$#" notation. +# +# Usage: bash bash/arguments.sh +# +##################################################### + +echo "You entered \"$1\" as the first argument" + +# Using a slightly different notation +if [ "${2}" != "" ]; then + echo "You entered \"${2}\" as the second argument" +fi + +if [ "${3}" != "" ]; then + echo "All of the arguments you entered \"${@}\"" +fi \ No newline at end of file diff --git a/bash/cat.sh b/bash/cat.sh new file mode 100644 index 0000000..f4da0cc --- /dev/null +++ b/bash/cat.sh @@ -0,0 +1,28 @@ +#!/bin/bash -e + +##################################################### +# +# "cat" is an application that prints text files +# into your terminal and allows you to concatinate +# multiple files together. +# +# Usage: bash bash/cat.sh +# +##################################################### + +cat "text/cat1.txt" + +sleep 2 + +# Concatinate multiple files and print the result +cat "text/cat2.txt" "text/cat3.txt" + +sleep 2 + +# Concatinate all files into a new file using the ">" redirect operator +cat "text/cat1.txt" "text/cat2.txt" "text/cat3.txt" > "text/all.txt" + +sleep 2 + +# Print the contents of this file +cat bash/cat.sh diff --git a/bash/functions.sh b/bash/functions.sh new file mode 100644 index 0000000..b20ccca --- /dev/null +++ b/bash/functions.sh @@ -0,0 +1,26 @@ +#!/bin/bash -e + +##################################################### +# +# Functions can be defined using the "name () {}" +# notation where the code is contained within the +# brackets. Functions receive arguments the same way +# a script does, using the "$#" notation. +# +##################################################### + +myFunction () { + if [ "${1}" != "" ]; then + echo "Your first argument is \"${1}\"" + else + echo "Called myFunction with no arguments" + fi +} + +# Invoke your function as if it were any other command +# or application located in your $PATH. +myFunction + +sleep 2 + +myFunction "First argument" \ No newline at end of file diff --git a/bash/ifelse.sh b/bash/ifelse.sh new file mode 100644 index 0000000..1d3ec12 --- /dev/null +++ b/bash/ifelse.sh @@ -0,0 +1,72 @@ +#!/bin/bash -e + +##################################################### +# +# If/else statements allow you to run select commands +# if conditions are met or if they are not. +# +# Usage: bash bash/ifelse.sh +# +##################################################### + +NUMBER=3 +COMPARE="Compare this string" +SUBSTRING="not" +SUBSTRING2="this" + +# Evaluate whether two numbers are equal with the "-eq" operator +if [ ${NUMBER} -eq 3 ]; then + echo "Variable \$NUMBER is equal to 3" +else + echo "Variable \$NUMBER is NOT equal to 3" +fi + +sleep 2 + +# Evaluate whether a string contains substrings. Uses the +# "elif" operator to check if a second condition is met if +# the first one fails. +if [[ "${COMPARE}" == *"${SUBSTRING}"* ]]; then + echo "String \$COMPARE contains substring \"${SUBSTRING}\"" +elif [[ "${COMPARE}" == *"${SUBSTRING2}"* ]]; then + echo "String \$COMPARE contains substring \"${SUBSTRING2}\"" +else + echo "String \$COMPARE contains none of the substrings" +fi + +sleep 2 + +# Evaluate whether a file exists with the "-f" operator +if [ -f "text/cat1.txt" ]; then + echo "File text/cat1.txt exists. Here are the contents:" + cat text/cat1.txt +else + echo "File text/cat1.txt does NOT exist" +fi + +sleep 2 + +# Evaluate whether a directory exists with the "-d" operator +if [ -d "not_here" ]; then + echo "Directory not_here exists. Here are the contents:" + ls not_here +else + echo "Directory not_here does NOT exist" +fi + +# Evaluate whether or not one condition OR another +# is met using the "||" operators. +# The "-gt" operator determines if a number is +# greater than a compared number and "-lt" for +# less than. +if [ $NUMBER -gt 1 ] || [ $NUMBER -lt 5]; then + echo "Variable \$NUMBER is greater than 1 or less than 5" +fi + +# Evaluate whether both conditions are met using +# the AND operator "&&" +if [[ "${COMPARE}" == *"${SUBSTRING}"* ]] && [[ "${COMPARE}" == *"${SUBSTRING2}"* ]]; then + echo "String \$COMPARE contains both substrings" +else + echo "String \$COMPARE does not contain both substrings" +fi \ No newline at end of file diff --git a/bash/paths.sh b/bash/paths.sh new file mode 100644 index 0000000..aa2f042 --- /dev/null +++ b/bash/paths.sh @@ -0,0 +1,50 @@ +#!/bin/bash -e + +##################################################### +# +# The commands "cd", +# +# Usage: bash bash/paths.sh +# +##################################################### + +THIS_DIRECTORY=`pwd` +echo "${THIS_DIRECTORY}" + +sleep 2 + +# Go to your "home" directory for your user +cd ~/ +echo "You are now in:" +pwd + +sleep 2 + +# Go to the root of your filesystem +cd / +echo "You are now in:" +pwd + +sleep 2 +# List all files and directories in "long" and "readable" format +ls -lh + +sleep 10 + +# Return to the directory you started in +cd "${THIS_DIRECTORY}" +echo "You are now back in:" +pwd + +sleep 2 + +# Go into the "bash" sub-directory +cd bash +echo "You are now in:" +pwd + +# Return to the parent directory, the one you started in, +# by using the double period ".." notation +cd ../ +echo "You are now back in:" +pwd diff --git a/bash/variables.sh b/bash/variables.sh new file mode 100644 index 0000000..360b349 --- /dev/null +++ b/bash/variables.sh @@ -0,0 +1,33 @@ +#!/bin/bash -e + +##################################################### +# +# Variables are stored values that can be set locally +# in the script or inherited from your environment. +# +# Usage: bash bash/variables.sh +# +##################################################### + +# Set this variable to run the script +VARIABLE="" +NUMBER=3 + +# Save output of the command "pwd" to a variable +# by running it in a subshell +CURRENT_DIR=`pwd` + +# A different notation for subshells, this time +# saving the contents of a text file to a variable +FILE=$(cat text/cat1.txt) + +if [ "${VARIABLE}" != "" ]; then + echo "Your variable: ${VARIABLE}" + echo "Your number: ${NUMBER}" + echo "Your current dir: ${CURRENT_DIR}" + # $PATH is an environment variable set by your system + echo "Your username: ${USER}" + echo "Your system \$PATH: ${PATH}" +else + echo "Please create within the quotes on line 13" +fi \ No newline at end of file diff --git a/download.sh b/download.sh new file mode 100644 index 0000000..58e6835 --- /dev/null +++ b/download.sh @@ -0,0 +1,23 @@ +#!/bin/bash -e + +VIDEOS=( + bear.mkv + bird.mkv + flower.mkv + ice.mkv + metamorphosis.mkv + negative.mkv + net.mkv + pig.mkv + stream.mkv + surface.mkv + water.mkv +) + +mkdir -p videos +mkdir -p output + +for video in ${VIDEOS[@]}; do + curl "https://sixteenmillimeter.com/unix4artists/${video}" -o "videos/${video}" + sleep 2 +done \ No newline at end of file diff --git a/ffmpeg/audio/audiovideo.sh b/ffmpeg/audio/audiovideo.sh new file mode 100644 index 0000000..43b2667 --- /dev/null +++ b/ffmpeg/audio/audiovideo.sh @@ -0,0 +1,28 @@ +#!/bin/bash -e + +##################################################### +# +# This script will combine the audio from one file and the +# video from another file. +# +# Usage: bash ffmpeg/audio/audiovideo.sh +# +##################################################### + +AUDIO="${1}" +VIDEO="${2}" +OUTPUT="${3}" + +# Check if output file extension is .mkv +if [[ "${OUTPUT}" != *".mkv" ]]; then + echo "Please use an .mkv extension on your output file argument" + exit 1 +fi + +ffmpeg -i "${VIDEO}" \ + -i "${AUDIO}" \ + -c copy \ + -map 0:v:0 \ + -map 1:a:0 \ + -shortest \ + "${OUTPUT}" \ No newline at end of file diff --git a/ffmpeg/audio/extract.sh b/ffmpeg/audio/extract.sh new file mode 100644 index 0000000..7783fd0 --- /dev/null +++ b/ffmpeg/audio/extract.sh @@ -0,0 +1,21 @@ +#!/bin/bash -e + +##################################################### +# +# This script will extract the audio from a video +# losslessly into a .wav file. +# +# Usage: bash ffmpeg/audio/extract.sh +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +# Check if output file extension is .wav +if [[ "${OUTPUT}" != *".wav" ]]; then + echo "Please use an .wav extension on your output file argument" + exit 1 +fi + +ffmpeg -i "${INPUT}" -vn "${OUTPUT}" \ No newline at end of file diff --git a/ffmpeg/audio/stillaudio.sh b/ffmpeg/audio/stillaudio.sh new file mode 100644 index 0000000..309b596 --- /dev/null +++ b/ffmpeg/audio/stillaudio.sh @@ -0,0 +1,35 @@ +#!/bin/bash -e + +##################################################### +# +# This script will take an audio file and an image and +# combine them to create a video. +# +# Usage: bash ffmpeg/audio/stillaudio.sh +# +##################################################### + +AUDIO="${1}" +IMAGE="${2}" +OUTPUT="${3}" + +# Check if file extension is .mkv or .MKV +if [[ "${OUTPUT}" != *".mkv" ]]; then + echo "Please use an .mkv extension on your output file argument" + exit 1 +fi + +ffmpeg -loop 1 \ + -framerate 2 \ + -i "${IMAGE}" \ + -i "${AUDIO}" \ + -c:v libx264 \ + -preset slow \ + -tune stillimage \ + -pix_fmt yuv420p \ + -crf 12 \ + -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:-1:-1:color=black" \ + -c:a aac \ + -b:a 192k \ + -shortest \ + "${OUTPUT}" \ No newline at end of file diff --git a/ffmpeg/basic/info.sh b/ffmpeg/basic/info.sh new file mode 100644 index 0000000..d335342 --- /dev/null +++ b/ffmpeg/basic/info.sh @@ -0,0 +1,23 @@ +#!/bin/bash -e + +##################################################### +# +# "ffprobe" is an application that comes bundled with +# the ffmpeg installation. It will print file +# information about the streams within video and audio +# files. +# +# Usage: bash ffmpeg/basic/info.sh +# +##################################################### + +if [ "${1}" == "" ]; then + echo "Please include a path to a file as the first argument" + exit 1 +fi + +if [ "${2}" != "json" ]; then + ffprobe -v quiet -show_format -show_streams "${1}" +else + ffprobe -v quiet -print_format json -show_format -show_streams "${1}" +fi \ No newline at end of file diff --git a/ffmpeg/basic/prores.sh b/ffmpeg/basic/prores.sh new file mode 100644 index 0000000..6e28444 --- /dev/null +++ b/ffmpeg/basic/prores.sh @@ -0,0 +1,55 @@ +#!/bin/bash -e + +##################################################### +# +# This command will re-encode a video into an MOV +# container with a ProRes HQ (422) video stream and +# audio preserved in its original format. +# +# -i "${INPUT}" +# Sets the first argument, saved as variable "$INPUT" +# as the input file that ffmpeg will re-encode. Must +# go at the beginning of your command. +# +# -c:v prores_ks +# Sets the video codec of the output video to be ProRes +# using the "prores_kostya" library. +# +# -profile:v 3 +# Sets the ProRes profile to 3 or "HQ" use on the output +# video stream. The profiles available: 0 = proxy (PR), +# 1 = light (LT), 2 = standard (ST), 3 = high quality (HQ), +# 4 = 4444, 5 = 4444 (XQ) +# +# -c:a copy +# Copies the audio stream and preserves the format +# and codec from the original file. +# +# "${OUTPUT}" +# The output video file, which should be the last +# argument you provide. +# +# More info: https://avpres.net/FFmpeg/im_ProRes.html +# https://codecs.multimedia.cx/2012/03/a-few-words-about-my-prores-encoder/ +# +# Usage: bash ffmpeg/basic/prores.sh +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +# Check if input and output arguments are supplied +if [ "${INPUT}" == "" ] || [ "${OUTPUT}" == "" ]; then + echo "Please provide input and output files as your first and second arguments" + exit 1 +fi + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 2 +fi + +# Command to re-encode the input video into ProRes +ffmpeg -i "${INPUT}" -c:v prores_ks -profile:v 3 -c:a copy "${OUTPUT}" \ No newline at end of file diff --git a/ffmpeg/basic/reencode.sh b/ffmpeg/basic/reencode.sh new file mode 100644 index 0000000..6a48288 --- /dev/null +++ b/ffmpeg/basic/reencode.sh @@ -0,0 +1,61 @@ +#!/bin/bash -e + +##################################################### +# +# This command will re-encode a video into an MP4 +# container with H264 video and AAC audio encoded to +# 96K. Each of the arguments are explained as follows: +# +# -i "${INPUT}" +# Sets the first argument, saved as variable "$INPUT" +# as the input file that ffmpeg will re-encode. Must +# go at the beginning of your command. +# +# -codec:video libx264 +# Sets the video codec to libx264, which is the library +# ffmpeg uses to encode H264 video streams. +# +# -preset slow +# Sets the preset of the H264 encoding process to run +# slow, which can yield higher quality imagery at the +# expense of speed. Available options: +# ultrafast, superfast, veryfast, faster, medium (default), +# slow, slower, veryslow, placebo +# +# -crf 22 +# Sets the Constant Rate Factor of the video stream, +# which determines the data rate. The range of -crf +# values range from 0 to 51, with 0 being visually +# lossless and 51 being extremely compressed. +# +# -codec:audio aac +# Sets the codec of the output audio stream to AAC. +# +# -b:a +# Sets the data rate of the audio to 192kbit/s. +# +# "${OUTPUT}" +# The output video file, which should be the last +# argument you provide. +# +# Usage: bash ffmpeg/basic/reencode.sh +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +# Check if input and output arguments are supplied +if [ "${INPUT}" == "" ] || [ "${OUTPUT}" == "" ]; then + echo "Please provide input and output files as your first and second arguments" + exit 1 +fi + +# Check if file extension is .mp4 +if [[ "${OUTPUT}" != *".mp4" ]]; then + echo "Please use an .mp4 extension on your output file argument" + exit 2 +fi + +# Command to re-encode the input video into H264/AAC +ffmpeg -i "${INPUT}" -codec:video libx264 -preset slow -crf 22 -codec:audio aac -bitrate:audio 192k "${OUTPUT}" \ No newline at end of file diff --git a/ffmpeg/basic/social.sh b/ffmpeg/basic/social.sh new file mode 100644 index 0000000..0c8a650 --- /dev/null +++ b/ffmpeg/basic/social.sh @@ -0,0 +1,74 @@ +#!/bin/bash -e + +##################################################### +# +# Youtube: https://trac.ffmpeg.org/wiki/Encode/YouTube +# Vimeo: https://vimeo.zendesk.com/hc/en-us/articles/360056550451-Video-and-audio-compression-guidelines +# +# Usage: bash ffmpeg/basic/social.sh +# +##################################################### + +INPUT=`realpath "${1}"` +FILENAME=`basename "${INPUT}"` +FILE="${FILENAME%.*}" +INPUTDIR=`dirname "${INPUT}"` + +if [ "${INPUT}" == "" ]; then + echo "Please provide an input video as your first argument" + exit 1 +fi + +YOUTUBE="${INPUTDIR}/${FILE}-youtube.mkv" +VIMEO="${INPUTDIR}/${FILE}-vimeo.mkv" +TWITTER="${INPUTDIR}/${FILE}-twitter.mp4" +INSTAGRAM="${INPUTDIR}/${FILE}-instagram.mp4" + +# Encode a video for YouTube +ffmpeg -i "${INPUT}" \ + -vcodec libx264 \ + -preset slow \ + -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2" \ + -pix_fmt yuv420p \ + -crf 12 \ + -c:a aac \ + -b:a 192k \ + "${YOUTUBE}" + +# Encode a video for Vimeo using the +# recommended settings for 720p video +ffmpeg -i "${INPUT}" \ + -vcodec libx264 \ + -preset slow \ + -pix_fmt yuv420p \ + -profile:v main \ + -b:v 7500k \ + -minrate 5000k \ + -maxrate 10000k \ + -c:a aac \ + -ar 48000 \ + "${VIMEO}" + +# Encode video for Twitter +ffmpeg -i "${INPUT}" \ + -pix_fmt yuv420p \ + -vcodec libx264 \ + -acodec aac \ + -vb 2048k\ + -minrate 1024k \ + -maxrate 4096k \ + -bufsize 1024k \ + -ar 44100 \ + -ac 2 \ + -strict experimental \ + "${TWITTER}" + +# Crop and encode for Instagram +ffmpeg -i "${INPUT}" \ + -vf "crop=w='min(iw\,ih)':h='min(iw\,ih)',setsar=1" \ + -framerate 30 \ + -vcodec mpeg4 \ + -vb 8000k \ + -strict experimental \ + -q:v 0 \ + "${INSTAGRAM}" \ No newline at end of file diff --git a/ffmpeg/basic/support.sh b/ffmpeg/basic/support.sh new file mode 100644 index 0000000..87ea01d --- /dev/null +++ b/ffmpeg/basic/support.sh @@ -0,0 +1,24 @@ +#!/bin/bash -e + +##################################################### +# +# These commands print out the formats and codecs that +# your installation of ffmpeg supports. You can customize +# ffmpeg to support additional formats, codecs and features +# by using installation options or flags when compiling +# from source. +# +# Mac (Homebrew): https://gist.github.com/Piasy/b5dfd5c048eb69d1b91719988c0325d8 +# Linux & Windows: https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu +# +# Usage: bash ffmpeg/format/support.sh +# +##################################################### + +echo "Formats your FFMPEG install supports:" +ffmpeg -formats + +sleep 10 + +echo "Codecs your FFMPEG install supports:" +ffmpeg -codecs diff --git a/ffmpeg/basic/trim.sh b/ffmpeg/basic/trim.sh new file mode 100644 index 0000000..34d5213 --- /dev/null +++ b/ffmpeg/basic/trim.sh @@ -0,0 +1,24 @@ +#!/bin/bash -e + +##################################################### +# +# This script will trim your video to a 5 second length +# starting 2 seconds into it and write the output as a +# ProRes video file. +# +# Usage: bash ffmpeg/basic/trim.sh +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 2 +fi + +# Trim the video using the "-t" argument with the value 5 (seconds) +# and starting at 00:00:02 (h:m:s) using the "-ss" seeking argument +ffmpeg -ss 00:00:02 -i "${INPUT}" -t 5 -c:v prores_ks -profile:v 3 -c:a copy "${OUTPUT}" \ No newline at end of file diff --git a/ffmpeg/filter/bipack.sh b/ffmpeg/filter/bipack.sh new file mode 100644 index 0000000..2519739 --- /dev/null +++ b/ffmpeg/filter/bipack.sh @@ -0,0 +1,41 @@ +#!/bin/bash -e + +##################################################### +# +# Bipack.sh will simulate the optical printing effect +# of superimposing video A over video B with a third +# video (the matte) as the alpha layer. +# +# Usage: bash ffmpeg/filter/bipack.sh +# +##################################################### + +A=${1} +B=${2} +MATTE=${3} +OUTPUT_FILE=${4} + +CONTRAST=100 +W=1280 +H=720 +RATE=24 + +echo "Running bipack on image sources ${A} and ${B} with ${MATTE} as the matte layer..." + +time ffmpeg -y -i $A -i $B -i $MATTE \ + -filter_complex " + color=0x000000:size=${W}x${H}, format=rgb24[bla]; + [0] fps=${RATE},scale=${W}:${H}:force_original_aspect_ratio=decrease,format=rgb24 [a]; + [1] fps=${RATE},scale=${W}:${H}:force_original_aspect_ratio=decrease,format=rgb24 [b]; + [2] fps=${RATE},scale=${W}:${H}:force_original_aspect_ratio=decrease,format=gray, + smartblur=1, eq=contrast=$CONTRAST, format=rgb24 [maska]; + [2] fps=${RATE},scale=${W}:${H}:force_original_aspect_ratio=decrease,format=gray, + smartblur=1, eq=contrast=$CONTRAST, negate, format=rgb24 [maskb]; + [bla][a][maska] maskedmerge, format=rgb24 [pass1]; + [pass1][b][maskb] maskedmerge, format=rgb24 + " \ + -r $RATE \ + -c:v prores_ks \ + -profile:v 3 \ + -shortest \ + $OUTPUT_FILE \ No newline at end of file diff --git a/ffmpeg/filter/bipack_steps.sh b/ffmpeg/filter/bipack_steps.sh new file mode 100644 index 0000000..daf73dd --- /dev/null +++ b/ffmpeg/filter/bipack_steps.sh @@ -0,0 +1,58 @@ +#!/bin/bash + +set -e + +W=1280 +H=720 + +A=${1} +B=${2} +MATTE=${3} +OUTPUT=${4} + +MATTE1=matte1.mov +MATTE2=matte2.mov +PASS1=pass1.mov +PASS2=pass2.mov + +echo "Generating mattes from $MATTE..." + +ffmpeg -y -i "$MATTE" -vf eq=saturation=0:contrast=100,smartblur=1 -c:v prores_ks -profile:v 4 "$MATTE1" +ffmpeg -y -i "$MATTE1" -vf negate -c:v prores_ks -profile:v 4 "$MATTE2" + +echo "Applying matte to $A..." + +ffmpeg -y -i "$A" -i "$MATTE1" \ + -filter_complex 'color=0x000000:size=1280x720,format=rgb24[bla];[bla][0][1]maskedmerge' \ + -c:v prores_ks \ + -profile:v 4 \ + -shortest \ + "$PASS1" + +echo "Applying matte to $B..." + +ffmpeg -y -i "$B" -i "$MATTE2" \ + -filter_complex 'color=0x000000:size=1280x720,format=rgb24[bla];[bla][0][1]maskedmerge' \ + -c:v prores_ks \ + -profile:v 4 \ + -shortest \ + "$PASS2" + +echo "Cleaning up tmp matte files..." + +rm "$MATTE1" +rm "$MATTE2" + +echo "Combining matted layers together into $OUTPUT_FILE..." + +ffmpeg -y -i "$PASS1" -i "$PASS2" \ + -filter_complex "[0][1]blend=all_mode='lighten'" \ + -c:v prores_ks \ + -profile:v 3 \ + -shortest \ + "$OUTPUT" + +echo "Cleaning up temp files..." + +rm "$PASS1" +rm "$PASS2" \ No newline at end of file diff --git a/ffmpeg/filter/brightness.sh b/ffmpeg/filter/brightness.sh new file mode 100644 index 0000000..c8548c2 --- /dev/null +++ b/ffmpeg/filter/brightness.sh @@ -0,0 +1,27 @@ +#!/bin/bash -e + +##################################################### +# +# This script will increase the contrast of an input +# video by 2 (acceptable values -1000 to 1000) and +# increases the brightness by 0.4 (-1 to 1) +# +# Usage: bash ffmpeg/filter/brightness.sh +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 1 +fi + +ffmpeg -i "${INPUT}" \ + -vf eq=contrast=2:brightness=0.4 \ + -c:v prores_ks \ + -profile:v 3 \ + "${OUTPUT}" + diff --git a/ffmpeg/filter/desaturate.sh b/ffmpeg/filter/desaturate.sh new file mode 100644 index 0000000..83f18aa --- /dev/null +++ b/ffmpeg/filter/desaturate.sh @@ -0,0 +1,25 @@ +#!/bin/bash -e + +##################################################### +# +# This script will convert a color video to black & +# white by reducing color saturation to 0. +# +# Usage: bash ffmpeg/filter/desaturate.sh +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 1 +fi + +ffmpeg -i "${INPUT}" \ + -vf hue=s=0 \ + -c:v prores_ks \ + -profile:v 3 \ + "${OUTPUT}" \ No newline at end of file diff --git a/ffmpeg/filter/grayscale.sh b/ffmpeg/filter/grayscale.sh new file mode 100644 index 0000000..78c1a61 --- /dev/null +++ b/ffmpeg/filter/grayscale.sh @@ -0,0 +1,25 @@ +#!/bin/bash -e + +##################################################### +# +# This script will convert a color video to black & +# white by converting to grayscale format. +# +# Usage: bash ffmpeg/filter/grayscale.sh +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 1 +fi + +ffmpeg -i "${INPUT}" \ + -vf format=gray \ + -c:v prores_ks \ + -profile:v 3 \ + "${OUTPUT}" \ No newline at end of file diff --git a/ffmpeg/filter/negative.sh b/ffmpeg/filter/negative.sh new file mode 100644 index 0000000..1ba7330 --- /dev/null +++ b/ffmpeg/filter/negative.sh @@ -0,0 +1,25 @@ +#!/bin/bash -e + +##################################################### +# +# This script will invert all colors of a video and +# save the output in a ProRes video. +# +# Usage: bash ffmpeg/filter/negative.sh +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 1 +fi + +ffmpeg -i "${INPUT}" \ + -vf negate \ + -c:v prores_ks \ + -profile:v 3 \ + "${OUTPUT}" \ No newline at end of file diff --git a/ffmpeg/filter/title.sh b/ffmpeg/filter/title.sh new file mode 100644 index 0000000..f7e1253 --- /dev/null +++ b/ffmpeg/filter/title.sh @@ -0,0 +1,98 @@ +#!/bin/bash -e + +##################################################### +# +# This script will create a 10 second title and +# overlay it on a video with a blend mode: +# +# addition +# grainmerge +# and +# average +# darken +# difference +# grainextract +# divide +# dodge +# freeze +# exlusion +# extremity +# glow +# hardlight +# hardmix +# heat +# lighten +# linearlight +# multiply +# multiply128 +# negation +# normal +# or +# overlay +# pheonix +# pinlight +# reflect +# screen +# softlight +# subtract +# vividlight +# xor +# +# Usage: bash ffmpeg/filter/title.sh <input video> <output video> <blendmode> "invert" (optional) +# +##################################################### + + +W=720 +H=480 + +TEXT="${1}" +INPUT="${2}" +OUTPUT="${3}" +BLENDMODE="${4}" + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 1 +fi + +if [ "${BLENDMODE}" == "" ]; then + BLENDMODE="multiply" +fi + +if [ "${5}" == "invert" ]; then + convert -size ${W}x${H} \ + -background white \ + -fill black \ + -gravity Center \ + -weight 700 \ + -pointsize 100 \ + label:"${TEXT}" \ + title.png +else + convert -size ${W}x${H} \ + -background black \ + -fill white \ + -gravity Center \ + -weight 700 \ + -pointsize 100 \ + label:"${TEXT}" \ + title.png +fi + +ffmpeg -y -loop 1 \ + -framerate 24 \ + -f image2 \ + -i title.png \ + -s ${W}x${H} \ + -c:v prores_ks \ + -profile:v 3 \ + -t 10 \ + "title.mov" + +ffmpeg -i "${INPUT}" -i "title.mov" -filter_complex "[0][1]blend=${BLENDMODE}" "${OUTPUT}" + +# Clean up by removing the image and title video +rm title.png +rm title.mov \ No newline at end of file diff --git a/ffmpeg/frames/basic.sh b/ffmpeg/frames/basic.sh new file mode 100644 index 0000000..b546afb --- /dev/null +++ b/ffmpeg/frames/basic.sh @@ -0,0 +1,30 @@ +#!/bin/bash -e + +##################################################### +# +# This script splits a video into an image sequence +# and then stitches the image sequence back into a +# ProRes video. +# +# Usage: bash ffmpeg/frames/basic.sh <input video> <output video> +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 1 +fi + +mkdir -p frames + +ffmpeg -i "${INPUT}" frames/frame-%06d.png + +sleep 20 + +ffmpeg -f image2 -i frames/frame-%06d.png -c:v prores_ks -profile:v 3 "${OUTPUT}" + +rm -rf frames \ No newline at end of file diff --git a/ffmpeg/frames/frameloom.sh b/ffmpeg/frames/frameloom.sh new file mode 100644 index 0000000..3c836e7 --- /dev/null +++ b/ffmpeg/frames/frameloom.sh @@ -0,0 +1,41 @@ +#!/bin/bash -e + +##################################################### +# +# Frameloom is a script that exports all frames from +# two videos and alternates them in +# +# Usage: bash ffmpeg/frames/frameloom.sh <input video1> <input video2> <output video> +# +##################################################### + +TMPDIR=`mktemp -d` +i=0 + +if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] ; + then + echo "Not enough arguments supplied" +fi + +# Check if output file extension is .mov +if [[ "${3}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 1 +fi + +mkdir -p $TMPDIR + +ffmpeg -i "$1" -compression_algo raw -pix_fmt rgb24 "${TMPDIR}export-%08d_a.tif" +ffmpeg -i "$2" -compression_algo raw -pix_fmt rgb24 "${TMPDIR}export-%08d_b.tif" + +#rm -r $TMP + +for filename in ${TMPDIR}*.tif; do + value=`printf %08d $i` + mv "$filename" "${TMPDIR}render_${value}.tif" + i=`expr $i + 1` +done + +ffmpeg -r 24 -f image2 -s 1280x720 -i "${TMPDIR}render_%08d.tif" -c:v prores_ks -profile:v 3 -y "$3" + +rm -r "$TMPDIR" \ No newline at end of file diff --git a/ffmpeg/frames/loop.sh b/ffmpeg/frames/loop.sh new file mode 100644 index 0000000..5060a01 --- /dev/null +++ b/ffmpeg/frames/loop.sh @@ -0,0 +1,39 @@ +#!/bin/bash -e + +##################################################### +# +# This script exports all frames of your video into +# a directory named "frames" and then runs a convert +# command which does edge detection on each frame. +# Then all frames are stitched back into a ProRes +# video. +# +# Usage: bash ffmpeg/frames/loop.sh <input video> <output video> +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 1 +fi + +mkdir -p frames + +ffmpeg -i "${INPUT}" frames/frame-%06d.png + +FRAMES=frames/*.png + +for frame in ${FRAMES}; do + echo "Running edge detection on $frame..." + convert "${frame}" -edge 1 "${frame}" +done + +sleep 10 + +ffmpeg -f image2 -i frames/frame-%06d.png -c:v prores_ks -profile:v 3 "${OUTPUT}" + +rm -rf frames \ No newline at end of file diff --git a/ffmpeg/frames/primitive.sh b/ffmpeg/frames/primitive.sh new file mode 100644 index 0000000..96fcc5e --- /dev/null +++ b/ffmpeg/frames/primitive.sh @@ -0,0 +1,53 @@ +#!/bin/bash -e + +##################################################### +# +# This script will export your video to frames and +# use the application "primitive" to reproduce the +# image with polygonal primitive shapes. Then the +# converted frames are p +# +# The modes available for the -m argument are: +# 0=combo +# 1=triangle +# 2=rect +# 3=ellipse +# 4=circle +# 5=rotatedrect +# 6=beziers +# 7=rotatedellipse +# 8=polygon +# +# primitive repo : https://github.com/fogleman/primitive +# +# Usage: bash ffmpeg/frames/primitive.sh <input video> <output video> +# +##################################################### + +INPUT="${1}" +OUTPUT="${2}" + +MODE=1 +NUMBER=100 + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 1 +fi + +mkdir -p frames + +ffmpeg -i "${INPUT}" frames/frame-%06d.png + +FRAMES=frames/*.png + +for frame in ${FRAMES}; do + echo "Processing $frame with primitive..." + # Run the "primitive" application on every frame + primitive -i "${frame}" -o "${frame}" -n 200 -m 4 +done + +ffmpeg -f image2 -i frames/frame-%06d.png -c:v prores_ks -profile:v 3 "${OUTPUT}" + +rm -rf frames \ No newline at end of file diff --git a/im/title.sh b/im/title.sh new file mode 100644 index 0000000..a86749e --- /dev/null +++ b/im/title.sh @@ -0,0 +1,42 @@ +#!/bin/bash -e + +##################################################### +# +# This script will create a 15 second title with a +# 5 second fade in and a 5 second fade out +# +# Usage: bash im/title.sh <title text> <output video> +# +##################################################### + +TEXT="${1}" +OUTPUT="${2}" + +# Check if output file extension is .mov +if [[ "${OUTPUT}" != *".mov" ]]; then + echo "Please use an .mov extension on your output file argument" + exit 1 +fi + +convert -size 1280x720 \ + -background black \ + -fill white \ + -gravity Center \ + -weight 700 \ + -pointsize 200 \ + label:"${TEXT}" \ + title.png + +ffmpeg -loop 1 \ + -framerate 24 \ + -f image2 \ + -i title.png \ + -s 1280x720 \ + -vf "fade=t=in:st=0:d=5,fade=t=out:st=10:d=5" \ + -c:v prores_ks \ + -profile:v 3 \ + -t 15 \ + "${OUTPUT}" + +# Clean up by removing the image +rm title.png \ No newline at end of file diff --git a/img/mac_terminal.png b/img/mac_terminal.png new file mode 100644 index 0000000..ba84690 Binary files /dev/null and b/img/mac_terminal.png differ diff --git a/img/sonicseasonings.jpg b/img/sonicseasonings.jpg new file mode 100644 index 0000000..2b603b6 Binary files /dev/null and b/img/sonicseasonings.jpg differ diff --git a/img/windows_10_admin.png b/img/windows_10_admin.png new file mode 100755 index 0000000..337d3f8 Binary files /dev/null and b/img/windows_10_admin.png differ diff --git a/img/windows_10_enable.png b/img/windows_10_enable.png new file mode 100755 index 0000000..3f33b99 Binary files /dev/null and b/img/windows_10_enable.png differ diff --git a/img/windows_10_install.png b/img/windows_10_install.png new file mode 100755 index 0000000..3de21f7 Binary files /dev/null and b/img/windows_10_install.png differ diff --git a/img/windows_10_terminal.png b/img/windows_10_terminal.png new file mode 100755 index 0000000..edd8b3f Binary files /dev/null and b/img/windows_10_terminal.png differ diff --git a/img/windows_10_turn_windows_features.png b/img/windows_10_turn_windows_features.png new file mode 100755 index 0000000..20cf274 Binary files /dev/null and b/img/windows_10_turn_windows_features.png differ diff --git a/img/windows_10_ubuntu.png b/img/windows_10_ubuntu.png new file mode 100755 index 0000000..f8ada60 Binary files /dev/null and b/img/windows_10_ubuntu.png differ diff --git a/img/windows_10_wsl.png b/img/windows_10_wsl.png new file mode 100755 index 0000000..202a319 Binary files /dev/null and b/img/windows_10_wsl.png differ diff --git a/node/index.js b/node/index.js new file mode 100644 index 0000000..fc416ee --- /dev/null +++ b/node/index.js @@ -0,0 +1,84 @@ +'use strict'; + +const PORT = typeof process.env.PORT !== 'undefined' ? parseInt(process.env.PORT) : 3000; + +const express = require('express'); +const bodyParser = require('body-parser'); +const { execSync } = require('child_process'); + +const app = express(); + +const HOME = `<html> + <head><title>FFMPEG demo + +
+ + +
+ +`; + +const VIDEO = ` + FFMPEG demo + + + +` + +function generateTitle (text) { + const title = `convert \ + -size 720x480 \ + -background black \ + -fill white \ + -gravity Center \ + -weight 700 \ + -pointsize 100 \ + label:"${text}" \ + title.png`; + + const ffmpeg = `ffmpeg -y \ + -loop 1 \ + -framerate 24 \ + -f image2 \ + -i title.png \ + -s 720x480 \ + -vf "fade=t=in:st=0:d=5,fade=t=out:st=10:d=5" \ + -c:v libx264 \ + -preset slow \ + -crf 22 \ + -f mp4 \ + -strict -2 \ + -pix_fmt yuv420p \ + -t 15 \ + -an \ + title.mp4`; + + const cleanup = `rm title.png`; + + execSync(title); + execSync(ffmpeg); + execSync(cleanup); +} + +app.use(express.static('./')); +app.use(bodyParser.urlencoded({ extended : true })); + +app.get('/', (req, res, next) => { + res.end(HOME); + return next(); +}); + +app.post('/', (req, res, next) => { + + generateTitle(req.body.title); + + res.end(VIDEO); + return next(); +}); + +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); + execSync('rm -f title.mp4'); +}); \ No newline at end of file diff --git a/node/package-lock.json b/node/package-lock.json new file mode 100644 index 0000000..f99ca86 --- /dev/null +++ b/node/package-lock.json @@ -0,0 +1,860 @@ +{ + "name": "ffmpeg-demo", + "version": "0.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "ffmpeg-demo", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "body-parser": "^1.19.0", + "express": "^4.17.1" + } + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.30", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", + "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "dependencies": { + "mime-db": "1.47.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + } + }, + "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", + "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==" + }, + "mime-types": { + "version": "2.1.30", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", + "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", + "requires": { + "mime-db": "1.47.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + } + } +} diff --git a/node/package.json b/node/package.json new file mode 100644 index 0000000..548d630 --- /dev/null +++ b/node/package.json @@ -0,0 +1,15 @@ +{ + "name": "ffmpeg-demo", + "version": "0.0.1", + "description": "Demo of ffmpeg used within node.js", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "mmcwilliams", + "license": "MIT", + "dependencies": { + "body-parser": "^1.19.0", + "express": "^4.17.1" + } +} diff --git a/stream/capture.sh b/stream/capture.sh new file mode 100644 index 0000000..aaadab9 --- /dev/null +++ b/stream/capture.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +################################# +# +# From https://gist.github.com/sixteenmillimeter/d6443c6b18a7d143b3695dd9d79c3c22 +# +# Instructions +# +# First, install ffmpeg and youtube-dl +# +# https://ffmpeg.org/download.html +# https://ytdl-org.github.io/youtube-dl/download.html +# +# To capture a stream at https://yoururl and save to a dated, named TS file: +# +# bash capture.sh https://yoururl streamfilename +# +# Will create file streamfile_STARTDATE.ts and streamfilename.txt containing stream metadata. +# +# To capture stream and convert to an MKV file after: +# +# bash capture.sh https://streamurl streamfilename "convert" +# +# Will create TS file and streamfile_STARTDATE_to_FINISHDATE.mkv after capture complete. +# +# Note: Since this is capturing from a M3U8 stream you may end up retrieving more of earlier +# parts of the stream than the metadata will indicate. This will depend on the service +# you are capturing from. +# +################################# + +THREADS="1" #set number of threads for process or comment out to utilize 100% of CPU +QUALITY=-1 #-2 for second best, -1 for best + +INPUT="${1}" +OUTPUT="${2}" # filepath without extension + +if [ "${OUTPUT}" == "" ]; then + OUTPUT=`basename "${INPUT}"` +fi + +if [ "${THREADS}" == "" ]; then + THREADS_ARG="" +else + THREADS_ARG="-threads ${THREADS}" +fi + +METADATA="${OUTPUT}.txt" +FORMATS=`mktemp` + +# Show the available streams using youtube-dl +youtube-dl --list-formats "${INPUT}" > "${FORMATS}" + +FORMAT_FULL=`tail ${QUALITY} "${FORMATS}" | head -1` +FORMAT=`echo "${FORMAT_FULL}" | awk -F" " '{print $1}'` +M3U8=`youtube-dl -f "${FORMAT}" -g "${INPUT}"` +STARTED=`date "+%F-%T-%Z"` +OUTPUT_TS="${OUTPUT}_${STARTED}.ts" + +echo "Stream: ${INPUT}" +echo "Format: ${FORMAT_FULL}" +echo "Output: ${OUTPUT_TS}" + +if [ -f "${METADATA}" ]; then + echo " " >> "${METADATA}" +else + echo "==========================" > "${METADATA}" + echo " " >> "${METADATA}" +fi + +echo "Stream : ${INPUT}" >> "${METADATA}" +echo "M3U8 : ${M3U8}" >> "${METADATA}" +echo "Format : ${FORMAT_FULL}" >> "${METADATA}" +echo "Output : ${OUTPUT_TS}" >> "${METADATA}" +echo "Started : ${STARTED}" >> "${METADATA}" + +echo "Starting capture, press 'q' to finish" + +# capture with no console output +ffmpeg -i "${M3U8}" \ + -loglevel warning \ + -hide_banner ${THREADS_ARG} \ + -c copy \ + "${OUTPUT_TS}" + +FINISHED=`date "+%F-%T-%Z"` +echo "Finished capturing at ${FINISHED}" +echo "Finished : ${FINISHED}" >> "${METADATA}" + +if [ "${3}" == "convert" ]; then + OUTPUT_MKV="${OUTPUT}_${STARTED}_to_${FINISHED}.mkv" + echo "Creating MKV file ${OUTPUT_MKV}..." + # put video in an MKV wrapper as is (not really a conversion) + ffmpeg -i "${OUTPUT_TS}" \ + -loglevel warning \ + -hide_banner ${THREADS_ARG} \ + -map 0 \ + -c copy \ + "${OUTPUT_MKV}" + + echo "Created ${OUTPUT_MKV}" + echo "MKV : ${OUTPUT_MKV}" >> "${METADATA}" +fi + +echo " " >> "${METADATA}" +echo "==========================" >> "${METADATA}" +echo "Cleaning up..." +# cleanup +rm -f "${FORMATS}" + +echo "Completed capturing ${INPUT}" \ No newline at end of file diff --git a/text/all.txt b/text/all.txt new file mode 100644 index 0000000..60c79ec --- /dev/null +++ b/text/all.txt @@ -0,0 +1,20 @@ +Welcome to Unix for Artists! + _______ __ __ __ _ _ _ ___ _______ __ __ +| || | | || | | | | | _ | || | | || | | | +| ___|| | | || |_| | | || || || | |_ _|| |_| | +| |___ | |_| || | | || | | | | | +| ___|| || _ | | || | | | | | +| | | || | | | | _ || | | | | _ | +|___| |_______||_| |__| |__| |__||___| |___| |__| |__| + ________ ________ __ __ _______ ________ ______ +| \| \| \ / \| \ | \ / \ +| $$$$$$$$| $$$$$$$$| $$\ / $$| $$$$$$$\| $$$$$$$$| $$$$$$\ +| $$__ | $$__ | $$$\ / $$$| $$__/ $$| $$__ | $$ __\$$ +| $$ \ | $$ \ | $$$$\ $$$$| $$ $$| $$ \ | $$| \ +| $$$$$ | $$$$$ | $$\$$ $$ $$| $$$$$$$ | $$$$$ | $$ \$$$$ +| $$ | $$ | $$ \$$$| $$| $$ | $$_____ | $$__| $$ +| $$ | $$ | $$ \$ | $$| $$ | $$ \ \$$ $$ + \$$ \$$ \$$ \$$ \$$ \$$$$$$$$ \$$$$$$ + + + \ No newline at end of file diff --git a/text/cat1.txt b/text/cat1.txt new file mode 100644 index 0000000..c3cc297 --- /dev/null +++ b/text/cat1.txt @@ -0,0 +1 @@ +Welcome to Unix for Artists! diff --git a/text/cat2.txt b/text/cat2.txt new file mode 100644 index 0000000..1409bcb --- /dev/null +++ b/text/cat2.txt @@ -0,0 +1,7 @@ + _______ __ __ __ _ _ _ ___ _______ __ __ +| || | | || | | | | | _ | || | | || | | | +| ___|| | | || |_| | | || || || | |_ _|| |_| | +| |___ | |_| || | | || | | | | | +| ___|| || _ | | || | | | | | +| | | || | | | | _ || | | | | _ | +|___| |_______||_| |__| |__| |__||___| |___| |__| |__| diff --git a/text/cat3.txt b/text/cat3.txt new file mode 100644 index 0000000..ec54af5 --- /dev/null +++ b/text/cat3.txt @@ -0,0 +1,9 @@ + ________ ________ __ __ _______ ________ ______ +| \| \| \ / \| \ | \ / \ +| $$$$$$$$| $$$$$$$$| $$\ / $$| $$$$$$$\| $$$$$$$$| $$$$$$\ +| $$__ | $$__ | $$$\ / $$$| $$__/ $$| $$__ | $$ __\$$ +| $$ \ | $$ \ | $$$$\ $$$$| $$ $$| $$ \ | $$| \ +| $$$$$ | $$$$$ | $$\$$ $$ $$| $$$$$$$ | $$$$$ | $$ \$$$$ +| $$ | $$ | $$ \$$$| $$| $$ | $$_____ | $$__| $$ +| $$ | $$ | $$ \$ | $$| $$ | $$ \ \$$ $$ + \$$ \$$ \$$ \$$ \$$ \$$$$$$$$ \$$$$$$