Code, second part
=== CODE === fetch_cover.pm === part 2/2
=== CODE === fetch_cover.pm === part 2/2
Code Select
#----------------------------------------------------------------
#
# Parse functies van de verschillende zoekmachines
#
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;
if ($res =~ m#<name>([^<]+)</name>#) {
$res{desc} = ::decode_html (Encode::decode('cp1252', $1)); # FIXME not sure of the encoding
}
while ($res =~ m#<cover>(.+?)</cover>#gs) {
my $cover = $1;
next unless $cover =~ m#<type>front</type>#;
$res{url} = $1 if $cover =~ m#<preview>([^<]+)</preview>#;
$res{previewurl} = $1 if $cover =~ m#<thumbnail>([^<]+)</thumbnail>#;
last;
}
push @list, \%res if $res{url};
}
return \@list;
}
sub parse_lastfm {
my ($results, $pageurl, $searchcontext) = @_;
$searchcontext->{baseurl} ||= $pageurl;
my @list;
while ($results =~ m#<a\s+href="/music/[^/]+/\+images/[0-9A-F]+"[^>]+?class="image-list-link"[^<]+<img[^>]+?src="([^"]+)"#gis) {
my $pre = $1;
my $url = $pre;
$url =~ s#/i/u/avatar170s/#/i/u/#;
$url .= '.jpg';
push @list, { url => $url, previewurl => $pre };
}
my $nexturl;
$nexturl = $searchcontext->{baseurl} . $1 if $results =~ m#<a href="(\?page=\d+)">Next</a>#;
return \@list, $nexturl;
}
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, $pageurl, $searchcontext) = @_;
$searchcontext->{baseurl} ||= $pageurl;
$searchcontext->{pagecount}++;
my @list;
# for my $res (split /<div class="rg_meta[^"]*"[^>]*>/, $result) {
# $res =~ s/(?<!\\)\\"/\\u0022/g; # escape \" to make extraction simpler, not perfect
# next unless $res =~ m#"ou":"(http[^"]+)"#i;
# my $url = $1;
# # ...
# }
$result =~ s/\n//g;
while ($result =~ m#"(https://encrypted-tbn\d.gstatic.com/images\?q[^"]+)",\d+,\d+\],\["(http[^"]+)".+?"(http[^"]+)","([^"]+)"#g) {
my ($preview, $url, $ref, $desc) = ($1, $2, $3, $4);
for ($url, $preview, $desc, $ref) {
s/\\u([0-9A-F]{4})/chr(hex($1))/eig; # FIXME maybe use proper JSON decoding library
}
# $desc should be decoded properly, but Encode::decode('utf8',$desc) sometimes complains about wide characters
push @list, { url => $url, previewurl => $preview, desc => $desc, referer => $ref };
}
my $nexturl = $searchcontext->{baseurl} . "&ijn=" . $searchcontext->{pagecount};
$nexturl = undef unless @list;
return \@list, $nexturl;
}
sub parse_bing {
# To get more results than the 35 on the first page,
# it uses the first= url argument, but it behaves strangely,
# in particular it includes results from previous pages, so these are ignored.
# The final results are not exactly those you get from the web page,
# but it seems good enough.
my ($result, $pageurl, $searchcontext) = @_;
$searchcontext->{baseurl} ||= $pageurl;
my $seen = $searchcontext->{seen} ||= {};
my @list;
while ($result =~ m/\s+m="([^"]+)"/g) {
my $metadata = ::decode_html (Encode::decode('utf8', $1));
next unless $metadata =~ m/"murl":"([^"]+)"/i;
my $url = $1;
my $purl = $metadata =~ m/"purl":"([^"]+)"/i ? $1 : undef;
my $turl = $metadata =~ m/"turl":"([^"]+)"/i ? $1 : undef;
next if $seen->{$url};
$seen->{$url} = ++$searchcontext->{count};
push @list, { url => $url, previewurl => $turl, referer => $purl };
}
my $n = ++$searchcontext->{pagecount};
my $nexturl = $searchcontext->{baseurl} . "&first=" . (1 + $n * 100) . "&count=100";
$nexturl = undef unless @list;
return \@list, $nexturl;
}
sub parse_yahoo {
my ($result, $pageurl, $searchcontext) = @_;
$searchcontext->{baseurl} ||= $pageurl;
my @list;
if ($result =~ m/^{"html":/) {
$result =~ s#\\(.)#$1#g; # with the o=js parameter the result html is in a js file -> un-escape
}
while ($result =~ m/<li class="ld ?"([^>]+)><a +(?:target="[^"]*" +)?href=([^>]+)>(?:<img src=['"]([^'"]+)['"])?/g) {
my $href = $2;
my $preview = $3;
next unless $href =~ m/imgurl=([^&"']+)[&"']/;
my $url = 'http://' . ::decode_url($1);
my $desc;
if ($href =~ m/aria-label=["']([^"']+)["']/) {
$desc = $1;
$desc =~ s#</?b>##g; # remove escaped bold markup around matched strings
$desc = Encode::decode ('utf8', $desc);
$desc = ::decode_html (::decode_html ($desc));
}
push @list, { url => $url, previewurl => $preview, desc => $desc };
}
my $n = ++$searchcontext->{pagecount};
my $nexturl = $searchcontext->{baseurl} . "&b=" . (1 + $n * 60) . "&iid=Y.$n&spos" . ($n * 12);
return \@list, $nexturl;
}
sub parse_ddg {
my ($result, $pageurl, $searchcontext) = @_;
unless ($searchcontext->{vqd}) {
# request to i.js don't work without a vqd number, get it from the first page
my $vqd = ($result =~ m/vqd=([0-9-]+)/) ? $1 : 0;
my $q = ($result =~ m/\?q=([^&"]+)[&"]/) ? $1 : 0;
my $url = ($vqd && $q) ? "https://duckduckgo.com/i.js?o=json&q=$q&vqd=$vqd&p=1" : undef;
$searchcontext->{vqd} = $vqd;
return [], $url, !!$url; # third return parameter true means get next url even though no results in this query
}
my (@list, $nexturl);
my $seen = $searchcontext->{seen} ||= {};
$nexturl = 'https://duckduckgo.com/' . $1 if $result =~ m#"next"\s*:\s*"(i.js[^"]+)#;
for my $res (split /}\s*,[^{]*{/, $result) {
my @kv = $res =~ m#("[^"]+"|[^:]+)\s*:\s*("[^"]*"|[^,}]*)\s*,?#g;
s/^"([^"]*)"$/$1/ for @kv; # Stripping quotes from keys
my %h = (title => '', @kv);
next unless $h{image};
$h{title} =~ s#\\u(....)#chr(hex($1))#eg; # Decoding Unicode characters
my $url = $h{image};
next if $seen->{$url};
$seen->{$url} = ++$searchcontext->{count};
push @list, { url => $url, previewurl => $h{thumbnail}, desc => $h{title}, referer => $h{url} };
}
return \@list, $nexturl;
}
sub parse_discogs {
my ($result, $pageurl, $searchcontext) = @_;
warn "Discogs raw result: " . substr($result, 0, 1000) . "\n" if $::debug;
$searchcontext->{baseurl} ||= $pageurl;
my $seen = $searchcontext->{seen} ||= {};
my @list;
eval {
my $json = JSON->new->utf8->decode($result);
warn "Discogs found " . $json->{pagination}{items} . " results\n" if $::debug && $json->{pagination};
return \@list unless $json && $json->{results};
foreach my $item (@{$json->{results}}) {
next unless $item->{type} eq 'release';
my $url = $item->{cover_image}; # Gebruik de grotere cover_image-URL
next unless $url;
warn "Discogs found image URL: $url\n" if $::debug;
next if $seen->{$url};
$seen->{$url} = ++$searchcontext->{count};
push @list, { url => $url, previewurl => $url, referer => undef };
}
};
if ($@) {
warn "Discogs API error: $@\n" if $::debug;
return \@list;
}
warn "Discogs parsed " . scalar(@list) . " results\n" if $::debug;
return \@list, undef;
}
sub parse_itunes {
my ($result, $pageurl, $searchcontext) = @_;
# warn "iTunes raw result: " . substr($result, 0, 500) . "\n" if $::debug; # Toon eerste 500 tekens van de respons
$searchcontext->{baseurl} ||= $pageurl;
my $seen = $searchcontext->{seen} ||= {};
my @list;
eval {
my $json = JSON->new->utf8->decode($result);
# warn "iTunes JSON resultCount: " . ($json->{resultCount} // 'undef') . "\n" if $::debug;
return \@list unless $json && $json->{resultCount} > 0;
foreach my $item (@{$json->{results}}) {
my $url = $item->{artworkUrl100} || $item->{artworkUrl600};
# warn "iTunes found URL: $url\n" if $::debug && $url;
next unless $url;
$url =~ s/100x100bb\.jpg$/600x600bb.jpg/;
next if $seen->{$url};
$seen->{$url} = ++$searchcontext->{count};
push @list, { url => $url, previewurl => $url, referer => undef };
}
};
if ($@) {
# warn "iTunes API error: $@\n" if $::debug;
return \@list;
}
# warn "iTunes parsed " . scalar(@list) . " results\n" if $::debug;
return \@list, undef;
}
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];
my ($list, $nexturl, $ignore0) = $parse->($result, $self->{url}, $self->{searchcontext});
$self->{nexturl} = $nexturl;
push @{$self->{results}}, @$list;
my $more = @{$self->{results}} - ($self->{page} + 1) * RES_PER_PAGE;
$self->{Bnext}->set_sensitive($more > 0 || $nexturl);
unless ($ignore0 || @{$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}->hide;
if ($error) {
my $label = Gtk3::Label->new($error);
$label->show;
$self->{table}->attach($label, 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}) {
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},
);
} elsif ($#$results >= $end) {
$self->{Bnext}->set_sensitive(1);
}
$end = $#$results if $#$results < $end;
for my $id ($start .. $end) {
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;
my $result = $self->{results}[$res_id];
$result->{waiting} = Simple_http::get_with_cb (
url => $result->{url},
referer => $result->{referer},
cache => 1,
cb => sub {
my $pixdata = $_[0];
$result->{waiting} = undef;
if ($pixdata) {
warn "Loading image: $result->{url}\n" if $::debug;
# Maak een tijdelijk bestand aan
my ($fh, $tempfile) = tempfile(SUFFIX => '.jpg');
binmode $fh;
print $fh $pixdata;
close $fh;
# Converteer de afbeelding naar een standaardformaat
my $convertedfile = "$tempfile.converted.jpg";
system("convert", $tempfile, "-quality", "90", $convertedfile);
# Laad de geconverteerde afbeelding
if (-e $convertedfile) {
open my $in, "<:raw", $convertedfile;
my $converted_pixdata = do { local $/; <$in> };
close $in;
my $loader = GMB::Picture::LoadPixData($converted_pixdata, PREVIEW_SIZE);
if ($loader) {
my $dim = $loader->{w} . ' x ' . $loader->{h};
my $table = $self->{table};
my $pixbuf = $loader->get_pixbuf;
my $image = Gtk3::Image->new_from_pixbuf($pixbuf);
my $button = Gtk3::Button->new;
$button->{pixdata} = $converted_pixdata;
$button->{ext} = 'jpg';
$button->{url} = $result->{url};
my $vbox = Gtk3::VBox->new(0, 0);
my $label = Gtk3::Label->new($dim);
$vbox->add($image);
$vbox->pack_end($label, 0, 0, 0);
$button->add($vbox);
my $tip = '';
$tip = ::PangoEsc($result->{desc}) . "\n" if $result->{desc};
$tip .= $dim . "\n" . ::MarkupFormat("<small>%s</small>", $result->{url});
$button->set_tooltip_markup($tip);
$button->signal_connect(clicked => \&set_cover);
$button->signal_connect(button_press_event => \&GMB::Picture::pixbox_button_press_cb, 3);
::set_drag($button, source => [::DRAG_FILE, sub { return ::DRAG_FILE, $_[0]{url} }]);
$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;
} else {
warn "Failed to load converted image data for: $result->{url}\n" if $::debug;
}
unlink $tempfile, $convertedfile;
} else {
warn "Failed to convert image: $result->{url}\n" if $::debug;
}
$self->{loaded}++;
} else {
warn "No image data for: $result->{url}\n" if $::debug;
$result->{done} = 'error';
}
$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 = $button->GET_ancestor;
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 = Gtk3::CheckButton->new( $text );
$check->set_active(1);
my $default_file = $::Options{OPT.'USEFILE'} ? $::Options{OPT.'COVERFILE'} : $name;
$default_file =~ s/$::Image_ext_re//;
$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($!,_"Error saving picture", details=>::__x( _"Error writing '{file}'", file => ::filename_to_utf8displayname($file) ), window=>$self);
redo if $retry eq 'retry';
return;
}
close $fh;
}
return unless $check->get_active;
AAPicture::SetPicture($field,$gid,$file);
}
1
Code, first part
=== CODE === fetch_cover.pm === part 1/2
=== CODE === fetch_cover.pm === part 1/2
Code Select
# Copyright (C) 2005-2014 Quentin Sculo <[email protected]>
# Update by Joppla, 2026
#
# 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
#
# dependencies for this version:
# ImageMagick (convert),Extern systeemprogramma,which convert,
# sudo apt-get install imagemagick
#
# Imager,Perl-module,eval { require Imager; },
# sudo apt-get install libimager-perl
#
# LWP::UserAgent,Perl-module,eval { require LWP::UserAgent; },
# sudo apt-get install libwww-perl
#
=for 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 'Gtk3::Window';
use constant {
OPT => 'PLUGIN_FETCHCOVER_',
RES_LINES => 4,
RES_PER_LINE => 6,
PREVIEW_SIZE => 100,
# GOOGLE_USER_AGENT => 'Mozilla/5.0 Gecko/20100101 Firefox/26.0', # google checks to see if the browser can handle the "standard" image search
GOOGLE_USER_AGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', # google checks to see if the browser can handle the "standard" image search
BING_USER_AGENT => ' ', # Bing returns weird results with the default user agent
};
use constant RES_PER_PAGE => RES_PER_LINE * RES_LINES;
use JSON;
use URI::Escape;
use URI::Escape qw(uri_escape_utf8);
use LWP::UserAgent;
use Data::Dumper; # voor debugging
use File::Temp qw(tempfile);
use Imager;
# foutmeldingen aanzetten in de terminal
# $::debug = 1;
my %Sites = (
artist => {
# googlei => [_"google images","http://images.google.com/images?q=%s&imgsz=medium|large", \&parse_googlei, GOOGLE_USER_AGENT],
googlei => [_"google images","https://www.google.com/search?tbm=isch&q=%s", \&parse_googlei, GOOGLE_USER_AGENT],
lastfm => ['last.fm',"http://www.last.fm/music/%a/+images", \&parse_lastfm],
discogs => ['discogs', "https://api.discogs.com/database/search?type=release&artist=%a&release_title=%l", \&parse_discogs, undef, undef],
bing => ['bing',"http://www.bing.com/images/async?q=%s", \&parse_bing, BING_USER_AGENT],
yahoo => ['yahoo',"http://images.search.yahoo.com/search/images?p=%s&o=js", \&parse_yahoo],
ddg => ["DuckDuckGo","https://duckduckgo.com/?q=%s&iax=1&ia=images", \&parse_ddg],
},
album => {
# googlei => [_"google images","http://images.google.com/images?q=%s&imgsz=medium|large&imgar=ns", \&parse_googlei, GOOGLE_USER_AGENT],
# googleihi => [_"google images (hi-res)","http://www.google.com/images?q=%s&imgsz=xlarge|xxlarge&imgar=ns", \&parse_googlei, GOOGLE_USER_AGENT],
googlei => [_"google images","https://www.google.com/search?tbm=isch&q=%s", \&parse_googlei, GOOGLE_USER_AGENT],
yahoo => ['yahoo',"http://images.search.yahoo.com/search/images?p=%s&o=js", \&parse_yahoo],
bing => ['bing',"http://www.bing.com/images/async?q=%s&qft=+filterui:aspect-square", \&parse_bing, BING_USER_AGENT],
ddg => ["DuckDuckGo","https://duckduckgo.com/?q=%s&iax=1&ia=images", \&parse_ddg],
itunes => ['itunes', "https://itunes.apple.com/search?term=%s&entity=album&limit=100", \&parse_itunes, undef],
#rateyourmusic=> ['rateyourmusic.com', "http://rateyourmusic.com/search?searchterm=%s&searchtype=l",\&parse_rateyourmusic], # urls results in "403 Forbidden"
discogs => ['discogs', "https://api.discogs.com/database/search?type=release&artist=%a&release_title=%l", \&parse_discogs, undef, undef],
},
);
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'
);
# controleer plaatjes voor je ze laad
sub is_valid_jpeg {
my ($image_data) = @_;
my $img = Imager->new;
return $img->read(data => $image_data, type => 'jpeg');
}
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 $discogs_token_entry = ::NewPrefEntry(OPT.'DISCOGSTOKEN'); # invoerveld voor Discogs-token
my ($radio1a, $radio1b) = ::NewPrefRadio(OPT.'USEPATH', [_"use song folder", 0, _"use :", 1]);
my ($radio2a, $radio2b) = ::NewPrefRadio(OPT.'USEFILE', [_"use album name", 0, _"use :", 1]);
my $frame1 = Gtk3::Frame->new(_"default folder");
my $frame2 = Gtk3::Frame->new(_"default filename");
my $frame3 = Gtk3::Frame->new(_"Discogs Token"); # Frame voor Discogs-token
my $vbox1 = ::Vpack($radio1a, [$radio1b, $entry1]);
my $vbox2 = ::Vpack($radio2a, [$radio2b, $entry2]);
my $vbox3 = ::Vpack($discogs_token_entry); # Voeg token invoerveld toe aan de VBox
$frame1->add($vbox1);
$frame2->add($vbox2);
$frame3->add($vbox3); # Voeg de nieuwe frame toe voor de token
return ::Vpack($frame1, $frame2, $frame3, $check1, $check2);
}
sub Fetch {
my ($field,$gid,$ID) = @_;
my $mainfield = Songs::MainField($field); #'artist' or 'album'
my $self = bless Gtk3::Window->new;
$self->set_border_width(4);
my $Bsearch = ::NewIconButton('gtk-find',_"Search");
my $Bcur = Gtk3::Button->new($mainfield eq 'artist' ? _"Search for current artist" : _"Search for current album");
::set_drag ($Bcur,
dest => [::DRAG_ID, sub { $_[0]->get_toplevel->SearchID(undef,$_[2]); }],
);
my $Bclose= Gtk3::Button->new_from_stock('gtk-close');
my @entry;
push @entry, $self->{"searchentry_$_"} = Gtk3::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= Gtk3::Button->new(_"More results");
$self->{Bstop} = my $Bstop= Gtk3::Button->new_from_stock('gtk-stop');
$self->{progress} = my $pbar = Gtk3::ProgressBar->new;
$self->{table} = my $table= Gtk3::Table->new(RES_LINES,RES_PER_LINE,::TRUE);
$self->add( ::Vpack
( [map( {('_',$_)} @entry), $Bsearch, $Bstop, $source],
'_',$table,
'-', ['_',$pbar , '-', $Bclose,$Bnext,$Bcur]
) );
$self->show_all;
for (@entry) {
$_->signal_connect( activate => \&NewSearch );
$_->set_no_show_all(1);
}
$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(undef,$::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($gid,$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,$gid,$ID) = @_; #only one of $gid and $ID needs to be defined
$self = $_[0]->GET_ancestor;
my $field = $self->{field};
if (!defined $ID) {
return unless defined $gid;
my $list = AA::GetIDs($field,$gid);
$ID = $list->[0];
return unless defined $ID;
} elsif (!defined $gid) {
$gid = Songs::Get_gid($ID,$field);
$gid = $gid->[0] if ref $gid; #for field like artists return an array of values, use first value
}
$self->{gid} = $gid;
$self->{dir} = Songs::Get($ID,'path');
my $search = my $name = Songs::Gid_to_Get($field,$gid);
$search = "\"$search\"" unless $search eq '' || $self->{site} eq 'itunes';
my $albumname = '';
my $artistname = '';
if ($self->{mainfield} eq 'album') {
$albumname = $name;
$artistname = Songs::Get($ID, 'album_artist');
if ($self->{site} eq 'itunes') {
$artistname =~ s/^\"|\"$//g if defined $artistname;
# Gebruik zowel de artiestnaam als de albumnaam
$search = "$artistname $albumname";
} else {
$search = "\"$search\"" unless $search eq '';
$search .= " \"$artistname\"" unless $search eq '' || $artistname eq '';
}
}
$self->set_title(_("Searching for a picture of : ").$name);
$self->{searchentry_s}->set_text($search);
# warn "Final iTunes search term: $search\n" if $self->{site} eq 'itunes' && $::debug;
$self->{searchentry_a}->set_text($artistname);
$self->{searchentry_l}->set_text($albumname);
$self->NewSearch;
}
sub myDiscogsToken {
my $token = $::Options{OPT . 'DISCOGSTOKEN'}; # Haal de token op met de juiste OPT-structuur
# warn "Fetched token: $token\n"; # Voeg deze regel toe om te debuggen
if (!$token) {
return 'Error: Discogs token is not filled in the settings.';
}
return $token;
}
sub init_settings {
::SetPref(OPT . 'DISCOGSTOKEN', ''); # Initialiseer met een lege waarde voor de token
}
sub discogs_http_request {
my ($url, $token) = @_;
my $ua = LWP::UserAgent->new;
$ua->default_header('Authorization' => "Discogs token=$token");
$ua->default_header('User-Agent' => 'gmusicbrowser/1.0');
my $response = $ua->get($url);
if ($response->is_success) {
return $response->content;
} else {
warn "Discogs request failed: " . $response->status_line . "\n" if $::debug;
return undef;
}
}
sub NewSearch {
my $self = $_[0]->GET_ancestor;
my $url = $Sites{$self->{mainfield}}{$self->{site}}[1];
$self->{user_agent} = $Sites{$self->{mainfield}}{$self->{site}}[3] || 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36';
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} = uri_escape_utf8($s);
}
$self->abort;
$self->{results} = [];
$self->{page} = 0;
$self->InitPage;
$url =~ s/%([sal])/$letter{$1}/g;
$self->{url} = $url;
$self->{searchcontext} = {};
# warn "fetchcover: Loading URL: $url\n" if $::debug;
my %headers;
if ($self->{site} eq 'discogs') {
my $token = myDiscogsToken ();
if ($token =~ /^Error:/) {
# Toon foutmelding aan de gebruiker
stop ($self, $token);
return;
}
my $result = discogs_http_request ($url, $token);
if ($result) {
$self -> searchresults_cb ($result);
} else {
stop ($self, "Discogs connection failed.");
}
return;
}
$self->{waiting} = Simple_http::get_with_cb (
cb => sub { $self->searchresults_cb(@_) },
url => $url,
cache => 1,
user_agent => $self->{user_agent},
headers => \%headers,
);
warn "Discogs request URL: $url\n" if $self->{site} eq 'discogs' && $::debug;
}
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);
}
Hi,
I have made an update for the fetch_cover.pm.
What I have done:
- add search on itunes
- update search for discogs
- update search on bing
- remove slothradio (depends now on itunes, so made for itunes)
- remove freecovers (doesn't excist anymore)
For discogs you need a api-token, you can use the most simple one. The token you can add on the settings page.
Further the update depends on 3 extra apps:
1. ImageMagick, sudo apt-get install imagemagick
2. Imager,Perl-module,sudo apt-get install libimager-perl
3. LWP::UserAgent,Perl-module, sudo apt-get install libwww-perl
DuckDuckGo sometimes works, sometimes not.
I tried to work on google and yahoo, but I didn't succeed.
I tried to attach the file, but it doesn't want to upload and is too long for one message.
I have made an update for the fetch_cover.pm.
What I have done:
- add search on itunes
- update search for discogs
- update search on bing
- remove slothradio (depends now on itunes, so made for itunes)
- remove freecovers (doesn't excist anymore)
For discogs you need a api-token, you can use the most simple one. The token you can add on the settings page.
Further the update depends on 3 extra apps:
1. ImageMagick, sudo apt-get install imagemagick
2. Imager,Perl-module,sudo apt-get install libimager-perl
3. LWP::UserAgent,Perl-module, sudo apt-get install libwww-perl
DuckDuckGo sometimes works, sometimes not.
I tried to work on google and yahoo, but I didn't succeed.
I tried to attach the file, but it doesn't want to upload and is too long for one message.
Hi,
Does someone know how I can change the fetch_cover.pm plugin to make the search for artwork on slothradio work. Or for another specialized site like discogs, etc.
Thanks,
Jop
Does someone know how I can change the fetch_cover.pm plugin to make the search for artwork on slothradio work. Or for another specialized site like discogs, etc.
Thanks,
Jop
I don't know if it is still actual. I had the same problem. The solution was in the way how to start. I have this starter and this works:
Code Select
bash -c "LC_ALL=C /pathto/gmusicbrowser/gmusicbrowser.pl"Et la partie AuTO put them before my %Sites=
Code Select
);
my @AUTO_SITES = grep { $_ ne 'AUTO' } qw(
musixmatch
genius
lyriki
parolesnet
lyricsondemand
);
Code Select
parolesnet => [
'Paroles.net',
# -------- URL builder --------
sub {
my $ID = $_[0];
my $artist = Songs::Get($ID,'artist') // '';
my $title = Songs::Get($ID,'title') // '';
for ($artist, $title)
{
$_ = ::superlc($_);
$_ = lc $_;
$_ =~ s/\(.*?\)//g;
$_ =~ s/\bfeat\.?.*//g;
$_ =~ s/&/ et /g;
$_ =~ s/[^a-z0-9]+/-/g;
$_ =~ s/-{2,}/-/g;
$_ =~ s/^-|-$//g;
}
return "https://www.paroles.net/$artist/paroles-$title";
},
undef,
# -------- Lyrics extractor --------
sub {
my $html = $_[0];
my $lyrics = '';
# Extraire le bloc song-text complet
if ($html =~ m|<div\s+class="song-text">(.*?)</div>|s) {
warn "DEBUG: Match found!\n";
my $main = $1;
warn "DEBUG: Captured " . length($main) . " chars\n";
# Virer le h2 titre
$main =~ s|<h2.*?</h2>||si;
# Virer les blocs de pub
$main =~ s|<div[^>]*min-height[^>]*>.*?</div>||gsi;
$main =~ s|<div[^>]*optidigital[^>]*>.*?</div>||gsi;
# Marquer les doubles <br> (paragraphes) différemment des simples
$main =~ s|<br\s*/?>[\s\n]*<br\s*/?>|###PARA###|gi;
$main =~ s|<br\s*/?>|###BR###|gi;
$main =~ s|</div>|###BR###|gi;
# Virer toutes les balises HTML restantes
$main =~ s|<[^>]+>||g;
# Nettoyer entités HTML
$main =~ s/ / /gi;
$main =~ s/&/&/gi;
$main =~ s/</</gi;
$main =~ s/>/>/gi;
$main =~ s/"/"/gi;
# Nettoyer espaces
$main =~ s/^\s+|\s+$//gm;
$main =~ s/###BR###\s*###BR###/###PARA###/g;
# Remplacer les marqueurs par <br> pour gmusicbrowser
$main =~ s|###PARA###|<br><br>|g; # Double saut pour paragraphes
$main =~ s|###BR###|<br>|g; # Simple saut pour vers
$lyrics = $main;
warn "DEBUG: Final lyrics length: " . length($lyrics) . "\n";
} else {
warn "DEBUG: NO MATCH for song-text div!\n";
$_[0] = $notfound;
return 0;
}
return 0 unless length($lyrics) > 20;
$_[0] = $lyrics;
return 1;
}
],Hello,
Please find a few update for lyrics'plugin find in /usr/share/gmusicbrowser/plugins :
you can put this after "my %Sites="
Please find a few update for lyrics'plugin find in /usr/share/gmusicbrowser/plugins :
you can put this after "my %Sites="
Code Select
lyricsondemand => [
'Lyricsondemand',
sub {
my $ID = $_[0];
my $artist = Songs::Get($ID,'artist') || '';
my $title = Songs::Get($ID,'title') || '';
# Nettoyer pour l'URL
$artist =~ s/\s+/_/g;
$artist =~ s/[^a-zA-Z0-9_]//g;
$artist = lc $artist;
$title =~ s/\s+/_/g;
$title =~ s/[^a-zA-Z0-9_]//g;
$title =~ s/_+/_/g;
$title = lc $title;
return "https://lyricsondemand.com/$artist/$title";
},
undef,
sub {
my $html = $_[0];
my $lyrics = '';
# Extraire tout le bloc mainlyrics
if ($html =~ m#<div\s+class="mainlyrics">(.*?)</div>\s*</div>\s*</div>#si) {
my $main = $1;
# Extraire toutes les lignes de paroles
my @lines = $main =~ m#<div\s+class="line main-single-line [^"]*"\s+data-line-index="\d+">(.*?)</div>#gis;
# Nettoyer chaque ligne
@lines = map {
my $line = $_;
$line =~ s/<[^>]+>//g; # retirer toutes les balises HTML restantes
$line =~ s/ / /gi; # espaces HTML
$line =~ s/&/&/gi; # ampersand HTML
$line =~ s/^\s+|\s+$//g; # trim
$line; # retourner la ligne nettoyée
} grep { $_ !~ /^\*/ } @lines; # filtrer les lignes qui commencent par *
# Joindre toutes les lignes avec saut HTML
$lyrics = join("<br>", @lines);
} else {
$_[0] = "No lyrics found";
return 0;
}
$_[0] = $lyrics;
return 1;
}],
Quentin's email no longer seems to exist, and I can't find any trace of him in the various developments.
I also share your opinion about this software—nothing else comes close. I'm not very knowledgeable about programming, so I don't know if it's easy to improve, but at least it works on the latest version of Debian.
I also share your opinion about this software—nothing else comes close. I'm not very knowledgeable about programming, so I don't know if it's easy to improve, but at least it works on the latest version of Debian.