package com.nexuiz.demorecorder.application.plugins.impl.virtualdub; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; import java.util.Properties; import com.nexuiz.demorecorder.application.DemoRecorderApplication; import com.nexuiz.demorecorder.application.DemoRecorderException; import com.nexuiz.demorecorder.application.DemoRecorderUtils; import com.nexuiz.demorecorder.application.jobs.RecordJob; import com.nexuiz.demorecorder.application.plugins.EncoderPlugin; import com.nexuiz.demorecorder.application.plugins.EncoderPluginException; public class VirtualDubPlugin implements EncoderPlugin { private static final String PLUGIN_NAME = "Virtual Dub"; private static class Preferences { public static final String ENABLED = "Enabled"; public static final String VIRTUAL_DUB_BINARY_PATH = "Path to vdub.exe"; public static final String VCF_PER_JOB_LIMIT = "Max. number of VCFs per job"; public static final String OUTPUT_FILE_MODE = "Output as suffix (0) or file (1)"; public static final String EXTRA_OPTIONS = "Show extra options"; public static final String[] GLOBAL_PREFERENCES_ORDER = { ENABLED, VIRTUAL_DUB_BINARY_PATH, VCF_PER_JOB_LIMIT, OUTPUT_FILE_MODE, EXTRA_OPTIONS }; //job-specific preferences public static final String CLEAR_JOBCONTROL = "Clear VDub job control on first VCF"; public static final String RENDER_OUTPUT = "VDub renders queued jobs"; public static final String VCF_PATH = "Path to VCF file "; //x will be attached, e.g. "Path to VCF file 1" public static final String OUTPUT_SUFFIX = "Suffix for output file "; //x will be attached, e.g. "Suffix for output file 1" public static final String OUTPUT_FILE = "Output file "; //x will be attached public static final String USE_ENCODED_VIDEO = "Use encoded video from VCF "; //x will be attached public static final String USE_ENCODED_VIDEO_2 = "
for consecutive VCFs"; public static final String DELETE_ORIG_FILE = "Delete orig. file after processing VCF "; //x will be attached } private DemoRecorderApplication appLayer = null; private Properties globalDefaultPreferences = new Properties(); public VirtualDubPlugin() { this.createPreferenceDefaultValues(); } @Override public void executeEncoder(RecordJob job) throws EncoderPluginException { this.checkAppLayer(); if (!this.isEnabled()) { return; } if (job.getActualVideoDestination() == null) { //should never happen... but just to make sure! throw new EncoderPluginException("Actual video destination is not set (should have been set when processing the job)"); } if (!job.getActualVideoDestination().exists()) { throw new EncoderPluginException("Could not locate video file (source) at location " + job.getActualVideoDestination().getAbsolutePath()); } String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT); int vcfCounter; try { vcfCounter = Integer.valueOf(limitStr); } catch (NumberFormatException e) { throw new EncoderPluginException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT); } //check vdub.exe String vDubBinary = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VIRTUAL_DUB_BINARY_PATH); File vDubBinaryFile = new File(vDubBinary); if (!vDubBinaryFile.exists() || !vDubBinaryFile.canExecute()) { throw new EncoderPluginException("Invalid location for the vdub.exe: " + vDubBinary); } this.doEncoding(job, vcfCounter); } @Override public Properties getGlobalPreferences() { return this.globalDefaultPreferences; } @Override public String[] getGlobalPreferencesOrder() { return Preferences.GLOBAL_PREFERENCES_ORDER; } @Override public Properties getJobSpecificPreferences() { this.checkAppLayer(); Properties jobSpecificPreferences = new Properties(); //static properties jobSpecificPreferences.setProperty(Preferences.CLEAR_JOBCONTROL, "true"); jobSpecificPreferences.setProperty(Preferences.RENDER_OUTPUT, "true"); //dynamic properties String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT); try { int limit = Integer.valueOf(limitStr); if (limit > 0) { for (int i = 1; i <= limit; i++) { jobSpecificPreferences.setProperty(Preferences.VCF_PATH + i, "filechooser"); if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) { //filechooser jobSpecificPreferences.setProperty(Preferences.OUTPUT_FILE + i, "filechooser"); } else { //suffix jobSpecificPreferences.setProperty(Preferences.OUTPUT_SUFFIX + i, "_vdub" + i); } if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) { String useEncStringKey = Preferences.USE_ENCODED_VIDEO + i + Preferences.USE_ENCODED_VIDEO_2; jobSpecificPreferences.setProperty(useEncStringKey, "false"); jobSpecificPreferences.setProperty(Preferences.DELETE_ORIG_FILE + i, "false"); } } } } catch (NumberFormatException e) { throw new DemoRecorderException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT); } return jobSpecificPreferences; } @Override public String[] getJobSpecificPreferencesOrder() { this.checkAppLayer(); List preferencesOrderList = new ArrayList(); //static properties preferencesOrderList.add(Preferences.CLEAR_JOBCONTROL); preferencesOrderList.add(Preferences.RENDER_OUTPUT); //dynamic properties String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT); try { int limit = Integer.valueOf(limitStr); if (limit > 0) { for (int i = 1; i <= limit; i++) { preferencesOrderList.add(Preferences.VCF_PATH + i); if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) { //filechooser preferencesOrderList.add(Preferences.OUTPUT_FILE + i); } else { //suffix preferencesOrderList.add(Preferences.OUTPUT_SUFFIX + i); } if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) { String useEncStringKey = Preferences.USE_ENCODED_VIDEO + i + Preferences.USE_ENCODED_VIDEO_2; preferencesOrderList.add(useEncStringKey); preferencesOrderList.add(Preferences.DELETE_ORIG_FILE + i); } } } } catch (NumberFormatException e) { throw new DemoRecorderException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT); } Object[] arr = preferencesOrderList.toArray(); String[] stringArr = new String[arr.length]; for (int i = 0; i < arr.length; i++) { stringArr[i] = (String) arr[i]; } return stringArr; } @Override public String getName() { return PLUGIN_NAME; } @Override public boolean isEnabled() { this.checkAppLayer(); String enabledString = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.ENABLED); return Boolean.valueOf(enabledString); } @Override public void setApplicationLayer(DemoRecorderApplication appLayer) { this.appLayer = appLayer; } private void checkAppLayer() { if (this.appLayer == null) { throw new DemoRecorderException("Error in plugin " + PLUGIN_NAME + "! Application layer not set!"); } } private void createPreferenceDefaultValues() { this.globalDefaultPreferences.setProperty(Preferences.ENABLED, "false"); this.globalDefaultPreferences.setProperty(Preferences.VIRTUAL_DUB_BINARY_PATH, "filechooser"); this.globalDefaultPreferences.setProperty(Preferences.VCF_PER_JOB_LIMIT, "1"); this.globalDefaultPreferences.setProperty(Preferences.OUTPUT_FILE_MODE, "false"); this.globalDefaultPreferences.setProperty(Preferences.EXTRA_OPTIONS, "false"); } private void doEncoding(RecordJob job, int vcfCounter) throws EncoderPluginException { boolean firstValidVCF = true; for (int i = 1; i <= vcfCounter; i++) { Properties jobSpecificSettings = job.getEncoderPluginSettings(this); String path = jobSpecificSettings.getProperty(Preferences.VCF_PATH + i); if (path != null) { File vcfFile = new File(path); if (vcfFile.exists()) { if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) { //filechooser String outputPath = jobSpecificSettings.getProperty(Preferences.OUTPUT_FILE + i, "filechooser"); if (outputPath == null || outputPath.equals("") || outputPath.equals("filechoose")) { //user has not yet selected a file continue; } } else { //suffix String suffix = jobSpecificSettings.getProperty(Preferences.OUTPUT_SUFFIX + i); if (suffix == null || suffix.equals("")) { continue; } } BufferedWriter logWriter = this.getLogWriter(job.getJobName(), i); this.executeVDub(job, i, firstValidVCF, logWriter); firstValidVCF = false; } } } } private void executeVDub(RecordJob job, int index, boolean firstValidVCF, BufferedWriter logWriter) throws EncoderPluginException { String shellString = ""; Properties jobSpecificSettings = job.getEncoderPluginSettings(this); File vcfFile = new File(jobSpecificSettings.getProperty(Preferences.VCF_PATH + index)); File sourceFile = job.getActualVideoDestination(); String vDubBinary = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VIRTUAL_DUB_BINARY_PATH); shellString += '"' + vDubBinary.trim() + '"'; shellString += " /s " + '"' + vcfFile.getAbsolutePath() + '"'; boolean clearJobControl = Boolean.valueOf(jobSpecificSettings.getProperty(Preferences.CLEAR_JOBCONTROL, "true")); if (clearJobControl && firstValidVCF) { shellString += " /c"; } String outputFilePath = this.getOutputFilePath(job, index); File outputFile = new File(outputFilePath); shellString += " /p " + '"' + sourceFile.getAbsolutePath() + '"'; shellString += " " + '"' + outputFilePath + '"'; boolean renderOutput = Boolean.valueOf(jobSpecificSettings.getProperty(Preferences.RENDER_OUTPUT, "true")); if (renderOutput) { shellString += " /r"; } shellString += " /x"; try { logWriter.write("Executing commandline: " + shellString); logWriter.newLine(); File vdubDir = new File(vDubBinary).getParentFile(); Process vDubProc; vDubProc = Runtime.getRuntime().exec(shellString, null, vdubDir); vDubProc.getOutputStream(); InputStreamReader isr = new InputStreamReader(vDubProc.getInputStream()); BufferedReader bufferedInputStream = new BufferedReader(isr); String currentLine; while ((currentLine = bufferedInputStream.readLine()) != null) { logWriter.write(currentLine); logWriter.newLine(); } InputStreamReader isrErr = new InputStreamReader(vDubProc.getErrorStream()); BufferedReader bufferedInputStreamErr = new BufferedReader(isrErr); while ((currentLine = bufferedInputStreamErr.readLine()) != null) { logWriter.write(currentLine); logWriter.newLine(); } logWriter.close(); } catch (IOException e) { throw new EncoderPluginException("I/O Exception occurred when trying to execute the VDub binary or logging output", e); } //extra options: replace original video with encoded one, possibly delete original one if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) { String useEncStringKey = Preferences.USE_ENCODED_VIDEO + index + Preferences.USE_ENCODED_VIDEO_2; String useEncVideo = jobSpecificSettings.getProperty(useEncStringKey); File origFile = job.getActualVideoDestination(); if (useEncVideo != null && Boolean.valueOf(useEncVideo)) { job.setActualVideoDestination(outputFile); } String deleteOrigFile = jobSpecificSettings.getProperty(Preferences.DELETE_ORIG_FILE + index); if (deleteOrigFile != null && Boolean.valueOf(deleteOrigFile)) { //only delete the original file if the encoded one exists: if (outputFile.exists() && outputFile.length() > 0) { origFile.delete(); } } } } private String getOutputFilePath(RecordJob job, int index) { File sourceFile = job.getActualVideoDestination(); String ext = DemoRecorderUtils.getFileExtension(sourceFile); String outputFilePath; Properties jobSpecificSettings = job.getEncoderPluginSettings(this); if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) { //filechooser outputFilePath = jobSpecificSettings.getProperty(Preferences.OUTPUT_FILE + index); } else { //suffix outputFilePath = sourceFile.getAbsolutePath(); String suffix = jobSpecificSettings.getProperty(Preferences.OUTPUT_SUFFIX + index); int idx = outputFilePath.indexOf("." + ext); outputFilePath = outputFilePath.substring(0, idx); outputFilePath += suffix + "." + ext; } return outputFilePath; } private BufferedWriter getLogWriter(String jobName, int vcfIndex) throws EncoderPluginException { File logDir = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.LOGS_DIRNAME, ""); if (jobName == null || jobName.equals("")) { jobName = "unnamed_job"; } String path = logDir.getAbsolutePath() + File.separator + PLUGIN_NAME + '_' + jobName + '_' + "vcf" + vcfIndex + ".log"; File logFile = new File(path); if (!DemoRecorderUtils.attemptFileCreation(logFile)) { throw new EncoderPluginException("Could not create log file for VDub job at location: " + path); } try { FileWriter fileWriter = new FileWriter(logFile); return new BufferedWriter(fileWriter); } catch (IOException e) { throw new EncoderPluginException("Could not create log file for VDub job at location: " + path, e); } } }