[mythtv-users] Australian TV grabber (tv_grab_au)
Michael Smith
myth at immir.com
Mon May 9 23:31:31 UTC 2005
Phil,
> Michael - this is really excellent. Thanks very much for doing this
> for us Aussies.
No problem. Glad you like it :-).
Use the version attached, and change your (perl syntax) conf section to
something like this:
$conf = {
'days' => 3,
'output' => '/my/path/to/output',
'TZ' => '+1000',
'services' => [
{
'name' => 'free',
'region' => 'Sydney',
'regionid' => '73'
}
],
'slow' => 1
};
-------------- next part --------------
#!/usr/bin/perl -w
eval 'exec /usr/bin/perl -w -S $0 ${1+"$@"}'
if 0; # not running under some shell
# --- new NMSN Australian TV grabber by Michael 'Immir' Smith...
# --- $Id: tv_grab_au,v 1.27 2005/05/09 23:27:50 michael Exp $
#
# A current version of this script should usually be available here:
#
# <http://immir.com/tv_grab_au>
#
# pod documentation coming later...
#
# in brief: --configure configure the grabber
# --config-file <file> use specified config file
# --list-channels show subscribed channels and xmltvids
# --slow download a details page for every show
# --fast opposite of --slow (to override config)
# --days <n> days to grab
# --output <file> xml output file
# --ignore-cache ignore cached information
#
# When run with mythfilldatabase, the config file may end up as
# ~/.mythtv/tv_grab_au.xmltv and this is where xmltvids for channels can
# be modified.
#
# Downloading of details pages can be controlled by the --slow option
# (possibly set by default during configuration) and regular expressions
# contained in the following two files (one regexp per line):
#
# ~/.tv_grab_au-detailed contains a list of regular expressions matching
# "chanid:titles" of shows for which we want details
# e.g.: Doctor Who
# Simpsons
# abc.gov.au:.*Chef
#
# ~/.tv_grab_au-undetailed regular expressions matching "chanid:titles" to
# skip details downloads
# e.g.: News
# news.sbs.com.au
#
# NB: a match against the detailed list overrides any undetailed matches.
# When changing these files, use the --ignore-cache option to ensure
# all new details are downloaded.
#
# TZ change added as suggested by Greg Boundy.
use strict;
use Getopt::Long;
use LWP::UserAgent;
use Date::Manip;
use File::Path;
use File::Basename;
use Data::Dumper;
use HTML::TreeBuilder;
use XMLTV;
use XMLTV::Ask;
use XMLTV::ProgressBar;
use XMLTV::Config_file;
# --- global parameters/constants
my $conf; # hashref for configuration parameters
my $channels; # ref to hash of subscribed channel names to xmltvids
my %chanid; # mapping from lowercase channel name to xmltv channel id
my $lang = "en";
my $spoofMSID = 1; # spoof random MSIDs to avoid redirects?
my $output_dir = "/var/local/tv_grab_au";
my $cache_file = "$output_dir/cached.pl";
my $want_details_file = "$ENV{HOME}/.tv_grab_au-detailed";
my $skip_details_file = "$ENV{HOME}/.tv_grab_au-undetailed";
my (@want_details, @skip_details);
my $count_guide = 0; # global counter for reporting
my $count_detail = 0; # global counter for reporting
# --- various NMSN site URLs
my $NMSN = "http://tvguide.ninemsn.com.au";
my $GUIDE = "http://tvguide.ninemsn.com.au/guide";
my $DETAILS = "http://tvguide.ninemsn.com.au/closeup/default.asp";
my $TVTODAY = "http://tvguide.ninemsn.com.au/todaytv/default.asp";
my ($Revision) = '$Revision: 1.27 $' =~ /Revision:\s*(\S+)/;
my $debug = 0;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Indent = 1;
# --- Command-line options
my ($opt_configfile, $opt_configure, $opt_list_channels);
my $opt_output = "$output_dir/guide.xml";
my $opt_gui = 1;
my $opt_slow = 0;
my $opt_fast = 0;
my $opt_days = 5;
my $opt_skipcache = 0;
GetOptions( 'days=i' => \$opt_days,
'list-channels' => \$opt_list_channels,
'output=s' => \$opt_output,
'config-file=s' => \$opt_configfile,
'configure' => \$opt_configure,
'gui=s' => \$opt_gui,
'slow' => \$opt_slow,
'fast' => \$opt_fast,
'ignore-cache' => \$opt_skipcache,
'debug' => \$debug );
XMLTV::Ask::init($opt_gui);
my $config_file = XMLTV::Config_file::filename
($opt_configfile, 'tv_grab_au', not $debug );
# --- read and parse configuration --- must do this before configuring
# in case the user has chosen non-default xmltvids for some channels...
if (-r $config_file) {
local (@ARGV, $/) = ($config_file, undef);
no warnings 'all';
eval <>;
die "error in conf file: $@" if $@ and not $opt_configure;
print "unable to read configuration file... configuring anyway..." if $@;
}
# --- extract sorted subscribed channel list from config-file hash;
# also compute canonicalised lowercased channel to xmltvid hash
%chanid = map { lc $_, $channels->{$_} } keys %$channels;
# --- are we configuring?
if ($opt_configure) { configure($config_file); exit 0 }
# --- are we just listing channels?
if ($opt_list_channels) {
print " $_ -> $channels->{$_}\n" for sort keys %$channels; exit 0;
}
# --- we must be grabbing program information...
my $runtime = time();
# --- explicitly Set Timezone
Date_Init("TZ=$conf->{TZ}");
# --- flag for whether we get individual program details (follow closeup pids)
die "cannot do slow and fast\n" if $opt_slow and $opt_fast;
my $slow = $opt_fast ? 0 : ($opt_slow || $conf->{slow});
# --- override slow for some shows (read list of regexps from file)
if (-f $want_details_file) {
local (@ARGV) = ($want_details_file);
chomp(@want_details = <>);
}
if (-f $skip_details_file) {
local (@ARGV) = ($skip_details_file);
chomp(@skip_details = <>);
}
# --- other configuration options
$opt_days = $conf->{days} if $conf->{days};
$opt_output = $conf->{output} if $conf->{output};
if ($debug) {
print "tv_grab_au revision $Revision\n";
print "configuration file $config_file:\n";
print " TZ = $conf->{TZ} slow = $conf->{slow}\n";
print " services:\n";
for my $service (@{$conf->{services}}) {
print " $service->{name}: " .
" region=$service->{region} id=$service->{regionid}\n";
}
print "channel list:\n";
print " $_ -> $channels->{$_}\n" for keys %$channels;
print "\n";
}
print fixplural("grabbing $opt_days days into $opt_output\n");
# --- first get cached list of shows from last time (if any)
my ($cached, $newcache);
if (-r $cache_file and not $opt_skipcache) {
local (@ARGV, $/) = ($cache_file, undef);
no warnings 'all';
eval <>;
die "$@" if $@;
}
# --- now, on with the shows...
# showlists is a hash of refs to arrays holding shows for each channel
# which we use to infer endtimes from starttimes since the rowspan
# information is generally unreliable (in slow mode)
my %showlists;
for my $day (0 .. $opt_days-1) {
my $date = Ymd(DateCalc("today", "+ $day days"));
my $date6am = ParseDate("6am $date");
for my $service (@{$conf->{services}}) {
my $guidedata = get_guide_page($service, dmY($date6am)) or next;
my $tree = HTML::TreeBuilder->new_from_content($guidedata);
for ($tree->look_down('_tag' => 'table', 'class' => 'tv')) {
# extract channel names from the first row of the table
# (this row has align=middle and the channels are in bold)
my @hdr = map { $_->as_text }
$_->look_down('_tag' => 'tr', 'align' => 'middle')
->look_down('_tag' => 'b');
my @span = (0) x @hdr; # rowspans to infer columns
my $row = 0; # row number (to compute start times)
for ($_->look_down('_tag' => 'tr', 'valign' => 'top')) {
my @idx = grep { $span[$_] == 0 } 0..$#hdr; # columns for this row
for ($_->look_down('_tag' => 'td', 'class' => 'tv')) {
my $col = shift @idx;
my $rowspan = $_->attr("rowspan") || 1;
$span[$col] = $rowspan;
my $channel = $hdr[$col];
next unless my $chanid = $chanid{lc $channel};
my ($e) = $_->content_list;
next unless ref($e) eq 'HTML::Element';
next unless $e->tag eq 'a';
my ($pid) = $e->as_HTML =~ /pid=(\d+)/;
next if $pid == 0; # sometimes null programs at bottom of table
my $title = $e->as_text();
# --- check (pid, row, rowspan, title) against old cached data
my $cache_id = "$date:$pid:$row:$rowspan:$title";
if ($cached->{$cache_id}) {
$newcache->{$cache_id} = $cached->{$cache_id};
push @{ $showlists{$chanid} }, $cached->{$cache_id};
next;
}
# --- compute start and stop times based on row of table and
# rowspan --- although this appears problematic for some days
my $start = DateCalc($date6am, ($row*5) . " minutes");
my $stop = DateCalc($start, ($rowspan*5) . " minutes");
if ($title =~ s/\s*\[\s* (\d+):(\d+) \s* (am|pm) \s*\]\s* //x) {
my ($hr, $min, $ampm) = ($1, $2, lc($3));
my $hrdm = $hr == 0 ? 12 : $hr; # Date::Manip convention
$min = sprintf "%02d", $min; # NMSN bug: 1-digit minutes
$start = ParseDate("$hrdm:$min$ampm $date");
$start = DateCalc($start, "+ 1 day") if $hr < 6 and $ampm eq "am";
}
($start .= " $conf->{TZ}") =~ tr/://d;
($stop .= " $conf->{TZ}") =~ tr/://d;
my $show = { 'title' => [[$title, $lang]],
'start' => $start,
'stop' => $stop,
'channel' => $chanid };
# --- fill in more details? ---
get_closeup_details($show,$pid) if want_details($show);
# --- that's it!
push @{ $showlists{$chanid} }, $show;
$newcache->{$cache_id} = $show;
print Dumper($show) if $debug;
}
++ $row;
@span = map { $_ - 1} @span; # update rowspan counts
}
}
$tree->delete();
}
}
# --- fill in stop times
for my $channel (keys %showlists) {
my @shows = @{ $showlists{$channel} };
for my $i (0 .. @shows-2) {
$shows[$i]->{stop} = $shows[$i+1]->{start};
}
}
# --- now write to xml
print "writing xml\n";
my %writer_args = ( encoding => 'ISO-8859-1' );
if ($opt_output) {
my $fh = new IO::File(">$opt_output") or die "can't open $opt_output: $!";
$writer_args{OUTPUT} = $fh;
}
my $writer = new XMLTV::Writer(%writer_args);
$writer->start
( { 'source-info-url' => $NMSN,
'source-info-name' => "NMSN TV Guide",
'generator-info-name' => "XMLTV - tv_grab_au NMSN v$Revision"} );
$writer->write_channel( { 'display-name' => [[$_, $lang]],
'id' => $chanid{lc $_} } ) for sort keys %$channels;
for my $channel (keys %showlists) {
$writer->write_programme($_) for @{ $showlists{$channel} };
}
$writer->end();
# --- save seen data
$cached = $newcache;
open(CACHE, "> $cache_file") or die "cannot open $cache_file: $!";
print CACHE Data::Dumper->Dump([$cached], ["cached"]);
close CACHE;
# --- report runtime
$runtime = time() - $runtime;
my $min = int($runtime / 60);
my $sec = $runtime % 60;
printf "tv_grab_au: downloads = %d guide pages, %d detail pages\n",
$count_guide, $count_detail;
printf "tv_grab_au: finished in %d:%02d\n", $min, $sec;
exit 0; # Game over, man!
# ---------------------------------------------------------------------------
# --- subroutines
# --- determine whether to get details
sub want_details {
my $show = shift;
my $title = $show->{title}->[0]->[0];
my $channel = $show->{channel};
my $string = "$channel:$title";
my $want = $slow;
for my $expr (@skip_details) { $want = 0 if $string =~ /$expr/ }
for my $expr (@want_details) { $want = 1 if $string =~ /$expr/ }
return $want;
}
# --- get details from the closeup page for given show
sub get_closeup_details {
my $show = shift;
my $pid = shift;
my $title = $show->{title}->[0]->[0];
my $date = Ymd($show->{start});
my $details = get_details_page($pid) or return;
# --- use HTML::TreeBuilder to parse the details from the page...
my $tree = HTML::TreeBuilder->new_from_content($details);
# --- the details are in a two row table: first row is the header
# ('Time', 'Program'), second row is the body of the table containing
# the information we want.
my $tb = $tree->look_down('_tag' => 'table', 'borderColor' => '#003366')
or return;
my ($hd,$bd) = $tb->content_list();
return unless $hd and $bd;
# --- sanity check the header row
unless ($hd->as_text =~ /Time .* Program/x) {
print "warning: table header missing for '$title' (pid = $pid)\n";
return;
}
# --- remove breaks and "revert" some markup to simplify parsing
$_->delete for $bd->look_down('_tag' => 'br');
$_->replace_with_content for $bd->look_down('_tag' => qr/^(b|font)$/);
# --- extract (text) content lists of the 2 row cells
my @td;
for ($bd->look_down('_tag' => 'table')) {
push @td, grep { not ref($_) } $_->content_list
for $_->look_down('_tag' => 'td');
}
unless (@td) {
print "warning: failed to fill in details for '$title' (pid=$pid)\n";
return
}
# --- clean fields up a little
s/(^\s+|\s+$)//g, s/\((.*?)\)/$1/g for @td;
# --- Here's an example of the contents of @td at this point:
# ("7:00 pm", "Southern Cross TEN Capital", "The All Time Greatest
# Simpsons", "- Cape Feare", "30 mins , Rated: PG", "Genre:",
# "Cartoon", "Sideshow Bob terrorises Bart after he is paroled
# from prison.", "" )
my ($start_time, $channel, $title1,
$title2, $genre, $desc) = @td[0,1,2,3,6,7];
my ($duration, $rating) = (split(/\s*,\s*/, $td[4]), "", "");
# --- is this a channel we know about? is it consistent with the guide?
$channel = $chanid{lc $channel}; # -- convert to xmltv channel id
unless ($channel eq $show->{channel}) {
print "channel mismatch for '$title' (pid = $pid)\n";
next
}
# --- now clean up a few things
$title2 =~ s/^\s*-\s*//;
$rating =~ s/Rated:\s*//;
$duration =~ s/mins/minutes/;
# --- compute start and stop times
my $start = ParseDate("$start_time $date");
# $start = DateCalc($start, "+ 1 day") if $start_time =~ /^0?[0-5]:.. am/;
my $stop = DateCalc($start, "+ $duration");
# --- append timezone and strip colons
($start .= " $conf->{TZ}") =~ tr/://d;
($stop .= " $conf->{TZ}") =~ tr/://d;
$show->{'title'} = [[$title1, $lang]];
$show->{'start'} = $start;
$show->{'stop'} = $stop;
$show->{'sub-title'} = [[$title2, $lang]] if $title2;
$show->{'desc'} = [[$desc, $lang]] if $desc;
$show->{'category'} = [[$genre, $lang]] if $genre;
}
# --- configure: query for region, services, and channels and write config
sub configure {
my $config_file = shift;
my $date = dmY("today");
my $firstpage;
XMLTV::Config_file::check_no_overwrite($config_file);
# --- extract user's ids for channels (if there were any in
# the config file), add the defaults then clear the channels hash
$chanid{lc $_} = $channels->{$_} for keys %$channels;
for (channel_mappings()) {
my ($name, $id) = / \s* (.+?) \s* : \s* (\S+) /x or next;
$chanid{lc $name} = $id unless $chanid{lc $name}; # use user's if defined
}
$channels = {};
$conf = {};
# --- get timezone
$conf->{TZ} = ask("Please enter your timezone offset (default '+1000') :");
$conf->{TZ} = '+1000' unless $conf->{TZ} =~ /^ \s* \+\d\d\d\d \s* $/x;
my @channellist;
# --- now find list of services - note that this appears to be invariant,
# so perhaps we should always offer the same list and skip the get?
{
my %servicenames = ( free => 1);
$firstpage = with_progress("getting list of services",
sub { get_page("$TVTODAY?channel=free") });
++$servicenames{$1} while $firstpage =~ /channel=(\w+)/g;
my @choices = sort keys %servicenames;
my @flag = ask_many_boolean
(0, map { "Grab listings for $_ channels" } @choices);
for (0..$#choices) {
next unless $flag[$_];
push @{$conf->{services}}, { name => $choices[$_] };
}
# --- now loop over services; find region/regionid and list of
# channels
for my $service (@{$conf->{services}}) {
my ($page, $base);
if ($service->{name} eq 'free') {
# --- get list of regions
my %region;
my $tree = HTML::TreeBuilder->new_from_content($firstpage);
for ($tree->look_down('_tag' => 'select', 'name' => 'region')) {
for ($_->look_down('_tag' => 'option')) {
$region{$_->as_text()} = $_->attr('value');
}
}
$tree->delete();
my @choices = sort keys %region;
$service->{region}
= ask_choice("Select your region for free channels",
$choices[0], @choices);
$service->{regionid} = $region{$service->{region}};
$page = with_progress
( "getting list of channels free service in " .
"$service->{region}", sub { get_guide_page($service, $date) } );
} else {
# --- find regionid for service
($page, $base) = with_progress
( "getting regionid and channels for service $service->{name}",
sub { get_content_base("$TVTODAY?channel=$service->{name}") } );
$service->{region} = "Australia";
($service->{regionid})
= $base =~ /_(\d+).asp/ or die "cannot find regionid";
# page now has channel list too
}
# --- now append channels for this service
my %skip;
# --- find the channels
my $tree = HTML::TreeBuilder->new_from_content($page);
for ($tree->look_down('_tag' => 'table', 'class' => 'tv',
'width' => '100%') # only one table of this type
->look_down('_tag' => 'tr') # ..first row has channels
->look_down('_tag' => 'b')) { # ..in bold tags
my $channel = $_->as_text;
push @channellist, $channel;
unless ($chanid{lc $channel}) { # check/define xmltvid
my $id = lc($channel);
$id =~ s/( ^\s+ | \s+$ | \W )//gx;
$id .= ".$service->{name}.au"; # e.g., "foxtel.au", "free.au"
print "Warning, unknown channel '$channel', using '$id' as id\n";
$chanid{lc $channel} = $id;
}
}
$tree->delete();
}
}
my @select = ask_many_boolean
(1, map { "subscribe to channel $_ -> $chanid{lc $_}" } @channellist);
for (0..$#channellist) {
next unless $select[$_];
my $name = $channellist[$_];
$channels->{$name} = $chanid{lc $name};
}
my @channels = sort keys %$channels;
# --- does the user want the slow option turned on by default?
$conf->{slow} =
ask_boolean("Show descriptions, ratings, genres and more accurate\n" .
"time information is available by downloading individual\n" .
"pages for each show, but this takes a lot longer\n\n" .
"Do you want this (--slow) option to be on by default?");
# --- report configuration and ask for confirmation
my $channel_count = @channels;
my $services_info;
for my $service (@{$conf->{services}}) {
$services_info .= "service: name=$service->{name}, " .
" region=$service->{region} (id=$service->{regionid})\n";
}
die "aborting configuration" unless
ask_boolean( "Please confirm the following configuration:\n" .
" TZ = $conf->{TZ}\n" .
" $services_info\n" .
" ($channel_count subscribed channels)\n\n" .
"[ use the '--list-channels' option for the\n" .
" xmltvids to use in mythtvsetup ]\n\n" .
" Continue?\n");
# --- open config file and write the configuration
-d dirname($config_file) or mkdir dirname($config_file)
or die "cannot create directory for $config_file: $!";
# --- dump as perl code using Data::Dumper
open(CONF, ">$config_file") or die "cannot write to $config_file: $!";
print CONF Data::Dumper->Dump([$conf, $channels], ["conf", "channels"]);
close CONF;
print "wrote config_file: $config_file\n";
}
# --- we can avoid redirections by spoofing random MSIDs in the URLs
use Digest::MD5 qw{md5_hex};
sub MSID { $spoofMSID ? "&MSID=" . md5_hex(rand) : "" }
# --- get NMSN guide for given date (perversely "ddmmYYYY")
sub get_guide_page {
my $service = shift;
my $date = shift;
my $url = "$GUIDE/${date}_$service->{regionid}.asp?"
. "channel=$service->{name}" . MSID();
print "GET_GUIDE_PAGE($url)\n" if $debug;
my $page = get_page($url) or
print "Warning: Failed to get program listing for day $date\n";
++$count_guide;
return $page;
}
# --- get NMSN program details for given pid
sub get_details_page {
my $pid = shift;
my $url = "$DETAILS?pid=$pid" . MSID();
print "GET_DETAILS_PAGE($url)\n" if $debug;
my $page = get_page($url)
or print "Warning: Failed to get program details for pid $pid\n";
++$count_detail;
return $page;
}
# --- get a page
sub get_page {
my $url = shift;
my $page = (get_content_base($url))[0];
$page =~ s/ / /g;
return $page;
}
# --- get a page and its base (and report all redirections if debugging)
# we attempt 5 times with 3 second pauses between failures.
my $ua;
sub get_content_base {
my $url = shift;
$ua or $ua = LWP::UserAgent->new, $ua->timeout(30);
my $response;
for (1..5) {
$response = $ua->get($url);
last if $response->is_success;
sleep 3;
}
die "error reading page $url in 5 attempts" unless $response->is_success;
if ($debug and (my $r = $response)->previous) {
print "GET_CONTENT_BASE redirection backtrace:\n";
while ($r) { print " ", $r->base, "\n"; $r = $r->previous }
}
return $response->content, $response->base;
}
# --- show a progress message during call to code (given by closure)
sub with_progress {
my ($message, $sub) = @_;
my $bar = new XMLTV::ProgressBar($message, 1);
my @results = $sub->();
$bar->update, short_pause(), $bar->finish;
return wantarray ? @results : $results[0];
}
sub short_pause { select(undef, undef, undef, 0.33) }
# ---------------------------------------------------------------------------
# --- misc/pedantic stuff...
sub fixplural { # hardly seems worth it sometimes... but, standards...
local $_ = shift;
s/(\d+) (\s+) (\w+)s (\s)/$1 . $2 . $3 . ($1==1?"":"s") . $4/xe;
$_
}
sub Ymd { UnixDate($_[0], "%Y%m%d") or die "problem in Ymd($_[0])" }
sub dmY { UnixDate($_[0], "%d%m%Y") or die "problem in dmY($_[0])" }
# ---------------------------------------------------------------------------
# --- here is default the channel list... comments welcome :-)
sub channel_mappings {
return grep ! /^#/, split "\n", qq{
# --- Free channels
ABC NSW : nsw.abc.gov.au
ABC QLD : qld.abc.gov.au
ABC TAS : tas.abc.gov.au
ABC ACT : act.abc.gov.au
ABC2 : abc2.abc.gov.au
Channel Seven Sydney : sydney.seven.com.au
Channel Seven Queensland : queensland.seven.com.au
Prime Southern : southern.prime.com.au
CHANNEL SEVEN BRISBANE : brisbane.seven.com.au
SBS Sydney : sydney.sbs.com.au
SBS Queensland : queensland.sbs.com.au
SBS News : news.sbs.com.au
SBS EASTERN : eastern.sbs.com.au
Network TEN Sydney : sydney.ten.com.au
NETWORK TEN BRISBANE : brisbane.ten.com.au
Southern Cross TEN Capital : capital.southerncrossten.com.au
Southern Cross TEN Queensland : queensland.southerncrossten.com.au
Channel Nine Sydney : sydney.nine.com.au
CHANNEL NINE BRISBANE METRO : brisbane.nine.com.au
WIN Television NSW : nsw.win.com.au
WIN Television QLD : qld.win.com.au
# --- Foxtel
Arena TV : arena.foxtel.com.au
BBC World : bbcworld.foxtel.com.au
Cartoon Network : cartoon.foxtel.com.au
Channel [V] : v.foxtel.com.au
CNBC : cnbc.foxtel.com.au
CNN : cnn.foxtel.com.au
Discovery Channel : discovery.foxtel.com.au
FOX News : foxnews.foxtel.com.au
FOX8 : fox8.foxtel.com.au
MAX : max.foxtel.com.au
National Geographic Channel : natgeo.foxtel.com.au
Nickelodeon : nickelodeon.foxtel.com.au
Showtime : showtime.foxtel.com.au
Showtime 2 : showtime2.foxtel.com.au
Sky News : skynews.foxtel.com.au
TV1 : tv1.foxtel.com.au
UKTV : uktv.foxtel.com.au
World Movies : worldmovies.foxtel.com.au
A1 : a1.foxtel.com.au
ACC : acc.foxtel.com.au
ADULTS ONLY : adultsonly.foxtel.com.au
ANIMAL PLANET : animalplanet.foxtel.com.au
ANTENNA PACIFIC : antennapacific.foxtel.com.au
ARENA+2 : arena2.foxtel.com.au
AURORA : aurora.foxtel.com.au
BLOOMBERG : bloomberg.foxtel.com.au
BOOMERANG : boomerang.foxtel.com.au
CLUB [V] : clubv.foxtel.com.au
CMC : cmc.foxtel.com.au
CRIME & INVESTIGATION NETWORK : crime.foxtel.com.au
DISCOVERY HEALTH : health.discovery.foxtel.com.au
DISCOVERY SCIENCE : science.discovery.foxtel.com.au
DISCOVERY TRAVEL & ADVENTURE : travel.discovery.foxtel.com.au
DISNEY CHANNEL : disney.foxtel.com.au
E! : e.foxtel.com.au
ESPN : espn.foxtel.com.au
EUROSPORT NEWS : eurosportnews.foxtel.com.au
FOOD : food.foxtel.com.au
FOX CLASSICS : classics.foxtel.com.au
FOX CLASSICS+2 : classics2.foxtel.com.au
FOX SPORTS 1 : sports1.foxtel.com.au
FOX SPORTS 2 : sports2.foxtel.com.au
FOX8+2 : fox82.foxtel.com.au
FTV : ftv.foxtel.com.au
FUEL : fuel.foxtel.com.au
HALLMARK CHANNEL : hallmark.foxtel.com.au
HOW TO : howto.foxtel.com.au
MAIN EVENT : mainevent.foxtel.com.au
MOVIE EXTRA : movieextra.foxtel.com.au
MOVIE GREATS : moviegreats.foxtel.com.au
MOVIE ONE : movieone.foxtel.com.au
MOVIE ONE TAKE 2 : movieonetake2.foxtel.com.au
MTV : mtv.foxtel.com.au
NICK JNR : nickjnr.foxtel.com.au
OVATION : ovation.foxtel.com.au
RAI INTERNATIONAL : rai.foxtel.com.au
SHOWTIME GREATS : showtimegreats.foxtel.com.au
SKY RACING : skyracing.foxtel.com.au
TCM : tcm.foxtel.com.au
THE BIOGRAPHY CHANNEL : biography.foxtel.com.au
THE COMEDY CHANNEL : comedy.foxtel.com.au
THE COMEDY CHANNEL+2 : comedy2.foxtel.com.au
THE HISTORY CHANNEL : history.foxtel.com.au
THE LIFESTYLE CHANNEL : lifestyle.foxtel.com.au
THE LIFESTYLE CHANNEL+2 : lifestyle2.foxtel.com.au
THE WEATHER CHANNEL : weather.foxtel.com.au
TV1+2 : tv12.foxtel.com.au
TVSN : tvsn.foxtel.com.au
UKTV+2 : uktv2.foxtel.com.au
VH1 : vh1.foxtel.com.au
W : w.foxtel.com.au
};
}
More information about the mythtv-users
mailing list