From a2c0e4d4b1c801ea29090e3eb36faefb5b7c0374 Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Mon, 20 Apr 2020 18:03:18 +0200 Subject: [PATCH] Improve album cover loader, lyrics search and streaming support - Improve album cover loader - Add album cover loader result struct - Move album cover thumbnail scaling to album cover loader - Make init art manual look for album cover images in song directory - Make album cover search work for songs outside of collection and streams - Make album cover search work based on artist + title if album is not present - Update art manual in playlist for local files, devices and CDDA - Make lyrics search work for streams - Add stream dialog to menu - Remove dead code in InternetSearchModel - Simplify code in InternetSearchView --- data/icons.qrc | 3 + data/icons/22x22/document-open-remote.png | Bin 0 -> 1287 bytes data/icons/32x32/document-open-remote.png | Bin 0 -> 2042 bytes data/icons/48x48/document-open-remote.png | Bin 0 -> 3377 bytes src/CMakeLists.txt | 3 + src/collection/collectionfilterwidget.cpp | 2 + src/collection/collectionmodel.cpp | 17 +- src/collection/collectionmodel.h | 3 +- src/collection/collectionplaylistitem.cpp | 13 + src/collection/collectionplaylistitem.h | 2 + src/context/contextalbum.cpp | 7 +- src/context/contextalbumsmodel.cpp | 14 +- src/context/contextalbumsmodel.h | 3 +- src/context/contextview.cpp | 20 +- src/context/contextview.h | 3 +- src/core/mainwindow.cpp | 57 +-- src/core/mainwindow.h | 13 +- src/core/mainwindow.ui | 6 + src/core/metatypes.cpp | 3 + src/core/mpris2.cpp | 20 +- src/core/mpris2.h | 3 +- src/core/song.cpp | 46 ++- src/core/song.h | 1 + src/core/standarditemiconloader.cpp | 10 +- src/core/standarditemiconloader.h | 8 +- .../albumcoverchoicecontroller.cpp | 8 +- src/covermanager/albumcoverchoicecontroller.h | 4 +- src/covermanager/albumcoverfetcher.cpp | 14 +- src/covermanager/albumcoverfetcher.h | 7 +- src/covermanager/albumcoverfetchersearch.cpp | 16 +- src/covermanager/albumcoverloader.cpp | 326 ++++++++++-------- src/covermanager/albumcoverloader.h | 41 ++- src/covermanager/albumcoverloaderoptions.h | 9 +- src/covermanager/albumcoverloaderresult.h | 52 +++ src/covermanager/albumcovermanager.cpp | 18 +- src/covermanager/albumcovermanager.h | 3 +- src/covermanager/albumcoversearcher.cpp | 33 +- src/covermanager/albumcoversearcher.h | 3 +- src/covermanager/coverprovider.cpp | 4 +- src/covermanager/coverprovider.h | 6 +- src/covermanager/currentalbumcoverloader.cpp | 73 ++-- src/covermanager/currentalbumcoverloader.h | 8 +- src/covermanager/deezercoverprovider.cpp | 62 ++-- src/covermanager/deezercoverprovider.h | 2 +- src/covermanager/discogscoverprovider.cpp | 6 +- src/covermanager/discogscoverprovider.h | 2 +- src/covermanager/lastfmcoverprovider.cpp | 6 +- src/covermanager/lastfmcoverprovider.h | 2 +- src/covermanager/musicbrainzcoverprovider.cpp | 6 +- src/covermanager/musicbrainzcoverprovider.h | 2 +- src/covermanager/tidalcoverprovider.cpp | 6 +- src/covermanager/tidalcoverprovider.h | 2 +- src/dialogs/addstreamdialog.cpp | 54 +++ src/dialogs/addstreamdialog.h | 51 +++ src/dialogs/addstreamdialog.ui | 98 ++++++ src/dialogs/edittagdialog.cpp | 14 +- src/dialogs/edittagdialog.h | 3 +- src/engine/gstenginepipeline.cpp | 10 + src/internet/internetplaylistitem.cpp | 13 + src/internet/internetplaylistitem.h | 3 +- src/internet/internetsearchitemdelegate.cpp | 6 +- src/internet/internetsearchmodel.cpp | 42 +-- src/internet/internetsearchmodel.h | 10 +- src/internet/internetsearchsortmodel.cpp | 8 +- src/internet/internetsearchview.cpp | 161 +++++---- src/internet/internetsearchview.h | 20 +- src/playlist/playlist.cpp | 92 +++-- src/playlist/playlist.h | 4 + src/playlist/playlistitem.cpp | 24 +- src/playlist/playlistitem.h | 3 +- src/playlist/playlistmanager.cpp | 8 +- src/playlist/playlistview.cpp | 15 +- src/playlist/playlistview.h | 3 +- src/playlist/songplaylistitem.cpp | 7 + src/playlist/songplaylistitem.h | 2 +- src/widgets/playingwidget.cpp | 8 +- src/widgets/playingwidget.h | 4 +- 77 files changed, 1057 insertions(+), 584 deletions(-) create mode 100644 data/icons/22x22/document-open-remote.png create mode 100644 data/icons/32x32/document-open-remote.png create mode 100644 data/icons/48x48/document-open-remote.png create mode 100644 src/covermanager/albumcoverloaderresult.h create mode 100644 src/dialogs/addstreamdialog.cpp create mode 100644 src/dialogs/addstreamdialog.h create mode 100644 src/dialogs/addstreamdialog.ui diff --git a/data/icons.qrc b/data/icons.qrc index 9993765b1..89503e12e 100644 --- a/data/icons.qrc +++ b/data/icons.qrc @@ -203,6 +203,7 @@ icons/48x48/document-download.png icons/48x48/document-new.png icons/48x48/document-open-folder.png + icons/48x48/document-open-remote.png icons/48x48/document-open.png icons/48x48/document-save.png icons/48x48/document-search.png @@ -297,6 +298,7 @@ icons/32x32/document-download.png icons/32x32/document-new.png icons/32x32/document-open-folder.png + icons/32x32/document-open-remote.png icons/32x32/document-open.png icons/32x32/document-save.png icons/32x32/document-search.png @@ -391,6 +393,7 @@ icons/22x22/document-download.png icons/22x22/document-new.png icons/22x22/document-open-folder.png + icons/22x22/document-open-remote.png icons/22x22/document-open.png icons/22x22/document-save.png icons/22x22/document-search.png diff --git a/data/icons/22x22/document-open-remote.png b/data/icons/22x22/document-open-remote.png new file mode 100644 index 0000000000000000000000000000000000000000..01273d8e4b1fbc3efed2fa4394f9e20942fe0952 GIT binary patch literal 1287 zcmV+i1^D`jP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L011Qu011Qvs^VjL00007bV*G`2ipY# z3n3Dvw~GA$00f6gL_t(I%cYY`Y*bYk$A90sciNc_eNAaW`ruViq$P!jXrdw2gv2Pi zAYJ%iBP`swFeb7wXhaiTz{ag^NPGle3o$AfQGBC7iiOhBhd4~z=}bE_omXe(-g8{s zJ8ey5<=vdz^PTT^|KIQva`_Oh&rNqcklkdiRtulr{U!B zHQj5YA06G-WdjuQ3zUjEhN2l_S0>rkSw}?#T~kA6SA_hc!^w{?(bnF=Os2^BpGFz{ za~g47!UfIEcuqRI+qnAs1ckO>=i#?6H7bBYc8+|Zh;d3xUmT`$!)nsGG6$dBO)^_1 zRn$E4*iIgJa4S8#Hgi3iqN%l>tX53LGYnsgaqj#Wy?eTuxG+vxr?mpeXOiTLMUs=Z zsI5`tOAZaqVHUOE7$}roy4stVOXvAzC`Qq;NR~9gh9H6ZAU7uGIrRKKHr&;YD#QBw z+FS+TRw`t3ITFz%We0v8oMvcZ4s8TG5FpYNqEc~~j;C06M?HjUQFXOQs0OPph-+y= zL5sRjfYvn;K78TKS{uNrl*#3?IQcxyEn#lXW(h>X#8Y`reKSl?cPr7^EWwb?j_wGP zsYS$baFoHgF3piugo6QQ6Gg0mP)HV9?6-`u-re^csdNT|CReNwtPPN>XwLpTij?Af zdG-qFMVDluOhYih%zOdc7987v&2RVj=*8nde4H})VYJq-wQMn zV!8q~EnzmUujA8RTX(7E~|4^7~rl@QSqTJppC{DgN-q`8qmgr z2^h1i$gHry;Q8ws4`c)76*U*ImDg_@-}s8(tuz>aylPm;G92ygSBwW${+QJXT-Trt z*dU0qs5PEqM?5u%pSdiLuL0Mui!28xE9#;YB4E$VXE^uARkW*D-kzngSNb46LF;9j zGAspa0L!+Sorqy~cec{Ey$9FTUI0Eo{D!K>HOdRp0G4kRc(zp)8v|n7j0I+~bA?JN zk;v4R3*_22PxsS(hkn48b$1R@*gy89Us#27>{%79>u xc*l)*eT`9`9~ciW8pJHUix@Np5ko5W{y%YKFRh4C^?U#T002ovPDHLkV1l=WXLSGo literal 0 HcmV?d00001 diff --git a/data/icons/32x32/document-open-remote.png b/data/icons/32x32/document-open-remote.png new file mode 100644 index 0000000000000000000000000000000000000000..c48683fe2e4f7bdbcf2d3f4aed799c341b1af9f2 GIT binary patch literal 2042 zcmVPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2ipY# z77{NVN|XNp00(+WL_t(o!{wG;jGSc^$A9N}-0Y^@DSB(v1^pOI=#6 z2oVTH*}Qc`$M4=MM_xO7?u|bkdj@^!#L&=pXf%F);|e_W;@@76qV53%Oy7b6ieO{Q zEm53I>=`fIe%F|ejgQF7zdA;e1Dp>8!<3gWY@k=v3{tm=TA?` z%ZL8@y`_bp9Quzf_|mh#)vr8$kj14gajF=}Rt$zTin#Q~+w7aTjrm%O`}W<*@TT=l zU90oX$r+{=+nhOhnXi50^9&S)sWX$rv8C+<5n<@X#~pjOkyu4=i6q@G z{P9PRzZn8Vtp;bWtgujPV@#HL2#67`ynd9gKed+&SL#S9cZnE-hU2w(*W4G z%R;pYHX@EAToRF_2^Wu^;?d9CPOTX;TWNCd?rj(mHm)mh`D%q{zW)l#8t|J}&r+!@ zb9JW5{Bj2?Fg4$#FfzmkSL@6-x(pAMx$w?;{(kBjmuH$%nY(82i9!&NMqN^u5(>l! zNjG9^n9yj&L~59rxSc)sjxkf|(2A8!TQ>3dm+xoSuG`pg&sJW3?*Du$Jr7OUzuD<%5DwJ>lS9KZ64Y_ z$|H~5%|atWjbWfHR4N?~9=Mlwlydm^MTSO)XwTNEtVC#`hQukYD5cSiC=VEJ zDu>K2E;HZgaPDf2wo`(EkZ>S`LI|Y-Zyi0)*wzt-N6M7fl^DNsl*;5RirUo*I2)r$ zgmVrDB(bC0jHoYndF|*V_wW4#lhZZYNkVlrFFuZZymsyU6;@E?5PSBnj=P1J)rTNE9)&VUW3{HnomrYJmt^R*QKj4|AlSf(p2L_wVc>rkhp-Gr-`+q5>6Xskr2D%I*W+7Pb-h~qY$ zdIg6A6>Pgpc}Q7kHn2fJZFz|kr&gG|euWpF|0dY4~GEr*Jr8KI|wfGkUG+arJXuVG33PQX|`-0WbEca3dJ(VPcITk=3M$z zGw^TMr*f`uJtv(41ru=o)IT2$pMUA_+mGy@AWf20#QAO`APP?NER^>y)Ch0{0@h_V zRv{2ba|e>y8v&y|8zW#n&;)~ZN_gpN<$+sAtF)4IHG{Qi^WW-}z+@7ex-7dKoO&bm z=?WRZKp<5-akDig``;>rC?rk^&oL&(&pJ2U-DG~dVX8=rT47~b8?CHHi&w%NY7*JdLw2e z1J`d`@;xOF;>qSbk@F`7$^waIZ4U*-D0H0?`V0YzrdNJ!Z`>Ria`pX8JWskV3z%wt z!u8r_=}kiDZOy8!2qp|DiW2nAvH&LR_1Cxj-)1_b7fga|J@0XmOzLud2t9Fg+rWeb zB7`R|E%4s;BZ#_;*=q!3?aeg@awD-}&2_F{q}&?c0fzR{0^#^bnN6cxaGLQz{OA~Z zJjj2$J_p8=PqkMO@e|NrrGANa+5;$oVJ@B~v{vax30B=lB-7Hnr1ocTeM!?bRni+& zi!~L?TY@np))C&bZIsPPkZav)qbkCYF*9+>hM&O!GnC57hRx$Ve{7lHI-qQ(vg93`^iM zk)F6HPy)Jr$n$>Y&I0wPU>`4XvW9)tQ-3n%;KeEHhsQ1nP`qjT=blw{>p(=+8Bl-b zsj<$9fKgQeH9k=TDynK!Rg5u~>>{LMaHuGPLqrYYG`nax1r*A50@_+yQ Y3zLu&OyV`D^#A|>07*qoM6N<$g6>!1rvLx| literal 0 HcmV?d00001 diff --git a/data/icons/48x48/document-open-remote.png b/data/icons/48x48/document-open-remote.png new file mode 100644 index 0000000000000000000000000000000000000000..18b19e2c10e96bac1a34af6c7b3cad20414df7ac GIT binary patch literal 3377 zcmV-14bJk3P)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L02IRj02IRk6>v>L00007bV*G`2ipV| z76}>v)O9`p01UB7L_t(&-tC%Qj9le;$A8az&dltWckQ*k>)pl1CfIx%tUw45(gFcB zq)Ah?L_&gIrBX#2fvR5QqV09mQdLzXl#8mWDn+?St11_v4XF}{2z3ENu(4ST?ArKi zzh-xKKF-XX_v7isIkR)TcB{5oOI5{@X3v>(-t&Lo=llP>v+&9B|N9Uiy85GUZrKD| zc8pJd>rrE(uiSO#-uose$37L6qU}*^NUaaGwf5rddlx@kU9SBs>9+r|Tl>!O{~H2S zo*tc^Iq)a<-gDrY$G&*`@YK{c78e@YPP%ewVVyXNs5Lq?+n!1#Ru@#N)r9wspFMl& z{OY$GD}V7_fSo(PNwxZ&|Gfae`=>vAPEGt*AzUe$06zBx1ymq1!nxPy|6%X;k>7db z;e#SZl*q{W^XoKPDM>q})k^VRN!m683Q80yCW_g2a8eg8*5vh{pI%yCSbeHd{mY;H zN)~wZ8-JtsJop%612J9%lg)<+7y(5P$-bxU4)6Z>D2H#_$<)*iz5d4CGO}%u*WWn9 z?Cb*Vt|MtXPJUEnc&N;+H&3#@+QeIr5d~HDTt9({1VQw~u`}}4(f40iUwr->|5E|t z-bK4r!y~+Rp^iu2X$8TDg4-!Ri=&5^gNr$9knVYM!?hGrZ zSGn!*J|?ak=P;g+emct&&wV*JKZs^)2%7y#tPZ|^^6i&^;mZW@-jQ@ug0tDXxyUM+ z7JPuy|MKrV`}_BE_S`z{)U#Ia^1y?KNS(t4B@J-s&?L9qJjv|r0(I80Ln3JDAxs%-HBKC*PW5eAhO<|J|23@*AH)Lm(Ih&En4n zC|AmoG-^XvxR4Pcc#Cr`3t9(}fK!N!;mp6i%anRiC z15AqH?|$$W-~Z7p2lkIMG;)ED&#Z9dwkgWPLoBU#Xml*oyN6j=Z_`#oWu?tr&2!t0 zJNWz;Zs*duDra6g!KIJRv3vg>)GEe+%av)Jqqe$uWei}1ZfXh6p=66!1uxL5HrS0P z9*F5$rQS|yCXOR_?PJ?uiQ$2Wxr;3l>zQBf5D&(D^-Fhh{?a=2Mu*YSGTx;Oj}A~N zMGTD$GC46uquHgMcy>)2>WSyoSB}wK>+;a&4)W^Hf64gFBY1@&J0 zAy~Xi2?`D<835a_k6q&KyZ3YM z#2nMN?jzNZ$01k|!bWHp1PDH0ZDs)^3z|*<-|jF`iRpA4wMK`X*75iw2ibr9I7_t- zUX_7?n2|VUexXjQ>v`fg?;!Osdv=+6vqL;MfOyBu)EKYNt{?=Y6cIuoE-SlcCK($a zWqA7#yC=8fyD3#`V5y2bI0dSB$>OjqhT!~_0%!<$o058q*F1(6Jt43>aZ%_b}7DhFD!|@vm>5g(xCPJ(Zy{ z14AV=itr}Jlp?egLljdg3(HFlc1-L*)ge&@BZ0~NJ6Sth#jD3UhqaEbwMfvb2@r7J z(yTARuke#P|Ygz&_K+-p!GKSU}eUORq?y?eLQXj_ax91C$IxZttD@xi%Os`Vz3 z5z4Xf(BWNtyqxgPnYFAcnE*zCfJOlg0WpEmkrG~%8}^PeS}}ZVz^S9#Ot9S+8Zvee z#g5>^RnjOP>r?QKpc((6+rfHI5CsEPmA6kXa?8!TdF#|7ci*vxKYQU-ZrL|Nqm?i- zF~scrO7^n!;0|7UbDr%JBiJ;>m=e}?xv;#-ws|FK)%f^) zjkVQfW>2nvf< zwh$4FF~sFTl6vhb0;Fwf)g=g?cJHANR9s9j4LYeMjw2j~59ZGB^09;`AH9iWeS!5> zhld~jH9mXqVY)V4(PkLrN`8J7S{$;VLPW^v7$SK^EgWfTNz=dn(IGC!sE}n3gqg{0EUYCQoEhW3`yb#7kA9B1v+J}v z{xTMsEl$x*3+}PYjZ7jCRLBah=I6@=++e1WVQ`?tvybl=0eJR>pUyx2*!}yhP5<+* zL5mYBsy(eivzf58l+f<_oOkzMF$H(h{3144QBpRB_pNZ1X|IM+DaZWLR}M%FaPH#T z{$IXx5%CtUk`;`+0}BN($tUbJIz1s&GE)n((ZL7pPgry+A&cSO!b0d)YD4XNW_`;dKgGH!B$`(SwCQMj5s$_ zCP2Yt#UteQ3uKgbvrB1V(v8$v!0_64-mSps`G2&zC(iq9RJHG$1@K;D)4LyS3btZa z35CGhY79f}c7V~M3*|j;CpA$Y`q_u08f#>5DkXRH|L zhHMY1M*^<}M#wYEh5@3OANK^fSZy-B-l3hhlcikxQo+nFzi$g>j5YfnOYQ=iX;$-~ z4M2H|FnamFmk>fRh#}h|5rj&?Q z8I|w>E3(n37=a4V$*W<}2VG98L^AVk@;{ zC=3fEdG(AnGi{L)#FAIpEy5Vc1ujL(dbJ@(Y! zelN~EZT!%alm;KhXayCsVQtl{(bb;y)nwP&{|I^6%g2+CmyHbx!Juf~b%lz^&eAeb(}z0A^y?+FKhdEm=Gj9B|nG9$)W88#-X7`6qj zC73w#N->bO1lIxA7JM7hy5OuJK?pY6BMAwJ11*CYP@*Vv5K~f0gGw~43=Am@!oYBz z^$n;OBvvZpnd*D{irKFgFm|Kzqp$kyzxT<%LVR-k%Ex~ICZe!MNyW^I00000NkvXX Hu0mjfm4AKE literal 0 HcmV?d00001 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 623c46603..28614dc03 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -254,6 +254,7 @@ set(SOURCES dialogs/errordialog.cpp dialogs/edittagdialog.cpp dialogs/trackselectiondialog.cpp + dialogs/addstreamdialog.cpp widgets/autoexpandingtreeview.cpp widgets/busyindicator.cpp @@ -439,6 +440,7 @@ set(HEADERS dialogs/console.h dialogs/edittagdialog.h dialogs/trackselectiondialog.h + dialogs/addstreamdialog.h widgets/autoexpandingtreeview.h widgets/busyindicator.h @@ -537,6 +539,7 @@ set(UI dialogs/console.ui dialogs/edittagdialog.ui dialogs/trackselectiondialog.ui + dialogs/addstreamdialog.ui widgets/trackslider.ui widgets/osdpretty.ui diff --git a/src/collection/collectionfilterwidget.cpp b/src/collection/collectionfilterwidget.cpp index ec92b22b8..be32fb0ce 100644 --- a/src/collection/collectionfilterwidget.cpp +++ b/src/collection/collectionfilterwidget.cpp @@ -297,6 +297,7 @@ void CollectionFilterWidget::SetCollectionModel(CollectionModel *model) { } void CollectionFilterWidget::GroupByClicked(QAction *action) { + if (action->property("group_by").isNull()) { group_by_dialog_->show(); return; @@ -304,6 +305,7 @@ void CollectionFilterWidget::GroupByClicked(QAction *action) { CollectionModel::Grouping g = action->property("group_by").value(); model_->SetGroupBy(g); + } void CollectionFilterWidget::GroupingChanged(const CollectionModel::Grouping &g) { diff --git a/src/collection/collectionmodel.cpp b/src/collection/collectionmodel.cpp index 71b1dbad3..7c69bef9c 100644 --- a/src/collection/collectionmodel.cpp +++ b/src/collection/collectionmodel.cpp @@ -66,10 +66,9 @@ #include "playlist/playlistmanager.h" #include "playlist/songmimedata.h" #include "covermanager/albumcoverloader.h" +#include "covermanager/albumcoverloaderresult.h" #include "settings/collectionsettingspage.h" -using std::bind; -using std::sort; using std::placeholders::_1; using std::placeholders::_2; @@ -116,7 +115,7 @@ CollectionModel::CollectionModel(CollectionBackend *backend, Application *app, Q cover_loader_options_.scale_output_image_ = true; if (app_) - connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage))); + connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult))); QIcon nocover = IconLoader::Load("cdcase"); no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); @@ -613,9 +612,7 @@ QVariant CollectionModel::AlbumIcon(const QModelIndex &idx) { } -void CollectionModel::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) { - - Q_UNUSED(cover_url); +void CollectionModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) { if (!pending_art_.contains(id)) return; @@ -628,26 +625,26 @@ void CollectionModel::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, pending_cache_keys_.remove(cache_key); // Insert this image in the cache. - if (image.isNull()) { + if (result.image_scaled.isNull()) { // Set the no_cover image so we don't continually try to load art. QPixmapCache::insert(cache_key, no_cover_icon_); } else { QPixmap image_pixmap; - image_pixmap = QPixmap::fromImage(image); + image_pixmap = QPixmap::fromImage(result.image_scaled); QPixmapCache::insert(cache_key, image_pixmap); } // If we have a valid cover not already in the disk cache if (use_disk_cache_) { std::unique_ptr cached_img(sIconCache->data(QUrl(cache_key))); - if (!cached_img && !image.isNull()) { + if (!cached_img && !result.image_scaled.isNull()) { QNetworkCacheMetaData item_metadata; item_metadata.setSaveToDisk(true); item_metadata.setUrl(QUrl(cache_key)); QIODevice* cache = sIconCache->prepare(item_metadata); if (cache) { - image.save(cache, "XPM"); + result.image_scaled.save(cache, "XPM"); sIconCache->insert(cache); } } diff --git a/src/collection/collectionmodel.h b/src/collection/collectionmodel.h index e8b8b9112..68e4dbe56 100644 --- a/src/collection/collectionmodel.h +++ b/src/collection/collectionmodel.h @@ -44,6 +44,7 @@ #include "core/simpletreemodel.h" #include "core/song.h" +#include "covermanager/albumcoverloader.h" #include "collectionquery.h" #include "collectionitem.h" #include "sqlrow.h" @@ -210,7 +211,7 @@ signals: // Called after ResetAsync void ResetAsyncQueryFinished(QFuture future); - void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image); + void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result); private: // Provides some optimisations for loading the list of items in the root. diff --git a/src/collection/collectionplaylistitem.cpp b/src/collection/collectionplaylistitem.cpp index 666321de9..5fd437327 100644 --- a/src/collection/collectionplaylistitem.cpp +++ b/src/collection/collectionplaylistitem.cpp @@ -43,20 +43,33 @@ void CollectionPlaylistItem::Reload() { } bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) { + // Rows from the songs tables come first song_.InitFromQuery(query, true); song_.set_source(Song::Source_Collection); return song_.is_valid(); + } QVariant CollectionPlaylistItem::DatabaseValue(DatabaseColumn column) const { + switch (column) { case Column_CollectionId: return song_.id(); default: return PlaylistItem::DatabaseValue(column); } + } Song CollectionPlaylistItem::Metadata() const { + if (HasTemporaryMetadata()) return temp_metadata_; return song_; + +} + +void CollectionPlaylistItem::SetArtManual(const QUrl &cover_url) { + + song_.set_art_manual(cover_url); + temp_metadata_.set_art_manual(cover_url); + } diff --git a/src/collection/collectionplaylistitem.h b/src/collection/collectionplaylistitem.h index 965c69de1..b9c6ac7df 100644 --- a/src/collection/collectionplaylistitem.h +++ b/src/collection/collectionplaylistitem.h @@ -46,6 +46,8 @@ class CollectionPlaylistItem : public PlaylistItem { bool IsLocalCollectionItem() const { return true; } + void SetArtManual(const QUrl &cover_url); + protected: QVariant DatabaseValue(DatabaseColumn column) const; Song DatabaseSongMetadata() const { return Song(Song::Source_Collection); } diff --git a/src/context/contextalbum.cpp b/src/context/contextalbum.cpp index 08a836896..fc7f1b9d1 100644 --- a/src/context/contextalbum.cpp +++ b/src/context/contextalbum.cpp @@ -57,7 +57,8 @@ ContextAlbum::ContextAlbum(QWidget *parent) : cover_loader_options_.desired_height_ = 600; cover_loader_options_.pad_output_image_ = true; cover_loader_options_.scale_output_image_ = true; - pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_strawberry_)); + QPair images = AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_strawberry_); + pixmap_current_ = QPixmap::fromImage(images.first); connect(timeline_fade_, SIGNAL(valueChanged(qreal)), SLOT(FadePreviousTrack(qreal))); timeline_fade_->setDirection(QTimeLine::Backward); // 1.0 -> 0.0 @@ -89,7 +90,7 @@ void ContextAlbum::DrawImage(QPainter *p) { if (width() != prev_width_) { cover_loader_options_.desired_height_ = width() - kWidgetSpacing; - pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_)); + pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_).first); prev_width_ = width(); } @@ -118,7 +119,7 @@ void ContextAlbum::FadePreviousTrack(const qreal value) { void ContextAlbum::ScaleCover() { cover_loader_options_.desired_height_ = width() - kWidgetSpacing; - pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_)); + pixmap_current_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_).first); prev_width_ = width(); update(); diff --git a/src/context/contextalbumsmodel.cpp b/src/context/contextalbumsmodel.cpp index 07bdb7a3b..3a8b0e5a2 100644 --- a/src/context/contextalbumsmodel.cpp +++ b/src/context/contextalbumsmodel.cpp @@ -48,11 +48,11 @@ #include "playlist/playlistmanager.h" #include "playlist/songmimedata.h" #include "covermanager/albumcoverloader.h" +#include "covermanager/albumcoverloaderoptions.h" +#include "covermanager/albumcoverloaderresult.h" #include "contextalbumsmodel.h" -using std::bind; -using std::sort; using std::placeholders::_1; using std::placeholders::_2; @@ -71,7 +71,7 @@ ContextAlbumsModel::ContextAlbumsModel(CollectionBackend *backend, Application * cover_loader_options_.pad_output_image_ = true; cover_loader_options_.scale_output_image_ = true; - connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage))); + connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult))); QIcon nocover = IconLoader::Load("cdcase"); no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(kPrettyCoverSize, kPrettyCoverSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); @@ -146,9 +146,7 @@ QVariant ContextAlbumsModel::AlbumIcon(const QModelIndex &index) { } -void ContextAlbumsModel::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) { - - Q_UNUSED(cover_url); +void ContextAlbumsModel::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) { if (!pending_art_.contains(id)) return; @@ -161,13 +159,13 @@ void ContextAlbumsModel::AlbumCoverLoaded(const quint64 id, const QUrl &cover_ur pending_cache_keys_.remove(cache_key); // Insert this image in the cache. - if (image.isNull()) { + if (result.image_scaled.isNull()) { // Set the no_cover image so we don't continually try to load art. QPixmapCache::insert(cache_key, no_cover_icon_); } else { QPixmap image_pixmap; - image_pixmap = QPixmap::fromImage(image); + image_pixmap = QPixmap::fromImage(result.image_scaled); QPixmapCache::insert(cache_key, image_pixmap); } diff --git a/src/context/contextalbumsmodel.h b/src/context/contextalbumsmodel.h index 9eb1bc3aa..3541af3be 100644 --- a/src/context/contextalbumsmodel.h +++ b/src/context/contextalbumsmodel.h @@ -45,6 +45,7 @@ #include "collection/collectionitem.h" #include "collection/sqlrow.h" #include "covermanager/albumcoverloaderoptions.h" +#include "covermanager/albumcoverloaderresult.h" class QMimeData; @@ -99,7 +100,7 @@ class ContextAlbumsModel : public SimpleTreeModel { void LazyPopulate(CollectionItem *item, bool signal); private slots: - void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &image); + void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result); private: QueryResult RunQuery(CollectionItem *parent); diff --git a/src/context/contextview.cpp b/src/context/contextview.cpp index 912c528f8..eff944d8a 100644 --- a/src/context/contextview.cpp +++ b/src/context/contextview.cpp @@ -124,6 +124,7 @@ ContextView::ContextView(QWidget *parent) : label_device_icon_(new QLabel(this)), label_engine_icon_(new QLabel(this)), spacer_bottom_(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Expanding)), + lyrics_tried_(false), lyrics_id_(-1), prev_width_(0) { @@ -365,19 +366,22 @@ void ContextView::Error() {} void ContextView::SongChanged(const Song &song) { - if (widget_stacked_->currentWidget() == widget_play_ && song_playing_.is_valid() && song == song_playing_) { + if (widget_stacked_->currentWidget() == widget_play_ && song_playing_.is_valid() && song == song_playing_ && song.title() == song_playing_.title() && song.album() == song_playing_.album() && song.artist() == song_playing_.artist()) { UpdateSong(song); } else { song_prev_ = song_playing_; + song_playing_ = song; lyrics_ = song.lyrics(); lyrics_id_ = -1; - song_playing_ = song; + lyrics_tried_ = false; SetSong(); - if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && !song.artist().isEmpty() && !song.title().isEmpty()) { - lyrics_fetcher_->Clear(); - lyrics_id_ = lyrics_fetcher_->Search(song.effective_albumartist(), song.album(), song.title()); - } + } + + if (lyrics_.isEmpty() && action_show_lyrics_->isChecked() && !song.artist().isEmpty() && !song.title().isEmpty() && !lyrics_tried_ && lyrics_id_ == -1) { + lyrics_fetcher_->Clear(); + lyrics_tried_ = true; + lyrics_id_ = lyrics_fetcher_->Search(song.effective_albumartist(), song.album(), song.title()); } } @@ -684,9 +688,7 @@ void ContextView::dropEvent(QDropEvent *e) { } -void ContextView::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) { - - Q_UNUSED(cover_url); +void ContextView::AlbumCoverLoaded(const Song &song, const QImage &image) { if (song != song_playing_ || image == image_original_) return; diff --git a/src/context/contextview.h b/src/context/contextview.h index cbb7f5c05..265a342ed 100644 --- a/src/context/contextview.h +++ b/src/context/contextview.h @@ -91,7 +91,7 @@ class ContextView : public QWidget { void Stopped(); void Error(); void SongChanged(const Song &song); - void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image); + void AlbumCoverLoaded(const Song &song, const QImage &image); void FadeStopFinished(); void UpdateLyrics(const quint64 id, const QString &provider, const QString &lyrics); @@ -164,6 +164,7 @@ class ContextView : public QWidget { Song song_playing_; Song song_prev_; QImage image_original_; + bool lyrics_tried_; qint64 lyrics_id_; QString lyrics_; QString title_fmt_; diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index 449b96df1..f2cdf2faf 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2013, Jonas Kvinge + * Copyright 2013-2020, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -96,6 +96,7 @@ #include "dialogs/console.h" #include "dialogs/trackselectiondialog.h" #include "dialogs/edittagdialog.h" +#include "dialogs/addstreamdialog.h" #include "organise/organisedialog.h" #include "widgets/fancytabwidget.h" #include "widgets/playingwidget.h" @@ -131,7 +132,7 @@ #endif #include "covermanager/albumcovermanager.h" #include "covermanager/albumcoverchoicecontroller.h" -#include "covermanager/albumcoverloader.h" +#include "covermanager/albumcoverloaderresult.h" #include "covermanager/currentalbumcoverloader.h" #ifndef Q_OS_WIN # include "device/devicemanager.h" @@ -178,10 +179,6 @@ # include "windows7thumbbar.h" #endif -using std::bind; -using std::floor; -using std::stable_sort; - const char *MainWindow::kSettingsGroup = "MainWindow"; const char *MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)"); @@ -231,6 +228,11 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co TranscodeDialog *dialog = new TranscodeDialog(this); return dialog; }), + add_stream_dialog_([=]() { + AddStreamDialog *add_stream_dialog = new AddStreamDialog; + connect(add_stream_dialog, SIGNAL(accepted()), this, SLOT(AddStreamAccepted())); + return add_stream_dialog; + }), #ifdef HAVE_SUBSONIC subsonic_view_(new InternetSongsView(app_, app->internet_services()->ServiceBySource(Song::Source_Subsonic), SubsonicSettingsPage::kSettingsGroup, SettingsDialog::Page_Subsonic, this)), #endif @@ -260,7 +262,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co // Initialise the UI ui_->setupUi(this); - connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage))); + connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult))); album_cover_choice_controller_->Init(app); connect(album_cover_choice_controller_->cover_from_file_action(), SIGNAL(triggered()), this, SLOT(LoadCoverFromFile())); connect(album_cover_choice_controller_->cover_to_file_action(), SIGNAL(triggered()), this, SLOT(SaveCoverToFile())); @@ -361,6 +363,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co ui_->action_add_file->setIcon(IconLoader::Load("document-open")); ui_->action_add_folder->setIcon(IconLoader::Load("document-open-folder")); + ui_->action_add_stream->setIcon(IconLoader::Load("document-open-remote")); ui_->action_shuffle_mode->setIcon(IconLoader::Load("media-playlist-shuffle")); ui_->action_repeat_mode->setIcon(IconLoader::Load("media-playlist-repeat")); ui_->action_new_playlist->setIcon(IconLoader::Load("document-new")); @@ -431,6 +434,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co connect(ui_->action_open_cd, SIGNAL(triggered()), SLOT(AddCDTracks())); connect(ui_->action_add_file, SIGNAL(triggered()), SLOT(AddFile())); connect(ui_->action_add_folder, SIGNAL(triggered()), SLOT(AddFolder())); + connect(ui_->action_add_stream, SIGNAL(triggered()), SLOT(AddStream())); connect(ui_->action_cover_manager, SIGNAL(triggered()), SLOT(ShowCoverManager())); connect(ui_->action_equalizer, SIGNAL(triggered()), equalizer_.get(), SLOT(show())); #if defined(HAVE_GSTREAMER) @@ -705,7 +709,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co connect(app_->player(), SIGNAL(Playing()), context_view_, SLOT(Playing())); connect(app_->player(), SIGNAL(Stopped()), context_view_, SLOT(Stopped())); connect(app_->player(), SIGNAL(Error()), context_view_, SLOT(Error())); - connect(this, SIGNAL(AlbumCoverReady(Song, QUrl, QImage)), context_view_, SLOT(AlbumCoverLoaded(Song, QUrl, QImage))); + connect(this, SIGNAL(AlbumCoverReady(Song, QImage)), context_view_, SLOT(AlbumCoverLoaded(Song, QImage))); connect(this, SIGNAL(SearchCoverInProgress()), context_view_->album_widget(), SLOT(SearchCoverInProgress())); connect(context_view_, SIGNAL(AlbumEnabledChanged()), SLOT(TabSwitched())); connect(context_view_->albums_widget(), SIGNAL(AddToPlaylistSignal(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); @@ -738,7 +742,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co connect(app_->player(), SIGNAL(Stopped()), ui_->widget_playing, SLOT(Stopped())); connect(app_->player(), SIGNAL(Error()), ui_->widget_playing, SLOT(Error())); connect(ui_->widget_playing, SIGNAL(ShowAboveStatusBarChanged(bool)), SLOT(PlayingWidgetPositionChanged(bool))); - connect(this, SIGNAL(AlbumCoverReady(Song, QUrl, QImage)), ui_->widget_playing, SLOT(AlbumCoverLoaded(Song, QUrl, QImage))); + connect(this, SIGNAL(AlbumCoverReady(Song, QImage)), ui_->widget_playing, SLOT(AlbumCoverLoaded(Song, QImage))); connect(this, SIGNAL(SearchCoverInProgress()), ui_->widget_playing, SLOT(SearchCoverInProgress())); connect(ui_->action_console, SIGNAL(triggered()), SLOT(ShowConsole())); @@ -1777,7 +1781,7 @@ void MainWindow::EditTracks() { void MainWindow::EditTagDialogAccepted() { - for (const PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) { + for (PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) { item->Reload(); } @@ -1923,6 +1927,16 @@ void MainWindow::AddCDTracks() { } +void MainWindow::AddStream() { add_stream_dialog_->show(); } + +void MainWindow::AddStreamAccepted() { + + MimeData* data = new MimeData; + data->setUrls(QList() << add_stream_dialog_->url()); + AddToPlaylist(data); + +} + void MainWindow::ShowInCollection() { // Show the first valid selected track artist/album in CollectionView @@ -2512,7 +2526,7 @@ void MainWindow::AutoCompleteTags() { void MainWindow::AutoCompleteTagsAccepted() { - for (const PlaylistItemPtr item : autocomplete_tag_items_) { + for (PlaylistItemPtr item : autocomplete_tag_items_) { item->Reload(); } autocomplete_tag_items_.clear(); @@ -2600,14 +2614,14 @@ void MainWindow::SearchCoverAutomatically() { } -void MainWindow::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) { +void MainWindow::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) { - if (song.effective_albumartist() != song_playing_.effective_albumartist() || song.effective_album() != song_playing_.effective_album() || song.title() != song_playing_.title()) return; + if (song != song_playing_) return; song_ = song; - image_original_ = image; + image_original_ = result.image_original; - emit AlbumCoverReady(song, cover_url, image); + emit AlbumCoverReady(song, result.image_original); GetCoverAutomatically(); @@ -2617,13 +2631,12 @@ void MainWindow::GetCoverAutomatically() { // Search for cover automatically? bool search = - song_.source() == Song::Source_Collection && - album_cover_choice_controller_->search_cover_auto_action()->isChecked() && - !song_.has_manually_unset_cover() && - !song_.art_automatic_is_valid() && - !song_.art_manual_is_valid() && - !song_.effective_albumartist().isEmpty() && - !song_.effective_album().isEmpty(); + album_cover_choice_controller_->search_cover_auto_action()->isChecked() && + !song_.has_manually_unset_cover() && + !song_.art_automatic_is_valid() && + !song_.art_manual_is_valid() && + !song_.effective_albumartist().isEmpty() && + !song_.effective_album().isEmpty(); if (search) { album_cover_choice_controller_->SearchCoverAutomatically(song_); diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h index 8b68c6326..77d9f02dd 100644 --- a/src/core/mainwindow.h +++ b/src/core/mainwindow.h @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2013, Jonas Kvinge + * Copyright 2013-2020, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -59,8 +59,7 @@ #include "playlist/playlistitem.h" #include "settings/settingsdialog.h" #include "settings/behavioursettingspage.h" - -using std::unique_ptr; +#include "covermanager/albumcoverloaderresult.h" class About; class AlbumCoverManager; @@ -95,6 +94,7 @@ class InternetTabsView; #ifdef Q_OS_WIN class Windows7ThumbBar; #endif +class AddStreamDialog; class MainWindow : public QMainWindow, public PlatformInterface { Q_OBJECT @@ -126,7 +126,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { bool LoadUrl(const QString& url); signals: - void AlbumCoverReady(const Song &song, const QUrl &cover_url, const QImage &image); + void AlbumCoverReady(const Song &song, const QImage &image); void SearchCoverInProgress(); // Signals that stop playing after track was toggled. void StopAfterToggled(bool stop); @@ -210,6 +210,8 @@ class MainWindow : public QMainWindow, public PlatformInterface { void AddFile(); void AddFolder(); void AddCDTracks(); + void AddStream(); + void AddStreamAccepted(); void CommandlineOptionsReceived(const quint32 instanceId, const QByteArray &string_options); @@ -251,7 +253,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { void UnsetCover(); void ShowCover(); void SearchCoverAutomatically(); - void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image); + void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result); void ScrobblingEnabledChanged(const bool value); void ScrobbleButtonVisibilityChanged(const bool value); @@ -308,6 +310,7 @@ class MainWindow : public QMainWindow, public PlatformInterface { #ifdef HAVE_GSTREAMER Lazy transcode_dialog_; #endif + Lazy add_stream_dialog_; #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) std::unique_ptr tag_fetcher_; diff --git a/src/core/mainwindow.ui b/src/core/mainwindow.ui index f683e9542..ff3211a61 100644 --- a/src/core/mainwindow.ui +++ b/src/core/mainwindow.ui @@ -469,6 +469,7 @@ + @@ -827,6 +828,11 @@ Rescan songs(s) + + + Add stream... + + diff --git a/src/core/metatypes.cpp b/src/core/metatypes.cpp index 0ed47731d..40de20b86 100644 --- a/src/core/metatypes.cpp +++ b/src/core/metatypes.cpp @@ -53,6 +53,7 @@ #include "collection/directory.h" #include "playlist/playlistitem.h" #include "playlist/playlistsequence.h" +#include "covermanager/albumcoverloaderresult.h" #include "covermanager/albumcoverfetcher.h" #include "equalizer/equalizer.h" @@ -98,6 +99,8 @@ void RegisterMetaTypes() { qRegisterMetaType >("QList"); qRegisterMetaType("PlaylistSequence::RepeatMode"); qRegisterMetaType("PlaylistSequence::ShuffleMode"); + qRegisterMetaType("AlbumCoverLoaderResult"); + qRegisterMetaType("AlbumCoverLoaderResult::Type"); qRegisterMetaType("CoverSearchResult"); qRegisterMetaType >("QList"); qRegisterMetaType("CoverSearchResults"); diff --git a/src/core/mpris2.cpp b/src/core/mpris2.cpp index ee0b7f789..8757cde9e 100644 --- a/src/core/mpris2.cpp +++ b/src/core/mpris2.cpp @@ -58,14 +58,13 @@ #include "playlist/playlistmanager.h" #include "playlist/playlistsequence.h" #include "covermanager/currentalbumcoverloader.h" +#include "covermanager/albumcoverloaderresult.h" #include #include #include #include -using std::reverse; - QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) { arg.beginStructure(); arg << playlist.id << playlist.name << playlist.icon; @@ -122,7 +121,7 @@ Mpris2::Mpris2(Application *app, QObject *parent) return; } - connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage))); + connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult))); connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State))); connect(app_->player(), SIGNAL(VolumeChanged(int)), SLOT(VolumeChanged())); @@ -378,7 +377,7 @@ QString Mpris2::current_track_id() const { // We send Metadata change notification as soon as the process of changing song starts... void Mpris2::CurrentSongChanged(const Song &song) { - AlbumCoverLoaded(song, QUrl(), QImage()); + AlbumCoverLoaded(song); EmitNotification("CanPlay"); EmitNotification("CanPause"); EmitNotification("CanGoNext", CanGoNext()); @@ -388,9 +387,7 @@ void Mpris2::CurrentSongChanged(const Song &song) { } // ... and we add the cover information later, when it's available. -void Mpris2::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) { - - Q_UNUSED(image); +void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) { last_metadata_ = QVariantMap(); song.ToXesam(&last_metadata_); @@ -398,9 +395,14 @@ void Mpris2::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QIm using mpris::AddMetadata; AddMetadata("mpris:trackid", current_track_id(), &last_metadata_); - if (cover_url.isValid()) { - AddMetadata("mpris:artUrl", cover_url.toString(), &last_metadata_); + QUrl cover_url; + if (result.cover_url.isValid() && result.cover_url.isLocalFile()) { + cover_url = result.cover_url; } + else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) { + cover_url = result.temp_cover_url; + } + if (cover_url.isValid()) AddMetadata("mpris:artUrl", result.cover_url.toString(), &last_metadata_); AddMetadata("year", song.year(), &last_metadata_); AddMetadata("bitrate", song.bitrate(), &last_metadata_); diff --git a/src/core/mpris2.h b/src/core/mpris2.h index 244fa9bd8..d882ff9a9 100644 --- a/src/core/mpris2.h +++ b/src/core/mpris2.h @@ -39,6 +39,7 @@ #include #include "engine/engine_fwd.h" +#include "covermanager/albumcoverloaderresult.h" class Application; class Song; @@ -204,7 +205,7 @@ signals: void PlaylistChanged(const MprisPlaylist &playlist); private slots: - void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image); + void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result = AlbumCoverLoaderResult()); void EngineStateChanged(Engine::State newState); void VolumeChanged(); diff --git a/src/core/song.cpp b/src/core/song.cpp index 7553ff11b..c8f9c4a08 100644 --- a/src/core/song.cpp +++ b/src/core/song.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ #include #include #include +#include #include #ifdef HAVE_LIBGPOD @@ -66,7 +68,6 @@ #include "covermanager/albumcoverloader.h" #include "tagreadermessages.pb.h" -using std::sort; #ifndef USE_SYSTEM_TAGLIB using namespace Strawberry_TagLib; #endif @@ -353,7 +354,8 @@ bool Song::is_cdda() const { return d->source_ == Source_CDDA; } bool Song::is_compilation() const { return (d->compilation_ || d->compilation_detected_ || d->compilation_on_) && !d->compilation_off_; } bool Song::art_automatic_is_valid() const { - return ( + return !d->art_automatic_.isEmpty() && + ( (d->art_automatic_.path() == kManuallyUnsetCover) || (d->art_automatic_.path() == kEmbeddedCover) || (d->art_automatic_.isValid() && !d->art_automatic_.isLocalFile()) || @@ -363,7 +365,8 @@ bool Song::art_automatic_is_valid() const { } bool Song::art_manual_is_valid() const { - return ( + return !d->art_manual_.isEmpty() && + ( (d->art_manual_.path() == kManuallyUnsetCover) || (d->art_manual_.path() == kEmbeddedCover) || (d->art_manual_.isValid() && !d->art_manual_.isLocalFile()) || @@ -655,6 +658,29 @@ Song::FileType Song::FiletypeByExtension(const QString &ext) { } +QString Song::ImageCacheDir(const Song::Source source) { + + switch (source) { + case Song::Source_Collection: + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/collectionalbumcovers"; + case Song::Source_Subsonic: + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/subsonicalbumcovers"; + case Song::Source_Tidal: + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/tidalalbumcovers"; + case Song::Source_Qobuz: + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/qobuzalbumcovers"; + case Song::Source_LocalFile: + case Song::Source_CDDA: + case Song::Source_Device: + case Song::Source_Stream: + case Song::Source_Unknown: + return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/albumcovers"; + } + + return QString(); + +} + int CompareSongsName(const Song &song1, const Song &song2) { return song1.PrettyTitleWithArtist().localeAwareCompare(song2.PrettyTitleWithArtist()) < 0; } @@ -997,16 +1023,24 @@ void Song::InitFromFilePartial(const QString &filename) { void Song::InitArtManual() { - QString album = d->album_; + QString album = effective_album(); album.remove(Song::kAlbumRemoveDisc); // If we don't have an art, check if we have one in the cache - if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty()) { + if (d->art_manual_.isEmpty() && d->art_automatic_.isEmpty() && !effective_albumartist().isEmpty() && !album.isEmpty()) { QString filename(Utilities::Sha1CoverHash(effective_albumartist(), album).toHex() + ".jpg"); - QString path(AlbumCoverLoader::ImageCacheDir(d->source_) + "/" + filename); + QString path(ImageCacheDir(d->source_) + "/" + filename); if (QFile::exists(path)) { d->art_manual_ = QUrl::fromLocalFile(path); } + else if (d->url_.isLocalFile()) { // Pick the first image file in the album directory. + QFileInfo file(d->url_.toLocalFile()); + QDir dir(file.path()); + QStringList files = dir.entryList(QStringList() << "*.jpg" << "*.png" << "*.gif" << "*.jpeg", QDir::Files|QDir::Readable, QDir::Name); + if (files.count() > 0) { + d->art_manual_ = QUrl::fromLocalFile(file.path() + QDir::separator() + files.first()); + } + } } } diff --git a/src/core/song.h b/src/core/song.h index d85c08a3f..24152baf9 100644 --- a/src/core/song.h +++ b/src/core/song.h @@ -147,6 +147,7 @@ class Song { static FileType FiletypeByMimetype(const QString &mimetype); static FileType FiletypeByDescription(const QString &text); static FileType FiletypeByExtension(const QString &ext); + static QString ImageCacheDir(const Song::Source source); // Sort songs alphabetically using their pretty title static void SortSongsListAlphabetically(QList *songs); diff --git a/src/core/standarditemiconloader.cpp b/src/core/standarditemiconloader.cpp index 468851251..783b865af 100644 --- a/src/core/standarditemiconloader.cpp +++ b/src/core/standarditemiconloader.cpp @@ -40,7 +40,7 @@ StandardItemIconLoader::StandardItemIconLoader(AlbumCoverLoader *cover_loader, Q cover_options_.desired_height_ = 16; - connect(cover_loader_, SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(ImageLoaded(quint64, QUrl, QImage))); + connect(cover_loader_, SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult))); } void StandardItemIconLoader::SetModel(QAbstractItemModel *model) { @@ -96,15 +96,13 @@ void StandardItemIconLoader::ModelReset() { } -void StandardItemIconLoader::ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) { - - Q_UNUSED(cover_url); +void StandardItemIconLoader::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult result) { QStandardItem *item = pending_covers_.take(id); if (!item) return; - if (!image.isNull()) { - item->setIcon(QIcon(QPixmap::fromImage(image))); + if (!result.image_scaled.isNull()) { + item->setIcon(QIcon(QPixmap::fromImage(result.image_scaled))); } } diff --git a/src/core/standarditemiconloader.h b/src/core/standarditemiconloader.h index 11569aaee..564d9fc7b 100644 --- a/src/core/standarditemiconloader.h +++ b/src/core/standarditemiconloader.h @@ -26,9 +26,11 @@ #include #include #include +#include #include #include "covermanager/albumcoverloaderoptions.h" +#include "covermanager/albumcoverloaderresult.h" class QAbstractItemModel; class QStandardItem; @@ -52,12 +54,12 @@ class StandardItemIconLoader : public QObject { void LoadIcon(const QUrl &art_automatic, const QUrl &art_manual, QStandardItem *for_item); void LoadIcon(const Song &song, QStandardItem *for_item); -private slots: - void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image); + private slots: + void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult result); void RowsAboutToBeRemoved(const QModelIndex &parent, int begin, int end); void ModelReset(); -private: + private: AlbumCoverLoader *cover_loader_; AlbumCoverLoaderOptions cover_options_; diff --git a/src/covermanager/albumcoverchoicecontroller.cpp b/src/covermanager/albumcoverchoicecontroller.cpp index 89252fa54..4d0f54d25 100644 --- a/src/covermanager/albumcoverchoicecontroller.cpp +++ b/src/covermanager/albumcoverchoicecontroller.cpp @@ -312,12 +312,14 @@ void AlbumCoverChoiceController::ShowCover(const Song &song, const QPixmap &pixm } -void AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) { +qint64 AlbumCoverChoiceController::SearchCoverAutomatically(const Song &song) { - qint64 id = cover_fetcher_->FetchAlbumCover(song.effective_albumartist(), song.effective_album(), true); + qint64 id = cover_fetcher_->FetchAlbumCover(song.effective_albumartist(), song.album(), song.title(), true); cover_fetching_tasks_[id] = song; + return id; + } void AlbumCoverChoiceController::AlbumCoverFetched(const quint64 id, const QUrl &cover_url, const QImage &image, const CoverSearchStatistics &statistics) { @@ -371,7 +373,7 @@ void AlbumCoverChoiceController::SaveCoverToSong(Song *song, const QUrl &cover_u } - if (song->url() == app_->current_albumcover_loader()->last_song().url()) { + if (*song == app_->current_albumcover_loader()->last_song()) { app_->current_albumcover_loader()->LoadAlbumCover(*song); } diff --git a/src/covermanager/albumcoverchoicecontroller.h b/src/covermanager/albumcoverchoicecontroller.h index 47f30731e..8900e59db 100644 --- a/src/covermanager/albumcoverchoicecontroller.h +++ b/src/covermanager/albumcoverchoicecontroller.h @@ -110,7 +110,7 @@ class AlbumCoverChoiceController : public QWidget { void ShowCover(const Song &song, const QPixmap &pixmap); // Search for covers automatically - void SearchCoverAutomatically(const Song &song); + qint64 SearchCoverAutomatically(const Song &song); // Saves the chosen cover as manual cover path of this song in collection. void SaveCoverToSong(Song *song, const QUrl &cover_url); @@ -124,7 +124,7 @@ class AlbumCoverChoiceController : public QWidget { static bool CanAcceptDrag(const QDragEnterEvent *e); -signals: + signals: void AutomaticCoverSearchDone(); private slots: diff --git a/src/covermanager/albumcoverfetcher.cpp b/src/covermanager/albumcoverfetcher.cpp index edf523cc1..29731f83a 100644 --- a/src/covermanager/albumcoverfetcher.cpp +++ b/src/covermanager/albumcoverfetcher.cpp @@ -44,14 +44,15 @@ AlbumCoverFetcher::AlbumCoverFetcher(CoverProviders *cover_providers, QObject *p connect(request_starter_, SIGNAL(timeout()), SLOT(StartRequests())); } -quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, bool fetchall) { +quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString &album, const QString &title, bool fetchall) { CoverSearchRequest request; request.id = next_id_++; request.artist = artist; request.album = album; - request.album.remove(Song::kAlbumRemoveDisc); - request.album.remove(Song::kAlbumRemoveMisc); + request.album = request.album.remove(Song::kAlbumRemoveDisc); + request.album = request.album.remove(Song::kAlbumRemoveMisc); + request.title = title; request.search = false; request.fetchall = fetchall; @@ -60,14 +61,15 @@ quint64 AlbumCoverFetcher::FetchAlbumCover(const QString &artist, const QString } -quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString &album) { +quint64 AlbumCoverFetcher::SearchForCovers(const QString &artist, const QString &album, const QString &title) { CoverSearchRequest request; request.id = next_id_++; request.artist = artist; request.album = album; - request.album.remove(Song::kAlbumRemoveDisc); - request.album.remove(Song::kAlbumRemoveMisc); + request.album = request.album.remove(Song::kAlbumRemoveDisc); + request.album = request.album.remove(Song::kAlbumRemoveMisc); + request.title = title; request.search = true; request.fetchall = false; diff --git a/src/covermanager/albumcoverfetcher.h b/src/covermanager/albumcoverfetcher.h index d25b58477..12509218b 100644 --- a/src/covermanager/albumcoverfetcher.h +++ b/src/covermanager/albumcoverfetcher.h @@ -50,6 +50,7 @@ struct CoverSearchRequest { // A search query QString artist; QString album; + QString title; // Is this only a search request or should we also fetch the first cover that's found? bool search; @@ -92,12 +93,12 @@ class AlbumCoverFetcher : public QObject { static const int kMaxConcurrentRequests; - quint64 SearchForCovers(const QString &artist, const QString &album); - quint64 FetchAlbumCover(const QString &artist, const QString &album, const bool fetchall); + quint64 SearchForCovers(const QString &artist, const QString &album, const QString &title = QString()); + quint64 FetchAlbumCover(const QString &artist, const QString &album, const QString &title, const bool fetchall); void Clear(); -signals: + signals: void AlbumCoverFetched(const quint64 request_id, const QUrl &cover_url, const QImage &cover, const CoverSearchStatistics &statistics); void SearchFinished(const quint64 request_id, const CoverSearchResults &results, const CoverSearchStatistics &statistics); diff --git a/src/covermanager/albumcoverfetchersearch.cpp b/src/covermanager/albumcoverfetchersearch.cpp index ae3c206e4..a1fca01cf 100644 --- a/src/covermanager/albumcoverfetchersearch.cpp +++ b/src/covermanager/albumcoverfetchersearch.cpp @@ -42,11 +42,6 @@ #include "coverprovider.h" #include "coverproviders.h" -using std::min; -using std::max; -using std::stable_sort; -using std::sqrt; - const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 25000; const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 3000; const int AlbumCoverFetcherSearch::kTargetSize = 500; @@ -81,13 +76,16 @@ void AlbumCoverFetcherSearch::Start(CoverProviders *cover_providers) { // Skip provider if it does not have fetchall set, and we are doing fetchall - "Fetch Missing Covers". if (!provider->fetchall() && request_.fetchall) { - //qLog(Debug) << "Skipping provider" << provider->name(); + continue; + } + // If album is missing, check if we can still use this provider by searching using artist + title. + if (!provider->allow_missing_album() && request_.album.isEmpty()) { continue; } connect(provider, SIGNAL(SearchFinished(int, CoverSearchResults)), SLOT(ProviderSearchFinished(int, CoverSearchResults))); const int id = cover_providers->NextId(); - const bool success = provider->StartSearch(request_.artist, request_.album, id); + const bool success = provider->StartSearch(request_.artist, request_.album, request_.title, id); if (success) { pending_requests_[id] = provider; @@ -112,7 +110,7 @@ void AlbumCoverFetcherSearch::ProviderSearchFinished(const int id, const CoverSe CoverProvider *provider = pending_requests_.take(id); CoverSearchResults results_copy(results); - for (int i = 0; i < results_copy.count(); ++i) { + for (int i = 0 ; i < results_copy.count() ; ++i) { results_copy[i].provider = provider->name(); results_copy[i].score = provider->quality(); if (results_copy[i].artist.toLower() == request_.artist.toLower()) { @@ -170,7 +168,7 @@ void AlbumCoverFetcherSearch::FetchMoreImages() { // Try the first one in each category. QString last_provider; - for (int i = 0; i < results_.count(); ++i) { + for (int i = 0 ; i < results_.count() ; ++i) { if (results_[i].provider == last_provider) { continue; } diff --git a/src/covermanager/albumcoverloader.cpp b/src/covermanager/albumcoverloader.cpp index 36a4ea7bc..c5c2ff667 100644 --- a/src/covermanager/albumcoverloader.cpp +++ b/src/covermanager/albumcoverloader.cpp @@ -50,6 +50,7 @@ #include "organise/organiseformat.h" #include "albumcoverloader.h" #include "albumcoverloaderoptions.h" +#include "albumcoverloaderresult.h" AlbumCoverLoader::AlbumCoverLoader(QObject *parent) : QObject(parent), @@ -98,89 +99,7 @@ void AlbumCoverLoader::ReloadSettings() { } -QString AlbumCoverLoader::ImageCacheDir(const Song::Source source) { - - switch (source) { - case Song::Source_LocalFile: - case Song::Source_Collection: - case Song::Source_CDDA: - case Song::Source_Device: - case Song::Source_Stream: - case Song::Source_Unknown: - return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/albumcovers"; - case Song::Source_Tidal: - return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/tidalalbumcovers"; - case Song::Source_Qobuz: - return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/qobuzalbumcovers"; - case Song::Source_Subsonic: - return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/subsonicalbumcovers"; - } - - return QString(); - -} - -QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url) { - - album.remove(Song::kAlbumRemoveDisc); - - QString path; - if (source == Song::Source_Collection && cover_album_dir_ && !album_dir.isEmpty()) { - path = album_dir; - } - else { - path = AlbumCoverLoader::ImageCacheDir(source); - } - - if (path.right(1) == QDir::separator()) { - path.chop(1); - } - - QDir dir; - if (!dir.mkpath(path)) { - qLog(Error) << "Unable to create directory" << path; - return QString(); - } - - QString filename; - if (source == Song::Source_Collection && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) { - filename = CreateCoverFilename(artist, album) + ".jpg"; - filename.remove(OrganiseFormat::kInvalidFatCharacters); - if (cover_lowercase_) filename = filename.toLower(); - if (cover_replace_spaces_) filename.replace(QRegExp("\\s"), "-"); - } - else { - switch (source) { - case Song::Source_Tidal: - filename = album_id + "-" + cover_url.fileName(); - break; - case Song::Source_Subsonic: - case Song::Source_Qobuz: - filename = AlbumCoverFileName(artist, album); - if (filename.length() > 8 && (filename.length() - 5) >= (artist.length() + album.length() - 2)) { - break; - } - // fallthrough - case Song::Source_Collection: - case Song::Source_LocalFile: - case Song::Source_CDDA: - case Song::Source_Device: - case Song::Source_Stream: - case Song::Source_Unknown: - filename = Utilities::Sha1CoverHash(artist, album).toHex() + ".jpg"; - break; - } - } - - if (filename.isEmpty()) return QString(); - - QString filepath(path + "/" + filename); - - return filepath; - -} - -QString AlbumCoverLoader::AlbumCoverFileName(QString artist, QString album) { +QString AlbumCoverLoader::AlbumCoverFilename(QString artist, QString album) { artist.remove('/'); album.remove('/'); @@ -196,7 +115,79 @@ QString AlbumCoverLoader::AlbumCoverFileName(QString artist, QString album) { } -QString AlbumCoverLoader::CreateCoverFilename(const QString &artist, const QString &album) { +QString AlbumCoverLoader::CoverFilePath(const Song &song, const QString &album_dir, const QUrl &cover_url) { + return CoverFilePath(song.source(), song.effective_albumartist(), song.album(), song.album_id(), album_dir, cover_url); +} + +QString AlbumCoverLoader::CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url) { + + album.remove(Song::kAlbumRemoveDisc); + + QString path; + if (source == Song::Source_Collection && cover_album_dir_ && !album_dir.isEmpty()) { + path = album_dir; + } + else { + path = Song::ImageCacheDir(source); + } + + if (path.right(1) == QDir::separator()) { + path.chop(1); + } + + QDir dir; + if (!dir.mkpath(path)) { + qLog(Error) << "Unable to create directory" << path; + path = QStandardPaths::writableLocation(QStandardPaths::TempLocation); + } + + QString filename; + if (source == Song::Source_Collection && cover_album_dir_ && cover_filename_ == CollectionSettingsPage::SaveCover_Pattern && !cover_pattern_.isEmpty()) { + filename = CoverFilenameFromVariable(artist, album) + ".jpg"; + filename.remove(OrganiseFormat::kInvalidFatCharacters); + if (cover_lowercase_) filename = filename.toLower(); + if (cover_replace_spaces_) filename.replace(QRegExp("\\s"), "-"); + } + else { + filename = CoverFilenameFromSource(source, cover_url, artist, album, album_id); + } + + QString filepath(path + "/" + filename); + + return filepath; + +} + +QString AlbumCoverLoader::CoverFilenameFromSource(const Song::Source source, const QUrl &cover_url, const QString &artist, const QString &album, const QString &album_id) { + + QString filename; + + switch (source) { + case Song::Source_Tidal: + filename = album_id + "-" + cover_url.fileName(); + break; + case Song::Source_Subsonic: + case Song::Source_Qobuz: + filename = AlbumCoverFilename(artist, album); + if (filename.length() > 8 && (filename.length() - 5) >= (artist.length() + album.length() - 2)) { + break; + } + // fallthrough + case Song::Source_Collection: + case Song::Source_LocalFile: + case Song::Source_CDDA: + case Song::Source_Device: + case Song::Source_Stream: + case Song::Source_Unknown: + filename = Utilities::Sha1CoverHash(artist, album).toHex() + ".jpg"; + break; + } + + return filename; + +} + +QString AlbumCoverLoader::CoverFilenameFromVariable(const QString &artist, const QString &album) { QString filename(cover_pattern_); filename.replace("%albumartist", artist); @@ -215,6 +206,7 @@ void AlbumCoverLoader::CancelTask(const quint64 id) { break; } } + } void AlbumCoverLoader::CancelTasks(const QSet &ids) { @@ -228,21 +220,25 @@ void AlbumCoverLoader::CancelTasks(const QSet &ids) { ++it; } } + } quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions& options, const Song &song) { - return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url().toLocalFile(), song.image()); + return LoadImageAsync(options, song.art_automatic(), song.art_manual(), song.url(), song, song.image()); } -quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QString &song_filename, const QImage &embedded_image) { +quint64 AlbumCoverLoader::LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url, const Song song, const QImage &embedded_image) { Task task; task.options = options; - task.art_automatic = art_automatic; + task.song = song; + task.song_url = song_url; task.art_manual = art_manual; - task.song_filename = song_filename; + task.art_automatic = art_automatic; + task.art_updated = false; task.embedded_image = embedded_image; - task.state = State_TryingManual; + task.type = AlbumCoverLoaderResult::Type_None; + task.state = State_Manual; { QMutexLocker l(&mutex_); @@ -269,20 +265,20 @@ void AlbumCoverLoader::ProcessTasks() { ProcessTask(&task); } + } void AlbumCoverLoader::ProcessTask(Task *task) { - TryLoadResult result = TryLoadImage(*task); + TryLoadResult result = TryLoadImage(task); if (result.started_async) { // The image is being loaded from a remote URL, we'll carry on later when it's done return; } if (result.loaded_success) { - QImage scaled = ScaleAndPad(task->options, result.image); - emit ImageLoaded(task->id, result.cover_url, scaled); - emit ImageLoaded(task->id, result.cover_url, scaled, result.image); + QPair images = ScaleAndPad(task->options, result.image); + emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(result.type, result.cover_url, result.image, images.first, images.second, task->art_updated)); return; } @@ -292,64 +288,85 @@ void AlbumCoverLoader::ProcessTask(Task *task) { void AlbumCoverLoader::NextState(Task *task) { - if (task->state == State_TryingManual) { + if (task->state == State_Manual) { // Try the automatic one next - task->state = State_TryingAuto; + task->state = State_Automatic; ProcessTask(task); } else { // Give up - emit ImageLoaded(task->id, QUrl(), task->options.default_output_image_); - emit ImageLoaded(task->id, QUrl(), task->options.default_output_image_, task->options.default_output_image_); + emit AlbumCoverLoaded(task->id, AlbumCoverLoaderResult(AlbumCoverLoaderResult::Type_None, QUrl(), task->options.default_output_image_, task->options.default_output_image_, task->options.default_thumbnail_image_, task->art_updated)); } } -AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(const Task &task) { +AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(Task *task) { // An image embedded in the song itself takes priority - if (!task.embedded_image.isNull()) - return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, task.embedded_image)); + if (!task->embedded_image.isNull()) { + QPair images = ScaleAndPad(task->options, task->embedded_image); + return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, QUrl(), images.first); + } + // Use cached album cover if possible. + if (task->state == State_Manual && + !task->song.art_manual_is_valid() && + task->art_manual.isEmpty() && + task->song.source() != Song::Source_Collection && + !task->options.scale_output_image_ && + !task->options.pad_output_image_) { + task->song.InitArtManual(); + if (task->art_manual != task->song.art_manual()) { + task->art_manual = task->song.art_manual(); + task->art_updated = true; + } + } + + AlbumCoverLoaderResult::Type type(AlbumCoverLoaderResult::Type_None); QUrl cover_url; - - switch (task.state) { - case State_TryingAuto: cover_url = task.art_automatic; break; - case State_TryingManual: cover_url = task.art_manual; break; + switch (task->state) { + case State_None: + case State_Automatic: + type = AlbumCoverLoaderResult::Type_Automatic; + cover_url = task->art_automatic; + break; + case State_Manual: + type = AlbumCoverLoaderResult::Type_Manual; + cover_url = task->art_manual; + break; } + task->type = type; - if (cover_url.path() == Song::kManuallyUnsetCover) - return TryLoadResult(false, true, QUrl(), task.options.default_output_image_); - - else if (cover_url.path() == Song::kEmbeddedCover && !task.song_filename.isEmpty()) { - const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task.song_filename); - - if (!taglib_image.isNull()) - return TryLoadResult(false, true, QUrl(), ScaleAndPad(task.options, taglib_image)); - } - - if (cover_url.path().isEmpty()) { - return TryLoadResult(false, false, cover_url, task.options.default_output_image_); - } - else { - if (cover_url.isLocalFile()) { + if (!cover_url.isEmpty() && !cover_url.path().isEmpty()) { + if (cover_url.path() == Song::kManuallyUnsetCover) { + return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_ManuallyUnset, QUrl(), task->options.default_output_image_); + } + else if (cover_url.path() == Song::kEmbeddedCover && task->song_url.isLocalFile()) { + const QImage taglib_image = TagReaderClient::Instance()->LoadEmbeddedArtBlocking(task->song_url.toLocalFile()); + if (!taglib_image.isNull()) { + return TryLoadResult(false, true, AlbumCoverLoaderResult::Type_Embedded, QUrl(), ScaleAndPad(task->options, taglib_image).first); + } + } + else if (cover_url.isLocalFile()) { QImage image(cover_url.toLocalFile()); - return TryLoadResult(false, !image.isNull(), cover_url, image.isNull() ? task.options.default_output_image_ : image); + return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : image); } else if (cover_url.scheme().isEmpty()) { // Assume a local file with no scheme. QImage image(cover_url.path()); - return TryLoadResult(false, !image.isNull(), cover_url, image.isNull() ? task.options.default_output_image_ : image); + return TryLoadResult(false, !image.isNull(), type, cover_url, image.isNull() ? task->options.default_output_image_ : image); } else if (network_->supportedSchemes().contains(cover_url.scheme())) { // Remote URL - QNetworkReply *reply = network_->get(QNetworkRequest(cover_url)); + QNetworkRequest request(cover_url); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + QNetworkReply *reply = network_->get(request); NewClosure(reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, QUrl)), reply, cover_url); - remote_tasks_.insert(reply, task); - return TryLoadResult(true, false, cover_url, QImage()); + remote_tasks_.insert(reply, *task); + return TryLoadResult(true, false, type, cover_url, QImage()); } } - return TryLoadResult(false, false, cover_url, task.options.default_output_image_); + return TryLoadResult(false, false, AlbumCoverLoaderResult::Type_None, cover_url, task->options.default_output_image_); } @@ -367,6 +384,7 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cov return; // Give up. } QNetworkRequest request = reply->request(); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setUrl(redirect.toUrl()); QNetworkReply *redirected_reply = network_->get(request); NewClosure(redirected_reply, SIGNAL(finished()), this, SLOT(RemoteFetchFinished(QNetworkReply*, QUrl)), redirected_reply, redirect.toUrl()); @@ -379,41 +397,67 @@ void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply *reply, const QUrl &cov // Try to load the image QImage image; if (image.load(reply, 0)) { - QImage scaled = ScaleAndPad(task.options, image); - emit ImageLoaded(task.id, cover_url, scaled); - emit ImageLoaded(task.id, cover_url, scaled, image); + QPair images = ScaleAndPad(task.options, image); + emit AlbumCoverLoaded(task.id, AlbumCoverLoaderResult(task.type, cover_url, image, images.first, images.second, task.art_updated)); return; } + else { + qLog(Error) << "Unable to load album cover image" << cover_url; + } + } + else { + qLog(Error) << "Unable to get album cover" << cover_url << reply->error() << reply->errorString(); } NextState(&task); } -QImage AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) { +QPair AlbumCoverLoader::ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image) { - if (image.isNull()) return image; + if (image.isNull()) return qMakePair(image, image); // Scale the image down - QImage copy; + QImage image_scaled; if (options.scale_output_image_) { - copy = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation); + image_scaled = image.scaled(QSize(options.desired_height_, options.desired_height_), Qt::KeepAspectRatio, Qt::SmoothTransformation); } else { - copy = image; + image_scaled = image; } - if (!options.pad_output_image_) return copy; + // Pad the image to height x height + if (options.pad_output_image_) { + QImage image_padded(options.desired_height_, options.desired_height_, QImage::Format_ARGB32); + image_padded.fill(0); - // Pad the image to height_ x height_ - QImage padded_image(options.desired_height_, options.desired_height_, QImage::Format_ARGB32); - padded_image.fill(0); + QPainter p(&image_padded); + p.drawImage((options.desired_height_ - image_scaled.width()) / 2, (options.desired_height_ - image_scaled.height()) / 2, image_scaled); + p.end(); - QPainter p(&padded_image); - p.drawImage((options.desired_height_ - copy.width()) / 2, (options.desired_height_ - copy.height()) / 2, copy); - p.end(); + image_scaled = image_padded; + } - return padded_image; + // Create thumbnail + QImage image_thumbnail; + if (options.create_thumbnail_) { + if (options.pad_thumbnail_image_) { + image_thumbnail = image.scaled(options.thumbnail_size_, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QImage image_padded(options.thumbnail_size_, QImage::Format_ARGB32_Premultiplied); + image_padded.fill(0); + + QPainter p(&image_padded); + p.drawImage((image_padded.width() - image_thumbnail.width()) / 2, (image_padded.height() - image_thumbnail.height()) / 2, image_thumbnail); + p.end(); + + image_thumbnail = image_padded; + } + else { + image_thumbnail = image.scaledToHeight(options.thumbnail_size_.height(), Qt::SmoothTransformation); + } + } + + return qMakePair(image_scaled, image_thumbnail); } diff --git a/src/covermanager/albumcoverloader.h b/src/covermanager/albumcoverloader.h index e01b7d0f4..f5d20c4ab 100644 --- a/src/covermanager/albumcoverloader.h +++ b/src/covermanager/albumcoverloader.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include "core/song.h" #include "settings/collectionsettingspage.h" #include "albumcoverloaderoptions.h" +#include "albumcoverloaderresult.h" class QThread; class QNetworkReply; @@ -48,29 +50,36 @@ class AlbumCoverLoader : public QObject { public: explicit AlbumCoverLoader(QObject *parent = nullptr); + enum State { + State_None, + State_Manual, + State_Automatic, + }; + void ReloadSettings(); void ExitAsync(); void Stop() { stop_requested_ = true; } - static QString ImageCacheDir(const Song::Source source); - QString CreateCoverFilename(const QString &artist, const QString &album); + static QString AlbumCoverFilename(QString artist, QString album); + + QString CoverFilenameFromSource(const Song::Source source, const QUrl &cover_url, const QString &artist, const QString &album, const QString &album_id); + QString CoverFilenameFromVariable(const QString &artist, const QString &album); + QString CoverFilePath(const Song &song, const QString &album_dir, const QUrl &cover_url); QString CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url); - QString AlbumCoverFileName(QString artist, QString album); quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song); - virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QString &song_filename = QString(), const QImage &embedded_image = QImage()); + virtual quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url = QUrl(), const Song song = Song(), const QImage &embedded_image = QImage()); void CancelTask(const quint64 id); void CancelTasks(const QSet &ids); static QPixmap TryLoadPixmap(const QUrl &automatic, const QUrl &manual, const QUrl &url = QUrl()); - static QImage ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image); + static QPair ScaleAndPad(const AlbumCoverLoaderOptions &options, const QImage &image); signals: void ExitFinished(); - void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image); - void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original); + void AlbumCoverLoaded(quint64 id, AlbumCoverLoaderResult result); protected slots: void Exit(); @@ -78,38 +87,38 @@ class AlbumCoverLoader : public QObject { void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url); protected: - enum State { - State_TryingManual, - State_TryingAuto, - }; struct Task { - explicit Task() : redirects(0) {} + explicit Task() : id(0), state(State_None), type(AlbumCoverLoaderResult::Type_None), art_updated(false), redirects(0) {} AlbumCoverLoaderOptions options; quint64 id; - QUrl art_automatic; QUrl art_manual; - QString song_filename; + QUrl art_automatic; + QUrl song_url; + Song song; QImage embedded_image; State state; + AlbumCoverLoaderResult::Type type; + bool art_updated; int redirects; }; struct TryLoadResult { - explicit TryLoadResult(bool async, bool success, const QUrl &_cover_url, const QImage &_image) : started_async(async), loaded_success(success), cover_url(_cover_url), image(_image) {} + explicit TryLoadResult(const bool _started_async = false, const bool _loaded_success = false, const AlbumCoverLoaderResult::Type _type = AlbumCoverLoaderResult::Type_None, const QUrl &_cover_url = QUrl(), const QImage &_image = QImage()) : started_async(_started_async), loaded_success(_loaded_success), type(_type), cover_url(_cover_url), image(_image) {} bool started_async; bool loaded_success; + AlbumCoverLoaderResult::Type type; QUrl cover_url; QImage image; }; void ProcessTask(Task *task); void NextState(Task *task); - TryLoadResult TryLoadImage(const Task &task); + TryLoadResult TryLoadImage(Task *task); bool stop_requested_; diff --git a/src/covermanager/albumcoverloaderoptions.h b/src/covermanager/albumcoverloaderoptions.h index 41cdef7b7..4c714a206 100644 --- a/src/covermanager/albumcoverloaderoptions.h +++ b/src/covermanager/albumcoverloaderoptions.h @@ -24,17 +24,24 @@ #include "config.h" #include +#include struct AlbumCoverLoaderOptions { explicit AlbumCoverLoaderOptions() : desired_height_(120), scale_output_image_(true), - pad_output_image_(true) {} + pad_output_image_(true), + create_thumbnail_(false), + pad_thumbnail_image_(false) {} int desired_height_; + QSize thumbnail_size_; bool scale_output_image_; bool pad_output_image_; + bool create_thumbnail_; + bool pad_thumbnail_image_; QImage default_output_image_; + QImage default_thumbnail_image_; }; #endif // ALBUMCOVERLOADEROPTIONS_H diff --git a/src/covermanager/albumcoverloaderresult.h b/src/covermanager/albumcoverloaderresult.h new file mode 100644 index 000000000..a11eea2a8 --- /dev/null +++ b/src/covermanager/albumcoverloaderresult.h @@ -0,0 +1,52 @@ +/* + * Strawberry Music Player + * Copyright 2020, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef ALBUMCOVERLOADERRESULT_H +#define ALBUMCOVERLOADERRESULT_H + +#include "config.h" + +#include +#include + +struct AlbumCoverLoaderResult { + + enum Type { + Type_None, + Type_ManuallyUnset, + Type_Embedded, + Type_Automatic, + Type_Manual, + Type_Remote, + }; + + explicit AlbumCoverLoaderResult(const Type _type = Type_None, const QUrl &_cover_url = QUrl(), const QImage &_image_original = QImage(), const QImage &_image_scaled = QImage(), const QImage &_image_thumbnail = QImage(), const bool _updated = false) : type(_type), cover_url(_cover_url), image_original(_image_original), image_scaled(_image_scaled), image_thumbnail(_image_thumbnail), updated(_updated) {} + + Type type; + QUrl cover_url; + QImage image_original; + QImage image_scaled; + QImage image_thumbnail; + bool updated; + + QUrl temp_cover_url; + +}; + +#endif // ALBUMCOVERLOADERRESULT_H diff --git a/src/covermanager/albumcovermanager.cpp b/src/covermanager/albumcovermanager.cpp index 7305df40c..990cdc48b 100644 --- a/src/covermanager/albumcovermanager.cpp +++ b/src/covermanager/albumcovermanager.cpp @@ -77,6 +77,8 @@ #include "albumcoverexporter.h" #include "albumcoverfetcher.h" #include "albumcoverloader.h" +#include "albumcoverloaderoptions.h" +#include "albumcoverloaderresult.h" #include "albumcovermanagerlist.h" #include "coversearchstatistics.h" #include "coversearchstatisticsdialog.h" @@ -216,7 +218,7 @@ void AlbumCoverManager::Init() { ui_->splitter->setSizes(QList() << 200 << width() - 200); } - connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(CoverImageLoaded(quint64, QUrl, QImage))); + connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult))); cover_searcher_->Init(cover_fetcher_); @@ -392,7 +394,7 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) { } if (!info.art_automatic.isEmpty() || !info.art_manual.isEmpty()) { - quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.first_url.toLocalFile()); + quint64 id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, info.art_automatic, info.art_manual, info.first_url); item->setData(Role_PathAutomatic, info.art_automatic); item->setData(Role_PathManual, info.art_manual); cover_loading_tasks_[id] = item; @@ -403,17 +405,15 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) { } -void AlbumCoverManager::CoverImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) { - - Q_UNUSED(cover_url); +void AlbumCoverManager::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) { if (!cover_loading_tasks_.contains(id)) return; QListWidgetItem *item = cover_loading_tasks_.take(id); - if (image.isNull()) return; + if (result.image_scaled.isNull()) return; - item->setIcon(QPixmap::fromImage(image)); + item->setIcon(QPixmap::fromImage(result.image_scaled)); UpdateFilter(); } @@ -488,7 +488,7 @@ void AlbumCoverManager::FetchAlbumCovers() { if (item->isHidden()) continue; if (ItemHasCover(*item)) continue; - quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), true); + quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), true); cover_fetching_tasks_[id] = item; jobs_++; } @@ -623,7 +623,7 @@ void AlbumCoverManager::ShowCover() { void AlbumCoverManager::FetchSingleCover() { for (QListWidgetItem *item : context_menu_items_) { - quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), false); + quint64 id = cover_fetcher_->FetchAlbumCover(EffectiveAlbumArtistName(*item), item->data(Role_AlbumName).toString(), QString(), false); cover_fetching_tasks_[id] = item; jobs_++; } diff --git a/src/covermanager/albumcovermanager.h b/src/covermanager/albumcovermanager.h index 90bbb0334..a100c893c 100644 --- a/src/covermanager/albumcovermanager.h +++ b/src/covermanager/albumcovermanager.h @@ -36,6 +36,7 @@ #include "core/song.h" #include "albumcoverloaderoptions.h" +#include "albumcoverloaderresult.h" #include "coversearchstatistics.h" class QWidget; @@ -132,7 +133,7 @@ class AlbumCoverManager : public QMainWindow { private slots: void ArtistChanged(QListWidgetItem *current); - void CoverImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image); + void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result); void UpdateFilter(); void FetchAlbumCovers(); void ExportCovers(); diff --git a/src/covermanager/albumcoversearcher.cpp b/src/covermanager/albumcoversearcher.cpp index 93fd9b407..521daaaaa 100644 --- a/src/covermanager/albumcoversearcher.cpp +++ b/src/covermanager/albumcoversearcher.cpp @@ -47,6 +47,7 @@ #include "core/application.h" #include "core/utilities.h" +#include "core/logging.h" #include "widgets/busyindicator.h" #include "widgets/forcescrollperpixel.h" #include "widgets/groupediconview.h" @@ -55,6 +56,7 @@ #include "albumcoverfetcher.h" #include "albumcoverloader.h" #include "albumcoverloaderoptions.h" +#include "albumcoverloaderresult.h" #include "ui_albumcoversearcher.h" const int SizeOverlayDelegate::kMargin = 4; @@ -129,8 +131,11 @@ AlbumCoverSearcher::AlbumCoverSearcher(const QIcon &no_cover_icon, Application * options_.scale_output_image_ = false; options_.pad_output_image_ = false; + options_.create_thumbnail_ = true; + options_.pad_thumbnail_image_ = true; + options_.thumbnail_size_ = ui_->covers->iconSize(); - connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(ImageLoaded(quint64, QUrl, QImage))); + connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult))); connect(ui_->search, SIGNAL(clicked()), SLOT(Search())); connect(ui_->covers, SIGNAL(doubleClicked(QModelIndex)), SLOT(CoverDoubleClicked(QModelIndex))); @@ -235,37 +240,25 @@ void AlbumCoverSearcher::SearchFinished(const quint64 id, const CoverSearchResul } -void AlbumCoverSearcher::ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image) { - - Q_UNUSED(cover_url); +void AlbumCoverSearcher::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) { if (!cover_loading_tasks_.contains(id)) return; QStandardItem *item = cover_loading_tasks_.take(id); if (cover_loading_tasks_.isEmpty()) ui_->busy->hide(); - if (image.isNull()) { + if (result.image_original.isNull()) { model_->removeRow(item->row()); return; } - QIcon icon(QPixmap::fromImage(image)); - - // Create a pixmap that's padded and exactly the right size for the icon. - QImage scaled_image(image.scaled(ui_->covers->iconSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation)); - - QImage padded_image(ui_->covers->iconSize(), QImage::Format_ARGB32_Premultiplied); - padded_image.fill(0); - - QPainter p(&padded_image); - p.drawImage((padded_image.width() - scaled_image.width()) / 2, (padded_image.height() - scaled_image.height()) / 2, scaled_image); - p.end(); - - icon.addPixmap(QPixmap::fromImage(padded_image)); + QIcon icon; + icon.addPixmap(QPixmap::fromImage(result.image_original)); + icon.addPixmap(QPixmap::fromImage(result.image_thumbnail)); item->setData(true, Role_ImageFetchFinished); - item->setData(image.width() * image.height(), Role_ImageDimensions); - item->setData(image.size(), Role_ImageSize); + item->setData(result.image_original.width() * result.image_original.height(), Role_ImageDimensions); + item->setData(result.image_original.size(), Role_ImageSize); item->setIcon(icon); } diff --git a/src/covermanager/albumcoversearcher.h b/src/covermanager/albumcoversearcher.h index 87432cd0b..a959c649d 100644 --- a/src/covermanager/albumcoversearcher.h +++ b/src/covermanager/albumcoversearcher.h @@ -36,6 +36,7 @@ #include "albumcoverfetcher.h" #include "albumcoverloaderoptions.h" +#include "albumcoverloaderresult.h" class QWidget; class QStandardItem; @@ -88,7 +89,7 @@ class AlbumCoverSearcher : public QDialog { private slots: void Search(); void SearchFinished(const quint64 id, const CoverSearchResults &results); - void ImageLoaded(const quint64 id, const QUrl &cover_url, const QImage &image); + void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result); void CoverDoubleClicked(const QModelIndex &index); diff --git a/src/covermanager/coverprovider.cpp b/src/covermanager/coverprovider.cpp index 232e007e3..debe03c6e 100644 --- a/src/covermanager/coverprovider.cpp +++ b/src/covermanager/coverprovider.cpp @@ -26,5 +26,5 @@ #include "core/application.h" #include "coverprovider.h" -CoverProvider::CoverProvider(const QString &name, const float &quality, const bool &fetchall, Application *app, QObject *parent) - : QObject(parent), app_(app), name_(name), quality_(quality), fetchall_(fetchall) {} +CoverProvider::CoverProvider(const QString &name, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent) + : QObject(parent), app_(app), name_(name), quality_(quality), fetchall_(fetchall), allow_missing_album_(allow_missing_album) {} diff --git a/src/covermanager/coverprovider.h b/src/covermanager/coverprovider.h index 5ffc6f8bf..7426d96e0 100644 --- a/src/covermanager/coverprovider.h +++ b/src/covermanager/coverprovider.h @@ -37,17 +37,18 @@ class CoverProvider : public QObject { Q_OBJECT public: - explicit CoverProvider(const QString &name, const float &quality, const bool &fetchall, Application *app, QObject *parent); + explicit CoverProvider(const QString &name, const float quality, const bool fetchall, const bool allow_missing_album, Application *app, QObject *parent); // A name (very short description) of this provider, like "last.fm". QString name() const { return name_; } bool quality() const { return quality_; } bool fetchall() const { return fetchall_; } + bool allow_missing_album() const { return allow_missing_album_; } // Starts searching for covers matching the given query text. // Returns true if the query has been started, or false if an error occurred. // The provider should remember the ID and emit it along with the result when it finishes. - virtual bool StartSearch(const QString &artist, const QString &album, int id) = 0; + virtual bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id) = 0; virtual void CancelSearch(int id) { Q_UNUSED(id); } @@ -59,6 +60,7 @@ class CoverProvider : public QObject { QString name_; float quality_; bool fetchall_; + bool allow_missing_album_; }; diff --git a/src/covermanager/currentalbumcoverloader.cpp b/src/covermanager/currentalbumcoverloader.cpp index 8d90276e3..e45909056 100644 --- a/src/covermanager/currentalbumcoverloader.cpp +++ b/src/covermanager/currentalbumcoverloader.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2019-2020, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -32,6 +33,7 @@ #include "playlist/playlistmanager.h" #include "albumcoverloader.h" #include "albumcoverloaderoptions.h" +#include "albumcoverloaderresult.h" #include "currentalbumcoverloader.h" CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *parent) @@ -43,56 +45,73 @@ CurrentAlbumCoverLoader::CurrentAlbumCoverLoader(Application *app, QObject *pare options_.scale_output_image_ = false; options_.pad_output_image_ = false; + options_.create_thumbnail_ = true; + options_.thumbnail_size_ = QSize(120, 120); options_.default_output_image_ = QImage(":/pictures/cdcase.png"); + options_.default_thumbnail_image_ = options_.default_output_image_.scaledToHeight(120, Qt::SmoothTransformation); - connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(TempAlbumCoverLoaded(quint64, QUrl, QImage))); + connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(TempAlbumCoverLoaded(quint64, AlbumCoverLoaderResult))); connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), SLOT(LoadAlbumCover(Song))); } CurrentAlbumCoverLoader::~CurrentAlbumCoverLoader() { + if (temp_cover_) temp_cover_->remove(); if (temp_cover_thumbnail_) temp_cover_thumbnail_->remove(); + } void CurrentAlbumCoverLoader::LoadAlbumCover(const Song &song) { + last_song_ = song; id_ = app_->album_cover_loader()->LoadImageAsync(options_, last_song_); + } -void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image) { - - Q_UNUSED(remote_url); +void CurrentAlbumCoverLoader::TempAlbumCoverLoaded(const quint64 id, AlbumCoverLoaderResult result) { if (id != id_) return; id_ = 0; - QUrl cover_url; - QUrl thumbnail_url; - QImage thumbnail; - - if (!image.isNull()) { - - QString filename; - + if (!result.image_scaled.isNull()) { temp_cover_.reset(new QTemporaryFile(temp_file_pattern_)); temp_cover_->setAutoRemove(true); - temp_cover_->open(); - - image.save(temp_cover_->fileName(), "JPEG"); - - // Scale the image down to make a thumbnail. It's a bit crap doing it here since it's the GUI thread, but the alternative is hard. - temp_cover_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_)); - temp_cover_thumbnail_->open(); - temp_cover_thumbnail_->setAutoRemove(true); - thumbnail = image.scaledToHeight(120, Qt::SmoothTransformation); - thumbnail.save(temp_cover_thumbnail_->fileName(), "JPEG"); - - cover_url = QUrl::fromLocalFile(temp_cover_->fileName()); - thumbnail_url = QUrl::fromLocalFile(temp_cover_thumbnail_->fileName()); + if (temp_cover_->open()) { + if (result.image_scaled.save(temp_cover_->fileName(), "JPEG")) { + result.temp_cover_url = QUrl::fromLocalFile(temp_cover_->fileName()); + } + else { + qLog(Error) << "Unable to save cover image to" << temp_cover_->fileName(); + } + } + else { + qLog(Error) << "Unable to open" << temp_cover_->fileName(); + } } - emit AlbumCoverLoaded(last_song_, cover_url, image); - emit ThumbnailLoaded(last_song_, thumbnail_url, thumbnail); + QUrl thumbnail_url; + if (!result.image_thumbnail.isNull()) { + temp_cover_thumbnail_.reset(new QTemporaryFile(temp_file_pattern_)); + temp_cover_thumbnail_->setAutoRemove(true); + if (temp_cover_thumbnail_->open()) { + if (result.image_thumbnail.save(temp_cover_thumbnail_->fileName(), "JPEG")) { + thumbnail_url = QUrl::fromLocalFile(temp_cover_thumbnail_->fileName()); + } + else { + qLog(Error) << "Unable to save cover thumbnail image to" << temp_cover_thumbnail_->fileName(); + } + } + else { + qLog(Error) << "Unable to open" << temp_cover_thumbnail_->fileName(); + } + } + + if (result.updated) { + last_song_.set_art_manual(result.cover_url); + } + + emit AlbumCoverLoaded(last_song_, result); + emit ThumbnailLoaded(last_song_, thumbnail_url, result.image_thumbnail); } diff --git a/src/covermanager/currentalbumcoverloader.h b/src/covermanager/currentalbumcoverloader.h index 69156eb8a..eebd6507f 100644 --- a/src/covermanager/currentalbumcoverloader.h +++ b/src/covermanager/currentalbumcoverloader.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome + * Copyright 2019-2020, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,6 +34,7 @@ #include "core/song.h" #include "albumcoverloaderoptions.h" +#include "albumcoverloaderresult.h" class Application; @@ -50,11 +52,11 @@ class CurrentAlbumCoverLoader : public QObject { void LoadAlbumCover(const Song &song); signals: - void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image); - void ThumbnailLoaded(const Song &song, const QUrl &thumbnail_uri, const QImage &image); + void AlbumCoverLoaded(Song song, AlbumCoverLoaderResult result); + void ThumbnailLoaded(Song song, QUrl thumbnail_uri, QImage image); private slots: - void TempAlbumCoverLoaded(const quint64 id, const QUrl &remote_url, const QImage &image); + void TempAlbumCoverLoaded(const quint64 id, AlbumCoverLoaderResult result); private: Application *app_; diff --git a/src/covermanager/deezercoverprovider.cpp b/src/covermanager/deezercoverprovider.cpp index c2a3a5855..15c2de35d 100644 --- a/src/covermanager/deezercoverprovider.cpp +++ b/src/covermanager/deezercoverprovider.cpp @@ -50,16 +50,27 @@ const char *DeezerCoverProvider::kApiUrl = "https://api.deezer.com"; const int DeezerCoverProvider::kLimit = 10; -DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): CoverProvider("Deezer", 2.0, true, app, parent), network_(new NetworkAccessManager(this)) {} +DeezerCoverProvider::DeezerCoverProvider(Application *app, QObject *parent): CoverProvider("Deezer", 2.0, true, true, app, parent), network_(new NetworkAccessManager(this)) {} -bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) { +bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { typedef QPair Param; typedef QList Params; typedef QPair EncodedParam; + QUrl url(kApiUrl); + QString search; + if (album.isEmpty()) { + url.setPath("/search/track"); + search = artist + " " + title; + } + else { + url.setPath("/search/album"); + search = artist + " " + album; + } + const Params params = Params() << Param("output", "json") - << Param("q", QString(artist + " " + album)) + << Param("q", search) << Param("limit", QString::number(kLimit)); QUrlQuery url_query; @@ -68,7 +79,6 @@ bool DeezerCoverProvider::StartSearch(const QString &artist, const QString &albu url_query.addQueryItem(encoded_param.first, encoded_param.second); } - QUrl url(kApiUrl + QString("/search/album")); url.setQuery(url_query); QNetworkRequest req(url); req.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); @@ -220,19 +230,27 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) for (const QJsonValue &value : json_data) { if (!value.isObject()) { - Error("Invalid Json reply, data is not an object.", value); + Error("Invalid Json reply, data in array is not a object.", value); continue; } QJsonObject json_obj = value.toObject(); + QJsonObject json_album = json_obj; + if (json_obj.contains("album") && json_obj["album"].isObject()) { // Song search, so extract the album. + json_album = json_obj["album"].toObject(); + } - if (!json_obj.contains("id") || !json_obj.contains("type")) { - Error("Invalid Json reply, item is missing ID or type.", json_obj); + if (!json_obj.contains("id") || !json_album.contains("id")) { + Error("Invalid Json reply, object is missing ID.", json_obj); continue; } - QString type = json_obj["type"].toString(); + if (!json_album.contains("type")) { + Error("Invalid Json reply, album object is missing type.", json_album); + continue; + } + QString type = json_album["type"].toString(); if (type != "album") { - Error("Invalid Json reply, incorrect type returned", json_obj); + Error("Invalid Json reply, incorrect type returned", json_album); continue; } @@ -242,7 +260,7 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) } QJsonValue json_value_artist = json_obj["artist"]; if (!json_value_artist.isObject()) { - Error("Invalid Json reply, item artist is not a object.", json_value_artist); + Error("Invalid Json reply, artist is not a object.", json_value_artist); continue; } QJsonObject json_artist = json_value_artist.toObject(); @@ -253,27 +271,27 @@ void DeezerCoverProvider::HandleSearchReply(QNetworkReply *reply, const int id) } QString artist = json_artist["name"].toString(); - if (!json_obj.contains("title")) { - Error("Invalid Json reply, data missing title.", json_obj); + if (!json_album.contains("title")) { + Error("Invalid Json reply, data missing title.", json_album); continue; } - QString album = json_obj["title"].toString(); + QString album = json_album["title"].toString(); QString cover; - if (json_obj.contains("cover_xl")) { - cover = json_obj["cover_xl"].toString(); + if (json_album.contains("cover_xl")) { + cover = json_album["cover_xl"].toString(); } - else if (json_obj.contains("cover_big")) { - cover = json_obj["cover_big"].toString(); + else if (json_album.contains("cover_big")) { + cover = json_album["cover_big"].toString(); } - else if (json_obj.contains("cover_medium")) { - cover = json_obj["cover_medium"].toString(); + else if (json_album.contains("cover_medium")) { + cover = json_album["cover_medium"].toString(); } - else if (json_obj.contains("cover_small")) { - cover = json_obj["cover_small"].toString(); + else if (json_album.contains("cover_small")) { + cover = json_album["cover_small"].toString(); } else { - Error("Invalid Json reply, data missing cover.", json_obj); + Error("Invalid Json reply, album missing cover.", json_album); continue; } QUrl url(cover); diff --git a/src/covermanager/deezercoverprovider.h b/src/covermanager/deezercoverprovider.h index f44500e3d..5858fe82c 100644 --- a/src/covermanager/deezercoverprovider.h +++ b/src/covermanager/deezercoverprovider.h @@ -40,7 +40,7 @@ class DeezerCoverProvider : public CoverProvider { public: explicit DeezerCoverProvider(Application *app, QObject *parent = nullptr); - bool StartSearch(const QString &artist, const QString &album, const int id); + bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id); void CancelSearch(const int id); private slots: diff --git a/src/covermanager/discogscoverprovider.cpp b/src/covermanager/discogscoverprovider.cpp index 092d7fc7d..e89dedc21 100644 --- a/src/covermanager/discogscoverprovider.cpp +++ b/src/covermanager/discogscoverprovider.cpp @@ -59,9 +59,11 @@ const char *DiscogsCoverProvider::kUrlReleases = "https://api.discogs.com/releas const char *DiscogsCoverProvider::kAccessKeyB64 = "dGh6ZnljUGJlZ1NEeXBuSFFxSVk="; const char *DiscogsCoverProvider::kSecretKeyB64 = "ZkFIcmlaSER4aHhRSlF2U3d0bm5ZVmdxeXFLWUl0UXI="; -DiscogsCoverProvider::DiscogsCoverProvider(Application *app, QObject *parent) : CoverProvider("Discogs", 0.0, false, app, parent), network_(new NetworkAccessManager(this)) {} +DiscogsCoverProvider::DiscogsCoverProvider(Application *app, QObject *parent) : CoverProvider("Discogs", 0.0, false, false, app, parent), network_(new NetworkAccessManager(this)) {} -bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, const int s_id) { +bool DiscogsCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int s_id) { + + Q_UNUSED(title); DiscogsCoverSearchContext *s_ctx = new DiscogsCoverSearchContext; diff --git a/src/covermanager/discogscoverprovider.h b/src/covermanager/discogscoverprovider.h index 8c429c9c0..1fd9c735c 100644 --- a/src/covermanager/discogscoverprovider.h +++ b/src/covermanager/discogscoverprovider.h @@ -73,7 +73,7 @@ class DiscogsCoverProvider : public CoverProvider { public: explicit DiscogsCoverProvider(Application *app, QObject *parent = nullptr); - bool StartSearch(const QString &artist, const QString &album, const int s_id); + bool StartSearch(const QString &artist, const QString &album, const QString &title, const int s_id); void CancelSearch(const int id); diff --git a/src/covermanager/lastfmcoverprovider.cpp b/src/covermanager/lastfmcoverprovider.cpp index 643a7c875..e58f857f9 100644 --- a/src/covermanager/lastfmcoverprovider.cpp +++ b/src/covermanager/lastfmcoverprovider.cpp @@ -52,9 +52,11 @@ const char *LastFmCoverProvider::kUrl = "https://ws.audioscrobbler.com/2.0/"; const char *LastFmCoverProvider::kApiKey = "211990b4c96782c05d1536e7219eb56e"; const char *LastFmCoverProvider::kSecret = "80fd738f49596e9709b1bf9319c444a8"; -LastFmCoverProvider::LastFmCoverProvider(Application *app, QObject *parent) : CoverProvider("last.fm", 1.0, true, app, parent), network_(new NetworkAccessManager(this)) {} +LastFmCoverProvider::LastFmCoverProvider(Application *app, QObject *parent) : CoverProvider("last.fm", 1.0, true, false, app, parent), network_(new NetworkAccessManager(this)) {} -bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) { +bool LastFmCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { + + Q_UNUSED(title); typedef QPair Param; typedef QPair EncodedParam; diff --git a/src/covermanager/lastfmcoverprovider.h b/src/covermanager/lastfmcoverprovider.h index 8acaeeba1..e456cc50b 100644 --- a/src/covermanager/lastfmcoverprovider.h +++ b/src/covermanager/lastfmcoverprovider.h @@ -40,7 +40,7 @@ class LastFmCoverProvider : public CoverProvider { public: explicit LastFmCoverProvider(Application *app, QObject *parent = nullptr); - bool StartSearch(const QString &artist, const QString &album, const int id); + bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id); private slots: void QueryFinished(QNetworkReply *reply, const int id); diff --git a/src/covermanager/musicbrainzcoverprovider.cpp b/src/covermanager/musicbrainzcoverprovider.cpp index 28dad534e..c0ac44d89 100644 --- a/src/covermanager/musicbrainzcoverprovider.cpp +++ b/src/covermanager/musicbrainzcoverprovider.cpp @@ -47,9 +47,11 @@ const char *MusicbrainzCoverProvider::kReleaseSearchUrl = "https://musicbrainz.o const char *MusicbrainzCoverProvider::kAlbumCoverUrl = "https://coverartarchive.org/release/%1/front"; const int MusicbrainzCoverProvider::kLimit = 8; -MusicbrainzCoverProvider::MusicbrainzCoverProvider(Application *app, QObject *parent): CoverProvider("MusicBrainz", 1.5, true, app, parent), network_(new NetworkAccessManager(this)) {} +MusicbrainzCoverProvider::MusicbrainzCoverProvider(Application *app, QObject *parent): CoverProvider("MusicBrainz", 1.5, true, false, app, parent), network_(new NetworkAccessManager(this)) {} -bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) { +bool MusicbrainzCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { + + Q_UNUSED(title); QString query = QString("release:\"%1\" AND artist:\"%2\"").arg(album.trimmed().replace('"', "\\\"")).arg(artist.trimmed().replace('"', "\\\"")); diff --git a/src/covermanager/musicbrainzcoverprovider.h b/src/covermanager/musicbrainzcoverprovider.h index 972289c51..905695d98 100644 --- a/src/covermanager/musicbrainzcoverprovider.h +++ b/src/covermanager/musicbrainzcoverprovider.h @@ -39,7 +39,7 @@ class MusicbrainzCoverProvider : public CoverProvider { public: explicit MusicbrainzCoverProvider(Application *app, QObject *parent = nullptr); - bool StartSearch(const QString &artist, const QString &album, const int id); + bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id); void CancelSearch(const int id); private slots: diff --git a/src/covermanager/tidalcoverprovider.cpp b/src/covermanager/tidalcoverprovider.cpp index 7a6658d42..6d404b9e1 100644 --- a/src/covermanager/tidalcoverprovider.cpp +++ b/src/covermanager/tidalcoverprovider.cpp @@ -52,13 +52,15 @@ const char *TidalCoverProvider::kResourcesUrl = "https://resources.tidal.com"; const int TidalCoverProvider::kLimit = 10; TidalCoverProvider::TidalCoverProvider(Application *app, QObject *parent) : - CoverProvider("Tidal", 2.0, true, app, parent), + CoverProvider("Tidal", 2.0, true, false, app, parent), service_(app->internet_services()->Service()), network_(new NetworkAccessManager(this)) { } -bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, const int id) { +bool TidalCoverProvider::StartSearch(const QString &artist, const QString &album, const QString &title, const int id) { + + Q_UNUSED(title); if (!service_ || !service_->authenticated()) return false; diff --git a/src/covermanager/tidalcoverprovider.h b/src/covermanager/tidalcoverprovider.h index 9eb3ee2e4..763e45c61 100644 --- a/src/covermanager/tidalcoverprovider.h +++ b/src/covermanager/tidalcoverprovider.h @@ -44,7 +44,7 @@ class TidalCoverProvider : public CoverProvider { public: explicit TidalCoverProvider(Application *app, QObject *parent = nullptr); - bool StartSearch(const QString &artist, const QString &album, const int id); + bool StartSearch(const QString &artist, const QString &album, const QString &title, const int id); void CancelSearch(int id); private slots: diff --git a/src/dialogs/addstreamdialog.cpp b/src/dialogs/addstreamdialog.cpp new file mode 100644 index 000000000..fbcf7ecd4 --- /dev/null +++ b/src/dialogs/addstreamdialog.cpp @@ -0,0 +1,54 @@ +/* + * Strawberry Music Player + * Copyright 2020, Jonas Kvinge + * + * 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 . + * + */ + +#include "addstreamdialog.h" +#include "ui_addstreamdialog.h" + +#include +#include +#include +#include +#include +#include +#include + +AddStreamDialog::AddStreamDialog(QWidget* parent) : QDialog(parent), ui_(new Ui_AddStreamDialog) { + + ui_->setupUi(this); + + connect(ui_->url, SIGNAL(textChanged(QString)), SLOT(TextChanged(QString))); + TextChanged(QString()); + +} + +AddStreamDialog::~AddStreamDialog() { delete ui_; } + +void AddStreamDialog::showEvent(QShowEvent*) { + + ui_->url->setFocus(); + ui_->url->selectAll(); + +} + +void AddStreamDialog::TextChanged(const QString &text) { + + QUrl url(text); + ui_->button_box->button(QDialogButtonBox::Ok)->setEnabled(url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()); + +} diff --git a/src/dialogs/addstreamdialog.h b/src/dialogs/addstreamdialog.h new file mode 100644 index 000000000..c04cbdf1c --- /dev/null +++ b/src/dialogs/addstreamdialog.h @@ -0,0 +1,51 @@ +/* + * Strawberry Music Player + * Copyright 2020, Jonas Kvinge + * + * 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 . + * + */ + +#ifndef ADDSTREAMDIALOG_H +#define ADDSTREAMDIALOG_H + +#include +#include +#include +#include + +#include "ui_addstreamdialog.h" + +class AddStreamDialog : public QDialog { + Q_OBJECT + + public: + AddStreamDialog(QWidget *parent = nullptr); + ~AddStreamDialog(); + + QUrl url() const { return QUrl(ui_->url->text()); } + void set_url(const QUrl &url) { ui_->url->setText(url.toString());} + + protected: + void showEvent(QShowEvent*); + + private slots: + void TextChanged(const QString &text); + + private: + Ui_AddStreamDialog *ui_; + +}; + +#endif // ADDSTREAMDIALOG_H diff --git a/src/dialogs/addstreamdialog.ui b/src/dialogs/addstreamdialog.ui new file mode 100644 index 000000000..304f29209 --- /dev/null +++ b/src/dialogs/addstreamdialog.ui @@ -0,0 +1,98 @@ + + + AddStreamDialog + + + + 0 + 0 + 400 + 120 + + + + Add Stream + + + + :/icons/48x48/document-open-remote.png:/icons/48x48/document-open-remote.png + + + + + + Enter the URL of a stream: + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + url + button_box + + + + + + + + button_box + accepted() + AddStreamDialog + accept() + + + 257 + 158 + + + 157 + 167 + + + + + button_box + rejected() + AddStreamDialog + reject() + + + 325 + 158 + + + 286 + 167 + + + + + diff --git a/src/dialogs/edittagdialog.cpp b/src/dialogs/edittagdialog.cpp index e9f5bd5ba..bd871779a 100644 --- a/src/dialogs/edittagdialog.cpp +++ b/src/dialogs/edittagdialog.cpp @@ -81,6 +81,8 @@ #endif #include "covermanager/albumcoverchoicecontroller.h" #include "covermanager/albumcoverloader.h" +#include "covermanager/albumcoverloaderoptions.h" +#include "covermanager/albumcoverloaderresult.h" #include "covermanager/coverproviders.h" #include "edittagdialog.h" #include "trackselectiondialog.h" @@ -106,9 +108,9 @@ EditTagDialog::EditTagDialog(Application *app, QWidget *parent) pending_(0) { - cover_options_.default_output_image_ = AlbumCoverLoader::ScaleAndPad(cover_options_, QImage(":/pictures/cdcase.png")); + cover_options_.default_output_image_ = AlbumCoverLoader::ScaleAndPad(cover_options_, QImage(":/pictures/cdcase.png")).first; - connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage, QImage))); + connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult))); #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) connect(tag_fetcher_, SIGNAL(ResultAvailable(Song, SongList)), results_dialog_, SLOT(FetchTagFinished(Song, SongList)), Qt::QueuedConnection); @@ -562,13 +564,11 @@ void EditTagDialog::UpdateStatisticsTab(const Song &song) { } -void EditTagDialog::AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original) { - - Q_UNUSED(cover_url); +void EditTagDialog::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result) { if (id == cover_art_id_) { - ui_->art->setPixmap(QPixmap::fromImage(scaled)); - original_ = original; + ui_->art->setPixmap(QPixmap::fromImage(result.image_scaled)); + original_ = result.image_original; } } diff --git a/src/dialogs/edittagdialog.h b/src/dialogs/edittagdialog.h index 7b2ced00f..39f684284 100644 --- a/src/dialogs/edittagdialog.h +++ b/src/dialogs/edittagdialog.h @@ -38,6 +38,7 @@ #include "core/tagreaderclient.h" #include "playlist/playlistitem.h" #include "covermanager/albumcoverloaderoptions.h" +#include "covermanager/albumcoverloaderresult.h" class QWidget; class QMenu; @@ -110,7 +111,7 @@ class EditTagDialog : public QDialog { void FetchTag(); void FetchTagSongChosen(const Song &original_song, const Song &new_metadata); - void AlbumCoverLoaded(const quint64 id, const QUrl &cover_url, const QImage &scaled, const QImage &original); + void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result); void LoadCoverFromFile(); void SaveCoverToFile(); diff --git a/src/engine/gstenginepipeline.cpp b/src/engine/gstenginepipeline.cpp index d30fe4169..71dd73cc5 100644 --- a/src/engine/gstenginepipeline.cpp +++ b/src/engine/gstenginepipeline.cpp @@ -839,6 +839,16 @@ void GstEnginePipeline::TagMessageReceived(GstMessage *msg) { bundle.bitrate = ParseUIntTag(taglist, GST_TAG_BITRATE) / 1000; bundle.lyrics = ParseStrTag(taglist, GST_TAG_LYRICS); + if (!bundle.title.isEmpty() && bundle.artist.isEmpty() && bundle.album.isEmpty() && bundle.title.contains(" - ")) { + QStringList title_splitted = bundle.title.split(" - "); + if (title_splitted.count() == 2) { + bundle.artist = title_splitted.first(); + bundle.title = title_splitted.last(); + bundle.artist = bundle.artist.trimmed(); + bundle.title = bundle.title.trimmed(); + } + } + gst_tag_list_free(taglist); emit MetadataFound(id(), bundle); diff --git a/src/internet/internetplaylistitem.cpp b/src/internet/internetplaylistitem.cpp index 432d5ab3f..15649fd5f 100644 --- a/src/internet/internetplaylistitem.cpp +++ b/src/internet/internetplaylistitem.cpp @@ -46,9 +46,11 @@ InternetPlaylistItem::InternetPlaylistItem(InternetService *service, const Song } bool InternetPlaylistItem::InitFromQuery(const SqlRow &query) { + metadata_.InitFromQuery(query, false, (Song::kColumns.count() + 1) * 1); InitMetadata(); return true; + } QVariant InternetPlaylistItem::DatabaseValue(DatabaseColumn column) const { @@ -56,15 +58,26 @@ QVariant InternetPlaylistItem::DatabaseValue(DatabaseColumn column) const { } void InternetPlaylistItem::InitMetadata() { + if (metadata_.title().isEmpty()) metadata_.set_title(metadata_.url().toString()); if (metadata_.source() == Song::Source_Unknown) metadata_.set_source(Song::Source_Stream); if (metadata_.filetype() == Song::FileType_Unknown) metadata_.set_filetype(Song::FileType_Stream); metadata_.set_valid(true); + } Song InternetPlaylistItem::Metadata() const { + if (HasTemporaryMetadata()) return temp_metadata_; return metadata_; + } QUrl InternetPlaylistItem::Url() const { return metadata_.url(); } + +void InternetPlaylistItem::SetArtManual(const QUrl &cover_url) { + + metadata_.set_art_manual(cover_url); + temp_metadata_.set_art_manual(cover_url); + +} diff --git a/src/internet/internetplaylistitem.h b/src/internet/internetplaylistitem.h index 0570a68dc..bb5373ed6 100644 --- a/src/internet/internetplaylistitem.h +++ b/src/internet/internetplaylistitem.h @@ -36,10 +36,11 @@ class InternetPlaylistItem : public PlaylistItem { public: explicit InternetPlaylistItem(const Song::Source &type); - InternetPlaylistItem(InternetService *service, const Song &metadata); + explicit InternetPlaylistItem(InternetService *service, const Song &metadata); bool InitFromQuery(const SqlRow &query); Song Metadata() const; QUrl Url() const; + void SetArtManual(const QUrl &cover_url); protected: QVariant DatabaseValue(DatabaseColumn) const; diff --git a/src/internet/internetsearchitemdelegate.cpp b/src/internet/internetsearchitemdelegate.cpp index 5165b8ef3..87c447efb 100644 --- a/src/internet/internetsearchitemdelegate.cpp +++ b/src/internet/internetsearchitemdelegate.cpp @@ -27,11 +27,11 @@ InternetSearchItemDelegate::InternetSearchItemDelegate(InternetSearchView *view) : CollectionItemDelegate(view), view_(view) {} -void InternetSearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { +void InternetSearchItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &idx) const { // Tell the view we painted this item so it can lazy load some art. - const_cast(view_)->LazyLoadAlbumCover(index); + const_cast(view_)->LazyLoadAlbumCover(idx); - CollectionItemDelegate::paint(painter, option, index); + CollectionItemDelegate::paint(painter, option, idx); } diff --git a/src/internet/internetsearchmodel.cpp b/src/internet/internetsearchmodel.cpp index c4785e9c5..c4a418175 100644 --- a/src/internet/internetsearchmodel.cpp +++ b/src/internet/internetsearchmodel.cpp @@ -2,6 +2,7 @@ * Strawberry Music Player * This code was part of Clementine (GlobalSearch) * Copyright 2012, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -56,21 +57,17 @@ InternetSearchModel::InternetSearchModel(InternetService *service, QObject *pare void InternetSearchModel::AddResults(const InternetSearchView::ResultList &results) { - int sort_index = 0; - for (const InternetSearchView::Result &result : results) { QStandardItem *parent = invisibleRootItem(); // Find (or create) the container nodes for this result if we can. ContainerKey key; - key.provider_index_ = sort_index; parent = BuildContainers(result.metadata_, parent, &key); // Create the item QStandardItem *item = new QStandardItem; item->setText(result.metadata_.TitleWithCompilationArtist()); item->setData(QVariant::fromValue(result), Role_Result); - item->setData(sort_index, Role_ProviderIndex); parent->appendRow(item); @@ -78,7 +75,7 @@ void InternetSearchModel::AddResults(const InternetSearchView::ResultList &resul } -QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, int level) { +QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, const int level) { if (level >= 3) { return parent; @@ -249,7 +246,6 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem QStandardItem *container = containers_[*key]; if (!container) { container = new QStandardItem(display_text); - container->setData(key->provider_index_, Role_ProviderIndex); container->setData(sort_text, CollectionModel::Role_SortText); container->setData(group_by_[level], CollectionModel::Role_ContainerType); @@ -275,8 +271,10 @@ QStandardItem *InternetSearchModel::BuildContainers(const Song &s, QStandardItem } void InternetSearchModel::Clear() { + containers_.clear(); clear(); + } InternetSearchView::ResultList InternetSearchModel::GetChildResults(const QModelIndexList &indexes) const { @@ -314,7 +312,7 @@ void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSea const QModelIndex parent_proxy_index = proxy_->mapFromSource(item->index()); // Yes - visit all the children, but do so through the proxy so we get them in the right order. - for (int i = 0; i < item->rowCount(); ++i) { + for (int i = 0 ; i < item->rowCount() ; ++i) { const QModelIndex proxy_index = parent_proxy_index.model()->index(i, 0, parent_proxy_index); const QModelIndex index = proxy_->mapToSource(proxy_index); GetChildResults(itemFromIndex(index), results, visited); @@ -326,21 +324,6 @@ void InternetSearchModel::GetChildResults(const QStandardItem *item, InternetSea if (result.isValid()) { results->append(result.value()); } - else { - // Maybe it's a provider then? - bool is_provider; - const int sort_index = item->data(Role_ProviderIndex).toInt(&is_provider); - if (is_provider) { - // Go through all the items (through the proxy to keep them ordered) and add the ones belonging to this provider to our list - for (int i = 0; i < proxy_->rowCount(invisibleRootItem()->index()); ++i) { - QModelIndex child_index = proxy_->index(i, 0, invisibleRootItem()->index()); - const QStandardItem *child_item = itemFromIndex(proxy_->mapToSource(child_index)); - if (child_item->data(Role_ProviderIndex).toInt() == sort_index) { - GetChildResults(child_item, results, visited); - } - } - } - } } } @@ -360,13 +343,13 @@ void GatherResults(const QStandardItem *parent, InternetSearchView::ResultList * (*results).append(result); } - for (int i = 0; i < parent->rowCount(); ++i) { + for (int i = 0 ; i < parent->rowCount() ; ++i) { GatherResults(parent->child(i), results); } } } -void InternetSearchModel::SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now) { +void InternetSearchModel::SetGroupBy(const CollectionModel::Grouping &grouping, const bool regroup_now) { const CollectionModel::Grouping old_group_by = group_by_; group_by_ = grouping; @@ -389,24 +372,17 @@ MimeData *InternetSearchModel::LoadTracks(const InternetSearchView::ResultList & return nullptr; } - InternetSearchView::ResultList results_copy; - for (const InternetSearchView::Result &result : results) { - results_copy << result; - } - SongList songs; + QList urls; for (const InternetSearchView::Result &result : results) { songs << result.metadata_; + urls << result.metadata_.url(); } InternetSongMimeData *internet_song_mime_data = new InternetSongMimeData(service_); internet_song_mime_data->songs = songs; MimeData *mime_data = internet_song_mime_data; - QList urls; - for (const InternetSearchView::Result &result : results) { - urls << result.metadata_.url(); - } mime_data->setUrls(urls); return mime_data; diff --git a/src/internet/internetsearchmodel.h b/src/internet/internetsearchmodel.h index 5e8729c23..fd14b45c0 100644 --- a/src/internet/internetsearchmodel.h +++ b/src/internet/internetsearchmodel.h @@ -2,6 +2,7 @@ * Strawberry Music Player * This code was part of Clementine (GlobalSearch) * Copyright 2012, David Sansome + * Copyright 2018, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -54,18 +55,16 @@ class InternetSearchModel : public QStandardItemModel { enum Role { Role_Result = CollectionModel::LastRole, Role_LazyLoadingArt, - Role_ProviderIndex, LastRole }; struct ContainerKey { - int provider_index_; QString group_[3]; }; void set_proxy(QSortFilterProxyModel *proxy) { proxy_ = proxy; } void set_use_pretty_covers(const bool pretty) { use_pretty_covers_ = pretty; } - void SetGroupBy(const CollectionModel::Grouping &grouping, bool regroup_now); + void SetGroupBy(const CollectionModel::Grouping &grouping, const bool regroup_now); void Clear(); @@ -82,7 +81,7 @@ class InternetSearchModel : public QStandardItemModel { void AddResults(const InternetSearchView::ResultList &results); private: - QStandardItem *BuildContainers(const Song &metadata, QStandardItem *parent, ContainerKey *key, int level = 0); + QStandardItem *BuildContainers(const Song &s, QStandardItem *parent, ContainerKey *key, const int level = 0); void GetChildResults(const QStandardItem *item, InternetSearchView::ResultList *results, QSet *visited) const; private: @@ -98,7 +97,7 @@ class InternetSearchModel : public QStandardItemModel { }; inline uint qHash(const InternetSearchModel::ContainerKey &key) { - return qHash(key.provider_index_) ^ qHash(key.group_[0]) ^ qHash(key.group_[1]) ^ qHash(key.group_[2]); + return qHash(key.group_[0]) ^ qHash(key.group_[1]) ^ qHash(key.group_[2]); } inline bool operator<(const InternetSearchModel::ContainerKey &left, const InternetSearchModel::ContainerKey &right) { @@ -106,7 +105,6 @@ inline bool operator<(const InternetSearchModel::ContainerKey &left, const Inter if (left.field < right.field) return true; \ if (left.field > right.field) return false - CMP(provider_index_); CMP(group_[0]); CMP(group_[1]); CMP(group_[2]); diff --git a/src/internet/internetsearchsortmodel.cpp b/src/internet/internetsearchsortmodel.cpp index 29aee512e..0fd5de785 100644 --- a/src/internet/internetsearchsortmodel.cpp +++ b/src/internet/internetsearchsortmodel.cpp @@ -32,15 +32,9 @@ #include "internetsearchsortmodel.h" #include "internetsearchview.h" -InternetSearchSortModel::InternetSearchSortModel(QObject *parent) - : QSortFilterProxyModel(parent) {} +InternetSearchSortModel::InternetSearchSortModel(QObject *parent) : QSortFilterProxyModel(parent) {} bool InternetSearchSortModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - // Compare the provider sort index first. - const int index_left = left.data(InternetSearchModel::Role_ProviderIndex).toInt(); - const int index_right = right.data(InternetSearchModel::Role_ProviderIndex).toInt(); - if (index_left < index_right) return true; - if (index_left > index_right) return false; // Dividers always go first if (left.data(CollectionModel::Role_IsDivider).toBool()) return true; diff --git a/src/internet/internetsearchview.cpp b/src/internet/internetsearchview.cpp index 35fee56d7..3c3df2ed2 100644 --- a/src/internet/internetsearchview.cpp +++ b/src/internet/internetsearchview.cpp @@ -21,6 +21,7 @@ #include "config.h" +#include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -69,10 +71,13 @@ #include "core/mimedata.h" #include "core/iconloader.h" #include "core/song.h" +#include "core/logging.h" #include "collection/collectionfilterwidget.h" #include "collection/collectionmodel.h" #include "collection/groupbydialog.h" #include "covermanager/albumcoverloader.h" +#include "covermanager/albumcoverloaderoptions.h" +#include "covermanager/albumcoverloaderresult.h" #include "internetsongmimedata.h" #include "internetservice.h" #include "internetsearchitemdelegate.h" @@ -102,11 +107,11 @@ InternetSearchView::InternetSearchView(QWidget *parent) back_proxy_(new InternetSearchSortModel(this)), current_proxy_(front_proxy_), swap_models_timer_(new QTimer(this)), + use_pretty_covers_(true), search_type_(InternetSearchView::SearchType_Artists), search_error_(false), last_search_id_(0), - searches_next_id_(1), - art_searches_next_id_(1) { + searches_next_id_(1) { ui_->setupUi(this); @@ -164,7 +169,7 @@ void InternetSearchView::Init(Application *app, InternetService *service) { current_model_ = front_model_; current_proxy_ = front_proxy_; - + // Set up the sorting proxy model front_proxy_->setSourceModel(front_model_); front_proxy_->setDynamicSortFilter(true); @@ -190,6 +195,7 @@ void InternetSearchView::Init(Application *app, InternetService *service) { connect(ui_->radiobutton_search_albums, SIGNAL(clicked(bool)), SLOT(SearchAlbumsClicked(bool))); connect(ui_->radiobutton_search_songs, SIGNAL(clicked(bool)), SLOT(SearchSongsClicked(bool))); connect(group_by_actions_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*))); + connect(group_by_actions_, SIGNAL(triggered(QAction*)), SLOT(GroupByClicked(QAction*))); connect(ui_->search, SIGNAL(textChanged(QString)), SLOT(TextEdited(QString))); connect(ui_->results, SIGNAL(AddToPlaylistSignal(QMimeData*)), SIGNAL(AddToPlaylist(QMimeData*))); @@ -201,7 +207,7 @@ void InternetSearchView::Init(Application *app, InternetService *service) { connect(service_, SIGNAL(SearchResults(int, SongList, QString)), SLOT(SearchDone(int, SongList, QString))); connect(app_, SIGNAL(SettingsChanged()), SLOT(ReloadSettings())); - connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QUrl, QImage)), SLOT(AlbumCoverLoaded(quint64, QUrl, QImage))); + connect(app_->album_cover_loader(), SIGNAL(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(quint64, AlbumCoverLoaderResult))); ReloadSettings(); @@ -214,9 +220,9 @@ void InternetSearchView::ReloadSettings() { // Collection settings s.beginGroup(service_->settings_group()); - const bool pretty = s.value("pretty_covers", true).toBool(); - front_model_->set_use_pretty_covers(pretty); - back_model_->set_use_pretty_covers(pretty); + use_pretty_covers_ = s.value("pretty_covers", true).toBool(); + front_model_->set_use_pretty_covers(use_pretty_covers_); + back_model_->set_use_pretty_covers(use_pretty_covers_); // Internet search settings @@ -248,12 +254,6 @@ void InternetSearchView::showEvent(QShowEvent *e) { } -void InternetSearchView::hideEvent(QHideEvent *e) { - - QWidget::hideEvent(e); - -} - bool InternetSearchView::eventFilter(QObject *object, QEvent *e) { if (object == ui_->search && e->type() == QEvent::KeyRelease) { @@ -374,6 +374,7 @@ void InternetSearchView::TextEdited(const QString &text) { const QString trimmed(text.trimmed()); search_error_ = false; + cover_loader_tasks_.clear(); // Add results to the back model, switch models after some delay. back_model_->Clear(); @@ -387,7 +388,7 @@ void InternetSearchView::TextEdited(const QString &text) { // If text query is empty, don't start a new search if (trimmed.isEmpty()) { last_search_id_ = -1; - ui_->label_helptext->setText("Enter search terms above to find music"); + ui_->label_helptext->setText(tr("Enter search terms above to find music")); ui_->label_status->clear(); ui_->progressbar->hide(); ui_->progressbar->reset(); @@ -401,7 +402,7 @@ void InternetSearchView::TextEdited(const QString &text) { void InternetSearchView::SwapModels() { - art_requests_.clear(); + cover_loader_tasks_.clear(); std::swap(front_model_, back_model_); std::swap(front_proxy_, back_proxy_); @@ -488,10 +489,8 @@ void InternetSearchView::SearchDone(const int service_id, const SongList &songs, results << result; } - if (results.isEmpty()) return; - // Load cached pixmaps into the results - for (InternetSearchView::ResultList::iterator it = results.begin(); it != results.end(); ++it) { + for (InternetSearchView::ResultList::iterator it = results.begin() ; it != results.end() ; ++it) { it->pixmap_cache_key_ = PixmapCacheKey(*it); } @@ -576,7 +575,7 @@ MimeData *InternetSearchView::SelectedMimeData() { QModelIndexList indexes = ui_->results->selectionModel()->selectedRows(); if (indexes.isEmpty()) { // There's nothing selected - take the first thing in the model that isn't a divider. - for (int i = 0; i < front_proxy_->rowCount(); ++i) { + for (int i = 0 ; i < front_proxy_->rowCount() ; ++i) { QModelIndex index = front_proxy_->index(i, 0); if (!index.data(CollectionModel::Role_IsDivider).toBool()) { indexes << index; @@ -663,7 +662,7 @@ void InternetSearchView::GroupByClicked(QAction *action) { if (action->property("group_by").isNull()) { if (!group_by_dialog_) { group_by_dialog_.reset(new GroupByDialog); - connect(group_by_dialog_.data(), SIGNAL(Accepted(CollectionModel::Grouping)), SLOT(SetGroupBy(CollectionModel::Grouping))); + connect(group_by_dialog_.get(), SIGNAL(Accepted(CollectionModel::Grouping)), SLOT(SetGroupBy(CollectionModel::Grouping))); } group_by_dialog_->show(); @@ -679,7 +678,8 @@ void InternetSearchView::SetGroupBy(const CollectionModel::Grouping &g) { // Clear requests: changing "group by" on the models will cause all the items to be removed/added again, // so all the QModelIndex here will become invalid. New requests will be created for those // songs when they will be displayed again anyway (when InternetSearchItemDelegate::paint will call LazyLoadAlbumCover) - art_requests_.clear(); + cover_loader_tasks_.clear(); + // Update the models front_model_->SetGroupBy(g, true); back_model_->SetGroupBy(g, false); @@ -722,10 +722,12 @@ void InternetSearchView::SearchSongsClicked(const bool) { void InternetSearchView::SetSearchType(const InternetSearchView::SearchType type) { search_type_ = type; + QSettings s; s.beginGroup(service_->settings_group()); s.setValue("type", int(search_type_)); s.endGroup(); + TextEdited(ui_->search->text()); } @@ -761,110 +763,101 @@ void InternetSearchView::AddSongs() { } QString InternetSearchView::PixmapCacheKey(const InternetSearchView::Result &result) const { - return "internet:" % result.metadata_.url().toString(); + + if (result.metadata_.art_automatic_is_valid()) { + return Song::TextForSource(service_->source()) + "/" + result.metadata_.art_automatic().toString(); + } + else if (!result.metadata_.effective_albumartist().isEmpty() && !result.metadata_.album().isEmpty()) { + return Song::TextForSource(service_->source()) + "/" + result.metadata_.effective_albumartist() + "/" + result.metadata_.album(); + } + else { + return Song::TextForSource(service_->source()) + "/" + result.metadata_.url().toString(); + } + } bool InternetSearchView::FindCachedPixmap(const InternetSearchView::Result &result, QPixmap *pixmap) const { return pixmap_cache_.find(result.pixmap_cache_key_, pixmap); } -int InternetSearchView::LoadAlbumCoverAsync(const InternetSearchView::Result &result) { - - const int id = art_searches_next_id_++; - - pending_art_searches_[id] = result.pixmap_cache_key_; - - quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, result.metadata_); - cover_loader_tasks_[loader_id] = id; - - return id; - -} - void InternetSearchView::LazyLoadAlbumCover(const QModelIndex &proxy_index) { if (!proxy_index.isValid() || proxy_index.model() != front_proxy_) { return; } + const QModelIndex source_index = front_proxy_->mapToSource(proxy_index); + if (!source_index.isValid()) { + return; + } + // Already loading art for this item? - if (proxy_index.data(InternetSearchModel::Role_LazyLoadingArt).isValid()) { + if (source_index.data(InternetSearchModel::Role_LazyLoadingArt).isValid()) { return; } // Should we even load art at all? - if (!app_->collection_model()->use_pretty_covers()) { + if (!use_pretty_covers_) { return; } // Is this an album? const CollectionModel::GroupBy container_type = CollectionModel::GroupBy(proxy_index.data(CollectionModel::Role_ContainerType).toInt()); if (container_type != CollectionModel::GroupBy_Album && + container_type != CollectionModel::GroupBy_AlbumDisc && container_type != CollectionModel::GroupBy_YearAlbum && + container_type != CollectionModel::GroupBy_YearAlbumDisc && container_type != CollectionModel::GroupBy_OriginalYearAlbum) { return; } // Mark the item as loading art - const QModelIndex source_index = front_proxy_->mapToSource(proxy_index); - QStandardItem *item = front_model_->itemFromIndex(source_index); - item->setData(true, InternetSearchModel::Role_LazyLoadingArt); + + QStandardItem *item_album = front_model_->itemFromIndex(source_index); + if (!item_album) { + return; + } + item_album->setData(true, InternetSearchModel::Role_LazyLoadingArt); // Walk down the item's children until we find a track - while (item->rowCount()) { - item = item->child(0); + QStandardItem *item_song = item_album; + while (item_song->rowCount()) { + item_song = item_song->child(0); } // Get the track's Result - const InternetSearchView::Result result = item->data(InternetSearchModel::Role_Result).value(); + const InternetSearchView::Result result = item_song->data(InternetSearchModel::Role_Result).value(); - // Load the art. - int id = LoadAlbumCoverAsync(result); - art_requests_[id] = source_index; - -} - -void InternetSearchView::AlbumCoverLoaded(const quint64 id, const QUrl&, const QImage &image) { - - if (!cover_loader_tasks_.contains(id)) return; - int orig_id = cover_loader_tasks_.take(id); - - const QString key = pending_art_searches_.take(orig_id); - - QPixmap pixmap = QPixmap::fromImage(image); - pixmap_cache_.insert(key, pixmap); - - if (!art_requests_.contains(id)) return; - QModelIndex idx = art_requests_.take(id); - - if (!pixmap.isNull() && idx.isValid()) { - front_model_->itemFromIndex(idx)->setData(pixmap, Qt::DecorationRole); + QPixmap cached_pixmap; + if (pixmap_cache_.find(result.pixmap_cache_key_, &cached_pixmap)) { + item_album->setData(cached_pixmap, Qt::DecorationRole); + } + else { + quint64 loader_id = app_->album_cover_loader()->LoadImageAsync(cover_loader_options_, result.metadata_); + cover_loader_tasks_[loader_id] = qMakePair(source_index, result.pixmap_cache_key_); } } -QImage InternetSearchView::ScaleAndPad(const QImage &image) { +void InternetSearchView::AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &albumcover_result) { - if (image.isNull()) return QImage(); + if (!cover_loader_tasks_.contains(id)) { + return; + } + QPair cover_loader_task = cover_loader_tasks_.take(id); + QModelIndex idx = cover_loader_task.first; + QString key = cover_loader_task.second; - const QSize target_size = QSize(kArtHeight, kArtHeight); + QPixmap pixmap = QPixmap::fromImage(albumcover_result.image_scaled); + if (!pixmap.isNull()) { + pixmap_cache_.insert(key, pixmap); + } - if (image.size() == target_size) return image; - - // Scale the image down - QImage copy; - copy = image.scaled(target_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - - // Pad the image to kHeight x kHeight - if (copy.size() == target_size) return copy; - - QImage padded_image(kArtHeight, kArtHeight, QImage::Format_ARGB32); - padded_image.fill(0); - - QPainter p(&padded_image); - p.drawImage((kArtHeight - copy.width()) / 2, (kArtHeight - copy.height()) / 2, copy); - p.end(); - - return padded_image; + if (idx.isValid()) { + QStandardItem *item = front_model_->itemFromIndex(idx); + if (item) { + item->setData(albumcover_result.image_scaled, Qt::DecorationRole); + } + } } diff --git a/src/internet/internetsearchview.h b/src/internet/internetsearchview.h index 3b7014c15..58735cb49 100644 --- a/src/internet/internetsearchview.h +++ b/src/internet/internetsearchview.h @@ -24,10 +24,13 @@ #include "config.h" +#include + #include #include #include #include +#include #include #include #include @@ -36,12 +39,12 @@ #include #include #include -#include #include #include "core/song.h" #include "collection/collectionmodel.h" #include "covermanager/albumcoverloaderoptions.h" +#include "covermanager/albumcoverloaderresult.h" #include "settings/settingsdialog.h" class QSortFilterProxyModel; @@ -53,14 +56,12 @@ class QActionGroup; class QEvent; class QKeyEvent; class QShowEvent; -class QHideEvent; class QContextMenuEvent; class QTimerEvent; class Application; class MimeData; class GroupByDialog; -class AlbumCoverLoader; class InternetService; class InternetSearchModel; class Ui_InternetSearchView; @@ -104,7 +105,6 @@ class InternetSearchView : public QWidget { }; void showEvent(QShowEvent *e); - void hideEvent(QHideEvent *e); bool eventFilter(QObject *object, QEvent *e); void timerEvent(QTimerEvent *e); @@ -135,7 +135,6 @@ class InternetSearchView : public QWidget { QString PixmapCacheKey(const Result &result) const; bool FindCachedPixmap(const Result &result, QPixmap *pixmap) const; - static QImage ScaleAndPad(const QImage &image); int LoadAlbumCoverAsync(const Result &result); signals: @@ -173,7 +172,7 @@ class InternetSearchView : public QWidget { void GroupByClicked(QAction *action); void SetGroupBy(const CollectionModel::Grouping &g); - void AlbumCoverLoaded(const quint64 id, const QUrl&, const QImage &image); + void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &albumcover_result); public slots: void ReloadSettings(); @@ -187,7 +186,7 @@ class InternetSearchView : public QWidget { Application *app_; InternetService *service_; Ui_InternetSearchView *ui_; - QScopedPointer group_by_dialog_; + std::unique_ptr group_by_dialog_; QMenu *context_menu_; QList context_actions_; @@ -206,18 +205,17 @@ class InternetSearchView : public QWidget { QTimer *swap_models_timer_; + bool use_pretty_covers_; SearchType search_type_; bool search_error_; int last_search_id_; int searches_next_id_; - int art_searches_next_id_; QMap delayed_searches_; QMap pending_searches_; - QMap pending_art_searches_; - QMap art_requests_; + AlbumCoverLoaderOptions cover_loader_options_; - QMap cover_loader_tasks_; + QMap> cover_loader_tasks_; QPixmapCache pixmap_cache_; }; diff --git a/src/playlist/playlist.cpp b/src/playlist/playlist.cpp index 0d3c6e481..a67812181 100644 --- a/src/playlist/playlist.cpp +++ b/src/playlist/playlist.cpp @@ -69,6 +69,7 @@ #include "collection/collection.h" #include "collection/collectionbackend.h" #include "collection/collectionplaylistitem.h" +#include "covermanager/albumcoverloader.h" #include "queue/queue.h" #include "playlist.h" #include "playlistitem.h" @@ -88,12 +89,6 @@ using std::placeholders::_1; using std::placeholders::_2; -using std::shared_ptr; -using std::unordered_map; -using std::sort; -using std::stable_sort; -using std::greater; -using std::swap; const char *Playlist::kCddaMimeType = "x-content/audio-cdda"; const char *Playlist::kRowsMimetype = "application/x-strawberry-playlist-rows"; @@ -256,6 +251,7 @@ bool Playlist::set_column_value(Song &song, Playlist::Column column, const QVari default: break; } + return true; } @@ -392,7 +388,9 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd if (reply->is_successful() && index.isValid()) { if (reply->message().save_file_response().success()) { - QFuture future = item_at(index.row())->BackgroundReload(); + PlaylistItemPtr item = item_at(index.row()); + if (!item) return; + QFuture future = item->BackgroundReload(); NewClosure(future, this, SLOT(ItemReloadComplete(QPersistentModelIndex)), index); } else { @@ -406,6 +404,20 @@ void Playlist::SongSaveComplete(TagReaderReply *reply, const QPersistentModelInd void Playlist::ItemReloadComplete(const QPersistentModelIndex &index) { if (index.isValid()) { + + PlaylistItemPtr item = item_at(index.row()); + if (item) { + + // Update temporary metadata for songs that are not in the collection. + // Songs that are in the collection is updated through the collection watcher/backend in playlist manager. + if (item->Metadata().source() != Song::Source_Collection) { + SongPlaylistItem *song_item = static_cast(item.get()); + if (song_item) { + song_item->UpdateTemporaryMetadata(song_item->DatabaseSongMetadata()); + } + } + } + emit dataChanged(index, index); emit EditingFinished(index); } @@ -879,7 +891,7 @@ void Playlist::InsertItems(const PlaylistItemList &itemsIn, int pos, bool play_n // exercise vetoes SongList songs; - for (const PlaylistItemPtr item : items) { + for (PlaylistItemPtr item : items) { songs << item->Metadata(); } @@ -1004,7 +1016,7 @@ void Playlist::InsertInternetItems(InternetService *service, const SongList &son PlaylistItemList playlist_items; for (const Song &song : songs) { - playlist_items << shared_ptr(new InternetPlaylistItem(service, song)); + playlist_items << std::shared_ptr(new InternetPlaylistItem(service, song)); } InsertItems(playlist_items, pos, play_now, enqueue, enqueue_next); @@ -1014,6 +1026,7 @@ void Playlist::InsertInternetItems(InternetService *service, const SongList &son void Playlist::UpdateItems(const SongList &songs) { qLog(Debug) << "Updating playlist with new tracks' info"; + // We first convert our songs list into a linked list (a 'real' list), because removals are faster with QLinkedList. // Next, we walk through the list of playlist's items then the list of songs // we want to update: if an item corresponds to the song (we rely on URL for this), we update the item with the new metadata, @@ -1027,20 +1040,12 @@ void Playlist::UpdateItems(const SongList &songs) { QMutableLinkedListIterator it(songs_list); while (it.hasNext()) { const Song &song = it.next(); - PlaylistItemPtr &item = items_[i]; - if (item->Metadata().url() == song.url() && - ( - item->Metadata().source() == Song::Source_Unknown || - item->Metadata().filetype() == Song::FileType_Unknown || - // Stream may change and may need to be updated too - item->Metadata().is_stream() || - // And CD tracks as well (tags are loaded in a second step) - item->Metadata().is_cdda() - ) - ) { + const PlaylistItemPtr &item = items_[i]; + if (item->Metadata().url() == song.url()) { PlaylistItemPtr new_item; if (song.is_collection_song()) { new_item = PlaylistItemPtr(new CollectionPlaylistItem(song)); + if (collection_items_by_id_.contains(song.id(), item)) collection_items_by_id_.remove(song.id(), item); collection_items_by_id_.insertMulti(song.id(), new_item); } else { @@ -1103,10 +1108,10 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const { } -bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr _a, shared_ptr _b) { +bool Playlist::CompareItems(int column, Qt::SortOrder order, std::shared_ptr _a, std::shared_ptr _b) { - shared_ptr a = order == Qt::AscendingOrder ? _a : _b; - shared_ptr b = order == Qt::AscendingOrder ? _b : _a; + std::shared_ptr a = order == Qt::AscendingOrder ? _a : _b; + std::shared_ptr b = order == Qt::AscendingOrder ? _b : _a; #define cmp(field) return a->Metadata().field() < b->Metadata().field() #define strcmp(field) return QString::localeAwareCompare(a->Metadata().field().toLower(), b->Metadata().field().toLower()) < 0; @@ -1154,10 +1159,10 @@ bool Playlist::CompareItems(int column, Qt::SortOrder order, shared_ptr _a, shared_ptr _b) { +bool Playlist::ComparePathDepths(Qt::SortOrder order, std::shared_ptr _a, std::shared_ptr _b) { - shared_ptr a = order == Qt::AscendingOrder ? _a : _b; - shared_ptr b = order == Qt::AscendingOrder ? _b : _a; + std::shared_ptr a = order == Qt::AscendingOrder ? _a : _b; + std::shared_ptr b = order == Qt::AscendingOrder ? _b : _a; int a_dir_level = a->Url().path().count('/'); int b_dir_level = b->Url().path().count('/'); @@ -1293,6 +1298,7 @@ void Playlist::SetCurrentIsPaused(bool paused) { } void Playlist::Save() const { + if (!backend_ || is_loading_) return; backend_->SavePlaylistAsync(id_, items_, last_played_row()); @@ -1443,7 +1449,7 @@ PlaylistItemList Playlist::RemoveItemsWithoutUndo(int row, int count) { if (item->source() == Song::Source_Collection) { int id = item->Metadata().id(); - if (id != -1) { + if (id != -1 && collection_items_by_id_.contains(id, item)) { collection_items_by_id_.remove(id, item); } } @@ -1755,22 +1761,26 @@ void Playlist::set_sequence(PlaylistSequence *v) { QSortFilterProxyModel *Playlist::proxy() const { return proxy_; } SongList Playlist::GetAllSongs() const { + SongList ret; - for (const PlaylistItemPtr item : items_) { + for (PlaylistItemPtr item : items_) { ret << item->Metadata(); } return ret; + } PlaylistItemList Playlist::GetAllItems() const { return items_; } quint64 Playlist::GetTotalLength() const { + quint64 ret = 0; - for (const PlaylistItemPtr item : items_) { + for (PlaylistItemPtr item : items_) { quint64 length = item->Metadata().length_nanosec(); if (length > 0) ret += length; } return ret; + } PlaylistItemList Playlist::collection_items_by_id(int id) const { @@ -1778,30 +1788,38 @@ PlaylistItemList Playlist::collection_items_by_id(int id) const { } void Playlist::TracksAboutToBeDequeued(const QModelIndex&, int begin, int end) { + for (int i = begin; i <= end; ++i) { temp_dequeue_change_indexes_ << queue_->mapToSource(queue_->index(i, Column_Title)); } + } void Playlist::TracksDequeued() { + for (const QModelIndex &index : temp_dequeue_change_indexes_) { emit dataChanged(index, index); } temp_dequeue_change_indexes_.clear(); emit QueueChanged(); + } void Playlist::TracksEnqueued(const QModelIndex&, int begin, int end) { + const QModelIndex &b = queue_->mapToSource(queue_->index(begin, Column_Title)); const QModelIndex &e = queue_->mapToSource(queue_->index(end, Column_Title)); emit dataChanged(b, e); + } void Playlist::QueueLayoutChanged() { + for (int i = 0; i < queue_->rowCount(); ++i) { const QModelIndex &index = queue_->mapToSource(queue_->index(i, Column_Title)); emit dataChanged(index, index); } + } void Playlist::ItemChanged(PlaylistItemPtr item) { @@ -1858,6 +1876,7 @@ void Playlist::InvalidateDeletedSongs() { } void Playlist::RemoveDeletedSongs() { + QList rows_to_remove; for (int row = 0; row < items_.count(); ++row) { @@ -1892,7 +1911,7 @@ struct SongSimilarEqual { void Playlist::RemoveDuplicateSongs() { QList rows_to_remove; - unordered_map unique_songs; + std::unordered_map unique_songs; for (int row = 0; row < items_.count(); ++row) { PlaylistItemPtr item = items_[row]; @@ -2007,3 +2026,16 @@ void Playlist::UpdateScrobblePoint(const qint64 seek_point_nanosec) { } +void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) { + + // Update art_manual for local songs that are not in the collection. + if (result.type == AlbumCoverLoaderResult::Type_Manual && result.cover_url.isLocalFile() && (song.source() == Song::Source_LocalFile || song.source() == Song::Source_CDDA || song.source() == Song::Source_Device)) { + PlaylistItemPtr item = current_item(); + if (item && item->Metadata() == song && !item->Metadata().art_manual_is_valid()) { + qLog(Debug) << "Updating art manual for local song" << song.title() << song.album() << song.title() << "to" << result.cover_url << "in playlist."; + item->SetArtManual(result.cover_url); + Save(); + } + } + +} diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h index 01c7c061f..f364752e3 100644 --- a/src/playlist/playlist.h +++ b/src/playlist/playlist.h @@ -42,6 +42,7 @@ #include "core/song.h" #include "core/tagreaderclient.h" +#include "covermanager/albumcoverloaderresult.h" #include "playlistitem.h" #include "playlistsequence.h" @@ -348,6 +349,7 @@ class Playlist : public QAbstractListModel { void ItemReloadComplete(const QPersistentModelIndex &index); void ItemsLoaded(QFuture future); void SongInsertVetoListenerDestroyed(); + void AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result); private: bool is_loading_; @@ -364,8 +366,10 @@ class Playlist : public QAbstractListModel { bool favorite_; PlaylistItemList items_; + // Contains the indices into items_ in the order that they will be played. QList virtual_items_; + // A map of collection ID to playlist item - for fast lookups when collection items change. QMultiMap collection_items_by_id_; diff --git a/src/playlist/playlistitem.cpp b/src/playlist/playlistitem.cpp index 9c48e31cd..04958ab44 100644 --- a/src/playlist/playlistitem.cpp +++ b/src/playlist/playlistitem.cpp @@ -43,21 +43,21 @@ PlaylistItem::~PlaylistItem() {} PlaylistItem *PlaylistItem::NewFromSource(const Song::Source &source) { switch (source) { - case Song::Source_Collection: return new CollectionPlaylistItem(); + case Song::Source_Collection: + return new CollectionPlaylistItem(); + case Song::Source_Subsonic: case Song::Source_Tidal: - case Song::Source_Stream: return new InternetPlaylistItem(source); - default: return new SongPlaylistItem(source); + case Song::Source_Qobuz: + case Song::Source_Stream: + return new InternetPlaylistItem(source); + case Song::Source_LocalFile: + case Song::Source_CDDA: + case Song::Source_Device: + case Song::Source_Unknown: + break; } -} - -PlaylistItem *PlaylistItem::NewFromSongsTable(const QString &table, const Song &song) { - - if (table == SCollection::kSongsTable) - return new CollectionPlaylistItem(song); - - qLog(Warning) << "Invalid PlaylistItem songs table:" << table; - return nullptr; + return new SongPlaylistItem(source); } diff --git a/src/playlist/playlistitem.h b/src/playlist/playlistitem.h index 9feebc1c1..075c4984c 100644 --- a/src/playlist/playlistitem.h +++ b/src/playlist/playlistitem.h @@ -49,7 +49,6 @@ class PlaylistItem : public std::enable_shared_from_this { virtual ~PlaylistItem(); static PlaylistItem *NewFromSource(const Song::Source &source); - static PlaylistItem *NewFromSongsTable(const QString &table, const Song &song); enum Option { Default = 0x00, @@ -87,6 +86,8 @@ class PlaylistItem : public std::enable_shared_from_this { qint64 effective_beginning_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.beginning_nanosec() != -1 ? temp_metadata_.beginning_nanosec() : Metadata().beginning_nanosec(); } qint64 effective_end_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.end_nanosec() != -1 ? temp_metadata_.end_nanosec() : Metadata().end_nanosec(); } + virtual void SetArtManual(const QUrl &cover_url) = 0; + // Background colors. void SetBackgroundColor(short priority, const QColor &color); bool HasBackgroundColor(short priority) const; diff --git a/src/playlist/playlistmanager.cpp b/src/playlist/playlistmanager.cpp index 7cf2e409e..0321fb66e 100644 --- a/src/playlist/playlistmanager.cpp +++ b/src/playlist/playlistmanager.cpp @@ -48,6 +48,8 @@ #include "core/utilities.h" #include "collection/collectionbackend.h" #include "collection/collectionplaylistitem.h" +#include "covermanager/albumcoverloaderresult.h" +#include "covermanager/currentalbumcoverloader.h" #include "playlist.h" #include "playlistbackend.h" #include "playlistcontainer.h" @@ -147,6 +149,7 @@ Playlist *PlaylistManager::AddPlaylist(int id, const QString &name, const QStrin connect(ret, SIGNAL(Error(QString)), SIGNAL(Error(QString))); connect(ret, SIGNAL(PlayRequested(QModelIndex)), SIGNAL(PlayRequested(QModelIndex))); connect(playlist_container_->view(), SIGNAL(ColumnAlignmentChanged(ColumnAlignmentMap)), ret, SLOT(SetColumnAlignment(ColumnAlignmentMap))); + connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), ret, SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult))); playlists_[id] = Data(ret, name); @@ -440,7 +443,8 @@ void PlaylistManager::UpdateSummaryText() { QString summary; if (selected > 1) { summary += tr("%1 selected of").arg(selected) + " "; - } else { + } + else { nanoseconds = current()->GetTotalLength(); } @@ -466,7 +470,7 @@ void PlaylistManager::SongsDiscovered(const SongList &songs) { for (const Song &song : songs) { for (const Data &data : playlists_) { PlaylistItemList items = data.p->collection_items_by_id(song.id()); - for (const PlaylistItemPtr item : items) { + for (PlaylistItemPtr item : items) { if (item->Metadata().directory_id() != song.directory_id()) continue; static_cast(item.get())->SetMetadata(song); item->UpdateTemporaryMetadata(song); diff --git a/src/playlist/playlistview.cpp b/src/playlist/playlistview.cpp index 52f36b09b..4858596fd 100644 --- a/src/playlist/playlistview.cpp +++ b/src/playlist/playlistview.cpp @@ -75,6 +75,7 @@ #include "playlistheader.h" #include "playlistview.h" #include "covermanager/currentalbumcoverloader.h" +#include "covermanager/albumcoverloaderresult.h" #include "settings/appearancesettingspage.h" #include "settings/playlistsettingspage.h" @@ -82,8 +83,6 @@ # include "moodbar/moodbaritemdelegate.h" #endif -using std::sort; - const int PlaylistView::kGlowIntensitySteps = 24; const int PlaylistView::kAutoscrollGraceTimeout = 30; // seconds const int PlaylistView::kDropIndicatorWidth = 2; @@ -221,7 +220,7 @@ void PlaylistView::SetApplication(Application *app) { Q_ASSERT(app); app_ = app; connect(app_->playlist_manager(), SIGNAL(CurrentSongChanged(Song)), this, SLOT(SongChanged(Song))); - connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, QUrl, QImage)), SLOT(AlbumCoverLoaded(Song, QUrl, QImage))); + connect(app_->current_albumcover_loader(), SIGNAL(AlbumCoverLoaded(Song, AlbumCoverLoaderResult)), SLOT(AlbumCoverLoaded(Song, AlbumCoverLoaderResult))); connect(app_->player(), SIGNAL(Playing()), SLOT(StartGlowing())); connect(app_->player(), SIGNAL(Paused()), SLOT(StopGlowing())); connect(app_->player(), SIGNAL(Stopped()), SLOT(Stopped())); @@ -1261,17 +1260,15 @@ void PlaylistView::Stopped() { if (song_playing_ == Song()) return; song_playing_ = Song(); StopGlowing(); - AlbumCoverLoaded(Song(), QUrl(), QImage()); + AlbumCoverLoaded(Song()); } -void PlaylistView::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &song_art) { +void PlaylistView::AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result) { - Q_UNUSED(cover_url); + if ((song != Song() && song_playing_ == Song()) || result.image_original == current_song_cover_art_) return; - if ((song != Song() && song_playing_ == Song()) || song_art == current_song_cover_art_) return; - - current_song_cover_art_ = song_art; + current_song_cover_art_ = result.image_original; if (background_image_type_ == AppearanceSettingsPage::BackgroundImageType_Album) { if (song.art_automatic().isEmpty() && song.art_manual().isEmpty()) { set_background_image(QImage()); diff --git a/src/playlist/playlistview.h b/src/playlist/playlistview.h index 79a7aa7ca..c05d9f9ed 100644 --- a/src/playlist/playlistview.h +++ b/src/playlist/playlistview.h @@ -47,6 +47,7 @@ #include #include "core/song.h" +#include "covermanager/albumcoverloaderresult.h" #include "settings/appearancesettingspage.h" #include "playlist.h" @@ -173,7 +174,7 @@ class PlaylistView : public QTreeView { void Playing(); void Stopped(); void SongChanged(const Song &song); - void AlbumCoverLoaded(const Song &new_song, const QUrl &cover_url, const QImage &song_art); + void AlbumCoverLoaded(const Song &song, AlbumCoverLoaderResult result = AlbumCoverLoaderResult()); private: void LoadGeometry(); diff --git a/src/playlist/songplaylistitem.cpp b/src/playlist/songplaylistitem.cpp index 50e841919..b5e2e6dfe 100644 --- a/src/playlist/songplaylistitem.cpp +++ b/src/playlist/songplaylistitem.cpp @@ -48,3 +48,10 @@ Song SongPlaylistItem::Metadata() const { if (HasTemporaryMetadata()) return temp_metadata_; return song_; } + +void SongPlaylistItem::SetArtManual(const QUrl &cover_url) { + + song_.set_art_manual(cover_url); + temp_metadata_.set_art_manual(cover_url); + +} diff --git a/src/playlist/songplaylistitem.h b/src/playlist/songplaylistitem.h index 9260f1151..c9f863749 100644 --- a/src/playlist/songplaylistitem.h +++ b/src/playlist/songplaylistitem.h @@ -44,8 +44,8 @@ class SongPlaylistItem : public PlaylistItem { QUrl Url() const; - protected: Song DatabaseSongMetadata() const { return song_; } + void SetArtManual(const QUrl &cover_url); private: Song song_; diff --git a/src/widgets/playingwidget.cpp b/src/widgets/playingwidget.cpp index b4570354a..36cf46cb9 100644 --- a/src/widgets/playingwidget.cpp +++ b/src/widgets/playingwidget.cpp @@ -48,8 +48,6 @@ #include "covermanager/albumcoverloader.h" #include "playingwidget.h" -using std::unique_ptr; - const char *PlayingWidget::kSettingsGroup = "PlayingWidget"; // Space between the cover and the details in small mode @@ -269,9 +267,7 @@ void PlayingWidget::SongChanged(const Song &song) { song_ = song; } -void PlayingWidget::AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image) { - - Q_UNUSED(cover_url); +void PlayingWidget::AlbumCoverLoaded(const Song &song, const QImage &image) { if (!playing_ || song != song_playing_ || (timeline_fade_->state() == QTimeLine::Running && image == image_original_)) return; @@ -313,7 +309,7 @@ void PlayingWidget::SetImage(const QImage &image) { } void PlayingWidget::ScaleCover() { - pixmap_cover_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_)); + pixmap_cover_ = QPixmap::fromImage(AlbumCoverLoader::ScaleAndPad(cover_loader_options_, image_original_).first); update(); } diff --git a/src/widgets/playingwidget.h b/src/widgets/playingwidget.h index f0d5b28d4..c97c3b2a1 100644 --- a/src/widgets/playingwidget.h +++ b/src/widgets/playingwidget.h @@ -40,8 +40,6 @@ #include "core/song.h" #include "covermanager/albumcoverloaderoptions.h" -using std::unique_ptr; - class QTimeLine; class QTextDocument; class QPainter; @@ -98,7 +96,7 @@ class PlayingWidget : public QWidget { void AutomaticCoverSearchDone(); - void AlbumCoverLoaded(const Song &song, const QUrl &cover_url, const QImage &image); + void AlbumCoverLoaded(const Song &song, const QImage &image); void SetHeight(int height); void FadePreviousTrack(qreal value);