I am doing a lot of work with Flash MX and video (flv) and I recently needed to get the duration off of a bunch of FLV files. Sorensen now correctly adds the duration, width, height, framerate, creation date, and the audio and video data rates. I wrote some C# code to read and parse this information off the file.
using System.IO; namespace Refresh.FlashTools { /// <summary> /// Reads the meta information embedded in an FLV file /// </summary> public class FlvMetaDataReader { /// <summary> /// Reads the meta information (if present) in an FLV /// </summary> /// <param name="path">The path to the FLV file</returns> public static FlvMetaInfo GetFlvMetaInfo(string path) { if (!File.Exists(path)) { throw new Exception(String.Format("File '{0}' doesn't exist for FlvMetaDataReader.GetFlvMetaInfo(path)", path)); } bool hasMetaData = false; double duration = 0; double width = 0; double height = 0; double videoDataRate = 0; double audioDataRate = 0; Double frameRate = 0; DateTime creationDate = DateTime.MinValue; // open file FileStream fileStream = new FileStream(path, FileMode.Open); try { // read where "onMetaData" byte[] bytes = new byte[10]; fileStream.Seek(27, SeekOrigin.Begin); int result = fileStream.Read(bytes, 0, 10); // if "onMetaData" exists then proceed to read the attributes string onMetaData = ByteArrayToString(bytes); if (onMetaData == "onMetaData") { hasMetaData = true; // 16 bytes past "onMetaData" is the data for "duration" duration = GetNextDouble(fileStream, 16, 8); // 8 bytes past "duration" is the data for "width" width = GetNextDouble(fileStream, 8, 8); // 9 bytes past "width" is the data for "height" height = GetNextDouble(fileStream, 9, 8); // 16 bytes past "height" is the data for "videoDataRate" videoDataRate = GetNextDouble(fileStream, 16, 8); // 16 bytes past "videoDataRate" is the data for "audioDataRate" audioDataRate = GetNextDouble(fileStream, 16, 8); // 12 bytes past "audioDataRate" is the data for "frameRate" frameRate = GetNextDouble(fileStream, 12, 8); // read in bytes for creationDate manually fileStream.Seek(17, SeekOrigin.Current); byte[] seekBytes = new byte[24]; result = fileStream.Read(seekBytes, 0, 24); string dateString = ByteArrayToString(seekBytes); // create .NET readable date string // cut off Day of Week dateString = dateString.Substring(4); // grab 1) month and day, 2) year, 3) time dateString = dateString.Substring(0, 6) + " " + dateString.Substring(16, 4) + " " + dateString.Substring(7, 8); // .NET 2.0 has DateTime.TryParse try { creationDate = Convert.ToDateTime(dateString); } catch { } } } catch (Exception e) { // no error handling } finally { fileStream.Close(); } return new FlvMetaInfo(hasMetaData, duration, width, height, videoDataRate, audioDataRate, frameRate, creationDate); } private static Double GetNextDouble(FileStream fileStream, int offset, int length) { // move the desired number of places in the array fileStream.Seek(offset, SeekOrigin.Current); // create byte array byte[] bytes = new byte[length]; // read bytes int result = fileStream.Read(bytes, 0, length); // convert to double (all flass values are written in reverse order) return ByteArrayToDouble(bytes, true); } private static string ByteArrayToString(byte[] bytes) { string byteString = string.Empty; foreach (byte b in bytes) { byteString += Convert.ToChar(b).ToString(); } return byteString; } private static Double ByteArrayToDouble(byte[] bytes, bool readInReverse) { if (bytes.Length != 8) throw new Exception("bytes must be exactly 8 in Length"); if (readInReverse) Array.Reverse(bytes); return BitConverter.ToDouble(bytes, 0); } } /// <summary> /// Read only container holding meta data embedded in FLV files /// </summary> public class FlvMetaInfo { private Double _duration; private Double _width; private Double _height; private Double _frameRate; private Double _videoDataRate; private Double _audioDataRate; private DateTime _creationDate; private bool _hasMetaData; /// <summary> /// The duration in seconds of the video /// </summary> public Double Duration { get { return _duration; } //set { _duration = value; } } /// <summary> /// The width in pixels of the video /// </summary> public Double Width { get { return _width; } //set { _width = value; } } /// <summary> /// The height in pixels of the video /// </summary> public Double Height { get { return _height; } //set { _height = value; } } /// <summary> /// The data rate in KB/sec of the video /// </summary> public Double VideoDataRate { get { return _videoDataRate; } //set { _videoDataRate = value; } } /// <summary> /// The data rate in KB/sec of the video's audio track /// </summary> public Double AudioDataRate { get { return _audioDataRate; } //set { _audioDataRate = value; } } /// <summary> /// The frame rate of the video /// </summary> public Double FrameRate { get { return _frameRate; } //set { _frameRate = value; } } /// <summary> /// The creation date of the video /// </summary> public DateTime CreationDate { get { return _creationDate; } //set { _creationDate = value; } } /// <summary> /// Whether or not the FLV has meta data /// </summary> public bool HasMetaData { get { return _hasMetaData; } //set { _hasMetaData = value; } } internal FlvMetaInfo(bool hasMetaData, Double duration, Double width, Double height, Double videoDataRate, Double audioDataRate, Double frameRate, DateTime creationDate) { _hasMetaData = hasMetaData; _duration = duration; _width = width; _height = height; _videoDataRate = videoDataRate; _audioDataRate = audioDataRate; _frameRate = frameRate; _creationDate = creationDate; } } }
Update: Dave Daku converted this to VB:
Imports System.IO Namespace Refresh.FlashTools ' ' Returns the meta information embedded in an FLV file ' Written in C# by John Dyer: http://johndyer.name/post/2005/08/Flash-FLV-meta-reader-in-NET-(C).aspx ' Conversion to VB.net by Dave Daku (11/29/2007) ' Public Class flvMetaDataReader Public Shared Function getFlvMetaInfo(ByRef path As String) As flvMetaInfo If (Not File.Exists(path)) Then Throw New Exception(String.Format("File '{0}' doesn't exist for flvMetaDataReader.getFlvMetaInfo(path)", path)) End If Dim hasMetaData As Boolean = False Dim duration As Double = 0 Dim width As Double = 0 Dim height As Double = 0 Dim videoDataRate As Double = 0 Dim audioDataRate As Double = 0 Dim frameRate As Double = 0 Dim creationDate As DateTime = DateTime.MinValue ' open the file Dim fileStream As FileStream = New FileStream(path, FileMode.Open) Try ' read where "onMetaData" Dim bytes As Byte() = New Byte(10) {} fileStream.Seek(27, SeekOrigin.Begin) Dim result As Integer = fileStream.Read(bytes, 0, 10) ' if "onMetaData" exists then proceed to read the attributes Dim onMetaData As String = byteArrayToString(bytes) If (onMetaData = "onMetaData") Then hasMetaData = True ' 16 bytes past "onMetaData" is the data for "duration" duration = getNextDouble(fileStream, 16, 8) ' 8 bytes past "duration" is the data for "width" width = getNextDouble(fileStream, 8, 8) ' 9 bytes past "width" is the data for "height" height = getNextDouble(fileStream, 9, 8) ' 16 bytes past "height" is the data for "videoDataRate" videoDataRate = getNextDouble(fileStream, 16, 8) ' 16 bytes past "videoDataRate" is the data for "audioDataRate" audioDataRate = getNextDouble(fileStream, 16, 8) ' 12 bytes past "audioDataRate" is the data for "frameRate" frameRate = getNextDouble(fileStream, 12, 8) ' read in bytes for creationDate manually fileStream.Seek(17, SeekOrigin.Current) Dim seekBytes As Byte() = New Byte(24) {} result = fileStream.Read(seekBytes, 0, 24) Dim dateString As String = byteArrayToString(seekBytes) ' create .NET readable date string ' cut off Day of Week dateString = dateString.Substring(4) ' grab 1) month and day, 2) year, 3) time dateString = dateString.Substring(0, 6) + " " + dateString.Substring(16, 4) + " " + dateString.Substring(7, 8) ' .NET 2.0 has dateTime.tryParse Try Date.TryParse(dateString, creationDate) Catch ex As Exception Try creationDate = CDate(dateString) Catch ex2 As Exception End Try End Try End If Catch ex As Exception ' no error handling Finally fileStream.Close() End Try Return New flvMetaInfo(hasMetaData, duration, width, height, videoDataRate, audioDataRate, frameRate, creationDate) End Function Private Shared Function getNextDouble(ByVal fileStream As FileStream, ByVal offset As Integer, ByVal length As Integer) As Double ' move the desired number of places in the array fileStream.Seek(offset, SeekOrigin.Current) ' create byte array Dim bytes As Byte() = New Byte(length) {} ' read bytes Dim result As Integer = fileStream.Read(bytes, 0, length) ' convert to double (all flash values are written in reverse orer) Return byteArrayToDouble(bytes, True) End Function Private Shared Function byteArrayToString(ByRef bytes() As Byte) As String Dim byteString As String = String.Empty For Each b As Byte In bytes byteString += Convert.ToChar(b).ToString() Next Return byteString End Function Private Shared Function byteArrayToDouble(ByRef bytes As Byte(), ByVal readInReverse As Boolean) As Double If (bytes.Length <> 8) Then Throw New Exception("Bytes must be exactly 8 in length") End If If (readInReverse) Then Array.Reverse(bytes) End If Return BitConverter.ToDouble(bytes, 0) End Function End Class ' Read-only container for holding meta data embedded in FLV files Public Class flvMetaInfo Private _duration As Double Private _width As Double Private _height As Double Private _frameRate As Double Private _videoDataRate As Double Private _audioDataRate As Double Private _creationDate As DateTime Private _hasMetaData As Boolean ' The duration in seconds of the video Public ReadOnly Property duration() As Double Get Return _duration End Get End Property ' The width in pixels of the video Public ReadOnly Property width() As Double Get Return _width End Get End Property ' The height in pixels of the video Public ReadOnly Property height() As Double Get Return _height End Get End Property ' the data rate in KB/sec of the video Public ReadOnly Property videoDataRate() As Double Get Return _videoDataRate End Get End Property ' the data rate in KB/sec of the video's audio track Public ReadOnly Property audioDataRate() As Double Get Return _audioDataRate End Get End Property ' the frame rate of the video Public ReadOnly Property frameRate() As Double Get Return _frameRate End Get End Property ' the creation date of the video Public ReadOnly Property creationDate() As DateTime Get Return _creationDate End Get End Property ' the frame rate of the video Public ReadOnly Property hasMetaData() As Boolean Get Return _hasMetaData End Get End Property Public Sub New(ByVal hasMetaData As Boolean, ByVal duration As Double, ByVal width As Double, ByVal height As Double, ByVal videoDataRate As Double, ByVal audioDataRate As Double, ByVal frameRate As Double, ByVal creationDate As DateTime) _hasMetaData = hasMetaData _duration = duration _width = width _height = height _videoDataRate = videoDataRate _audioDataRate = audioDataRate _frameRate = frameRate _creationDate = creationDate End Sub End Class End Namespace
I’d like to also be able to get the duration off the FLV if the meta data did not get inserted, but the best solution I found (FLV MetaData Injector) is not open source.
many Thanks. This saved my time a lot
it doesn’t pull the duration properly for me… seems to want to pull a decimal value of 9.8732 or something instead of the actual 48 seconds… why??
@shawn, it could be that the meta data for your FLV is somehow incorrect. You can check it with http://www.buraks.com/flvmdi/
Thanks alot for this class.
Hi, I am looking to embed a flash action script in a c# application. However once the objects are layed out can the flash object be saved so that the user can view that flash object each time?
Assistance in this regard will be appreciated
Either bad conversion or bad code.
In getNextDouble, array of Byte was dimmed incorrectly.
Correct line should read:
Dim bytes As Byte() = New Byte(length – 1) {}
First of all thanks for this article! Although I needed to tweak some code, this sure put me on the right track.
Concerning Shawns remark on the odd decimal values for duration – I had the same problem getting the correct width and height values from the header. Even after using the tools from http://www.buraks.com/flvmdi/ I kept getting bizarre results. It turned out that the parameter/value pairs are not always on the same offset in the headers, and in some cases (Youtube!) width and height are not available at all.
Eventually I changed the code, I dump the header in a string and use string.IndexOf to determine the offset value for the needed parameter. Works like a charm.
Thanks, Herman
If anyone’s interested, here is some code to extract duration if metadata is missing. It’s modeled after code found at http://flixforums.com/archive/index.php/t-149.html
[quote]
Public Function getFLVDuration(ByVal flv As String) As Double
If File.Exists(flv) Then
Dim flvFile As New FileStream(flv, FileMode.Open, FileAccess.Read, FileShare.Read)
flvFile.Position = flvFile.Length – 4
Using br As New BinaryReader(flvFile)
‘– set Byte Array to length four array from end of file
Dim offsetHEX32 As Byte() = br.ReadBytes(4)
‘– Convert to Little Endian Byte Array for BitConverter
Array.Reverse(offsetHEX32)
‘– Convert to Int32 as offset to find last timestamp tag in FLV
Dim offset As Int32 = BitConverter.ToInt32(offsetHEX32, 0)
flvFile.Position = flvFile.Length – (offset)
Dim durationHEX32 As Byte()
‘– Read in three Byte duration
durationHEX32 = br.ReadBytes(3)
‘– Convert to Little Endian Byte Array for BitConverter
Array.Reverse(durationHEX32)
‘– Pad to 32 bits for conversion
ReDim Preserve durationHEX32(3)
durationHEX32(3) = 0 ‘– this actually redundant, default is zero
‘– Convert Byte Array to FLV duration in ms
Dim durationms As Int32 = BitConverter.ToInt32(durationHEX32, 0)
‘– Return in seconds
Return durationms / 1000
End Using
End If
End Function
[/quote]
Oh and the above code is VB.NET. Sorry for the addendum.
hi,
vb.net version doesnt appear to work – even with suggested ammendment …. i get no metedata – if i switxh to C# – all is revealed – using same flv …… can u advise ? thanks
@Nazimba, sorry I didn’t write the VB version. It was submitted by another user. If you are set on using VB perhaps you could compare the two and contribute your fixed code…
I have tried your class and on first look it works ok, but then I’ve stumbled upon a flv file from which the class would read really strange values. In the header the onMetaData field is present, but then there is data in the format (As far as I can see) [length][string of specified length(ex:"framerate")][some binary data] then it repeats itself.
Do you have an update for this class by any chance?
I’d like to suggest changing line "32" to:
FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
… that prevents two process from competing for file access. For example: IIS, PDF creator and your class.
Great work, very helpful. Thank you,
Hi,
I am getting video height in Double value format. How to convert it into pixel format?
Thank You,
Hi,
Whenever I tries to run this program by passing FLV file as input. I am getting height = 1.76797281436801E-205 and width = 2.86533732650716E+161
I have checked with http://www.buraks.com/flvmdi/ tool by passing same FLV and it is giving me the correct result like height = 320 px and width = 420 px
Can you please suggest / comments on this to solve this issues.
Jd
Jd, it’s been a few years since I’ve looked at this. If you’re familiar with C# you could walk through the code and see if your FLV is formatted differently.
Yes. my flv file is formatted properly otherwise how http://www.buraks.com/flvmdi/ tool is giving me the correct result?
Any hint?
Thanks,
Jd
@Colin: Your code did the trick for me for extracting FLV duration, here is C# conversion I did:
public static double GetFLVDuration( string filename )
{
if( !File.Exists( filename ) ) return 0;
try
{
int duration = 0;
FileStream fs = new FileStream( filename, FileMode.Open, FileAccess.Read, FileShare.Read );
fs.Position = fs.Length – 4;
using( BinaryReader br = new BinaryReader( fs ) )
{
byte[] hex = br.ReadBytes( 4 );
int offset = hex[3] + (hex[2] << 8) + (hex[1] << 16) + (hex[0] << 24);
fs.Position = fs.Length – offset;
hex = br.ReadBytes( 3 );
duration = hex[2] + (hex[1] << 8) + (hex[0] << 16);
}
fs.Close();
fs.Dispose();
return duration / 1000.0;
} catch( Exception )
{
return 0;
}
}
hi,
i want to extract the iframes and write to a file form the transport stream file .plz explain me step by step how to do this.
I’m not undertanding how to use VB.net version….
My file is
http://www.mydonain/myfile.flv
I Need duration and file size
How can I call the function that give me duration and size ?
Thanks for your help
How can I pass video’s path to the VB Function?
Thanks!
Thanks a lot!!!!!
Can you play a flv file like this way ??
I had the same problem as others; newer FLV files had a different structure to older ones. Working with another one of our devs, we came up with this solution that worked across the board:
/// <summary>
/// Reads the meta information embedded in an FLV file
/// </summary>
public class FlvMetaDataReader
{
static string onMetaData = "";
static string bytesToFile = "";
/// <summary>
/// Reads the meta information (if present) in an FLV
/// </summary>
/// <param name="path">The path to the FLV file</returns>
public static FlvMetaInfo GetFlvMetaInfo(string path)
{
if (!File.Exists(path))
{
throw new Exception(String.Format("File ‘{0}’ doesn’t exist for FlvMetaDataReader.GetFlvMetaInfo(path)", path));
}
bool hasMetaData = false;
double duration = 0;
double width = 0;
double height = 0;
double videoDataRate = 0;
double audioDataRate = 0;
Double frameRate = 0;
// open file
FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
try
{
byte[] bytes = new byte[1000];
fileStream.Seek(27, SeekOrigin.Begin);
int result = fileStream.Read(bytes, 0, 1000);
bytesToFile = ByteArrayToString(bytes);
onMetaData = bytesToFile.Substring(0,10);
// if "onMetaData" exists then proceed to read the attributes
if (onMetaData == "onMetaData")
{
hasMetaData = true;
// 16 bytes past "onMetaData" is the data for "duration"
duration = GetNextDouble(bytes, bytesToFile.IndexOf("duration") + 9, 8);
// 8 bytes past "duration" is the data for "width"
width = GetNextDouble(bytes, bytesToFile.IndexOf("width") + 6, 8);
// 9 bytes past "width" is the data for "height"
height = GetNextDouble(bytes, bytesToFile.IndexOf("height") + 7, 8);
// 16 bytes past "height" is the data for "videoDataRate"
videoDataRate = GetNextDouble(bytes, bytesToFile.IndexOf("videodatarate") + 14, 8);
// 16 bytes past "videoDataRate" is the data for "audioDataRate"
audioDataRate = GetNextDouble(bytes, bytesToFile.IndexOf("audiodatarate") + 14, 8);
// 12 bytes past "audioDataRate" is the data for "frameRate"
frameRate = GetNextDouble(bytes, bytesToFile.IndexOf("framerate") + 10, 8);
}
}
catch (Exception e)
{
// no error handling
}
finally
{
fileStream.Close();
}
return new FlvMetaInfo(hasMetaData, duration, width, height, videoDataRate, audioDataRate, frameRate);
}
private static Double GetNextDouble(Byte[] b, int offset, int length)
{
MemoryStream ms = new MemoryStream(b);
// move the desired number of places in the array
ms.Seek(offset, SeekOrigin.Current);
// create byte array
byte[] bytes = new byte[length];
// read bytes
int result = ms.Read(bytes, 0, length);
// convert to double (all flass values are written in reverse order)
return ByteArrayToDouble(bytes, true);
}
private static string ByteArrayToString(byte[] bytes)
{
string byteString = string.Empty;
foreach (byte b in bytes)
{
byteString += Convert.ToChar(b).ToString();
}
return byteString;
}
private static Double ByteArrayToDouble(byte[] bytes, bool readInReverse)
{
if (bytes.Length != 8)
throw new Exception("bytes must be exactly 8 in Length");
if (readInReverse)
Array.Reverse(bytes);
return BitConverter.ToDouble(bytes, 0);
}
}
/// <summary>
/// Read only container holding meta data embedded in FLV files
/// </summary>
public class FlvMetaInfo
{
private Double _duration;
private Double _width;
private Double _height;
private Double _frameRate;
private Double _videoDataRate;
private Double _audioDataRate;
private bool _hasMetaData;
/// <summary>
/// The duration in seconds of the video
/// </summary>
public Double Duration
{
get { return _duration; }
//set { _duration = value; }
}
/// <summary>
/// The width in pixels of the video
/// </summary>
public Double Width
{
get { return _width; }
//set { _width = value; }
}
/// <summary>
/// The height in pixels of the video
/// </summary>
public Double Height
{
get { return _height; }
//set { _height = value; }
}
/// <summary>
/// The data rate in KB/sec of the video
/// </summary>
public Double VideoDataRate
{
get { return _videoDataRate; }
//set { _videoDataRate = value; }
}
/// <summary>
/// The data rate in KB/sec of the video’s audio track
/// </summary>
public Double AudioDataRate
{
get { return _audioDataRate; }
//set { _audioDataRate = value; }
}
/// <summary>
/// The frame rate of the video
/// </summary>
public Double FrameRate
{
get { return _frameRate; }
//set { _frameRate = value; }
}
/// <summary>
/// Whether or not the FLV has meta data
/// </summary>
public bool HasMetaData
{
get { return _hasMetaData; }
//set { _hasMetaData = value; }
}
internal FlvMetaInfo(bool hasMetaData, Double duration, Double width, Double height, Double videoDataRate, Double audioDataRate, Double frameRate)
{
_hasMetaData = hasMetaData;
_duration = duration;
_width = width;
_height = height;
_videoDataRate = videoDataRate;
_audioDataRate = audioDataRate;
_frameRate = frameRate;
}
}
Cheers for the original code, and for the update above – I’ve literally copied and pasted the modified class by Sec and tested it with a bunch of FLVs and it works perfectly!
Great stuff.
Thank you so much for the code – it works perfectly! Does anyone have a version (or recommendation) for extracting similar metadata from an MP4 video also?
Hi,
How can I download FlvMetaInfo, could you please send to me following this email: thanhsu0308@yahoo.com, thank you.
I am looking to embed a flash action script in a c# application. However once the objects are layed out can the flash object be saved so that the user can view that flash object each time?
Can I extract the iframes and write to a file form the transport stream file .Can you explain me step by step how should I do this.