com.flipzu.PostProcThread.java Source code

Java tutorial

Introduction

Here is the source code for com.flipzu.PostProcThread.java

Source

package com.flipzu;
/**
* Copyright 2011 Flipzu
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*  
*  Initial Release: Dario Rapisardi <dario@rapisardi.org>
*  
*/

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.TimerTask;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.json.JSONException;
import org.tritonus.share.sampled.file.TAudioFileFormat;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.amazonaws.services.s3.model.S3Object;
import com.soundcloud.api.ApiWrapper;
import com.soundcloud.api.Endpoints;
import com.soundcloud.api.Env;
import com.soundcloud.api.Http;
import com.soundcloud.api.Params;
import com.soundcloud.api.Request;
import com.soundcloud.api.Token;

/**
 * post processing tasks thread
 * (upload to Amazon S3, SoundCloud, etc)
 * @author Dario Rapisardi <dario@rapisardi.org>
 *
 */
public class PostProcThread extends TimerTask {

    private Debug debug = Debug.getInstance();

    private Broadcast bcast;

    public PostProcThread(Broadcast bcast) {
        this.bcast = bcast;
    }

    @Override
    public void run() {

        if (bcast == null) {
            debug.logPostProc("PostProcThread, run(), got NULL bcast!");
            return;
        }

        debug.logPostProc("PostProcThread, run() for " + bcast);

        Broadcast nowBcast = Shows.getInstance().getBroadcast(bcast.getUsername());

        /* if we're still live, exit */
        if (bcast.getState() == BroadcastState.LIVE) {
            debug.logPostProc("PostProcThread, " + bcast + "still LIVE");
            return;
        }

        /* check if we reconnected */
        if (bcast.getId().equals(nowBcast.getId())) {
            if (bcast.getUUID().equals(nowBcast.getUUID()) == false) {
                debug.logPostProc("PostProcThread for " + bcast + ", UUID MISMATCH orig UUID " + bcast.getUUID()
                        + " new " + nowBcast.getUUID() + " removing old one");
                Shows.getInstance().delBroadcast(bcast.getUUID());
                return;
            }

            if (nowBcast.getState() == BroadcastState.LIVE) {
                debug.logPostProc("PostProcThread, " + bcast + " reconnected");
                return;
            }

            //         if ( nowBcast.getStartTime().after(bcast.getStartTime())) {
            //            debug.logPostProc("PostProcThread, this was not the last broadcast for " + bcast);
            //            return;            
            //         }
        }

        /* in case we have a small reconnect... */
        if (bcast.getFilename() == null) {
            String dest = Config.getInstance().getFileWriterDestDir() + "/" + bcast.getId()
                    + Config.getInstance().getFileWriterExtension();
            bcast.setFilename(dest);

        }

        /* file operations, if we have a file :) */
        if (bcast.isStorage()) {
            // consolidate file if it was uploaded from another instance before
            if (Config.getInstance().useS3()) {
                consolidateS3(bcast);
            }

            // then upload, AS IS and ASAP
            if (Config.getInstance().useS3()) {
                // retry 5 times in case it fails...
                for (int i = 0; i < 5; i++) {
                    if (uploadToS3(bcast, false))
                        break;
                }

            }

            // cleanup, might take some time...
            ffmpegCleanUp(bcast);

            // upload to SoundCloud, if necessary
            if (bcast.isScShare()) {
                uploadToSoundCloud(bcast);
            }

            // upload cleaned file to S3
            if (Config.getInstance().useS3()) {
                // retry 5 times in case it fails...
                for (int i = 0; i < 5; i++) {
                    if (uploadToS3(bcast, true))
                        break;
                }
            }
        }

        // cleanup from Shows table.
        debug.logPostProc("PostProcThread, " + bcast + " not live, deleting.");
        Shows.getInstance().delBroadcast(bcast.getUUID());

    }

    private void ffmpegCleanUp(Broadcast bcast) {
        debug.logPostProc("PostProcThread, ffmpegCleanUp for " + bcast.getFilename());

        Process ffmpeg = null;
        String outputCodec = Config.getInstance().getOutputCodec();
        String inputFilename = bcast.getFilename();
        Integer outputBitrate = bcast.getBitrate();
        String outputFilename = Config.getInstance().getFileWriterDestDir() + "/" + bcast.getId() + "-postproc"
                + Config.getInstance().getFileWriterExtension();
        String ffmpegCmd = Config.getInstance().getFfmpegCommand();
        String inputFileFlag = Config.getInstance().getInputFileFlag();
        String outputBitrateFlag = Config.getInstance().getOutputBitrateFlag();
        String outputCodecFlag = Config.getInstance().getOutputCodecFlag();
        String outputFileFlag = Config.getInstance().getOutputFileFlag();

        try {
            ffmpeg = new ProcessBuilder(ffmpegCmd, inputFileFlag, inputFilename, outputBitrateFlag,
                    outputBitrate.toString(), outputCodecFlag, outputCodec, "-y", outputFileFlag, outputFilename)
                            .start();
        } catch (IOException e) {
            Debug.getInstance().logError("ffmpegClenup exception", e);
        }

        int retcode = -1;
        try {
            retcode = ffmpeg.waitFor();
        } catch (InterruptedException e) {
            Debug.getInstance().logError("ffmpegClenup exception", e);
        }

        if (retcode == 0) {
            File f = new File(inputFilename);
            debug.logPostProc("ffmpegCleanup, deleting " + inputFilename);
            f.delete();
            bcast.setFilename(outputFilename);
        }

        ffmpeg.destroy();

    }

    private boolean uploadToS3(Broadcast bcast, boolean delete) {
        debug.logPostProc("PostProcThread, S3 upload for " + bcast.getFilename());

        if (bcast.getFilename() == null) {
            debug.logPostProc("PostProcThread, uploadToS3, filename is null");
            return false;
        }

        File file = new File(bcast.getFilename());
        if (!file.exists()) {
            debug.logPostProc("PostProcThread, uploadToS3, " + bcast.getFilename() + " does not exist");
            return false;
        }

        AmazonS3 s3 = null;

        try {
            InputStream is = new FileInputStream("aws.properties");
            s3 = new AmazonS3Client(new PropertiesCredentials(is));
        } catch (Exception e) {
            Debug.getInstance().logError("uploadToS3 Error ", e);
            return false;
        }

        String bucketName = Config.getInstance().getS3Bucket();
        String dirName = Config.getInstance().getS3dir();
        String objName = dirName + "/" + bcast.getId() + Config.getInstance().getFileWriterExtension();

        PutObjectRequest po = new PutObjectRequest(bucketName, objName, file);

        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentType("audio/mpeg");
        po.setMetadata(metadata);
        po.setCannedAcl(CannedAccessControlList.PublicRead);

        try {
            s3.putObject(po);
        } catch (AmazonServiceException ase) {
            debug.logPostProc("Caught an AmazonServiceException, which means your request made it "
                    + "to Amazon S3, but was rejected with an error response for some reason.");
            debug.logPostProc("Error Message:    " + ase.getMessage());
            debug.logPostProc("HTTP Status Code: " + ase.getStatusCode());
            debug.logPostProc("AWS Error Code:   " + ase.getErrorCode());
            debug.logPostProc("Error Type:       " + ase.getErrorType());
            debug.logPostProc("Request ID:       " + ase.getRequestId());
            return false;

        } catch (AmazonClientException ace) {
            debug.logPostProc("Caught an AmazonClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with S3, "
                    + "such as not being able to access the network.");
            debug.logPostProc("Error Message: " + ace.getMessage());
            return false;
        }

        if (delete) {
            if (Config.getInstance().deleteSmallBcasts())
                /* check and remove empty/short broadcasts */
                cleanCrappyBroadcasts(bcast.getKey(), file);

            debug.logPostProc("uploadToS3, deleting file " + bcast.getFilename());
            file.delete();
        }

        return true;

    }

    private void consolidateS3(Broadcast bcast) {
        debug.logPostProc("PostProcThread, consolidate S3 for " + bcast);

        File file = new File(bcast.getFilename());
        if (!file.exists()) {
            debug.logPostProc("consolidateS3, empty broadcast, doing nothing");
            return;
        }

        AmazonS3 s3 = null;

        try {
            InputStream is = new FileInputStream("aws.properties");
            s3 = new AmazonS3Client(new PropertiesCredentials(is));
        } catch (Exception e) {
            debug.logError("consolidateS3 Error ", e);
            return;
        }

        String bucketName = Config.getInstance().getS3Bucket();
        String dirName = Config.getInstance().getS3dir();
        String objName = dirName + "/" + bcast.getId() + Config.getInstance().getFileWriterExtension();

        S3Object obj = null;
        try {
            obj = s3.getObject(bucketName, objName);
        } catch (AmazonServiceException ase) {
            debug.logPostProc("consolidateS3 for " + bcast + ". File not found, doing nothing...");
            return;
        } catch (AmazonClientException ace) {
            debug.logPostProc("consolidateS3 for " + bcast + ". File not found, doing nothing...");
            return;
        }

        if (obj == null) {
            debug.logPostProc("consolidateS3 for " + bcast + ". File not found, doing nothing.");
            return;
        }

        debug.logPostProc("consolidateS3 for " + bcast + ". File found, consolidating.");

        String auxFile = Config.getInstance().getFileWriterDestDir() + "/" + bcast.getId() + "-aux"
                + Config.getInstance().getFileWriterExtension();

        BufferedOutputStream bosAux = null;
        try {
            FileOutputStream fos = new FileOutputStream(auxFile);
            bosAux = new BufferedOutputStream(fos);
        } catch (FileNotFoundException e) {
            debug.logError("consolidateS3 for, error creating output stream", e);
            return;
        }

        BufferedInputStream is = new BufferedInputStream(obj.getObjectContent());

        /* fetch file from S3 */
        int r = 0;
        do {
            byte[] b = new byte[1024];
            try {
                r = is.read(b);
                if (r > 0)
                    bosAux.write(b, 0, r);
            } catch (IOException e) {
                debug.logError("consolidateS3 error", e);
                /* cleanup */
                File aFile = new File(auxFile);
                aFile.delete();
                return;
            }
        } while (r > 0);

        try {
            is.close();
        } catch (IOException e) {
            debug.logError("consolidateS3 error", e);
        }

        /* append our file to aux file */
        BufferedInputStream bis;
        try {
            FileInputStream fis = new FileInputStream(bcast.getFilename());
            bis = new BufferedInputStream(fis);
        } catch (FileNotFoundException e) {
            debug.logPostProc("consolidateS3 error, FileNotFoundException");
            return;
        }

        r = 0;
        do {
            byte[] b = new byte[1024];
            try {
                r = bis.read(b);
                bosAux.write(b);
            } catch (IOException e) {
                debug.logError("consolidateS3 error", e);
                return;
            }
        } while (r > 0);

        try {
            bis.close();
            bosAux.close();
        } catch (IOException e) {
            debug.logError("consolidateS3 error", e);
        }

        /* delete old crap */
        file.delete();

        bcast.setFilename(auxFile);

        debug.logPostProc("consolidateS3 for " + bcast + ". File consolidated in " + bcast.getFilename());

        return;
    }

    private boolean uploadToSoundCloud(Broadcast bcast) {

        if (bcast == null) {
            debug.logPostProc("uploadToSoundCloud, bcast is null");
            return false;
        }

        debug.logPostProc("uploadToSoundCloud for " + bcast.getFilename());

        if (bcast.getScToken() == null) {
            debug.logPostProc("uploadToSoundCloud, NULL TOKEN!");
            return false;
        }

        Token token = new Token(bcast.getScToken(), Token.ACCESS_TOKEN);
        debug.logPostProc("uploadToSoundCloud, authenticating with " + Config.getInstance().getSCKey() + " "
                + Config.getInstance().getSCSecret());
        ApiWrapper wrapper = new ApiWrapper(Config.getInstance().getSCKey(), Config.getInstance().getSCSecret(),
                null, token, Env.LIVE);

        File file = new File(bcast.getFilename());
        File artwork = new File(Config.getInstance().getSCArtwork());

        if (!file.exists()) {
            debug.logPostProc("uploaToSoundCloud: file " + bcast.getFilename() + " doesn't exist");
            return false;
        }

        String title = bcast.getTitle();
        if (title == null) {
            title = bcast.getUsername() + "'s recorded broadcast from Flipzu";
        }

        try {
            HttpResponse resp = wrapper.post(Request.to(Endpoints.TRACKS).add(Params.Track.TITLE, title)
                    .add(Params.Track.TAG_LIST, "Flipzu " + bcast.getUsername())
                    .add(Params.Track.DESCRIPTION,
                            "Live Broadcast from Flipzu - http://flipzu.com/" + bcast.getUsername())
                    .withFile(Params.Track.ARTWORK_DATA, artwork).withFile(Params.Track.ASSET_DATA, file));
            // you can add more parameters here, e.g.
            // .withFile(Params.Track.ARTWORK_DATA, file)) /* to add artwork */
            if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED) {
                debug.logPostProc("uploadToSoundCloud, 201 Created " + resp.getFirstHeader("Location").getValue());
                debug.logPostProc("uploadToSoundCloud " + Http.getJSON(resp).toString(4));
            } else {
                debug.logPostProc("uploadToSoundCloud, invalid status received " + resp.getStatusLine());
            }
        } catch (IOException e) {
            debug.logError("uploadToSoundCloud, IOException ", e);
            return false;
        } catch (JSONException e) {
            debug.logError("uploadToSoundCloud, JSONException ", e);
            return false;
        }

        return true;
    }

    private long getDurationWithMp3Spi(File file) {

        AudioFileFormat fileFormat;
        try {
            fileFormat = AudioSystem.getAudioFileFormat(file);
            if (fileFormat instanceof TAudioFileFormat) {
                Map<?, ?> properties = ((TAudioFileFormat) fileFormat).properties();
                String key = "duration";
                Long microseconds = (Long) properties.get(key);
                long sec = (microseconds / 1000000);
                return sec;
            } else {
                return 0;
            }
        } catch (UnsupportedAudioFileException e) {
            debug.logPostProc("getDurationWithMp3Spi UnsupportedAudioFile error ");
        } catch (IOException e) {
            debug.logPostProc("getDurationWithMp3Spi IOException error ");
        }
        return 0;
    }

    private void cleanCrappyBroadcasts(String key, File file) {
        Integer minLength = Config.getInstance().getMinimumLength();

        long secs = getDurationWithMp3Spi(file);

        if (secs <= minLength) {
            debug.logPostProc("cleanCrappyBroadcasts got " + secs + "<" + minLength + " secs, deleting...");

            FlipInterface fi = new FlipInterface();
            if (fi.deleteAircast(key)) {
                debug.logPostProc("cleanCrappyBroadcasts key " + key + " deleted OK");
            } else {
                debug.logPostProc("cleanCrappyBroadcasts key " + key + " NOT deleted?");
            }
        }

    }
}