1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.xnap.plugin.viewer.videoinfo;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.RandomAccessFile;
25
26 import org.apache.log4j.Logger;
27 import org.xnap.util.Formatter;
28
29 /***
30 * Class to extract a video's play length and resolution.
31 *
32 * The video parsing code for avi and mpeg files is taken from the ziga
33 * project. See README for more information.
34 * The video parsing code for asf files is taken from the avifile project
35 * (http://avifile.sourceforge.net/).
36 */
37
38 public class VideoFile extends File
39 {
40
41
42
43 protected static final int PACK_START_CODE = 0x000001BA;
44 protected static final int SEQ_START_CODE = 0x000001B3;
45 protected static final int GROUP_START_CODE = 0x000001B8;
46 protected static final int MAX_FORWARD_READ_LENGTH = 50000;
47 protected static final int MAX_BACKWARD_READ_LENGTH = 3000000;
48
49 protected static final int ASF_FILE_PROPERTIES_SIZE = 80;
50 protected static final int ASF_STREAM_PROPERTIES_SIZE = 1024;
51 protected static final int GUID_SIZE = 16;
52
53 protected final transient GUID ASF_HEADER_GUID =
54 new GUID(0x75b22630, 0x668e, 0x11cf,
55 new byte[] { (byte)0xa6, (byte)0xd9, (byte)0x00, (byte)0xaa,
56 (byte)0x00, (byte)0x62, (byte)0xce,
57 (byte)0x6c });
58
59 protected final transient GUID ASF_FILE_PROPERTIES_GUID =
60 new GUID(0x8cabdca1, 0xa947, 0x11cf,
61 new byte[] { (byte)0x8e, (byte)0xe4, (byte)0x00, (byte)0xc0,
62 (byte)0x0c, (byte)0x20, (byte)0x53,
63 (byte)0x65 });
64
65 protected final transient GUID ASF_STREAM_PROPERTIES_GUID =
66 new GUID(0xb7dc0791, 0xa9b7, 0x11cf,
67 new byte[] { (byte)0x8e, (byte)0xe6, (byte)0x00, (byte)0xc0,
68 (byte)0x0c, (byte)0x20, (byte)0x53,
69 (byte)0x65 });
70
71 protected final transient GUID ASF_AUDIO_MEDIA_GUID =
72 new GUID(0xf8699e40, 0x5b4d, 0x11cf,
73 new byte[] { (byte)0xa8, (byte)0xfd, (byte)0x00, (byte)0x80,
74 (byte)0x5f, (byte)0x5c, (byte)0x44,
75 (byte)0x2b });
76
77 protected final transient GUID ASF_CODEC_LIST_GUID =
78 new GUID(0x86d15240, 0x311d, 0x11d0,
79 new byte[] { (byte)0xa3, (byte)0xa4, (byte)0x00, (byte)0xa0,
80 (byte)0xc9, (byte)0x03, (byte)0x48,
81 (byte)0xf6 });
82
83
84
85 private int height = -1;
86 private int length = -1;
87 private int width = -1;
88
89 private static Logger logger = Logger.getLogger(VideoFile.class);
90
91 private transient RandomAccessFile raf;
92 private transient ASFCodecList codecList;
93
94
95
96 public VideoFile(File f)
97 {
98 super(f, "");
99 }
100
101
102
103 public boolean parse()
104 {
105 boolean videoInfoFound = false;
106
107
108 logger.debug("VideoFile: parsing " + getName());
109
110 try {
111 raf = new RandomAccessFile(this, "r");
112
113 videoInfoFound = lookForAVI();
114
115 if (!videoInfoFound) {
116 videoInfoFound = lookForMPEG();
117 }
118 if (!videoInfoFound) {
119 videoInfoFound = lookForASF();
120 }
121 }
122 catch (IOException ie) {
123 logger.debug("VideoFile " + ie);
124 }
125 finally {
126 try {
127 raf.close();
128 }
129 catch (IOException ie) {
130 }
131 }
132
133 return videoInfoFound;
134 }
135
136 public int getHeight()
137 {
138 return height;
139 }
140
141 public String getInfo()
142 {
143 return Formatter.formatLength(getLength());
144 }
145
146 public int getLength()
147 {
148 return length;
149 }
150
151 public int getWidth()
152 {
153 return width;
154 }
155
156 protected boolean lookForAVI() throws IOException
157 {
158 boolean avihFound = false;
159 raf.seek(0);
160
161 String s = readChunk();
162
163 if (s.equals("RIFF")) {
164 raf.skipBytes(4);
165 s = readChunk();
166
167 if (s.equals("AVI ")) {
168 while (true) {
169 s = readChunk();
170 raf.skipBytes(4);
171
172 if (s.equals("LIST")) {
173 s = readChunk();
174 if (s.equals("hdrl")) {
175 s = readChunk();
176 int avihLength = readBigEndianInt();
177 if (s.equals("avih") && avihLength >= 56) {
178 double microSecPerFrame = readBigEndianInt();
179 raf.skipBytes(12);
180 double totalFrames = readBigEndianInt();
181
182 length = (int) (totalFrames * (microSecPerFrame / 1000000L));
183 raf.skipBytes(12);
184
185 width = readBigEndianInt();
186 height = readBigEndianInt();
187 raf.skipBytes(avihLength - 40);
188 avihFound = true;
189 }
190 }
191 }
192 else {
193 break;
194 }
195 }
196 }
197 }
198
199 return avihFound;
200 }
201
202 protected boolean lookForASF() throws IOException
203 {
204 boolean haveMainHeader = false;
205 boolean haveStreamHeader = false;
206 boolean haveCodecListHeader = false;
207 ASFMainHeader mainHeader = null;
208 ASFStreamHeader streamHeader = null;
209
210 raf.seek(0);
211
212 logger.debug("looking for asf");
213
214 while (!haveMainHeader || !haveStreamHeader || !haveCodecListHeader) {
215 GUID guid;
216 long size;
217
218 guid = new GUID();
219
220 size = readBigEndianLong() - 24;
221
222 logger.debug("GUID " + guid);
223 logger.debug("size " + size);
224
225 if (size < 0)
226 return false;
227
228 if (guid.equals(ASF_HEADER_GUID)) {
229 logger.debug("Found header, skipping 6 bytes");
230 raf.skipBytes(6);
231 }
232 else if (guid.equals(ASF_FILE_PROPERTIES_GUID)) {
233 logger.debug("Found asf main header");
234 if (size < ASF_FILE_PROPERTIES_SIZE) {
235 logger.debug("main header too small");
236 return false;
237 }
238
239 mainHeader = new ASFMainHeader();
240 raf.skipBytes((int) (size - ASF_FILE_PROPERTIES_SIZE));
241 haveMainHeader = true;
242 }
243 else if (guid.equals(ASF_STREAM_PROPERTIES_GUID)) {
244 logger.debug("Found stream props");
245 streamHeader = new ASFStreamHeader(size);
246 if (size > streamHeader.count) {
247 raf.skipBytes((int) (size - streamHeader.count));
248 }
249 haveStreamHeader = true;
250 }
251 else if (guid.equals(ASF_CODEC_LIST_GUID)) {
252 logger.debug("Found codec list header");
253 codecList = new ASFCodecList(size);
254 haveCodecListHeader = true;
255 }
256 else {
257 logger.debug("Unknown packet, skipping " + size);
258 raf.skipBytes((int)size);
259 }
260 }
261
262 if (haveMainHeader && haveStreamHeader && haveCodecListHeader) {
263
264 length = (int)(mainHeader.play_time / 10000000L);
265
266 logger.debug("asf length " + length);
267
268 if (streamHeader.videoHeader) {
269 height = streamHeader.height;
270 width = streamHeader.width;
271 }
272
273 return true;
274 }
275
276 return false;
277 }
278
279 protected boolean lookForMPEG() throws IOException
280 {
281 raf.seek(0);
282
283 if (nextStartCode()) {
284 if (raf.readInt() == PACK_START_CODE) {
285 byte[] b = new byte[6];
286 Long initialSCR = null;
287
288 raf.readFully(b);
289
290 if ((b[0] & 0xF0) == 0x20) {
291 initialSCR = getMPEGSCR(b);
292 }
293 else if ((b[0] & 0xC0) == 0x40) {
294 initialSCR = getMPEG2SCR(b);
295 }
296
297 boolean seqStartCodeFound = false;
298 while (true) {
299 if (nextStartCode()) {
300 if(raf.readInt() == SEQ_START_CODE) {
301 seqStartCodeFound = true;
302 break;
303 }
304 }
305 else {
306 break;
307 }
308 }
309 if (seqStartCodeFound) {
310 b = new byte[3];
311 raf.readFully(b);
312
313 width = (((b[0] & 0xff) << 4) | (b[1] & 0xf0));
314
315
316 height = (((b[1] & 0x0f) << 8) | (b[2] & 0xff));
317
318 boolean groupStartCodeFound = false;
319 raf.seek(raf.length());
320 while (true) {
321 if (previousStartCode()) {
322 if(raf.readInt() == PACK_START_CODE) {
323 groupStartCodeFound = true;
324 break;
325 }
326 }
327 else {
328 break;
329 }
330 raf.seek(raf.getFilePointer() - 4);
331 }
332
333 if (groupStartCodeFound) {
334 b = new byte[6];
335 Long lastSCR = null;
336
337 raf.readFully(b);
338
339 if ((b[0] & 0xF0) == 0x20) {
340 lastSCR = getMPEGSCR(b);
341 }
342 else if ((b[0] & 0xC0) == 0x40) {
343 lastSCR = getMPEG2SCR(b);
344 }
345
346 if (initialSCR != null && lastSCR != null) {
347 length = (int) (lastSCR.longValue()
348 - initialSCR.longValue());
349 }
350 }
351
352 return true;
353 }
354 }
355 }
356 return false;
357 }
358
359 protected final String readChunk() throws IOException
360 {
361 byte[] b = new byte[4];
362 raf.readFully(b);
363 return new String(b);
364 }
365
366 protected final short readBigEndianShort() throws IOException
367 {
368 byte[] b = new byte[2];
369 raf.readFully(b);
370 return (short) ((b[0] & 0xff) | ((b[1] & 0xff) << 8));
371 }
372
373 protected final int readBigEndianInt() throws IOException
374 {
375 byte[] b = new byte[4];
376 raf.readFully(b);
377 return (b[0] & 0xff) | ((b[1] & 0xff) << 8) | ((b[2] & 0xff) << 16)
378 | ((b[3] & 0xff) << 24);
379 }
380
381 protected final long readBigEndianLong() throws IOException
382 {
383 return (readBigEndianInt() | (readBigEndianInt() << 32));
384 }
385
386
387 protected Long getMPEGSCR(byte[] b)
388 {
389 long scr;
390
391
392 long low4Bytes = (((b[0] & 0xff) >> 1) & 0x03) << 30
393 |(b[1] & 0xff) << 22 | ((b[2] & 0xff) >> 1) << 15 |
394 (b[3] & 0xff) << 7 | (b[4] & 0xff) >> 1;
395
396 scr = low4Bytes;
397 scr /= 90000;
398
399 return new Long(scr);
400 }
401
402 protected Long getMPEG2SCR(byte[] b)
403 {
404 long scr;
405
406
407
408 long low4Bytes = ((b[0] & 0x18) >> 3) << 30 | (b[0] & 0x03) << 28
409 | (b[1] & 0xff) << 20 | ((b[2] & 0xF8) >> 1) << 15 |
410 (b[2] & 0x03) << 13 | (b[3] & 0xff) << 5 | (b[4] & 0xff) >> 3;
411
412 int sys_clock_extension=(b[4] & 0x3) << 7 | ((b[5] & 0xff) >> 1);
413
414 scr = low4Bytes;
415 if (sys_clock_extension == 0) {
416 scr /= 90000;
417 return new Long(scr);
418 }
419 else {
420
421 return null;
422 }
423 }
424
425 protected boolean nextStartCode()
426 {
427 byte[] b = new byte[1024];
428 int available;
429 boolean startCodeFound = false;
430
431 try {
432 for (int i = 0; i < MAX_FORWARD_READ_LENGTH && !startCodeFound;
433 i += available) {
434
435 available = raf.read(b);
436 if (available > 0) {
437 i += available;
438 for (int offset = 0; offset < available-2; offset++) {
439 if (b[offset] == 0 && b[offset+1] == 0 && b[offset+2] == 1) {
440 raf.seek(raf.getFilePointer() - (available-offset));
441 startCodeFound = true;
442 break;
443 }
444 }
445 }
446 else {
447 break;
448 }
449 }
450 }
451 catch(IOException e) {
452 }
453 return startCodeFound;
454 }
455
456 protected boolean previousStartCode()
457 {
458 byte[] b = new byte[8024];
459 boolean startCodeFound = false;
460
461 try {
462 for (int i = 0; i < MAX_BACKWARD_READ_LENGTH && !startCodeFound;
463 i += b.length) {
464 long fp = raf.getFilePointer() - b.length;
465 if (fp < 0) {
466 if (fp <= b.length) {
467 break;
468 }
469 fp = 0;
470 }
471 raf.seek(fp);
472 raf.readFully(b);
473 for (int offset = b.length-1; offset > 1; offset--) {
474 if (b[offset-2] == 0 && b[offset-1] == 0
475 && b[offset] == 1) {
476 raf.seek(raf.getFilePointer() - (b.length-offset) - 2);
477 startCodeFound = true;
478 break;
479 }
480 }
481 if (!startCodeFound) {
482 raf.seek(raf.getFilePointer() - b.length);
483 }
484 }
485 } catch(IOException e) {
486 }
487 return startCodeFound;
488 }
489
490 protected class GUID
491 {
492 public int f1;
493 public short f2;
494 public short f3;
495 public byte[] f4;
496
497 public GUID() throws IOException
498 {
499 f4 = new byte[8];
500
501 f1 = readBigEndianInt();
502 f2 = readBigEndianShort();
503 f3 = readBigEndianShort();
504
505 for (int i = 0; i < f4.length; i++) {
506 f4[i] = raf.readByte();
507 }
508
509 }
510
511 public GUID(int f1, int f2, int f3, byte[] f4)
512 {
513 this.f1 = f1;
514 this.f2 = (short) f2;
515 this.f3 = (short) f3;
516 this.f4 = f4;
517 }
518
519
520 public boolean equals(Object o)
521 {
522 if (! (o instanceof GUID)) {
523 return false;
524 }
525
526 GUID g = (GUID) o;
527
528 if (f1 != g.f1 || f2 != g.f2 || f3 != g.f3) {
529 return false;
530 }
531
532 for (int i = 0; i < f4.length; i++) {
533 if (f4[i] != g.f4[i])
534 return false;
535 }
536
537 return true;
538 }
539
540 public String toString()
541 {
542 return "f1 " + f1 + "\n"
543 + "f2 " + f2 + "\n"
544 + "f3 " + f3 + "\n";
545 }
546 }
547
548 protected class ASFMainHeader
549 {
550 public GUID guid;
551 long file_size;
552
553 long create_time;
554
555 long pkts_count;
556
557 public long play_time;
558
559 long send_time;
560
561 int preroll;
562
563 int ignore;
564 int flags;
565
566
567 int min_pktsize;
568
569 int max_pktsize;
570
571 int max_bitrate;
572
573
574 public ASFMainHeader() throws IOException
575 {
576 guid = new GUID();
577
578 file_size = readBigEndianLong();
579 create_time = readBigEndianLong();
580 pkts_count = readBigEndianLong();
581 play_time = readBigEndianLong();
582 send_time = readBigEndianLong();
583
584 preroll = readBigEndianInt();
585 ignore = readBigEndianInt();
586 flags = readBigEndianInt();
587 min_pktsize = readBigEndianInt();
588 max_pktsize = readBigEndianInt();
589 max_bitrate = readBigEndianInt();
590 }
591
592 }
593
594 protected class ASFStreamHeader
595 {
596 public GUID stream_guid;
597 public GUID error_guid;
598 public long time_offset;
599 public int stream_size;
600 public int error_size;
601 public short stream;
602 public int reserved;
603
604
605
606
607
608
609 public int width;
610 public int height;
611 public byte flags;
612 public short data_size;
613
614
615 public int count = 0;
616 public boolean videoHeader = false;
617
618 public ASFStreamHeader(long size) throws IOException
619 {
620 if (count < size - GUID_SIZE) {
621 stream_guid = new GUID();
622 count += GUID_SIZE;
623 }
624 if (count < size - GUID_SIZE) {
625 error_guid = new GUID();
626 count += GUID_SIZE;
627 }
628
629 if (count < size - 8) {
630 time_offset = readBigEndianLong();
631 count += 8;
632 }
633 if (count < size - 4) {
634 stream_size = readBigEndianInt();
635 count += 4;
636 }
637 if (count < size - 4) {
638 error_size = readBigEndianInt();
639 count += 4;
640 }
641 if (count < size - 2) {
642 stream = readBigEndianShort();
643 count += 2;
644 }
645 if (count < size - 4) {
646 reserved = readBigEndianInt();
647 count += 4;
648 }
649
650 if (!stream_guid.equals(ASF_AUDIO_MEDIA_GUID)) {
651 videoHeader = true;
652 if (count < size - 4) {
653 width = readBigEndianInt();
654 count += 4;
655 }
656 if (count < size - 4) {
657 height = readBigEndianInt();
658 count += 4;
659 }
660 if (count < size - 1) {
661 flags = raf.readByte();
662 count += 1;
663 }
664 if (count < size - 2) {
665 data_size = readBigEndianShort();
666 count += 2;
667 }
668 }
669 }
670 }
671
672 protected class ASFCodecList
673 {
674 public final String[] names = { "Codec Name", "Codec Description",
675 "Information" };
676
677 public ASFCodecList(long size) throws IOException
678 {
679 byte[] buffer = new byte[(int)size];
680
681 raf.readFully(buffer);
682
683 StringBuffer sb = new StringBuffer(buffer.length);
684 StringBuffer sbcount = new StringBuffer(buffer.length);
685 for (int i = 0; i < buffer.length; i++) {
686 sb.append((char) buffer[i]);
687 sbcount.append(i % 10);
688 }
689
690 logger.debug(sb);
691 logger.debug(sbcount);
692
693 int count = (buffer[16] & 0xFF) | ((buffer[17] & 0xFF) << 8)
694 | ((buffer[18] & 0xFF) << 16) | ((buffer[19] & 0xFF) << 32);
695
696 logger.debug("count " + count);
697
698 int index = 20;
699
700 for (int i = 0; i < count; i++) {
701 short ctype = getShort(buffer, index);
702 index += 2;
703 logger.debug("ASF reader Codec Type: " + ctype);
704
705 for (int j = 0; j < 3; j++) {
706 short csize = getShort(buffer, index);
707 index += 2;
708 if (j < 2) {
709 csize *= 2;
710 String s = getString(buffer, index, csize);
711 logger.debug(names[j] + " " + s);
712 }
713 index += csize;
714 }
715 }
716 }
717
718 public short getShort(byte[] buffer, int offset)
719 {
720 return (short) ((buffer[offset] & 0xFF)
721 | ((buffer[offset + 1] & 0xFF) << 8));
722 }
723
724 public String getString(byte[] buffer, int offset, int length)
725 {
726 StringBuffer sb = new StringBuffer(length);
727
728 for (int i = 0; i < length && buffer[i] != '\0'; i += 2) {
729 sb.append((char) buffer[i]);
730 }
731
732 return sb.toString();
733 }
734
735 }
736 }