blob: 5362f436b37c2401e2c3d9b9d79673451149102a [file] [log] [blame]
/*
* 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;
}
}
}