View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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      // --- Constant(s) ---
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      // --- Data Field(s) ---
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      // --- Constructor(s) ---
95  
96      public VideoFile(File f)
97      {
98  		super(f, "");
99      }
100 
101     // --- Method(s) ---
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 								// set member variable length
182 								length = (int) (totalFrames * (microSecPerFrame / 1000000L));
183 								raf.skipBytes(12);
184 								// set members
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 		//	int highbit = (b[0] >> 3) & 0x01;
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 		//	int highbit = (b[0] & 0x20) >> 5;
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;		// in bytes
552                                 // invalid if broadcasting
553 		long create_time;	// time of creation, in 100-nanosecond units since 1.1.1601
554                                 // invalid if broadcasting
555 		long pkts_count;	// how many packets are there in the file
556                                 // invalid if broadcasting
557 		public long play_time;		// play time, in 100-nanosecond units
558                                 // invalid if broadcasting
559 		long send_time;		// time to send file, in 100-nanosecond units
560                                 // invalid if broadcasting (could be ignored)
561 		int preroll;		// timestamp of the first packet, in milliseconds
562 		// if nonzero - substract from time
563 		int ignore;            // preroll is 64bit - but let's just ignore it
564 		int flags;		// 0x01 - broadcast
565 		// 0x02 - seekable
566                                 // rest is reserved should be 0
567 		int min_pktsize;	// size of a data packet
568                                 // invalid if broadcasting
569 		int max_pktsize;	// shall be the same as for min_pktsize
570                                 // invalid if broadcasting
571 		int max_bitrate;	// bandwith of stream in bps
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;	// type of media stream
597 		public GUID error_guid;	// data error correction used
598 		public long time_offset;	// presentation time (in 100-nanosecond unit)
599 		public int stream_size;	// size of type-specific data
600 		public int error_size;	        // size of error correct data
601 		public short stream;	        // number ( 1, 2 ... )
602 		public int reserved;	        // usually the same in both streams:
603 		// Eyes on me.asf: 0x62dffd4
604 		// Alsou - Solo.asf: 0x10
605 		// Baby one more time.asf: 0x10
606 		// Cure_LastDayOfSummer.wma: 0x818f900c
607 		// Windows Movie Maker Sample File.wmv: 0x3f
608 
609 		public int width;	// witdth of encoded image
610 		public int height;	// height of encoded image
611 		public byte flags;	// shall be set to 2
612 		public short data_size;
613 		//  BITMAPINFOHEADER bh;
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 }