Skip to content

Instantly share code, notes, and snippets.

@aboettger
Created March 1, 2011 17:19
Show Gist options
  • Save aboettger/849499 to your computer and use it in GitHub Desktop.
Save aboettger/849499 to your computer and use it in GitHub Desktop.
Jamendo support added
# Copyright (C) 2005-2008 Quentin Sculo <[email protected]>
#
# 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
=gmbplugin FETCHCOVER
name Picture finder
title Picture finder plugin
desc Adds a menu entry to artist/album context menu, allowing to search the picture/cover in google and save it.
=cut
package GMB::Plugin::FETCHCOVER;
use strict;
use warnings;
require $::HTTP_module;
use base 'Gtk2::Window';
use constant
{ OPT => 'PLUGIN_FETCHCOVER_',
RES_LINES => 4,
RES_PER_LINE => 5,
PREVIEW_SIZE => 100,
};
use constant RES_PER_PAGE => RES_PER_LINE*RES_LINES;
my %Sites=
(artist =>
{ googlei => ['google images',"http://images.google.com/images?q=%s&imgsz=medium|large", \&parse_googlei],
lastfm => ['last.fm',"http://www.last.fm/music/%a/+images", \&parse_lastfm],
jamendo => ['Jamendo', "http://api.jamendo.com/get2/image/artist/plain/?name=%a&n=all", \&parse_jamendo],
},
album =>
{ googlei => [_"google images","http://images.google.com/images?q=%s&imgsz=medium|large&imgar=ns", \&parse_googlei],
googleihi =>[_"google images (hi-res)","http://www.google.com/images?q=%s&imgsz=xlarge|xxlarge&imgar=ns", \&parse_googlei],
slothradio => ['slothradio', "http://www.slothradio.com/covers/?artist=%a&album=%l", \&parse_sloth],
jamendo => ['Jamendo', "http://api.jamendo.com/get2/image/album/plain/?artist_name=%a&n=all&imagesize=600", \&parse_jamendo],
#itunesgrabber => ['itunesgrabber',"http://www.thejosher.net/iTunes/index.php?artist=%a&album=%l", \&parse_itunesgrabber],
freecovers => ['freecovers.net', "http://www.freecovers.net/api/search/%s", \&parse_freecovers], #could add /Music+CD but then we'd lose /Soundtrack
#rateyourmusic=> ['rateyourmusic.com', "http://rateyourmusic.com/search?searchterm=%s&searchtype=l",\&parse_rateyourmusic], # urls results in "403 Forbidden"
},
);
my %menuitem=
( label => _"Search for a picture on internet", #label of the menu item
code => sub { Fetch($_[0]{mainfield},$_[0]{gid},$_[0]{ID}); }, #when menu item selected
test => sub {$_[0]{mainfield} eq 'album' || $_[0]{mainfield} eq 'artist'}, #the menu item is displayed if returns true
);
my %fpane_menuitem=
( label=> _"Search for a picture on internet",
code => sub { Fetch($_[0]{field},$_[0]{gidlist}[0]); },
onlyone=> 'gidlist', #menu item is hidden if more than one album/artist is selected
istrue => 'aa', #menu item is hidden for non artist/album (aa) FPanes
);
::SetDefaultOptions(OPT, USEFILE => 1, COVERFILE => 'cover', PictureSite_artist => 'googlei', PictureSite_album => 'googlei');
sub Start
{ push @::cMenuAA,\%menuitem;
push @FilterPane::cMenu, \%fpane_menuitem;
}
sub Stop
{ @::cMenuAA= grep $_!=\%menuitem, @::SongCMenu;
@FilterPane::cMenu= grep $_!=\%fpane_menuitem, @FilterPane::cMenu;
}
sub prefbox
{ my $check1=::NewPrefCheckButton(OPT.'ASK',_"Ask confirmation only if file already exists");
my $check2=::NewPrefCheckButton(OPT.'UNIQUE',_"Find a unique filename if file already exists");
my $entry1=::NewPrefEntry(OPT.'COVERPATH');
my $entry2=::NewPrefEntry(OPT.'COVERFILE');
my ($radio1a,$radio1b)=::NewPrefRadio(OPT.'USEPATH',undef,_"use song folder",0,_"use :",1);
my ($radio2a,$radio2b)=::NewPrefRadio(OPT.'USEFILE',undef,_"use album name",0,_"use :",1);
my $frame1=Gtk2::Frame->new(_"default folder");
my $frame2=Gtk2::Frame->new(_"default filename");
my $vbox1=::Vpack( $radio1a,[$radio1b,$entry1] );
my $vbox2=::Vpack( $radio2a,[$radio2b,$entry2] );
$frame1->add($vbox1);
$frame2->add($vbox2);
return ::Vpack( $frame1,$frame2,$check1,$check2 );
}
sub Fetch
{ my ($field,$gid,$ID)=@_;
unless (defined $ID)
{ my $list= AA::GetIDs($field,$gid);
$ID=$list->[0];
return unless defined $ID;
}
my $mainfield=Songs::MainField($field); #'artist' or 'album'
my $self=bless Gtk2::Window->new;
$self->set_border_width(4);
my $Bsearch=::NewIconButton('gtk-find',_"Search");
my $Bcur=Gtk2::Button->new($mainfield eq 'artist' ? _"Search for current artist" : _"Search for current album");
::set_drag($Bcur, dest => [::DRAG_ID, sub { $_[0]->get_toplevel->SearchID($_[2]); }], );
my $Bclose=Gtk2::Button->new_from_stock('gtk-close');
my @entry;
push @entry, $self->{"searchentry_$_"}=Gtk2::Entry->new for qw/s a l/;
$self->{searchentry_s}->set_tooltip_text(_"Keywords");
$self->{searchentry_a}->set_tooltip_text(_"Artist");
$self->{searchentry_l}->set_tooltip_text(_"Album");
my $source=::NewPrefCombo( OPT.'PictureSite_'.$mainfield, {map {$_=>$Sites{$mainfield}{$_}[0]} keys %{$Sites{$mainfield}}} , cb => \&combo_changed_cb);
#$self->{Bnext}= my $Bnext=::NewIconButton('gtk-go-forward',"More");
$self->{Bnext}= my $Bnext=Gtk2::Button->new(_"More results");
$self->{Bstop}= my $Bstop=Gtk2::Button->new_from_stock('gtk-stop');
$self->{progress}= my $pbar =Gtk2::ProgressBar->new;
$self->{table}= my $table=Gtk2::Table->new(RES_LINES,RES_PER_LINE,::TRUE);
$self->add( ::Vpack
( [map( {('_',$_)} @entry), $Bsearch, $Bstop, $source],
'_',$table,
'-', ['_',$pbar , '-', $Bclose,$Bnext,$Bcur]
) );
for (@entry)
{ $_->signal_connect( activate => \&NewSearch );
$_->show_all;
$_->set_no_show_all(1);
}
$self->show_all;
$Bsearch->signal_connect( clicked => \&NewSearch );
$Bstop->signal_connect( clicked => sub {$_[0]->get_toplevel->stop });
$Bclose->signal_connect(clicked => sub {$_[0]->get_toplevel->destroy});
$Bnext->signal_connect( clicked => sub {$_[0]->get_toplevel->NextPage});
$Bcur->signal_connect(clicked =>sub {$_[0]->get_toplevel->SearchID($::SongID)});
$self->signal_connect( destroy => \&abort);
$self->signal_connect( unrealize => sub {$::Options{OPT.'winsize'}=join ' ',$_[0]->get_size; });
my $size= $::Options{OPT.'winsize'} || RES_PER_LINE*PREVIEW_SIZE.' '.RES_LINES*PREVIEW_SIZE;
$self->resize(split ' ',$size,2);
$self->{mainfield}=$mainfield;
$self->{field}=$field;
$self->{site}=$::Options{OPT.'PictureSite_'.$mainfield};
$self->SearchID($ID);
$self->UpdateSite;
}
sub combo_changed_cb
{ my $self=$_[0]->get_toplevel;
$self->{site}=$::Options{OPT.'PictureSite_'.$self->{mainfield}};
$self->UpdateSite;
$self->NewSearch;
}
sub UpdateSite
{ my $self=$_[0];
my $url=$Sites{$self->{mainfield}}{$self->{site}}[1];
for my $l (qw/s a l/)
{ my $entry=$self->{"searchentry_$l"};
if ($url=~m/\%$l/) {$entry->show}
else {$entry->hide}
}
}
sub SearchID
{ my ($self,$ID)=@_;
$self=::find_ancestor($_[0],__PACKAGE__);
$self->{gid}= Songs::Get_gid($ID,$self->{field});
$self->{dir}= Songs::Get($ID,'path');
my $search=my $name= Songs::Get($ID,$self->{field});
$search="\"$search\"" unless $search eq '';
my $albumname='';
my $artistname='';
if ($self->{mainfield} eq 'album')
{ $albumname=$name;
$artistname= Songs::Get($ID,'album_artist');
$search.=" \"$artistname\"" unless $search eq '' || $artistname eq '';
}
else { $artistname=$name }
$self->set_title(_("Searching for a picture of : ").$name);
$self->{searchentry_s}->set_text($search);
$self->{searchentry_a}->set_text($artistname);
$self->{searchentry_l}->set_text($albumname);
$self->NewSearch;
}
sub NewSearch
{ my $self=::find_ancestor($_[0],__PACKAGE__);
my $url=$Sites{$self->{mainfield}}{$self->{site}}[1];
my %letter;
for my $l (qw/s a l/)
{ next unless $url=~m/\%$l/;
my $s= $self->{"searchentry_$l"}->get_text;
$s=~s/^\s+//; $s=~s/\s+$//;
return if $s eq '';
$letter{$l}=::url_escapeall($s);
}
$self->abort;
$self->{results}=[];
$self->{page}=0;
$self->InitPage;
$url=~s/%([sal])/$letter{$1}/g;
warn "fetchcover : loading $url\n" if $::debug;
$self->{waiting}=Simple_http::get_with_cb
( cb => sub {$self->searchresults_cb(@_)},
url => $url, cache=>1
);
}
sub InitPage
{ my $self=$_[0];
$self->abort;
$self->{loaded}=0;
$self->{Bnext}->set_sensitive(0);
$self->{Bstop}->set_sensitive(1);
$self->{progress}->set_fraction(0);
$self->{progress}->show;
my $table=$self->{table};
$table->remove($_) for $table->get_children;
}
sub PrevPage
{ my $self=$_[0];
return unless $self->{page};
$self->{page}--;
$self->InitPage;
::IdleDo('8_FetchCovers'.$self,100,\&get_next,$self);
}
sub NextPage
{ my $self=$_[0];
$self->{page}++;
$self->InitPage;
::IdleDo('8_FetchCovers'.$self,100,\&get_next,$self);
}
sub parse_rateyourmusic
{ my $result=$_[0];
my @list;
while ($result=~m#a title="\[Album(\d+)\]"#g)
{ push @list,
{ url=> "http://static.rateyourmusic.com/album_images/$1.jpg",
previewurl => "http://static.rateyourmusic.com/album_images/s$1.jpg",
};
}
return \@list;
}
sub parse_freecovers #FIXME could use a XML module #can provide backcover and more too
{ my $result=$_[0];
my @list;
while ($result=~m#<title>(.+?)</title>#gs)
{ my $res=$1;
my %res;
$res{desc}= ::decode_html(Encode::decode('cp1252',$1)) if $res=~m#<name>([^<]+)</name>#; #FIXME not sure of the encoding
while ($res=~m#<cover>(.+?)</cover>#gs)
{ my $res=$1;
next unless $res=~m#<type>front</type>#;
$res{url}=$1 if $res=~m#<preview>([^<]+)</preview>#;
$res{previewurl}=$1 if $res=~m#<thumbnail>([^<]+)</thumbnail>#;
last;
}
push @list, \%res if $res{url};
}
return \@list;
}
sub parse_lastfm
{ my $result=$_[0];
my @list;
while ($result=~m#<a href="/music/[^/]+/\+images/\d+"[^>]+?class="pic".+?<img [^>]+?src="([^"]+)"#gs)
{ my $url=my $pre=$1;
$url=~s#/\w+/(\d+.jpg)$#/_/$1#; ### /126b/123456.jpg -> /_/123456.jpg
push @list, {url => $url, previewurl =>$pre,};
}
my $nexturl;
$nexturl='http://www.lastfm.com'.$1 if $result=~m#<a href="([^"]+)" class="nextlink">#;
return \@list,$nexturl;
}
sub parse_itunesgrabber
{ my $result=$_[0];
my @list;
push @list,{url=>$1} if $result=~m#Album art found.*?<a href="([^"]+)"#;
return \@list;
}
sub parse_sloth
{ my $result=$_[0];
my @list;
while ($result=~m#<div class="album\d+"><img src="([^"]+)"#g)
{ push @list, {url => $1};
}
my $nexturl;
$nexturl='http://www.slothradio.com/covers/'.$1 if $result=~m#<div class="pages">[^<>]*(?:\s*<a href="[^"]+">\d+</a>)*\s*\d\s+<a href="([^"]+)">\d+</a>#;
return \@list,$nexturl;
}
sub parse_googlei
{ my $result=$_[0];
my @list;
if ($result=~m#dyn.setResults\([^)](.*)\)#) #parse google image results #assumes no unencoded ')' in the array of results
{ my @matches=split /\],\["/,$1; #not very reliable
for my $m (@matches)
{ my @fields=split /["\]],["\[]/,$m;
next if @fields<3; #happens if no results
my $url=$fields[3];
my $desc= $fields[6]||''; $desc=~s#\\x([0-9a-f]{2})#chr(hex $1)#gie; $desc=~s#</?b>##g;
$desc=Encode::decode('cp1252',$desc); #FIXME not sure of the encoding
$desc=::decode_html($desc);
my $preview='http://images.google.com/images?q=tbn:'.$fields[2].$url;
push @list, {url => $url, previewurl =>$preview, desc => $desc };
}
}
my $nexturl;
if ($result=~m#<a href="(/images\?[^>"]*)"( [^>]*)?class=pn\b#)
{ $nexturl='http://images.google.com'.$1;
$nexturl=~s#&amp;#&#g;
}
return \@list,$nexturl;
}
sub parse_jamendo
{ my $result=$_[0];
my @list;
#print $result."\n";
while ($result=~m#(.+imgjam.com/[albums|artists].+)#g)
{ push @list, {url => $1};
}
return \@list;
}
sub searchresults_cb
{ my ($self,$result)=@_;
$self->{waiting}=undef;
unless (defined $result) { stop($self,_"connection failed."); return; }
my $parse= $Sites{$self->{mainfield}}{$self->{site}}[2];
my ($list,$nexturl)=$parse->($result);
$self->{nexturl}=$nexturl;
#$table->set_size_request(110*5,110*int(1+@list/5));
push @{$self->{results}}, @$list;
my $more= @{$self->{results}} - ($self->{page}+1) * RES_PER_PAGE;
$self->{Bnext}->set_sensitive( $more>0 || $nexturl );
unless (@{$self->{results}}) { stop($self,_"no matches found, you might want to remove some search terms."); return; }
::IdleDo('8_FetchCovers'.$self,100,\&get_next,$self);
}
sub abort
{ my $self=$_[0];
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};
}
sub stop
{ my ($self,$error)=@_;
$self->abort;
$self->{Bstop}->set_sensitive(0);
#$self->{progress}->set_fraction(1);
$self->{progress}->hide;
if ($error)
{ my $l=Gtk2::Label->new($error);
$l->show;
$self->{table}->attach($l,0,5,0,1,'fill','fill',1,1);
}
}
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
$self->{waiting}=Simple_http::get_with_cb(cb => sub {$self->searchresults_cb(@_)}, url => delete $self->{nexturl}, cache=>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})
{ $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}, cache=>1, cb =>
sub
{ my $pixdata=$_[0];
$result->{waiting}=undef;
my $loader;
$loader= GMB::Picture::LoadPixData($pixdata,PREVIEW_SIZE) if $pixdata;
if ($loader)
{ my $dim=$loader->{w}.' x '.$loader->{h};
my $table=$self->{table};
my $pixbuf=$loader->get_pixbuf;
my $image=Gtk2::Image->new_from_pixbuf($pixbuf);
my $button=Gtk2::Button->new;
$button->{pixdata}=$pixdata;
$button->{ext}= ($Gtk2::VERSION >= 1.092)?
$loader->get_format->{extensions}[0]
: ( EntryCover::_identify_pictype($pixdata) )[0];
$button->{ext}='jpg' if $button->{ext} eq 'jpeg';
my $vbox=Gtk2::VBox->new(0,0);
my $label=Gtk2::Label->new($dim);
$vbox->add($image);
$vbox->pack_end($label,0,0,0);
$button->add($vbox);
my $tip='';
$tip=$result->{desc}."\n" if $result->{desc};
$tip.=$dim."\n".$result->{url};
$button->set_tooltip_text($tip);
$button->signal_connect(clicked => \&set_cover);
$button->signal_connect(button_press_event => \&GMB::Picture::pixbox_button_press_cb,3); # 3 : mouse button 3
$button->set_relief('none');
$button->show_all;
my $i= $res_id % RES_PER_PAGE;
my $y=int( $i/RES_PER_LINE);
my $x= $i % RES_PER_LINE;
$table->attach($button,$x,$x+1,$y,$y+1,'fill','fill',1,1);
$result->{done}=1;
$self->{loaded}++;
}
elsif ($result->{previewurl})
{ $result->{originurl}=$result->{url};
$result->{url}=delete $result->{previewurl};
}
else { $result->{done}='error'; $self->{loaded}++; }
$self->{progress}->set_fraction( $self->{loaded} / ( @{$self->{results}} - RES_PER_PAGE*$self->{page} ));
::IdleDo('8_FetchCovers'.$self,100,\&get_next,$self);
});
::IdleDo('8_FetchCovers'.$self,1000,\&get_next,$self);
}
sub set_cover
{ my $button=$_[0];
my $self=::find_ancestor($button,__PACKAGE__);
my $field=$self->{field};
my $gid= $self->{gid};
my $name= Songs::Gid_to_Get($field,$gid);
my $text;
if ($self->{mainfield} eq 'album')
{ $text=::__x(_"Use this picture as cover for album '{album}'", album => $name);
}
else
{ $text=::__x(_"Use this picture for artist '{artist}'", artist => $name);
}
my $check=Gtk2::CheckButton->new( $text );
$check->set_active(1);
my $default_file= $::Options{OPT.'USEFILE'} ?
$::Options{OPT.'COVERFILE'} : $name;
$default_file=~s/\.(?:jpe?g|png|gif)$//;
$default_file.='.'.$button->{ext};
$default_file=::filename_from_unicode(::CleanupFileName($default_file));
my $default_dir=$::Options{OPT.'COVERPATH'} || '';
$default_dir=::filename_from_unicode(::CleanupDirName($default_dir));
$default_dir=$self->{dir} unless $::Options{OPT.'USEPATH'} && -d $default_dir;
if ($::Options{OPT.'UNIQUE'})
{ while (-e $default_dir.::SLASH.$default_file) #find a unique name
{ last unless $default_file=~s/(?:_(\d+))?\.(\w+)$/'_'.($1? $1+1 : 1).".$2"/e ; }
}
my $file=$default_dir.::SLASH.$default_file;
if (!$::Options{OPT.'ASK'} || -e $file)
{$file=::ChooseSaveFile($self,_"Save picture as",
$default_dir,$default_file,
$check);
}
return unless $file;
#write file
{ my $fh;
my $ok= open $fh,'>',$file;
if ($ok)
{ $ok= print $fh $button->{pixdata};
unlink $file unless $ok;
}
unless ($ok)
{ my $retry=::Retry_Dialog( ::__x( _"Error writing '{file}'", file => ::filename_to_utf8displayname($file) )." :\n$!." ,$self);
redo if $retry eq 'yes';
return;
}
close $fh;
}
return unless $check->get_active;
AAPicture::SetPicture($field,$gid,$file);
}
1
__END__
xml.amazon.com/onca/xml3?t=webservices-20&dev-t=%l&KeywordSearch=%s&mode=music&type=heavy&locale=us&page=1&f=xml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment