Requirements
- Some spare time and basic perl programming skills
- Mplayer/ffplay and mpg123
- Wireshark
- Perl 5 with following modules
What does the script do?
The script processes FLV stream by deleting audio/video tags. It is also possible to obtain a raw content of the tags (eg. mp3). Data is processed on fly, so there is no necessity to download whole file to listen favorite music from eg. YouTube.
3. Legal notice
The script uses direct YouTube FLV stream which is inconsistent with YouTube terms. This feature was included only for education purposes. To avoid undesired usage I didn’t publish the CGI version of the script.
4. Usage
# Returns whole FLV stream (audio: MP3, video: Sorensen H.263)
perl extract.pl -u http://www.youtube.com/watch?v=s0NlNOI5LG0 -f 5 -g AV 2> /dev/null | mplayer -
# Returns "audio only" FLV stream (audio: MP3)
perl extract.pl -u http://www.youtube.com/watch?v=8UVNT4wvIGY -f 5 -g Audio 2> /dev/null | mplayer -
# Returns raw mp3 stream
perl extract.pl -u http://www.youtube.com/watch?v=8UVNT4wvIGY -f 5 -g Audio -r 2> /dev/null | mpg123 -
# Returns "video only" FLV stream (video: Sorensen H.263)
perl extract.pl -u http://mkaczanowski.com/demos/test.flv -g Video 2> /dev/null | mplayer -
# Returns "audio only" FLV stream starting from 01:20 (audio: AAC)
perl extract.pl -u http://www.youtube.com/watch?v=8UVNT4wvIGY -g Audio -t 01m20s 2> /dev/null | mplayer -
# See debug messages. "1> /dev/null" redirects sdtout to /dev/null, so only stderr is visible
perl extract.pl -u http://mkaczanowski.com/demos/test.flv -g Audio 1> /dev/null
# See debug messages. "1> /dev/null" redirects sdtout to /dev/null, so only stderr is visible
perl extract.pl -u http://www.youtube.com/watch?v=8UVNT4wvIGY -f 34 -g Audio -t 01m20s 1> /dev/null
# Returns raw mp3 stream (local url)
perl extract.pl -u http://mkaczanowski.com/demos/test.flv -g Audio -r 2> /dev/null | mpg123 -
FLV format specification
Before we start programming let’s cover some theory. After reading specification (links below) you will have to know answers to the following questions:
- What is the FLV header?
- What is „tag” and what are the its types?
- How are the tags placed? What is the order?
- What are the flags?
- What are the specific data types (such as uint8, uint32_be etc)?
- What is the big endian notation?
References:
Get YouTube video URI using wireshark
Extract.pl – main file
Extract.pl is responsible for handling command line arguments and for creating an appropriate object ( depending on given URL ).
#!/usr/bin/perl
use warnings;
use strict;
use Getopt::Long;
use YouTube;
use Print;
use Local;
use Switch;
# Local Variables
my $params = {};
my $obj; # holds 'Youtube' or 'Local' object
# Collect parameters
GetOptions ('help|?' => sub{ showHelp(); },
'url=s' => \$params->{_url},
'time:s'=>sub{ $params->{_startFromTime} = timeToMsecs($_[1]); },
'fmt:i'=>\$params->{_selectedFmt},
'raw' => \$params->{_raw},
'get=s' => \$params->{_get} # Audio, Video, AV
);
# Mandatory params
if(!defined($params->{_url}) || !defined($params->{_get})){ showHelp(); }
# Check if url contains youtube.com address
if($params->{_url} =~ m/(youtube.com)/){
$obj = new YouTube();
$obj->{_url} = $params->{_url};
$obj->{_startFromTime} = $params->{_startFromTime}; # Time in milliseconds
$obj->{_selectedFmt} = $params->{_selectedFmt}; # YT fmt -> http://en.wikipedia.org/wiki/YouTube
}else{
$obj = new Local();
$obj->{_url} = $params->{_url};
}
switch($params->{_get}){
case "Audio" {
if(!defined($params->{_raw})){
$obj->getAudio();
}else{
$obj->getRawAudio();
}
}
case "Video" {
if(!defined($params->{_raw})){
$obj->getVideo();
}else{
$obj->getRawVideo();
}
}
case "AV" {
$obj->getAV();
}
default {
Print::err("Get: Audio, Video, AV options are avalible");
}
}
sub showHelp{
print "Options: \n";
print "\t --url http://www.youtube.com/watch?v=VIDEO_ID\t #YouTube video url address\n";
print "\t --time ##m##s\t\t\t\t\t #Start playing from given time\n";
print "\t --fmt FMT_VAL\t\t\t\t\t #Choose fmt manually\n";
print "\t --raw\t\t\t\t\t\t #Raw output\n";
print "\t --help\t\t\t\t\t\t #Show this menu\n";
exit;
}
sub timeToMsecs{
my $str = shift;
if(!($str =~ m/([0-9]+)m([0-5][0-9])s/)){ return 0; }
$str =~ s//([0-9]+)m([0-5][0-9])s/;
return($1*60+$2)*1000;
}
YouTube.pm
YouTube.pm downloads site, parse it and finds the video URI which is passed to Download.pm.
#!/usr/bin/perl
package YouTube;
use WWW::Curl::Easy;
use URI::Escape;
use Mojo::DOM;
use Download;
use FLV;
sub new{
my $class = shift;
my $self = {
_url => undef, # contains stream url
_startFromTime => shift || 0,
_fmt => {}, # Fmt list
_html => [], # HTML code of YouTube site
_cookies => [], # Part of HTTP Header
_selectedFmt => undef,
_httpReferer => undef, # Part of HTTP Header
_download => undef # contains 'download' object
};
bless $self, $class;
return $self;
}
sub init {
my $self = shift;
if(!defined($self->{_url})){
Print::err("URL is missing");
}
$self->getHtml();
$self->parseHtml();
$self->{_url} = $self->{_fmt}{$self->{_selectedFmt}}{url};
}
# Prints FLV stream whith only audio tags
sub getAudio{
my $self = shift;
$self->run(0,1,0);
}
# Prints codec part from audio tags
sub getRawAudio{
my $self = shift;
$self->run(0,1,1);
}
# Prints FLV stream with only video tags
sub getVideo{
my $self = shift;
$self->run(1,0,0);
}
# Prints codec part from video tags
sub getRawVideo{
my $self = shift;
$self->run(1,0,1);
}
# Prints both: audio and video as FLV stream
sub getAV{
my $self = shift;
$self->run(1,1,0);
}
# Start downloading
sub run{
my $self = shift;
$self->init();
$self->{_download} = new Download($self);
$self->{_download}->start();
$self->summary();
my $flv = new FLV(shift, shift, shift);
$flv->getTags();
}
# Download YouTube website
sub getHtml{
my $self = shift;
my $curl = WWW::Curl::Easy->new;
my $response;
my $headers;
$curl->setopt(CURLOPT_HEADER,0);
$curl->setopt(CURLOPT_COOKIESESSION, 1);
$curl->setopt(CURLOPT_USERAGENT, Download::getUserAgent());
$curl->setopt(CURLOPT_URL, $self->{_url});
$curl->setopt(CURLOPT_WRITEDATA,\$response);
$curl->setopt(CURLOPT_WRITEHEADER, \$headers);
$curl->perform();
if ($curl->getinfo(CURLINFO_HTTP_CODE) == 200) {
my @headers = split /\n/, $headers;
@{$self->{_html}} = split /\n/, $response;
@{$self->{_cookies}} = grep {/Set\-Cookie/} @headers; # extract cookies
# Parse cookies
for(my $i = 0; $i {_cookies}}; $i++){
$self->{_cookies}[$i] =~ s/(\\n|\s+)//g;
my @temp = split(/\;/, $self->{_cookies}[$i], 2);
$self->{_cookies}[$i] = substr($temp[0],11);
}
} else {
Print::err("Download Error");
}
}
sub parseHtml{
my $self = shift;
my @explode = grep {/flashvars/} @{$self->{_html}}; # extract embed tag which is contained inside of flashvars parameter
if($#explode == -1){ Print::err("flashvars NOT FOUND"); }
$explode[0] =~ s/\\u([[:xdigit:]]{1,4})/chr(eval("0x$1"))/egis; # convert unicode to asci characters
$explode[0] =~ s/(\;|\n|\\")//g; # remove ";", "\n", " ", \" (|\s+|) from string
$explode[0] =~ s/\\\//\//g; # change \/ to /
my $dom = Mojo::DOM->new($explode[0]);
$self->{_httpReferer} = $dom->embed->{src};
my %embed_params;
foreach(split(/\&/,$dom->embed->{flashvars})){ # get List of embed params
(my $param, my $val) = split(/\=/, $_);
$embed_params{$param} = $val;
}
foreach(split(/\,/,uri_unescape($embed_params{fmt_list}))){ # get List of avalible FMT
(my $id, my $resolution) = split(/\//, $_, 3);
$self->{_fmt}{$id} = {'resolution' => $resolution};
}
foreach(split(/\,/,uri_unescape($embed_params{url_encoded_fmt_stream_map}))){ # get Url List
my @spli = split(/itag\=/,$_);
$self->{_fmt}{$spli[1]}{'url'} = substr uri_unescape($_), 4;
}
# Check if _selectedFmt is defined or sort and select lowest fmt
if(!defined($self->{'_selectedFmt'}) || (defined($self->{'_selectedFmt'}) && !defined($self->{'_fmt'}{$self->{'_selectedFmt'}}))){
$self->{'_selectedFmt'} = (sort {$a $b} keys %{$self->{'_fmt'}})[0];
}
}
# Prepare HTTP header
sub getHeader{
my $self = shift;
my $url = shift || exit;
my $return = "";
$url =~ m|(\w+)://([^/:]+)(:\d+)?/(.*)|;
$return .= "Host: $2\r\n";
$return .= "Connection: keep-alive\r\n";
$return .= "Referer: ".$self->{_httpReferer}."\r\n";
$return .= "Accept: */*\r\n";
$return .= "Accept-Encoding: gzip,deflate,sdch\r\n";
$return .= "Accept-Language: en-US,pl;q=0.8,en-US;q=0.6,en;q=0.4\r\n";
$return .= "Accept-Charset: Accept-Charset: UTF-8,utf-8;q=0.7,*;q=0.3\r\n";
$return .= "Cookie: ".$self->{_cookies}[0].";".$self->{_cookies}[1].";".$self->{_cookies}[2].";".$self->{_cookies}[3]."&fv=10.1.103\r\n";
return $return;
}
sub summary{
my $self = shift;
Print::info("FMT: ".$self->{_selectedFmt});
Print::info("Time: ".$self->{_startFromTime}."ms");
Print::info("Resolution: ".$self->{_fmt}{$self->{_selectedFmt}}{resolution});
Print::info("Http Referer: ".$self->{_httpReferer});
Print::info("URI: ".$self->{_download}->{_uri});
Print::info("Host: ".$self->{_download}->{_host});
}
Local.pm
Local.pm handles "direct" URL (different from YouTube address) and pass it to Download.pm.
#!/usr/bin/perl
package Local;
use Download;
use FLV;
sub new{
my $class = shift;
my $self = {
_url => undef,
_download => undef # Holds 'download' object
};
bless $self, $class;
return $self;
}
# Prints FLV stream whith only audio tags
sub getAudio{
my $self = shift;
$self->run(0,1,0);
}
# Prints codec part from audio tags
sub getRawAudio{
my $self = shift;
$self->run(0,1,1);
}
# Prints FLV stream with only video tags
sub getVideo{
my $self = shift;
$self->run(1,0,0);
}
# Prints codec part from video tags
sub getRawVideo{
my $self = shift;
$self->run(1,0,1);
}
# Prints both: audio and video as FLV stream
sub getAV{
my $self = shift;
$self->run(1,1,0);
}
# Starts downloading
sub run{
my $self = shift;
$self->{_download} = new Download($self);
$self->{_download}->start();
$self->summary(); # Print summary of connection such as Host, URI
my $flv = new FLV(shift, shift, shift);
$flv->getTags(); # Print out tags
}
# Prepares HTTP header
sub getHeader{
my $self = shift;
my $url = shift || exit;
my $return = "";
$url =~ m|(\w+)://([^/:]+)(:\d+)?/(.*)|; # split url
$return .= "Host: $2\r\n";
$return .= "Connection: keep-alive\r\n";
$return .= "Accept: */*\r\n";
return $return;
}
# Prints URI and HOST to stderr
sub summary{
my $self = shift;
Print::info("URI: ".$self->{_download}->{_uri});
Print::info("Host: ".$self->{_download}->{_host});
}
Download.pm
In general Download.pm handles the Sock.pm operations (such as write, read). Also perpares headers and requests.
#!/usr/bin/perl
package Download;
use IO::Lambda qw(:all);
use IO::Socket;
use HTTP::Request;
use HTTP::Response;
use URI::Escape;
use Sock;
sub new{
my $class = shift;
my $self = {
_obj => shift || die("YT object is missing"),
_contentLength => undef,
_uri => undef,
_host => undef
};
bless $self, $class;
return $self;
}
# Prepares request and starts downloading!
sub start{
my $self = shift;
my $request = HTTP::Request-> new( GET => $self->{_obj}->{_url});
my $q = $self->talkRedirect($request);
$q->wait;
}
sub talkRedirect{
my $self = shift;
my $req = shift;
lambda{
context $self->talk($req);
tail{
my @tempHeader;
my $newUrl;
while (defined($_ = Sock->instance()->get()->getline)){ # read line from socket
last if($_ =~ /^[\r\n]+$/);
if($_ =~ /Location/){ (my $first, $newUrl) = split /\ /,$_; }
if($_ =~ /Content\-Length/){ $_ =~ s/.Length\: /\r/; $self->{_contentLength} = $_;} # save ContentLength
$tempHeader[$#tempHeader+1] = $_;
}
my $res = HTTP::Response->parse(@tempHeader);
return $res unless $res->code == 302; # if response code == 302 prepare and send another request
$req->uri($newUrl);
context $self->talk($req);
again;
}
}
}
sub talk{
my $self = shift;
my $req = shift;
my $url = $req->uri;
my($uri, $host) = $self->getUriHost($url);
my @spl = split(/\&quality/, $uri, 0);
$uri = $spl[0];
# GVS adds redirect_counter parameter automatically
if(!($uri =~ m/redirect_counter/) && defined($self->{_obj}->{_startFromTime}) && $self->{_obj}->{_startFromTime} > 0){
$uri .= "&begin=".$self->{_obj}->{_startFromTime};
}
$self->{_uri} = $uri;
$self->{_host} = $host;
my $sock = Sock->instance($host)->get();
lambda {
context $sock;
writable {
$sock->write("GET ".$uri." HTTP/1.1\r\n");
$sock->write("User-Agent: ".$self->getUserAgent()."\r\n");
$sock->write($self->{_obj}->getHeader($url)."\r\n");
}
}
}
# Gets Uri and Host address
sub getUriHost{
my $self = shift;
my $url = shift || exit;
my $uri = URI->new($url);
my $path = $uri->path;
if($uri->query){
$path .= "?".$uri->query;
}
return ($path,$uri->host);
}
# Gets UserAgent string. For IE YouTube generates diffrent HTML code, so IE is not included in the list
sub getUserAgent{
my @agents = [];
$agents[0] = "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10";
$agents[1] = "Mozilla/5.0 (X11; Linux i686; rv:2.0) Gecko/20110321 Firefox/4.0";
$agents[2] = "Opera/9.80 (X11; Linux i686; U; en-US) Presto/2.7.62 Version/11.01";
$agents[3] = "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.5) Gecko/2008120122 Firefox/3.0.5";
$agents[4] = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_6; en-us) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/3.2.1 Safari/525.27.1";
return $agents[rand($#agents)];
}
Sock.pm
Sock.pm holds IO::Socket::INET singleton object instance.
#!/usr/bin/perl
package Sock;
use IO::Socket;
use Time::HiRes;
# Create singleton instance
my $singleton;
# Returns Sock instance
sub instance {
my $class = shift;
unless($singleton){
my $self = {
_socket => IO::Socket::INET-> new( PeerAddr => shift || die("Host is missing"), PeerPort => 80), # Create new IO::Socket::Inet object
_readBytes => 0
};
bless ($self, $class);
$singleton = $self;
}
return $singleton;
}
# Reads bytes from IO::Socket::INET if can't wait 10 secs
sub readBytes{
my $self = shift;
my $n = shift;
my $wait = shift;
my $counter = shift || 10;
my $buffer;
my $bytes;
if($wait){
if($counter > 0){
Print::warn("Waiting ".$counter);
Time::HiRes::sleep(.5);
$counter--;
}else{
Print::err("Problem during download");
}
}
$bytes = read $self->{_socket}, $buffer, $n or $self->readBytes($n,1,$counter);
$self->{_readBytes} += $bytes;
return $buffer;
}
# Gets socket instance
sub get{
my $self = shift;
return $self->{_socket};
}
# How many bytes have been read?
sub bytesCount{
my $self = shift;
return $self->{_readBytes};
}
FLV.pm
FLV.pm is the core of the script. It’s resposible for stream processing (reading/creating tags, reading/sending headers etc.)
#!/usr/bin/perl
package FLV;
use Tag;
use Sock;
sub new{
my $class = shift;
my $self = {
_hasVideo => shift, # Enable/Disable video output
_hasAudio => shift, # Enable/Disable audio output
_isRawOutput => shift
};
if($self->{_hasVideo} == 0 && $self->{_hasAudio} == 0){
Print::err("There's nothing to be done");
}
bless $self, $class;
return $self;
}
sub getNewFlvHeader{
my $self = shift;
my $flags = (($self->{_hasAudio}) ? 0x04 : 0) | (($self->{_hasVideo}) ? 0x01 : 0);
return pack 'A3CCN', 'FLV', 1, $flags, 9;
}
sub getTags{
my $self = shift;
my $sock = Sock->instance(); # Get socket instance
my ($signature, $version, $flags, $offset) = unpack 'A3CCN', $sock->readBytes(9); # Unpack header
my $lastTagSize = 0;
if ($signature ne 'FLV'){ Print::err("FLV signature: $signature"); }
if ($version != 1){ Print::err('Internal error: I only understand FLV version 1'); }
if (0 != ($flags & 0xf0)){ Print::err('Reserved header flags are non-zero at byte'); }
if ($offset < 9){ Print::err('Illegal value for body offset at byte'); } if(!$self->{_isRawOutput}){
Print::out($self->getNewFlvHeader()); # Prints new FLV Header
Print::out(pack 'N', 0); # First previous tag is alwas equal to 0
}
while(1) {
$sock->readBytes(4); # Read last tag size
if(eof($sock->get())){ last; }
my $tag = new Tag($self); # Create new Tag
if(!$self->{_isRawOutput}){
if(($self->{_hasAudio} && !$tag->isVideo()) || ($self->{_hasVideo} && !$tag->isAudio() || ($self->{_hasVideo} && $self->{_hasAudio}))){
$tag->printTag();
$lastTagSize = $tag->getSize();
Print::out(pack 'N', $lastTagSize); # Each tag is followed by "Last Tag Size" Tag
}
}else{
if($self->{_hasAudio} && $tag->isAudio() || $self->{_hasVideo} && $tag->isVideo()){
$tag->{_obj}->printCodec(); # Raw output
}
}
}
}
# Converts timestamp to seconds
sub timestampToSeconds{
my $self = shift;
my $msec = shift;
return $self->round($msec/1000,2);
}
# Rounds up the number
sub round {
my $self = shift;
my $number = shift || 0;
my $dec = 10 ** (shift || 0);
return int( $dec * $number + .5 * ($number 0)) / $dec;
}
Tag.pm
Tag.pm parse raw Tag header and then creates appropriate tag object (see AudioTag.pm, VideoTag.pm)
#!/usr/bin/perl
package Tag;
use AudioTag;
use VideoTag;
use Sock;
sub new{
my $class = shift;
my $self = {
_flv => shift || die("FLV object is missing"), # holds FLV object
_rawHeader => undef, # contains raw Tag header
_type => undef, # Tag type, 8:Audio, 9:Video, 18: Meta
_dataSize => undef, # Tag size
_timestamp => undef,
_obj => undef, # Tag object
_script => undef
};
bless $self, $class;
$self->{_rawHeader} = Sock->instance()->readBytes(11); # Read raw tag header from stream
($self->{_type}, $self->{_dataSize}, $self->{_timestamp}) = $self->getTagInfo($self->{_rawHeader});
$self->isValid(); # Check if tag is valid
if($self->{_type} == 8){
$self->{_obj} = new AudioTag($self->{_dataSize}); # Create AudioTag obj
}elsif($self->{_type} == 9){
$self->{_obj} = new VideoTag($self->{_dataSize}); # Create VideoTag obj
}elsif($self->{_type} == 12 || $self->{_type} == 18){
$self->{_script} = Sock->instance()->readBytes($self->{_dataSize}); # Just save raw data, we don't need to parse script or meta tag
}
return $self;
}
# Checks if current tag is an AudioTag
sub isAudio{
return shift->{_type} == 8;
}
# Checks if current tag is an VideoTag
sub isVideo{
return shift->{_type} == 9;
}
# Prints out the tag
sub printTag{
my $self = shift;
Print::out($self->{_rawHeader});
if(defined($self->{_obj})){
$self->{_obj}->printTag();
}else{
Print::tag("Meta Tag at ".Sock->instance()->bytesCount());
Print::out($self->{_script});
}
}
# Returns size of whole tag (header+content)
sub getSize{
return shift->{_dataSize} + 11;
}
# Reads raw tag and returns TimeStamp, DataSize, Type
sub getTagInfo{
my $self = shift;
my $bytes = shift || exit;
my ($type, @datasize, @timestamp);
($type, $dataSize[0], $dataSize[1], $dataSize[2], $timestamp[1], $timestamp[2], $timestamp[3], $timestamp[0]) = unpack 'CCCCCCCC', $bytes;
my $dataSize = ($dataSize[0] * 256 + $dataSize[1]) * 256 + $dataSize[2];
my $timestamp = (($timestamp[0] * 256 + $timestamp[1]) * 256 + $timestamp[2]) * 256 +$timestamp[3];
return $type, $dataSize, $timestamp;
}
# Validates tag
sub isValid{
my $self = shift;
if ($self->{_timestamp} > 4_000_000_000 || $self->{_timestamp} < 0){ Print::warn("Strange timestamp: $self->{_timestamp}");
}
if($self->{_dataSize} < 4){ Print::err("Tag size is too small ($self->{_dataSize})");
}
}
AudioTag.pm
AudioTag.pm holds information about audio (format, codec etc.)
#!/usr/bin/perl
package AudioTag;
use Sock;
my $formats = {
0 => "Uncompressed",
1 => "DPCM",
2 => "MP3",
10 => "AAC"
};
sub new{
my $class = shift;
my $self = {
_tagSize => shift || die("TagSize is missing"),
_flags => shift,
_rawFlags => shift,
_format => undef,
_rate => undef,
_size => undef,
_type => undef,
_codecData => undef
};
bless $self, $class;
my $sock = Sock->instance();
$self->{_rawFlags} = $sock->readBytes(1); # Read flags
$self->{_codecData} = $sock->readBytes($self->{_tagSize}-1);
$self->{_flags} = unpack 'C', $self->{_rawFlags}; # Unpack. "C" - An unsigned char (octet) value.
$self->{_format} = (($self->{_flags} >> 4) & 0x0f);
$self->{_rate} = (($self->{_flags} >> 2) & 0x0C);
$self->{_size} = (($self->{_flags} >> 1) & 0x02);
$self->{_type} = (($self->{_flags} >> 0) & 0x01);
return $self;
}
sub printCodec{
Print::out(shift->{_codecData});
}
sub printTag{
my $self = shift;
Print::tag("Audio Tag at ".Sock->instance()->bytesCount()." format: ".$formats->{$self->{_format}});
Print::out($self->{_rawFlags});
Print::out($self->{_codecData});
}
1;
VideoTag.pm
VideoTag.pm holds information about video (format, codec etc.)
#!/usr/bin/perl
package VideoTag;
use Sock;
my $codecs = {
2 => "Sorensen H.263",
3 => "Screen video",
4 => "On2 VP6",
5 => "On2 VP6 Alpha",
6 => "ScreenVideo 2",
7 => "MPEG-4 AVC (H.264)"
};
sub new{
my $class = shift;
my $self = {
_tagSize => shift || die("TagSize is missing"),
_flags => shift,
_rawFlags => shift,
_codecData => undef,
_codecId => undef,
_frameType => undef
};
bless $self, $class;
my $sock = Sock->instance();
$self->{_rawFlags} = $sock->readBytes(1);
$self->{_codecData} = $sock->readBytes($self->{_tagSize}-1);
$self->{_flags} = unpack 'C', $self->{_rawFlags};
$self->{_codecId} = (($self->{_flags} >> 0) & 0x0f);
$self->{_frameType} = (($self->{_flags} >> 4) & 0xf0);
return $self;
}
sub printCodec{
my $self = shift;
Print::out($self->{_codecData});
}
sub printTag{
my $self = shift;
Print::tag("Video Tag at ".Sock->instance()->bytesCount()." codec: ".$codecs->{$self->{_codecId}});
Print::out($self->{_rawFlags});
Print::out($self->{_codecData});
}
Print.pm
Print.pm is responsible for printing out debug information/raw data.
#!/usr/bin/perl
package Print;
use Term::ANSIColor qw(:constants);
sub err{
print STDERR BOLD, RED, "[Error]", RESET, " @_\n"; exit;
}
sub warn{
print STDERR BOLD, YELLOW, "[Warning]", RESET, " @_\n";
}
sub tag{
print STDERR BOLD, GREEN, "\r[Tag]", RESET, " @_";
}
sub info{
print STDERR BOLD, GREEN, "[Info]", RESET, " @_\n";
}
sub out{
print STDOUT @_;
}
1;
Demo
Sources: