| 1 |
#!/usr/bin/perl |
| 2 |
|
| 3 |
# EPG (Electronic Program Guide Decoder) |
| 4 |
# Commercial Name: nexTView |
| 5 |
|
| 6 |
use Video::Capture::V4l; |
| 7 |
use Video::Capture::VBI; |
| 8 |
|
| 9 |
package Video::Capture::VBI::EPG; |
| 10 |
|
| 11 |
use Video::Capture::VBI qw(:DEFAULT %VPS_CNI %VT_NI); |
| 12 |
use Storable; |
| 13 |
|
| 14 |
use base 'Video::Capture::VBI::VT'; |
| 15 |
|
| 16 |
sub new { |
| 17 |
my $class = shift; |
| 18 |
my $self = $class->SUPER::new(@_); |
| 19 |
$self; |
| 20 |
} |
| 21 |
|
| 22 |
my $lastcni = -1; |
| 23 |
sub feed { |
| 24 |
my $self = shift; |
| 25 |
my @r; |
| 26 |
for ($self->SUPER::feed(@_)) { |
| 27 |
if ($_->[0] == VBI_VPS) { |
| 28 |
if ($_->[3] != $lastcni) { |
| 29 |
$lastcni = $_->[3]; |
| 30 |
printf "CNI %04x (%s)\n", $_->[3], $VPS_CNI{$_->[3] & 0xfff}; |
| 31 |
} |
| 32 |
} else { |
| 33 |
push @r, $_; |
| 34 |
} |
| 35 |
} |
| 36 |
@r; |
| 37 |
} |
| 38 |
|
| 39 |
sub mydump { |
| 40 |
local $_ = shift; |
| 41 |
if (ref $_ eq "HASH") { |
| 42 |
print "{ "; |
| 43 |
while(my($k,$v)=each %$_) { |
| 44 |
print ", $k => "; |
| 45 |
mydump($v); |
| 46 |
} |
| 47 |
print " }"; |
| 48 |
} elsif (ref $_ eq "ARRAY") { |
| 49 |
print "[ "; |
| 50 |
for (@$_) { |
| 51 |
print ", "; |
| 52 |
mydump($_); |
| 53 |
} |
| 54 |
print " ]"; |
| 55 |
} else { |
| 56 |
y/\x20-\x7e//cd; |
| 57 |
print $_; |
| 58 |
} |
| 59 |
} |
| 60 |
|
| 61 |
sub set_epg { |
| 62 |
my $self = shift; |
| 63 |
my $name = shift; |
| 64 |
my $new_db = main::gen_db_name ($name); |
| 65 |
print "SET_EPG $new_db <= ", $self->{db_name}, "\n"; |
| 66 |
if ($self->{db_name} and $new_db eq $self->{db_name}) { |
| 67 |
print "saving ",$new_db,"..."; |
| 68 |
Storable::nstore($self->db, "$new_db~"); |
| 69 |
rename "$new_db~", $new_db; |
| 70 |
print "ok\n"; |
| 71 |
} else { |
| 72 |
$self->{db_name} = $new_db; |
| 73 |
print "trying to load $new_db..."; |
| 74 |
$self->db(eval { $self->db(Storable::retrieve($new_db)) }); |
| 75 |
print "ok\n"; |
| 76 |
} |
| 77 |
} |
| 78 |
|
| 79 |
# just display the page |
| 80 |
sub enter_page { |
| 81 |
my($self,$page)=@_; |
| 82 |
|
| 83 |
$pages++; |
| 84 |
|
| 85 |
return unless $page->{page} == 0x1df; |
| 86 |
|
| 87 |
my $sub = $page->{ctrl}; |
| 88 |
my $stream = VTX_S3($sub); |
| 89 |
my $height = VTX_S4($sub)<<3 | VTX_S2($sub); |
| 90 |
my $seq = VTX_S1($sub); |
| 91 |
|
| 92 |
my $s = \%{$self->{stream}{$stream}}; |
| 93 |
|
| 94 |
delete $s->{raw} unless (($seq-1)&15) == $s->{seq}; |
| 95 |
$s->{seq}=$seq; |
| 96 |
push @{$s->{raw}}, @{$page->{packet}}[1..$height]; |
| 97 |
|
| 98 |
$packets[$stream]++; |
| 99 |
#print "stream $stream, height $height, seq $seq, ",scalar @{$s->{raw}},"<\n"; |
| 100 |
|
| 101 |
for(decode_stream $s->{raw}) { |
| 102 |
if (my ($dt, $bi) = decode_block $_, \@{$self->{bundle}}) { |
| 103 |
if ($dt == 1) { |
| 104 |
my $ai = $self->{db}{ai}; |
| 105 |
$self->set_epg($bi->{service_name}); |
| 106 |
if ($ai) { |
| 107 |
print "$ai->{epg_version}, $ai->{epg_version_swo}, $ai->{this_network_op}, $ai->{service_name}\n"; |
| 108 |
if ($bi->{epg_version} != $ai->{epg_version} |
| 109 |
|| $bi->{epg_version_swo} != $ai->{epg_version_swo} |
| 110 |
|| $bi->{this_network_op} != $ai->{this_network_op}) { |
| 111 |
print "CHANNEL SWITCH, WIPING DATABASE\n"; |
| 112 |
delete $self->{db}; |
| 113 |
} |
| 114 |
} |
| 115 |
$self->{db}{ai} = $bi; |
| 116 |
} elsif ($dt == 2) { |
| 117 |
$self->{db}{pi}{$bi->{netwop_no}}{$bi->{block_no}} = $bi; |
| 118 |
} elsif ($dt == 3) { |
| 119 |
$self->{db}{ni}{$bi->{block_no}} = $bi; |
| 120 |
} elsif ($dt == 4) { |
| 121 |
$self->{db}{oi}{$bi->{block_no}} = $bi; |
| 122 |
} elsif ($dt == 5) { |
| 123 |
$self->{db}{mi}{$bi->{block_no}} = $bi; |
| 124 |
} else { |
| 125 |
print "UNKNOWN BLOCK TYPE $dt found\n"; |
| 126 |
} |
| 127 |
} |
| 128 |
} |
| 129 |
$|=1; |
| 130 |
printf "%7d %6d %6d %5d %5d %5d %5d\r", $pages, $packets[0], $packets[1], |
| 131 |
1*%{$self->{db}{pi}}, |
| 132 |
1*%{$self->{db}{ni}}, |
| 133 |
1*%{$self->{db}{oi}}, |
| 134 |
1*%{$self->{db}{mi}}; |
| 135 |
|
| 136 |
#printf "received epg page %04x\n", $sub; |
| 137 |
} |
| 138 |
|
| 139 |
sub enter_packet { |
| 140 |
my $self = shift; |
| 141 |
my $p = shift; |
| 142 |
return unless $p->[3] <= 1 && $p->[2] == 30; |
| 143 |
#printf "packet(@$p %04x)", $p->[6]; |
| 144 |
print "\n"; |
| 145 |
} |
| 146 |
|
| 147 |
sub db { |
| 148 |
my $self = shift; |
| 149 |
$self->{db} = shift if @_; |
| 150 |
$self->{db}; |
| 151 |
} |
| 152 |
|
| 153 |
package main; |
| 154 |
|
| 155 |
$vbi = new Video::Capture::V4l::VBI or die; |
| 156 |
|
| 157 |
# the next line is optional (it enables buffering) |
| 158 |
$vbi->backlog(150); # max. 5 seconds backlog (~4.6Mb) |
| 159 |
|
| 160 |
$inp_fd = fileno STDIN; |
| 161 |
$vbi_fd = $vbi->fileno; |
| 162 |
|
| 163 |
$vt = new Video::Capture::VBI::EPG; |
| 164 |
|
| 165 |
sub gen_db_name { |
| 166 |
shift() . ".epg"; |
| 167 |
} |
| 168 |
|
| 169 |
print "Capturing VBI block. Make sure you have tuned in to a channel with EPG!\n"; |
| 170 |
print "Aquisition may take up to twenty minutes (and more)!\n"; |
| 171 |
|
| 172 |
for(;;) { |
| 173 |
my $r=""; |
| 174 |
vec($r,$inp_fd,1)=1; |
| 175 |
vec($r,$vbi_fd,1)=1; |
| 176 |
select $r,undef,undef,0.04; |
| 177 |
$vt->feed(decode_field $vbi->field, VBI_VT|VBI_VPS) while $vbi->queued; |
| 178 |
if (vec($r,$inp_fd,1)) { |
| 179 |
$_ = <STDIN>; |
| 180 |
if (/^q/i) { |
| 181 |
last; |
| 182 |
} |
| 183 |
} |
| 184 |
} |
| 185 |
|