#!/usr/bin/perl use strict; use Audio::Wav; use Data::Dumper; use Getopt::Long; use IO::Socket; use IO::Select; use Pod::Usage; use Net::Telnet; use Time::HiRes qw(time); use URI::Escape; use Net::Google::Spreadsheets; my @cols = ( 'Fail', 'Playlist', 'Track', 'Type', 'Samplerate', 'SLength', 'RLength', 'Perf', 'RmsLevel', 'MaxDiff', 'TestDate', 'TestBy', ); my ($sc_addr, $playlist_name, $player_id); my ($google_username, $google_password); GetOptions('server=s' => \$sc_addr, 'playlist=s' => \$playlist_name, 'player=s' => \$player_id, 'username=s' => \$google_username, 'password=s' => \$google_password ) or pod2usage(); my $wav = new Audio::Wav; # pcm socket my $server = IO::Socket::INET->new( LocalPort => "9001", Type => SOCK_STREAM, Reuse => 1, Listen => 1, Timeout => 10) or die "Can't create pcm socket: $!\n"; my $select = IO::Select->new($server); # squeezecenter connection my $cli = cli_open($sc_addr); my $r; print("SqueezeCenter:\t$sc_addr\n"); # check player is connected $r = cli_request($cli, 'players', 0, 50); my $players = cli_args($r, 'playerindex'); my $player; foreach my $p (@$players) { if ($p->{playerid} eq $player_id && $p->{connected} == 1) { $player = $p; } } die "No player $player_id" unless $player; print("Player:\t\t" . $player->{name} . " ($player_id)\n"); # set player to a known state: cli_request($cli, $player_id, 'playlist', 'stop'); cli_request($cli, $player_id, 'playlist', 'repeat', 0); cli_request($cli, $player_id, 'playlist', 'shuffle', 0); # load worksheet my $worksheet = upload_player_worksheet($player); # find playlist $r = cli_request($cli, 'playlists', 0, 1, "search:$playlist_name", "tags:u"); my $playlist = cli_args($r, 'id'); $playlist = $playlist->[0]; die "No playlist $playlist_name" unless $playlist; print("Playlist:\t" . $playlist->{url} . " (" . $playlist->{id} . ")\n"); # find tracks $r = cli_request($cli, 'playlists', 'tracks', 0, 50, "playlist_id:" . $playlist->{id}, "tags:uodT"); my $tracks = cli_args($r, 'playlist index'); print("Tracks:"); foreach my $track (@$tracks) { print("\t\t" . $track->{title} . " (" . ($track->{duration} or '?') . " @ " . ($track->{samplerate} or '?') . ")\n"); } # capture pcm my $pcm; foreach my $track (@$tracks) { my $bytes = 0; print("\n"); print("Track:\t\t" . $track->{title} . "\n"); print("Url:\t\t". $track->{url}. "\n"); if (not $track->{samplerate}) { print("FAILED:\t", $track->{title}. " [no sample rate]\n"); upload_player_track($worksheet, $playlist, $track, { fail => "NO SAMPLERATE", }); next; } # play track/playlist cli_request($cli, $player_id, 'playlist', 'play', $track->{url}); my $client = $server->accept(); $select->add($client); if (not $client) { print("FAILED:\t", $track->{title}. " [timeout]\n"); upload_player_track($worksheet, $playlist, $track, { fail => "TIMEOUT", }); next; } my $t0 = time(); # open wav my $details = { 'bits_sample' => 32, 'sample_rate' => $track->{samplerate}, 'channels' => 2, }; my $filename = 'test-' . $track->{'playlist index'} . '.wav'; $pcm = $wav->write($filename, $details); print("Length:\t\t" . $track->{duration} ."\n"); print("Sample rate :\t" . $track->{samplerate} ."\n"); print("Store:\t\t" . $filename ."\n"); # record pcm while (1) { my $chunk; my @ready = $select->can_read(); if (scalar(@ready) == 0) { last; # timeout } my $err = $client->recv($chunk, 1024); unless (defined $err & length($chunk) > 0) { last; # eof } $pcm->write_raw($chunk); $bytes += length($chunk); } $select->remove($client); $client->close(); my $t1 = time(); # also stereo, 32-bit samples my $len = $bytes / 2 / 4 / $track->{samplerate}; my $perform = ($t1 - $t0)/ $track->{duration} * 100; print("Bytes:\t\t$bytes\n"); print("Length:\t\t$len\n"); print("Elapsed:\t", ($t1 - $t0). "\n"); # close wav $pcm->finish(); my @fail = (); if (abs($len - $track->{duration}) > 0.01) { push @fail, "LENGTH"; } if ($perform > 90) { push @fail, "PERF"; } upload_player_track($worksheet, $playlist, $track, { "fail" => join(", ", @fail), "slength" => $track->{duration}, "rlength" => $len, "perf" => $perform, }) } # playlist complete $server->close(); # stop player cli_request($cli, $player_id, 'playlist', 'stop'); # Open SqueezeCenter cli connection sub cli_open { my ($host) = @_; my $cli = new Net::Telnet( Timeout => 10, Prompt => '/^\s+$/', Host => $host, Port => 9090 ); $cli->open() || die "Can't open CLI"; return $cli; } # Send cli request sub cli_request { my $cli = shift; my $command = join(' ', map(uri_escape($_), @_)); $cli->print($command); my $line = $cli->getline(); my @elements = map(uri_unescape($_), split / /, $line); return \@elements; } # Parse extended cli response. $key is the item seperator sub cli_args { my ($elements, $key) = @_; my @values; my $block; foreach my $line (@$elements) { my ($k, $v) = ($line =~ /([^:]+):(.+)/); next unless $k; if ($k eq $key) { $block = {}; push @values, $block; } if (defined $block) { $block->{$k} = $v; } } return \@values; } # Finds or creates a worksheet for this player model on google docs sub upload_player_worksheet { my ($player) = @_; # XXXX my $service = Net::Google::Spreadsheets->new( username => $google_username, password => $google_password ); # XXXX my $spreadsheet = $service->spreadsheet({ title => "Codec QA" }); # XXXX my $worksheet = $spreadsheet->worksheet({ title => $player->{model} }); if (not $worksheet) { $worksheet = $spreadsheet->add_worksheet( { title => $player->{model}, row_count => 100, col_count => 20, }); # XXXX my $i = 1; my @cells = (); foreach my $col (@cols) { push @cells, { row => 1, col => $i++, input_value => $col }; } $worksheet->batchupdate_cell(@cells); } return $worksheet; } # Updates the test results for this track at google docs sub upload_player_track { my ($worksheet, $playlist, $track, $info) = @_; return if not $worksheet; $info->{testdate} = localtime(); $info->{testby} = $ENV{USERNAME}; $info->{state} = "" if not $info->{state}; my $playlist_name = $playlist->{playlist}; my $track_name = $track->{title}; # queries can't include non-alphanums $playlist_name =~ s/[^\w\d]/_/g; $track_name =~ s/[^\w\d]/_/g; # queries can't start with \d if ($track_name =~ /^\d/) { $track_name = '_' . $track_name; } $info->{playlist} = $playlist_name; $info->{track} = $track_name; $info->{type} = $track->{type}; $info->{samplerate} = $track->{samplerate}; # add - for cells without data foreach my $col (@cols) { $col = lc($col); next if $col eq 'fail'; $info->{$col} = '-' if not defined($info->{$col}); } my $row = $worksheet->row({sq => "playlist = " . $playlist_name . " and track = " . $track_name}); if ($row) { $row->content($info); } else { $worksheet->add_row($info); } } __END__ =head1 NAME codec_upload =head1 SYNOPSIS codec_upload [options] Options: --server squeezecenter address --player player mac address --playlist name of playlist for test --username google docs username --password google docs password Example: codec_upload.pl --server=192.168.1.199 --player=00:04:20:08:20:05 --username=user@slimdevices.com --password=passwd --playlist=profiles =cut