blob: 0aa2ab632186a28addf5db39a4522c640b7709c4 [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.apache.openmeetings.data.flvrecord.converter;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.transaction.util.FileHelper;
import org.apache.openmeetings.OpenmeetingsVariables;
import org.apache.openmeetings.data.flvrecord.FlvRecordingDao;
import org.apache.openmeetings.data.flvrecord.FlvRecordingLogDao;
import org.apache.openmeetings.data.flvrecord.FlvRecordingMetaDataDao;
import org.apache.openmeetings.documents.GenerateThumbs;
import org.apache.openmeetings.documents.beans.ConverterProcessResult;
import org.apache.openmeetings.persistence.beans.flvrecord.FlvRecording;
import org.apache.openmeetings.persistence.beans.flvrecord.FlvRecordingMetaData;
import org.apache.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 FlvRecordingDao flvRecordingDaoImpl = null;
@Autowired
private FlvRecordingMetaDataDao flvRecordingMetaDataDaoImpl = null;
@Autowired
private FlvRecordingLogDao 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<ConverterProcessResult> returnLog = new ArrayList<ConverterProcessResult>();
List<String> listOfFullWaveFiles = new LinkedList<String>();
File streamFolder = getStreamFolder(flvRecording);
List<FlvRecordingMetaData> metaDataList = flvRecordingMetaDataDaoImpl
.getFlvRecordingMetaDataAudioFlvsByRecording(flvRecording
.getFlvRecordingId());
stripAudioFirstPass(flvRecording, returnLog, listOfFullWaveFiles,
streamFolder, metaDataList);
try {
// Merge Wave to Full Length
File streamFolderGeneral = getStreamFolder();
String hashFileFullName = "INTERVIEW_"
+ flvRecording.getFlvRecordingId() + "_FINAL_WAVE.wav";
String outputFullWav = streamFolder.getAbsolutePath() + File.separatorChar + 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
File outputWav = new File(streamFolderGeneral, "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.getCanonicalPath(), 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 = new File(streamFolder, flvRecordingMetaData.getStreamName() + ".flv").getCanonicalPath();
File inputFlvFile = new File(inputFlv);
if (inputFlvFile.exists()) {
// TO Image Sequence
// Image Folder
File imageSequenceFolder = new File(streamFolder
, "" + flvRecordingMetaData.getFlvRecordingMetaDataId());
imageSequenceFolder.mkdir();
String outputImages = new File(imageSequenceFolder, "image%d.png").getCanonicalPath();
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
File defaultInterviewImageFile = new File(streamFolderGeneral, "default_interview_image.png");
if (!defaultInterviewImageFile.exists()) {
throw new Exception("defaultInterviewImageFile does not exist!");
}
String defaultInterviewImage = defaultInterviewImageFile.getCanonicalPath();
// Create Folder for the output Image Sequence
String outputImageMergedData = streamFolder.getAbsolutePath() + File.separatorChar
+ "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";
File outputMetaImageFullDataFile = new File(streamFolder
, "" + flvRecordingMetaData.getFlvRecordingMetaDataId()
+ File.separatorChar + imageName);
String outputMetaImageFullData =
!outputMetaImageFullDataFile.exists() ? defaultInterviewImage
: outputMetaImageFullDataFile.getCanonicalPath();
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 = streamFolder.getAbsolutePath() + File.separatorChar
+ "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 = new File(streamFolderGeneral, hashFileFullNameFlv).getCanonicalPath();
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 = new File(streamFolderGeneral, hashFileFullNameJPEG).getCanonicalPath();
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 = new File(streamFolderGeneral, alternateDownloadName).getCanonicalPath();
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 (ConverterProcessResult 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) {
FileHelper.removeRec(new File(streamFolder, "" + flvRecordingMetaData.getFlvRecordingMetaDataId()));
}
FileHelper.removeRec(new File(outputImageMergedData));
} catch (Exception err) {
log.error("[stripAudioFromFLVs]", err);
}
}
public ConverterProcessResult thumbProcessImageWindows(String file1,
String file2, String file3) {
// Init variables
String[] cmd = { this.getPathToImageMagick(), file1, file2, "+append",
file3 };
return generateThumbs.processImageWindows(cmd);
}
public ConverterProcessResult processImageWindows(String file1,
String file2, String file3) {
return ProcessHelper.executeScriptWindows("processImageWindows"
, new String[]{getPathToImageMagick(), file1, file2, "+append", file3});
}
}