]> git.xonotic.org Git - xonotic/xonotic.git/blob - misc/tools/NexuizDemoRecorder/plugins/virtualdub/src/main/java/com/nexuiz/demorecorder/application/plugins/impl/virtualdub/VirtualDubPlugin.java
fix lots of CRLFs
[xonotic/xonotic.git] / misc / tools / NexuizDemoRecorder / plugins / virtualdub / src / main / java / com / nexuiz / demorecorder / application / plugins / impl / virtualdub / VirtualDubPlugin.java
1 package com.nexuiz.demorecorder.application.plugins.impl.virtualdub;
2
3 import java.io.BufferedReader;
4 import java.io.BufferedWriter;
5 import java.io.File;
6 import java.io.FileWriter;
7 import java.io.IOException;
8 import java.io.InputStreamReader;
9 import java.util.ArrayList;
10 import java.util.List;
11 import java.util.Properties;
12
13 import com.nexuiz.demorecorder.application.DemoRecorderApplication;
14 import com.nexuiz.demorecorder.application.DemoRecorderException;
15 import com.nexuiz.demorecorder.application.DemoRecorderUtils;
16 import com.nexuiz.demorecorder.application.jobs.RecordJob;
17 import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
18 import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;
19
20 public class VirtualDubPlugin implements EncoderPlugin {
21         
22         private static final String PLUGIN_NAME = "Virtual Dub";
23         
24         private static class Preferences {
25                 public static final String ENABLED = "Enabled";
26                 public static final String VIRTUAL_DUB_BINARY_PATH = "Path to vdub.exe";
27                 public static final String VCF_PER_JOB_LIMIT = "Max. number of VCFs per job";
28                 public static final String OUTPUT_FILE_MODE = "Output as suffix (0) or file (1)";
29                 public static final String EXTRA_OPTIONS = "Show extra options";
30                 
31                 public static final String[] GLOBAL_PREFERENCES_ORDER = {
32                         ENABLED,
33                         VIRTUAL_DUB_BINARY_PATH,
34                         VCF_PER_JOB_LIMIT,
35                         OUTPUT_FILE_MODE,
36                         EXTRA_OPTIONS
37                 };
38                 
39                 //job-specific preferences
40                 public static final String CLEAR_JOBCONTROL = "Clear VDub job control on first VCF";
41                 public static final String RENDER_OUTPUT = "VDub renders queued jobs";
42                 public static final String VCF_PATH = "Path to VCF file "; //x will be attached, e.g. "Path to VCF file 1"
43                 public static final String OUTPUT_SUFFIX = "Suffix for output file "; //x will be attached, e.g. "Suffix for output file 1"
44                 public static final String OUTPUT_FILE = "Output file "; //x will be attached
45                 public static final String USE_ENCODED_VIDEO = "<HTML><BODY>Use encoded video from VCF "; //x will be attached
46                 public static final String USE_ENCODED_VIDEO_2 = "<BR>for consecutive VCFs</BODY></HTML>";
47                 public static final String DELETE_ORIG_FILE = "Delete orig. file after processing VCF "; //x will be attached
48         }
49         
50         private DemoRecorderApplication appLayer = null;
51         private Properties globalDefaultPreferences = new Properties();
52         
53         public VirtualDubPlugin() {
54                 this.createPreferenceDefaultValues();
55         }
56
57         @Override
58         public void executeEncoder(RecordJob job) throws EncoderPluginException {
59                 this.checkAppLayer();
60                 if (!this.isEnabled()) {
61                         return;
62                 }
63                 
64                 if (job.getActualVideoDestination() == null) {
65                         //should never happen... but just to make sure!
66                         throw new EncoderPluginException("Actual video destination is not set (should have been set when processing the job)");
67                 }
68                 
69                 if (!job.getActualVideoDestination().exists()) {
70                         throw new EncoderPluginException("Could not locate video file (source) at location "
71                                         + job.getActualVideoDestination().getAbsolutePath());
72                 }
73                 
74                 String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);
75                 int vcfCounter;
76                 try {
77                         vcfCounter = Integer.valueOf(limitStr);
78                 } catch (NumberFormatException e) {
79                         throw new EncoderPluginException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);
80                 }
81                 
82                 //check vdub.exe
83                 String vDubBinary = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VIRTUAL_DUB_BINARY_PATH);
84                 File vDubBinaryFile = new File(vDubBinary);
85                 if (!vDubBinaryFile.exists() || !vDubBinaryFile.canExecute()) {
86                         throw new EncoderPluginException("Invalid location for the vdub.exe: " + vDubBinary);
87                 }
88                 
89                 this.doEncoding(job, vcfCounter);
90         }
91
92         @Override
93         public Properties getGlobalPreferences() {
94                 return this.globalDefaultPreferences;
95         }
96
97         @Override
98         public String[] getGlobalPreferencesOrder() {
99                 return Preferences.GLOBAL_PREFERENCES_ORDER;
100         }
101
102         @Override
103         public Properties getJobSpecificPreferences() {
104                 this.checkAppLayer();
105                 Properties jobSpecificPreferences = new Properties();
106                 
107                 //static properties
108                 jobSpecificPreferences.setProperty(Preferences.CLEAR_JOBCONTROL, "true");
109                 jobSpecificPreferences.setProperty(Preferences.RENDER_OUTPUT, "true");
110                 
111                 //dynamic properties
112                 String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);
113                 try {
114                         int limit = Integer.valueOf(limitStr);
115                         if (limit > 0) {
116                                 for (int i = 1; i <= limit; i++) {
117                                         jobSpecificPreferences.setProperty(Preferences.VCF_PATH + i, "filechooser");
118                                         if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
119                                                 //filechooser
120                                                 jobSpecificPreferences.setProperty(Preferences.OUTPUT_FILE + i, "filechooser");
121                                         } else {
122                                                 //suffix
123                                                 jobSpecificPreferences.setProperty(Preferences.OUTPUT_SUFFIX + i, "_vdub" + i);
124                                         }
125                                         
126                                         if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {
127                                                 String useEncStringKey = Preferences.USE_ENCODED_VIDEO + i + Preferences.USE_ENCODED_VIDEO_2;
128                                                 jobSpecificPreferences.setProperty(useEncStringKey, "false");
129                                                 jobSpecificPreferences.setProperty(Preferences.DELETE_ORIG_FILE + i, "false");
130                                         }
131                                 }
132                         }
133                 } catch (NumberFormatException e) {
134                         throw new DemoRecorderException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);
135                 }
136                 
137                 return jobSpecificPreferences;
138         }
139         
140         @Override
141         public String[] getJobSpecificPreferencesOrder() {
142                 this.checkAppLayer();
143                 List<String> preferencesOrderList = new ArrayList<String>();
144                 
145                 //static properties
146                 preferencesOrderList.add(Preferences.CLEAR_JOBCONTROL);
147                 preferencesOrderList.add(Preferences.RENDER_OUTPUT);
148                 
149                 //dynamic properties
150                 String limitStr = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VCF_PER_JOB_LIMIT);
151                 try {
152                         int limit = Integer.valueOf(limitStr);
153                         if (limit > 0) {
154                                 for (int i = 1; i <= limit; i++) {
155                                         preferencesOrderList.add(Preferences.VCF_PATH + i);
156                                         if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
157                                                 //filechooser
158                                                 preferencesOrderList.add(Preferences.OUTPUT_FILE + i);
159                                         } else {
160                                                 //suffix
161                                                 preferencesOrderList.add(Preferences.OUTPUT_SUFFIX + i);
162                                         }
163                                         
164                                         if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {
165                                                 String useEncStringKey = Preferences.USE_ENCODED_VIDEO + i + Preferences.USE_ENCODED_VIDEO_2;
166                                                 preferencesOrderList.add(useEncStringKey);
167                                                 preferencesOrderList.add(Preferences.DELETE_ORIG_FILE + i);
168                                         }
169                                 }
170                         }
171                 } catch (NumberFormatException e) {
172                         throw new DemoRecorderException("Invalid value \"" + limitStr + "\" for setting " + Preferences.VCF_PER_JOB_LIMIT);
173                 }
174                 
175                 Object[] arr = preferencesOrderList.toArray();
176                 String[] stringArr = new String[arr.length];
177                 for (int i = 0; i < arr.length; i++) {
178                         stringArr[i] = (String) arr[i];
179                 }
180                 
181                 return stringArr;
182         }
183
184         @Override
185         public String getName() {
186                 return PLUGIN_NAME;
187         }
188
189         @Override
190         public boolean isEnabled() {
191                 this.checkAppLayer();
192                 String enabledString = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.ENABLED);
193                 return Boolean.valueOf(enabledString);
194         }
195
196         @Override
197         public void setApplicationLayer(DemoRecorderApplication appLayer) {
198                 this.appLayer = appLayer;
199         }
200         
201         private void checkAppLayer() {
202                 if (this.appLayer == null) {
203                         throw new DemoRecorderException("Error in plugin " + PLUGIN_NAME + "! Application layer not set!");
204                 }
205         }
206         
207         private void createPreferenceDefaultValues() {
208                 this.globalDefaultPreferences.setProperty(Preferences.ENABLED, "false");
209                 this.globalDefaultPreferences.setProperty(Preferences.VIRTUAL_DUB_BINARY_PATH, "filechooser");
210                 this.globalDefaultPreferences.setProperty(Preferences.VCF_PER_JOB_LIMIT, "1");
211                 this.globalDefaultPreferences.setProperty(Preferences.OUTPUT_FILE_MODE, "false");
212                 this.globalDefaultPreferences.setProperty(Preferences.EXTRA_OPTIONS, "false");
213         }
214         
215         private void doEncoding(RecordJob job, int vcfCounter) throws EncoderPluginException {
216                 boolean firstValidVCF = true;
217                 for (int i = 1; i <= vcfCounter; i++) {
218                         Properties jobSpecificSettings = job.getEncoderPluginSettings(this);
219                         String path = jobSpecificSettings.getProperty(Preferences.VCF_PATH + i);
220                         if (path != null) {
221                                 File vcfFile = new File(path);
222                                 if (vcfFile.exists()) {
223                                         if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
224                                                 //filechooser
225                                                 String outputPath = jobSpecificSettings.getProperty(Preferences.OUTPUT_FILE + i, "filechooser");
226                                                 if (outputPath == null || outputPath.equals("") || outputPath.equals("filechoose")) {
227                                                         //user has not yet selected a file
228                                                         continue;
229                                                 }
230                                         } else {
231                                                 //suffix
232                                                 String suffix = jobSpecificSettings.getProperty(Preferences.OUTPUT_SUFFIX + i);
233                                                 if (suffix == null || suffix.equals("")) {
234                                                         continue;
235                                                 }
236                                         }
237                                         BufferedWriter logWriter = this.getLogWriter(job.getJobName(), i);
238                                         this.executeVDub(job, i, firstValidVCF, logWriter);
239                                         firstValidVCF = false;
240                                 }
241                         }
242                 }
243         }
244         
245         private void executeVDub(RecordJob job, int index, boolean firstValidVCF, BufferedWriter logWriter) throws EncoderPluginException {
246                 String shellString = "";
247                 Properties jobSpecificSettings = job.getEncoderPluginSettings(this);
248                 File vcfFile = new File(jobSpecificSettings.getProperty(Preferences.VCF_PATH + index));
249                 File sourceFile = job.getActualVideoDestination();
250                 
251                 String vDubBinary = this.appLayer.getPreferences().getProperty(this.getName(), Preferences.VIRTUAL_DUB_BINARY_PATH);
252                 shellString += '"' + vDubBinary.trim() + '"';
253                 
254                 shellString += " /s " + '"' + vcfFile.getAbsolutePath() + '"';
255                 
256                 boolean clearJobControl = Boolean.valueOf(jobSpecificSettings.getProperty(Preferences.CLEAR_JOBCONTROL, "true"));
257                 if (clearJobControl && firstValidVCF) {
258                         shellString += " /c";
259                 }
260                 
261                 String outputFilePath = this.getOutputFilePath(job, index);
262                 File outputFile = new File(outputFilePath);
263                 shellString += " /p " + '"' + sourceFile.getAbsolutePath() + '"';
264                 shellString += " " + '"' + outputFilePath + '"';
265                 
266                 boolean renderOutput = Boolean.valueOf(jobSpecificSettings.getProperty(Preferences.RENDER_OUTPUT, "true"));
267                 if (renderOutput) {
268                         shellString += " /r";
269                 }
270                 
271                 shellString += " /x";
272                 
273                 try {
274                         logWriter.write("Executing commandline: " + shellString);
275                         logWriter.newLine();
276                         File vdubDir = new File(vDubBinary).getParentFile();
277                         Process vDubProc;
278                         vDubProc = Runtime.getRuntime().exec(shellString, null, vdubDir);
279                         vDubProc.getOutputStream();
280                         InputStreamReader isr = new InputStreamReader(vDubProc.getInputStream());
281                         BufferedReader bufferedInputStream = new BufferedReader(isr);
282                         String currentLine;
283                         while ((currentLine = bufferedInputStream.readLine()) != null) {
284                                 logWriter.write(currentLine);
285                                 logWriter.newLine();
286                         }
287                         InputStreamReader isrErr = new InputStreamReader(vDubProc.getErrorStream());
288                         BufferedReader bufferedInputStreamErr = new BufferedReader(isrErr);
289                         while ((currentLine = bufferedInputStreamErr.readLine()) != null) {
290                                 logWriter.write(currentLine);
291                                 logWriter.newLine();
292                         }
293                         logWriter.close();
294                         
295                 } catch (IOException e) {
296                         throw new EncoderPluginException("I/O Exception occurred when trying to execute the VDub binary or logging output", e);
297                 }
298                 
299                 //extra options: replace original video with encoded one, possibly delete original one
300                 if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.EXTRA_OPTIONS))) {
301                         String useEncStringKey = Preferences.USE_ENCODED_VIDEO + index + Preferences.USE_ENCODED_VIDEO_2;
302                         String useEncVideo = jobSpecificSettings.getProperty(useEncStringKey);
303                         File origFile = job.getActualVideoDestination();
304                         if (useEncVideo != null && Boolean.valueOf(useEncVideo)) {
305                                 job.setActualVideoDestination(outputFile);
306                         }
307                         
308                         String deleteOrigFile = jobSpecificSettings.getProperty(Preferences.DELETE_ORIG_FILE + index);
309                         if (deleteOrigFile != null && Boolean.valueOf(deleteOrigFile)) {
310                                 //only delete the original file if the encoded one exists:
311                                 if (outputFile.exists() && outputFile.length() > 0) {
312                                         origFile.delete();
313                                 }
314                         }
315                 }
316         }
317         
318         private String getOutputFilePath(RecordJob job, int index) {
319                 File sourceFile = job.getActualVideoDestination();
320                 String ext = DemoRecorderUtils.getFileExtension(sourceFile);
321                 String outputFilePath;
322                 Properties jobSpecificSettings = job.getEncoderPluginSettings(this);
323                 if (Boolean.valueOf(this.appLayer.getPreferences().getProperty(this.getName(), Preferences.OUTPUT_FILE_MODE))) {
324                         //filechooser
325                         outputFilePath = jobSpecificSettings.getProperty(Preferences.OUTPUT_FILE + index);
326                 } else {
327                         //suffix
328                         outputFilePath = sourceFile.getAbsolutePath();
329                         String suffix = jobSpecificSettings.getProperty(Preferences.OUTPUT_SUFFIX + index);
330                         int idx = outputFilePath.indexOf("." + ext);
331                         outputFilePath = outputFilePath.substring(0, idx);
332                         outputFilePath += suffix + "." + ext;
333                 }
334                 
335                 return outputFilePath;
336         }
337         
338         private BufferedWriter getLogWriter(String jobName, int vcfIndex) throws EncoderPluginException {
339                 File logDir = DemoRecorderUtils.computeLocalFile(DemoRecorderApplication.LOGS_DIRNAME, "");
340                 if (jobName == null || jobName.equals("")) {
341                         jobName = "unnamed_job";
342                 }
343                 String path = logDir.getAbsolutePath() + File.separator + PLUGIN_NAME + '_' + jobName + '_' + "vcf" + vcfIndex + ".log";
344                 File logFile = new File(path);
345                 if (!DemoRecorderUtils.attemptFileCreation(logFile)) {
346                         throw new EncoderPluginException("Could not create log file for VDub job at location: " + path);
347                 }
348                 try {
349                         FileWriter fileWriter = new FileWriter(logFile);
350                         return new BufferedWriter(fileWriter);
351                 } catch (IOException e) {
352                         throw new EncoderPluginException("Could not create log file for VDub job at location: " + path, e);
353                 }
354         }
355 }