# Metadaten aus Flash-Videos auslesen



## heart_disease (28. Apr 2009)

Ich möchte mit Java aus FLV-Dateien auslesen können, welcher Audio-Codec verwendet wird.
Nach etwas Sucharbeit habe ich die Dokumentation des FLV-Standards gefunden: Adobe - FLV/F4V Technology Center

Die ersten Werte welche nur 1 Byte groß sind werden soweit korrekt eingelesen, aber ich scheitere bereits beim Feld _DataOffset_. Es handelt sich dabei gemäß Dokumentation um einen UnsignedInteger32 (UI32) - meinem Wissen nach also 4 Byte. Um aus dem ausgelesenen Byte-Array nun einen Integer zu machen, habe ich eine passende Methode im Internet gefunden. Der Rückgabewert ist allerdings viel zu groß (über eine Mio.), laut Doku sollte er 9 sein.

Alle weiteren Felder die eingelesen werden enthalten ohnehin falsche Werte. Ich habe leider nicht die geringste Ahnung was ich falsch mache.

Hier der ganze Code: 
	
	
	
	





```
import java.io.*;

/**
 * Reads meta data from flash videos.
 * 
 * @author Christoph Matscheko, 2009
*/
public class MetaDataExtractor {
	protected String path;
	
	//// FLV-Header
	/** File version (for example, 0x01 for FLV version 1) */
	protected int Version;
	/** Audio tags are present */
	protected int TypeFlagsAudio;
	/** Video tags are present */
	protected int TypeFlagsVideo;
	/** Offset in bytes from start of file to start of body (that is, size of header) */
	protected int[] DataOffset = new int[4];
	protected int DataOffsetInt;
	
	//// FLV-Body
	/** First tag */
	protected FlvTag Tag1;
	/** Size of previous tag, including its header.
	 * For FLV version 1, this value is 11 plus the
	 * DataSize of the previous tag.
	*/
	protected byte[] PreviousTagSize1 = new byte[4];
	/** Second tag */
	protected FlvTag Tag2;
	/** Size of second-to-last tag */
	protected byte[] PreviousTagSizeN_1 = new byte[4];
	/** Last tag */
	protected FlvTag TagN;
	/** Size of last tag */
	protected byte[] PreviousTagSizeN = new byte[4];
	
	//// AUDIODATA
	/** Format of SoundData */
	protected byte[] SoundFormat = new byte[4];
	
	public static void main( String[] args ) {
		MetaDataExtractor mde = new MetaDataExtractor( "/media/daten/videos/imperial_march.flv" );
		mde.printMetaData();
	}
	
	/**
	 * Set path to video file and read meta data.
	 * 
	 * @param path Path to video file
	*/
	public MetaDataExtractor( String path ) {
		this.path = path;
		this.readMetaData();
	}
	
	/**
	 * Write all meta data to stdout.
	*/
	public void printMetaData() {
		System.out.println(
			"+++ FLV-HEADER +++\n" +
			"UI8 Version = " + this.Version + "\n" +
			"UB[1] TypeFlagsAudio = " + this.TypeFlagsAudio + "\n" +
			"UB[1] TypeFlagsVideo = " + this.TypeFlagsVideo + "\n" +
			"UI32 DataOffset = " + byteArrayToInt(DataOffset) + " > " + listArray( DataOffset ) + "\n"
		);
		
		/*System.out.print(
			"+++ FLV-BODY +++\n" +
			"FLVTAG Tag1 = "
		);
		printFlvTag( this.Tag1 );
		System.out.print(
			"UI32 PreviousTagSize1 = " + byteArrayToInt(PreviousTagSize1) + " > " + listArray(PreviousTagSize1) + "\n" +
			"FLVTAG Tag2 = "
		);
		printFlvTag( this.Tag2 );
		System.out.print(
			"UI32 PreviousTagSizeN-1 = " + byteArrayToInt(PreviousTagSizeN_1) + " > " + listArray(PreviousTagSizeN_1) + "\n" +
			"FLVTAG TagN = "
		);
		printFlvTag( this.TagN );
		System.out.print(
			"UI32 PreviousTagSizeN = " + byteArrayToInt(PreviousTagSizeN) + " > " + listArray(PreviousTagSizeN) + "\n" +
			"\n+++ AUDIODATA +++\n" +
			"UB[4] SoundFormat = " + byteArrayToInt(SoundFormat) + " > " + listArray(SoundFormat)
		);*/
	}
	
	/**
	 * Read FLVTAG data block.
	 * 
	 * @param is instance of InputStream for the video file
	 * @return Structure of FlvTag
	 * @throws IOException
	 */
	protected FlvTag readFlvTag( InputStream is ) throws IOException {
		FlvTag ft = new FlvTag();         
		
		// TagType (UInt8)
		is.read( ft.TagType );
		// DataSize (UInt24)
		is.read( ft.DataSize );
		// Timestamp (UInt24)
		is.read( ft.Timestamp );
		// TimestampExtended (UInt8)
		is.read( ft.TimestampExtended );
		// StreamID (UInt24)
		is.read( ft.StreamID );
		// Data
		int TagType = byteArrayToInt( ft.TagType ); 
		if ( TagType == 8 ) {
			ft.Data = "AUDIODATA";
		} else if ( TagType == 9 ) {
			ft.Data = "VIDEODATA";
		} else if ( TagType == 18 ) {
			ft.Data = "SCRIPTDATAOBJECT";
		} else {
			ft.Data = null;
		}
		
		return ft;
	}
	
	/**
	 * Parse meta data from video file.
	*/
	protected void readMetaData() {
		try {
			DataInputStream dis = new DataInputStream( new FileInputStream(this.path) );
			//// FLV-Header ////
	 		// Skip first 3 bytes ('F', 'L', 'V')
			dis.skip( 3 );
			
			// FlashVideo-Version (UI8)
	 		this.Version = dis.readUnsignedByte();
	 		
	 		// Skip 5 bytes (TypeFlagsReserved UB[5] - Must be 0)
	 		dis.skip( 5 );
	 		
	 		// TypeFlagsAudio (UI8)
	 		this.TypeFlagsAudio = dis.readUnsignedByte();
	 		
	 		// Skip 1 byte (TypeFlagsReserved UB[1] - Must be 0)
	 		dis.skip( 1 );
	 		
	 		// TypeFlagsVideo (UB[1])
	 		this.TypeFlagsVideo = dis.readUnsignedByte();
	 		
	 		// DataOffset (UI32)
	 		this.DataOffset[0] = dis.readUnsignedByte();
	 		this.DataOffset[1] = dis.readUnsignedByte();
	 		this.DataOffset[2] = dis.readUnsignedByte();
	 		this.DataOffset[3] = dis.readUnsignedByte();
	 		//this.DataOffsetInt = dis.readUnsignedShort();
	 		
	 		//// FLV-Body ////
	 		// Skip 4 bytes (PreviousTagSize0 (UI32) - Always 0)
	 		dis.skip( 4 );
	 		// Tag1 (FLVTAG)
	 		this.Tag1 = this.readFlvTag( dis );		
	 		// PreviousTagSize1 (UI32)
	 		dis.read( PreviousTagSize1 );
	 		// Tag2 (FLVTAG)
	 		this.Tag2 = this.readFlvTag( dis );
	 		// PreviousTagSizeN-1 (UI32)
	 		dis.read( PreviousTagSizeN_1 );
	 		// TagN (FLVTAG)
	 		this.TagN = this.readFlvTag( dis );
	 		// PreviousTagSizeN (UI32)
	 		dis.read( this.PreviousTagSizeN );
	 		
	 		//// AUDIODATA ////
	 		// SoundFormat (UB[4])
	 		dis.read( this.SoundFormat );
	 		
	 		dis.close();
	 	} catch (FileNotFoundException e) {
	 		e.printStackTrace();
	 	} catch (IOException e) {
	 		e.printStackTrace();
	 	} catch ( Exception e ) {
	 		e.printStackTrace();
	 	}
	}
	
	/**
	 * Codesnippet from http://snippets.dzone.com/posts/show/94
	 * 
	 * Convert the byte array to an int.
	 * 
	 * @param b The byte array
	 * @return The integer
	*/
	public static int byteArrayToInt( byte[] arr ) {
		int number = 0;
		for ( int i=0; i<arr.length; ++i ) {
			number |= (arr[arr.length-1-i] & 0xff) << (i << arr.length-1);
		}
		
		return number;
	}
	
	public static int byteArrayToInt( int[] arr ) {
		int number = 0;
		for ( int i=0; i<arr.length; ++i ) {
			number |= (arr[arr.length-1-i] & 0xff) << (i << arr.length-1);
		}
		
		return number;
	}
	
	/*public static int bytesToInt32( byte[] buffer ) {
		return buffer[0] << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3];
	}*/
	
	/**
	 * Write array content into stout.
	*/
	public static void print_r( int[] b ) {
		System.out.println( "Array(){" );
		
		for ( int i=0; i<b.length; i++ ) {
			System.out.println( "\t[" + i + "] = " + b[i] );
		}
		
		System.out.println( "}" );
	}
	
	/**
	 * Visualize byte array as a String.
	 * 
	 * @param array array with bytes
	 * @return String with array data
	*/
	public static String listArray( byte[] array ) {
		StringBuffer buf = new StringBuffer( "Array{ " );
		
		for ( int i=0; i<array.length; i++ ) {
			buf.append( "[" + i + "]=" + array[i] );
			if ( i+1 < array.length )
				buf.append( " " );
		}
		
		buf.append( " }" );
		
		return buf.toString();
	}
	
	public static String listArray( int[] array ) {
		StringBuffer buf = new StringBuffer( "Array{ " );
		
		for ( int i=0; i<array.length; i++ ) {
			buf.append( "[" + i + "]=" + array[i] );
			if ( i+1 < array.length )
				buf.append( " " );
		}
		
		buf.append( " }" );
		
		return buf.toString();
	}
	
	/**
	 * Writes data from FLVTAG into stdout.
	 * 
	 * @param ft FlvTag
	*/
	public static void printFlvTag( FlvTag ft ) {
		System.out.println(
			"FlvTag() {\n" +
			"\tTagType = " + ft.TagType[0] + "\n" +
			"\tDataSize = " + listArray( ft.DataSize ) + "\n" +
			"\tTimestamp = " + listArray( ft.Timestamp ) + "\n" +
			"\tTimestampExtended = " + ft.TimestampExtended[0] + "\n" +
			"\tStreamID = " + listArray( ft.StreamID ) + "\n" +
			"\tData = " + ft.Data + "\n}"
		);
	}
	
	/**
	 * Structure for FLVTAG data.
	*/
	class FlvTag {
		public byte[] TagType = new byte[1];
		public byte[] DataSize = new byte[3];
		public byte[] Timestamp = new byte[3];
		public byte[] TimestampExtended = new byte[1];
		public byte[] StreamID = new byte[3];
		/** Body of the tag **/
		public String Data;
	}
}
```


----------



## maki (28. Apr 2009)

> Der Rückgabewert ist allerdings viel zu groß (über eine Mio.), laut Doku sollte er 9 sein.


Geraten: Vielleicht konvertieren von/nach little Endian?


----------



## heart_disease (28. Apr 2009)

Fehlanzeige. Bringt leider gar nichts 

Ich hätte jetzt auch einmal die Methode DataInputStream.readInt() versucht:

```
this.DataOffsetInt = dis.readInt() & 0xff;
```
 und 
	
	
	
	





```
this.DataOffsetInt = dis.readInt();
```

Ersteres liefert den Wert 5, was wieder zu klein ist.


----------



## maki (29. Apr 2009)

Würde mir mal diese Methoden byteArrayToInt etc. genauer ansehen.

Welche Werte stehen denn genau in DataOffset? (am besten in Hex)


----------



## heart_disease (29. Apr 2009)

Sooo, ich habe den ganzen Code jetzt nochmal umgekrempelt, nachdem ich draufgekommen bin, dass die FLV-Doku teils falsche Angaben enthält und manche dokumentierte Felder gar nicht existieren!

Zumindest bin ich jetzt soweit, dass das Feld _DataOffset_ den Wert 9 hat. Das nächste Problem stellt sich beim Einlesen des Structs FLVTAG. Das Feld Tag1 vom Typ FLVTAG enthält ein weiteres Struct vom Typ SCRIPTDATAOBJECT. Dieses Struct enthält wiederum die zwei Structs SCRIPTDATASTRING und SCRIPTDATAVALUE. Soweit ich das beurteilen kann, wird SCRIPTDATASTRING (siehe Dokumentation S. 16) noch richtig eingelesen (auch wenn der eingelesene String etwas merkwürdig aussieht).

Das Feld _Type_ von SCRIPTDATAVALUE hat den (lt. Spezifikation gültigen) Wert 5, was einem "Null type" entspricht. Dadurch müssten alle weiteren Felder von SCRIPTDATAVALUE hinfällig sein, da diese alle von Werten die NICHT 5 entsprechen, abhängig sind.

Ich krieg echt die Krise 

Hier der aktuelle Code: 
	
	
	
	





```
import java.io.*;

/**
 * Reads meta data from flash videos.
 * 
 * @author Christoph Matscheko, 2009
*/
public class MetaDataExtractor {
	protected String path;
	protected DataInputStream dis;
	
	public static void main( String[] args ) {
		MetaDataExtractor mde = new MetaDataExtractor( "/media/daten/videos/imperial_march.flv" );
		mde.getMeta();
	}
	
	/**
	 * Set path to video file and read meta data.
	 * 
	 * @param path Path to video file
	*/
	public MetaDataExtractor( String path ) {
		this.path = path;
	}
	
	/**
	 * Write meta data of video file to StdOut.
	*/
	public void getMeta() {
		try {
			this.dis = new DataInputStream( new FileInputStream(this.path) );
			
			this.readFlvHead();
			this.readFlvBody();
			
	 		this.dis.close();
	 	} catch (FileNotFoundException e) {
	 		e.printStackTrace();
	 	} catch (IOException e) {
	 		e.printStackTrace();
	 	} catch ( Exception e ) {
	 		e.printStackTrace();
	 	}
	}
	
	/**
	 * Read Flv-Head and write content to StdOut.
	 * 
	 * @throws IOException
	*/
	protected void readFlvHead() throws IOException {
		/** Signature UI8 - Signature byte always 'F' (0x46) */
		byte signature1 = dis.readByte();
		
		/** Signature UI8 - Signature byte always 'L' (0x4C) */
		byte signature2 = dis.readByte();
		
		/** Signature UI8 - Signature byte always 'V' (0x56) */
		byte signature3 = dis.readByte();
		
		/** Version UI8 - File version (for example, 0x01 for FLV version 1) */
		byte version = dis.readByte();
		
		/** [FIELD DOES NOT EXIST] TypeFlagsReserved UB[5] - Must be 0 */
		//byte[] typeFlagsReserved = new byte[5];
		//dis.readFully( typeFlagsReserved );
		
		/** TypeFlagsAudio UB[1] - Audio tags are present */
		byte typeFlagsAudio = dis.readByte();
		
		/** [FIELD DOES NOT EXIST] TypeFlagsReserved UB[1] - Must be 0 */
		//byte typeFlagsReserved2 = dis.readByte();
		
		/** [FIELD DOES NOT EXIST] TypeFlagsVideo UB[1] - Video tags are present */
		//byte typeFlagsVideo = dis.readByte();
		
		/** DataOffset UI32 - Offset in bytes from start of file to start of body (that is, size of header) */
		int dataOffset = dis.readInt();
		
		System.out.println(
			"+++ FLV-HEAD +++\n" +
			"Signature: " + ((char) signature1) + "\n" +
			"Signature: " + ((char) signature2) + "\n" +
			"Signature: " + ((char) signature3) + "\n" +
			"Version: " + version + "\n" +
			//"TypeFlagsReserved: " + this.listArray(typeFlagsReserved) + "\n" +
			"TypeFlags: " + typeFlagsAudio + "\n" +
			//"TypeFlagsReserved: " + typeFlagsReserved2 + "\n" +
			//"TypeFlagsVideo: " + typeFlagsVideo + "\n" +
			"DataOffset: " + dataOffset
		);
	}
	
	/**
	 * Read Flv-Body and write content to StdOut.
	 * 
	 * @throws IOException
	*/
	protected void readFlvBody() throws IOException {
		System.out.println( "\n+++ FLV-HEAD +++" );
		
		/** PreviousTagSize0 UI32 - Always 0 */
		int previousTagSize0 = dis.readInt();
		System.out.println( "PreviousTagSize0: " + previousTagSize0 );
		
		/** Tag1 FLVTAG - First tag */
		this.readFlvTag( "Tag1" );
		
		/** PreviousTagSize1 UI32 - Size of previous tag, including its
		 * header. For FLV version 1, this value is 11 plus the DataSize of
		 * the previous tag. */
		//int previousTagSize1 = dis.readInt();
		//System.out.println( "PreviousTagSize1: " + previousTagSize1 );
		
		/** Tag2 FLVTAG - Second tag */
		//this.readFlvTag( "Tag2" );
	}
	
	/**
	 * Parse content of struct FLVGTAG.
	 * 
	 * @param tagName field name
	 * @throws IOException
	*/
	protected void readFlvTag( String tagName ) throws IOException {
		/** TagType UI8 - Type of this tag.
		 *  8: audio
		 *  9: video
         *  18: script data */
		byte tagType = dis.readByte();
		/** DataSize UI24 - Length of the data in the Data field */
		int dataSize = readInt24(dis);
		/** Timestamp UI24 */
		int timestamp = readInt24(dis);
		/** TimestampExtended UI8 */
		byte timestampExtended = dis.readByte();
		/** StreamID UI24 - Always 0 */
		int streamID = readInt24(dis);
		
		System.out.print(
			tagName + ": FLVTAG {\n" +
			"\tTagType: " + tagType + "\n" +
			"\tDataSize: " + dataSize + "\n" +
			"\tTimestamp: " + timestamp + "\n" +
			"\tTimestampExtended: " + timestampExtended + "\n" +
			"\tStreamID: " + streamID + "\n" +
			"\tData: "
		);
		
		/** Data */
		if ( tagType == 8 ) {
			System.out.println( "AUDIODATA{}" );
		} else if ( tagType == 9 ) {
			System.out.println( "VIDEODATA{}" );
		} else if ( tagType == 18 ) {
			System.out.println( "SCRIPTDATAOBJECT{" );
			this.readScriptDataObject();
		} else {
			System.out.println( "RESERVED{}" );
		}
		
		System.out.println( "}" );
	}
	
	/**
	 * Parse content of struct SCRIPTDATAOBJECT.
	 * 
	 * @throws IOException
	*/
	protected void readScriptDataObject() throws IOException {
		/** ObjectName SCRIPTDATASTRING - Name of the object */
		this.readScriptDataString();
		/** ObjectData SCRIPTDATAVALUE - Data of the object */
		this.readScriptDataValue();
		
		/** ObjectEndMarker2 UI24 - Always 9 */
		int objectEndMarker2 = readInt24( dis );
		
		System.out.println(
			"\t\tObjectEndMarker2: " + objectEndMarker2
		);
	}
	
	/**
	 * Parse content of struct SCRIPTDATASTRING.
	 * 
	 * @throws IOException
	*/
	protected void readScriptDataString() throws IOException {
		/** StringLength UI16 - String length in bytes */
		int stringLength = this.dis.readShort();
		
		/** StringData STRING - String data */
		byte[] stringData = new byte[stringLength];
		this.dis.read( stringData );
		
		System.out.println(
			"\t\tStringLength: " + stringLength + "\n" +
			"\t\tStringData: " + new String(stringData)
		);
	}
	
	/**
	 * Parse content of struct SCRIPTDATAVALUE.
	 * 
	 * @throws IOException
	*/
	protected void readScriptDataValue() throws IOException {
		/** Type UI8 - Type of the variable:
		 * 0 = Number type (see notes following table)
		 * 1 = Boolean type
		 * 2 = String type
		 * 3 = Object type
		 * 4 = MovieClip type
		 * 5 = Null type
		 * 6 = Undefined type
		 * 7 = Reference type
		 * 8 = ECMA array type
		 * 10 = Strict array type
		 * 11 = Date type
		 * 12 = Long string type */
		byte type = this.dis.readByte();
		
		/** ECMAArrayLength If Type = 8, UI32 - Approximate number of fields of ECMA array */
		int ECMAArrayLength = -1;
		if ( type == 8 ) {
			ECMAArrayLength = this.dis.readInt();
		}
		
		if ( type != 5 && type != 6 ) {
			/** ScriptDataValue - Script data values */
			int scriptDataValue = this.dis.readByte();
		}
		
		System.out.println(
			"\t\tType: " + type + "\n" +
			"\t\tECMAArrayLength: " + ECMAArrayLength + "\n"
		);
		
		/** other fields??? */
	}
	
	/**
	 * Convert the byte array to an int.
	 * 
	 * @param b The byte array
	 * @return The integer
	*/
	public static int byteArrayToInt( byte[] arr ) {
		int number = 0;
		for ( int i=0; i<arr.length; ++i ) {
			number |= (arr[arr.length-1-i] & 0xff) << (i << arr.length-1);
		}
		
		return number;
	}
	
	/**
	 * Reads Int24 from InputStream.
	 * 
	 * @param is InputStream from video file
	 * @return 24bit Integer
	 * @throws IOException
	*/
	public static int readInt24( InputStream is ) throws IOException {
		byte[] buffer = new byte[3];
		is.read( buffer );
		
		return byteArrayToInt( buffer );
	}
	
	/**
	 * Visualize byte array as a String.
	 * 
	 * @param array array with bytes
	 * @return String with array data
	*/
	public static String listArray( byte[] array ) {
		StringBuffer buf = new StringBuffer( "Array{ " );
		
		for ( int i=0; i<array.length; i++ ) {
			buf.append( "[" + i + "]=" + array[i] );
			if ( i+1 < array.length )
				buf.append( " " );
		}
		
		buf.append( " }" );
		
		return buf.toString();
	}
}
```


----------

