major refactoring
This commit is contained in:
parent
9012927cd8
commit
1db51d5776
132
src/app.js
132
src/app.js
|
@ -1,13 +1,9 @@
|
||||||
const fs = require('fs');
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
const Busboy = require('busboy');
|
|
||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
const ffmpeg = require('fluent-ffmpeg');
|
const all_routes = require('express-list-endpoints');
|
||||||
const uniqueFilename = require('unique-filename');
|
|
||||||
const endpoints = require('./endpoints.js');
|
|
||||||
|
|
||||||
const logger = require('./logger.js');
|
const logger = require('./utils/logger.js');
|
||||||
const constants = require('./constants.js');
|
const constants = require('./constants.js');
|
||||||
|
|
||||||
fileSizeLimit = constants.fileSizeLimit;
|
fileSizeLimit = constants.fileSizeLimit;
|
||||||
|
@ -27,103 +23,22 @@ process.on('SIGTERM', handle);
|
||||||
|
|
||||||
app.use(compression());
|
app.use(compression());
|
||||||
|
|
||||||
for (let prop in endpoints.types) {
|
//routes to handle file upload for all POST methods
|
||||||
if (endpoints.types.hasOwnProperty(prop)) {
|
var upload = require('./routes/uploadfile.js');
|
||||||
let ffmpegParams = endpoints.types[prop];
|
app.use(upload);
|
||||||
let bytes = 0;
|
|
||||||
app.post('/' + prop, function(req, res) {
|
|
||||||
let hitLimit = false;
|
|
||||||
let fileName = '';
|
|
||||||
let savedFile = uniqueFilename('/tmp/');
|
|
||||||
let busboy = new Busboy({
|
|
||||||
headers: req.headers,
|
|
||||||
limits: {
|
|
||||||
files: 1,
|
|
||||||
fileSize: fileSizeLimit,
|
|
||||||
}});
|
|
||||||
busboy.on('filesLimit', function() {
|
|
||||||
logger.error(`upload file size limit hit. max file size ${fileSizeLimit} bytes.`)
|
|
||||||
});
|
|
||||||
|
|
||||||
busboy.on('file', function(
|
//test route for development
|
||||||
fieldname,
|
var test = require('./routes/test.js')
|
||||||
file,
|
app.use('/test', test)
|
||||||
filename,
|
|
||||||
encoding,
|
|
||||||
mimetype
|
|
||||||
) {
|
|
||||||
file.on('limit', function(file) {
|
|
||||||
hitLimit = true;
|
|
||||||
let msg = `${filename} exceeds max size limit. max file size ${fileSizeLimit} bytes.`
|
|
||||||
logger.error(msg);
|
|
||||||
res.writeHead(500, {'Connection': 'close'});
|
|
||||||
res.end(JSON.stringify({error: msg}));
|
|
||||||
});
|
|
||||||
let log = {
|
|
||||||
file: filename,
|
|
||||||
encoding: encoding,
|
|
||||||
mimetype: mimetype,
|
|
||||||
};
|
|
||||||
logger.debug(`file:${log.file}, encoding: ${log.encoding}, mimetype: ${log.mimetype}`);
|
|
||||||
file.on('data', function(data) {
|
|
||||||
bytes += data.length;
|
|
||||||
});
|
|
||||||
file.on('end', function(data) {
|
|
||||||
log.bytes = bytes;
|
|
||||||
logger.debug(`file: ${log.file}, encoding: ${log.encoding}, mimetype: ${log.mimetype}, bytes: ${log.bytes}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
fileName = filename;
|
//routes to convert audio/video/image files to mp3/mp4/jpg
|
||||||
logger.debug(`uploading ${fileName}`)
|
var convert = require('./routes/convert.js')
|
||||||
let written = file.pipe(fs.createWriteStream(savedFile));
|
app.use('/convert', convert)
|
||||||
if (written) {
|
|
||||||
logger.debug(`${fileName} saved, path: ${savedFile}`)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
busboy.on('finish', function() {
|
|
||||||
if (hitLimit) {
|
|
||||||
fs.unlinkSync(savedFile);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.debug(`upload complete. file: ${fileName}`)
|
|
||||||
let outputFile = savedFile + '.' + ffmpegParams.extension;
|
|
||||||
logger.debug(`begin conversion from ${savedFile} to ${outputFile}`)
|
|
||||||
|
|
||||||
//ffmpeg processing...
|
|
||||||
let ffmpegConvertCommand = ffmpeg(savedFile);
|
|
||||||
ffmpegConvertCommand
|
|
||||||
.renice(15)
|
|
||||||
.outputOptions(ffmpegParams.outputOptions)
|
|
||||||
.on('error', function(err) {
|
|
||||||
logger.error(`${err}`);
|
|
||||||
fs.unlinkSync(savedFile);
|
|
||||||
res.writeHead(500, {'Connection': 'close'});
|
|
||||||
res.end(JSON.stringify({error: `${err}`}));
|
|
||||||
})
|
|
||||||
.on('end', function() {
|
|
||||||
fs.unlinkSync(savedFile);
|
|
||||||
logger.debug(`starting download to client ${savedFile}`);
|
|
||||||
|
|
||||||
res.download(outputFile, null, function(err) {
|
|
||||||
if (err) {
|
|
||||||
logger.error(`download ${err}`);
|
|
||||||
}
|
|
||||||
logger.debug(`deleting ${outputFile}`);
|
|
||||||
if (fs.unlinkSync(outputFile)) {
|
|
||||||
logger.debug(`deleted ${outputFile}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.save(outputFile);
|
|
||||||
});
|
|
||||||
return req.pipe(busboy);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
require('express-readme')(app, {
|
require('express-readme')(app, {
|
||||||
filename: 'index.md',
|
filename: 'index.md',
|
||||||
routes: ['/', '/readme'],
|
routes: ['/'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -140,6 +55,25 @@ server.on('connection', function(socket) {
|
||||||
server.keepAliveTimeout = timeout;
|
server.keepAliveTimeout = timeout;
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(function(req, res, next) {
|
app.get('/endpoints', function(req, res) {
|
||||||
res.status(404).send(JSON.stringify({error: 'route not found'})+'\n');
|
let code = 200;
|
||||||
|
res.writeHead(code, {'content-type' : 'text/plain'});
|
||||||
|
res.end("Endpoints:\n\n"+JSON.stringify(all_routes(app),null,2)+'\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.use(function(req, res, next) {
|
||||||
|
res.status(404).send({error: 'route not found'});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//custom error handler to return text/plain and message only
|
||||||
|
app.use(function(err, req, res, next){
|
||||||
|
let code = err.statusCode || 500;
|
||||||
|
let message = err.message;
|
||||||
|
res.writeHead(code, {'content-type' : 'text/plain'});
|
||||||
|
res.end(`${err.message}\n`);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
logger.debug(JSON.stringify(all_routes(app)));
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
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',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
18
src/index.md
18
src/index.md
|
@ -11,20 +11,6 @@ Based on:
|
||||||
- https://github.com/fluent-ffmpeg/node-fluent-ffmpeg
|
- https://github.com/fluent-ffmpeg/node-fluent-ffmpeg
|
||||||
|
|
||||||
|
|
||||||
### Endpoints
|
# 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 /` - Web Service Readme, this file
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
- `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`
|
|
||||||
|
|
||||||
|
|
||||||
|
[API endpoints](./endpoints)
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-readme": "0.0.5",
|
"express-readme": "0.0.5",
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
|
"express-list-endpoints": "4.0.1",
|
||||||
"fs": "0.0.1-security",
|
"fs": "0.0.1-security",
|
||||||
"unique-filename": "^1.1.1",
|
"unique-filename": "^1.1.1",
|
||||||
"winston": "^3.2.1"
|
"winston": "^3.2.1"
|
||||||
|
|
105
src/routes/convert.js
Normal file
105
src/routes/convert.js
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
var express = require('express')
|
||||||
|
const fs = require('fs');
|
||||||
|
const ffmpeg = require('fluent-ffmpeg');
|
||||||
|
|
||||||
|
var router = express.Router()
|
||||||
|
const logger = require('../utils/logger.js')
|
||||||
|
|
||||||
|
|
||||||
|
//routes for /convert
|
||||||
|
//adds conversion type and format to res.locals. to be used in final post function
|
||||||
|
router.post('/audio/to/mp3', function (req, res,next) {
|
||||||
|
|
||||||
|
res.locals.conversion="audio"
|
||||||
|
res.locals.format="mp3"
|
||||||
|
next()
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/video/to/mp4', function (req, res,next) {
|
||||||
|
|
||||||
|
res.locals.conversion="video"
|
||||||
|
res.locals.format="mp4"
|
||||||
|
next()
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/image/to/jpg', function (req, res,next) {
|
||||||
|
|
||||||
|
res.locals.conversion="image"
|
||||||
|
res.locals.format="jpg"
|
||||||
|
next()
|
||||||
|
});
|
||||||
|
|
||||||
|
// convert audio or video or image to mp3 or mp4 or jpg
|
||||||
|
router.post('*', function (req, res,next) {
|
||||||
|
let format = res.locals.format;
|
||||||
|
let conversion = res.locals.conversion;
|
||||||
|
logger.debug(`path: ${req.path}, conversion: ${conversion}, format: ${format}`);
|
||||||
|
if (conversion == undefined || format == undefined)
|
||||||
|
{
|
||||||
|
res.status(400).send("Invalid convert URL. Use one of: /convert/image/to/jpg, /convert/audio/to/mp3 or /convert/video/to/mp4.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ffmpegParams ={
|
||||||
|
extension: format
|
||||||
|
};
|
||||||
|
if (conversion == "image")
|
||||||
|
{
|
||||||
|
ffmpegParams.outputOptions= ['-pix_fmt yuv422p']
|
||||||
|
}
|
||||||
|
if (conversion == "audio")
|
||||||
|
{
|
||||||
|
ffmpegParams.outputOptions=['-codec:a libmp3lame' ]
|
||||||
|
}
|
||||||
|
if (conversion == "video")
|
||||||
|
{
|
||||||
|
ffmpegParams.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',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
let savedFile = res.locals.savedFile;
|
||||||
|
let outputFile = savedFile + '-output.' + ffmpegParams.extension;
|
||||||
|
logger.debug(`begin conversion from ${savedFile} to ${outputFile}`)
|
||||||
|
|
||||||
|
//ffmpeg processing... converting file...
|
||||||
|
let ffmpegConvertCommand = ffmpeg(savedFile);
|
||||||
|
ffmpegConvertCommand
|
||||||
|
.renice(15)
|
||||||
|
.outputOptions(ffmpegParams.outputOptions)
|
||||||
|
.on('error', function(err) {
|
||||||
|
logger.error(`${err}`);
|
||||||
|
fs.unlinkSync(savedFile);
|
||||||
|
res.writeHead(500, {'Connection': 'close'});
|
||||||
|
res.end(JSON.stringify({error: `${err}`}));
|
||||||
|
})
|
||||||
|
.on('end', function() {
|
||||||
|
fs.unlinkSync(savedFile);
|
||||||
|
logger.debug(`starting download to client ${savedFile}`);
|
||||||
|
|
||||||
|
res.download(outputFile, null, function(err) {
|
||||||
|
if (err) {
|
||||||
|
logger.error(`download ${err}`);
|
||||||
|
}
|
||||||
|
logger.debug(`deleting ${outputFile}`);
|
||||||
|
if (fs.unlinkSync(outputFile)) {
|
||||||
|
logger.debug(`deleted ${outputFile}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.save(outputFile);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router
|
22
src/routes/test.js
Normal file
22
src/routes/test.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
var express = require('express')
|
||||||
|
|
||||||
|
|
||||||
|
var router = express.Router()
|
||||||
|
const logger = require('../utils/logger.js')
|
||||||
|
|
||||||
|
//route to handle file upload in all POST requests
|
||||||
|
|
||||||
|
|
||||||
|
// convert audio or video or image to mp3 or mp4 or jpg
|
||||||
|
router.post("/",function (req, res,next) {
|
||||||
|
logger.debug("path: " + req.path);
|
||||||
|
logger.debug("req.params: ");
|
||||||
|
for (const key in req.params) {
|
||||||
|
logger.debug(`${key}: ${req.params[key]}`);
|
||||||
|
}
|
||||||
|
logger.debug("res.locals.savedFile: " + res.locals.savedFile);
|
||||||
|
res.status(200).send("Test OK.");
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
88
src/routes/uploadfile.js
Normal file
88
src/routes/uploadfile.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
var express = require('express')
|
||||||
|
const fs = require('fs');
|
||||||
|
const Busboy = require('busboy');
|
||||||
|
const uniqueFilename = require('unique-filename');
|
||||||
|
|
||||||
|
var router = express.Router()
|
||||||
|
const logger = require('../utils/logger.js')
|
||||||
|
|
||||||
|
//route to handle file upload in all POST requests
|
||||||
|
router.use(function (req, res,next) {
|
||||||
|
|
||||||
|
if(req.method == "POST")
|
||||||
|
{
|
||||||
|
logger.debug(`${__filename} path: ${req.path}`);
|
||||||
|
|
||||||
|
let bytes = 0;
|
||||||
|
let hitLimit = false;
|
||||||
|
let fileName = '';
|
||||||
|
var savedFile = uniqueFilename('/tmp/');
|
||||||
|
let busboy = new Busboy({
|
||||||
|
headers: req.headers,
|
||||||
|
limits: {
|
||||||
|
fields: 0, //no non-files allowed
|
||||||
|
files: 1,
|
||||||
|
fileSize: fileSizeLimit,
|
||||||
|
}});
|
||||||
|
busboy.on('filesLimit', function() {
|
||||||
|
logger.error(`upload file size limit hit. max file size ${fileSizeLimit} bytes.`)
|
||||||
|
});
|
||||||
|
busboy.on('fieldsLimit', function() {
|
||||||
|
let msg="Non-file field detected. Only files can be POSTed.";
|
||||||
|
logger.error(msg);
|
||||||
|
let err = new Error(msg);
|
||||||
|
err.statusCode = 400;
|
||||||
|
next(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
busboy.on('file', function(
|
||||||
|
fieldname,
|
||||||
|
file,
|
||||||
|
filename,
|
||||||
|
encoding,
|
||||||
|
mimetype
|
||||||
|
) {
|
||||||
|
file.on('limit', function(file) {
|
||||||
|
hitLimit = true;
|
||||||
|
let msg = `${filename} exceeds max size limit. max file size ${fileSizeLimit} bytes.`
|
||||||
|
logger.error(msg);
|
||||||
|
res.writeHead(500, {'Connection': 'close'});
|
||||||
|
res.end(JSON.stringify({error: msg}));
|
||||||
|
});
|
||||||
|
let log = {
|
||||||
|
file: filename,
|
||||||
|
encoding: encoding,
|
||||||
|
mimetype: mimetype,
|
||||||
|
};
|
||||||
|
logger.debug(`file:${log.file}, encoding: ${log.encoding}, mimetype: ${log.mimetype}`);
|
||||||
|
file.on('data', function(data) {
|
||||||
|
bytes += data.length;
|
||||||
|
});
|
||||||
|
file.on('end', function(data) {
|
||||||
|
log.bytes = bytes;
|
||||||
|
logger.debug(`file: ${log.file}, encoding: ${log.encoding}, mimetype: ${log.mimetype}, bytes: ${log.bytes}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileName = filename;
|
||||||
|
savedFile = savedFile + "-" + fileName;
|
||||||
|
logger.debug(`uploading ${fileName}`)
|
||||||
|
let written = file.pipe(fs.createWriteStream(savedFile));
|
||||||
|
if (written) {
|
||||||
|
logger.debug(`${fileName} saved, path: ${savedFile}`)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
busboy.on('finish', function() {
|
||||||
|
if (hitLimit) {
|
||||||
|
fs.unlinkSync(savedFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.debug(`upload complete. file: ${fileName}`)
|
||||||
|
res.locals.savedFile = savedFile;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
return req.pipe(busboy);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
Loading…
Reference in New Issue
Block a user