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