]> git.xonotic.org Git - xonotic/xonotic.git/blob - misc/tools/NexuizDemoRecorder/main/src/main/java/com/nexuiz/demorecorder/application/democutter/DemoCutter.java
initial checkin from nexuiz svn r8756
[xonotic/xonotic.git] / misc / tools / NexuizDemoRecorder / main / src / main / java / com / nexuiz / demorecorder / application / democutter / DemoCutter.java
1 package com.nexuiz.demorecorder.application.democutter;\r
2 import java.io.DataInputStream;\r
3 import java.io.DataOutputStream;\r
4 import java.io.EOFException;\r
5 import java.io.File;\r
6 import java.io.FileInputStream;\r
7 import java.io.FileNotFoundException;\r
8 import java.io.FileOutputStream;\r
9 import java.io.IOException;\r
10 import java.io.UnsupportedEncodingException;\r
11 \r
12 public class DemoCutter {\r
13 \r
14         private static final byte CDTRACK_SEPARATOR = 0x0A;\r
15 \r
16         private DataInputStream inStream;\r
17         private DataOutputStream outStream;\r
18         private File inFile;\r
19         private File outFile;\r
20 \r
21         /**\r
22          * Calls the cutDemo method with reasonable default values for the second and first fast-forward stage.\r
23          * @param inFile @see other cutDemo method\r
24          * @param outFile @see other cutDemo method\r
25          * @param startTime @see other cutDemo method\r
26          * @param endTime @see other cutDemo method\r
27          * @param injectAtStart @see other cutDemo method\r
28          * @param injectBeforeCap @see other cutDemo method\r
29          * @param injectAfterCap @see other cutDemo method\r
30          */\r
31         public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap) {\r
32                 this.cutDemo(inFile, outFile, startTime, endTime, injectAtStart, injectBeforeCap, injectAfterCap, 100, 10);\r
33         }\r
34         \r
35         /**\r
36          * Cuts the demo by injecting a 2-phase fast forward command until startTime is reached, then injects the cl_capturevideo 1 command\r
37          * and once endTime is reached the cl_capturevideo 0 command is injected.\r
38          * @param inFile the original demo file\r
39          * @param outFile the new cut demo file\r
40          * @param startTime when to start capturing (use the gametime in seconds)\r
41          * @param endTime when to stop capturing\r
42          * @param injectAtStart a String that will be injected right at the beginning of the demo\r
43          *                                              can be anything that would make sense and can be parsed by DP's console\r
44          * @param injectBeforeCap a String that will be injected 5 seconds before capturing starts\r
45          * @param injectAfterCap a String that will be injected shortly after capturing ended\r
46          * @param ffwSpeedFirstStage fast-forward speed at first stage, when the startTime is still about a minute away (use high values, e.g. 100)\r
47          * @param ffwSpeedSecondStage fast-forward speed when coming a few seconds close to startTime, use lower values e.g. 5 or 10\r
48          */\r
49         public void cutDemo(File inFile, File outFile, float startTime, float endTime, String injectAtStart, String injectBeforeCap, String injectAfterCap, int ffwSpeedFirstStage, int ffwSpeedSecondStage) {\r
50                 this.inFile = inFile;\r
51                 this.outFile = outFile;\r
52                 this.prepareStreams();\r
53                 this.readCDTrack();\r
54                 injectAfterCap = this.checkInjectString(injectAfterCap);\r
55                 injectAtStart = this.checkInjectString(injectAtStart);\r
56                 injectBeforeCap = this.checkInjectString(injectBeforeCap);\r
57 \r
58                 byte[] data;\r
59                 float svctime = -1;\r
60                 boolean firstLoop = true;\r
61                 String injectBuffer = "";\r
62                 int demoStarted = 0;\r
63                 boolean endIsReached = false;\r
64                 boolean finalInjectionDone = false;\r
65                 boolean disconnectIssued = false;\r
66                 int svcLoops = 0;\r
67                 float firstSvcTime = -1;\r
68                 float lastSvcTime = -1;\r
69                 \r
70                 try {\r
71                         while (true) {\r
72                                 DemoPacket demoPacket = new DemoPacket(this.inStream);\r
73                                 if (demoPacket.isEndOfFile()) {\r
74                                         break;\r
75                                 }\r
76                                 \r
77                                 if (demoPacket.isClientToServerPacket()) {\r
78                                         try {\r
79                                                 this.outStream.write(demoPacket.getOriginalLengthAsByte());\r
80                                                 this.outStream.write(demoPacket.getAngles());\r
81                                                 this.outStream.write(demoPacket.getOriginalData());\r
82                                         } catch (IOException e) {\r
83                                                 throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);\r
84                                         }\r
85                                         \r
86                                         continue;\r
87                                 }\r
88 \r
89                                 if (demoPacket.getSvcTime() != -1) {\r
90                                         svctime = demoPacket.getSvcTime();\r
91                                 }\r
92 \r
93                                 if (svctime != -1) {\r
94                                         if (firstSvcTime == -1) {\r
95                                                 firstSvcTime = svctime;\r
96                                         }\r
97                                         lastSvcTime = svctime;\r
98                                         \r
99                                         if (firstLoop) {\r
100                                                 injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedFirstStage + "\n\000";\r
101                                                 firstLoop = false;\r
102                                         }\r
103                                         if (demoStarted < 1 && svctime > (startTime - 50)) {\r
104                                                 if (svcLoops == 0) {\r
105                                                         //make sure that for short demos (duration less than 50 sec)\r
106                                                         //the injectAtStart is still honored\r
107                                                         injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedSecondStage + "\n\000";\r
108                                                 } else {\r
109                                                         injectBuffer = "\011\nslowmo " + ffwSpeedSecondStage + "\n\000";\r
110                                                 }\r
111                                                 \r
112                                                 demoStarted = 1;\r
113                                         }\r
114                                         if (demoStarted < 2 && svctime > (startTime - 5)) {\r
115                                                 injectBuffer = "\011\nslowmo 1;" + injectBeforeCap +"\n\000";\r
116                                                 demoStarted = 2;\r
117                                         }\r
118                                         if (demoStarted < 3 && svctime > startTime) {\r
119                                                 injectBuffer = "\011\ncl_capturevideo 1\n\000";\r
120                                                 demoStarted = 3;\r
121                                         }\r
122                                         if (!endIsReached && svctime > endTime) {\r
123                                                 injectBuffer = "\011\ncl_capturevideo 0\n\000";\r
124                                                 endIsReached = true;\r
125                                         }\r
126                                         if (endIsReached && !finalInjectionDone && svctime > (endTime + 1)) {\r
127                                                 injectBuffer = "\011\n" + injectAfterCap + "\n\000";\r
128                                                 finalInjectionDone = true;\r
129                                         }\r
130                                         if (finalInjectionDone && !disconnectIssued && svctime > (endTime + 2)) {\r
131                                                 injectBuffer = "\011\ndisconnect\n\000";\r
132                                                 disconnectIssued = true;\r
133                                         }\r
134                                         svcLoops++;\r
135                                 }\r
136 \r
137                                 byte[] injectBufferAsBytes = null;\r
138                                 try {\r
139                                         injectBufferAsBytes = injectBuffer.getBytes("US-ASCII");\r
140                                 } catch (UnsupportedEncodingException e) {\r
141                                         throw new DemoCutterException("Could not convert String to bytes using US-ASCII charset!", e);\r
142                                 }\r
143 \r
144                                 data = demoPacket.getOriginalData();\r
145                                 if ((injectBufferAsBytes.length + data.length) < 65536) {\r
146                                         data = DemoCutterUtils.mergeByteArrays(injectBufferAsBytes, data);\r
147                                         injectBuffer = "";\r
148                                 }\r
149                                 \r
150                                 byte[] newLengthLittleEndian = DemoCutterUtils.convertLittleEndian(data.length);\r
151                                 try {\r
152                                         this.outStream.write(newLengthLittleEndian);\r
153                                         this.outStream.write(demoPacket.getAngles());\r
154                                         this.outStream.write(data);\r
155                                 } catch (IOException e) {\r
156                                         throw new DemoCutterException("Unexpected I/O Exception occurred when writing to the cut demo", e);\r
157                                 }\r
158 \r
159                         }\r
160                         \r
161                         if (startTime < firstSvcTime) {\r
162                                 throw new DemoCutterException("Start time for the demo is " + startTime + ", but demo doesn't start before " + firstSvcTime);\r
163                         }\r
164                         if (endTime > lastSvcTime) {\r
165                                 throw new DemoCutterException("End time for the demo is " + endTime + ", but demo already stops at " + lastSvcTime);\r
166                         }\r
167                 } catch (DemoCutterException e) {\r
168                         throw e;\r
169                 } catch (Throwable e) {\r
170                         throw new DemoCutterException("Internal error in demo cutter sub-route (invalid demo file?)", e);\r
171                 } finally {\r
172                         try {\r
173                                 this.outStream.close();\r
174                                 this.inStream.close();\r
175                         } catch (IOException e) {}\r
176                 }\r
177         }\r
178 \r
179         \r
180 \r
181         /**\r
182          * Seeks forward in the inStream until CDTRACK_SEPARATOR byte was reached.\r
183          * All the content is copied to the outStream.\r
184          */\r
185         private void readCDTrack() {\r
186                 byte lastByte;\r
187                 try {\r
188                         while ((lastByte = inStream.readByte()) != CDTRACK_SEPARATOR) {\r
189                                 this.outStream.write(lastByte);\r
190                         }\r
191                         this.outStream.write(CDTRACK_SEPARATOR);\r
192                 } catch (EOFException e) {\r
193                         throw new DemoCutterException("Unexpected EOF occurred when reading CD track of demo " + inFile.getPath(), e);\r
194                 }\r
195                 catch (IOException e) {\r
196                         throw new DemoCutterException("Unexpected I/O Exception occurred when reading CD track of demo " + inFile.getPath(), e);\r
197                 }\r
198         }\r
199 \r
200         private void prepareStreams() {\r
201                 try {\r
202                         this.inStream = new DataInputStream(new FileInputStream(this.inFile));\r
203                 } catch (FileNotFoundException e) {\r
204                         throw new DemoCutterException("Could not open demo file " + inFile.getPath(), e);\r
205                 }\r
206                 \r
207                 try {\r
208                         this.outStream = new DataOutputStream(new FileOutputStream(this.outFile));\r
209                 } catch (FileNotFoundException e) {\r
210                         throw new DemoCutterException("Could not open demo file " + outFile.getPath(), e);\r
211                 }\r
212         }\r
213         \r
214         private String checkInjectString(String injectionString) {\r
215                 while (injectionString.endsWith(";") || injectionString.endsWith("\n")) {\r
216                         injectionString = injectionString.substring(0, injectionString.length()-1);\r
217                 }\r
218                 return injectionString;\r
219         }\r
220 }\r