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
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
12 public class DemoCutter {
\r
14 private static final byte CDTRACK_SEPARATOR = 0x0A;
\r
16 private DataInputStream inStream;
\r
17 private DataOutputStream outStream;
\r
18 private File inFile;
\r
19 private File outFile;
\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
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
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
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
54 injectAfterCap = this.checkInjectString(injectAfterCap);
\r
55 injectAtStart = this.checkInjectString(injectAtStart);
\r
56 injectBeforeCap = this.checkInjectString(injectBeforeCap);
\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
67 float firstSvcTime = -1;
\r
68 float lastSvcTime = -1;
\r
72 DemoPacket demoPacket = new DemoPacket(this.inStream);
\r
73 if (demoPacket.isEndOfFile()) {
\r
77 if (demoPacket.isClientToServerPacket()) {
\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
89 if (demoPacket.getSvcTime() != -1) {
\r
90 svctime = demoPacket.getSvcTime();
\r
93 if (svctime != -1) {
\r
94 if (firstSvcTime == -1) {
\r
95 firstSvcTime = svctime;
\r
97 lastSvcTime = svctime;
\r
100 injectBuffer = "\011\n" + injectAtStart + ";slowmo " + ffwSpeedFirstStage + "\n\000";
\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
109 injectBuffer = "\011\nslowmo " + ffwSpeedSecondStage + "\n\000";
\r
114 if (demoStarted < 2 && svctime > (startTime - 5)) {
\r
115 injectBuffer = "\011\nslowmo 1;" + injectBeforeCap +"\n\000";
\r
118 if (demoStarted < 3 && svctime > startTime) {
\r
119 injectBuffer = "\011\ncl_capturevideo 1\n\000";
\r
122 if (!endIsReached && svctime > endTime) {
\r
123 injectBuffer = "\011\ncl_capturevideo 0\n\000";
\r
124 endIsReached = true;
\r
126 if (endIsReached && !finalInjectionDone && svctime > (endTime + 1)) {
\r
127 injectBuffer = "\011\n" + injectAfterCap + "\n\000";
\r
128 finalInjectionDone = true;
\r
130 if (finalInjectionDone && !disconnectIssued && svctime > (endTime + 2)) {
\r
131 injectBuffer = "\011\ndisconnect\n\000";
\r
132 disconnectIssued = true;
\r
137 byte[] injectBufferAsBytes = null;
\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
144 data = demoPacket.getOriginalData();
\r
145 if ((injectBufferAsBytes.length + data.length) < 65536) {
\r
146 data = DemoCutterUtils.mergeByteArrays(injectBufferAsBytes, data);
\r
150 byte[] newLengthLittleEndian = DemoCutterUtils.convertLittleEndian(data.length);
\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
161 if (startTime < firstSvcTime) {
\r
162 throw new DemoCutterException("Start time for the demo is " + startTime + ", but demo doesn't start before " + firstSvcTime);
\r
164 if (endTime > lastSvcTime) {
\r
165 throw new DemoCutterException("End time for the demo is " + endTime + ", but demo already stops at " + lastSvcTime);
\r
167 } catch (DemoCutterException e) {
\r
169 } catch (Throwable e) {
\r
170 throw new DemoCutterException("Internal error in demo cutter sub-route (invalid demo file?)", e);
\r
173 this.outStream.close();
\r
174 this.inStream.close();
\r
175 } catch (IOException e) {}
\r
182 * Seeks forward in the inStream until CDTRACK_SEPARATOR byte was reached.
\r
183 * All the content is copied to the outStream.
\r
185 private void readCDTrack() {
\r
188 while ((lastByte = inStream.readByte()) != CDTRACK_SEPARATOR) {
\r
189 this.outStream.write(lastByte);
\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
195 catch (IOException e) {
\r
196 throw new DemoCutterException("Unexpected I/O Exception occurred when reading CD track of demo " + inFile.getPath(), e);
\r
200 private void prepareStreams() {
\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
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
214 private String checkInjectString(String injectionString) {
\r
215 while (injectionString.endsWith(";") || injectionString.endsWith("\n")) {
\r
216 injectionString = injectionString.substring(0, injectionString.length()-1);
\r
218 return injectionString;
\r