14

I want to concatenate the files uploaded on Amazon S3 server. How can I do this.

Concatenation on local machine i can do using following code.

var fs = require('fs'),
    files = fs.readdirSync('./files'),
    clips = [],
    stream,
    currentfile,
    dhh = fs.createWriteStream('./concatfile.mp3');

files.forEach(function (file) {
    clips.push(file.substring(0, 6));  
});


function main() {
    if (!clips.length) {
        dhh.end("Done");
        return;
    }
    currentfile = './files/' + clips.shift() + '.mp3';
    stream = fs.createReadStream(currentfile);
    
    stream.pipe(dhh, {end: false});
    stream.on("end", function() {
        main();        
    });
}


main();
ghost...
  • 975
  • 3
  • 16
  • 33
  • 4
    You can't because you can't execute code on S3 servers. You need to download, concat, upload. – Andrey Jul 07 '15 at 12:27
  • 1
    concating files can be done in unix shell by using `cat` pipes `|` and greater than `>`. Putting together two mp3s is a different question. Please differentiate and describe your problem better. – firelynx Jul 07 '15 at 12:27
  • I have 10 mp3 files uploaded on Amazon S3 server. Now I want one single file as a concatenation of all 10 mp3. – ghost... Jul 07 '15 at 12:30
  • 2
    yeah concatenating content of mp3 is different business then just concatenating files – Andrey Jul 07 '15 at 12:37
  • ok....how to do this... – ghost... Jul 07 '15 at 12:42
  • 1
    Concatenating text files is one thing...you'd just download concat and then upload. However, binary media files, such as an MP3 cannot just be concatenated as that's actually a fairly complicated process. So is this question regarding MP3s or how to concatenate generic text files from S3. Please edit your question appropriately. – JNYRanger Jul 07 '15 at 12:43
  • @JNYRanger That's actually wrong. You can concatenate MPEG files together and they will work, provided its the same sample rate. However, some MP3 files have extra things added like ID3 tags which can make this tricky. So while it is best to use a tool like FFmpeg to do this, remember that MPEG can actually be spliced in this manner. – Brad Jul 07 '15 at 13:26
  • You might want to look into AWS lambda too: http://aws.amazon.com/documentation/lambda/ – Samar Jul 07 '15 at 13:36
  • @Brad to which extend just sticking mp3 will work? will it show current track length? And other headers. – Andrey Jul 07 '15 at 13:43
  • @Andrey Yep, all of that works. MP3 has a frame header in every frame which has everything the decoder needs. Your MP3 player doesn't actually know how long the MP3 (in time) file is until it decodes it. It estimates based on frame size and file byte length (which is plenty accurate for most purposes). – Brad Jul 07 '15 at 13:57
  • @Brad interesting, thanks – Andrey Jul 07 '15 at 14:14
  • 1
    Your code seems to be copied from here: http://blog.ragingflame.co.za/2013/5/31/using-nodejs-to-join-audio-files A credit/shoutout would be nice. – Rahat Mahbub Jul 11 '15 at 06:17
  • 2
    You can use the audiosprite node module to join mp3 files with ffmpeg. `https://github.com/tonistiigi/audiosprite`. As others have mentioned. you will generally need to download the mp3 files from S3 and then join them. Depending on your ultimate goal, it is possible to join and serve the joined file to your users without downloading to your server but doing the processing with AWS lambda based on a user event (e.g. `join button`). lambda and S3 are close together so no added double latency or extra server load. If that's what you'd like to know, please update the question. – Rahat Mahbub Jul 11 '15 at 06:45
  • 1
    @napalm Example: https://github.com/binoculars/aws-lambda-ffmpeg and imagemagick example: http://docs.aws.amazon.com/lambda/latest/dg/walkthrough-s3-events-adminuser-create-test-function-create-function.html – Rahat Mahbub Jul 17 '15 at 03:24
  • Cool, thanks for correcting me. Didn't know the boxes running Lambdas have `ffmpeg` installed. – adamkonrad Jul 17 '15 at 04:57

2 Answers2

6

You can achieve what you want by breaking it into two steps:

Manipulating files on s3

Since s3 is a remote file storage, you can't run code on s3 server to do the operation locally (as @Andrey mentioned). what you will need to do in your code is to fetch each input file, process them locally and upload the results back to s3. checkout the code examples from amazon:

var s3 = new AWS.S3();
var params = {Bucket: 'myBucket', Key: 'mp3-input1.mp3'};
var file = require('fs').createWriteStream('/path/to/input.mp3');
s3.getObject(params).createReadStream().pipe(file);

at this stage you'll run your concatenation code, and upload the results back:

var fs = require('fs');
var zlib = require('zlib');

var body = fs.createReadStream('bigfile.mp3').pipe(zlib.createGzip());
var s3obj = new AWS.S3({params: {Bucket: 'myBucket', Key: 'myKey'}});
s3obj.upload({Body: body}).
   on('httpUploadProgress', function(evt) { console.log(evt); }).
   send(function(err, data) { console.log(err, data) });

Merging two (or more) mp3 files

Since MP3 file include a header that specifies some information like bitrate, simply concatenating them together might introduce playback issues. See: https://stackoverflow.com/a/5364985/1265980

what you want to use a tool to that. you can have one approach of saving your input mp3 files in tmp folder, and executing an external program like to change the bitrate, contcatenate files and fix the header. alternatively you can use an library that allows you to use ffmpeg within node.js.

in their code example shown, you can see how their merge two files together within the node api.

ffmpeg('/path/to/part1.avi')
  .input('/path/to/part2.avi')
  .input('/path/to/part2.avi')
  .on('error', function(err) {
    console.log('An error occurred: ' + err.message);
  })
  .on('end', function() {
    console.log('Merging finished !');
  })
  .mergeToFile('/path/to/merged.avi', '/path/to/tempDir'); 
Community
  • 1
  • 1
Ereli
  • 965
  • 20
  • 34
2

Here's my quick take on the problem of downloading and processing S3 objects. My example is focused mostly on getting the data local and then processing it once it's all downloaded. I suggest you use one of the ffmpeg approaches mentioned above.

var RSVP = require('rsvp');

var s3 = new AWS.S3();
var bucket = '<your bucket name>';

var getFile = function(key, filePath) {
    return new RSVP.Promise(function(resolve, reject) {
        var file = require('fs').createWriteStream(filePath);
        if(!file) {
            reject('unable to open file');
        }

        s3.getObject({
            Bucket: bucket,
            Key: key
        }).on('httpData', function(chunk) {
            file.write(chunk);
        }).on('httpDone', function() {
            file.end();
            resolve(filePath);
        });
    });
};

var tempFiles = ['<local temp filename 1>', '<local temp filename 2>'];
var keys = ['<s3 object key 1>', '<s3 object key 2>'];
var promises = [];

for(var i = 0; i < keys.length; ++i) {
    var promise = getFile(keys[i], tempFiles[i]);
    promises.push(promise);
}

RSVP.all(promises).then(function(data) {
    //do something with your files
}).catch(function(error) {
    //handle errors
});
adamkonrad
  • 6,794
  • 1
  • 34
  • 41