#! /usr/bin/env perl # Generate C definitions for parsing Matroska files. use strict; use warnings; use FindBin; use lib "$FindBin::Bin/lib"; use Parse::Matroska::Definitions; use Parse::Matroska::Reader; use Getopt::Long; use List::Util qw{max}; my @global_elem_list = @Parse::Matroska::Definitions::global_elem_list; Getopt::Long::Configure(qw{auto_version auto_help}); my %opt; GetOptions(\%opt, "generate-header", "generate-definitions", "full", ); if ($opt{"generate-header"}) { generate_c_header(); } elsif ($opt{"generate-definitions"}) { generate_c_definitions(); } else { for (@ARGV) { my $reader = Parse::Matroska::Reader->new($_ eq '-' ? \*STDIN : $_) or die $!; while (my $elem = $reader->read_element($_ eq '-')) { process_elem($elem, $_ eq '-'); } } } # Generate declarations for libmpdemux/ebml_types.h sub generate_c_header { print "/* Generated by TOOLS/matroska.pl, do not edit manually */\n\n"; # Write a #define for the ElementID of each known element for my $el (@global_elem_list) { printf "#define %-40s 0x%s\n", $el->{definename}, $el->{elid}; } print "\n"; # Define a struct for each ElementID that has child elements for my $el (@global_elem_list) { next unless $el->{subelements}; print "\nstruct $el->{structname} {\n"; # Figure out the length of the longest variable name # Used for pretty-printing in the next step my $l = max(map { length $_->{valname} } values %{$el->{subelements}}); # Output each variable, with pointers for array (multiple) elements for my $subel (sort { $a->{definename} cmp $b->{definename} } values %{$el->{subelements}}) { printf " %-${l}s %s%s;\n", $subel->{valname}, $subel->{multiple}?'*':' ', $subel->{fieldname}; } print "\n"; # Output a counter variable for each element # (presence/absence for scalars, item count for arrays) for my $subel (values %{$el->{subelements}}) { print " int n_$subel->{fieldname};\n" } print "};\n"; } print "\n"; # Output extern references for ebml_elem_desc structs for each of the elements # These are defined by generate_c_definitions for my $el (@global_elem_list) { next unless $el->{subelements}; print "extern const struct ebml_elem_desc $el->{structname}_desc;\n"; } print "\n"; # Output the max number of sub-elements a known element might have printf "#define MAX_EBML_SUBELEMENTS %d\n", max(map { scalar keys %{$_->{subelements}} } grep { $_->{subelements} } @global_elem_list); } # Generate definitions for libmpdemux/ebml_defs.c sub generate_c_definitions { print "/* Generated by TOOLS/matroska.pl, do not edit manually */\n\n"; # ebml_defs.c uses macros declared in ebml.c for my $el (@global_elem_list) { print "\n"; if ($el->{subelements}) { # set N for the next macros print "#define N $el->{fieldname}\n"; # define a struct ebml_$N_desc and gets ready to define fields # this secretly opens two scopes; hence the }}; at the end print "E_S(\"$el->{name}\", ".scalar(keys %{$el->{subelements}}).")\n"; # define a field for each subelement # also does lots of macro magic, but doesn't open a scope for my $subel (sort { $a->{definename} cmp $b->{definename} } values %{$el->{subelements}}) { print "F($subel->{definename}, $subel->{fieldname}, ". ($subel->{multiple}?'1':'0').")\n"; } # close the struct print "}};\n"; # unset N since we've used it print "#undef N\n"; } else { print "E(\"$el->{name}\", $el->{fieldname}, $el->{ebmltype})\n"; } } } sub repr { my @ret; foreach (@_) { if (/'/) { s/"/\\"/g; push @ret, "\"$_\""; } else { push @ret, "'$_'"; } } return @ret if wantarray; return pop @ret if defined wantarray; return; } sub process_elem { my ($elem, $read_bin) = @_; unless ($opt{full}) { if ($elem->{name} eq 'Cluster' || $elem->{name} eq 'Cues') { $elem->skip; return; } } die unless $elem; if ($elem->{type} ne 'skip') { print "$elem->{depth} $elem->{elid} $elem->{name} size: $elem->{content_len} value: "; } if ($elem->{type} eq 'sub') { print "subelements:\n"; while (my $chld = $elem->next_child($read_bin)) { process_elem($chld); } } elsif ($elem->{type} eq 'binary') { my $t = "{content_len} bytes>"; if ($elem->{content_len} < 20) { $t = unpack "H*", $elem->get_value; } print "binary $t\n"; delete $elem->{value}; } elsif ($elem->{type} eq 'ebml_id') { print "binary $elem->{value}->{elid} (".($elem->{value}->{name}||"UNKNOWN").")\n"; } elsif ($elem->{type} eq 'skip') { # skip } elsif ($elem->{type} eq 'str') { print "string ". repr($elem->get_value) . "\n"; } else { print "$elem->{type} ". $elem->get_value ."\n"; } }