]> de.git.xonotic.org Git - xonotic/xonotic.git/blob - misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/jobs/RecordJob.java
Merge commit 'origin/esteel/ignore'
[xonotic/xonotic.git] / misc / tools / NexuizDemoRecorder / main / src / main / java / com / nexuiz / demorecorder / application / jobs / RecordJob.java
1 package com.nexuiz.demorecorder.application.jobs;
2
3 import java.io.BufferedReader;
4 import java.io.File;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.io.InputStreamReader;
8 import java.io.Serializable;
9 import java.util.ArrayList;
10 import java.util.HashMap;
11 import java.util.List;
12 import java.util.Map;
13 import java.util.Properties;
14
15 import com.nexuiz.demorecorder.application.DemoRecorderApplication;
16 import com.nexuiz.demorecorder.application.DemoRecorderException;
17 import com.nexuiz.demorecorder.application.DemoRecorderUtils;
18 import com.nexuiz.demorecorder.application.NDRPreferences;
19 import com.nexuiz.demorecorder.application.DemoRecorderApplication.Preferences;
20 import com.nexuiz.demorecorder.application.democutter.DemoCutter;
21 import com.nexuiz.demorecorder.application.democutter.DemoCutterException;
22 import com.nexuiz.demorecorder.application.plugins.EncoderPlugin;
23 import com.nexuiz.demorecorder.application.plugins.EncoderPluginException;
24
25 public class RecordJob implements Runnable, Serializable {
26         
27         private static final long serialVersionUID = -4585637490345587912L;
28
29         public enum State {
30                 WAITING, PROCESSING, ERROR, ERROR_PLUGIN, DONE
31         }
32         
33         public static final String CUT_DEMO_FILE_SUFFIX = "_autocut";
34         public static final String CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE = "autocap";
35         public static final String CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE = "1234567";
36         protected static final String[] VIDEO_FILE_ENDINGS = {"avi", "ogv"};
37         
38         private DemoRecorderApplication appLayer;
39         protected String jobName;
40         private int jobIndex;
41         protected File enginePath;
42         protected String engineParameters;
43         protected File demoFile;
44         protected String relativeDemoPath;
45         protected File dpVideoPath;
46         protected File videoDestination;
47         protected String executeBeforeCap;
48         protected String executeAfterCap;
49         protected float startSecond;
50         protected float endSecond;
51         protected State state = State.WAITING;
52         protected DemoRecorderException lastException = null;
53         
54         /**
55          * Points to the actual final file, including possible suffixes, e.g. _copy1, and the actualy ending
56          */
57         protected File actualVideoDestination = null;
58         /**
59          * Map that identifies the plug-in by its name (String) and maps to the plug-in's job-specific settings
60          */
61         protected Map<String, Properties> encoderPluginSettings = new HashMap<String, Properties>();
62         
63         private List<File> cleanUpFiles = null;
64         
65         public RecordJob(
66                 DemoRecorderApplication appLayer,
67                 String jobName,
68                 int jobIndex,
69                 File enginePath,
70                 String engineParameters,
71                 File demoFile,
72                 String relativeDemoPath,
73                 File dpVideoPath,
74                 File videoDestination,
75                 String executeBeforeCap,
76                 String executeAfterCap,
77                 float startSecond,
78                 float endSecond
79         ) {
80                 this.appLayer = appLayer;
81                 this.jobName = jobName;
82                 this.jobIndex = jobIndex;
83                 
84                 this.setEnginePath(enginePath);
85                 this.setEngineParameters(engineParameters);
86                 this.setDemoFile(demoFile);
87                 this.setRelativeDemoPath(relativeDemoPath);
88                 this.setDpVideoPath(dpVideoPath);
89                 this.setVideoDestination(videoDestination);
90                 this.setExecuteBeforeCap(executeBeforeCap);
91                 this.setExecuteAfterCap(executeAfterCap);
92                 this.setStartSecond(startSecond);
93                 this.setEndSecond(endSecond);
94         }
95         
96         public RecordJob(){}
97         
98         /**
99          * Constructor that can be used by other classes such as job templates. Won't throw exceptions
100          * as it won't check the input for validity.
101          */
102         protected RecordJob(
103                 File enginePath,
104                 String engineParameters,
105                 File demoFile,
106                 String relativeDemoPath,
107                 File dpVideoPath,
108                 File videoDestination,
109                 String executeBeforeCap,
110                 String executeAfterCap,
111                 float startSecond,
112                 float endSecond
113                 ) {
114                 this.jobIndex = -1;
115                 this.enginePath = enginePath;
116                 this.engineParameters = engineParameters;
117                 this.demoFile = demoFile;
118                 this.relativeDemoPath = relativeDemoPath;
119                 this.dpVideoPath = dpVideoPath;
120                 this.videoDestination = videoDestination;
121                 this.executeBeforeCap = executeBeforeCap;
122                 this.executeAfterCap = executeAfterCap;
123                 this.startSecond = startSecond;
124                 this.endSecond = endSecond;
125         }
126         
127         public void execute() {
128                 if (this.state == State.PROCESSING) {
129                         return;
130                 }
131                 boolean errorOccurred = false;
132                 this.setState(State.PROCESSING);
133                 this.appLayer.fireUserInterfaceUpdate(this);
134                 cleanUpFiles = new ArrayList<File>();
135                 
136                 File cutDemo = computeCutDemoFile();
137                 cutDemo.delete(); //delete possibly old cutDemoFile
138                 
139                 EncoderPlugin recentEncoder = null;
140                 
141                 try {
142                         this.cutDemo(cutDemo);
143                         this.removeOldAutocaps();
144                         this.recordClip(cutDemo);
145                         this.moveRecordedClip();
146                         for (EncoderPlugin plugin : this.appLayer.getEncoderPlugins()) {
147                                 recentEncoder = plugin;
148                                 plugin.executeEncoder(this);
149                         }
150                 } catch (DemoRecorderException e) {
151                         errorOccurred = true;
152                         this.lastException = e;
153                         this.setState(State.ERROR);
154                 } catch (EncoderPluginException e) {
155                         errorOccurred = true;
156                         this.lastException = new DemoRecorderException("Encoder plug-in " + recentEncoder.getName() + " failed: "
157                                         + e.getMessage(), e);
158                         this.setState(State.ERROR_PLUGIN);
159                 } catch (Exception e) {
160                         errorOccurred = true;
161                         this.lastException = new DemoRecorderException("Executing job failed, click on details for more info", e);
162                 } finally {
163                         NDRPreferences preferences = this.appLayer.getPreferences();
164                         if (!Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DO_NOT_DELETE_CUT_DEMOS))) {
165                                 cleanUpFiles.add(cutDemo);
166                         }
167                         if (!errorOccurred) {
168                                 this.setState(State.DONE);
169                         }
170                         this.cleanUpFiles();
171                         this.appLayer.fireUserInterfaceUpdate(this);
172                         this.appLayer.saveJobQueue();
173                 }
174         }
175         
176         /**
177          * Will execute just the specified encoder plug-in on an already "done" job.
178          * @param pluginName
179          */
180         public void executePlugin(EncoderPlugin plugin) {
181                 if (this.getState() != State.DONE) {
182                         return;
183                 }
184                 this.setState(State.PROCESSING);
185                 this.appLayer.fireUserInterfaceUpdate(this);
186                 
187                 try {
188                         plugin.executeEncoder(this);
189                         this.setState(State.DONE);
190                 } catch (EncoderPluginException e) {
191                         this.lastException = new DemoRecorderException("Encoder plug-in " + plugin.getName() + " failed: "
192                                         + e.getMessage(), e);
193                         this.setState(State.ERROR_PLUGIN);
194                 }
195                 
196                 this.appLayer.fireUserInterfaceUpdate(this);
197         }
198         
199         private void cleanUpFiles() {
200                 try {
201                         for (File f : this.cleanUpFiles) {
202                                 f.delete();
203                         }
204                 } catch (Exception e) {}
205                 
206         }
207         
208         private void moveRecordedClip() {
209                 //1. Figure out whether the file is .avi or .ogv
210                 File sourceFile = null;
211                 for (String videoExtension : VIDEO_FILE_ENDINGS) {
212                         String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
213                         + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;
214                         File videoFile = new File(fileString);
215                         if (videoFile.exists()) {
216                                 sourceFile = videoFile;
217                                 break;
218                         }
219                 }
220                 
221                 if (sourceFile == null) {
222                         String p = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
223                         + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE;
224                         throw new DemoRecorderException("Could not locate the expected video file being generated by Nexuiz (should have been at "
225                                         + p + ".avi/.ogv");
226                 }
227                 cleanUpFiles.add(sourceFile);
228                 
229                 File destinationFile = null;
230                 NDRPreferences preferences = this.appLayer.getPreferences();
231                 String sourceFileExtension = DemoRecorderUtils.getFileExtension(sourceFile);
232                 String destinationFilePath = this.videoDestination + "." + sourceFileExtension;
233                 destinationFile = new File(destinationFilePath);
234                 if (destinationFile.exists()) {
235                         if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.OVERWRITE_VIDEO_FILE))) {
236                                 if (!destinationFile.delete()) {
237                                         throw new DemoRecorderException("Could not delete the existing video destinatin file " + destinationFile.getAbsolutePath()
238                                                         + " (application setting to overwrite existing video files is enabled!)");
239                                 }
240                         } else {
241                                 destinationFilePath = this.videoDestination + "_copy" + this.getVideoDestinationCopyNr(sourceFileExtension) + "." + sourceFileExtension;
242                                 destinationFile = new File(destinationFilePath);
243                         }
244                 }
245                 
246                 //finally move the file
247                 if (!sourceFile.renameTo(destinationFile)) {
248                         cleanUpFiles.add(destinationFile);
249                         throw new DemoRecorderException("Could not move the video file from " + sourceFile.getAbsolutePath()
250                                         + " to " + destinationFile.getAbsolutePath());
251                 }
252                 
253                 this.actualVideoDestination = destinationFile;
254         }
255         
256         /**
257          * As destination video files, e.g. "test"[.avi] can already exist, we have to save the
258          * the video file to a file name such as test_copy1 or test_copy2.
259          * This function will figure out what the number (1, 2....) is.
260          * @return
261          */
262         private int getVideoDestinationCopyNr(String sourceFileExtension) {
263                 int i = 1;
264                 File lastFile;
265                 while (true) {
266                         lastFile = new File(this.videoDestination + "_copy" + i + "." + sourceFileExtension);
267                         if (!lastFile.exists()) {
268                                 break;
269                         }
270                         
271                         i++;
272                 }
273                 return i;
274         }
275
276         private File computeCutDemoFile() {
277                 String origFileString = this.demoFile.getAbsolutePath();
278                 int lastIndex = origFileString.lastIndexOf(File.separator);
279                 String autoDemoFileName = origFileString.substring(lastIndex+1, origFileString.length());
280                 //strip .dem ending
281                 autoDemoFileName = autoDemoFileName.substring(0, autoDemoFileName.length()-4);
282                 autoDemoFileName = autoDemoFileName + CUT_DEMO_FILE_SUFFIX + ".dem";
283                 String finalString = origFileString.substring(0, lastIndex) + File.separator + autoDemoFileName;
284                 File f = new File(finalString);
285                 
286                 return f;
287         }
288         
289         private void cutDemo(File cutDemo) {
290                 String injectAtStart = "";
291                 String injectBeforeCap = "";
292                 String injectAfterCap = "";
293                 
294                 NDRPreferences preferences = this.appLayer.getPreferences();
295                 if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_RENDERING))) {
296                         injectAtStart += "r_render 0;";
297                         injectBeforeCap += "r_render 1;";
298                 }
299                 if (Boolean.valueOf(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.DISABLE_SOUND))) {
300                         injectAtStart += "set _volume $volume;volume 0;";
301                         injectBeforeCap += "set volume $_volume;";
302                 }
303                 injectBeforeCap += this.executeBeforeCap + "\n";
304                 injectBeforeCap += "set _cl_capturevideo_nameformat $cl_capturevideo_nameformat;set _cl_capturevideo_number $cl_capturevideo_number;";
305                 injectBeforeCap += "cl_capturevideo_nameformat " + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE + ";";
306                 injectBeforeCap += "cl_capturevideo_number " + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + ";";
307                 
308                 injectAfterCap += this.executeAfterCap + "\n";
309                 injectAfterCap += "cl_capturevideo_nameformat $_cl_capturevideo_nameformat;cl_capturevideo_number $_cl_capturevideo_number;";
310                 
311                 
312                 DemoCutter cutter = new DemoCutter();
313                 int fwdSpeedFirstStage, fwdSpeedSecondStage;
314                 try {
315                         fwdSpeedFirstStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_FIRST_STAGE));
316                         fwdSpeedSecondStage = Integer.parseInt(preferences.getProperty(NDRPreferences.MAIN_APPLICATION, Preferences.FFW_SPEED_SECOND_STAGE));
317                 } catch (NumberFormatException e) {
318                         throw new DemoRecorderException("Make sure that you specified valid numbers for the settings "
319                                         + Preferences.FFW_SPEED_FIRST_STAGE + " and " + Preferences.FFW_SPEED_SECOND_STAGE, e);
320                 }
321                 
322                 try {
323                         cutter.cutDemo(
324                                 this.demoFile,
325                                 cutDemo,
326                                 this.startSecond,
327                                 this.endSecond,
328                                 injectAtStart,
329                                 injectBeforeCap,
330                                 injectAfterCap,
331                                 fwdSpeedFirstStage,
332                                 fwdSpeedSecondStage
333                         );
334                 } catch (DemoCutterException e) {
335                         throw new DemoRecorderException("Error occurred while trying to cut the demo: " + e.getMessage(), e);
336                 }
337                 
338         }
339         
340         private void removeOldAutocaps() {
341                 for (String videoExtension : VIDEO_FILE_ENDINGS) {
342                         String fileString = this.dpVideoPath.getAbsolutePath() + File.separator + CUT_DEMO_CAPVIDEO_NAMEFORMAT_OVERRIDE
343                         + CUT_DEMO_CAPVIDEO_NUMBER_OVERRIDE + "." + videoExtension;
344                         File videoFile = new File(fileString);
345                         cleanUpFiles.add(videoFile);
346                         if (videoFile.exists()) {
347                                 if (!videoFile.delete()) {
348                                         throw new DemoRecorderException("Could not delete old obsolete video file " + fileString);
349                                 }
350                         }
351                 }
352         }
353         
354         private void recordClip(File cutDemo) {
355                 Process nexProc;
356                 String demoFileName = DemoRecorderUtils.getJustFileNameOfPath(cutDemo);
357                 String execPath = this.enginePath.getAbsolutePath() + " " + this.engineParameters + " -demo "
358                                                 + this.relativeDemoPath + "/" + demoFileName;
359                 File engineDir = this.enginePath.getParentFile();
360                 try {
361                         nexProc = Runtime.getRuntime().exec(execPath, null, engineDir);
362                         nexProc.getErrorStream();
363                         nexProc.getOutputStream();
364                         InputStream is = nexProc.getInputStream();
365                         InputStreamReader isr = new InputStreamReader(is);
366                         BufferedReader br = new BufferedReader(isr);
367                         while (br.readLine() != null) {
368                                 //System.out.println(line);
369                         }
370                 } catch (IOException e) {
371                         throw new DemoRecorderException("I/O Exception occurred when trying to execute the Nexuiz binary", e);
372                 }
373         }
374
375         public void run() {
376                 this.execute();
377         }
378         
379         public void setAppLayer(DemoRecorderApplication appLayer) {
380                 this.appLayer = appLayer;
381         }
382
383         public int getJobIndex() {
384                 return jobIndex;
385         }
386
387         public File getEnginePath() {
388                 return enginePath;
389         }
390
391         public void setEnginePath(File enginePath) {
392                 this.checkForProcessingState();
393                 if (enginePath == null || !enginePath.exists()) {
394                         throw new DemoRecorderException("Could not locate engine binary!");
395                 }
396                 if (!enginePath.canExecute()) {
397                         throw new DemoRecorderException("The file you specified is not executable!");
398                 }
399                 this.enginePath = enginePath.getAbsoluteFile();
400         }
401
402         public String getEngineParameters() {
403                 return engineParameters;
404         }
405
406         public void setEngineParameters(String engineParameters) {
407                 this.checkForProcessingState();
408                 if (engineParameters == null) {
409                         engineParameters = "";
410                 }
411                 this.engineParameters = engineParameters.trim();
412         }
413
414         public File getDemoFile() {
415                 return demoFile;
416         }
417
418         public void setDemoFile(File demoFile) {
419                 this.checkForProcessingState();
420                 if (demoFile == null) {
421                         throw new DemoRecorderException("Could not locate demo file!");
422                 }
423                 if (!demoFile.exists()) {
424                         throw new DemoRecorderException("Could not locate demo file!: " + demoFile.getAbsolutePath());
425                 }
426                 if (!doReadWriteTest(demoFile.getParentFile())) {
427                         throw new DemoRecorderException("The directory you specified for the demo to be recorded is not writable!");
428                 }
429                 if (!demoFile.getAbsolutePath().endsWith(".dem")) {
430                         throw new DemoRecorderException("The demo file you specified must have the ending .dem");
431                 }
432                 
433                 this.demoFile = demoFile.getAbsoluteFile();
434         }
435
436         public String getRelativeDemoPath() {
437                 return relativeDemoPath;
438         }
439
440         public void setRelativeDemoPath(String relativeDemoPath) {
441                 this.checkForProcessingState();
442                 if (relativeDemoPath == null) {
443                         relativeDemoPath = "";
444                 }
445                 
446                 //get rid of possible slashes
447                 while (relativeDemoPath.startsWith("/") || relativeDemoPath.startsWith("\\")) {
448                         relativeDemoPath = relativeDemoPath.substring(1, relativeDemoPath.length());
449                 }
450                 while (relativeDemoPath.endsWith("/") || relativeDemoPath.endsWith("\\")) {
451                         relativeDemoPath = relativeDemoPath.substring(0, relativeDemoPath.length() - 1);
452                 }
453                 
454                 this.relativeDemoPath = relativeDemoPath.trim();
455         }
456
457         public File getDpVideoPath() {
458                 return dpVideoPath;
459         }
460
461         public void setDpVideoPath(File dpVideoPath) {
462                 this.checkForProcessingState();
463                 if (dpVideoPath == null || !dpVideoPath.isDirectory()) {
464                         throw new DemoRecorderException("Could not locate the specified DPVideo directory!");
465                 }
466                 
467                 if (!this.doReadWriteTest(dpVideoPath)) {
468                         throw new DemoRecorderException("The DPVideo directory is not writable! It needs to be writable so that the file can be moved to its new location");
469                 }
470                 this.dpVideoPath = dpVideoPath.getAbsoluteFile();
471         }
472
473         public File getVideoDestination() {
474                 return videoDestination;
475         }
476
477         public void setVideoDestination(File videoDestination) {
478                 this.checkForProcessingState();
479                 //keep in mind, the parameter videoDestination points to the final avi/ogg file w/o extension!
480                 if (videoDestination == null || !videoDestination.getParentFile().isDirectory()) {
481                         throw new DemoRecorderException("Could not locate the specified video destination");
482                 }
483                 
484                 if (!this.doReadWriteTest(videoDestination.getParentFile())) {
485                         throw new DemoRecorderException("The video destination directory is not writable! It needs to be writable so that the file can be moved to its new location");
486                 }
487                 
488                 this.videoDestination = videoDestination.getAbsoluteFile();
489         }
490
491         public String getExecuteBeforeCap() {
492                 return executeBeforeCap;
493         }
494
495         public void setExecuteBeforeCap(String executeBeforeCap) {
496                 this.checkForProcessingState();
497                 if (executeBeforeCap == null) {
498                         executeBeforeCap = "";
499                 }
500                 executeBeforeCap = executeBeforeCap.trim();
501                 while (executeBeforeCap.endsWith(";")) {
502                         executeBeforeCap = executeBeforeCap.substring(0, executeBeforeCap.length()-1);
503                 }
504                 this.executeBeforeCap = executeBeforeCap;
505         }
506
507         public String getExecuteAfterCap() {
508                 return executeAfterCap;
509         }
510
511         public void setExecuteAfterCap(String executeAfterCap) {
512                 this.checkForProcessingState();
513                 if (executeAfterCap == null) {
514                         executeAfterCap = "";
515                 }
516                 executeAfterCap = executeAfterCap.trim();
517                 while (executeAfterCap.endsWith(";")) {
518                         executeAfterCap = executeAfterCap.substring(0, executeAfterCap.length()-1);
519                 }
520                 if (executeAfterCap.contains("cl_capturevideo_number") || executeAfterCap.contains("cl_capturevideo_nameformat")) {
521                         throw new DemoRecorderException("Execute after String cannot contain cl_capturevideo_number or _nameformat changes!");
522                 }
523                 this.executeAfterCap = executeAfterCap;
524         }
525
526         public float getStartSecond() {
527                 return startSecond;
528         }
529
530         public void setStartSecond(float startSecond) {
531                 this.checkForProcessingState();
532                 if (startSecond < 0) {
533                         throw new DemoRecorderException("Start second cannot be < 0");
534                 }
535                 this.startSecond = startSecond;
536         }
537
538         public float getEndSecond() {
539                 return endSecond;
540         }
541
542         public void setEndSecond(float endSecond) {
543                 this.checkForProcessingState();
544                 if (endSecond < this.startSecond) {
545                         throw new DemoRecorderException("End second cannot be < start second");
546                 }
547                 this.endSecond = endSecond;
548         }
549
550         public State getState() {
551                 return state;
552         }
553
554         public void setState(State state) {
555                 this.state = state;
556                 this.appLayer.fireUserInterfaceUpdate(this);
557         }
558
559         public String getJobName() {
560                 if (this.jobName == null || this.jobName.equals("")) {
561                         return "Job " + this.jobIndex;
562                 }
563                 return this.jobName;
564         }
565         
566         public void setJobName(String jobName) {
567                 if (jobName == null || jobName.equals("")) {
568                         this.jobIndex = appLayer.getNewJobIndex();
569                         this.jobName = "Job " + this.jobIndex;
570                 } else {
571                         this.jobName = jobName;
572                 }
573         }
574
575         public DemoRecorderException getLastException() {
576                 return lastException;
577         }
578         
579         /**
580          * Tests whether the given directory is writable by creating a file in there and deleting
581          * it again.
582          * @param directory
583          * @return true if directory is writable
584          */
585         protected boolean doReadWriteTest(File directory) {
586                 boolean writable = false;
587                 String fileName = "tmp." + Math.random()*10000 + ".dat";
588                 File tempFile = new File(directory, fileName);
589                 try {
590                         writable = tempFile.createNewFile();
591                         if (writable) {
592                                 tempFile.delete();
593                         }
594                 } catch (IOException e) {
595                         writable = false;
596                 }
597                 return writable;
598         }
599         
600         private void checkForProcessingState() {
601                 if (this.state == State.PROCESSING) {
602                         throw new DemoRecorderException("Cannot modify this job while it is processing!");
603                 }
604         }
605
606         public Properties getEncoderPluginSettings(EncoderPlugin plugin) {
607                 if (this.encoderPluginSettings.containsKey(plugin.getName())) {
608                         return this.encoderPluginSettings.get(plugin.getName());
609                 } else {
610                         return new Properties();
611                 }
612         }
613
614         public void setEncoderPluginSetting(String pluginName, String pluginSettingKey, String value) {
615                 Properties p = this.encoderPluginSettings.get(pluginName);
616                 if (p == null) {
617                         p = new Properties();
618                         this.encoderPluginSettings.put(pluginName, p);
619                 }
620                 
621                 p.put(pluginSettingKey, value);
622         }
623
624         public Map<String, Properties> getEncoderPluginSettings() {
625                 return encoderPluginSettings;
626         }
627
628         public void setEncoderPluginSettings(Map<String, Properties> encoderPluginSettings) {
629                 this.encoderPluginSettings = encoderPluginSettings;
630         }
631
632         public File getActualVideoDestination() {
633                 return actualVideoDestination;
634         }
635         
636         public void setActualVideoDestination(File actualVideoDestination) {
637                 this.actualVideoDestination = actualVideoDestination;
638         }
639 }