├── .gitignore ├── package.json ├── README.md ├── src ├── s3-util.js ├── child-process-promise.js └── index.js └── template.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | output.yml 4 | .vscode 5 | .idea 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "s3-lambda-ffmpeg-aac-wav-to-mp3-s3", 3 | "version": "1.0.0", 4 | "description": "A Serverless Application Repository Component for converting AAC,WAV files from one S3 bucket, into MP3 files to another S3 bucket.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "package": "aws cloudformation package --template-file template.yml --output-template-file output.yml --s3-bucket app-repo-components", 8 | "deploy": "aws cloudformation deploy --template-file output.yml --stack-name s3-lambda-ffmpeg-aac-wav-to-mp3-s3 --capabilities CAPABILITY_IAM", 9 | "qd": "npm run package && npm run deploy" 10 | }, 11 | "keywords": [], 12 | "author": "Aleksandar Simovic ", 13 | "license": "MIT" 14 | } 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # S3 Bucket -> Lambda (FFMPEG Convert AAC,WAV to MP3) -> S3 Bucket 3 | 4 | ## Description 5 | 6 | This is a serverless component that takes uploaded AAC, WAV audio files from one S3 Bucket, then using FFMPEG Lambda Layer converts them to MP3 and uploads to another S3 Bucket. It contains: 7 | 8 | - an Input S3 Bucket that accepts WAV,AAC audio files. 9 | 10 | - a Lambda that takes the WAV/AAC audio file from the Input S3 bucket, converts it to a MP3 one and uploads it to the Output bucket 11 | 12 | - an Output S3 Bucket that receives MP3 files. 13 | 14 | ## Deployment Parameters 15 | 16 | This component has one CloudFormation deployment parameter: 17 | 18 | - `ConversionTimeout`, an optional parameter, represents the timeout of the Conversion Lambda function. By default it's 180 seconds. 19 | - `QualityLevel`, a required parameter, represents the quality level for the output MP3. Its a range 0-9, where a lower value is a higher quality. By default it's 9 -> minimal quality. 20 | 21 | ## Latest Release - 1.0.0 22 | 23 | - Initial release. 24 | 25 | ## Roadmap - Upcoming changes 26 | 27 | Here are the upcoming changes that I'll add to this serverless component: 28 | 29 | - ESLint 30 | - Tests 31 | -------------------------------------------------------------------------------- /src/s3-util.js: -------------------------------------------------------------------------------- 1 | /*global module, require, Promise, console */ 2 | 3 | const aws = require('aws-sdk'), 4 | fs = require('fs'), 5 | s3 = new aws.S3(), 6 | downloadFileFromS3 = function (bucket, fileKey, filePath) { 7 | 'use strict'; 8 | console.log('downloading', bucket, fileKey, filePath); 9 | return new Promise(function (resolve, reject) { 10 | const file = fs.createWriteStream(filePath), 11 | stream = s3.getObject({ 12 | Bucket: bucket, 13 | Key: fileKey 14 | }).createReadStream(); 15 | stream.on('error', reject); 16 | file.on('error', reject); 17 | file.on('finish', function () { 18 | console.log('downloaded', bucket, fileKey); 19 | resolve(filePath); 20 | }); 21 | stream.pipe(file); 22 | }); 23 | }, uploadFileToS3 = function (bucket, fileKey, filePath, contentType) { 24 | 'use strict'; 25 | console.log('uploading', bucket, fileKey, filePath); 26 | return s3.upload({ 27 | Bucket: bucket, 28 | Key: fileKey, 29 | Body: fs.createReadStream(filePath), 30 | ACL: 'private', 31 | ContentType: contentType 32 | }).promise(); 33 | }; 34 | 35 | module.exports = { 36 | downloadFileFromS3: downloadFileFromS3, 37 | uploadFileToS3: uploadFileToS3 38 | }; -------------------------------------------------------------------------------- /src/child-process-promise.js: -------------------------------------------------------------------------------- 1 | /*global module, require, console, Promise */ 2 | 'use strict'; 3 | const childProcess = require('child_process'), 4 | execPromise = function (command) { 5 | return new Promise((resolve, reject) => { 6 | childProcess.exec(command, (err, result) => { 7 | if (err) { 8 | reject(err); 9 | } else { 10 | resolve(result && String(result).trim()); 11 | } 12 | }); 13 | }); 14 | }, 15 | spawnPromise = function (command, argsarray, envOptions) { 16 | return new Promise((resolve, reject) => { 17 | console.log('executing', command, argsarray.join(' ')); 18 | const childProc = childProcess.spawn(command, argsarray, envOptions || {env: process.env, cwd: process.cwd()}), 19 | resultBuffers = []; 20 | childProc.stdout.on('data', buffer => { 21 | console.log(buffer.toString()); 22 | resultBuffers.push(buffer); 23 | }); 24 | childProc.stderr.on('data', buffer => console.error(buffer.toString())); 25 | childProc.on('exit', (code, signal) => { 26 | console.log(`${command} completed with ${code}:${signal}`); 27 | if (code !== 0) { 28 | reject(`${command} failed with ${code || signal}`); 29 | } else { 30 | resolve(Buffer.concat(resultBuffers).toString().trim()); 31 | } 32 | }); 33 | }); 34 | }; 35 | module.exports = { 36 | exec: execPromise, 37 | spawn: spawnPromise 38 | }; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const s3Util = require('./s3-util'), 2 | childProcessPromise = require('./child-process-promise'), 3 | path = require('path'), 4 | os = require('os'), 5 | EXTENSION = process.env.EXTENSION, 6 | OUTPUT_BUCKET = process.env.OUTPUT_BUCKET, 7 | QUALITY_SCALE = process.env.QUALITY_SCALE, 8 | MIME_TYPE = process.env.MIME_TYPE; 9 | 10 | exports.handler = function (eventObject, context) { 11 | const eventRecord = eventObject.Records && eventObject.Records[0], 12 | inputBucket = eventRecord.s3.bucket.name, 13 | key = eventRecord.s3.object.key, 14 | id = context.awsRequestId, 15 | resultKey = key.replace(/\.[^.]+$/, EXTENSION), 16 | workdir = os.tmpdir(), 17 | inputFile = path.join(workdir, id + path.extname(key)), 18 | outputFile = path.join(workdir, 'converted-' + id + EXTENSION), 19 | qualityScale = !isNaN(QUALITY_SCALE) ? (QUALITY_SCALE > 9 ? 9 : QUALITY_SCALE) : 9; 20 | 21 | console.log(qualityScale) 22 | 23 | 24 | console.log('converting', inputBucket, key, 'using', inputFile); 25 | return s3Util.downloadFileFromS3(inputBucket, key, inputFile) 26 | .then(() => childProcessPromise.spawn( 27 | '/opt/bin/ffmpeg', 28 | ['-loglevel', 'error', '-y', '-i', inputFile,'-codec:a', 'libmp3lame','-q:a', qualityScale, '-f', 'mp3', outputFile], 29 | {env: process.env, cwd: workdir} 30 | )) 31 | .then(() => s3Util.uploadFileToS3(OUTPUT_BUCKET, resultKey, outputFile, MIME_TYPE)); 32 | }; 33 | -------------------------------------------------------------------------------- /template.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Transform: AWS::Serverless-2016-10-31 3 | Description: A serverless component for conversion from AAC,WAV to MP3 4 | Parameters: 5 | ConversionTimeout: 6 | Type: Number 7 | Default: 180 8 | QualityLevel: 9 | Type: String 10 | Default: 9 11 | Description: 'The level of quality for your mp3 file, 0-9' 12 | AllowedPattern: '[0-9]' 13 | InputS3BucketName: 14 | Type: String 15 | Default: 's3-lambda-ffmpeg-input-aac-wav-bucket' 16 | MinLength: 3 17 | OutputS3BucketName: 18 | Type: String 19 | Default: 's3-lambda-ffmpeg-output-mp3-bucket' 20 | MinLength: 3 21 | Resources: 22 | InputS3Bucket: 23 | Type: AWS::S3::Bucket 24 | Properties: 25 | BucketName: !Ref InputS3BucketName 26 | ConvertFileFunction: 27 | Type: AWS::Serverless::Function 28 | Properties: 29 | Handler: index.handler 30 | Timeout: !Ref ConversionTimeout 31 | MemorySize: 1024 32 | Runtime: nodejs8.10 33 | CodeUri: src/ 34 | Layers: 35 | - 'arn:aws:lambda:us-east-1:145266761615:layer:ffmpeg:4' 36 | Policies: 37 | - S3CrudPolicy: 38 | BucketName: !Ref OutputS3BucketName 39 | - S3ReadPolicy: 40 | BucketName: !Ref InputS3BucketName 41 | Environment: 42 | Variables: 43 | OUTPUT_BUCKET: !Ref OutputS3Bucket 44 | EXTENSION: '.mp3' 45 | QUALITY_LEVEL: !Ref QualityLevel 46 | MIME_TYPE: 'audio/mpeg' 47 | Events: 48 | FileUpload: 49 | Type: S3 50 | Properties: 51 | Bucket: !Ref InputS3Bucket 52 | Events: s3:ObjectCreated:* 53 | OutputS3Bucket: 54 | Type: AWS::S3::Bucket 55 | Properties: 56 | BucketName: !Ref OutputS3BucketName 57 | 58 | Outputs: 59 | InputS3Bucket: 60 | Description: Input S3 bucket 61 | Value: !Ref InputS3Bucket 62 | OutputS3Bucket: 63 | Description: Output S3 bucket 64 | Value: !Ref OutputS3Bucket 65 | --------------------------------------------------------------------------------