App-DrivePlayer
view release on metacpan or search on metacpan
lib/App/DrivePlayer/GUI.pm view on Meta::CPAN
has _playlist => ( is => 'rw', isa => ArrayRef, default => sub { [] } );
has _track_by_id => ( is => 'rw', default => sub { {} } );
has _playing_row_ref => ( is => 'rw', default => sub { undef } );
has _playing_track_id => ( is => 'rw', default => sub { undef } );
has _progress_dragging => ( is => 'rw', isa => Bool, default => 0 );
# Widget accessors â set during _build_ui
has win => ( is => 'rw' );
has sidebar_store => ( is => 'rw' );
has sidebar_view => ( is => 'rw' );
has alpha_view => ( is => 'rw' );
has _alpha_category => ( is => 'rw', default => sub { 'Artists' } );
has track_store => ( is => 'rw' );
has _track_iter_map => ( is => 'rw', default => sub { {} } );
has track_view => ( is => 'rw' );
has track_count_label => ( is => 'rw' );
has now_playing_label => ( is => 'rw' );
has progress => ( is => 'rw' );
has time_label => ( is => 'rw' );
has dur_label => ( is => 'rw' );
has play_btn => ( is => 'rw' );
has prev_btn => ( is => 'rw' );
has stop_btn => ( is => 'rw' );
has next_btn => ( is => 'rw' );
has vol_scale => ( is => 'rw' );
has search_entry => ( is => 'rw' );
has statusbar => ( is => 'rw' );
has _status_ctx => ( is => 'rw' );
with qw(
App::DrivePlayer::GUI::MetadataFetch
App::DrivePlayer::GUI::SheetSync
App::DrivePlayer::GUI::FolderBrowse
);
sub _bearer_token {
my ($self) = @_;
return unless $self->rest_api;
my %h = @{ $self->rest_api->auth->headers() };
return $h{Authorization};
}
sub _build_db {
my ($self) = @_;
return App::DrivePlayer::DB->new(path => $self->config->db_path());
}
sub BUILD {
my ($self) = @_;
$self->_init_logging();
}
sub run {
my ($self) = @_;
my $db_is_new = !-f $self->config->db_path();
$self->_build_ui();
$self->_auto_sync_from_sheet_on_new_db() if $db_is_new;
$self->_prune_removed_folders();
$self->_load_library();
Glib::Timeout->add($POLL_INTERVAL_MS, sub {
$self->_player_poll();
return TRUE;
});
Gtk3->main();
$self->player->quit() if $self->player;
}
# ---- Initialisation ----
sub _init_logging {
my ($self) = @_;
$self->config->ensure_dirs();
my $level = $self->config->log_level();
my $file = $self->config->log_file() // '/tmp/drive_player.log';
my $log4perl_conf = "
log4perl.rootLogger=$level, Screen, File
log4perl.appender.Screen=Log::Log4perl::Appender::Screen
log4perl.appender.Screen.layout=Log::Log4perl::Layout::PatternLayout
log4perl.appender.Screen.layout.ConversionPattern=%d [%p] %m%n
log4perl.appender.File=Log::Log4perl::Appender::File
log4perl.appender.File.filename=$file
log4perl.appender.File.utf8=1
log4perl.appender.File.layout=Log::Log4perl::Layout::PatternLayout
log4perl.appender.File.layout.ConversionPattern=%d [%p] %m%n
";
if (eval { require Log::Log4perl; 1 }) {
Log::Log4perl->init(\$log4perl_conf);
binmode STDERR, ':encoding(UTF-8)';
}
}
sub _init_api {
my ($self) = @_;
return $self->rest_api if $self->rest_api;
my $auth_cfg = $self->config->auth_config();
unless ($auth_cfg->{client_id} && $auth_cfg->{client_secret}) {
$self->_show_error(
"Google API credentials not configured.\n\n" .
"Open File > Settings and enter your OAuth Client ID and Secret.\n\n" .
"You can obtain these from the Google Cloud Console under\n" .
"APIs & Services > Credentials (OAuth 2.0 Client ID, Desktop app type)."
);
return;
}
unless (-f ($auth_cfg->{token_file} // '')) {
$self->_show_error("OAuth token file not found: $auth_cfg->{token_file}\n\n" .
"Run the token creator from p5-google-restapi:\n" .
" bin/google_restapi_oauth_token_creator");
return;
}
my $api = eval { Google::RestApi->new(auth => $auth_cfg) };
if ($@) {
$self->_show_error("Failed to initialise Google API: $@");
return;
}
$self->rest_api($api);
lib/App/DrivePlayer/GUI.pm view on Meta::CPAN
$fp_frame->set_border_width(8);
my $fp_grid = Gtk3::Grid->new();
$fp_grid->set_row_spacing(8);
$fp_grid->set_column_spacing(8);
$fp_grid->set_border_width(8);
$fp_frame->add($fp_grid);
$vbox->pack_start($fp_frame, FALSE, FALSE, 0);
# fpcalc status row
my $fp_lbl = Gtk3::Label->new('fpcalc:');
$fp_lbl->set_xalign(1.0);
$fp_grid->attach($fp_lbl, 0, 0, 1, 1);
my $fp_status = Gtk3::Label->new();
$fp_status->set_xalign(0.0);
my $install_btn = Gtk3::Button->new_with_label('Installâ¦');
$install_btn->set_tooltip_text(
'Installs libchromaprint-tools via apt (requires administrator password)'
);
my $fp_hbox = Gtk3::Box->new('horizontal', 8);
$fp_hbox->pack_start($fp_status, FALSE, FALSE, 0);
$fp_hbox->pack_start($install_btn, FALSE, FALSE, 0);
$fp_grid->attach($fp_hbox, 1, 0, 1, 1);
# Helper: refresh the fpcalc status label
my $refresh_fp_status = sub {
if (App::DrivePlayer::MetadataFetcher::fpcalc_available()) {
$fp_status->set_markup('<span foreground="#2d862d"><b>Installed</b></span>');
$install_btn->hide();
}
else {
$fp_status->set_markup(
'<span foreground="#cc0000">Not installed</span>'
. ' <span size="small" foreground="#666666">'
. '(needed for fingerprint-based lookup)</span>'
);
$install_btn->show();
}
};
$refresh_fp_status->();
$install_btn->signal_connect(clicked => sub {
$install_btn->set_sensitive(FALSE);
$fp_status->set_markup('<span foreground="#666666">Installingâ¦</span>');
Gtk3::main_iteration_do(FALSE) while Gtk3::events_pending();
my $pid = fork();
if (!defined $pid) {
$fp_status->set_markup('<span foreground="#cc0000">Fork failed</span>');
$install_btn->set_sensitive(TRUE);
return;
}
if ($pid == 0) {
exec('pkexec', 'apt-get', 'install', '-y', 'libchromaprint-tools')
or POSIX::_exit(1);
}
# Poll every 500 ms until the child exits
Glib::Timeout->add(500, sub {
my $res = waitpid($pid, WNOHANG());
if ($res == $pid) {
$refresh_fp_status->();
$install_btn->set_sensitive(TRUE);
return FALSE; # remove timer
}
return TRUE; # keep polling
});
});
# AcoustID API key
my $aid_lbl = Gtk3::Label->new('AcoustID API Key:');
$aid_lbl->set_xalign(1.0);
$fp_grid->attach($aid_lbl, 0, 1, 1, 1);
my $aid_entry = Gtk3::Entry->new();
$aid_entry->set_hexpand(TRUE);
$aid_entry->set_text($self->config->acoustid_key());
$aid_entry->set_placeholder_text('Get a free key at acoustid.org');
$aid_entry->set_tooltip_text(
'Free AcoustID API key â used with fpcalc to look up missing tags '
. 'by acoustic fingerprint.'
);
$aid_lbl->set_tooltip_text($aid_entry->get_tooltip_text());
$fp_grid->attach($aid_entry, 1, 1, 1, 1);
$fp_lbl->set_tooltip_text(
'fpcalc (from chromaprint) generates audio fingerprints for '
. 'acoustic-ID lookup. Install it via apt if missing.'
);
my $aid_note = Gtk3::Label->new();
$aid_note->set_markup(
'<span size="small" foreground="#555555">'
. 'Register a free application at '
. '<a href="https://acoustid.org/new-application">acoustid.org</a> '
. 'to obtain a key. '
. '<a href="https://acoustid.org/">More info</a>.'
. '</span>'
);
$aid_note->set_xalign(0.0);
$aid_note->set_line_wrap(TRUE);
$aid_note->set_max_width_chars(60);
$fp_grid->attach($aid_note, 1, 2, 1, 1);
# ---- Config file path (informational) ----
my $info_grid = Gtk3::Grid->new();
$info_grid->set_row_spacing(4);
$info_grid->set_column_spacing(8);
$info_grid->set_border_width(8);
$vbox->pack_start($info_grid, FALSE, FALSE, 0);
my $cfg_key = Gtk3::Label->new('Config file:');
$cfg_key->set_xalign(1.0);
$info_grid->attach($cfg_key, 0, 0, 1, 1);
my $cfg_lbl = Gtk3::Label->new($self->config->config_file());
$cfg_lbl->set_xalign(0.0);
$cfg_lbl->set_selectable(TRUE);
$info_grid->attach($cfg_lbl, 1, 0, 1, 1);
( run in 1.688 second using v1.01-cache-2.11-cpan-39bf76dae61 )