When I use the FLV::Info
module to extract metadata from or merge multiple FLV files, I frequently receive a "Tag size is too small" error and then the module will just refuse to work. Someone issued a bug report here three years ago but there does not seem to be a fix.
Well, recently I find if I simply comment out the following lines of code in Tag.pm, one of FLV::Info
's dependency modules like so:
=pod
if ($datasize < 11)
{
die "Tag size is too small ($datasize) at byte " . $file->get_pos(-10);
}
=cut
FLV::Info
will then readily do the work as expected.
I'm not sure if this is a very dumb question but I feel curious:
Is there a simple way of changing a couple of lines of code in a loaded module without modifying the original .pm file?
Any ideas, suggestions or comments? Thanks like always :)
UPDATE
Big thanks to @Shwern. Your answer is highly satisfying :) Also thanks to @DVK for the suggestion and that "monkey patch" term and @brian for the book recommendation.
Here's my feedback for tests on a sample FLV file that would throw me "Tag size is too small" error if I use the original module without doing anything to it.
The "eval it back" approach solves the problem
use FLV::Info;
use Data::Dump::Streamer;
my $original = FLV::Tag->can("parse");
my $code = Dump($original)->Out;
#$code =~ s{\Qif ($datasize < 11)\E}{if (0)}; #This somehow won't work
$code =~ s{die "Tag}{warn "Tag}; #Let it warn but not die
no warnings 'redefine';
*FLV::Tag::parse = eval $code;
my $reader = FLV::Info->new();
$reader->parse('sample.flv');
my %info = $reader->get_info();
print "$info{video_count} video frames\n";
print $reader->report();
The "overide die to not die" approach also works
BEGIN {
*CORE::GLOBAL::die = sub { return CORE::die(@_) };
}
use FLV::Info;
{
local *CORE::GLOBAL::die = sub {
return if $_[0] =~ /^Tag size is too small/;
return CORE::die(@_);
};
my $reader = FLV::Info->new();
$reader->parse('sample.flv');
my %info = $reader->get_info();
print "$info{video_count} video frames\n";
print $reader->report();
}
The "redefine" approach, however, does not work as I expected.
I copy-and-pasted the original FLV::Tag::parse subroutine and commented out the lines of code exactly the way I modified the original Tag.pm file like so:
use FLV::Info;
no warnings 'redefine';
*FLV::Tag::parse = sub {
...
...
=pod
if ($datasize < 11)
{
die "Tag size is too small ($datasize) at byte " . $file->get_pos(-10);
}
=cut
...
...
};
my $reader = FLV::Info->new();
$reader->parse('sample.flv');
my %info = $reader->get_info();
print "$info{video_count} video frames\n";
print $reader->report();
but I got this error:
Unknown tag type 18 at byte 13 (0xd)
Well, even if copy-and-pasted exactly the same parse subroutine without any modification in my redefinition, I'm receiving the "Unknown tag type" error instead of "Tag size is too small".
This is strange!
For reference, the "eval it back" and "override die to not die" approaches will give me the following:
1992 video frames
File name sample.flv
File size 5767831 bytes
Duration about 79.6 seconds
Video 1992 frames
codec AVC
type interframe/keyframe
Audio 1712 packets
format AAC
rate 44100 Hz
size 16 bit
type stereo
Meta 1 event
audiocodecid 10
audiosamplerate 22050
audiosamplesize 16
audiosize 342817
creationdate unknown
datasize 805
duration 79.6
filesize 5767869
framerate 25
height 300
keyframes {
>>> 'filepositions' => [
>>> '780',
>>> '865',
>>> '1324122',
>>> '2348913',
>>> '2978630',
>>> '3479001',
>>> '3973756',
>>> '4476281',
>>> '4997226',
>>> '5391890'
>>> ],
>>> 'times' => [
>>> '0',
>>> '0',
>>> '9.6',
>>> '19.2',
>>> '28.8',
>>> '38.4',
>>> '46.32',
>>> '55.92',
>>> '64.88',
>>> '73.88'
>>> ]
>>> }
lastkeyframetimestamp 73.88
lasttimestamp 79.6
metadatacreator Manitu Group FLV MetaData Injector 2
metadatadate 1281964633858
stereo 1
videocodecid 7
videosize 5424234
width 400
FINAL UPDATE
I've figured out why the "redefine" approach failed by turning on strict and warnings pragma. Thanks to @Schwern for the reminder :)
Add the following lines of code (copied from the FLV::Util module) first and then do the redefining of the FLV::Tag::parse subroutine.
Readonly::Hash our %TAG_CLASSES => (
8 => 'FLV::AudioTag',
9 => 'FLV::VideoTag',
18 => 'FLV::MetaTag',
);
Simple? No. But there are some crazy things you can do. Here's some bad ideas.
One of the more obvious is to put a hacked copy of the .pm file into your project, somewhere so its seen before the system version.
Another is similar, but to cut & paste the whole routine into your code and inject it after loading the original.
use FLV::Tag;
no warnings 'redefine';
*FLV::Tag::parse = sub {
...copy of FLV::Tag::parse with your edits...
};
You could override die
to not die when it sees that message.
BEGIN {
# In order to override die() later, you must override it at compile time.
*CORE::GLOBAL::die = sub { return CORE::die(@_) };
}
{
local *CORE::GLOBAL::die = sub {
return if $_[0] =~ /^Tag size too small/;
return CORE::die(@_);
}
...do your thing...
}
You could dump the contents of that subroutine back into Perl, do a string replace on the code, and eval it back.
use Data::Dump::Streamer;
my $original = FLV::Tag->can("parse");
my $code = Dump($original)->Out;
$code =~ s{\Qif ($datasize < 11)\E}{if( 0 )};
no warnings 'redefine';
*FLV::Tag::parse = eval $code;
Or you could walk the opcode tree of that subroutine and change the condition, which I leave as an exercise for someone with more time on their hands.
They are all bad ideas. You're better off just changing the code in place and contacting the author again about it letting them know there's new information.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With