2018-02-27 18:06:05 +01:00
/*
* Strawberry Music Player
* This file was part of Clementine .
* Copyright 2010 , David Sansome < me @ davidsansome . com >
*
* Strawberry is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* Strawberry is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with Strawberry . If not , see < http : //www.gnu.org/licenses/>.
2018-08-09 18:39:44 +02:00
*
2018-02-27 18:06:05 +01:00
*/
# include "config.h"
2018-05-01 00:41:33 +02:00
# include <QtGlobal>
# include <QWidget>
2018-02-27 18:06:05 +01:00
# include <QDialog>
2018-05-01 00:41:33 +02:00
# include <QDir>
# include <QFileInfo>
2018-02-27 18:06:05 +01:00
# include <QList>
2018-05-01 00:41:33 +02:00
# include <QSet>
2018-02-27 18:06:05 +01:00
# include <QMimeData>
2018-05-01 00:41:33 +02:00
# include <QByteArray>
# include <QVariant>
# include <QString>
# include <QStringBuilder>
2018-03-13 17:48:25 +01:00
# include <QRegExp>
2018-05-01 00:41:33 +02:00
# include <QUrl>
# include <QImage>
# include <QImageWriter>
# include <QPixmap>
# include <QIcon>
# include <QRect>
# include <QAction>
# include <QFileDialog>
# include <QLabel>
# include <QDesktopWidget>
# include <QtEvents>
2018-02-27 18:06:05 +01:00
# include "core/utilities.h"
2018-05-01 00:41:33 +02:00
# include "core/song.h"
2018-02-27 18:06:05 +01:00
# include "core/iconloader.h"
2018-05-01 00:41:33 +02:00
# include "core/application.h"
2018-02-27 18:06:05 +01:00
2018-05-01 00:41:33 +02:00
# include "collection/collectionbackend.h"
2019-03-11 23:07:11 +01:00
# include "settings/collectionsettingspage.h"
# include "organise/organiseformat.h"
2018-05-01 00:41:33 +02:00
# include "albumcoverchoicecontroller.h"
# include "albumcoverfetcher.h"
# include "albumcoverloader.h"
# include "albumcoversearcher.h"
# include "coverfromurldialog.h"
# include "currentartloader.h"
2018-02-27 18:06:05 +01:00
const char * AlbumCoverChoiceController : : kLoadImageFileFilter = QT_TR_NOOP ( " Images (*.png *.jpg *.jpeg *.bmp *.gif *.xpm *.pbm *.pgm *.ppm *.xbm) " ) ;
const char * AlbumCoverChoiceController : : kSaveImageFileFilter = QT_TR_NOOP ( " Images (*.png *.jpg *.jpeg *.bmp *.xpm *.pbm *.ppm *.xbm) " ) ;
const char * AlbumCoverChoiceController : : kAllFilesFilter = QT_TR_NOOP ( " All files (*) " ) ;
QSet < QString > * AlbumCoverChoiceController : : sImageExtensions = nullptr ;
AlbumCoverChoiceController : : AlbumCoverChoiceController ( QWidget * parent ) :
QWidget ( parent ) ,
2019-02-10 21:25:36 +01:00
app_ ( nullptr ) ,
cover_searcher_ ( nullptr ) ,
cover_fetcher_ ( nullptr ) ,
save_file_dialog_ ( nullptr ) ,
2019-03-11 23:07:11 +01:00
cover_from_url_dialog_ ( nullptr ) ,
cover_album_dir_ ( false ) ,
cover_filename_ ( CollectionSettingsPage : : SaveCover_Hash ) ,
cover_overwrite_ ( false ) ,
cover_lowercase_ ( true ) ,
cover_replace_spaces_ ( true )
{
2018-02-27 18:06:05 +01:00
cover_from_file_ = new QAction ( IconLoader : : Load ( " document-open " ) , tr ( " Load cover from disk... " ) , this ) ;
cover_to_file_ = new QAction ( IconLoader : : Load ( " document-save " ) , tr ( " Save cover to disk... " ) , this ) ;
cover_from_url_ = new QAction ( IconLoader : : Load ( " download " ) , tr ( " Load cover from URL... " ) , this ) ;
search_for_cover_ = new QAction ( IconLoader : : Load ( " search " ) , tr ( " Search for album covers... " ) , this ) ;
unset_cover_ = new QAction ( IconLoader : : Load ( " list-remove " ) , tr ( " Unset cover " ) , this ) ;
show_cover_ = new QAction ( IconLoader : : Load ( " zoom-in " ) , tr ( " Show fullsize... " ) , this ) ;
2018-08-29 21:42:24 +02:00
search_cover_auto_ = new QAction ( tr ( " Search automatically " ) , this ) ;
2018-02-27 18:06:05 +01:00
search_cover_auto_ - > setCheckable ( true ) ;
search_cover_auto_ - > setChecked ( false ) ;
separator_ = new QAction ( this ) ;
separator_ - > setSeparator ( true ) ;
2019-03-12 00:01:52 +01:00
ReloadSettings ( ) ;
2018-02-27 18:06:05 +01:00
}
AlbumCoverChoiceController : : ~ AlbumCoverChoiceController ( ) { }
2019-03-11 23:07:11 +01:00
void AlbumCoverChoiceController : : ReloadSettings ( ) {
QSettings s ;
s . beginGroup ( CollectionSettingsPage : : kSettingsGroup ) ;
cover_album_dir_ = s . value ( " cover_album_dir " , false ) . toBool ( ) ;
cover_filename_ = CollectionSettingsPage : : SaveCover ( s . value ( " cover_filename " , CollectionSettingsPage : : SaveCover_Hash ) . toInt ( ) ) ;
cover_pattern_ = s . value ( " cover_pattern " , " %albumartist-%album " ) . toString ( ) ;
cover_overwrite_ = s . value ( " cover_overwrite " , false ) . toBool ( ) ;
cover_lowercase_ = s . value ( " cover_lowercase " , false ) . toBool ( ) ;
cover_replace_spaces_ = s . value ( " cover_replace_spaces " , false ) . toBool ( ) ;
s . endGroup ( ) ;
}
2018-02-27 18:06:05 +01:00
void AlbumCoverChoiceController : : SetApplication ( Application * app ) {
app_ = app ;
cover_fetcher_ = new AlbumCoverFetcher ( app_ - > cover_providers ( ) , this ) ;
2019-02-09 14:51:12 +01:00
cover_searcher_ = new AlbumCoverSearcher ( QIcon ( " :/pictures/cdcase.png " ) , app , this ) ;
2018-02-27 18:06:05 +01:00
cover_searcher_ - > Init ( cover_fetcher_ ) ;
connect ( cover_fetcher_ , SIGNAL ( AlbumCoverFetched ( quint64 , QImage , CoverSearchStatistics ) ) , this , SLOT ( AlbumCoverFetched ( quint64 , QImage , CoverSearchStatistics ) ) ) ;
}
QList < QAction * > AlbumCoverChoiceController : : GetAllActions ( ) {
return QList < QAction * > ( ) < < cover_from_file_ < < cover_to_file_ < < separator_ < < cover_from_url_ < < search_for_cover_ < < unset_cover_ < < show_cover_ ;
}
QString AlbumCoverChoiceController : : LoadCoverFromFile ( Song * song ) {
2018-04-06 22:13:11 +02:00
2018-02-27 18:06:05 +01:00
QString cover = QFileDialog : : getOpenFileName ( this , tr ( " Load cover from disk " ) , GetInitialPathForFileDialog ( * song , QString ( ) ) , tr ( kLoadImageFileFilter ) + " ;; " + tr ( kAllFilesFilter ) ) ;
if ( cover . isNull ( ) ) return QString ( ) ;
// Can we load the image?
QImage image ( cover ) ;
if ( ! image . isNull ( ) ) {
SaveCover ( song , cover ) ;
return cover ;
}
else {
return QString ( ) ;
}
}
2019-03-11 23:07:11 +01:00
void AlbumCoverChoiceController : : SaveCoverToFileManual ( const Song & song , const QImage & image ) {
2018-02-27 18:06:05 +01:00
2019-03-11 23:07:11 +01:00
QString initial_file_name = " / " ;
if ( ! song . effective_albumartist ( ) . isEmpty ( ) ) {
initial_file_name = initial_file_name + song . effective_albumartist ( ) ;
}
initial_file_name = initial_file_name + " - " + ( song . effective_album ( ) . isEmpty ( ) ? tr ( " unknown " ) : song . effective_album ( ) ) + " .jpg " ;
initial_file_name = initial_file_name . toLower ( ) ;
initial_file_name . replace ( QRegExp ( " \\ s " ) , " - " ) ;
initial_file_name . remove ( OrganiseFormat : : kValidFatCharacters ) ;
2018-02-27 18:06:05 +01:00
QString save_filename = QFileDialog : : getSaveFileName ( this , tr ( " Save album cover " ) , GetInitialPathForFileDialog ( song , initial_file_name ) , tr ( kSaveImageFileFilter ) + " ;; " + tr ( kAllFilesFilter ) ) ;
if ( save_filename . isNull ( ) ) return ;
QString extension = save_filename . right ( 4 ) ;
if ( ! extension . startsWith ( ' . ' ) | | ! QImageWriter : : supportedImageFormats ( ) . contains ( extension . right ( 3 ) . toUtf8 ( ) ) ) {
save_filename . append ( " .jpg " ) ;
}
image . save ( save_filename ) ;
}
QString AlbumCoverChoiceController : : GetInitialPathForFileDialog ( const Song & song , const QString & filename ) {
2018-10-02 00:38:52 +02:00
2018-05-01 00:41:33 +02:00
// Art automatic is first to show user which cover the album may be using now;
// The song is using it if there's no manual path but we cannot use manual path here because it can contain cached paths
2018-02-27 18:06:05 +01:00
if ( ! song . art_automatic ( ) . isEmpty ( ) & & ! song . has_embedded_cover ( ) ) {
return song . art_automatic ( ) ;
2018-05-01 00:41:33 +02:00
// If no automatic art, start in the song's folder
2018-02-27 18:06:05 +01:00
}
else if ( ! song . url ( ) . isEmpty ( ) & & song . url ( ) . toLocalFile ( ) . contains ( ' / ' ) ) {
return song . url ( ) . toLocalFile ( ) . section ( ' / ' , 0 , - 2 ) + filename ;
2018-05-01 00:41:33 +02:00
// Fallback - start in home
2018-02-27 18:06:05 +01:00
}
else {
return QDir : : home ( ) . absolutePath ( ) + filename ;
}
}
QString AlbumCoverChoiceController : : LoadCoverFromURL ( Song * song ) {
2018-10-02 00:38:52 +02:00
2018-02-27 18:06:05 +01:00
if ( ! cover_from_url_dialog_ ) { cover_from_url_dialog_ = new CoverFromURLDialog ( this ) ; }
QImage image = cover_from_url_dialog_ - > Exec ( ) ;
if ( ! image . isNull ( ) ) {
2019-03-11 23:07:11 +01:00
QString cover = SaveCoverToFileAutomatic ( song , image ) ;
if ( cover . isEmpty ( ) ) return QString ( ) ;
2018-02-27 18:06:05 +01:00
SaveCover ( song , cover ) ;
return cover ;
}
else { return QString ( ) ; }
2018-10-02 00:38:52 +02:00
2018-02-27 18:06:05 +01:00
}
QString AlbumCoverChoiceController : : SearchForCover ( Song * song ) {
2018-03-04 14:10:50 +01:00
2018-02-27 18:06:05 +01:00
QString album = song - > effective_album ( ) ;
2019-01-22 22:49:48 +01:00
album . remove ( Song : : kAlbumRemoveDisc ) ;
album . remove ( Song : : kAlbumRemoveMisc ) ;
2018-02-27 18:06:05 +01:00
// Get something sensible to stick in the search box
QImage image = cover_searcher_ - > Exec ( song - > effective_albumartist ( ) , album ) ;
if ( ! image . isNull ( ) ) {
2019-03-11 23:07:11 +01:00
QString cover = SaveCoverToFileAutomatic ( song , image ) ;
if ( cover . isEmpty ( ) ) return QString ( ) ;
2018-02-27 18:06:05 +01:00
SaveCover ( song , cover ) ;
return cover ;
}
else { return QString ( ) ; }
2018-10-02 00:38:52 +02:00
2018-02-27 18:06:05 +01:00
}
QString AlbumCoverChoiceController : : UnsetCover ( Song * song ) {
QString cover = Song : : kManuallyUnsetCover ;
SaveCover ( song , cover ) ;
return cover ;
}
void AlbumCoverChoiceController : : ShowCover ( const Song & song ) {
2018-09-10 21:58:57 +02:00
QPixmap pixmap = AlbumCoverLoader : : TryLoadPixmap ( song . art_automatic ( ) , song . art_manual ( ) , song . url ( ) . toLocalFile ( ) ) ;
ShowCover ( song , pixmap ) ;
}
void AlbumCoverChoiceController : : ShowCover ( const Song & song , const QImage image ) {
QUrl url_manual ( song . art_manual ( ) ) ;
QUrl url_automatic ( song . art_automatic ( ) ) ;
if ( url_manual . isLocalFile ( ) | | url_automatic . isLocalFile ( ) ) {
2018-10-02 00:46:54 +02:00
QPixmap pixmap = AlbumCoverLoader : : TryLoadPixmap ( song . art_automatic ( ) , song . art_manual ( ) , song . url ( ) . toLocalFile ( ) ) ;
2018-09-10 21:58:57 +02:00
ShowCover ( song , pixmap ) ;
}
else if ( ! image . isNull ( ) ) ShowCover ( song , QPixmap : : fromImage ( image ) ) ;
}
void AlbumCoverChoiceController : : ShowCover ( const Song & song , const QPixmap & pixmap ) {
2018-02-27 18:06:05 +01:00
QDialog * dialog = new QDialog ( this ) ;
dialog - > setAttribute ( Qt : : WA_DeleteOnClose , true ) ;
// Use Artist - Album as the window title
QString title_text ( song . effective_albumartist ( ) ) ;
if ( ! song . effective_album ( ) . isEmpty ( ) ) title_text + = " - " + song . effective_album ( ) ;
QLabel * label = new QLabel ( dialog ) ;
2018-09-10 21:58:57 +02:00
label - > setPixmap ( pixmap ) ;
2018-02-27 18:06:05 +01:00
2018-05-01 00:41:33 +02:00
// Add (WxHpx) to the title before possibly resizing
2018-02-27 18:06:05 +01:00
title_text + = " ( " + QString : : number ( label - > pixmap ( ) - > width ( ) ) + " x " + QString : : number ( label - > pixmap ( ) - > height ( ) ) + " px) " ;
2018-05-01 00:41:33 +02:00
// If the cover is larger than the screen, resize the window 85% seems to be enough to account for title bar and taskbar etc.
2018-02-27 18:06:05 +01:00
QDesktopWidget desktop ;
int current_screen = desktop . screenNumber ( this ) ;
int desktop_height = desktop . screenGeometry ( current_screen ) . height ( ) ;
int desktop_width = desktop . screenGeometry ( current_screen ) . width ( ) ;
2018-05-01 00:41:33 +02:00
// Resize differently if monitor is in portrait mode
2018-02-27 18:06:05 +01:00
if ( desktop_width < desktop_height ) {
const int new_width = ( double ) desktop_width * 0.95 ;
if ( new_width < label - > pixmap ( ) - > width ( ) ) {
2018-04-06 22:13:11 +02:00
label - > setPixmap ( label - > pixmap ( ) - > scaledToWidth ( new_width , Qt : : SmoothTransformation ) ) ;
2018-02-27 18:06:05 +01:00
}
}
else {
const int new_height = ( double ) desktop_height * 0.85 ;
if ( new_height < label - > pixmap ( ) - > height ( ) ) {
2018-04-06 22:13:11 +02:00
label - > setPixmap ( label - > pixmap ( ) - > scaledToHeight ( new_height , Qt : : SmoothTransformation ) ) ;
2018-02-27 18:06:05 +01:00
}
}
dialog - > setWindowTitle ( title_text ) ;
dialog - > setFixedSize ( label - > pixmap ( ) - > size ( ) ) ;
dialog - > show ( ) ;
}
void AlbumCoverChoiceController : : SearchCoverAutomatically ( const Song & song ) {
2018-09-04 20:51:47 +02:00
qint64 id = cover_fetcher_ - > FetchAlbumCover ( song . effective_albumartist ( ) , song . effective_album ( ) , true ) ;
2018-02-27 18:06:05 +01:00
cover_fetching_tasks_ [ id ] = song ;
}
void AlbumCoverChoiceController : : AlbumCoverFetched ( quint64 id , const QImage & image , const CoverSearchStatistics & statistics ) {
Song song ;
if ( cover_fetching_tasks_ . contains ( id ) ) {
song = cover_fetching_tasks_ . take ( id ) ;
}
if ( ! image . isNull ( ) ) {
2019-03-11 23:07:11 +01:00
QString cover = SaveCoverToFileAutomatic ( & song , image ) ;
if ( cover . isEmpty ( ) ) return ;
2018-02-27 18:06:05 +01:00
SaveCover ( & song , cover ) ;
}
emit AutomaticCoverSearchDone ( ) ;
}
void AlbumCoverChoiceController : : SaveCover ( Song * song , const QString & cover ) {
if ( song - > is_valid ( ) & & song - > id ( ) ! = - 1 ) {
song - > set_art_manual ( cover ) ;
app_ - > collection_backend ( ) - > UpdateManualAlbumArtAsync ( song - > artist ( ) , song - > albumartist ( ) , song - > album ( ) , cover ) ;
if ( song - > url ( ) = = app_ - > current_art_loader ( ) - > last_song ( ) . url ( ) ) {
app_ - > current_art_loader ( ) - > LoadArt ( * song ) ;
}
}
}
2019-03-11 23:07:11 +01:00
QString AlbumCoverChoiceController : : SaveCoverToFileAutomatic ( const Song * song , const QImage & image ) {
QString albumartist ( song - > effective_albumartist ( ) ) ;
QString artist ( song - > artist ( ) ) ;
QString album ( song - > effective_album ( ) ) ;
album . remove ( Song : : kAlbumRemoveDisc ) ;
return SaveCoverToFileAutomatic ( albumartist , artist , album , song - > url ( ) . adjusted ( QUrl : : RemoveFilename ) . path ( ) , image ) ;
}
QString AlbumCoverChoiceController : : SaveCoverToFileAutomatic ( const QString & albumartist , const QString & artist , const QString & album , const QString & album_dir , const QImage & image ) {
2018-02-27 18:06:05 +01:00
2019-03-11 23:07:11 +01:00
QString album_new ( album ) ;
album_new . remove ( Song : : kAlbumRemoveDisc ) ;
2018-05-01 00:41:33 +02:00
2019-03-11 23:07:11 +01:00
QString path ;
QString filename ;
if ( cover_album_dir_ ) {
path = album_dir ;
}
else {
path = AlbumCoverLoader : : ImageCacheDir ( ) ;
}
if ( path . right ( 1 ) = = QDir : : separator ( ) ) {
path . chop ( 1 ) ;
}
2018-02-27 18:06:05 +01:00
QDir dir ;
2019-03-11 23:07:11 +01:00
if ( ! dir . mkpath ( path ) ) {
qLog ( Error ) < < " Unable to create directory " < < path ;
return QString ( ) ;
}
if ( cover_album_dir_ & & cover_filename_ = = CollectionSettingsPage : : SaveCover_Pattern & & ! cover_pattern_ . isEmpty ( ) ) {
filename = CreateCoverFilename ( albumartist , artist , album_new ) + " .jpg " ;
filename . remove ( OrganiseFormat : : kValidFatCharacters ) ;
if ( cover_lowercase_ ) filename = filename . toLower ( ) ;
if ( cover_replace_spaces_ ) filename . replace ( QRegExp ( " \\ s " ) , " - " ) ;
}
else {
filename = Utilities : : Sha1CoverHash ( albumartist , album_new ) . toHex ( ) + " .jpg " ;
}
QString filepath ( path + " / " + filename ) ;
// Don't overwrite when saving in album dir if the filename is set to pattern unless the "cover_overwrite" is set.
if ( QFile : : exists ( filepath ) & & ! cover_overwrite_ & & cover_album_dir_ & & cover_filename_ = = CollectionSettingsPage : : SaveCover_Pattern ) {
return filepath ;
}
image . save ( filepath , " JPG " ) ;
return filepath ;
}
2018-02-27 18:06:05 +01:00
2019-03-11 23:07:11 +01:00
QString AlbumCoverChoiceController : : CreateCoverFilename ( const QString & albumartist , const QString & artist , const QString & album ) {
2018-02-27 18:06:05 +01:00
2019-03-11 23:07:11 +01:00
QString filename ( cover_pattern_ ) ;
filename . replace ( " %albumartist " , albumartist ) ;
filename . replace ( " %artist " , artist ) ;
filename . replace ( " %album " , album ) ;
return filename ;
2018-02-27 18:06:05 +01:00
}
bool AlbumCoverChoiceController : : IsKnownImageExtension ( const QString & suffix ) {
if ( ! sImageExtensions ) {
sImageExtensions = new QSet < QString > ( ) ;
( * sImageExtensions ) < < " png " < < " jpg " < < " jpeg " < < " bmp " < < " gif " < < " xpm " < < " pbm " < < " pgm " < < " ppm " < < " xbm " ;
}
return sImageExtensions - > contains ( suffix ) ;
}
bool AlbumCoverChoiceController : : CanAcceptDrag ( const QDragEnterEvent * e ) {
for ( const QUrl & url : e - > mimeData ( ) - > urls ( ) ) {
const QString suffix = QFileInfo ( url . toLocalFile ( ) ) . suffix ( ) . toLower ( ) ;
if ( IsKnownImageExtension ( suffix ) ) return true ;
}
if ( e - > mimeData ( ) - > hasImage ( ) ) {
return true ;
}
return false ;
}
QString AlbumCoverChoiceController : : SaveCover ( Song * song , const QDropEvent * e ) {
for ( const QUrl & url : e - > mimeData ( ) - > urls ( ) ) {
2018-09-10 21:58:57 +02:00
2018-02-27 18:06:05 +01:00
const QString filename = url . toLocalFile ( ) ;
const QString suffix = QFileInfo ( filename ) . suffix ( ) . toLower ( ) ;
if ( IsKnownImageExtension ( suffix ) ) {
SaveCover ( song , filename ) ;
return filename ;
}
}
if ( e - > mimeData ( ) - > hasImage ( ) ) {
QImage image = qvariant_cast < QImage > ( e - > mimeData ( ) - > imageData ( ) ) ;
if ( ! image . isNull ( ) ) {
2019-03-11 23:07:11 +01:00
QString cover_path = SaveCoverToFileAutomatic ( song , image ) ;
if ( cover_path . isEmpty ( ) ) return QString ( ) ;
2018-02-27 18:06:05 +01:00
SaveCover ( song , cover_path ) ;
return cover_path ;
}
}
return QString ( ) ;
}