adding initial project which created web API to convert

files to jpg, mp3, and mp4
This commit is contained in:
Visco, Paul 2018-04-14 23:10:36 -04:00
commit c36d1e14fb
8 changed files with 392 additions and 0 deletions

7
.eslintrc.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
"extends": "google",
"parserOptions": {
"ecmaVersion": 6
}
};

21
.gitignore vendored Normal file
View File

@ -0,0 +1,21 @@
*.avi
*.mp3
*.m4a
*.zip
uploads/*
.dockerignore
.ebextensions
nbproject
public
package-lock.json
input
node-ffmpeg.zip
/node_modules
# Elastic Beanstalk Files
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml
/nbproject/private/

61
Dockerfile Normal file
View File

@ -0,0 +1,61 @@
FROM jrottenberg/ffmpeg:centos
MAINTAINER Paul Visco <paul.visco@gmail.com>
#####################################################################
#
# A Docker image to convert audio and video for web using web API
#
# with
# - Latest FFMPEG (built)
# - NodeJS
# - fluent-ffmpeg
#
# For more on Fluent-FFMPEG, see
#
# https://github.com/fluent-ffmpeg/node-fluent-ffmpeg
#
#####################################################################
# Add the following two dependencies for nodejs
RUN yum install -y git
RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
RUN yum install -y nodejs npm --enablerepo=epel
WORKDIR /usr/local/src
# Custom Builds go here
RUN npm install -g fluent-ffmpeg
# Remove all tmpfile and cleanup
# =================================
WORKDIR /usr/local/
RUN rm -rf /usr/local/src
RUN yum clean all
RUN rm -rf /var/cache/yum
# =================================
# Setup a working directory to allow for
# docker run --rm -ti -v ${PWD}:/work ...
# =======================================
WORKDIR /work
# Make sure Node.js is installed
RUN node -v
RUN npm -v
#Create app dir
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
#Install Dependencies
COPY package.json /usr/src/app
RUN npm install
#Bundle app source
COPY . /usr/src/app
EXPOSE 3000
ENTRYPOINT []
CMD [ "node", "app.js" ]

70
README.md Normal file
View File

@ -0,0 +1,70 @@
# ffmpeg web service
An web service for converting audio/video files using Nodejs, Express and FFMPEG
Based off of jrottenberg/ffmpeg
## Endpoints
> POST /mp3 - Convert audio file in request body to mp3
> POST /mp4 - Convert video file in request body to mp4
> POST /jpg - Convert image file to jpg
> GET /, /readme - Web Service Readme
### /mp3, /m4a
Curl Ex:
> curl -F "file=@input.wav" 127.0.0.1:3000/mp3 > output.mp3
> curl -F "file=@input.m4a" 127.0.0.1:3000/mp3 > output.mp3
> curl -F "file=@input.mov" 127.0.0.1:3000/mp4 > output.mp4
> curl -F "file=@input.mp4" 127.0.0.1:3000/mp4 > output.mp4
> curl -F "file=@input.tiff" 127.0.0.1:3000/jpg > output.jpg
> curl -F "file=@input.png" 127.0.0.1:3000/jpg > output.jpg
## Configuration and New Endpoints
You can change the ffmpeg conversion settings or add new endpoints by editing
the /app/endpoints.js file
## Installation
Requires local Node and FFMPEG installation.
1) Install FFMPEG https://ffmpeg.org/download.html
2) Install node https://nodejs.org/en/download/
Using homebrew:
> $ brew install node
## Dev - Running Local Node.js Web Service
Navigate to project directory and:
Install dependencies:
> $ npm install
Start app:
> $ node app.js
Check for errors with ESLint:
> $ ./node_modules/.bin/eslint .
## Running Local Docker Container
Build Docker Image from Dockerfile with a set image tag. ex: docker-ffpmeg
> $ docker build -t surebert/docker-ffpmeg .
Launch Docker Container from Docker Image, exposing port 9025 on localhost only
> docker run -d \
--name ffmpeg-service \
--restart=always \
-v /storage/tmpfs:/usr/src/app/uploads \
-p 127.0.0.1:9025:3000 \
surebert/docker-ffpmeg
Launch Docker Container from Docker Image, exposing port 9026 on all IPs
> docker run -p 9026:3000 -d surebert/docker-ffpmeg

168
app.js Normal file
View File

@ -0,0 +1,168 @@
const fs = require('fs');
const express = require('express');
const app = express();
const Busboy = require('busboy');
const compression = require('compression');
const ffmpeg = require('fluent-ffmpeg');
const uniqueFilename = require('unique-filename');
const consts = require(__dirname + '/app/constants.js');
const endpoints = require(__dirname + '/app/endpoints.js');
const winston = require('winston');
app.use(compression());
winston.remove(winston.transports.Console);
winston.add(winston.transports.Console, {'timestamp': true});
for (let prop in endpoints.types) {
if (endpoints.types.hasOwnProperty(prop)) {
let ffmpegParams = endpoints.types[prop];
let bytes = 0;
app.post('/' + prop, function(req, res) {
let hitLimit = false;
let fileName = '';
let savedFile = uniqueFilename(__dirname + '/uploads/');
let busboy = new Busboy({
headers: req.headers,
limits: {
files: 1,
fileSize: consts.fileSizeLimit,
}});
busboy.on('filesLimit', function() {
winston.error(JSON.stringify({
type: 'filesLimit',
message: 'Upload file size limit hit',
}));
});
busboy.on('file', function(
fieldname,
file,
filename,
encoding,
mimetype
) {
file.on('limit', function(file) {
hitLimit = true;
let err = {file: filename, error: 'exceeds max size limit'};
err = JSON.stringify(err);
winston.error(err);
res.writeHead(500, {'Connection': 'close'});
res.end(err);
});
let log = {
file: filename,
encoding: encoding,
mimetype: mimetype,
};
winston.info(JSON.stringify(log));
file.on('data', function(data) {
bytes += data.length;
});
file.on('end', function(data) {
log.bytes = bytes;
winston.info(JSON.stringify(log));
});
fileName = filename;
winston.info(JSON.stringify({
action: 'Uploading',
name: fileName,
}));
let written = file.pipe(fs.createWriteStream(savedFile));
if (written) {
winston.info(JSON.stringify({
action: 'saved',
path: savedFile,
}));
}
});
busboy.on('finish', function() {
if (hitLimit) {
fs.unlinkSync(savedFile);
return;
}
winston.info(JSON.stringify({
action: 'upload complete',
name: fileName,
}));
let outputFile = savedFile + '.' + ffmpegParams.extension;
winston.info(JSON.stringify({
action: 'begin conversion',
from: savedFile,
to: outputFile,
}));
let ffmpegConvertCommand = ffmpeg(savedFile);
ffmpegConvertCommand
.renice(15)
.outputOptions(ffmpegParams.outputOptions)
.on('error', function(err) {
let log = JSON.stringify({
type: 'ffmpeg',
message: err,
});
winston.error(log);
fs.unlinkSync(savedFile);
res.writeHead(500, {'Connection': 'close'});
res.end(log);
})
.on('end', function() {
fs.unlinkSync(savedFile);
winston.info(JSON.stringify({
action: 'starting download to client',
file: savedFile,
}));
res.download(outputFile, null, function(err) {
if (err) {
winston.error(JSON.stringify({
type: 'download',
message: err,
}));
}
winston.info(JSON.stringify({
action: 'deleting',
file: outputFile,
}));
if (fs.unlinkSync(outputFile)) {
winston.info(JSON.stringify({
action: 'deleted',
file: outputFile,
}));
}
});
})
.save(outputFile);
});
return req.pipe(busboy);
});
}
}
require('express-readme')(app, {
filename: 'README.md',
routes: ['/', '/readme'],
});
const server = app.listen(consts.port, function() {
let host = server.address().address;
let port = server.address().port;
winston.info(JSON.stringify({
action: 'listening',
url: 'http://'+host+':'+port,
}));
});
server.on('connection', function(socket) {
winston.info(JSON.stringify({
action: 'new connection',
timeout: consts.timeout,
}));
socket.setTimeout(consts.timeout);
socket.server.timeout = consts.timeout;
server.keepAliveTimeout = consts.timeout;
});
app.use(function(req, res, next) {
res.status(404).send(JSON.stringify({error: 'route not available'})+'\n');
});

3
app/constants.js Normal file
View File

@ -0,0 +1,3 @@
exports.fileSizeLimit = 524288000;
exports.port = 3000;
exports.timeout = 3600000;

37
app/endpoints.js Normal file
View File

@ -0,0 +1,37 @@
exports.types = {
jpg: {
extension: 'jpg',
outputOptions: [
'-pix_fmt yuv422p',
],
},
m4a: {
extension: 'm4a',
outputOptions: [
'-codec:a libfdk_aac',
],
},
mp3: {
extension: 'mp3',
outputOptions: [
'-codec:a libmp3lame',
],
},
mp4: {
extension: 'mp4',
outputOptions: [
'-codec:v libx264',
'-profile:v high',
'-r 15',
'-crf 23',
'-preset ultrafast',
'-b:v 500k',
'-maxrate 500k',
'-bufsize 1000k',
'-vf scale=-2:640',
'-threads 8',
'-codec:a libfdk_aac',
'-b:a 128k',
],
},
};

25
package.json Normal file
View File

@ -0,0 +1,25 @@
{
"name": "node-ffmpeg-av",
"version": "1.0.0",
"description": "media conversion utilities",
"main": "index.js",
"author": {
"name": "Paul Visco",
"email": "paul.visco@gmail.com"
},
"license": "ISC",
"dependencies": {
"busboy": "^0.2.14",
"compression": "^1.7.2",
"express": "^4.16.3",
"express-readme": "0.0.5",
"fluent-ffmpeg": "^2.1.2",
"fs": "0.0.1-security",
"unique-filename": "^1.1.0",
"winston": "^2.3.1"
},
"devDependencies": {
"eslint": "^3.19.0",
"eslint-config-google": "^0.8.0"
}
}