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 app = express(); | ||||
| const Busboy = require('busboy'); | ||||
| const compression = require('compression'); | ||||
| const ffmpeg = require('fluent-ffmpeg'); | ||||
| const uniqueFilename = require('unique-filename'); | ||||
| const endpoints = require('./endpoints.js'); | ||||
| const all_routes = require('express-list-endpoints'); | ||||
| 
 | ||||
| const logger = require('./logger.js'); | ||||
| const logger = require('./utils/logger.js'); | ||||
| const constants = require('./constants.js'); | ||||
| 
 | ||||
| fileSizeLimit = constants.fileSizeLimit; | ||||
|  | @ -27,103 +23,22 @@ process.on('SIGTERM', handle); | |||
| 
 | ||||
| app.use(compression()); | ||||
| 
 | ||||
| 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('/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.`) | ||||
|             }); | ||||
| //routes to handle file upload for all POST methods
 | ||||
| var upload = require('./routes/uploadfile.js'); | ||||
| app.use(upload); | ||||
| 
 | ||||
|             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}`); | ||||
|                 }); | ||||
| //test route for development
 | ||||
| var test = require('./routes/test.js') | ||||
| app.use('/test', test) | ||||
| 
 | ||||
|                 fileName = 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}`) | ||||
|                 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}`); | ||||
| //routes to convert audio/video/image files to mp3/mp4/jpg
 | ||||
| var convert = require('./routes/convert.js') | ||||
| app.use('/convert', convert) | ||||
| 
 | ||||
|                             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, { | ||||
|     filename: 'index.md', | ||||
|     routes: ['/', '/readme'], | ||||
|     routes: ['/'], | ||||
| }); | ||||
| 
 | ||||
| 
 | ||||
|  | @ -140,6 +55,25 @@ server.on('connection', function(socket) { | |||
|     server.keepAliveTimeout = timeout; | ||||
| }); | ||||
| 
 | ||||
| app.use(function(req, res, next) { | ||||
|   res.status(404).send(JSON.stringify({error: 'route not found'})+'\n'); | ||||
| app.get('/endpoints', function(req, res) { | ||||
|     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 | ||||
| 
 | ||||
| 
 | ||||
| ### 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` | ||||
| 
 | ||||
| # Endpoints | ||||
| 
 | ||||
| [API endpoints](./endpoints) | ||||
|  |  | |||
|  | @ -15,6 +15,7 @@ | |||
|     "express": "^4.17.1", | ||||
|     "express-readme": "0.0.5", | ||||
|     "fluent-ffmpeg": "^2.1.2", | ||||
|     "express-list-endpoints": "4.0.1", | ||||
|     "fs": "0.0.1-security", | ||||
|     "unique-filename": "^1.1.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
	 Sami Salkosuo
						Sami Salkosuo