diff --git a/.gitignore b/.gitignore index 66708ab8..f56bb8a2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ locale/ po/gmusicbrowser.pot nytprof* t/samples/ + +.vscode/ +TODO diff --git a/debian/compat b/debian/compat index 7ed6ff82..f599e28b 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -5 +10 diff --git a/debian/control b/debian/control index b0c7a1ac..d87f6a04 100644 --- a/debian/control +++ b/debian/control @@ -7,9 +7,10 @@ Build-Depends: debhelper (>= 5.0.0), markdown Standards-Version: 3.7.3 Package: gmusicbrowser +Version: 1.1.99.2 Architecture: all -Depends: perl, libgtk3-perl, libgtk-3-0 -Recommends: libintl-perl, libnet-dbus-perl, libglib-object-introspection-perl, gir1.2-gstreamer-1.0, libdigest-crc-perl, libhtml-parser-perl, libio-compress-perl +Depends: perl, libgtk3-perl, libgtk-3-0, libutf8-all-perl +Recommends: libintl-perl, libnet-dbus-perl, libglib-object-introspection-perl, gir1.2-gstreamer-1.0, libdigest-crc-perl, libhtml-parser-perl, libio-compress-perl, libtext-autoformat-perl, libxml-perl Suggests: mpv, mplayer, mpg123, vorbis-tools, alsa-utils, gir1.2-webkit2, gir1.2-notify, gir1.2-wnck, gir1.2-poppler Description: very customizable jukebox for large collections of music files The interface is extremely customizable. It has easy access to related songs diff --git a/gmusicbrowser.pl b/gmusicbrowser.pl index dbaf30d3..aa0b34fd 100755 --- a/gmusicbrowser.pl +++ b/gmusicbrowser.pl @@ -17,9 +17,7 @@ use strict; use warnings; -use utf8; -binmode STDERR,':utf8'; -binmode STDOUT,':utf8'; +use utf8::all; package main; @@ -528,18 +526,13 @@ BEGIN } # end of command line handling -our $HTTP_module; our ($Play_package,%PlayPacks); my ($PlayNext_package,$Vol_package); BEGIN{ require 'gmusicbrowser_songs.pm'; require 'gmusicbrowser_tags.pm'; require 'gmusicbrowser_layout.pm'; require 'gmusicbrowser_list.pm'; -$HTTP_module= -e $DATADIR.SLASH.'simple_http_wget.pm' && (grep -x $_.SLASH.'wget', split /:/, $ENV{PATH}) ? 'simple_http_wget.pm' : - -e $DATADIR.SLASH.'simple_http_AE.pm' && (grep -f $_.SLASH.'AnyEvent'.SLASH.'HTTP.pm', @INC) ? 'simple_http_AE.pm' : - 'simple_http.pm'; -#warn "using $HTTP_module for http requests\n"; -#require $HTTP_module; +require 'simple_http.pm'; # load gstreamer backend module if (!$CmdLine{nogst}) @@ -10306,22 +10299,16 @@ sub Start $self->Done; } else - { unless (eval {require $::HTTP_module}) {warn "Loading $::HTTP_module failed, can't download $display_uri\n"; $self->Done; return} + { warn "Downloading '$display_uri' to '$destpath'\n" if $::debug; ::Progress( $progressid, bartext_append=>$display_uri, title=>_"Downloading"); - $self->{waiting}= Simple_http::get_with_cb(url => $uri, cache=>1, progress=>1, cb => sub { $self->Downloaded(@_); }); - $self->{track_progress} ||= Glib::Timeout->add(200, - sub { if (my $w= $self->{waiting}) { my ($p,$s)=$w->progress; ::Progress( $progressid, partial=>$p ) if defined $p; } - else { $self->{track_progress}=0 } - return $self->{track_progress}; - }); + Simple_http::get_with_cb(url => $uri, cb => sub { $self->Downloaded(@_); }); return } } sub Downloaded { my ($self,$content,%content_prop)=@_; - delete $self->{waiting}; my $type=$content_prop{type}; my $params= $self->{current}; my $uri= $params->{uri}; @@ -10409,7 +10396,6 @@ sub Done # file done, if no $self->{newfile} it means the file has been skipped } sub Abort # GMB::DropURI object must not be used after that { my $self=shift; - $self->{waiting}->abort if $self->{waiting}; Glib::Source->remove( $self->{track_progress} ) if $self->{track_progress}; ::Progress( 'DropURI_'.$self, abort=>1 ); $self->{cb_end}() if $self->{cb_end}; diff --git a/gmusicbrowser_songs.pm b/gmusicbrowser_songs.pm index 3d84a476..f1f856d2 100644 --- a/gmusicbrowser_songs.pm +++ b/gmusicbrowser_songs.pm @@ -7,7 +7,7 @@ use strict; use warnings; -use utf8; +use utf8::all; package Songs; @@ -1151,6 +1151,24 @@ our %timespan_menu= editsubmenu=>0, category=>'extra', }, +# https://github.com/carl-di-ortus/gmusicbrowser/issues/3 +# https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html +# musicbrainz_trackid => +# { name => _"MBID", +# id3v2 => 'TXXX;MusicBrainz Release Track Id;%v', vorbis => 'musicbrainz_releasetrackid', ape => 'MUSICBRAINZ_RELEASETRACKID', ilst => "----:com.apple.iTunes:MusicBrainz Release Track Id", 'id3v2.3' => 'TXXX;MusicBrainz Release Track Id;%v', +# flags => 'fgaescpi', +# type => 'string', +# category=>'extra', +# postread=> sub { my $v=shift; warn "V $v"; }, +# }, +# acoustid => +# { name => _"AcoustID", +# id3v2 => 'TXXX;Acoustid Id;%v', vorbis => 'ACOUSTID_ID', ape => 'ACOUSTID_ID', ilst => "----:com.apple.iTunes:Acoustid Id", +# flags => 'fgaescpi', +# type => 'string', +# category=>'extra', +# postread=> sub { my $v=shift }, +# }, style => { name => _"Styles", width => 180, flags => 'fgaescpil', type => 'flags', diff --git a/gmusicbrowser_tags.pm b/gmusicbrowser_tags.pm index 15ef608c..69ea1910 100644 --- a/gmusicbrowser_tags.pm +++ b/gmusicbrowser_tags.pm @@ -19,7 +19,7 @@ BEGIN } use strict; use warnings; -use utf8; +use utf8::all; package FileTag; diff --git a/plugins/albuminfo.pm b/plugins/albuminfo.pm index ff3cac45..2b05594d 100644 --- a/plugins/albuminfo.pm +++ b/plugins/albuminfo.pm @@ -20,8 +20,8 @@ desc Retrieves album-relevant information (review etc.) from allmusic.com. package GMB::Plugin::ALBUMINFO; use strict; use warnings; -use utf8; -require $::HTTP_module; +use utf8::all; +require 'simple_http.pm'; use base 'Gtk3::Box'; use constant { OPT => 'PLUGIN_ALBUMINFO_', @@ -153,7 +153,7 @@ sub download_one { } else { my $url = AMG_SEARCH_URL.::url_escapeall($album); warn "Albuminfo: fetching search results from $url\n" if $::debug; - $self->{waiting} = Simple_http::get_with_cb(url=>$url, cache=>1, cb=>sub {$self->load_search_results($ID,1,\&download_one,@_)}); + Simple_http::get_with_cb(url=>$url, cb=>sub {$self->load_search_results($ID,1,\&download_one,@_)}); } } @@ -512,12 +512,11 @@ sub new_search { $self->cancel(); $self->{treeview}->get_model->clear; warn "Albuminfo: fetching search results from $url.\n" if $::debug; - $self->{waiting} = Simple_http::get_with_cb(cb=>sub {$self->print_results(@_)},url=>$url, cache=>1); + Simple_http::get_with_cb(cb=>sub {$self->print_results(@_)},url=>$url); } sub print_results { my ($self,$html,%prop) = @_; - delete $self->{waiting}; my $result = parse_amg_search_results($html, $prop{type}); # result is a ref to an array of hash refs my $store= $self->{treeview}->get_model; $store->set_sort_column_id(5, 'ascending'); @@ -534,8 +533,8 @@ sub entry_selected_cb { my $url = $store->get($store->get_iter($path),4); warn "Albuminfo: fetching review from $url\n" if $::debug; $self->cancel(); - $self->{waiting} = Simple_http::get_with_cb(cb=>sub {$self->{searchview}->hide(); $self->{infoview}->show(); - $self->load_review(::GetSelID($self),0,undef,$url,@_)}, url=>$url, cache=>1); + Simple_http::get_with_cb(cb=>sub {$self->{searchview}->hide(); $self->{infoview}->show(); + $self->load_review(::GetSelID($self),0,undef,$url,@_)}, url=>$url); } @@ -580,7 +579,7 @@ sub album_changed { } } warn "Albuminfo: fetching search results from $url\n" if $::debug; - $self->{waiting} = Simple_http::get_with_cb(cb=>sub {$self->load_search_results($ID,0,undef,@_)}, url=>$url, cache=>1); + Simple_http::get_with_cb(cb=>sub {$self->load_search_results($ID,0,undef,@_)}, url=>$url); } sub update_titlebox { @@ -600,7 +599,6 @@ sub update_titlebox { sub load_search_results { my ($self,$ID,$md,$cb,$html,%prop) = @_; # $md = 1 if mass_download, 0 otherwise. $cb = callback function if mass_download, undef otherwise. - delete $self->{waiting}; my $result = parse_amg_search_results($html, $prop{type}); # $result[$i] = {url, album, artist, genres, year} my ($artist,$year) = ::Songs::Get($ID, qw/artist year/); my $url; @@ -616,7 +614,7 @@ sub load_search_results { } if ($url) { warn "Albuminfo: fetching review from $url\n" if $::debug; - $self->{waiting} = Simple_http::get_with_cb(cb=>sub {$self->load_review($ID,$md,$cb,$url,@_)}, url=>$url, cache=>1); + Simple_http::get_with_cb(cb=>sub {$self->load_review($ID,$md,$cb,$url,@_)}, url=>$url); } else { $self->{fields} = {}; warn "Albuminfo: album not found in search results\n" if $::debug; @@ -627,7 +625,6 @@ sub load_search_results { sub load_review { my ($self,$ID,$md,$cb,$url,$html,%prop) = @_; - delete $self->{waiting}; $self->{fields} = parse_amg_album_page($url,$html,$prop{type}); $self->print_review() unless $md; save_review($ID, $self->{fields}) if $::Options{OPT.'AutoSave'} || $md; @@ -804,7 +801,6 @@ sub cancel { my $self = shift; delete $::ToDo{'9_load_albuminfo'.$self}; delete $::ToDo{'9_refresh_albuminfo'.$self}; - $self->{waiting}->abort() if $self->{waiting}; ::Progress('albuminfo', abort=>1); $self->{abort}=1; } diff --git a/plugins/artistinfo.pm b/plugins/artistinfo.pm index 2fbda13b..d86b8c1c 100644 --- a/plugins/artistinfo.pm +++ b/plugins/artistinfo.pm @@ -18,8 +18,8 @@ url http://gmusicbrowser.org/dokuwiki/doku.php?id=plugins:artistinfo package GMB::Plugin::ARTISTINFO; use strict; use warnings; -use utf8; -require $::HTTP_module; +use utf8::all; +require 'simple_http.pm'; use base 'Gtk3::Box'; use constant { OPT => 'PLUGIN_ARTISTINFO_', # MUST begin by PLUGIN_ followed by the plugin ID / package name @@ -29,7 +29,7 @@ use constant my %sites = ( biography => ['http://ws.audioscrobbler.com/2.0/?method=artist.getinfo&artist=%a&api_key=7aa688c2466dc17263847da16f297835&autocorrect=1',_"Biography",_"Show artist's biography"], - events => ['http://ws.audioscrobbler.com/2.0/?method=artist.getevents&artist=%a&api_key=7aa688c2466dc17263847da16f297835&autocorrect=1',_"Events",_"Show artist's upcoming events"], + #events => ['http://ws.audioscrobbler.com/2.0/?method=artist.getevents&artist=%a&api_key=7aa688c2466dc17263847da16f297835&autocorrect=1',_"Events",_"Show artist's upcoming events"], similar => ['http://ws.audioscrobbler.com/2.0/?method=artist.getsimilar&artist=%a&api_key=7aa688c2466dc17263847da16f297835&autocorrect=1&limit=%l',_"Similar",_"Show similar artists"]); my @External= @@ -49,7 +49,6 @@ my %menuitem= test => sub {$_[0]{mainfield} eq 'artist'}, #the menu item is displayed if returns true ); my $nowplayingaID; -my $queuewaiting; my %queuemode= ( order=>10, icon=>'gtk-refresh', short=> _"similar-artists", long=> _"Auto-fill queue with similar artists (from last.fm)", changed=>\&QAutofillSimilarArtists, keep=>1,save=>1,autofill=>1, ); @@ -97,7 +96,6 @@ sub Stop { Layout::RegisterWidget(PluginArtistinfo => undef); @::cMenuAA= grep $_!=\%menuitem, @::SongCMenu; delete $::QActions{'autofill-similar-artists'}; ::Update_QueueActionList(); - $queuewaiting->abort if $queuewaiting; $queuewaiting=undef; } sub new @@ -271,7 +269,6 @@ sub destroy_event_cb sub cancel { my $self=shift; delete $::ToDo{'8_artistinfo'.$self}; - $self->{waiting}->abort if $self->{waiting}; } sub prefbox @@ -506,25 +503,18 @@ sub load_url warn "info : loading $url\n" if $::debug; $self->{url}=$url; $self->{sw2}->hide; $self->{sw1}->show; - $self->{waiting}=Simple_http::get_with_cb(cb => sub {$self->loaded(@_)},url => $url, cache => 1); + Simple_http::get_with_cb(cb => sub {$self->loaded(@_)},url => $url); } sub loaded { my ($self,$data,%prop)=@_; - delete $self->{waiting}; my $buffer=$self->{buffer}; my $type=$prop{type}; unless ($data) { $data=_("Loading failed.").qq( )._("retry").''; $type="text/html"; } $self->{url}=$prop{url} if $prop{url}; #for redirections $buffer->delete($buffer->get_bounds); - my $encoding; - if ($type && $type=~m#^text/.*; ?charset=([\w-]+)#) {$encoding=$1} - if ($data=~m/xml version/) { $encoding='utf-8'; } - $encoding=$1 if $data=~m#get_start_iter; my $fontsize = $self->{fontsize}; @@ -669,7 +659,7 @@ sub Save_text } sub QAutofillSimilarArtists -{ $queuewaiting->abort if $queuewaiting; $queuewaiting=undef; +{ return unless $::QueueAction eq 'autofill-similar-artists'; return if $::Options{MaxAutoFill}<=@$::Queue; return unless $::SongID; @@ -679,11 +669,11 @@ sub QAutofillSimilarArtists my $url = GetUrl($sites{similar}[0],$nowplayingaID); return unless $url; - $queuewaiting=Simple_http::get_with_cb(url => $url, cb => \&PopulateQueue ); + Simple_http::get_with_cb(url => $url, cb => \&PopulateQueue ); } sub PopulateQueue -{ $queuewaiting=undef; +{ if ( $nowplayingaID != Songs::Get_gid($::SongID,'artist')) { QAutofillSimilarArtists; return; } my $data = $_[0]; diff --git a/plugins/audioscrobbler.pm b/plugins/audioscrobbler.pm index 3e634dec..e6564241 100644 --- a/plugins/audioscrobbler.pm +++ b/plugins/audioscrobbler.pm @@ -21,13 +21,13 @@ use constant SAVEFILE => 'audioscrobbler.queue', #file used to save unsent data }; use Digest::MD5 'md5_hex'; -require $::HTTP_module; +require 'simple_http.pm'; our $ignore_current_song; my $self=bless {},__PACKAGE__; my @ToSubmit; my $NowPlaying; my $NowPlayingID; my $unsent_saved=0; -my $interval=5; my ($timeout,$waiting); +my $interval=5; my $timeout; my ($HandshakeOK,$submiturl,$nowplayingurl,$sessionid); my ($Serrors,$Stop); my $Log= Gtk3::ListStore->new('Glib::String'); @@ -43,9 +43,9 @@ sub Start $Serrors=$Stop=undef; } sub Stop -{ $waiting->abort if $waiting; +{ Glib::Source->remove($timeout) if $timeout; - $timeout=$waiting=undef; + $timeout=undef; ::UnWatch($self,$_) for qw/PlayingSong Played Save/; $self->{on}=undef; $interval=5; @@ -86,10 +86,10 @@ sub prefbox sub update_queue_label { my $qbox=shift; my $label= $qbox->{label}; - if (@ToSubmit && (@ToSubmit>1 || (!$waiting && (!$timeout || $interval>10)))) + if (@ToSubmit && (@ToSubmit>1 || (!$timeout || $interval>10))) { $label->set_text(::__n("%d song waiting to be sent","%d songs waiting to be sent", scalar @ToSubmit )); $label->get_parent->show; - $qbox->{button}->set_sensitive(!$waiting); + $qbox->{button}->set_sensitive(); } else { $label->get_parent->hide } } @@ -236,14 +236,14 @@ sub Sleep { #warn "Sleep\n"; return unless $self->{on}; ::QHasChanged('Lastfm_state_change'); - return if $Stop || $waiting || $timeout; + return if $Stop || $timeout; $timeout=Glib::Timeout->add(1000*$interval,\&Awake) if @ToSubmit || $NowPlaying; #warn "Sleeping $interval seconds\n" if $timeout; } sub Awake { #warn "Awoke\n"; $timeout=undef; - return 0 if !$self->{on} || $waiting; + return 0 if !$self->{on}; if ($HandshakeOK) { Submit(); } else { Handshake(); } Sleep(); @@ -253,11 +253,10 @@ sub Send { my ($response_cb,$url,$post)=@_; my $cb=sub { my @response=(defined $_[0])? split "\012",$_[0] : (); - $waiting=undef; &$response_cb(@response); Sleep(); }; - $waiting=Simple_http::get_with_cb(cb => $cb,url => $url,post => $post); + Simple_http::post_with_cb(cb => $cb,url => $url,post => $post); ::QHasChanged('Lastfm_state_change'); } diff --git a/plugins/fetch_cover.pm b/plugins/fetch_cover.pm index 78c243bf..5cfd98fb 100644 --- a/plugins/fetch_cover.pm +++ b/plugins/fetch_cover.pm @@ -14,7 +14,7 @@ desc Adds a menu entry to artist/album context menu, allowing to search the pict package GMB::Plugin::FETCHCOVER; use strict; use warnings; -require $::HTTP_module; +require 'simple_http.pm'; use base 'Gtk3::Window'; use constant { OPT => 'PLUGIN_FETCHCOVER_', @@ -205,11 +205,7 @@ sub NewSearch $self->{url}= $url; $self->{searchcontext}={}; #hash that the parser can use to store data between searches warn "fetchcover : loading $url\n" if $::debug; - $self->{waiting}=Simple_http::get_with_cb - ( cb => sub {$self->searchresults_cb(@_)}, - url => $url, cache=>1, - user_agent => $self->{user_agent}, - ); + Simple_http::get_with_cb(cb => sub {$self->searchresults_cb(@_)}, url => $url); } sub InitPage @@ -410,7 +406,6 @@ sub parse_discogs sub searchresults_cb { my ($self,$result)=@_; - $self->{waiting}=undef; warn "Getting results from $self->{url}\n" if $::Verbose; unless (defined $result) { stop($self,_"connection failed."); return; } my $parse= $Sites{$self->{mainfield}}{$self->{site}}[2]; @@ -429,10 +424,7 @@ sub abort my $results=$self->{results}; for my $r ($self,@$results) { delete $r->{done}; - $r->{waiting}->abort if $r->{waiting}; - delete $r->{waiting}; } - delete $self->{waiting}; delete $::ToDo{'8_FetchCovers'.$self}; } @@ -453,41 +445,33 @@ sub get_next { my $self=shift; my $results=$self->{results}; my $res_id; - my $waiting; my $start= $self->{page} * RES_PER_PAGE; my $end= $start + RES_PER_PAGE -1; if ($#$results<$end && $self->{nexturl}) { #load next page my $url= $self->{url}= delete $self->{nexturl}; - $self->{waiting}=Simple_http::get_with_cb - ( cb => sub {$self->searchresults_cb(@_)}, - url => $url, cache=>1, - user_agent => $self->{user_agent}, - ); + Simple_http::get_with_cb(cb => sub {$self->searchresults_cb(@_)}, url => $url); } elsif ($#$results>=$end) { $self->{Bnext}->set_sensitive(1); } $end=$#$results if $#$results<$end; for my $id ($start .. $end) - { #warn "$id : waiting=".$results->[$id]{waiting}." done=".$results->[$id]{done}; - if ($results->[$id]{waiting}) {$waiting++; next}; + { next if $results->[$id]{done}; $res_id=$id; last; } - unless (defined $res_id || $waiting || $self->{waiting}) + unless (defined $res_id) { $self->stop; return; } return unless defined $res_id; - return if $waiting && $waiting > 3; #no more than 4 pictures at once my $result=$self->{results}[$res_id]; - $result->{waiting}=Simple_http::get_with_cb(url => $result->{url}, referer=>$result->{referer}, cache=>1, cb => + Simple_http::get_with_cb(url => $result->{url}, referer=>$result->{referer}, cb => sub { my $pixdata=$_[0]; - $result->{waiting}=undef; my $loader; $loader= GMB::Picture::LoadPixData($pixdata,PREVIEW_SIZE) if $pixdata; if ($loader) diff --git a/plugins/listenbrainz.pm b/plugins/listenbrainz.pm new file mode 100644 index 00000000..f3e04107 --- /dev/null +++ b/plugins/listenbrainz.pm @@ -0,0 +1,229 @@ +# Copyright (C) 2024 Carl di Ortus +# +# This file is part of Gmusicbrowser. +# Gmusicbrowser is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation + +=for gmbplugin LISTENBRAINZ +name listenbrainz +title listenbrainz.org plugin +desc Submit played songs to listenbrainz +=cut + + +package GMB::Plugin::LISTENBRAINZ; +use strict; +use warnings; +use JSON; +use List::Util qw(max); +use constant +{ CLIENTID => 'gmb', VERSION => '0.1', + OPT => 'PLUGIN_LISTENBRAINZ_', #used to identify the plugin's options + SAVEFILE => 'listenbrainz.queue', #file used to save unsent data +}; +require 'simple_http.pm'; + +our $ignore_current_song; + +my $self=bless {},__PACKAGE__; +my @ToSubmit; my @NowPlaying; my $NowPlayingID; +my $interval=10; my ($timeout); +my ($Stop); +my $Log= Gtk3::ListStore->new('Glib::String'); + +sub Start +{ ::Watch($self,PlayingSong=> \&SongChanged); + ::Watch($self,Played => \&Played); + $self->{on}=1; + Sleep(); + SongChanged() if $::TogPlay; + $Stop=undef; +} +sub Stop +{ + @NowPlaying=undef; + Glib::Source->remove($timeout) if $timeout; + $timeout=undef; + ::UnWatch($self,$_) for qw/PlayingSong Played/; + $self->{on}=undef; + $interval=10; +} + +sub prefbox +{ my $vbox= Gtk3::VBox->new(::FALSE, 2); + my $sg1= Gtk3::SizeGroup->new('horizontal'); + my $sg2= Gtk3::SizeGroup->new('horizontal'); + my $entry2=::NewPrefEntry(OPT.'TOKEN',_"Token :", cb => \&Stop, sizeg1 => $sg1,sizeg2=>$sg2, hide => 1); + my $label2= Gtk3::Button->new(_"(see https://listenbrainz.org)"); + $label2->set_relief('none'); + $label2->signal_connect(clicked => sub + { my $url='https://listenbrainz.org'; + my $user=$::Options{OPT.'USER'}; + $url.="/user/$user/" if defined $user && $user ne ''; + ::openurl($url); + }); + my $ignore= Gtk3::CheckButton->new(_"Don't submit current song"); + $ignore->signal_connect(toggled=>sub { return if $_[0]->{busy}; $ignore_current_song= $_[0]->get_active ? $::SongID : undef; ::HasChanged('Listenbrainz_ignore_current'); }); + ::Watch($ignore,Listenbrainz_ignore_current => sub { $_[0]->{busy}=1; $_[0]->set_active(defined $ignore_current_song); delete $_[0]->{busy}; } ); + my $queue= Gtk3::Label->new; + my $sendnow= Gtk3::Button->new(_"Send now"); + $sendnow->signal_connect(clicked=> \&SendNow); + my $qbox= ::Hpack($queue,$sendnow); + $vbox->pack_start($_,::FALSE,::FALSE,0) for $label2,$entry2,$ignore,$qbox; + $vbox->add( ::LogView($Log) ); + $qbox->{label}=$queue; + $qbox->{button}=$sendnow; + $qbox->show_all; + return $vbox; +} + +sub SongChanged +{ + @NowPlaying=undef; + if (defined $ignore_current_song) + { return if defined $::SongID && $::SongID == $ignore_current_song; + $ignore_current_song=undef; ::HasChanged('Listenbrainz_ignore_current'); + } + my ($title,$artist,$album)= Songs::Get($::SongID,qw/title artist album/); + return if $title eq '' || $artist eq ''; + @NowPlaying= ( $artist, $title, $album ); + $NowPlayingID=$::SongID; + $interval=10; + SendNow(); +} + +sub Played +{ my (undef,$ID,undef,$start_time,$seconds,$coverage)=@_; + return if $ignore_current_song; + return unless $seconds>10; + my $length= Songs::Get($ID,'length'); + if ($length>=30 && ($seconds >= 240 || $coverage >= .5) ) + { my ($title,$artist,$album)= Songs::Get($ID,qw/title artist album/); + return if $title eq '' || $artist eq ''; + @ToSubmit= ( $artist, $title, $album ); + $interval=10; + Sleep(); + ::QHasChanged('Listenbrainz_state_change'); + } +} + +sub Submit +{ + my $i=0; + my $url= 'https://api.listenbrainz.org/1/submit-listens'; + my $listen_type; + my $listened_at; + my @payload; + if (@ToSubmit) + { @payload= @ToSubmit; + $listen_type= "single"; + $listened_at= time(); + } + elsif (@NowPlaying) + { if (!defined $::PlayingID || $::PlayingID!=$NowPlayingID) { @NowPlaying=undef; return } + @payload= @NowPlaying; + $listen_type= "playing_now"; + $listened_at= undef; + } + else { return; } + my $post= { + listen_type => $listen_type, + payload => [ + { + track_metadata => { + artist_name => $payload[0], + track_name => $payload[1] + } + } + ] + }; + $post->{payload}[0]->{listened_at} = $listened_at if $listened_at; + $post->{payload}[0]->{track_metadata}->{release_name} = $payload[2] if $payload[2]; + my $response_cb=sub + { + my ($response,$error)=@_; + + if ($error) + { Log(_("Submit failed : ").$error); + Log(_("Response : ").$response) if $response; + $interval*=2; + $interval=max($interval, 300); + return; + } + + unlink $::HomeDir.SAVEFILE; + if (@ToSubmit) { + Log( _("Submit OK ") . + ::__x( _"{song} by {artist}", song=> $payload[1], artist => $payload[0]) ); + undef @ToSubmit; + $interval=10; + return + }; + if (@NowPlaying) { + Log( _("NowPlaying OK ") . + ::__x( _"{song} by {artist}", song=> $payload[1], artist => $payload[0]) ); + $interval=60; + return + }; + }; + + my $authtoken=$::Options{OPT.'TOKEN'}; + Save($post); + Send($response_cb,$url,$::HomeDir.SAVEFILE,$authtoken); +} + +sub SendNow +{ $interval=10; + $Stop=undef; + Awake(); +} + +sub Sleep +{ return unless $self->{on}; + ::QHasChanged('Listenbrainz_state_change'); + return if $Stop || $timeout; + $timeout=Glib::Timeout->add(1000*$interval,\&Awake) if @ToSubmit || @NowPlaying; +} + +sub Awake +{ Glib::Source->remove($timeout) if $timeout; + $timeout=undef; + return 0 if !$self->{on}; + Submit(); + Sleep(); + return 0; +} + +sub Send +{ my ($response_cb,$url,$post,$authtoken)=@_; + my $cb=sub + { my @response=(defined $_[0])? split "\012",$_[0] : (); + &$response_cb(@response); + Sleep(); + }; + + Simple_http::post_with_cb(cb => $cb,url => $url,post => $post,authtoken => $authtoken); + ::QHasChanged('Listenbrainz_state_change'); +} + +sub Log +{ my $text=$_[0]; + $Log->set( $Log->prepend,0, localtime().' '.$text ); + warn "$text\n" if $::debug; + if (my $iter=$Log->iter_nth_child(undef,50)) { $Log->remove($iter); } +} + +sub Save +{ my $savebody=$_[0]; + unless ($savebody) + { unlink $::HomeDir.SAVEFILE; return } + my $fh; + unless (open $fh,'>:utf8',$::HomeDir.SAVEFILE) + { warn "Error creating '$::HomeDir".SAVEFILE."' : $!\nUnsent listenbrainz.org data will be lost.\n"; return; } + my $json=(to_json $savebody); + print $fh $json; + close $fh; +} + +1; diff --git a/plugins/lyrics.pm b/plugins/lyrics.pm index 4cf862c1..b90ad883 100644 --- a/plugins/lyrics.pm +++ b/plugins/lyrics.pm @@ -14,16 +14,19 @@ desc Search and display lyrics package GMB::Plugin::LYRICS; use strict; use warnings; -use utf8; -require $::HTTP_module; +use utf8::all; +require 'simple_http.pm'; our @ISA; BEGIN {push @ISA,'GMB::Context';} use base 'Gtk3::Box'; +use Text::Autoformat; +use HTML::TreeBuilder; use constant { OPT => 'PLUGIN_LYRICS_', # MUST begin by PLUGIN_ followed by the plugin ID / package name }; my $notfound=_"No lyrics found"; +my $instrumental=_"Instrumental"; my @justification= ( left => _"Left aligned", @@ -48,54 +51,51 @@ my @ContextMenuAppend= }, ); -my %Sites= # id => [name,url,?post?,function] if the function return 1 => lyrics can be saved -( #lyrc => ['lyrc','http://lyrc.com.ar/en/tema1en.php','artist=%a&songname=%t'], - #lyrc => ['lyrc','http://lyrc.com.ar/en/tema1en.php?artist=%a&songname=%t',undef,sub - # { local $_=$_[0]; - # return -1 if m#]+add[^>]+>(?:[^<]*#i; - # return 1 if s#]+badsong[^>]+>BADSONG##i; - # return 0; - # }], - #leoslyrics => ['leolyrics','http://api.leoslyrics.com/api_search.php?artist=%a&songtitle=%t'], - #google => ['google','http://www.google.com/search?q="%a"+"%t"'], - lyriki => ['lyriki','http://lyriki.com/index.php?title=%a:%t',undef, - sub { my $no= $_[0]=~m/
/s; $_[0]=~s/^.*(.*?).*$/$1/s && !$no; }], - #lyricsplugin => [lyricsplugin => 'http://www.lyricsplugin.com/winamp03/plugin/?title=%t&artist=%a',undef, - # sub { my $ok=$_[0]=~m#
.*\w\n.*\w.*
#s; $_[0]=~s/
['lyrics-songs',sub { ::ReplaceFields($_[0], "http://letras.terra.com.br/winamp.php?musica=%t&artista=%a", sub {::url_escapeall(::superlc($_[0]));}) },undef, -# sub { my $is_suggestion= $_[0]=~m#

Provável música

#i; -# my $l=html_extract($_[0],div=>'letra'); -# $l=~s#
.*?
##s if $l; #remove header with title and artist links -# my $ref=\$_[0]; -# $$ref= $l ? $l : $notfound; -# return $l && !$is_suggestion; -# }], -# lyricwiki => [lyricwiki => 'http://lyrics.wikia.com/%a:%t',undef, -# sub { return 0,'http://lyrics.wikia.com/'.$1 if $_[0]=~m#.*?((?:&\#\d+;|
|){5,}).*!$1!s; #keep only the "lyric box" -# return 0 if $_[0]=~m![...](?:
)*!; # truncated lyrics : "[...]" followed by italic explanation => not auto-saved -# return !!$1; -# }], - musixmatch => [musixmatch => sub { ::ReplaceFields($_[0], "http://www.musixmatch.com/lyrics/%a/%t", sub { my $s=::url_escapeall($_[0]); $s=~s/%20/-/g; $s }) }, undef, - sub - { $_[0] =~ s/[\r\n]/
/g; - my $l=""; - $l=join "
", ($_[0] =~ m/(.+?)<\/span>/g); - if ($l) - { $_[0]=$l; - return 1; - } - else - { # FIXME try searching with "http://www.musixmatch.com/search/%a %t" and take best result ? - # FIXME or try searching again after cleaning title and artist for things like "(live)" ? - $_[0] = $notfound; - return 0; - } - }], - #lyricwikiapi => [lyricwiki => 'http://lyricwiki.org/api.php?artist=%a&song=%t&fmt=html',undef, - # sub { $_[0]!~m#
\W*Not found\W*
#s }], - #azlyrics => [ azlyrics => 'http://search.azlyrics.com/cgi-bin/azseek.cgi?q="%a"+"%t"'], - #Lyricsfly ? +my %Sites= # id => [name,url,?,function] if the function return 1 => lyrics can be saved +( # http://lyrc.com.ar/en/tema1en.php + # http://lyrc.com.ar/en/tema1en.php?artist=%a&songname=%t + # http://api.leoslyrics.com/api_search.php?artist=%a&songtitle=%t + # http://www.google.com/search?q="%a"+"%t" + lyriki => [ + lyriki => sub { ::ReplaceFields($_[0], "https://lyriki.com/%a:%t", + sub { my $s=::url_escapeall($_[0]); $s = autoformat($s, { case => "title" }); $s=~s/%20/_/g; $s=~s/\n//g; $s }) }, + #'https://lyriki.com/%a:%t', + sub { my $no= $_[0]=~m/
/s; + $_[0]=~s/^.*(.*?).*$/$1/s && !$no; }], + # http://www.lyricsplugin.com/winamp03/plugin/?title=%t&artist=%a + # http://letras.terra.com.br/winamp.php?musica=%t&artista=%a + # http://lyrics.wikia.com/%a:%t + musixmatch => [ + musixmatch => sub { ::ReplaceFields($_[0], "https://www.musixmatch.com/lyrics/%a/%t", sub { my $s=::url_escapeall($_[0]); $s=~s/%20/-/g; $s }) }, + sub + { + if (!$_[0]) { + $_[0] = $notfound; return 0; + } + eval { + my $dom_tree = HTML::TreeBuilder->new_from_content($_[0]); + my @content = $dom_tree->look_down(_tag => "h2"); + my $start_content = @content[2]; + my $parent = $start_content->parent(); + my $l = $parent->as_HTML; + if (index($l, "Still no lyrics here. Be the first to add them.") != -1) { + $_[0] = $notfound; return 0; + } + $l =~ s/<(\w+) [^>]*>/<$1>/g; + $l =~ s/(
)+/
/g; + $l =~ s/(<\/div>)+/<\/div>/g; + $l =~ s/
<\/div>//g; + $l =~ s/
.*//g; + $l =~ s/(

)/$1/g; + #warn $l; + $_[0] = $l; return 1; + } + or do { + $_[0] = $notfound; return 0; + } + }], + # http://lyricwiki.org/api.php?artist=%a&song=%t&fmt=html + # http://search.azlyrics.com/cgi-bin/azseek.cgi?q="%a"+"%t" AUTO => [_"Auto",], #special mode that search multiple sources ); @@ -209,8 +209,7 @@ sub destroy_event_cb sub cancel { my $self=shift; delete $::ToDo{'8_lyrics'.$self}; - $self->{waiting}->abort if $self->{waiting}; - $self->{waiting}=$self->{pixtoload}=undef; + $self->{pixtoload}=undef; } sub prefbox @@ -347,15 +346,15 @@ sub load_from_web } } return unless $site; - my (undef,$url,$post,$check)=@{$Sites{$site}}; + my (undef,$url,$check)=@{$Sites{$site}}; my $ID= $self->{ID}; - for my $val ($url,$post) + for my $val ($url) { next unless defined $val; if (ref $val) { $val= $val->($ID); } else { $val= ::ReplaceFields($ID, $val, \&::url_escapeall); } } return unless $url; - ::IdleDo('8_lyrics'.$self,1000,\&load_url,$self,$url,$post,$check); + ::IdleDo('8_lyrics'.$self,1000,\&load_url,$self,$url,$check); } sub TimeChanged #scroll the text @@ -419,26 +418,21 @@ sub html_extract } sub load_url -{ my ($self,$url,$post,$check)=@_; +{ my ($self,$url,$check)=@_; $self->Set_message(_"Loading..."); $self->cancel; - warn "lyrics : loading $url\n";# if $::debug; + warn "lyrics : loading $url\n"; $self->{url}=$url; - $self->{post}=$post; $self->{check}=$check; # function to check if lyrics found - $self->{waiting}=Simple_http::get_with_cb(cb => sub {$self->loaded(@_)},url => $url,post => $post); + Simple_http::get_with_cb(cb => sub {$self->loaded(@_)},url => $url); } sub loaded #_very_ crude html to gtktextview renderer { my ($self,$data,%data_prop)=@_; - delete $self->{waiting}; my $type=$data_prop{type}; my $buffer=$self->{buffer}; - my $encoding; - unless ($data) { $data=_("Loading failed.").qq( )._("retry").''; $type="text/html"; $encoding=0; } $self->{url}=$data_prop{url} if $data_prop{url}; #for redirections $buffer->delete($buffer->get_bounds); - if ($type && $type=~m#^text/.*; ?charset=([\w-]+)#) {$encoding=$1} if ($type && $type!~m#^text/html#) { if ($type=~m#^text/#) {$buffer->set_text($data);} elsif ($type=~m#^image/#) @@ -447,10 +441,6 @@ sub loaded #_very_ crude html to gtktextview renderer } return; } - $encoding=$1 if $data=~m#{check}) @@ -466,6 +456,11 @@ sub loaded #_very_ crude html to gtktextview renderer } $self->{lastokurl}=$self->{url}; + if (index($data, "This music is instrumental. Let the music play") != -1) { + warn "found instrumental"; + $self->Set_message($instrumental); return; + } + for ($data) {s///gs; s###gsi; #added to remove warnings from lyrc.com.ar, maybe should be restricted to lyrc.com.ar ? @@ -578,9 +573,9 @@ sub load_pixbuf my $ref=shift @{ $self->{pixtoload} }; return 0 unless $ref; my ($mark,$url,$link)=@$ref; - $self->{waiting}=Simple_http::get_with_cb(url => $self->full_url($url), cache=>1, cb=> + Simple_http::get_with_cb(url => $self->full_url($url), cb=> sub - { $self->{waiting}=undef; + { my $loader; $loader= GMB::Picture::LoadPixData($_[0]) if $_[0]; if ($loader) @@ -597,36 +592,8 @@ sub load_pixbuf } ::IdleDo('8_FetchPix'.$self,100,\&load_pixbuf,$self); #load next }); -#::IdleDo('8_FetchPix'.$self,100,\&load_pixbuf,$self) unless $self->{waiting}; -} - -#sub loaded_old #old method, more crude :) -#{ my $self=shift; -# my $buffer=$self->{buffer}; -# unless ($_[0]) {$buffer->delete($buffer->get_bounds);$buffer->set_text(_"Loading failed.");return;} -# local $_=$_[0]; -# s/[\r\n]//g; -# s#.*?##; -# s#