package Linux::DVB::DVBT::Config ;
=head1 NAME
Linux::DVB::DVBT::Config - DVBT configuration functions
=head1 SYNOPSIS
use Linux::DVB::DVBT::Config ;
=head1 DESCRIPTION
Module provides a set of configuration routines used by the DVBT module. It is unlikely that you will need to access these functions directly, but
you can if you wish.
=cut
use strict ;
use Data::Dumper ;
our $VERSION = '2.08' ;
our $DEBUG = 0 ;
our $DEFAULT_CONFIG_PATH = '/etc/dvb:~/.tv' ;
use File::Path ;
use File::Spec ;
my %FILES = (
'ts' => { 'file' => "dvb-ts", 'required' => 1 },
'pr' => { 'file' => "dvb-pr", 'required' => 1 },
'aliases' => { 'file' => "dvb-aliases", 'required' => 0 },
) ;
my %NUMERALS = (
'one' => 1,
'two' => 2,
'three' => 3,
'four' => 4,
'five' => 5,
'six' => 6,
'seven' => 7,
'eight' => 8,
'nine' => 9,
) ;
our @SCAN_INFO_FIELDS = qw/pr ts lcn freqs/ ;
#============================================================================================
=head2 Functions
=over 4
=cut
#----------------------------------------------------------------------
=item B<find_tsid($frequency, $tuning_href)>
Given a frequency, find the matching TSID.
$tuning_href is the HASH returned by L<Linux::DVB::DVBT::get_tuning_info()|lib::Linux::DVB::DVBT/get_tuning_info()>.
=cut
sub find_tsid
{
my ($frequency, $tuning_href) = @_ ;
my $tsid ;
# 'ts' =>
# 4107 =>
# {
# tsid => 4107,
# frequency => 57800000,
# ...
# },
foreach my $this_tsid (keys %{$tuning_href->{'ts'}})
{
if ($frequency == $tuning_href->{'ts'}{$this_tsid}{'frequency'})
{
$tsid = $this_tsid ;
last ;
}
}
return $tsid ;
}
#----------------------------------------------------------------------
=item B<tsid_params($tsid, $tuning_href)>
Given a tsid, return the frontend params (or undef). The frontend params HASH
contain the information used to tune the frontend i.e. this is the transponder
(TSID) information. It corresponds to the matching 'ts' entry in the tuning info
HASH.
$tuning_href is the HASH returned by L<Linux::DVB::DVBT::get_tuning_info()|lib::Linux::DVB::DVBT/get_tuning_info()>.
=cut
sub tsid_params
{
my ($tsid, $tuning_href) = @_ ;
my $params_href ;
# 'ts' =>
# 4107 =>
# {
# tsid => 4107,
# frequency => 57800000,
# ...
# },
if ($tsid && exists($tuning_href->{'ts'}{$tsid}))
{
$params_href = $tuning_href->{'ts'}{$tsid} ;
}
return $params_href ;
}
#----------------------------------------------------------------------
=item B<chan_from_pid($tsid, $pid, $tuning_href)>
Given a tsid and pid, find the matching channel information and returns the
program HASH ref if found. This corresponds to the matching 'pr' entry in the tuning
info HASH.
$tuning_href is the HASH returned by L<Linux::DVB::DVBT::get_tuning_info()|lib::Linux::DVB::DVBT/get_tuning_info()>.
=cut
sub chan_from_pid
{
my ($tsid, $pid, $tuning_href) = @_ ;
my $pr_href ;
# skip PAT
return $pr_href unless $pid ;
# 'pr' =>
# BBC ONE =>
# {
# pnr => 4171,
# tsid => 4107,
# tuned_freq => 57800000,
# ...
# },
foreach my $chan (keys %{$tuning_href->{'pr'}})
{
# if ($tsid == $tuning_href->{'pr'}{$chan}{'tsid'})
if ($tsid eq $tuning_href->{'pr'}{$chan}{'tsid'})
{
foreach my $stream (qw/video audio teletext subtitle/)
{
if ($pid == $tuning_href->{'pr'}{$chan}{$stream})
{
$pr_href = $tuning_href->{'pr'}{$chan} ;
last ;
}
}
last if $pr_href ;
# check other audio
my @audio = audio_list( $tuning_href->{'pr'}{$chan} ) ;
foreach (@audio)
{
if ($pid == $_)
{
$pr_href = $tuning_href->{'pr'}{$chan} ;
last ;
}
}
}
last if $pr_href ;
}
return $pr_href ;
}
#----------------------------------------------------------------------
=item B<pid_info($pid, $tuning_href)>
Given a pid, find the matching channel & TSID information
Returns an array of HASH entries, each HASH containing the stream type (video, audio, subtitle, or
teletext), along with a copy of the associated program information (i.e. the matching 'pr' entry from the
tuning info HASH):
@pid_info = [
{
'pidtype' => video, audio, subtitle, teletext
pnr => 4171,
tsid => 4107,
tuned_freq => 57800000,
...
},
...
]
$tuning_href is the HASH returned by L<Linux::DVB::DVBT::get_tuning_info()|lib::Linux::DVB::DVBT/get_tuning_info()>.
=cut
sub pid_info
{
my ($pid, $tuning_href) = @_ ;
print "pid_info(pid=\"$pid\")\n" if $DEBUG ;
my @pid_info ;
# skip PAT
return @pid_info unless $pid ;
foreach my $chan (keys %{$tuning_href->{'pr'}})
{
my $tsid = $tuning_href->{'pr'}{$chan}{'tsid'} ;
# program
my @chan_pids ;
foreach my $stream (qw/video audio teletext subtitle/)
{
push @chan_pids, [$stream, $tuning_href->{'pr'}{$chan}{$stream}] ;
}
# extra audio
my @audio = audio_list( $tuning_href->{'pr'}{$chan} ) ;
foreach (@audio)
{
push @chan_pids, ['audio', $_] ;
}
# extra subtitle by rainbowcrypt
my @sub = sub_list( $tuning_href->{'pr'}{$chan} ) ;
foreach (@sub)
{
push @chan_pids, ['subtitle', $_] ;
}
# SI
foreach my $si (qw/pmt/)
{
push @chan_pids, [uc $si, $tuning_href->{'pr'}{$chan}{$si}] ;
}
# check pids
foreach my $aref (@chan_pids)
{
if ($pid == $aref->[1])
{
print " + pidtype=$aref->[0]\n" if $DEBUG ;
push @pid_info, {
%{$tuning_href->{'pr'}{$chan}},
'pidtype' => $aref->[0],
# keep ref to program HASH (used by downstream functions)
'demux_params' => $tuning_href->{'pr'}{$chan},
} ;
}
}
}
return @pid_info ;
}
#----------------------------------------------------------------------
=item B<find_channel($channel_name, $tuning_href)>
Given a channel name, do a "fuzzy" search and return an array containing params:
($frontend_params_href, $demux_params_href)
$demux_params_href HASH ref are of the form:
{
pnr => 4171,
tsid => 4107,
tuned_freq => 57800000,
...
},
(i.e. $tuning_href->{'pr'}{$channel_name})
$frontend_params_href HASH ref are of the form:
{
tsid => 4107,
frequency => 57800000,
...
},
(i.e. $tuning_href->{'ts'}{$tsid} where $tsid is TSID for the channel)
$tuning_href is the HASH returned by L<Linux::DVB::DVBT::get_tuning_info()|lib::Linux::DVB::DVBT/get_tuning_info()>.
=cut
sub find_channel
{
my ($channel_name, $tuning_href) = @_ ;
my ($frontend_params_href, $demux_params_href) ;
## Look for channel info
print STDERR "find $channel_name ...\n" if $DEBUG ;
$channel_name = _channel_alias($channel_name, $tuning_href->{'aliases'}) ;
my $found_channel_name = _channel_search($channel_name, $tuning_href->{'pr'}) ;
if ($found_channel_name)
{
$demux_params_href = $tuning_href->{'pr'}{$found_channel_name} ;
}
## If we've got the channel, look up it's frontend settings
if ($demux_params_href)
{
my $tsid = $demux_params_href->{'tsid'} ;
$frontend_params_href = {
%{$tuning_href->{'ts'}{$tsid}},
'tsid' => $tsid,
} ;
}
return ($frontend_params_href, $demux_params_href) ;
}
#----------------------------------------------------------------------
# Do "fuzzy" search for channel name
#
sub _channel_search
{
my ($channel_name, $search_href) = @_ ;
my $found_channel_name ;
# start by just seeing if it's the correct name...
if (exists($search_href->{$channel_name}))
{
return $channel_name ;
}
else
{
## Otherwise, try finding variations on the channel name
my %search ;
$channel_name = lc $channel_name ;
# lower-case, no spaces
my $srch = $channel_name ;
$srch =~ s/\s+//g ;
$search{$srch}=1 ;
# lower-case, replaced words with numbers, no spaces
$srch = $channel_name ;
foreach my $num (keys %NUMERALS)
{
$srch =~ s/\b($num)\b/$NUMERALS{$num}/ge ;
}
$srch =~ s/\s+//g ;
$search{$srch}=1 ;
# lower-case, replaced numbers with words, no spaces
$srch = $channel_name ;
foreach my $num (keys %NUMERALS)
{
print STDERR " -- $srch - replace $NUMERALS{$num} with $num..\n" if $DEBUG>3 ;
$srch =~ s/($NUMERALS{$num})\b/$num/ge ;
print STDERR " -- -- $srch\n" if $DEBUG>3 ;
}
$srch =~ s/\s+//g ;
$search{$srch}=1 ;
print STDERR " + Searching tuning info [", keys %search, "]...\n" if $DEBUG>2 ;
foreach my $chan (keys %$search_href)
{
my $srch_chan = lc $chan ;
$srch_chan =~ s/\s+//g ;
foreach my $search (keys %search)
{
print STDERR " + + checking $search against $srch_chan \n" if $DEBUG>2 ;
if ($srch_chan eq $search)
{
$found_channel_name = $chan ;
print STDERR " + found $channel_name\n" if $DEBUG ;
last ;
}
}
last if $found_channel_name ;
}
}
return $found_channel_name ;
}
#----------------------------------------------------------------------
# Lookup channel name alias (if it exists)
#
sub _channel_alias
{
my ($channel_name, $alias_href) = @_ ;
if ($alias_href && scalar(keys %$alias_href))
{
print STDERR "Searching channel aliases for \"$channel_name\" ... \n" if $DEBUG>3 ;
my $alias_key = _channel_search($channel_name, $alias_href) ;
if ($alias_key)
{
my $alias = $alias_href->{$alias_key} ;
print STDERR "... using alias \"$alias\" for \"$channel_name\"\n" if $DEBUG>3 ;
$channel_name = $alias ;
}
}
return $channel_name ;
}
#----------------------------------------------------------------------
=item B<audio_pids($demux_params_href, $language_spec, $pids_aref)>
Process the demux parameters and a language specifier to return the list of audio
streams required.
demux_params are of the form:
{
pnr => 4171,
tsid => 4107,
tuned_freq => 57800000,
...
},
(i.e. $tuning_href->{'pr'}{$channel_name})
Language specifier string is in the format:
=over 4
=item a)
Empty string : just return the default audio stream pid
=item b)
Comma/space seperated list of one or more language names : returns the audio stream pids for all that match (does not necessarily include default stream)
=back
If the list in (b) contains a '+' character (normally at the start) then the default audio stream is automatically included in teh list, and the
extra streams are added to it.
For example, if a channel has the following audio details: eng:100 eng:101 fra:102 deu:103
Then the following specifications result in the lists as shown:
=over 4
=item *
"" => (100)
=item *
"eng deu" => (100, 103)
=item *
"+eng fra" => (100, 101, 102)
=back
Note that the language names are not case sensitive
=cut
sub audio_pids
{
my ($demux_params_href, $language_spec, $pids_aref) = @_ ;
my $error = 0 ;
print "audio_pids(lang=\"$language_spec\")\n" if $DEBUG ;
my $audio_pid = $demux_params_href->{'audio'} ;
## simplest case is no language spec
$language_spec ||= "" ;
if (!$language_spec)
{
print " + simplest case - add default audio $audio_pid\n" if $DEBUG ;
push @$pids_aref, $audio_pid ;
return 0 ;
}
# split details
my @audio_details ;
my $details = $demux_params_href->{'audio_details'} ;
print "audio_details=\"$details\")\n" if $DEBUG ;
while ($details =~ m/(\S+):(\d+)/g)
{
my ($lang, $pid) = ($1, $2) ;
push @audio_details, {'lang'=>lc $lang, 'pid'=>$pid} ;
print " + lang=$audio_details[-1]{lang} pid=$audio_details[-1]{pid}\n" if $DEBUG >= 10 ;
}
# drop default audio
shift @audio_details ;
# process language spec
if ($language_spec =~ s/\+//g)
{
# ensure default is in the list
push @$pids_aref, $audio_pid ;
print " - lang spec contains '+', added default audio\n" if $DEBUG >= 10 ;
}
print "process lang spec\n" if $DEBUG >= 10 ;
# work through the language spec
my $pid ;
my $lang ;
my @lang = split /[\s,]+/, $language_spec ;
while (@lang)
{
$lang = shift @lang ;
print " + lang=$lang\n" if $DEBUG >= 10 ;
$pid = undef ;
while (!$pid && @audio_details)
{
my $audio_href = shift @audio_details ;
print " + + checking this audio detail: lang=$audio_href->{lang} pid=$audio_href->{pid}\n" if $DEBUG >= 10 ;
if ($audio_href->{'lang'} =~ /$lang/i)
{
$pid = $audio_href->{'pid'} ;
print " + + Found pid = $pid\n" if $DEBUG >= 10 ;
push @$pids_aref, $pid ;
print " + Added pid = $pid\n" if $DEBUG >= 10 ;
}
}
last unless @audio_details ;
}
# clean up
if (@lang || !$pid)
{
unshift @lang, $lang if $lang ;
$error = "Error: could not find the languages: " . join(', ', @lang) . " associated with program \"$demux_params_href->{pnr}\"" ;
}
return $error ;
}
#----------------------------------------------------------------------
=item B<subtitle_pids($demux_params_href, $language_spec, $pids_aref)> #copy/paste from audio_pid by rainbowcrypt
Process the demux parameters and a language specifier to return the list of audio
streams required.
demux_params are of the form:
{
pnr => 4171,
tsid => 4107,
tuned_freq => 57800000,
...
},
(i.e. $tuning_href->{'pr'}{$channel_name})
Language specifier string is in the format:
=over 4
=item a)
Empty string : just return the default audio stream pid
=item b)
Comma/space seperated list of one or more language names : returns the audio stream pids for all that match (does not necessarily include default stream)
=back
If the list in (b) contains a '+' character (normally at the start) then the default audio stream is automatically included in teh list, and the
extra streams are added to it.
For example, if a channel has the following audio details: eng:100 eng:101 fra:102 deu:103
Then the following specifications result in the lists as shown:
=over 4
=item *
"" => (100)
=item *
"eng deu" => (100, 103)
=item *
"+eng fra" => (100, 101, 102)
=back
Note that the language names are not case sensitive
=cut
sub subtitle_pids
{ #copy/paste from audio_pid by rainbowcrypt
my ($demux_params_href, $language_spec, $pids_aref) = @_ ;
my $error = 0 ;
print "subtitle_pids(lang=\"$language_spec\")\n" if $DEBUG ;
my $subtitle_pid = $demux_params_href->{'subtitle'} ;
## simplest case is no language spec
$language_spec ||= "" ;
if (!$language_spec)
{
print " + simplest case - add default subtitle $subtitle_pid\n" if $DEBUG ;
push @$pids_aref, $subtitle_pid ;
return 0 ;
}
# split details
my @subtitle_details ;
my $details = $demux_params_href->{'subtitle_details'} || "" ;
print "subtitle_details=\"$details\")\n" if $DEBUG ;
while ($details =~ m/(\S+):(\d+)/g)
{
my ($lang, $pid) = ($1, $2) ;
push @subtitle_details, {'lang'=>lc $lang, 'pid'=>$pid} ;
print " + lang=$subtitle_details[-1]{lang} pid=$subtitle_details[-1]{pid}\n" if $DEBUG >= 10 ;
}
# drop default audio
shift @subtitle_details ;
# process language spec
if ($language_spec =~ s/\+//g)
{
# ensure default is in the list
push @$pids_aref, $subtitle_pid ;
print " - lang spec contains '+', added default subtitle\n" if $DEBUG >= 10 ;
}
print "process lang spec\n" if $DEBUG >= 10 ;
# work through the language spec
my $pid ;
my $lang ;
my @lang = split /[\s,]+/, $language_spec ;
while (@lang)
{
$lang = shift @lang ;
print " + lang=$lang\n" if $DEBUG >= 10 ;
$pid = undef ;
while (!$pid && @subtitle_details)
{
my $subtitle_href = shift @subtitle_details ;
print " + + checking this subtitle detail: lang=$subtitle_href->{lang} pid=$subtitle_href->{pid}\n" if $DEBUG >= 10 ;
if ($subtitle_href->{'lang'} =~ /$lang/i)
{
$pid = $subtitle_href->{'pid'} ;
print " + + Found pid = $pid\n" if $DEBUG >= 10 ;
push @$pids_aref, $pid ;
print " + Added pid = $pid\n" if $DEBUG >= 10 ;
}
}
last unless @subtitle_details ;
}
# clean up
if (@lang || !$pid)
{
unshift @lang, $lang if $lang ;
$error = "Error: could not find the languages: " . join(', ', @lang) . " associated with program \"$demux_params_href->{pnr}\"" ;
}
return $error ;
}
#----------------------------------------------------------------------
=item B<out_pids($demux_params_href, $out_spec, $language_spec, $subtitle_language_spec, $pids_aref)> #modified by rainbowcrypt
Process the demux parameters and an output specifier to return the list of all
stream pids required.
Output specifier string is in the format such that it just needs to contain the following characters:
a = audio
v = video
s = subtitle
Returns an array of HASHes of the form:
{'pid' => $pid, 'pidtype' => $type, 'pmt' => $pmt}
=cut
sub out_pids
{
my ($demux_params_href, $out_spec, $language_spec, $subtitle_language_spec, $pids_aref) = @_ ;
my $error = 0 ;
## default
$out_spec ||= "av" ;
# my $pmt = $demux_params_href->{'pmt'} ;
## Audio required?
if ($out_spec =~ /a/i)
{
my @audio_pids ;
$error = audio_pids($demux_params_href, $language_spec, \@audio_pids) ;
return $error if $error ;
foreach my $pid (@audio_pids)
{
push @$pids_aref, {
'pid' => $pid,
'pidtype' => 'audio',
# keep ref to program HASH (used by downstream functions)
'demux_params' => $demux_params_href,
} if $pid ;
}
}
## Video required?
if ($out_spec =~ /v/i)
{
my $pid = $demux_params_href->{'video'} ;
push @$pids_aref, {
'pid' => $pid,
'pidtype' => 'video',
# keep ref to program HASH (used by downstream functions)
'demux_params' => $demux_params_href,
} if $pid ;
}
## Subtitle required?
if ($out_spec =~ /s/i) #modified by rainbowcrypt
{
my @subtitle_pids ;
$error = subtitle_pids($demux_params_href, $subtitle_language_spec, \@subtitle_pids) ;
return $error if $error ;
foreach my $pid (@subtitle_pids)
{
push @$pids_aref, {
'pid' => $pid,
'pidtype' => 'subtitle',
# keep ref to program HASH (used by downstream functions)
'demux_params' => $demux_params_href,
} if $pid ;
}
}
return $error ;
}
#----------------------------------------------------------------------
=item B<audio_list($demux_params_href)>
Process the demux parameters and return a list of additional audio
streams (or an empty list if none available).
For example:
{
audio => 601,
audio_details => eng:601 eng:602,
...
},
would return the list: ( 602 )
=cut
sub audio_list
{
my ($demux_params_href) = @_ ;
my @pids ;
my $audio_pid = $demux_params_href->{'audio'} ;
my $details = $demux_params_href->{'audio_details'} ;
while ($details =~ m/(\S+):(\d+)/g)
{
my ($lang, $pid) = ($1, $2) ;
push @pids, $pid if ($pid != $audio_pid) ;
}
return @pids ;
}
#----------------------------------------------------------------------
=item B<sub_list($demux_params_href)> by rainbowcrypt
Process the demux parameters and return a list of additional subtitle
streams (or an empty list if none available).
For example:
{
subtitle => 601,
subtitle_details => DVD_malentendant:601 DVB-francais:602,
...
},
would return the list: ( 602 )
=cut
sub sub_list
{
my ($demux_params_href) = @_ ;
my @pids ;
my $sub_pid = $demux_params_href->{'subtitle'} ;
my $details = $demux_params_href->{'subtitle_details'} || "" ;
while ($details =~ m/(\S+):(\d+)/g)
{
my ($lang, $pid) = ($1, $2) ;
push @pids, $pid if ($pid != $sub_pid) ;
}
return @pids ;
}
#----------------------------------------------------------------------
=item B<read($search_path)>
Read tuning information from config files. Look in search path and return first
set of readable file information in a tuning HASH ref.
Returns a HASH ref of tuning information - i.e. it contains the complete information on all
transponders (under the 'ts' field), and all programs (under the 'pr' field). [see L<Linux::DVB::DVBT::scan()> method for format].
=cut
sub read
{
my ($search_path) = @_ ;
$search_path = $DEFAULT_CONFIG_PATH unless defined($search_path) ;
my $href ;
my $dir = read_dir($search_path) ;
if ($dir)
{
$href = {} ;
foreach my $region (keys %FILES)
{
no strict "refs" ;
my $fn = "read_dvb_$region" ;
print STDERR " + Running $fn() for $region ...\n" if $DEBUG ;
$href->{$region} = &$fn("$dir/$FILES{$region}{'file'}") ;
}
## Special case - get tuning info if present
$href->{'freqfile'} = read_dvb_ts_freqs("$dir/$FILES{ts}{'file'}") ;
print STDERR "Read config from $dir\n" if $DEBUG ;
print STDERR Data::Dumper->Dump(["Config=", $href]) if $DEBUG >= 5 ;
}
return $href ;
}
#----------------------------------------------------------------------
=item B<write($search_path, $tuning_href)>
Write tuning information into the first writeable area in the search path.
=cut
sub write
{
my ($search_path, $href) = @_ ;
$search_path = $DEFAULT_CONFIG_PATH unless defined($search_path) ;
my $dir = write_dir($search_path) ;
if ($dir && $href)
{
foreach my $region (keys %FILES)
{
no strict "refs" ;
my $fn = "write_dvb_$region" ;
&$fn("$dir/$FILES{$region}{'file'}", $href->{$region}, $href->{'freqfile'}) ;
}
print STDERR "Written config to $dir\n" if $DEBUG ;
}
}
#----------------------------------------------------------------------
=item B<read_filename($filetype, [$search_path] )>
Returns the readable filename for the specified file type, which can be one of: 'pr'=program, 'ts'=transponder.
Optionally specify the search path (otherwise the default search path is used)
Returns undef if invalid file type is specified, or unable to find a readable area.
=cut
sub read_filename
{
my ($filetype, $search_path) = @_ ;
my $filename ;
return $filename if (!exists($FILES{$filetype}));
$search_path = $DEFAULT_CONFIG_PATH unless defined($search_path) ;
my $dir = read_dir($search_path) ;
if ($dir)
{
$filename = "$dir/$FILES{$filetype}{'file'}" ;
}
return $filename ;
}
#----------------------------------------------------------------------
=item B<write_filename($filetype, [$search_path] )>
Returns the writeable filename for the specified file type, which can be one of: 'pr'=program, 'ts'=transponder.
Optionally specify the search path (otherwise the default search path is used)
Returns undef if invalid file type is specified, or unable to find a writeable area.
=cut
sub write_filename
{
my ($filetype, $search_path) = @_ ;
my $filename ;
return $filename if (!exists($FILES{$filetype}));
$search_path = $DEFAULT_CONFIG_PATH unless defined($search_path) ;
my $dir = write_dir($search_path) ;
if ($dir)
{
$filename = "$dir/$FILES{$filetype}{'file'}" ;
}
return $filename ;
}
#----------------------------------------------------------------------
=item B<tsid_sort($tsid_a, $tsid_b)>
Sorts TSIDs. As I now allow duplicate TSIDs in scans, and the duplicates
are suffixed with a letter to make it obvious, numeric sorting is not possible.
This function can be used to correctly sort the TSIDs into order. Returns the usual
-1, 0, 1 depending on if a is <, ==, or > b
=cut
sub tsid_sort
{
my ($tsid_a, $tsid_b) = @_ ;
my $a_int = int($tsid_a) ;
my $b_int = int($tsid_b) ;
return
$a_int <=> $b_int
||
$tsid_a cmp $tsid_b
;
}
#----------------------------------------------------------------------
=item B<tsid_str($tsid)>
Format the tsid number/name into a string. As I now allow duplicate TSIDs in
scans, and the duplicates are suffixed with a letter to make it obvious which
are duplicates. This routine formats the numeric part and always adds a suffix
character (or space if none present).
=cut
sub tsid_str
{
my ($tsid) = @_ ;
my ($tsid_int, $tsid_suffix) = ($tsid, " ") ;
if ($tsid =~ /(\d+)([a-z])/i)
{
($tsid_int, $tsid_suffix) = ($1, $2) ;
}
return sprintf "%5d$tsid_suffix", $tsid_int ;
}
#----------------------------------------------------------------------
=item B<tsid_delete($tsid, $tuning_href)>
Remove the specified TSID from the tuning information. Also removes any channels
that are under that TSID.
=cut
sub tsid_delete
{
my ($tsid, $tuning_href) = @_ ;
my $ok = 0;
if (exists($tuning_href->{'ts'}{$tsid}))
{
$ok = 1 ;
my $info_href = _scan_info($tuning_href) ;
delete $tuning_href->{'ts'}{$tsid} ;
foreach my $pnr (keys %{$info_href->{'tsid'}{$tsid}{'pr'}} )
{
my $chan = $info_href->{'tsid'}{$tsid}{'pr'}{$pnr} ;
delete $tuning_href->{'pr'}{$chan} ;
}
}
return $ok ;
}
#----------------------------------------------------------------------
=item B<merge($new_href, $old_href)>
Merge tuning information - overwrites previous with new - into $old_href and return
the HASH ref.
=cut
sub merge
{
my ($new_href, $old_href, $scan_info_href) = @_ ;
print STDERR Data::Dumper->Dump(["merge - Scan info [$scan_info_href]=", $scan_info_href]) if $DEBUG>=5 ;
$scan_info_href ||= {} ;
# region: 'ts' =>
# section: '4107' =>
# field: name = Oxford/Bexley
#
if ($old_href && $new_href)
{
foreach my $region (keys %FILES)
{
$old_href->{$region} ||= {} ;
if (exists($new_href->{$region}))
{
foreach my $section (keys %{$new_href->{$region}})
{
foreach my $field (keys %{$new_href->{$region}{$section}})
{
$old_href->{$region}{$section}{$field} = $new_href->{$region}{$section}{$field} ;
}
}
}
}
}
$old_href = $new_href if (!$old_href) ;
print STDERR Data::Dumper->Dump(["merge END - Scan info [$scan_info_href]=", $scan_info_href]) if $DEBUG>=5 ;
return $old_href ;
}
#----------------------------------------------------------------------
=item B<merge_scan_freqs($new_href, $old_href, $verbose)>
Merge tuning information - checks to ensure new program info has the
best strength, and that new program has all of it's settings
'pr' => {
BBC ONE =>
{
pnr => 4171,
tsid => 4107,
lcn => 1,
...
},
$chan => ...
},
'lcn' => {
4107 => {
4171 => {
service_type => 2,
visible => 1,
lcn => 46,
...
},
},
$tsid => {
$pnr => ...
}
},
'ts' => {
4107 =>
{
tsid => 4107,
frequency => 57800000,
strength => 46829,
...
},
$tsid => ..
},
'freqs' => {
57800000 =>
{
strength => 46829,
snr => bbb,
ber => ccc,
...
},
$freq => ...
},
=cut
sub merge_scan_freqs
{
my ($new_href, $old_href, $options_href, $verbose, $scan_info_href) = @_ ;
print STDERR Data::Dumper->Dump(["merge_scan_freqs - Scan info [$scan_info_href]=", $scan_info_href]) if $DEBUG>=5 ;
$scan_info_href ||= {} ;
$scan_info_href->{'chans'} ||= {} ;
$scan_info_href->{'tsids'} ||= {} ;
print STDERR "merge_scan_freqs()\n" if $DEBUG ;
if ($old_href && $new_href)
{
print STDERR Data::Dumper->Dump(["New:", $new_href, "Old:", $old_href]) if $DEBUG>=2 ;
## gather information on new & existing
my %old_new_info ;
$old_new_info{'new'} = _scan_info($new_href) ;
$old_new_info{'old'} = _scan_info($old_href) ;
## Copy special fields first
my %fields = map {$_ => 1} @SCAN_INFO_FIELDS ;
# ts
delete $fields{'ts'} ;
_merge_tsid($new_href, $old_href, $options_href, $verbose, $scan_info_href, \%old_new_info) ;
# pr
delete $fields{'pr'} ;
_merge_chan($new_href, $old_href, $options_href, $verbose, $scan_info_href, \%old_new_info) ;
# merge the rest
foreach my $region (keys %fields)
{
foreach my $section (keys %{$new_href->{$region}})
{
print STDERR " + Overwrite existing {$region}{$section} with new ....\n" if $DEBUG ;
## Just overwrite
foreach my $field (keys %{$new_href->{$region}{$section}})
{
$old_href->{$region}{$section}{$field} = $new_href->{$region}{$section}{$field} ;
}
}
}
}
$old_href = $new_href if (!$old_href) ;
print STDERR Data::Dumper->Dump(["merge_scan_freqs END - Scan info [$scan_info_href]=", $scan_info_href]) if $DEBUG>=5 ;
print STDERR "merge_scan_freqs() - DONE\n" if $DEBUG ;
return $old_href ;
}
#----------------------------------------------------------------------
sub _merge_tsid
{
my ($new_href, $old_href, $options_href, $verbose, $scan_info_href, $new_old_info_href) = @_ ;
$scan_info_href->{'chans'} ||= {} ;
$scan_info_href->{'tsids'} ||= {} ;
print STDERR "_merge_tsid()\n" if $DEBUG ;
print STDERR Data::Dumper->Dump(["_merge_tsid()", $new_href->{'ts'}]) if $DEBUG>=2 ;
## Compare new with old
foreach my $tsid (keys %{$new_old_info_href->{'new'}{'tsid'}})
{
my $new_chans = scalar(keys %{$new_old_info_href->{'new'}{'tsid'}{$tsid}{'pr'}}) ;
my $old_chans = 0 ;
my $new_strength_href = _strength_create($new_old_info_href->{'new'}{'tsid'}{$tsid}) ;
my $old_strength_href = _strength_create(0) ;
# my $new_strength = $new_old_info_href->{'new'}{'tsid'}{$tsid}{'strength'} ;
# my $old_strength = 0 ;
my $new_freq = $new_old_info_href->{'new'}{'tsid'}{$tsid}{'freq'} ;
my $old_freq ;
my $overlap = 0 ;
if ( exists($new_old_info_href->{'old'}{'tsid'}{$tsid}) )
{
$overlap = 1 ;
$old_chans = scalar(keys %{$new_old_info_href->{'old'}{'tsid'}{$tsid}{'pr'}}) ;
# $old_strength = $new_old_info_href->{'old'}{'tsid'}{$tsid}{'strength'} ;
$old_strength_href = _strength_create($new_old_info_href->{'old'}{'tsid'}{$tsid}) ;
$old_freq = $new_old_info_href->{'old'}{'tsid'}{$tsid}{'freq'} ;
if ($old_freq == $new_freq)
{
$overlap = 0 ;
}
}
$scan_info_href->{'tsids'}{$tsid} ||= {
'comments' => [],
} ;
my $delete = 0 ;
my $duplicate = 0 ;
my $reason = "" ;
if (!$overlap)
{
$reason = "[merge] TSID $tsid : creating new freq $new_freq (contains $new_chans chans)" ;
}
else
{
## overlap - do something
if ($options_href->{'duplicates'})
{
$duplicate = 1 ;
$reason = "[duplicate] TSID $tsid : tsid already exists (new freq $new_freq chans $new_chans, old freq $old_freq chans $old_chans), creating duplicate" ;
}
else
{
# do we overwrite based on number of channels a multiplex contains OR on the signal strength
if (!$options_href->{'num_chans'} || ($new_chans == $old_chans))
{
# overwrite based on signal strength
## if ($new_strength < $old_strength)
if (_strength_cmp($new_strength_href, $old_strength_href) < 0)
{
my $new_strength_str = _strength_str($new_strength_href);
my $old_strength_str = _strength_str($old_strength_href);
$delete = 1 ;
# $reason = "[overlap] TSID $tsid : new freq $new_freq strength $new_strength ($new_chans chans) < existing freq $old_freq strength $old_strength ($old_chans chans) - new freq ignored" ;
$reason = "[overlap] TSID $tsid : new freq $new_freq strength $new_strength_str ($new_chans chans) < existing freq $old_freq strength $old_strength_str ($old_chans chans) - new freq ignored" ;
}
else
{
my $new_strength_str = _strength_str($new_strength_href);
my $old_strength_str = _strength_str($old_strength_href);
# $reason = "[overlap] TSID $tsid : new freq $new_freq strength $new_strength >= existing freq $old_freq strength $old_strength - using new freq" ;
$reason = "[overlap] TSID $tsid : new freq $new_freq strength $new_strength_str ($new_chans chans) >= existing freq $old_freq strength $old_strength_str ($old_chans chans) - using new freq" ;
}
}
# compare number of channels
elsif ($new_chans < $old_chans)
{
$delete = 1 ;
$reason = "[overlap] TSID $tsid : new freq $new_freq has only $new_chans chans (existing freq $old_freq has $old_chans chans) - new freq ignored" ;
}
else
{
$reason = "[overlap] TSID $tsid : new freq $new_freq has $new_chans chans (existing freq $old_freq has $old_chans chans) - using new freq" ;
}
}
}
## delete if required
if ($delete)
{
delete $new_href->{'ts'}{$tsid} ;
foreach my $pnr (keys %{$new_old_info_href->{'new'}{'tsid'}{$tsid}{'pr'}} )
{
my $chan = $new_old_info_href->{'new'}{'tsid'}{$tsid}{'pr'}{$pnr} ;
$scan_info_href->{'chans'}{$chan} ||= {
'comments' => [],
} ;
push @{$scan_info_href->{'chans'}{$chan}{'comments'}}, $reason ;
delete $new_href->{'pr'}{$chan} ;
}
}
## duplicate if required
elsif ($duplicate)
{
## Create a dummy name for this tsid
my $suffix = 'a' ;
my $tsid_dup = "$tsid$suffix" ;
while (exists($new_old_info_href->{'old'}{'tsid'}{$tsid_dup}))
{
++$suffix ;
$tsid_dup = "$tsid$suffix" ;
}
$reason .= " TSID $tsid_dup" ;
## rename tsid
# ts
my $tsid_href = delete $new_href->{'ts'}{$tsid} ;
$new_href->{'ts'}{$tsid_dup} = $tsid_href ;
$new_href->{'ts'}{$tsid_dup}{'tsid'} = $tsid_dup ;
# pr
foreach my $pnr (keys %{$new_old_info_href->{'new'}{'tsid'}{$tsid}{'pr'}} )
{
my $chan = $new_old_info_href->{'new'}{'tsid'}{$tsid}{'pr'}{$pnr} ;
$scan_info_href->{'chans'}{$chan} ||= {
'comments' => [],
} ;
push @{$scan_info_href->{'chans'}{$chan}{'comments'}}, $reason ;
$new_href->{'pr'}{$chan}{'tsid'} = $tsid_dup ;
}
# lcn
my $lcn_href = delete $new_href->{'lcn'}{$tsid} ;
$new_href->{'lcn'}{$tsid_dup} = $lcn_href ;
## rename chan
# pr
foreach my $pnr (keys %{$new_old_info_href->{'new'}{'tsid'}{$tsid}{'pr'}} )
{
my $chan = $new_old_info_href->{'new'}{'tsid'}{$tsid}{'pr'}{$pnr} ;
my $count = 1 ;
my $chan_dup = "$chan ($count)";
while (exists($new_old_info_href->{'old'}{'pr'}{$chan_dup}))
{
++$count ;
$chan_dup = "$chan ($count)";
}
push @{$scan_info_href->{'chans'}{$chan}{'comments'}}, "[duplicate] Renamed $chan to $chan_dup" ;
my $chan_href = delete $new_href->{'pr'}{$chan} ;
$new_href->{'pr'}{$chan_dup} = $chan_href ;
$new_href->{'pr'}{$chan_dup}{'name'} = $chan_dup ;
}
print STDERR " + duplicate TSID\n" if $DEBUG ;
print STDERR Data::Dumper->Dump(["After tsid rename ", $new_href]) if $DEBUG>=2 ;
}
else
{
## ok to copy
foreach my $pnr (keys %{$new_old_info_href->{'new'}{'tsid'}{$tsid}{'pr'}} )
{
my $chan = $new_old_info_href->{'new'}{'tsid'}{$tsid}{'pr'}{$pnr} ;
$scan_info_href->{'chans'}{$chan} ||= {
'comments' => [],
} ;
push @{$scan_info_href->{'chans'}{$chan}{'comments'}}, $reason ;
}
}
# update TSID debug info
push @{$scan_info_href->{'tsids'}{$tsid}{'comments'}}, $reason ;
}
## Do merge
foreach my $tsid (keys %{$new_href->{'ts'}})
{
## Just overwrite
foreach my $field (keys %{$new_href->{'ts'}{$tsid}})
{
$old_href->{'ts'}{$tsid}{$field} = $new_href->{'ts'}{$tsid}{$field} ;
}
}
}
#----------------------------------------------------------------------
sub _merge_chan
{
my ($new_href, $old_href, $options_href, $verbose, $scan_info_href, $new_old_info_href) = @_ ;
$scan_info_href->{'chans'} ||= {} ;
$scan_info_href->{'tsids'} ||= {} ;
print STDERR "_merge_chan()\n" if $DEBUG ;
print STDERR Data::Dumper->Dump(["_merge_chan()", $new_href->{'pr'}]) if $DEBUG>=2 ;
## Do merge
foreach my $chan (keys %{$new_href->{'pr'}})
{
## Check for channel rename
my $tsid = $new_href->{'pr'}{$chan}{'tsid'} ;
my $pnr = $new_href->{'pr'}{$chan}{'pnr'} ;
print STDERR " + check {$tsid-$pnr} = $chan \n" if $DEBUG ;
if (exists($new_old_info_href->{'old'}{'tsid-pnr'}{"$tsid-$pnr"}) && ($new_old_info_href->{'old'}{'tsid-pnr'}{"$tsid-$pnr"} ne $chan))
{
# Rename
my $old_chan = $new_old_info_href->{'old'}{'tsid-pnr'}{"$tsid-$pnr"} ;
push @{$scan_info_href->{'chans'}{$chan}{'comments'}}, "[merge] channel renamed from \"$old_chan\" to \"$chan\" " ;
delete $old_href->{'pr'}{$old_chan} ;
print STDERR " + + delete $old_chan \n" if $DEBUG ;
}
## Check for channel TSID change
my $overlap = 0 ;
if (exists($old_href->{'pr'}{$chan}))
{
$overlap = 1 ;
if ($new_href->{'pr'}{$chan}{'tsid'} eq $old_href->{'pr'}{$chan}{'tsid'})
{
$overlap = 0 ;
}
}
$scan_info_href->{'chans'}{$chan} ||= {
'comments' => [],
} ;
my $reason ;
my $copy_chan = $chan ;
if (!$overlap)
{
$reason = "[merge] creating new channel info" ;
}
else
{
## overlap - do something
if ($options_href->{'duplicates'})
{
# duplicate
$reason = "[duplicate] Channel $chan already exists (new TSID $new_href->{'pr'}{$chan}{'tsid'}, old TSID $old_href->{'pr'}{$chan}{'tsid'}), creating duplicate" ;
my $count = 1 ;
$copy_chan = "$chan ($count)";
while (exists($old_href->{'pr'}{$copy_chan}))
{
++$count ;
$copy_chan = "$chan ($count)";
}
$reason .= " New channel name $copy_chan" ;
}
else
{
# overwrite
$reason = "[overlap] overwriting existing channel info with new (old: TSID $old_href->{'pr'}{$chan}{tsid})" ;
}
}
push @{$scan_info_href->{'chans'}{$chan}{'comments'}}, $reason ;
## Now overwrite
foreach my $field (keys %{$new_href->{'pr'}{$chan}})
{
$old_href->{'pr'}{$copy_chan}{$field} = $new_href->{'pr'}{$chan}{$field} ;
}
$old_href->{'pr'}{$copy_chan}{'name'} = $copy_chan ;
}
}
#----------------------------------------------------------------------
sub _scan_info
{
my ($scan_href) = @_ ;
## Get info on existing
my %tsid_map ;
foreach my $chan (keys %{$scan_href->{'pr'}})
{
my $tsid = $scan_href->{'pr'}{$chan}{'tsid'} ;
my $pnr = $scan_href->{'pr'}{$chan}{'pnr'} ;
$tsid_map{"$tsid-$pnr"} = $chan ;
}
## Various ways of looking at tsid info
my %ts_info ;
foreach my $tsid (keys %{$scan_href->{'ts'}})
{
my $freq = $scan_href->{'ts'}{$tsid}{'frequency'} ;
$ts_info{$tsid} = {
'pr' => {},
'freq' => $scan_href->{'ts'}{$tsid}{'frequency'},
'strength' => $scan_href->{'ts'}{$tsid}{'strength'},
'snr' => $scan_href->{'ts'}{$tsid}{'snr'},
'ber' => $scan_href->{'ts'}{$tsid}{'ber'},
} ;
}
foreach my $chan (keys %{$scan_href->{'pr'}})
{
my $tsid = $scan_href->{'pr'}{$chan}{'tsid'} ;
my $pnr = $scan_href->{'pr'}{$chan}{'pnr'} ;
$ts_info{$tsid}{'pr'}{$pnr} = $chan ;
}
## Various ways of looking at channel info
my %chan_info ;
foreach my $chan (keys %{$scan_href->{'pr'}})
{
my $tsid = $scan_href->{'pr'}{$chan}{'tsid'} ;
$chan_info{$chan} = $tsid ;
}
my %info = (
'tsid-pnr' => \%tsid_map,
'tsid' => \%ts_info,
'chan' => \%chan_info,
) ;
return \%info ;
}
#----------------------------------------------------------------------
# Split the search path & expand all the directories to absolute paths
#
sub _expand_search_path
{
my ($search_path) = @_ ;
my @dirs = split /:/, $search_path ;
foreach my $d (@dirs)
{
# Replace any '~' with $HOME
$d =~ s/~/\$HOME/g ;
# Now replace any vars with values from the environment
$d =~ s/\$(\w+)/$ENV{$1}/ge ;
# Ensure path is clean
$d = File::Spec->rel2abs($d) ;
}
return @dirs ;
}
#----------------------------------------------------------------------
=item B<read_dir($search_path)>
Find directory to read from - first readable directory in search path
=cut
sub read_dir
{
my ($search_path) = @_ ;
my @dirs = _expand_search_path($search_path) ;
my $dir ;
foreach my $d (@dirs)
{
my $found=1 ;
foreach my $region (keys %FILES)
{
if ($FILES{$region}{'required'})
{
$found=0 if (! -f "$d/$FILES{$region}{'file'}") ;
}
}
if ($found)
{
$dir = $d ;
last ;
}
}
print STDERR "Searched $search_path : read dir=".($dir?$dir:"")."\n" if $DEBUG ;
return $dir ;
}
#----------------------------------------------------------------------
=item B<write_dir($search_path)>
Find directory to write to - first writeable directory in search path
=cut
sub write_dir
{
my ($search_path) = @_ ;
my @dirs = _expand_search_path($search_path) ;
my $dir ;
print STDERR "Find dir to write to from $search_path ...\n" if $DEBUG ;
foreach my $d (@dirs)
{
my $found=1 ;
print STDERR " + processing $d\n" if $DEBUG ;
# See if dir exists
if (!-d $d)
{
# See if this user can create the dir
eval {
mkpath([$d], $DEBUG, 0755) ;
};
$found=0 if $@ ;
print STDERR " + $d does not exist - attempt to mkdir=$found\n" if $DEBUG ;
}
if (-d $d)
{
print STDERR " + $d does exist ...\n" if $DEBUG ;
# See if this user can write to the dir
foreach my $region (keys %FILES)
{
if (open my $fh, ">>$d/$FILES{$region}{'file'}")
{
close $fh ;
print STDERR " + + Write to $d/$FILES{$region}{'file'} succeded\n" if $DEBUG ;
}
else
{
print STDERR " + + Unable to write to $d/$FILES{$region}{'file'} - aborting this dir\n" if $DEBUG ;
$found = 0;
last ;
}
}
}
if ($found)
{
$dir = $d ;
last ;
}
}
print STDERR "Searched $search_path : write dir=".($dir?$dir:"")."\n" if $DEBUG ;
return $dir ;
}
#============================================================================================
=back
=head3 TSID config file (dvb-ts) read/write
=over 4
=cut
#----------------------------------------------------------------------
=item B<read_dvb_ts($fname)>
Read the transponder settings file of the form:
[4107]
name = Oxford/Bexley
frequency = 578000000
bandwidth = 8
modulation = 16
hierarchy = 0
code_rate_high = 34
code_rate_low = 34
guard_interval = 32
transmission = 2
=cut
sub read_dvb_ts
{
my ($fname) = @_ ;
my %dvb_ts ;
open my $fh, "<$fname" or die "Error: Unable to read $fname : $!" ;
my $line ;
my $tsid ;
while(defined($line=<$fh>))
{
chomp $line ;
next if $line =~ /^\s*#/ ; # skip comments
if ($line =~ /\[([\da-z]+)\]/i)
{
$tsid=$1;
}
elsif ($line =~ /(\S+)\s*=\s*(\S+)/)
{
if ($tsid)
{
$dvb_ts{$tsid}{$1} = $2 ;
}
}
elsif ($line =~ /(\S+)\s*=/)
{
# skip empty entries
}
else
{
$tsid = undef ;
}
}
close $fh ;
return \%dvb_ts ;
}
#----------------------------------------------------------------------
=item B<read_dvb_ts_freqs($fname)>
Read the transponder settings file comments section, if present, containing the
frequency file information used during the scan. The values are in "VDR" format:
# VDR freq bw fec_hi fec_lo mod transmission-mode guard-interval hierarchy inversion
For example, the frequency file format:
# T 578000000 8MHz 2/3 NONE QAM64 2k 1/32 NONE
will be saved as:
# VDR 578000000 8 23 0 64 2 32 0 0
=cut
sub read_dvb_ts_freqs
{
my ($fname) = @_ ;
print STDERR "read_dvb_ts_freqs($fname)\n" if $DEBUG>=5 ;
my %dvb_ts_freqs = () ;
open my $fh, "<$fname" or die "Error: Unable to read $fname : $!" ;
my $line ;
while(defined($line=<$fh>))
{
chomp $line ;
next unless $line =~ /^\s*#/ ; # skip non-comments
print STDERR " + line $line\n" if $DEBUG>=5 ;
## Parse line
if ($line =~ m%^\s*#\s*VDR\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)%i)
{
my $freq = Linux::DVB::DVBT::dvb_round_freq($1) ;
if (exists($dvb_ts_freqs{$freq}))
{
print STDERR "Note: frequency $freq Hz already seen, skipping\n" ;
next ;
}
print STDERR " + + add $freq\n" if $DEBUG>=5 ;
$dvb_ts_freqs{$freq} = {
frequency => $freq,
bandwidth => $2,
code_rate_high => $3,
code_rate_low => $4,
modulation => $5,
transmission => $6,
guard_interval => $7,
hierarchy => $8,
inversion => $9,
} ;
}
}
close $fh ;
print STDERR Data::Dumper->Dump(["read_dvb_ts_freqs - href=", \%dvb_ts_freqs]) if $DEBUG>=5 ;
return \%dvb_ts_freqs ;
}
#----------------------------------------------------------------------
=item B<write_dvb_ts($fname, $href)>
Write transponder config information
=cut
sub write_dvb_ts
{
my ($fname, $href, $freqs_href) = @_ ;
open my $fh, ">$fname" or die "Error: Unable to write $fname : $!" ;
print STDERR Data::Dumper->Dump(["write_dvb_ts - href=", $href, "freqs=", $freqs_href]) if $DEBUG>=5 ;
## Save frequency list first (if available)
if ($freqs_href && (keys %$freqs_href))
{
# # VDR freq bw fec_hi fec_lo mod transmission-mode guard-interval hierarchy inversion
#
# # VDR 578000000 8 23 0 64 2 32 0 0
#
print $fh "## freq bw fec_hi fec_lo mod transmission-mode guard-interval hierarchy inversion\n" ;
foreach my $freq (sort {$a <=> $b} keys %$freqs_href)
{
my $tuning_href = $freqs_href->{$freq} ;
print $fh "# VDR " ;
foreach my $field (qw/
frequency
bandwidth
code_rate_high
code_rate_low
modulation
transmission
guard_interval
hierarchy
inversion
/)
{
printf $fh "%d ", $tuning_href->{$field} ;
}
print $fh "\n" ;
}
}
# Write config information
#
# 'ts' =>
# 4107 =>
# { # HASH(0x83241b8)
# bandwidth => 8,
# code_rate_hp => 34, code_rate_high
# code_rate_lp => 34, code_rate_low
# constellation => 16, modulation
# frequency => 578000000,
# guard => 32, guard_interval
# hierarchy => 0,
# net => Oxford/Bexley,
# transmission => 2,
# tsid => 4107,
# },
#
#[4107]
#name = Oxford/Bexley
#frequency = 578000000
#bandwidth = 8
#modulation = 16
#hierarchy = 0
#code_rate_high = 34
#code_rate_low = 34
#guard_interval = 32
#transmission = 2
#
#
foreach my $section (sort {$a <=> $b} keys %$href)
{
print $fh "[$section]\n" ;
foreach my $field (sort keys %{$href->{$section}})
{
my $val = $href->{$section}{$field} ;
if ($val =~ /\S+/)
{
print $fh "$field = $val\n" ;
}
}
print $fh "\n" ;
}
close $fh ;
}
#============================================================================================
=back
=head3 Channels config file (dvb-pr) read/write
=over 4
=cut
#----------------------------------------------------------------------
=item B<read_dvb_pr($fname)>
Read dvb-pr - channel information - of the form:
[4107-4171]
video = 600
audio = 601
audio_details = eng:601 eng:602
type = 1
net = BBC
name = BBC ONE
=cut
sub read_dvb_pr
{
my ($fname) = @_ ;
my %dvb_pr ;
open my $fh, "<$fname" or die "Error: Unable to read $fname : $!" ;
my $line ;
my $pnr ;
my $tsid ;
while(defined($line=<$fh>))
{
chomp $line ;
next if $line =~ /^\s*#/ ; # skip comments
if ($line =~ /\[([\da-z]+)\-([\d]+)\]/i)
{
($tsid, $pnr)=($1,$2);
}
elsif ($line =~ /(\S+)\s*=\s*(\S+.*)/)
{
if ($pnr && $tsid)
{
$dvb_pr{"$tsid-$pnr"}{$1} = $2 ;
# ensure tsid & pnr are in the hash
$dvb_pr{"$tsid-$pnr"}{'tsid'} = $tsid ;
$dvb_pr{"$tsid-$pnr"}{'pnr'} = $pnr ;
}
}
elsif ($line =~ /(\S+)\s*=/)
{
# skip empty entries
}
else
{
$pnr = undef ;
$tsid = undef ;
}
}
close $fh ;
# Make channel name the first key
my %chans ;
foreach (keys %dvb_pr)
{
# handle chans with no name
my $name = $dvb_pr{$_}{'name'} || $_ ;
$chans{$name} = $dvb_pr{$_} ;
}
return \%chans ;
}
#----------------------------------------------------------------------
=item B<write_dvb_pr($fname, $href)>
Write program config file.
=cut
sub write_dvb_pr
{
my ($fname, $href) = @_ ;
open my $fh, ">$fname" or die "Error: Unable to write $fname : $!" ;
# Write config information
#
# 'pr' =>
# BBC ONE =>
# { # HASH(0x8327848)
# a_pid => 601, audio
# audio => eng:601 eng:602, audio_details
# ca => 0,
# name => "BBC ONE",
# net => BBC,
# p_pid => 4171, -N/A-
# pnr => 4171,
# running => 4,
# t_pid => 0, teletext
# tsid => 4107,
# type => 1,
# v_pid => 600, video
# version => 26, -N/A-
# },
#
#[4107-4171]
#video = 600
#audio = 601
#audio_details = eng:601 eng:602
#type = 1
#net = BBC
#name = BBC ONE
#
foreach my $section (sort {
$href->{$a}{'tsid'} <=> $href->{$b}{'tsid'}
||
$href->{$a}{'pnr'} <=> $href->{$b}{'pnr'}
} keys %$href)
{
print $fh "[$href->{$section}{tsid}-$href->{$section}{pnr}]\n" ;
foreach my $field (sort keys %{$href->{$section}})
{
my $val = $href->{$section}{$field} ;
if ($val =~ /\S+/)
{
print $fh "$field = $val\n" ;
}
}
print $fh "\n" ;
}
close $fh ;
}
#============================================================================================
=back
=head3 Channel names aliases config file (dvb-aliases) read/write
=over 4
=cut
#----------------------------------------------------------------------
=item B<read_dvb_aliases($fname)>
Read dvb-aliases - channel names aliases - of the form:
FIVE = Channel 5
=cut
sub read_dvb_aliases
{
my ($fname) = @_ ;
my %dvb_aliases ;
#print STDERR "read_dvb_aliases($fname)\n" ;
if (-f $fname)
{
open my $fh, "<$fname" or die "Error: Unable to read $fname : $!" ;
my $line ;
while(defined($line=<$fh>))
{
chomp $line ;
next if $line =~ /^\s*#/ ; # skip comments
$line =~ s/\s+$// ;
$line =~ s/^\s+// ;
# print STDERR "!! $line !!\n" ;
if ($line =~ /(\S+[^=]+)\s*=\s*(\S+[^=]+)\s*/)
{
my ($from, $to) = ($1, $2) ;
$from =~ s/\s+$// ;
$dvb_aliases{$from} = $to ;
# print STDERR " + <$from> = <$to>\n" ;
}
}
close $fh ;
}
#print STDERR "read_dvb_aliases - done\n" ;
return \%dvb_aliases ;
}
#----------------------------------------------------------------------
=item B<write_dvb_aliases($fname, $href)>
Write channel names aliases config file.
=cut
sub write_dvb_aliases
{
my ($fname, $href) = @_ ;
open my $fh, ">$fname" or die "Error: Unable to write $fname : $!" ;
# Write config information
#
# 'aliases' =>
# "FIVE" => "Channel 5"
#
# FIVE = Channel 5
#
foreach my $from (sort keys %$href)
{
my $val = $href->{$from} ;
if ($val =~ /\S+/)
{
print $fh "$from = $val\n" ;
}
}
close $fh ;
}
#============================================================================================
# TSID strength/snr/ber
#----------------------------------------------------------------------
sub _strength_create
{
my ($href) = @_ ;
my $strength_href = {
'strength' => 0,
'snr' => 0,
'ber' => undef,
'use' => undef,
} ;
print STDERR "_strength_create()\n" if $DEBUG ;
if (ref($href) eq 'HASH')
{
foreach my $field (qw/strength snr ber/)
{
print STDERR " + $field = $href->{$field}\n" if $DEBUG ;
$strength_href->{$field} = $href->{$field} if exists($href->{$field}) ;
# Handle special case where value reads back as all 1's
if ($strength_href->{$field} == 0xffff)
{
print STDERR " + + clamped dodgy value\n" if $DEBUG ;
# treat it as a bad value
$strength_href->{$field} = 0 ;
}
}
# # Handle special case where strength reads back as all 1's
# if ($strength_href->{'strength'} == 0xffff)
# {
# # treat it as a bad value
# $strength_href->{'strength'} = 0 ;
# }
}
return $strength_href ;
}
#----------------------------------------------------------------------
sub _strength_cmp
{
my ($a_href, $b_href) = @_ ;
## Work through the fields in order of preference
my $use ;
foreach my $field (qw/snr strength ber/)
{
if (defined($a_href->{$field}) && defined($b_href->{$field}) && ($a_href->{$field} > 0) && ($a_href->{$field} > 0))
{
$use = $field ;
last ;
}
}
print STDERR "_strength_cmp()\n" if $DEBUG ;
$use ||= 'strength' ;
$a_href->{'use'} = $use ;
$b_href->{'use'} = $use ;
my $a_val = $a_href->{$use} ;
my $b_val = $b_href->{$use} ;
if ($use eq 'ber')
{
$a_val = 0xffff - $a_val ;
$b_val = 0xffff - $b_val ;
}
print STDERR " + using $use - $a_val <=> $b_val\n" if $DEBUG ;
return $a_val <=> $b_val ;
}
#----------------------------------------------------------------------
sub _strength_str
{
my ($href) = @_ ;
my $str = "unset" ;
if ($href->{'use'})
{
$str = "$href->{$href->{use}} ($href->{use})" ;
}
return $str ;
}
# ============================================================================================
# END OF PACKAGE
1;