#!/usr/bin/perl use strict; use Tk; use Tk::Font; use Tk::BrowseEntry; use Getopt::Long qw(:config no_ignore_case bundling); use Video::Frequencies 0.03; use Video::ivtv 0.12; use Config::IniFiles; my $version="1.9"; # +--------------------------+ # | * tuner O comp. O svideo | # | ch: [ ] | # | | # | +---+ +---+ +---+ +---+ | # | | 1 | | 2 | | 3 | | ^ | | ch up # | +---+ +---+ +---+ +---+ | # | +---+ +---+ +---+ +---+ | # | | 4 | | 5 | | 6 | | v | | ch dn # | +---+ +---+ +---+ +---+ | # | +---+ +---+ +---+ +---+ | # | | 7 | | 8 | | 9 | | + | | fine up # | +---+ +---+ +---+ +---+ | # | +---+ +---+ +---+ +---+ | # | |100| | 0 | | | | - | | fine dn # | +---+ +---+ +---+ +---+ | # +--------------------------+ my $tvchan = "??"; my $state = 0; my $digits = 0; my $hundred = 0; my $prechan = 0; my $chan = -1; # state # 0 - ready for new input # 1 - inputing # fwd decl sub change_chan; sub change_input; my %settings = ( Channel => 73, # default to SCI-FI VideoStandard => "NTSC", VideoDevice => "/dev/video0", FrequencyTable => "ntsc-cable", # default to NTSC_CABLE mapping. ConfigFileName => "$ENV{HOME}/.ivtvrc", UseConfigFile => 0, UsingIvtvDriver => 1, # default to being able to use the ivtv "enhancements". # msp matrix settings SetMSPMatrix => 1, MSPInput => 3, MSPOutput => 1, MSPSleep => 2, # number of seconds the card needs before we can set the msp matrix. # other settings Debug => 0, TunerNum => 0, ); # map the Settings/Config file parameter to the command line variable that specifies it. my %mappings = ( "VideoDevice" => "d|input", "FrequencyTable" => "f|freqtable", "Debug" => "debug", "TunerNum" => "tuner-num", "SetMSPMatrix" => "set-msp-matrix", "MSPInput" => "msp-input", "MSPOutput" => "msp-output", "MSPSleep" => "msp-sleep", ); my %configIni; # config hash we tie to for Config::IniFiles. my $ivtvObj = Video::ivtv->new(); # check for devfs support if ( -e "/dev/.devfsd" ) { $settings{VideoDevice} = "/dev/v4l/video0"; } # check for the config file if (-f $settings{ConfigFileName}) { $settings{UseConfigFile} = 1; # tie to it. tie %configIni, 'Config::IniFiles', (-file => $settings{ConfigFileName}) or die "Error: Opening config file '$settings{ConfigFileName}' failed! $!\n"; my $profile = "defaults"; if (exists $configIni{$profile}) { # update the defaults stored. foreach my $arg (keys %mappings) { if (exists $configIni{$profile}{$arg}) { $settings{$arg} = $configIni{$profile}{$arg}; #print "settings{$arg} = '" . $settings{$arg} . "'\n"; } } } else { print "Warning: config file '$settings{ConfigFileName}' exists but does not have the\n[$profile] section! Use record_ivtv.pl to create it.\n\n"; } } else # create the config file { print "Auto Creating config file $settings{ConfigFileName}...\n"; my $profile = "defaults"; # we have to create the config file and tie to it. tie %configIni, 'Config::IniFiles', () or die "Error: Initializing config file '$settings{ConfigFileName}' failed! $!\n"; # now set the name to work with. tied(%configIni)->SetFileName($settings{ConfigFileName}) or die "Error: Setting config file to '$settings{ConfigFileName}' failed! $!\n"; $configIni{$profile} = {}; # make sure the section exists. foreach my $arg (keys %mappings) { $configIni{$profile}{$arg} = $settings{$arg}; print "configIni{$profile}{$arg} = '" . $settings{$arg} . "'\n" if $settings{Debug}; } # write the config file out. tied(%configIni)->RewriteConfig or die "Error: Writing config file '$settings{ConfigFileName}' failed! $!\n"; } my $versionStr = "ptune-ui.pl $version for use with http://ivtv.sf.net/"; my $usageStr = <<"END_OF_USAGE"; $versionStr Usage: ptune-ui.pl [--input VIDEO_DEV] [--freqtable FREQENCY MAP] [--set-msp-matrix BOOL] [--msp-sleep SLEEP] [--msp-input INPUT] [--msp-output OUTPUT] [--tuner-num TUNERNUM] [--help] [--version] [--debug] -d/--input VIDEO_DEV: video device to work with --tuner-num TUNERNUM: Specify the tuner to use. --set-msp-matrix BOOL: 1 - set the msp matrix after Video Standard changes 0 - never set the msp matrix Uses the --msp-input and --msp-output options. --msp-sleep SLEEP: number of seconds the card needs before we can program the msp matrix. --msp-input INPUT: Specify the input parameter to program the msp matrix. Valid values are from 1 - 8. --msp-output OUTPUT: Specify the output parameter to program the msp matrix. Valid values are from 0 - 3. -f/--freqtable FREQUENCY MAP: Specify the frequency mapping to use. -h/--help: display this help -v/--version: display the version of this program --debug: turns on debug output Defaults: --input $settings{VideoDevice} --freqtable $settings{FrequencyTable} --tuner-num $settings{TunerNum} --set-msp-matrix $settings{SetMSPMatrix} --msp-sleep $settings{MSPSleep} --msp-input $settings{MSPInput} --msp-output $settings{MSPOutput} config file = '$settings{ConfigFileName}' Note: This script relies on Perl Modules: Video::Frequencies, Video::ivtv, Config::IniFiles, Getopt::Long and Perl::Tk. END_OF_USAGE # handle user input here my %opts; GetOptions(\%opts, "help|h", "input|d=s", "version|v", "freqtable|f=s", "debug", "tuner-num=i", "set-msp-matrix=i", "msp-input=i", "msp-output=i"); foreach my $option (keys %opts) { my $found = 0; foreach my $mapName (keys %mappings) { if ($option =~ /^($mappings{$mapName})$/) { $settings{$mapName} = $opts{$option}; $found = 1; print "$mapName = '$opts{$option}'\n" if $settings{Debug}; } } if (!$found) { # handle the non-settings cases. if ($option =~ /^(v|version)$/) { print "$versionStr\n"; exit 0; } elsif ($option =~ /^(h|help)$/) { usage(0, ""); } else { usage(1, "-$option is an unknown option!"); } } } # verify input if ( ! -c "$settings{VideoDevice}") { usage(1, "Video Dev = '$settings{VideoDevice}' is invalid! $!"); } if ($settings{TunerNum} !~ /^(\d)$/) { error(1, "TunerNum = '$settings{TunerNum}' is invalid!"); } # build up the "custom" frequency table my %customMap = (); foreach my $profileName (keys %configIni) { next if $profileName =~ /^(defaults)$/; if (exists $configIni{$profileName}{Frequency}) { $customMap{$profileName} = $configIni{$profileName}{Frequency}; } } $CHANLIST{custom} = \%customMap; if (!exists $CHANLIST{$settings{FrequencyTable}}) { usage(1, "Frequency Table = '$settings{FrequencyTable}' is invalid!"); } # enumerations my @standards; my %name2std; my @inputs; my %name2input; # Current settings (Input, Channel, Standard) my $input = 0; my $curinput; my $frequency; my $std; my $curstd = "???"; my $curfreqtable = $settings{FrequencyTable}; my $curchannel = ""; my $curFreq = 0; my $tuner; my $err; my $v4l2input; my $tunerFD; # card probe flag if (1) { ## ## probe the card ## open($tuner, "<$settings{VideoDevice}") or die "unable to open: $!"; $tunerFD = fileno($tuner); my $i; $err=0; # Capabilities and Is it V4L2 compat? # get the current capabilities. my @capabilities = $ivtvObj->getCapabilities($tunerFD); if (@capabilities != keys %{$ivtvObj->{capIndexes}}) { error(1, "getCapabilities() failed!"); } if ($capabilities[$ivtvObj->{capIndexes}{driver}] ne "ivtv") { $settings{UsingIvtvDriver} = 0; # we can't use the ivtv "enhancements". print "Warning: V4l2 driver = '$capabilities[$ivtvObj->{capIndexes}{driver}]' does not support the ivtv \"enhancements\"!\n"; print " All codec related options will be ignored.\n\n"; } if ($settings{UsingIvtvDriver}) { if ($settings{SetMSPMatrix} !~ /^(0|1)$/) { error(1, "SetMSPMatrix = '$settings{SetMSPMatrix}' is invalid! It can only be 0 or 1."); } if ($settings{MSPInput} < 1 || $settings{MSPInput} > 8) { error(1, "MSPInput = '$settings{MSPInput}' is invalid! It can only be 1 - 8."); } if ($settings{MSPOutput} < 0 || $settings{MSPOutput} > 3) { error(1, "MSPOutput = '$settings{MSPOutput}' is invalid! It can only be 0 - 3."); } } # get the current video standard $std = $ivtvObj->getStandard($tunerFD); if ($std > 0) { printf("Standard: 0x%08x\n",$std) if ($settings{Debug}); } else { die "Error: getStandard() failed!\n"; } # get the current input $input = $ivtvObj->getInput($tunerFD); if ($input < 0) { die "Error: getInput() failed!\n"; } printf("Input: 0x%08x\n",$input) if ($settings{Debug}); my $done=0; # Standards for ($i=0; !$done; ++$i) { my($index,$std_id,$name,$frameperiod_n,$frameperiod_d,$framelines) = $ivtvObj->enumerateStandard($tunerFD, $i); if ($index == -1) { $done = 1; } else { printf("%d 0x%08x %s %d/%d %d\n",$index,$std_id,$name,$frameperiod_n,$frameperiod_d,$framelines) if ($settings{Debug}); push @standards, [($name,$std_id)]; $name2std{$name} = $std_id; if( (($std_id & $std) == $std)) { $curstd = $name; $settings{VideoStandar} = $name; } } } $done=0; # Inputs for ($i=0; !$done; ++$i) { my($index,$name,$type,$audioset,$tuner,$std,$status) = $ivtvObj->enumerateInput($tunerFD, $i); if ($index == -1) { $done = 1; } else { push @inputs, $name; $name2input{$name} = $index; } } $curinput = $inputs[$input]; # frequency's a little funny, you need to specify a valid tuner. WTF? } else { $input = 2; @inputs = ( "Composite 1", "Composite 2", "Tuner 1", "SVideo 1", "SVideo 2" ); } my $mw = MainWindow->new; my $i=0; my $j=0; my @frame; my @blab = ("1","2","3","^", "4","5","6","v", "7","8","9","+", "100","0"," ","-"); # radio, entry, keypad for $i (0..1) { $frame[$i] = $mw->Frame( -borderwidth => 0 ); $frame[$i]->pack( -fill => 'x' ); } $frame[2] = $mw->Frame( -borderwidth => 0 ); $frame[2]->pack( -fill => "both", -expand => 1 ); $i = 0; my @wid; # foreach my $label (("Tuner","Comp.","SVideo")) { # $wid[$i] = $frame[0]->Radiobutton( # -text => $label, # -variable => \$input, # -command => [\&change_input], # -value => $i ); # $wid[$i]->pack( -side => "left" ); # ++$i; # } my $label; # -state => "readonly", my $sentry = $frame[0]->BrowseEntry(-state => "readonly", -browsecmd => \&change_std, -variable => \$curstd); foreach $label (@standards) { $sentry->insert( "end", $label->[0] ); } $sentry->pack( -side => "left", -fill => "x", -expand => 1 ); my $bentry = $frame[0]->BrowseEntry(-state => "readonly", -browsecmd => \&change_input, -variable => \$curinput); foreach $label (@inputs) { $bentry->insert( "end", $label ); } $bentry->pack( -side => "left", -fill => "x", -expand => 1 ); my $fentry = $frame[1]->BrowseEntry(-state => "readonly", -browsecmd => \&change_freqtable, -variable => \$curfreqtable); foreach $label (sort keys %CHANLIST) { $fentry->insert( "end", $label ); } $fentry->pack( -side => "left", -fill => "x", -expand => 1 ); my $centry = $frame[1]->BrowseEntry(-state => "readonly", -browsecmd => \&change_channel, -variable => \$curchannel); foreach $label (sort { $a <=> $b } keys %{$CHANLIST{$curfreqtable}}) { $centry->insert( "end", $label ); } $centry->pack( -side => "left", -fill => "x", -expand => 1 ); my $efont = $mw->Font( -size => 24 ); my $entry = $frame[1]->Entry( -state => "disabled", -background => "white", -justify => "right", -font => \$efont, -textvariable => \$tvchan ); $entry->pack( -side => "left", -fill => "x", -expand => 1 ); # keypad frame $frame[3] = $frame[2]->Frame( -borderwidth => 0 ); $frame[3]->pack( -side=> "left", -fill => "both", -expand => 1 ); $j=3; for $i (4..7) { $frame[$i] = $frame[3]->Frame( -borderwidth => 0 ); for my $k (0..3) { $wid[$j] = $frame[$i]->Button( -text => $blab[$j-3], -command => [\&change_chan,$blab[$j-3]], -width => 1 ); $wid[$j]->pack( -side => "left", -fill => "both", -expand => 1 ); ++$j; } $frame[$i]->pack( -fill => "both", -expand => 1 ); } # enter button # $wid[3] = $frame[2]->Button( -text => "Enter" ); # $wid[3]->pack( -side => "left", -fill => 'y' ); MainLoop; sub do_chan { if($prechan) { if ($prechan =~ /^(\d+)$/) { $chan = $prechan + (100*$hundred); } else { $chan = $prechan; # string channel. } my $freq = $CHANLIST{$settings{FrequencyTable}}->{$chan}; if ($freq) { my $drfreq = ($freq * 16)/1000; $curFreq = $freq; if (!$ivtvObj->setFrequency($tunerFD, $settings{TunerNum}, $drfreq)) { die "Error: changeChannel($chan) failed!\n"; } print "Channel changed to $chan, freq = '$freq'\n"; } else { print "Channel $chan doesn't exist in $settings{FrequencyTable}!\n"; } } $tvchan = ($chan =~ /^(-1)$/)?"??":$chan; $prechan = 0; $hundred = 0; $state = 0; $digits = 0; } sub tuneFrequency { my $drfreq = ($curFreq * 16)/1000; if (!$ivtvObj->setFrequency($tunerFD, $settings{TunerNum}, $drfreq)) { die "Error: setFrequency($drfreq) failed!\n"; } print "Frequency tuned to '$curFreq'\n"; } sub change_chan { my ($key) = @_; print "channel input $key\n"; if( $key =~ /[ v^]/ ) { if( $key eq ' ' ) { $prechan = 0; } elsif( $key eq '^' ) { $prechan = $chan + 1 if ($chan =~ /^(\d+)$/); } elsif( $key eq 'v' ) { $prechan = $chan - 1 if ($chan =~ /^(\d+)$/); } do_chan(); } elsif( $key =~ /[\-+]/ ) { $curFreq += 1 if ($key eq "+"); $curFreq -= 1 if ($key eq "-" && $curFreq > 1); tuneFrequency(); } elsif( $key == 100 ) { ++$state; $hundred = 1; $tvchan = $prechan + 100*$hundred; } else { $prechan = ($prechan * 10) + $key; $tvchan = $prechan + 100*$hundred; ++$state; if(++$digits>1) { do_chan(); } } if($state == 1) { $entry->configure( -background => '#e0ffe0' ); } elsif ( $state == 0 ) { $entry->configure( -background => 'white' ); } } sub change_input { my $preinput = $name2input{$curinput}; if($preinput != $input) { $input = $preinput; print "input now $curinput, #$preinput\n"; my $result = $ivtvObj->setInput($tunerFD, $input); if (!$result) { die "Error: setInput($input) failed!\n"; } # see if we have to re-program the msp matrix if ($settings{UsingIvtvDriver}) { if ($settings{SetMSPMatrix}) { print "Setting msp matrix: input = $settings{MSPInput}, output = $settings{MSPOutput}\n" if $settings{Debug}; sleep ($settings{MSPSleep}); # sleep for 2 seconds to let card settle down. my $result = $ivtvObj->mspMatrixSet($tunerFD, $settings{MSPInput}, $settings{MSPOutput}); if (not defined $result) { die "Error calling mspMatrixSet!\n"; } if (!$result) { die "Error in mspMatrixSet ioctl call!\n"; } } } } } sub change_std { my $standard = $name2std{$curstd}; if($standard != $std) { $std = $standard; print "standard now $curstd, #$standard\n"; my $result = $ivtvObj->setStandard($tunerFD, $standard); if (!$result) { die "Error: setStandard($standard) failed!\n"; } # see if we have to re-program the msp matrix if ($settings{UsingIvtvDriver}) { if ($settings{SetMSPMatrix}) { print "Setting msp matrix: input = $settings{MSPInput}, output = $settings{MSPOutput}\n" if $settings{Debug}; sleep ($settings{MSPSleep}); # sleep for 2 seconds to let card settle down. my $result = $ivtvObj->mspMatrixSet($tunerFD, $settings{MSPInput}, $settings{MSPOutput}); if (not defined $result) { die "Error calling mspMatrixSet!\n"; } if (!$result) { die "Error in mspMatrixSet ioctl call!\n"; } } } } } sub change_freqtable { if ($curfreqtable ne $settings{FrequencyTable}) { $settings{FrequencyTable} = $curfreqtable; print "frequency table now $curfreqtable\n"; # flush the contents of the $centry drop-down combo # and re-populate with the new frequency table contents. $centry->delete(0, 'end'); foreach my $label (sort { $a <=> $b } keys %{$CHANLIST{$curfreqtable}}) { $centry->insert( "end", $label ); } } } # changes to the selected channel from the drop-down. sub change_channel { $prechan = $curchannel; do_chan(); } # usage(returnValue, ErrorString) # returnValue = 1 or 0 # ErrorString = message you want to tell the user, but only if returnValue = 1. sub usage { my $errorCode = shift; my $error = shift; print $usageStr; print "\nError: $error\n" if ($errorCode == 1); print "$error\n" if ($errorCode == 2); exit $errorCode; } __END__