#!/usr/bin/perl # # avi-metadata script 0.1 # copyright (c) 2007 nhjm449 # # Reads/writes metadata from AVI files via File::Format::RIFF. # # You may do whatever you want to with this script as long as copyright # information is retained. # $|++; use strict; use Getopt::Long; use File::Format::RIFF; my %options = ( ); sub usage { print << "EOF"; usage: $0 [OPTIONS] [FILE] -h, --help this (help) message -o, --output output file -s, --strip-tags strip tags from file -l, --list-tags lists tags in file -a, --all-tags lists all possible tags -t, --tag set tag value example: $0 input.avi -l $0 input.avi -o output.avi -t IART=foo -t INAM=bar EOF exit; }; Getopt::Long::Configure("bundling"); GetOptions(\%options, "output|o=s", "strip-tags|s", "list-tags|l", "all-tags|a", "tag|t=s%", "debug|d+") or usage(); $options{"input"} = $ARGV[0]; my %tags = ( 'INAM' => 'Name', 'IART' => 'Artist', 'ICOP' => 'Copyright', 'IPRD' => 'Product', 'ICRD' => 'Creation Date', 'IGNR' => 'Genre', 'ISGN' => 'Secondary Genre (EXT)', 'ISBJ' => 'Subject', 'IKEY' => 'Keywords', 'ICMT' => 'Comments', 'IWRI' => 'Written by (EXT)', 'IPRO' => 'Produced by (EXT)', 'ICNM' => 'Cinematographer (EXT)', 'IPDS' => 'Production Designer (EXT)', 'IEDT' => 'Edited by (EXT)', 'ICDS' => 'Costume Designer (EXT)', 'IMUS' => 'Music by (EXT)', 'ISTD' => 'Production Studio (EXT)', 'IDST' => 'Distributed by (EXT)', 'ICNT' => 'Country (EXT)', 'ILNG' => 'Language (EXT)', 'IRTD' => 'Rating (EXT)', 'ISTR' => 'Starring (EXT)', 'ISFT' => 'Software', 'ITCH' => 'Technician / Encoded by', 'IENG' => 'Engineer / Digitized by', 'IWEB' => 'Website (EXT)', 'IDIT' => 'Digitizing Date', 'ISMP' => 'SMPTE time code', 'ISRF' => 'Source From', 'IMED' => 'Medium', 'ISRC' => 'Source', 'IARL' => 'Archival Location', 'ICMS' => 'Commissioned by', 'IPRT' => 'Part (EXT)', 'IFRM' => 'Total Number of Parts (EXT)', 'ICRP' => 'Cropped', 'ISHP' => 'Sharpness', 'IDIM' => 'Dimensions', 'ILGT' => 'Lightness', 'IDPI' => 'Dots Per Inch', 'IPLT' => 'Palette Setting', 'IAS1' => 'First language (EXT)', 'IAS2' => 'Second language (EXT)', 'IAS3' => 'Third language (EXT)', 'IAS4' => 'Fourth language (EXT)', 'IAS5' => 'Fifth language (EXT)', 'IAS6' => 'Sixth language (EXT)', 'IAS7' => 'Seventh language (EXT)', 'IAS8' => 'Eighth language (EXT)', 'IAS9' => 'Ninth language (EXT)', 'ICAS' => 'Default audio stream (EXT)', 'IBSU' => 'Base URL (EXT)', 'ILGU' => 'Logo URL (EXT)', 'ILIU' => 'Logo Icon URL (EXT)', 'IWMU' => 'Watermark URL (EXT)', 'IMIU' => 'More Info URL (EXT)', 'IMBI' => 'More Info Banner Image (EXT)', 'IMBU' => 'More Info Banner URL (EXT)', 'IMIT' => 'More Info Text (EXT)' ); if ( $options{"all-tags"} ) { print "\nAll available tags: (EXT=Extended Info)\n\n"; foreach my $tag ( sort keys %tags ) { print " $tag: " . $tags{$tag} . "\n"; } print "\nMore info: http://abcavi.kibi.ru/infotags.htm\n\n"; exit; } usage() if ( !($options{"input"} && $options{"output"}) && !($options{"input"} && $options{"list-tags"}) ); sub { foreach ( keys %options ) { print "$_=" . $options{$_} . "\n"; } } if ($options{"debug"}); my %currentinfo = ( ); my %new = ( ); my $position = 0; my @junktags; my $junk_recurse_has_run = 0; foreach ( keys %{$options{"tag"}} ) { next if ( $_ eq uc($_) ); $options{"tag"}{uc($_)} = $options{"tag"}{$_}; delete( $options{"tag"}{$_} ); } print "\nReading from " . $options{"input"} . "... "; open( IN, $options{"input"} ) or die "Could not open file: $!\n\n"; my ( $riff1 ) = File::Format::RIFF->read( \*IN ); close( IN ); print "done\n"; sub list_tags { my $tagsfound; my ( $data, $file, $out ) = @_; print (($out ? "\nOutput" : "\nInput") . " tags: ($file)\n\n"); foreach my $chunk ( $data->data ) { if ( $chunk->id eq 'LIST' ) { if ($chunk->type eq 'INFO') { foreach my $currentinfochunk ( $chunk->data ) { my $cleandata = $currentinfochunk->data; $cleandata =~ s/\c@//g; print " " . uc($currentinfochunk->id) . ": ".$tags{uc($currentinfochunk->id)}.(' ' x (30- length($tags{uc($currentinfochunk->id)}))).": ".$cleandata."\n"; $tagsfound = 1; } } } } print " None found!\n" if (!$tagsfound); junk_recurse( $riff1 ) if (!$junk_recurse_has_run); print "\n" if ($#junktags > -1); foreach my $junkinfo (@junktags) { $junkinfo =~ s/\c@//g; print " JUNK found: : " . $junkinfo . "\n"; } print "\n"; } sub junk_recurse { $junk_recurse_has_run = 1; my ( $data ) = shift; foreach my $chunk ( $data->data ) { print "recursing...".$chunk->id."\n" if ($options{"debug"} > 1); if ( $chunk->id eq 'LIST' ) { print "> LIST type ".$chunk->type."\n" if ($options{"debug"} > 1); junk_recurse($chunk); next; } if ( uc($chunk->id) eq 'JUNK' ) { print " JUNK: " . $chunk->data . "\n" if ($options{"debug"} > 1); push(@junktags, $chunk->data); } } } if ( $options{"list-tags"} ) { list_tags( $riff1, $options{"input"} ); $riff1->dump if ($options{"debug"} && !$options{"output"}); exit if ( !$options{"output"} ); } foreach my $chunk ( $riff1->data ) { $position++; if ( $chunk->id eq 'LIST' ) { if ( $chunk->type eq 'INFO' ) { foreach my $currentinfochunk ( $chunk->data ) { $currentinfo{uc($currentinfochunk->id)} = $currentinfochunk->data; print "current: " . uc($currentinfochunk->id) . " = " . $currentinfochunk->data . "\n" if ($options{"debug"}); } print @{ $riff1->{data} }[$position-1]->type."<-THE ID IS $position\n" if ($options{"debug"}); $riff1->splice( $position-1, 1 ); } } } $position = 0; if ( !$options{"strip-tags"} ) { foreach my $chunk ( $riff1->data ) { $position++; print "($position)".$chunk->id.":"."\n" if ($options{"debug"}); if ( $chunk->id eq 'LIST' ) { if ( $chunk->type eq 'hdrl' ) { my $infochunk = new File::Format::RIFF::List( 'INFO' ); foreach my $newitem ( sort keys %tags ) { next if ( !$options{"tag"}{$newitem} && !$currentinfo{$newitem} ); print "new $newitem:".($options{"tag"}{$newitem} ? "new:".$options{"tag"}{$newitem} : "old:".$currentinfo{$newitem})."\n" if ($options{"debug"}); $infochunk->addChunk( $newitem => ($options{"tag"}{$newitem} ? $options{"tag"}{$newitem} : $currentinfo{$newitem}) ); } $riff1->splice( $position, 0, $infochunk ); } } } } $riff1->dump if ($options{"debug"}); print "\n" if (!$options{"list-tags"}); print "Outputting to ".$options{"output"}."... "; open( OUT, ">", $options{"output"} ) or die "Could not open file: $!\n\n"; $riff1->write( \*OUT ); close( OUT ); print "done!\n"; if ( $options{"list-tags"} ) { list_tags( $riff1, $options{"output"}, 1 ); } else { print "\n"; } # id: LIST (INFO) size: 70 (82) # id: IART size: 5 (14): [p7]^@ # id: ICMT size: 11 (20): Code Lyoko^@ # id: INAM size: 27 (36): (301) X.A.N.A. Awakens (1)^@