1 package com.nexuiz.demorecorder.application.plugins.impl.virtualdub;
\r
3 import java.io.BufferedReader;
\r
4 import java.io.BufferedWriter;
\r
6 import java.io.FileWriter;
\r
7 import java.io.IOException;
\r
8 import java.io.InputStreamReader;
\r
9 import java.util.ArrayList;
\r
10 import java.util.List;
\r
11 import java.util.Properties;
\r
13 import com.nexuiz.demorecorder.application.DemoRecorderApplication;
\r
14 import com.nexuiz.demorecorder.application.DemoRecorderException;
\r
15 import com.nexuiz.demorecorder.application.DemoRecorderUtils;
\r
16 import com.nexuiz.demorecorder.application.jobs.RecordJob;
\r
17 import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
\r
18 import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;
\r
20 public class VirtualDubPlugin implements EncoderPlugin {
\r
22 private static final String PLUGIN_NAME = "Virtual Dub";
\r
24 private static class Preferences {
\r
25 public static final String ENABLED = "Enabled";
\r
26 public static final String VIRTUAL_DUB_BINARY_PATH = "Path to vdub.exe";
\r
27 public static final String VCF_PER_JOB_LIMIT = "Max. number of VCFs per job";
\r
28 public static final String OUTPUT_FILE_MODE = "Output as suffix (0) or file (1)";
\r
29 public static final String EXTRA_OPTIONS = "Show extra options";
\r
31 public static final String[] GLOBAL_PREFERENCES_ORDER = {
\r
33 VIRTUAL_DUB_BINARY_PATH,
\r
39 //job-specific preferences
\r
40 public static final String CLEAR_JOBCONTROL = "Clear VDub job control on first VCF";
\r
41 public static final String RENDER_OUTPUT = "VDub renders queued jobs";
\r
42 public static final String VCF_PATH = "Path to VCF file "; //x will be attached, e.g. "Path to VCF file 1"
\r
43 public static final String OUTPUT_SUFFIX = "Suffix for output file "; //x will be attached, e.g. "Suffix for output file 1"
\r
44 public static final String OUTPUT_FILE = "Output file "; //x will be attached
\r
45 public static final String USE_ENCODED_VIDEO = "<HTML><BODY>Use encoded video from VCF "; //x will be attached
\r
46 public static final String USE_ENCODED_VIDEO_2 = "<BR>for consecutive VCFs</BODY></HTML>";
\r
47 public static final String DELETE_ORIG_FILE = "Delete orig. file after processing VCF "; //x will be attached
\r
50 private DemoRecorderApplication appLayer = null;
\r
51 private Properties globalDefaultPreferences = new Properties();
\r
53 public VirtualDubPlugin() {
\r
54 this.createPreferenceDefaultValues();
\r
58 public void executeEncoder(RecordJob job) throws EncoderPluginException {
\r
59 this.checkAppLayer();
\r
60 if (!this.isEnabled()) {
\r
64 if (job.getActualVideoDestination() == null) {
\r
65 //should never happen... but just to make sure!
\r
66 throw new EncoderPluginException("Actual video destination is not set (should have been set when processing the job)");
\r
69 if (!job.getActualVideoDestination().exists()) {
\r
70 throw new EncoderPluginException("Could not locate video file (source) at location "
\r
71 + job.getActualVideoDestination().getAbsolutePath());
\r
74 String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);
\r
77 vcfCounter = Integer.valueOf(limitStr);
\r
78 } catch (NumberFormatException e) {
\r
79 throw new EncoderPluginException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);
\r
83 String vDubBinary = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VIRTUAL_DUB_BINARY_PATH);
\r
84 File vDubBinaryFile = new File(vDubBinary);
\r
85 if (!vDubBinaryFile.exists() || !vDubBinaryFile.canExecute()) {
\r
86 throw new EncoderPluginException("Invalid location for the vdub.exe: " + vDubBinary);
\r
89 this.doEncoding(job, vcfCounter);
\r
93 public Properties getGlobalPreferences() {
\r
94 return this.globalDefaultPreferences;
\r
98 public String[] getGlobalPreferencesOrder() {
\r
99 return Preferences.GLOBAL_PREFERENCES_ORDER;
\r
103 public Properties getJobSpecificPreferences() {
\r
104 this.checkAppLayer();
\r
105 Properties jobSpecificPreferences = new Properties();
\r
107 //static properties
\r
108 jobSpecificPreferences.setProperty(Preferences.CLEAR_JOBCONTROL, "true");
\r
109 jobSpecificPreferences.setProperty(Preferences.RENDER_OUTPUT, "true");
\r
111 //dynamic properties
\r
112 String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);
\r
114 int limit = Integer.valueOf(limitStr);
\r
116 for (int i = 1; i <= limit; i++) {
\r
117 jobSpecificPreferences.setProperty(Preferences.VCF_PATH + i, "filechooser");
\r
118 if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
\r
120 jobSpecificPreferences.setProperty(Preferences.OUTPUT_FILE + i, "filechooser");
\r
123 jobSpecificPreferences.setProperty(Preferences.OUTPUT_SUFFIX + i, "_vdub" + i);
\r
126 if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {
\r
127 String useEncStringKey = Preferences.USE_ENCODED_VIDEO + i + Preferences.USE_ENCODED_VIDEO_2;
\r
128 jobSpecificPreferences.setProperty(useEncStringKey, "false");
\r
129 jobSpecificPreferences.setProperty(Preferences.DELETE_ORIG_FILE + i, "false");
\r
133 } catch (NumberFormatException e) {
\r
134 throw new DemoRecorderException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);
\r
137 return jobSpecificPreferences;
\r
141 public String[] getJobSpecificPreferencesOrder() {
\r
142 this.checkAppLayer();
\r
143 List<String> preferencesOrderList = new ArrayList<String>();
\r
145 //static properties
\r
146 preferencesOrderList.add(Preferences.CLEAR_JOBCONTROL);
\r
147 preferencesOrderList.add(Preferences.RENDER_OUTPUT);
\r
149 //dynamic properties
\r
150 String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);
\r
152 int limit = Integer.valueOf(limitStr);
\r
154 for (int i = 1; i <= limit; i++) {
\r
155 preferencesOrderList.add(Preferences.VCF_PATH + i);
\r
156 if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
\r
158 preferencesOrderList.add(Preferences.OUTPUT_FILE + i);
\r
161 preferencesOrderList.add(Preferences.OUTPUT_SUFFIX + i);
\r
164 if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {
\r
165 String useEncStringKey = Preferences.USE_ENCODED_VIDEO + i + Preferences.USE_ENCODED_VIDEO_2;
\r
166 preferencesOrderList.add(useEncStringKey);
\r
167 preferencesOrderList.add(Preferences.DELETE_ORIG_FILE + i);
\r
171 } catch (NumberFormatException e) {
\r
172 throw new DemoRecorderException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);
\r
175 Object[] arr = preferencesOrderList.toArray();
\r
176 String[] stringArr = new String[arr.length];
\r
177 for (int i = 0; i < arr.length; i++) {
\r
178 stringArr[i] = (String) arr[i];
\r
185 public String getName() {
\r
186 return PLUGIN_NAME;
\r
190 public boolean isEnabled() {
\r
191 this.checkAppLayer();
\r
192 String enabledString = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.ENABLED);
\r
193 return Boolean.valueOf(enabledString);
\r
197 public void setApplicationLayer(DemoRecorderApplication appLayer) {
\r
198 this.appLayer = appLayer;
\r
201 private void checkAppLayer() {
\r
202 if (this.appLayer == null) {
\r
203 throw new DemoRecorderException("Error in plugin " + PLUGIN_NAME + "! Application layer not set!");
\r
207 private void createPreferenceDefaultValues() {
\r
208 this.globalDefaultPreferences.setProperty(Preferences.ENABLED, "false");
\r
209 this.globalDefaultPreferences.setProperty(Preferences.VIRTUAL_DUB_BINARY_PATH, "filechooser");
\r
210 this.globalDefaultPreferences.setProperty(Preferences.VCF_PER_JOB_LIMIT, "1");
\r
211 this.globalDefaultPreferences.setProperty(Preferences.OUTPUT_FILE_MODE, "false");
\r
212 this.globalDefaultPreferences.setProperty(Preferences.EXTRA_OPTIONS, "false");
\r
215 private void doEncoding(RecordJob job, int vcfCounter) throws EncoderPluginException {
\r
216 boolean firstValidVCF = true;
\r
217 for (int i = 1; i <= vcfCounter; i++) {
\r
218 Properties jobSpecificSettings = job.getEncoderPluginSettings(this);
\r
219 String path = jobSpecificSettings.getProperty(Preferences.VCF_PATH + i);
\r
220 if (path != null) {
\r
221 File vcfFile = new File(path);
\r
222 if (vcfFile.exists()) {
\r
223 if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
\r
225 String outputPath = jobSpecificSettings.getProperty(Preferences.OUTPUT_FILE + i, "filechooser");
\r
226 if (outputPath == null || outputPath.equals("") || outputPath.equals("filechoose")) {
\r
227 //user has not yet selected a file
\r
232 String suffix = jobSpecificSettings.getProperty(Preferences.OUTPUT_SUFFIX + i);
\r
233 if (suffix == null || suffix.equals("")) {
\r
237 BufferedWriter logWriter = this.getLogWriter(job.getJobName(), i);
\r
238 this.executeVDub(job, i, firstValidVCF, logWriter);
\r
239 firstValidVCF = false;
\r
245 private void executeVDub(RecordJob job, int index, boolean firstValidVCF, BufferedWriter logWriter) throws EncoderPluginException {
\r
246 String shellString = "";
\r
247 Properties jobSpecificSettings = job.getEncoderPluginSettings(this);
\r
248 File vcfFile = new File(jobSpecificSettings.getProperty(Preferences.VCF_PATH + index));
\r
249 File sourceFile = job.getActualVideoDestination();
\r
251 String vDubBinary = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VIRTUAL_DUB_BINARY_PATH);
\r
252 shellString += '"' + vDubBinary.trim() + '"';
\r
254 shellString += " /s " + '"' + vcfFile.getAbsolutePath() + '"';
\r
256 boolean clearJobControl = Boolean.valueOf(jobSpecificSettings.getProperty(Preferences.CLEAR_JOBCONTROL, "true"));
\r
257 if (clearJobControl && firstValidVCF) {
\r
258 shellString += " /c";
\r
261 String outputFilePath = this.getOutputFilePath(job, index);
\r
262 File outputFile = new File(outputFilePath);
\r
263 shellString += " /p " + '"' + sourceFile.getAbsolutePath() + '"';
\r
264 shellString += " " + '"' + outputFilePath + '"';
\r
266 boolean renderOutput = Boolean.valueOf(jobSpecificSettings.getProperty(Preferences.RENDER_OUTPUT, "true"));
\r
267 if (renderOutput) {
\r
268 shellString += " /r";
\r
271 shellString += " /x";
\r
274 logWriter.write("Executing commandline: " + shellString);
\r
275 logWriter.newLine();
\r
276 File vdubDir = new File(vDubBinary).getParentFile();
\r
278 vDubProc = Runtime.getRuntime().exec(shellString, null, vdubDir);
\r
279 vDubProc.getOutputStream();
\r
280 InputStreamReader isr = new InputStreamReader(vDubProc.getInputStream());
\r
281 BufferedReader bufferedInputStream = new BufferedReader(isr);
\r
282 String currentLine;
\r
283 while ((currentLine = bufferedInputStream.readLine()) != null) {
\r
284 logWriter.write(currentLine);
\r
285 logWriter.newLine();
\r
287 InputStreamReader isrErr = new InputStreamReader(vDubProc.getErrorStream());
\r
288 BufferedReader bufferedInputStreamErr = new BufferedReader(isrErr);
\r
289 while ((currentLine = bufferedInputStreamErr.readLine()) != null) {
\r
290 logWriter.write(currentLine);
\r
291 logWriter.newLine();
\r
295 } catch (IOException e) {
\r
296 throw new EncoderPluginException("I/O Exception occurred when trying to execute the VDub binary or logging output", e);
\r
299 //extra options: replace original video with encoded one, possibly delete original one
\r
300 if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {
\r
301 String useEncStringKey = Preferences.USE_ENCODED_VIDEO + index + Preferences.USE_ENCODED_VIDEO_2;
\r
302 String useEncVideo = jobSpecificSettings.getProperty(useEncStringKey);
\r
303 File origFile = job.getActualVideoDestination();
\r
304 if (useEncVideo != null && Boolean.valueOf(useEncVideo)) {
\r
305 job.setActualVideoDestination(outputFile);
\r
308 String deleteOrigFile = jobSpecificSettings.getProperty(Preferences.DELETE_ORIG_FILE + index);
\r
309 if (deleteOrigFile != null && Boolean.valueOf(deleteOrigFile)) {
\r
310 //only delete the original file if the encoded one exists:
\r
311 if (outputFile.exists() && outputFile.length() > 0) {
\r
318 private String getOutputFilePath(RecordJob job, int index) {
\r
319 File sourceFile = job.getActualVideoDestination();
\r
320 String ext = DemoRecorderUtils.getFileExtension(sourceFile);
\r
321 String outputFilePath;
\r
322 Properties jobSpecificSettings = job.getEncoderPluginSettings(this);
\r
323 if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
\r
325 outputFilePath = jobSpecificSettings.getProperty(Preferences.OUTPUT_FILE + index);
\r
328 outputFilePath = sourceFile.getAbsolutePath();
\r
329 String suffix = jobSpecificSettings.getProperty(Preferences.OUTPUT_SUFFIX + index);
\r
330 int idx = outputFilePath.indexOf("." + ext);
\r
331 outputFilePath = outputFilePath.substring(0, idx);
\r
332 outputFilePath += suffix + "." + ext;
\r
335 return outputFilePath;
\r
338 private BufferedWriter getLogWriter(String jobName, int vcfIndex) throws EncoderPluginException {
\r
339 File logDir = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.LOGS_DIRNAME, "");
\r
340 if (jobName == null || jobName.equals("")) {
\r
341 jobName = "unnamed_job";
\r
343 String path = logDir.getAbsolutePath() + File.separator + PLUGIN_NAME + '_' + jobName + '_' + "vcf" + vcfIndex + ".log";
\r
344 File logFile = new File(path);
\r
345 if (!DemoRecorderUtils.attemptFileCreation(logFile)) {
\r
346 throw new EncoderPluginException("Could not create log file for VDub job at location: " + path);
\r
349 FileWriter fileWriter = new FileWriter(logFile);
\r
350 return new BufferedWriter(fileWriter);
\r
351 } catch (IOException e) {
\r
352 throw new EncoderPluginException("Could not create log file for VDub job at location: " + path, e);
\r