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