[matroska] r858 - in trunk: . Perl.Parser
[email protected] Sun, 3 Oct 2004 12:52:58 +0400 (MSD)
Newsgroups
gmane.comp.multimedia.matroska.cvs
Message-ID
<[email protected] >
Author: robux4
Date: 2004-10-03 12:52:57 +0400 (Sun, 03 Oct 2004)
New Revision: 858
Added:
trunk/Perl.Parser/
trunk/Perl.Parser/MatroskaParser.pm
Log:
add a basic Matroska parser in Perl, done by Omion (on HA)
Added: trunk/Perl.Parser/MatroskaParser.pm
===================================================================
--- trunk/Perl.Parser/MatroskaParser.pm 2004-10-02 16:02:48 UTC (rev 857)
+++ trunk/Perl.Parser/MatroskaParser.pm 2004-10-03 08:52:57 UTC (rev 858)
@@ -0,0 +1,816 @@
+# This work is hereby released into the Public Domain.
+# To view a copy of the public domain dedication,
+# visit http://creativecommons.org/licenses/publicdomain/
+# or send a letter to
+# Creative Commons,
+# 559 Nathan Abbott Way
+# Stanford, California 94305, USA.
+#
+################################################################################
+#
+# To use:
+# $file = new matroska('filename');
+# $structure = $file->parse();
+#
+# parse() may take an optional element which is a hash of tags to override handling of.
+# parse({'Segment' => 0}) will skip any 'Segment' tags found, and place 'SKIPPED' in the 'value' key.
+# parse({'Block' => 1}) will read blocks (they are by default skipped)
+# parse({'Cluster' => 2}) will read clusters and everything within a cluster.
+# parse({'EBML' => 0, 'Segment' => 2}) will skip the EBML elements, but read everything in the 'Segment' elements
+# A list of valid element names is in the "$matroska::Elements" hash in this file
+# Also, the default handling of each element is in the 'transverse' key of each element.
+#
+# All the data in the "$matroska::Elements" hash was gathered from http://www.matroska.org/technical/specs/
+#
+# $file->parse() will give you a variable that looks like this:
+# --HASH--
+# 'EBML'> --ARRAY--
+# 0: --HASH--
+# 'headersize'> 5
+# 'pos'> 0
+# 'size'> 19
+# 'children'> --HASH--
+# 'DocType'> --HASH--
+# 'headersize'> 3
+# 'pos'> 5
+# 'size'> 8
+# 'value'> 'matroska'
+# 'DocTypeReadVersion'> --HASH--
+# 'headersize'> 3
+# 'pos'> 20
+# 'size'> 1
+# 'value'> 1
+# 'DocTypeVersion'> --HASH--
+# 'headersize'> 3
+# 'pos'> 16
+# 'size'> 1
+# 'value'> 1
+# 'Segment'> --ARRAY--
+# 0: --HASH--
+# 'headersize'> 12
+# 'pos'> 24
+# 'size'> 4908505
+# 'children'> --HASH--
+# 'Cluster'> --ARRAY--
+# 0: --HASH--
+# 'headersize'> 7
+# 'pos'> 5378
+# 'size'> 18728
+# 'value'> 'SKIPPED'
+# 1: --HASH--
+# 'headersize'> 7
+# 'pos'> 24113
+# 'size'> 20471
+# 'value'> 'SKIPPED'
+# etc.
+#
+# So to get the DocType, use $a->{'EBML'}[0]{'children'}{'DocType'}{'value'}
+# To get the position of the DocTypeVersion, use $a->{'EBML'}[0]{'children'}{'DocTypeVersion'}{'pos'}
+#
+# Elements which can have multiple entries are in an array, so the first cluster is {'Cluster'}[0], second is {'Cluster'}[1], etc.
+# Elements which can not have multiple entries have no array, so the doctype is just {'DocType'}.
+# Each element has a 'pos', 'size', and 'headersize' key. These are the position in the file,
+# the size of the inner contents (ie. minus the header), and the size of the header.
+# So to go to the end of an element, seek to 'pos' + 'headersize' + 'size'.
+#
+# Blocks (if parsed) have the following structure:
+# 'Block'> --HASH--
+# 'headersize'> 3
+# 'pos'> 5391
+# 'size'> 1626
+# 'children'> --HASH--
+# 'lacing'> 'EBML'
+# ('framesize') <-- Only for 'lacing' == 'Fixed'
+# ('gap') <-- Only if the 'gap' flag is set (see Matroska specs for more info)
+# 'timecode'> 0
+# 'track'> 1
+# 'frames'> --ARRAY--
+# 0: --HASH--
+# 'pos'> 5408
+# 'size'> 28
+# 1: --HASH--
+# 'pos'> 5436
+# 'size'> 22
+# 2: --HASH--
+# 'pos'> 5458
+# 'size'> 22
+# 3: --HASH--
+# 'pos'> 5480
+# 'size'> 22
+# 4: --HASH--
+# 'pos'> 5502
+# 'size'> 332
+# 5: --HASH--
+# 'pos'> 5834
+# 'size'> 295
+# 6: --HASH--
+# 'pos'> 6129
+# 'size'> 416
+# 7: --HASH--
+# 'pos'> 6545
+# 'size'> 475
+#
+# No-lacing looks like:
+# 'Block'> --HASH--
+# 'children'> --HASH--
+# 'lacing'> 'None'
+# 'timecode'> 1567
+# 'track'> 1
+# 'frames'> --ARRAY--
+# 0: --HASH--
+# 'pos'> 15505
+# 'size'> 1951
+#
+# # Example:
+#
+# use MatroskaParser;
+#
+# $filename = 'path/to/matroska.mkv';
+# $file = new matroska($filename);
+#
+# # Standard parsing (don't go into clusters, seek entries, etc.
+# $std = $file->parse();
+#
+# # Parse the entire EBML elements, but no Segment elements
+# $onlyEBML = $file->parse({'EBML' => 2, 'Segment' => 2});
+#
+# $onlyEBML:
+# --HASH--
+# 'EBML'> --ARRAY--
+# 0: --HASH--
+# 'children'> --HASH--
+# 'DocType'> --HASH--
+# 'headersize'> 3
+# 'pos'> 5
+# 'size'> 8
+# 'value'> 'matroska'
+# 'DocTypeReadVersion'> --HASH--
+# 'headersize'> 3
+# 'pos'> 20
+# 'size'> 1
+# 'value'> 1
+# 'DocTypeVersion'> --HASH--
+# 'headersize'> 3
+# 'pos'> 16
+# 'size'> 1
+# 'value'> 1
+# 'headersize'> 5
+# 'pos'> 0
+# 'size'> 19
+# 'Segment'> --ARRAY--
+# 0: --HASH--
+# 'headersize'> 12
+# 'pos'> 24
+# 'size'> 4908505
+# 'value'> 'SKIPPED'
+# (That's the entire thing)
+#
+################################################################################
+#
+# Questions, comments, and bugs can be emailed to "rswilson at ucsc dot edu".
+#
+# KNOWN PROBLEMS:
+#
+# * It's not astoundingly fast. This is partially because it's Perl, but
+# partially because I don't use the seektables. I'll probably add that later.
+#
+# * Dates are interpreted as integers (=nanoseconds since millenium).
+#
+# * All integers are treated as unsigned. This is because I'm lazy.
+#
+# * A broken file will result in several warning messages, followed by complete
+# and utter death.
+#
+# * I don't know if it will handle files > 4GB correctly. I think Perl uses
+# floats for integers that are too big, so the seeking might not work, which
+# would result in strange errors and eventual termination (see previous).
+#
+# * I've only tested it on my Windows XP machine, but it should work just fine
+# on Linux, etc.. HOWEVER, reading floats will break on a little-endian
+# machine.
+#
+# * Easy-to-parse binary data (like 4CC codes and SeekID entries) are simply
+# treated as binary. Blocks, however, are parsed to the frame level.
+#
+################################################################################
+#
+# CHANGELOG:
+# 0.1
+# First released into the wild.
+#
+################################################################################
+package matroska;
+################################################################################
+use strict;
+use constant {
+ SINT => 0,
+ UINT => 1,
+ FLOAT => 2,
+ STRING => 3,
+ UTF8 => 4,
+ DATE => 5,
+ ELEMENT => 6,
+ BINARY => 7,
+ BLOCK => 8,
+
+ SINGLE => 1,
+ MULTI => 0,
+};
+
+use Math::BigInt;
+
+BEGIN {
+ $matroska::DefaultBuffer = 65536;
+ $matroska::FlushBuffer = 65536;
+ $matroska::UseSeekInfo = 0; # This currently does not work. Seek info is never used.
+
+ $matroska::Elements = {
+
+ # Dummy parent
+ '' => {'name' => 'FILE', 'value' => ELEMENT, 'multi' => 0, 'traverse' => 1, 'pos' => {}},
+
+ # EBML Basics
+ 0x0A45DFA3 => {'name' => 'EBML', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {'' => 1}},
+ 0x0286 => {'name' => 'EBMLVersion', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0A45DFA3 => 1}},
+ 0x02F7 => {'name' => 'EBMLReadVersion', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0A45DFA3 => 1}},
+ 0x02F2 => {'name' => 'EBMLMaxIDLength', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0A45DFA3 => 1}},
+ 0x02F3 => {'name' => 'EBMLMaxSizeLength', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0A45DFA3 => 1}},
+ 0x0282 => {'name' => 'DocType', 'value' => STRING, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0A45DFA3 => 1}},
+ 0x0287 => {'name' => 'DocTypeVersion', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0A45DFA3 => 1}},
+ 0x0285 => {'name' => 'DocTypeReadVersion', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0A45DFA3 => 1}},
+ # Global
+ 0x3F => {'name' => 'CRC-32', 'value' => BINARY, 'multi' => 1, 'traverse' => 0, 'pos' => {'all' => 1}},
+ 0x6C => {'name' => 'Void', 'value' => BINARY, 'multi' => 1, 'traverse' => 0, 'pos' => {'all' => 1}},
+ # Segment
+ 0x08538067 => {'name' => 'Segment', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {'' => 1}},
+ # Meta-seek
+ 0x014D9B74 => {'name' => 'SeekHead', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x08538067 => 1}},
+ 0x0DBB => {'name' => 'Seek', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 0, 'pos' => {0x014D9B74 => 1}},
+ 0x13AB => {'name' => 'SeekID', 'value' => BINARY, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0DBB => 1}},
+ 0x13AC => {'name' => 'SeekPosition', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0DBB => 1}},
+ # Segment Information
+ 0x0549A966 => {'name' => 'Info', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x08538067 => 1}},
+ 0x33A4 => {'name' => 'SegmentUID', 'value' => BINARY, 'multi' => 0, 'traverse' => 0, 'pos' => {0x0549A966 => 1}},
+ 0x3384 => {'name' => 'SegmentFilename', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0549A966 => 1}},
+ 0x1CB923 => {'name' => 'PrevUID', 'value' => BINARY, 'multi' => 0, 'traverse' => 0, 'pos' => {0x0549A966 => 1}},
+ 0x1C83AB => {'name' => 'PrevFilename', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0549A966 => 1}},
+ 0x1EB923 => {'name' => 'NextUID', 'value' => BINARY, 'multi' => 0, 'traverse' => 0, 'pos' => {0x0549A966 => 1}},
+ 0x1E83BB => {'name' => 'NextFilename', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0549A966 => 1}},
+ 0x0AD7B1 => {'name' => 'TimecodeScale', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0549A966 => 1}},
+ 0x0489 => {'name' => 'Duration', 'value' => FLOAT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0549A966 => 1}},
+ 0x0461 => {'name' => 'DateUTC', 'value' => DATE, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0549A966 => 1}},
+ 0x3BA9 => {'name' => 'Title', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0549A966 => 1}},
+ 0x0D80 => {'name' => 'MuxingApp', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0549A966 => 1}},
+ 0x1741 => {'name' => 'WritingApp', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0549A966 => 1}},
+ # Cluster
+ 0x0F43B675 => {'name' => 'Cluster', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 0, 'pos' => {0x08538067 => 1}},
+ 0x67 => {'name' => 'Timecode', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0F43B675 => 1}},
+ 0x27 => {'name' => 'Position', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0F43B675 => 1}},
+ 0x2B => {'name' => 'PrevSize', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x0F43B675 => 1}},
+ 0x20 => {'name' => 'BlockGroup', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 0, 'pos' => {0x0F43B675 => 1}},
+ 0x21 => {'name' => 'Block', 'value' => BLOCK, 'multi' => 0, 'traverse' => 0, 'pos' => {0x20 => 1}},
+ 0x22 => {'name' => 'BlockVirtual', 'value' => BINARY, 'multi' => 1, 'traverse' => 0, 'pos' => {0x20 => 1}},
+ 0x35A1 => {'name' => 'BlockAdditions', 'value' => ELEMENT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x20 => 1}},
+ 0x26 => {'name' => 'BlockMore', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 0, 'pos' => {0x35A1 => 1}},
+ 0x5E => {'name' => 'BlockAddID', 'value' => UINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x26 => 1}},
+ 0x25 => {'name' => 'BlockAdditional', 'value' => BINARY, 'multi' => 0, 'traverse' => 0, 'pos' => {0x26 => 1}},
+ 0x1B => {'name' => 'BlockDuration', 'value' => UINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x20 => 1}},
+ 0x7A => {'name' => 'ReferencePriority', 'value' => UINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x20 => 1}},
+ 0x7B => {'name' => 'ReferenceBlock', 'value' => SINT, 'multi' => 1, 'traverse' => 0, 'pos' => {0x20 => 1}},
+ 0x7D => {'name' => 'ReferenceVirtual', 'value' => SINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x20 => 1}},
+ 0x24 => {'name' => 'CodecState', 'value' => BINARY, 'multi' => 0, 'traverse' => 0, 'pos' => {0x20 => 1}},
+ 0x0E => {'name' => 'Slices', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 0, 'pos' => {0x20 => 1}},
+ 0x68 => {'name' => 'TimeSlice', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 0, 'pos' => {0x0E => 1}},
+ 0x4C => {'name' => 'LaceNumber-DEAD', 'value' => UINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x68 => 1}},
+ 0x4D => {'name' => 'FrameNumber', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x68 => 1}},
+ 0x4B => {'name' => 'BlockAdditionID', 'value' => UINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x68 => 1}},
+ 0x4E => {'name' => 'Delay', 'value' => UINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x68 => 1}},
+ 0x4F => {'name' => 'Duration', 'value' => UINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x68 => 1}},
+ # Track
+ 0x0654AE6B => {'name' => 'Tracks', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x08538067 => 1}},
+ 0x2E => {'name' => 'TrackEntry', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x0654AE6B => 1}},
+ 0x57 => {'name' => 'TrackNumber', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x33C5 => {'name' => 'TrackUID', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x03 => {'name' => 'TrackType', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x29 => {'name' => 'FlagEnabled', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x08 => {'name' => 'FlagDefault', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x1C => {'name' => 'FlagLacing', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x2DE7 => {'name' => 'MinCache', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x2DF8 => {'name' => 'MaxCache', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x03E383 => {'name' => 'DefaultDuration', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x03314F => {'name' => 'TrackTimecodeScale', 'value' => FLOAT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x137F => {'name' => 'TrackOffset', 'value' => SINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x2E => 1}},
+ 0x136E => {'name' => 'Name', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x02B59C => {'name' => 'Language', 'value' => STRING, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x06 => {'name' => 'CodecID', 'value' => STRING, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x23A2 => {'name' => 'CodecPrivate', 'value' => BINARY, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x058688 => {'name' => 'CodecName', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x1A9697 => {'name' => 'CodecSettings', 'value' => UTF8, 'multi' => 0, 'traverse' => 0, 'pos' => {0x2E => 1}},
+ 0x1B4040 => {'name' => 'CodecInfoURL', 'value' => STRING, 'multi' => 1, 'traverse' => 0, 'pos' => {0x2E => 1}},
+ 0x06B240 => {'name' => 'CodecDownloadURL', 'value' => STRING, 'multi' => 1, 'traverse' => 0, 'pos' => {0x2E => 1}},
+ 0x2A => {'name' => 'CodecDecodeAll', 'value' => UINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x2E => 1}},
+ 0x2FAB => {'name' => 'TrackOverlay', 'value' => UINT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x2E => 1}},
+ # Video
+ 0x60 => {'name' => 'Video', 'value' => ELEMENT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x1A => {'name' => 'FlagInterlaced', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x60 => 1}},
+ 0x13B8 => {'name' => 'StereoMode', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x60 => 1}},
+ 0x30 => {'name' => 'PixelWidth', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x60 => 1}},
+ 0x3A => {'name' => 'PixelHeight', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x60 => 1}},
+ 0x14B0 => {'name' => 'DisplayWidth', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x60 => 1}},
+ 0x14BA => {'name' => 'DisplayHeight', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x60 => 1}},
+ 0x14B2 => {'name' => 'DisplayUnit', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x60 => 1}},
+ 0x14B3 => {'name' => 'AspectRatioType', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x60 => 1}},
+ 0x0EB524 => {'name' => 'ColorSpace', 'value' => BINARY, 'multi' => 0, 'traverse' => 1, 'pos' => {0x60 => 1}},
+ 0x0FB523 => {'name' => 'GammaValue', 'value' => FLOAT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x60 => 1}},
+ # Audio
+ 0x61 => {'name' => 'Audio', 'value' => ELEMENT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x2E => 1}},
+ 0x35 => {'name' => 'SamplingFrequency', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x61 => 1}},
+ 0x38B5 => {'name' => 'OutputSamplingFrequency','value' => UINT,'multi' => 0, 'traverse' => 1, 'pos' => {0x61 => 1}},
+ 0x1F => {'name' => 'Channels', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x61 => 1}},
+ 0x3D7B => {'name' => 'ChannelPositions', 'value' => BINARY, 'multi' => 0, 'traverse' => 1, 'pos' => {0x61 => 1}},
+ 0x2264 => {'name' => 'BitDepth', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x61 => 1}},
+ # Content Encoding
+ 0x2D80 => {'name' => 'ContentEncodings', 'value' => ELEMENT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x2E => 1}},
+ 0x2240 => {'name' => 'ContentEncoding', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x2D80 => 1}},
+ 0x1031 => {'name' => 'ContentEncodingOrder','value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x2240 => 1}},
+ 0x1032 => {'name' => 'ContentEncodingScope','value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x2240 => 1}},
+ 0x1033 => {'name' => 'ContentEncodingType','value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x2240 => 1}},
+ 0x1034 => {'name' => 'ContentCompression', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x2240 => 1}},
+ 0x0254 => {'name' => 'ContentCompAlgo', 'value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x1034 => 1}},
+ 0x0255 => {'name' => 'ContentCompSettings','value' => BINARY, 'multi' => 1, 'traverse' => 1, 'pos' => {0x1034 => 1}},
+ 0x1035 => {'name' => 'ContentEncryption', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x2240 => 1}},
+ 0x07E1 => {'name' => 'ContentEncAlgo', 'value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x1035 => 1}},
+ 0x07E2 => {'name' => 'ContentEncKeyID', 'value' => BINARY, 'multi' => 1, 'traverse' => 1, 'pos' => {0x1035 => 1}},
+ 0x07E3 => {'name' => 'ContentSignature', 'value' => BINARY, 'multi' => 1, 'traverse' => 1, 'pos' => {0x1035 => 1}},
+ 0x07E4 => {'name' => 'ContentSigKeyID', 'value' => BINARY, 'multi' => 1, 'traverse' => 1, 'pos' => {0x1035 => 1}},
+ 0x07E5 => {'name' => 'ContentSigAlgo', 'value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x1035 => 1}},
+ 0x07E6 => {'name' => 'ContentSigHashAlgo', 'value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x1035 => 1}},
+ # Cueing Data
+ 0x0C53BB6B => {'name' => 'Cues', 'value' => ELEMENT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x08538067 => 1}},
+ 0x3B => {'name' => 'CuePoint', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x0C53BB6B => 1}},
+ 0x33 => {'name' => 'CueTime', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x3B => 1}},
+ 0x37 => {'name' => 'CueTrackPositions', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x3B => 1}},
+ 0x77 => {'name' => 'CueTrack', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x37 => 1}},
+ 0x71 => {'name' => 'CueClusterPosition', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x37 => 1}},
+ 0x1378 => {'name' => 'CueBlockNumber', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x37 => 1}},
+ 0x6A => {'name' => 'CueCodecState', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x37 => 1}},
+ 0x5B => {'name' => 'CueReference', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x37 => 1}},
+ 0x16 => {'name' => 'CueRefTime', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x5B => 1}},
+ 0x17 => {'name' => 'CueRefCluster', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x5B => 1}},
+ 0x135F => {'name' => 'CueRefNumber', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x5B => 1}},
+ 0x6B => {'name' => 'CueRefCodecState', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x5B => 1}},
+ # Attachments
+ 0x0941A469 => {'name' => 'Attachments', 'value' => ELEMENT, 'multi' => 0, 'traverse' => 0, 'pos' => {0x08538067 => 1}},
+ 0x21A7 => {'name' => 'AttachedFile', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x0941A469 => 1}},
+ 0x067E => {'name' => 'FileDescription', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x21A7 => 1}},
+ 0x066E => {'name' => 'FileName', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x21A7 => 1}},
+ 0x0660 => {'name' => 'FileMimeType', 'value' => STRING, 'multi' => 0, 'traverse' => 1, 'pos' => {0x21A7 => 1}},
+ 0x065C => {'name' => 'FileData', 'value' => BINARY, 'multi' => 0, 'traverse' => 0, 'pos' => {0x21A7 => 1}},
+ 0x06AE => {'name' => 'FileUID', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x21A7 => 1}},
+ # Chapters
+ 0x0043A770 => {'name' => 'Chapters', 'value' => ELEMENT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x08538067 => 1}},
+ 0x05B9 => {'name' => 'EditionEntry', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x0043A770 => 1}},
+ 0x05BC => {'name' => 'EditionUID', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x05B9 => 1}},
+ 0x05BD => {'name' => 'EditionFlagHidden', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x05B9 => 1}},
+ 0x05DB => {'name' => 'EditionFlagDefault', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x05B9 => 1}},
+ 0x05DD => {'name' => 'EditionManaged', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x05B9 => 1}},
+ 0x36 => {'name' => 'ChapterAtom', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x05B9 => 1, 0x36 => 1}},
+ 0x33C4 => {'name' => 'ChapterUID', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x36 => 1}},
+ 0x11 => {'name' => 'ChapterTimeStart', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x36 => 1}},
+ 0x12 => {'name' => 'ChapterTimeEnd', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x36 => 1}},
+ 0x18 => {'name' => 'ChapterFlagHidden', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x36 => 1}},
+ 0x0598 => {'name' => 'ChapterFlagEnabled', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x36 => 1}},
+ 0x23C3 => {'name' => 'ChapterPhysicalEquiv','value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x36 => 1}},
+ 0x0F => {'name' => 'ChapterTrack', 'value' => ELEMENT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x36 => 1}},
+ 0x09 => {'name' => 'ChapterTrackNumber', 'value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x0F => 1}},
+ 0x00 => {'name' => 'ChapterDisplay', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x36 => 1}},
+ 0x05 => {'name' => 'ChapString', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x00 => 1}},
+ 0x037C => {'name' => 'ChapLanguage', 'value' => STRING, 'multi' => 1, 'traverse' => 1, 'pos' => {0x00 => 1}},
+ 0x037E => {'name' => 'ChapCountry', 'value' => STRING, 'multi' => 1, 'traverse' => 1, 'pos' => {0x00 => 1}},
+ # Tagging
+ 0x0254C367 => {'name' => 'Tags', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x08538067 => 1}},
+ 0x3373 => {'name' => 'Tag', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x0254C367 => 1}},
+ 0x23C0 => {'name' => 'Targets', 'value' => ELEMENT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x3373 => 1}},
+ 0x28CA => {'name' => 'TargetTypevalue', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x23C0 => 1}},
+ 0x23CA => {'name' => 'TargetType', 'value' => STRING, 'multi' => 0, 'traverse' => 1, 'pos' => {0x23C0 => 1}},
+ 0x23C9 => {'name' => 'EditionUID', 'value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x23C0 => 1}},
+ 0x23C4 => {'name' => 'ChapterUID', 'value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x23C0 => 1}},
+ 0x23C5 => {'name' => 'TrackUID', 'value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x23C0 => 1}},
+ 0x23C6 => {'name' => 'AttachmentUID', 'value' => UINT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x23C0 => 1}},
+ 0x27C8 => {'name' => 'SimpleTag', 'value' => ELEMENT, 'multi' => 1, 'traverse' => 1, 'pos' => {0x3373 => 1, 0x27C8 => 1}},
+ 0x05A3 => {'name' => 'TagName', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x27C8 => 1}},
+ 0x047A => {'name' => 'TagLanguage', 'value' => STRING, 'multi' => 0, 'traverse' => 1, 'pos' => {0x27C8 => 1}},
+ 0x0484 => {'name' => 'TagDefault', 'value' => UINT, 'multi' => 0, 'traverse' => 1, 'pos' => {0x27C8 => 1}},
+ 0x0487 => {'name' => 'TagString', 'value' => UTF8, 'multi' => 0, 'traverse' => 1, 'pos' => {0x27C8 => 1}},
+ 0x0485 => {'name' => 'TagBinary', 'value' => BINARY, 'multi' => 0, 'traverse' => 0, 'pos' => {0x27C8 => 1}},
+# => {'name' => '', 'value' => , 'multi' => 1, 'traverse' => 1, 'pos' => {}},
+ };
+
+}
+
+################################################################################
+sub new {
+ open(my $temp, $_[1]) or die "ERROR: Can't open file '$_[1]' for reading from matroska::new\n$!\n";
+ binmode($temp);
+ my $self = {
+ 'filename' => $_[1],
+ 'buffersize' => $matroska::DefaultBuffer,
+ 'buffer' => '',
+ 'filehandle' => $temp,
+ 'bufferpos' => 0,
+ 'useseek' => (($#_ > 1) ? (0 + $_[2]) : (1)),
+ };
+ bless $self;
+ return $self;
+}
+
+sub readToBuffer {
+ my $self = $_[0];
+ my $len = read($self->{'filehandle'}, my $temp, $self->{'buffersize'});
+ $self->{'buffer'} .= $temp;
+ return $len;
+}
+
+sub flush {
+ my $self = $_[0];
+ if($self->{'bufferpos'} >= $matroska::FlushBuffer) {
+ # FLUSH!
+ substr($self->{'buffer'}, 0, $self->{'bufferpos'}) = '';
+ $self->{'bufferpos'} = 0;
+ }
+}
+
+################################################################################
+sub readBytes {
+ my $self = $_[0];
+ while($self->{'bufferpos'} + $_[1] > length($self->{'buffer'})) {
+ # The buffer is not long enough to return the amount of stuff requested; make it longer!
+ unless($self->readToBuffer()) {
+ last;
+# die "ERROR: Can't read past end of file";
+# return '';
+ }
+ }
+ # The buffer is now long enough to grab the thigie out of
+ my $returnThis = substr($self->{'buffer'}, $self->{'bufferpos'}, $_[1]);
+ $self->{'bufferpos'} += $_[1];
+ $self->flush();
+ return $returnThis;
+}
+
+sub readSize {
+ my $self = $_[0];
+ my $b1 = $self->readBytes(1);
+ if(ord($b1) & 0x80) {
+ # 1 byte
+ return ord($b1) & 0x7f;
+ } elsif(ord($b1) & 0x40) {
+ # 2 bytes
+ $b1 .= $self->readBytes(1);
+ return unpack("n", chr(0x40) ^ $b1);
+ } elsif(ord($b1) & 0x20) {
+ # 3 bytes
+ $b1 .= $self->readBytes(2);
+ return unpack("N", chr(0) . (chr(0x20) ^ $b1));
+ } elsif(ord($b1) & 0x10) {
+ # 4 bytes
+ $b1 .= $self->readBytes(3);
+ return unpack("N", chr(0x10) ^ $b1);
+ } elsif(ord($b1) & 0x08) {
+ # 5 bytes
+ $b1 .= $self->readBytes(4);
+ my ($high, $low) = unpack("CN", chr(0x08) ^ $b1);
+ $high ? (return Math::BigInt->new("$high")->bmul("4294967296")->badd("$low")->bstr()) : (return $low);
+ } elsif(ord($b1) & 0x04) {
+ # 6 bytes
+ $b1 .= $self->readBytes(5);
+ my ($high, $low) = unpack("nN", chr(0x04) ^ $b1);
+ $high ? (return Math::BigInt->new("$high")->bmul("4294967296")->badd("$low")->bstr()) : (return $low);
+ } elsif(ord($b1) & 0x02) {
+ # 7 bytes
+ $b1 .= $self->readBytes(6);
+ my ($high, $low) = unpack("NN", chr(0) . (chr(0x02) ^ $b1));
+ $high ? (return Math::BigInt->new("$high")->bmul("4294967296")->badd("$low")->bstr()) : (return $low);
+ } elsif(ord($b1) & 0x01) {
+ # 8 bytes
+ $b1 .= $self->readBytes(7);
+ my ($high, $low) = unpack("NN", chr(0x01) ^ $b1);
+ $high ? (return Math::BigInt->new("$high")->bmul("4294967296")->badd("$low")->bstr()) : (return $low);
+ } else {
+ # SOMETHIN' WEIRD
+ return "???";
+ }
+}
+
+sub readInteger {
+ my $self = $_[0];
+ my $len = $_[1];
+ if($len == 1) {
+ # 1 byte
+ return ord($self->readBytes(1));
+ } elsif($len == 2) {
+ # 2 bytes
+ return unpack("n", $self->readBytes(2));
+ } elsif($len == 3) {
+ # 3 bytes
+ return unpack("N", chr(0) . ($self->readBytes(3)));
+ } elsif($len == 4) {
+ # 4 bytes
+ return unpack("N", $self->readBytes(4));
+ } elsif($len == 5) {
+ # 5 bytes
+ my ($high, $low) = unpack("CN", $self->readBytes(5));
+ $high ? (return Math::BigInt->new("$high")->bmul("4294967296")->badd("$low")->bstr()) : (return $low);
+ } elsif($len == 6) {
+ # 6 bytes
+ my ($high, $low) = unpack("nN", $self->readBytes(6));
+ $high ? (return Math::BigInt->new("$high")->bmul("4294967296")->badd("$low")->bstr()) : (return $low);
+ } elsif($len == 7) {
+ # 7 bytes
+ my ($high, $low) = unpack("NN", chr(0) . ($self->readBytes(7)));
+ $high ? (return Math::BigInt->new("$high")->bmul("4294967296")->badd("$low")->bstr()) : (return $low);
+ } elsif($len == 8) {
+ # 8 bytes
+ my ($high, $low) = unpack("NN", $self->readBytes(8));
+ $high ? (return Math::BigInt->new("$high")->bmul("4294967296")->badd("$low")->bstr()) : (return $low);
+ } else {
+ # SPECIAL
+ die "ERROR: Don't know what length '$len' means at " . $self->tell();
+ }
+}
+
+sub readFloat {
+ my $self = $_[0];
+ my $len = $_[1];
+ # Need to reverse the bytes for little-endian machines
+ if($len == 4) {
+ # single
+ return unpack('f', scalar reverse($self->readBytes(4)));
+ #return unpack('f', $self->readBytes(4));
+ } elsif($len == 8) {
+ # double
+ return unpack('d', scalar reverse($self->readBytes(8)));
+ #return unpack('d', $self->readBytes(8));
+ } elsif($len == 10) {
+ # extended (don't know how to handle it)
+ return 'EXTENDED';
+ } else {
+ # INVALID!!!11
+ die "ERROR: Don't know how to make a '$len' byte float at " . $self->tell();
+ }
+}
+
+sub readID {
+ my $self = $_[0];
+ my $b1 = $self->readBytes(1);
+ my $number;
+ if(ord($b1) & 0x80) {
+ # 1 byte
+ $number = ord($b1) & 0x7f;
+ } elsif(ord($b1) & 0x40) {
+ # 2 bytes
+ $b1 .= $self->readBytes(1);
+ $number = unpack("n", chr(0x40) ^ $b1);
+ } elsif(ord($b1) & 0x20) {
+ # 3 bytes
+ $b1 .= $self->readBytes(2);
+ $number = unpack("N", chr(0) . (chr(0x20) ^ $b1));
+ } elsif(ord($b1) & 0x10) {
+ # 4 bytes
+ $b1 .= $self->readBytes(3);
+ $number = unpack("N", chr(0x10) ^ $b1);
+ } else {
+ # SPECIAL; just return -1
+ return -1;
+ }
+ return $number;
+}
+
+################################################################################
+sub seek {
+ my ($self, $offset, $whence) = @_;
+ my $bufferEnd = tell($self->{'filehandle'});
+ my $bufferBegin = $bufferEnd - length($self->{'buffer'});
+ my $outsideTell = $bufferBegin + $self->{'bufferpos'};
+ my $absOffset;
+ if($whence == 0) {
+ # Beginning of the file
+ $absOffset = $offset;
+ } elsif($whence == 1) {
+ # Current position
+ $absOffset = $outsideTell + $offset;
+ }
+ if($absOffset >= $bufferBegin && $absOffset < $bufferEnd) {
+ # The requested position is within the buffer
+ $self->{'bufferpos'} = $absOffset - $bufferBegin;
+ } else {
+ # Buffer is of no use any more
+ $self->{'bufferpos'} = 0;
+ $self->{'buffer'} = '';
+ seek($self->{'filehandle'}, $absOffset, 0);
+ }
+}
+
+sub tell {
+ # Same as standard tell()
+ return(tell($_[0]->{'filehandle'}) - length($_[0]{'buffer'}) + $_[0]{'bufferpos'});
+}
+
+################################################################################
+sub parse {
+ my $self = $_[0];
+ my $override = $_[1] ? $_[1] : {}; # For override, -1 is default 0 is no traverse, 1 is traverse, 2 is traverse all
+ my $filesize = -s ($self->{'filehandle'});
+
+ return $self->parseRecurse(0,0,'',$override,0);
+}
+
+# To be parsed
+sub parseRecurse {
+ # If all is 0, then elements are parsed normally
+ # If all is 1, then all elements are traversed
+ my ($self, $from, $to, $parentElement, $override, $all) = @_;
+ $self->seek($from, 0);
+
+ my $ret = {};
+
+ while(($to == 0) ? ($self->tell() < -s $self->{'filehandle'}) : ($self->tell() < $to)) {
+ my $currentElementBegin = $self->tell();
+ my $idNumber = $self->readID();
+ if($idNumber == -1) {
+ return $ret;
+ }
+
+ my $elementSize = $self->readSize();
+ my $elementInsideBegin = $self->tell();
+ if(defined($matroska::Elements->{$idNumber})) {
+ # ID in database; parse
+ my $idInfo = $matroska::Elements->{$idNumber};
+
+ if($idInfo->{'pos'}{$parentElement} || ($parentElement && $idInfo->{'pos'}{'all'})) {
+ # Element is OK where it is
+
+ my $traverse;
+
+ if($all) {
+ $traverse = 2;
+ } elsif(defined($override->{$idInfo->{'name'}})) {
+ if($override->{$idInfo->{'name'}} == 2) {
+ $traverse = 2;
+ } elsif($override->{$idInfo->{'name'}} == 1) {
+ $traverse = 1;
+ } elsif($override->{$idInfo->{'name'}} == 0) {
+ $traverse = 0;
+ } else {
+ $traverse = $idInfo->{'traverse'};
+ }
+ } else {
+ $traverse = $idInfo->{'traverse'};
+ }
+
+ # Figure out where to put the element
+ my $elementPlace; # This is a reference to the spot to stick the elephant
+ if($idInfo->{'multi'}) {
+ push(@{$ret->{$idInfo->{'name'}}}, {'size' => $elementSize, 'headersize' => $elementInsideBegin - $currentElementBegin, 'pos' => $currentElementBegin});
+ $elementPlace = $ret->{$idInfo->{'name'}}[-1];
+ } else {
+ $ret->{$idInfo->{'name'}} = {'size' => $elementSize, 'headersize' => $elementInsideBegin - $currentElementBegin, 'pos' => $currentElementBegin};
+ $elementPlace = $ret->{$idInfo->{'name'}};
+ }
+
+ # Figure out what to do with it
+ if($idInfo->{'value'} == ELEMENT) {
+ if($traverse == 2) {
+ $elementPlace->{'children'} = $self->parseRecurse($self->tell(), $self->tell() + $elementSize, $idNumber, $override, 1);
+ } elsif($traverse == 1) {
+ $elementPlace->{'children'} = $self->parseRecurse($self->tell(), $self->tell() + $elementSize, $idNumber, $override, 0);
+ } else {
+ $elementPlace->{'value'} = 'SKIPPED';
+ $self->seek($elementSize, 1);
+ }
+ } elsif($idInfo->{'value'} == UINT || $idInfo->{'value'} == SINT || $idInfo->{'value'} == DATE) {
+ $elementPlace->{'value'} = $self->readInteger($elementSize);
+ } elsif($idInfo->{'value'} == STRING || $idInfo->{'value'} == UTF8) {
+ $elementPlace->{'value'} = $self->readBytes($elementSize);
+ } elsif($idInfo->{'value'} == BINARY) {
+ $elementPlace->{'value'} = 'BINARY';
+ $self->seek($elementSize, 1);
+ } elsif($idInfo->{'value'} == FLOAT) {
+ $elementPlace->{'value'} = $self->readFloat($elementSize);
+ } elsif($idInfo->{'value'} == BLOCK) {
+ if($traverse) {
+ $elementPlace->{'children'} = $self->readBlock($elementSize);
+ } else {
+ $elementPlace->{'value'} = 'SKIPPED';
+ $self->seek($elementSize, 1);
+ }
+ } else {
+ $elementPlace->{'value'} = 'DEATH';
+ $self->seek($elementSize, 1);
+ }
+
+ } else {
+ # Element does not belong here!
+ warn "Warning: Element number $idNumber can not be a child of $parentElement at ", $self->tell(), "!\n";
+ $self->seek($elementSize, 1);
+ }
+ } else {
+ # Does not exist in the database, but assume the file is OK (might be a bad idea)
+ warn "Warning: Can't find the ID $idNumber at ", $self->tell(), " in the database!\n";
+ $self->seek($elementSize, 1);
+ }
+
+ }
+ return $ret;
+}
+
+sub readBlock {
+ my ($self, $size) = @_;
+ my %out;
+ my $blockHeaderLocation = $self->tell();
+ my $trackNum = $self->readSize();
+ my $timecode = $self->readInteger(2);
+ my $bitField = ord($self->readBytes(1));
+ my $gap = $bitField & 0x01;
+ my $lace = ($bitField & 0x06) >> 1;
+ my $reserved = $bitField & 0xf8;
+ $out{'track'} = $trackNum;
+ $out{'timecode'} = $timecode;
+ if($gap) {
+ $out{'gap'} = 1;
+ }
+ if($lace == 0) {
+ $out{'frames'}[0] = {'pos' => $self->tell(), 'size' => $size - $self->tell + $blockHeaderLocation};
+ $out{'lacing'} = 'None';
+ } else {
+ my $frames = $self->readInteger(1) + 1;
+ my $frameStart = $self->tell();
+ if($lace == 1) {
+ # Xiph lacing
+ $out{'lacing'} = 'Xiph';
+ my @sizePerFrame = (0);
+ my $totalFrameSize = 0;
+ for my $frameNow (2 .. $frames) {
+ while(1) {
+ my $a = $self->readInteger(1);
+ $totalFrameSize += $a;
+ if($a == 255) {
+ $sizePerFrame[-1] += 255;
+ } else {
+ $sizePerFrame[-1] += $a;
+ push(@sizePerFrame, 0);
+ last;
+ }
+ }
+ }
+
+ $sizePerFrame[-1] = $size - $self->tell() + $blockHeaderLocation - $totalFrameSize;
+
+ $totalFrameSize = 0;
+ foreach my $frameSize (@sizePerFrame) {
+ push(@{$out{'frames'}}, {'size' => $frameSize, 'pos' => $self->tell() + $totalFrameSize});
+ $totalFrameSize += $frameSize;
+ }
+
+ } elsif($lace == 2) {
+ # Fixed-size lacing
+ $out{'lacing'} = 'Fixed';
+ my $sizePerFrame = ($size - $self->tell() + $blockHeaderLocation) / $frames;
+ $out{'framesize'} = $sizePerFrame;
+ for my $frameNow (0 .. $frames - 1) {
+ push(@{$out{'frames'}}, {'size' => $sizePerFrame, 'pos' => $frameStart + $frameNow * $sizePerFrame});
+ }
+ } elsif($lace == 3) {
+ # EBML lacing
+ $out{'lacing'} = 'EBML';
+ my $totalFrameSize = $self->readSize();
+ my @sizePerFrame = $totalFrameSize;
+ my $prevPos = $self->tell();
+ for my $a (3 .. $frames) {
+ my $prevPos = $self->tell();
+ my $uint = $self->readSize();
+ my $thisFrameSize = $sizePerFrame[-1] + $uint - 2 ** (7 * ($self->tell() - $prevPos) - 1) + 1;
+ $totalFrameSize += $thisFrameSize;
+ push(@sizePerFrame, $thisFrameSize);
+ }
+
+ push(@sizePerFrame, $size - $self->tell() + $blockHeaderLocation - $totalFrameSize);
+
+ $totalFrameSize = 0;
+ foreach my $frameSize (@sizePerFrame) {
+ push(@{$out{'frames'}}, {'size' => $frameSize, 'pos' => $self->tell() + $totalFrameSize});
+ $totalFrameSize += $frameSize;
+ }
+
+ } else {
+ $out{'lacing'} = 'COMPLETELY WRONG! OH NOES!!!11';
+ }
+
+
+ }
+ $self->seek($blockHeaderLocation, 0);
+ $self->seek($size, 1);
+ return \%out;
+}
+
+1;