org.openmeetings.app.data.flvrecord.converter.FlvInterviewConverter.java Source code

Java tutorial

Introduction

Here is the source code for org.openmeetings.app.data.flvrecord.converter.FlvInterviewConverter.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
package org.openmeetings.app.data.flvrecord.converter;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import org.openmeetings.app.OpenmeetingsVariables;
import org.openmeetings.app.data.flvrecord.FlvRecordingDaoImpl;
import org.openmeetings.app.data.flvrecord.FlvRecordingLogDaoImpl;
import org.openmeetings.app.data.flvrecord.FlvRecordingMetaDataDaoImpl;
import org.openmeetings.app.documents.GenerateThumbs;
import org.openmeetings.app.persistence.beans.flvrecord.FlvRecording;
import org.openmeetings.app.persistence.beans.flvrecord.FlvRecordingMetaData;
import org.openmeetings.app.remote.red5.ScopeApplicationAdapter;
import org.openmeetings.utils.ProcessHelper;
import org.red5.logging.Red5LoggerFactory;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;

public class FlvInterviewConverter extends BaseConverter {
    private int leftSideLoud = 1;
    private int rightSideLoud = 1;
    private Integer leftSideTime = 0;
    private Integer rightSideTime = 0;

    private static final Logger log = Red5LoggerFactory.getLogger(FlvInterviewConverter.class,
            OpenmeetingsVariables.webAppRootKey);

    // Spring loaded Beans
    @Autowired
    private FlvRecordingDaoImpl flvRecordingDaoImpl = null;
    @Autowired
    private FlvRecordingMetaDataDaoImpl flvRecordingMetaDataDaoImpl = null;
    @Autowired
    private FlvRecordingLogDaoImpl flvRecordingLogDaoImpl;
    @Autowired
    private GenerateThumbs generateThumbs;

    public void startReConversion(Long flvRecordingId, Integer leftSideLoud, Integer rightSideLoud,
            Integer leftSideTime, Integer rightSideTime) {

        log.debug("++++++++++++ leftSideLoud :: " + leftSideLoud);
        log.debug("++++++++++++ rightSideLoud :: " + rightSideLoud);

        this.leftSideLoud += leftSideLoud;
        this.rightSideLoud += rightSideLoud;

        this.leftSideTime = leftSideTime;
        this.rightSideTime = rightSideTime;

        log.debug("++++++++++++ this.leftSideLoud :: " + this.leftSideLoud);
        log.debug("++++++++++++ this.rightSideLoud :: " + this.rightSideLoud);
        log.debug("++++++++++++ this.leftSideTime :: " + this.leftSideTime);
        log.debug("++++++++++++ this.rightSideTime :: " + this.rightSideTime);
        startConversion(flvRecordingId, true);
    }

    public void startConversion(Long flvRecordingId) {
        startConversion(flvRecordingId, false);
    }

    public void startConversion(Long flvRecordingId, boolean reconversion) {
        try {

            FlvRecording flvRecording = this.flvRecordingDaoImpl.getFlvRecordingById(flvRecordingId);
            log.debug("flvRecording " + flvRecording.getFlvRecordingId());

            // Strip Audio out of all Audio-FLVs
            stripAudioFromFLVs(flvRecording, reconversion);

            // Add empty pieces at the beginning and end of the wav

        } catch (Exception err) {
            log.error("[startConversion]", err);
        }
    }

    private String[] mergeAudioToWaves(List<String> listOfFullWaveFiles, String outputFullWav,
            List<FlvRecordingMetaData> metaDataList) {
        String[] argv_full_sox = new String[listOfFullWaveFiles.size() + 5];
        argv_full_sox[0] = this.getPathToSoX();
        argv_full_sox[1] = "-m";

        int counter = 2;
        for (int i = 0; i < listOfFullWaveFiles.size(); i++) {
            for (FlvRecordingMetaData flvRecordingMetaData : metaDataList) {
                String hashFileFullNameStored = flvRecordingMetaData.getFullWavAudioData();

                String fullFilePath = listOfFullWaveFiles.get(i);
                String fileNameOnly = new File(fullFilePath).getName();

                if (hashFileFullNameStored.equals(fileNameOnly)) {
                    if (flvRecordingMetaData.getInteriewPodId() == 1) {
                        argv_full_sox[counter] = "-v " + this.leftSideLoud;
                        counter++;
                    }
                    if (flvRecordingMetaData.getInteriewPodId() == 2) {
                        argv_full_sox[counter] = "-v " + this.rightSideLoud;
                        counter++;
                    }
                }
            }
            argv_full_sox[counter] = listOfFullWaveFiles.get(i);
            counter++;
        }

        argv_full_sox[counter] = outputFullWav;

        return argv_full_sox;
    }

    public void stripAudioFromFLVs(FlvRecording flvRecording, boolean reconversion) {
        List<HashMap<String, String>> returnLog = new LinkedList<HashMap<String, String>>();
        List<String> listOfFullWaveFiles = new LinkedList<String>();
        String streamFolderName = getStreamFolderName(flvRecording);
        List<FlvRecordingMetaData> metaDataList = flvRecordingMetaDataDaoImpl
                .getFlvRecordingMetaDataAudioFlvsByRecording(flvRecording.getFlvRecordingId());

        stripAudioFirstPass(flvRecording, returnLog, listOfFullWaveFiles, streamFolderName, metaDataList);
        try {
            // Merge Wave to Full Length
            String streamFolderGeneralName = getStreamFolderName();

            String hashFileFullName = "INTERVIEW_" + flvRecording.getFlvRecordingId() + "_FINAL_WAVE.wav";
            String outputFullWav = streamFolderName + hashFileFullName;
            deleteFileIfExists(outputFullWav);

            if (listOfFullWaveFiles.size() == 1) {

                outputFullWav = listOfFullWaveFiles.get(0);

            } else if (listOfFullWaveFiles.size() > 0) {
                String[] argv_full_sox;
                if (reconversion) {
                    argv_full_sox = mergeAudioToWaves(listOfFullWaveFiles, outputFullWav, metaDataList);
                } else {
                    argv_full_sox = mergeAudioToWaves(listOfFullWaveFiles, outputFullWav);
                }

                log.debug("START mergeAudioToWaves ################# ");
                log.debug(argv_full_sox.toString());
                String iString = "";
                for (int i = 0; i < argv_full_sox.length; i++) {
                    iString += argv_full_sox[i] + " ";
                }
                log.debug(iString);
                log.debug("END mergeAudioToWaves ################# ");

                returnLog.add(ProcessHelper.executeScript("mergeWave", argv_full_sox));
            } else {

                // create default Audio to merge it.
                // strip to content length
                String outputWav = streamFolderGeneralName + "one_second.wav";

                // Calculate delta at beginning
                Long deltaTimeMilliSeconds = flvRecording.getRecordEnd().getTime()
                        - flvRecording.getRecordStart().getTime();
                Float deltaPadding = (Float.parseFloat(deltaTimeMilliSeconds.toString()) / 1000) - 1;

                String[] argv_full_sox = new String[] { this.getPathToSoX(), outputWav, outputFullWav, "pad", "0",
                        deltaPadding.toString() };

                log.debug("START generateSampleAudio ################# ");
                String tString = "";
                for (int i = 0; i < argv_full_sox.length; i++) {
                    tString += argv_full_sox[i] + " ";
                }
                log.debug(tString);
                log.debug("END generateSampleAudio ################# ");

                returnLog.add(ProcessHelper.executeScript("mergeWave", argv_full_sox));

            }

            // Merge Audio with Video / Calculate resulting FLV

            // Start extracting image sequence
            int frameRate = 25;

            for (FlvRecordingMetaData flvRecordingMetaData : metaDataList) {

                // FLV to 24 FPS Sequence AVI
                String inputFlv = streamFolderName + flvRecordingMetaData.getStreamName() + ".flv";

                File inputFlvFile = new File(inputFlv);

                if (inputFlvFile.exists()) {
                    // TO Image Sequence

                    String outputMetaImageData = streamFolderName + flvRecordingMetaData.getFlvRecordingMetaDataId()
                            + File.separatorChar;

                    // Image Folder
                    File imageSequenceFolder = new File(outputMetaImageData);
                    imageSequenceFolder.mkdir();

                    String outputImages = outputMetaImageData + "image%d.png";

                    String[] argv_imageSeq = new String[] { this.getPathToFFMPEG(), "-i", inputFlv, "-r",
                            "" + frameRate, "-s", "320x240", outputImages };

                    log.debug("START generateImageSequence ################# ");
                    String iString = "";
                    for (int i = 0; i < argv_imageSeq.length; i++) {
                        iString += argv_imageSeq[i] + " ";
                    }
                    log.debug(iString);
                    log.debug("END generateImageSequence ################# ");

                    returnLog.add(ProcessHelper.executeScript("generateImageSequence", argv_imageSeq));

                }

            }

            // Default Image for empty interview video pods
            String defaultInterviewImage = streamFolderGeneralName + "default_interview_image.png";
            File defaultInterviewImageFile = new File(defaultInterviewImage);

            if (!defaultInterviewImageFile.exists()) {
                throw new Exception("defaultInterviewImageFile does not exist!");
            }

            // Create Folder for the output Image Sequence
            String outputImageMergedData = streamFolderName + "INTERVIEW_" + flvRecording.getFlvRecordingId()
                    + File.separatorChar;

            // Merged Image Folder
            File outputImageMergedDateFolder = new File(outputImageMergedData);
            outputImageMergedDateFolder.mkdir();

            // Generate the Single Image by sequencing
            boolean jobRunning = true;
            long currentTimeInMilliSeconds = 0;

            long completeLengthInSeconds = flvRecording.getRecordEnd().getTime()
                    - flvRecording.getRecordStart().getTime();

            log.debug("completeLengthInSeconds :: " + completeLengthInSeconds);

            int sequenceCounter = 0;

            while (jobRunning) {

                // Process one Second of Movie
                String[] interviewPod1Images = new String[frameRate];
                String[] interviewPod2Images = new String[frameRate];
                int[] outputFrameNumbers = new int[frameRate];

                for (FlvRecordingMetaData flvRecordingMetaData : metaDataList) {

                    long deltaStartRecording = flvRecordingMetaData.getRecordStart().getTime()
                            - flvRecording.getRecordStart().getTime();

                    if (flvRecording.getRecordStart().getTime() + currentTimeInMilliSeconds >= flvRecordingMetaData
                            .getRecordStart().getTime()
                            && flvRecording.getRecordStart().getTime()
                                    + currentTimeInMilliSeconds <= flvRecordingMetaData.getRecordEnd().getTime()) {

                        // Calculate which images should be in here

                        // Calculate the relative starting point
                        long thisImageSequenceStartingPoint = currentTimeInMilliSeconds - deltaStartRecording;

                        // Calculate the first and following frameRate FPS
                        // Number
                        int secondToStart = Long.valueOf(thisImageSequenceStartingPoint / 1000).intValue();

                        int firstFrame = secondToStart * frameRate;

                        for (int i = 0; i < frameRate; i++) {

                            int currentImageNumber = firstFrame + i;
                            currentImageNumber -= (frameRate / 2); // Remove the
                            // first
                            // half
                            // seconds
                            // and fill
                            // it up
                            // with
                            // black
                            // screens

                            // Remove the first period of Images, this is where
                            // the user has started
                            // to share his Video but does not have agreed in
                            // the Flash Security Warning Dialogue
                            Integer initialGapSeconds = flvRecordingMetaData.getInitialGapSeconds();
                            if (initialGapSeconds != null) {
                                int initialMissingImages = Double
                                        .valueOf(Math.floor((initialGapSeconds / 1000) * frameRate)).intValue();
                                currentImageNumber -= initialMissingImages;
                            }

                            String imageName = "image" + currentImageNumber + ".png";

                            String outputMetaImageFullData = streamFolderName
                                    + flvRecordingMetaData.getFlvRecordingMetaDataId() + File.separatorChar
                                    + imageName;

                            File outputMetaImageFullDataFile = new File(outputMetaImageFullData);

                            if (!outputMetaImageFullDataFile.exists()) {
                                outputMetaImageFullData = defaultInterviewImage;
                            }

                            if (flvRecordingMetaData.getInteriewPodId() == 1) {
                                interviewPod1Images[i] = outputMetaImageFullData;
                            } else if (flvRecordingMetaData.getInteriewPodId() == 2) {
                                interviewPod2Images[i] = outputMetaImageFullData;
                            }

                        }

                    }

                }

                // Update Sequence Count
                for (int i = 0; i < frameRate; i++) {
                    outputFrameNumbers[i] = sequenceCounter;
                    sequenceCounter++;
                }

                // Now we should have found the needed Images to calculate, in
                // case not we add an empty black screen
                for (int i = 0; i < frameRate; i++) {
                    String addZeros = "";

                    String outputImageName = outputImageMergedData + "image" + addZeros + outputFrameNumbers[i]
                            + ".png";

                    if (interviewPod1Images[i] == null) {
                        interviewPod1Images[i] = defaultInterviewImage;
                    }
                    if (interviewPod2Images[i] == null) {
                        interviewPod2Images[i] = defaultInterviewImage;
                    }

                    if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") == -1) {
                        String[] argv_imageMagick = new String[] { this.getPathToImageMagick(), "+append",
                                interviewPod1Images[i], interviewPod2Images[i], outputImageName };
                        returnLog.add(ProcessHelper.executeScript("generateImageSequence", argv_imageMagick));
                    } else {
                        returnLog.add(processImageWindows(interviewPod1Images[i], interviewPod2Images[i],
                                outputImageName));
                    }
                }

                currentTimeInMilliSeconds += 1000;

                double cLength = 100 * ((double) currentTimeInMilliSeconds) / completeLengthInSeconds;

                int progress = Double.valueOf(cLength).intValue();

                log.debug("completeLengthInSeconds|currentTimeInMilliSeconds " + completeLengthInSeconds + "|"
                        + currentTimeInMilliSeconds + "|" + progress + "|" + cLength);

                flvRecordingDaoImpl.updateFlvRecordingProgress(flvRecording.getFlvRecordingId(), progress);

                if (currentTimeInMilliSeconds >= completeLengthInSeconds) {

                    jobRunning = false;

                }

            }

            // Generate Movie by sequence of Images

            String imagescomplete = outputImageMergedData + "image%d.png";

            String[] argv_generatedMoview = null;

            String inputScreenFullFlv = streamFolderName + "COMPLETE_INTERVIEW_" + flvRecording.getFlvRecordingId()
                    + ".flv";
            deleteFileIfExists(inputScreenFullFlv);

            argv_generatedMoview = new String[] { this.getPathToFFMPEG(), "-i", imagescomplete, "-r",
                    "" + frameRate, "-vcodec", "flv", "-qmax", "1", "-qmin", "1", inputScreenFullFlv };

            log.debug("START generateFullBySequenceFLV ################# ");
            String tString2 = "";
            for (int i = 0; i < argv_generatedMoview.length; i++) {
                tString2 += argv_generatedMoview[i] + " ";
            }
            log.debug(tString2);
            log.debug("END generateFullBySequenceFLV ################# ");

            returnLog.add(ProcessHelper.executeScript("generateFullBySequenceFLV", argv_generatedMoview));

            String hashFileFullNameFlv = "flvRecording_" + flvRecording.getFlvRecordingId() + ".flv";
            String outputFullFlv = streamFolderGeneralName + hashFileFullNameFlv;
            deleteFileIfExists(outputFullFlv);

            // ffmpeg -vcodec flv -qscale 9.5 -r 25 -ar 22050 -ab 32k -s 320x240
            // -i
            // 65318fb5c54b1bc1b1bca077b493a914_28_12_2009_23_38_17_FINAL_WAVE.wav
            // -i 65318fb5c54b1bc1b1bca077b493a914_28_12_2009_23_38_17.flv
            // final1.flv

            int flvWidth = 640;
            int flvHeight = 240;

            flvRecording.setFlvWidth(flvWidth);
            flvRecording.setFlvHeight(flvHeight);

            String[] argv_fullFLV = new String[] { this.getPathToFFMPEG(), //
                    "-i", inputScreenFullFlv, "-i", outputFullWav, "-ar", "22050", //
                    "-ab", "32k", //
                    "-s", flvWidth + "x" + flvHeight, //
                    "-vcodec", "flv", //
                    "-r", "" + frameRate, "-qmax", "1", "-qmin", "1", outputFullFlv };

            log.debug("START generateFullFLV ################# ");
            String tString = "";
            for (int i = 0; i < argv_fullFLV.length; i++) {
                tString += argv_fullFLV[i] + " ";
                // log.debug(" i " + i + " argv-i " + argv_fullFLV[i]);
            }
            log.debug(tString);
            log.debug("END generateFullFLV ################# ");

            returnLog.add(ProcessHelper.executeScript("generateFullFLV", argv_fullFLV));

            flvRecording.setFileHash(hashFileFullNameFlv);

            // Extract first Image for preview purpose
            // ffmpeg -i movie.flv -vcodec mjpeg -vframes 1 -an -f rawvideo -s
            // 320x240 movie.jpg

            String hashFileFullNameJPEG = "flvRecording_" + flvRecording.getFlvRecordingId() + ".jpg";
            String outPutJpeg = streamFolderGeneralName + hashFileFullNameJPEG;
            deleteFileIfExists(outPutJpeg);

            flvRecording.setPreviewImage(hashFileFullNameJPEG);

            String[] argv_previewFLV = new String[] { //
                    this.getPathToFFMPEG(), //
                    "-i", outputFullFlv, //
                    "-vcodec", "mjpeg", //
                    "-vframes", "100", "-an", //
                    "-f", "rawvideo", //
                    "-s", flvWidth + "x" + flvHeight, //
                    outPutJpeg };

            log.debug("START previewFullFLV ################# ");
            log.debug(argv_previewFLV.toString());
            String kString = "";
            for (int i = 0; i < argv_previewFLV.length; i++) {
                kString += argv_previewFLV[i] + " ";
            }
            log.debug(kString);
            log.debug("END previewFullFLV ################# ");

            returnLog.add(ProcessHelper.executeScript("generateFullFLV", argv_previewFLV));

            String alternateDownloadName = "flvRecording_" + flvRecording.getFlvRecordingId() + ".avi";
            String alternateDownloadFullName = streamFolderGeneralName + alternateDownloadName;
            deleteFileIfExists(alternateDownloadFullName);

            String[] argv_alternateDownload = new String[] { this.getPathToFFMPEG(), "-i", outputFullFlv,
                    alternateDownloadFullName };

            log.debug("START alternateDownLoad ################# ");
            log.debug(argv_previewFLV.toString());
            String sString = "";
            for (int i = 0; i < argv_alternateDownload.length; i++) {
                sString += argv_alternateDownload[i] + " ";
            }
            log.debug(sString);
            log.debug("END alternateDownLoad ################# ");

            returnLog.add(ProcessHelper.executeScript("alternateDownload", argv_alternateDownload));

            flvRecording.setAlternateDownload(alternateDownloadName);

            flvRecordingDaoImpl.updateFlvRecording(flvRecording);

            flvRecordingLogDaoImpl.deleteFLVRecordingLogByRecordingId(flvRecording.getFlvRecordingId());

            for (HashMap<String, String> returnMap : returnLog) {
                flvRecordingLogDaoImpl.addFLVRecordingLog("generateFFMPEG", flvRecording, returnMap);
            }

            // Delete Wave Files
            for (String fileName : listOfFullWaveFiles) {
                File audio = new File(fileName);
                if (audio.exists()) {
                    audio.delete();
                }
            }

            // Delete all Image temp dirs
            for (FlvRecordingMetaData flvRecordingMetaData : metaDataList) {
                String outputMetaImageFullData = streamFolderName + flvRecordingMetaData.getFlvRecordingMetaDataId()
                        + File.separatorChar;

                this.deleteDirectory(new File(outputMetaImageFullData));
            }

            this.deleteDirectory(new File(outputImageMergedData));

        } catch (Exception err) {
            log.error("[stripAudioFromFLVs]", err);
        }
    }

    public boolean deleteDirectory(File path) throws Exception {
        if (path.exists()) {
            File[] files = path.listFiles();
            for (int i = 0; i < files.length; i++) {
                if (files[i].isDirectory()) {
                    deleteDirectory(files[i]);
                } else {
                    files[i].delete();
                }
            }
        }
        return (path.delete());
    }

    public HashMap<String, String> thumbProcessImageWindows(String file1, String file2, String file3) {

        // Init variables
        String[] cmd = { this.getPathToImageMagick(), file1, file2, "+append", file3 };

        return generateThumbs.processImageWindows(cmd);

        // GenerateSWF.executeScript("mergeWave",cmd);

    }

    public HashMap<String, String> processImageWindows(String file1, String file2, String file3) {
        HashMap<String, String> returnMap = new HashMap<String, String>();
        returnMap.put("process", "processImageWindows");
        try {

            // Init variables
            String[] cmd;
            String executable_fileName = "";
            String pathToIMagick = this.getPathToImageMagick();

            Date tnow = new Date();
            String runtimeFile = "interviewMerge" + tnow.getTime() + ".bat";

            // String runtimeFile = "interviewMerge.bat";
            executable_fileName = ScopeApplicationAdapter.batchFileDir + runtimeFile;

            cmd = new String[1];
            cmd[0] = executable_fileName;

            // Create the Content of the Converter Script (.bat or .sh File)
            String fileContent = pathToIMagick + " " + file1 + " " + file2 + " " + "+append" + " " + file3
                    + ScopeApplicationAdapter.lineSeperator + "";

            File previous = new File(executable_fileName);
            if (previous.exists()) {
                previous.delete();
            }

            // execute the Script
            FileOutputStream fos = new FileOutputStream(executable_fileName);
            fos.write(fileContent.getBytes());
            fos.close();

            File now = new File(executable_fileName);
            now.setExecutable(true);

            Runtime rt = Runtime.getRuntime();
            returnMap.put("command", cmd.toString());
            Process proc = rt.exec(cmd);

            InputStream stderr = proc.getErrorStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(stderr));
            String line = null;
            String error = "";
            while ((line = br.readLine()) != null) {
                error += line;
            }
            br.close();
            returnMap.put("error", error);
            int exitVal = proc.waitFor();
            returnMap.put("exitValue", "" + exitVal);

            if (now.exists()) {
                now.delete();
            }

            return returnMap;
        } catch (Throwable t) {
            t.printStackTrace();
            returnMap.put("error", t.getMessage());
            returnMap.put("exitValue", "-1");
            return returnMap;
        }
    }
}