From 16793744c0100cf3fd56250ddd6b9e6a6c1f0e23 Mon Sep 17 00:00:00 2001 From: Arnaud Bienner Date: Thu, 28 Feb 2013 02:17:15 +0100 Subject: [PATCH] Read and write POPM tags --- ext/libclementine-tagreader/tagreader.cpp | 65 ++++++++++++++++++++++ ext/libclementine-tagreader/tagreader.h | 5 ++ tests/data/fmpspopmrating.mp3 | Bin 0 -> 6614 bytes tests/data/popmrating.mp3 | Bin 0 -> 6614 bytes tests/data/testdata.qrc | 2 + tests/song_test.cpp | 14 +++++ 6 files changed, 86 insertions(+) create mode 100644 tests/data/fmpspopmrating.mp3 create mode 100644 tests/data/popmrating.mp3 diff --git a/ext/libclementine-tagreader/tagreader.cpp b/ext/libclementine-tagreader/tagreader.cpp index beb6c70c7..362d9376d 100644 --- a/ext/libclementine-tagreader/tagreader.cpp +++ b/ext/libclementine-tagreader/tagreader.cpp @@ -40,6 +40,7 @@ #include #endif #include +#include #include #include #include @@ -180,6 +181,25 @@ void TagReader::ReadFile(const QString& filename, song); } } + + // Check POPM tags + // We do this after checking FMPS frames, so FMPS have precedence, as we + // will consider POPM tags iff song has no rating/playcount already set. + qLog(Debug) << "POPM"; + if (!map["POPM"].isEmpty()) { + const TagLib::ID3v2::PopularimeterFrame* frame = + dynamic_cast(map["POPM"].front()); + if (frame) { + // Take a user rating only if there's no rating already set + if (song->rating() <= 0 && frame->rating() > 0) { + song->set_rating(ConvertPOPMRating(frame->rating())); + } + if (song->playcount() <= 0 && frame->counter() > 0) { + song->set_playcount(frame->counter()); + } + } + } + } } else if (TagLib::Ogg::Vorbis::File* file = dynamic_cast(fileref->file())) { if (file->tag()) { @@ -486,8 +506,26 @@ bool TagReader::SaveSongStatisticsToFile(const QString& filename, if (TagLib::MPEG::File* file = dynamic_cast(fileref->file())) { TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true); + + // Save as FMPS SetUserTextFrame("FMPS_Rating", QString::number(song.rating()), tag); SetUserTextFrame("FMPS_PlayCount", QString::number(song.playcount()), tag); + + // Also save as POPM + TagLib::ID3v2::PopularimeterFrame* frame = NULL; + + const TagLib::ID3v2::FrameListMap& map = file->ID3v2Tag()->frameListMap(); + if (!map["POPM"].isEmpty()) { + frame = dynamic_cast(map["POPM"].front()); + } + + if (!frame) { + frame = new TagLib::ID3v2::PopularimeterFrame(); + tag->addFrame(frame); + } + + frame->setRating(ConvertToPOPMRating(song.rating())); + frame->setCounter(song.playcount()); } else { // Nothing to save: stop now return true; @@ -734,3 +772,30 @@ bool TagReader::ReadCloudFile(const QUrl& download_url, } #endif // HAVE_GOOGLE_DRIVE +float TagReader::ConvertPOPMRating(const int POPM_rating) { + if (POPM_rating < 0x01) { + return 0.0; + } else if (POPM_rating < 0x40) { + return 0.20; // 1 star + } else if (POPM_rating < 0x80) { + return 0.40; // 2 stars + } else if (POPM_rating < 0xC0) { + return 0.60; // 3 stars + } else if (POPM_rating < 0xFF) { + return 0.80; // 4 stars + } + return 1.0; // 5 stars +} + +int TagReader::ConvertToPOPMRating(const float rating) { + if (rating < 0.20) { + return 0x00; + } else if (rating < 0.40) { + return 0x01; + } else if (rating < 0.60) { + return 0x80; + } else if (rating < 0.80) { + return 0xC0; + } + return 0xFF; +} diff --git a/ext/libclementine-tagreader/tagreader.h b/ext/libclementine-tagreader/tagreader.h index 314f11bb7..e21384dac 100644 --- a/ext/libclementine-tagreader/tagreader.h +++ b/ext/libclementine-tagreader/tagreader.h @@ -93,6 +93,11 @@ class TagReader { TagLib::ID3v2::Tag* tag) const; private: + // Returns a float in [0.0..1.0] corresponding to the rating range we use in Clementine + static float ConvertPOPMRating(const int POPM_rating); + // Reciprocal + static int ConvertToPOPMRating(const float rating); + FileRefFactory* factory_; QNetworkAccessManager* network_; diff --git a/tests/data/fmpspopmrating.mp3 b/tests/data/fmpspopmrating.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..c1f8bf629d861753899e08d1f2021e5f91a958e0 GIT binary patch literal 6614 zcmeI1cT`j78^>=*2mvBxN&<*XDazq@vlz+Ohc#$C~N|f;MIE{UA1aE(Tf6=X+DA~HWdj)&a5~KF-0YF0{-rddJWxY0q zwm%^z3~9FRM-Omiit-}d@eJjv6?R|E$I{h78(;{^cf+*0WU~E zZ8t)}iz`fXLZc~!_#!1N?$!JzEY3q=gCyuKf9GteJ=|S|QHZQ`k*KWLgeg?`B->r6UhxecTI?LlGzv6QX)Ty;ID@%)$lMIm;VUr@gGp0ouXjgT0 z^+J(BPEJlfA}X=0nw(t*&;pTGMA@dzg85YdfHTN&hG~WdC7Z)!FhE)4zf4_iw{A9f zVzaRb7F!1v`Yn^9u%W|;56=OnKW$c0cD50mL+cGUX$JG)m`rbUg%gJ5a(NJEf~{4)pT zW7HlMXwu!An(SnT1>i7-loza7DnYy+(+cRQrX!XOL&HkAtseH@v?>>xLrcedq4_x)1VvduF%6eOFk9#(k19f0G?(G$l= z(uI%9=cUlTeCxDYzEy`eN77!o$h8>l>_v>$c;Mk>uFyq^kp3h<{m`<<1kU@clh$2~ znKD+)scf-GwcW7vOHQR~+ETCTzVx%0yej7kogY-FXH-Ul-2R6Yur;>%wcC#E10~tV~H+f#9udYm!s44@pl# z8EmH@jN}o0Kc$Bm48cQ4QI!ZAmp#5gpt0y3-sz1RZFst(06Cd*x_XiJGS7DJsog4j zJ@Q;>3y8=sc8*=A|1|N1YU=LDHpxszuFMnLDMiN9HA1K0c5&KjuBmCX&6)HYJfY*N zJ)(T>+qHgI3!pa&vR)p}{rRrac_;q*$ljNhc(qczdp|+fg5($ja_sh!u%;~H0+#}| zq$!hf$+v7q_B6`Kd6AA4BOG1)0(Yj;g4g``R#w?C&ElG|x-Yx9ZP;9U^Z4%B`(0iB z=3Ti5Hhwu+z-*ANs%dPfzAIOfS(QBLt<}^=V^dpfnl^!56xcdz6Yqo^fUR_$!es(n1u2?cTROah8lU0Fm;^}w^(nDkd=I-%afx^H91K%BltXw{b8v#CKC z>%i(N%l9Q5yd@<_q_1@qBOql-x=!vX}lFuHN z4|+5O&ar;r8fv0uPArAIOxBF!?m+(_)UO}oPTW^MJd(#AKw!EHGg@;aMmH~xGZHUt zYRrAYFN7-VgwV#}NdaC3UM|-Yxx+_=xC|GW|%*vQkaXVEOompxx zxAE(pYM0ju*}l+QBSec9*Mf-%&EYJb&hlP7;$BM(4)hwi0fFcPeWZ@>p@ji6ItxI5Dp9P__^D3d%9s?dsi^Xj#yu%=*u=2g< zjc{m<^Cu14vK|IB&b%W8LDQfTqXYz*iC_tn4DxNi!c7k~SOf^u%vDQz33553xaxF8 zFa`b${22~{=yYzgCzl@+$!ldkUxIk?( zEB=BcSty%nwYnvpiHbGtFH7)gK$#}^^i>dLvTX5jL0Db4s-F+-fYl|n_Eorls-{_$ z6S~D;bYwL_rF^xDQOfNr9KL~Y=J+-7@9e&qnj^(MW_aG0I$%8elyT9I+sZA_+U0*! z!T1Q27`mq*n#!z?Ri_T}x#JifcU+5l|3|ZPgZ!BS9xu6;^!YK;uZLzo;mrtZ&8J-s z&CZ-GNMihPKKRGyj_Ak?M1QvU|i34tkVm1+> z(^4cMy9=~~#goOI`>~j24NQolu{A(8xJm<1w0g&ohlV)=!nSrEnxi}@Z@H0ggOpUdH;6B`^{W^L+f~Rzv4ngB7U(B)}kxDPp965*M8TF+dtT z4rrj;rZ0$0`)S9hAgp&(cX4kJrZ}pW8q8a3BvO*n_SvL$bNxEzOoZ??zi)oF{uxqd zD;FDrp{a=>{?BUuQ{`_7F$_G$%mZc^vH(C#X8?$Qs`K}-pZ^k}bT$C15yh{Dh!6w# VNa%lRW_erLz59nELi8UO^ly>v-k$&f literal 0 HcmV?d00001 diff --git a/tests/data/popmrating.mp3 b/tests/data/popmrating.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..9408934e0c2717adb381cb37502a80b841bd8328 GIT binary patch literal 6614 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 literal 0 HcmV?d00001 diff --git a/tests/data/testdata.qrc b/tests/data/testdata.qrc index 97d7dceb0..42716808b 100644 --- a/tests/data/testdata.qrc +++ b/tests/data/testdata.qrc @@ -11,6 +11,7 @@ fmpsplaycount.mp3 fmpsplaycountboth.mp3 fmpsplaycountuser.mp3 + fmpspopmrating.mp3 fmpsrating.mp3 fmpsratingboth.mp3 fmpsratinguser.mp3 @@ -18,6 +19,7 @@ manyfiles.cue manyfilesbroken.cue onesong.cue + popmrating.mp3 pls_one.pls pls_somafm.pls secretagent.asx diff --git a/tests/song_test.cpp b/tests/song_test.cpp index bb92a3312..cb4d5d10a 100644 --- a/tests/song_test.cpp +++ b/tests/song_test.cpp @@ -146,4 +146,18 @@ TEST_F(SongTest, FMPSPlayCountBoth) { EXPECT_EQ(123, song.playcount()); } +TEST_F(SongTest, POPMRating) { + TemporaryResource r(":/testdata/popmrating.mp3"); + Song song = ReadSongFromFile(r.fileName()); + EXPECT_FLOAT_EQ(0.60, song.rating()); +} + +TEST_F(SongTest, BothFMPSPOPMRating) { + // fmpspopmrating.mp3 contains FMPS with rating 0.42 and POPM with 0x80 + // (corresponds to 0.60 rating for us): check that FMPS tag has precedence + TemporaryResource r(":/testdata/fmpspopmrating.mp3"); + Song song = ReadSongFromFile(r.fileName()); + EXPECT_FLOAT_EQ(0.42, song.rating()); +} + } // namespace