1 /* This file is part of Clementine.
2    Copyright 2010, David Sansome <me@davidsansome.com>
3 
4    Clementine is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8 
9    Clementine is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with Clementine.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 
18 
19 #include "gmock/gmock-matchers.h"
20 #include "gtest/gtest.h"
21 #include "test_utils.h"
22 
23 #include "core/timeconstants.h"
24 #include "playlistparsers/cueparser.h"
25 
26 #include <QUrl>
27 
28 class CueParserTest : public ::testing::Test {
29  protected:
CueParserTest()30   CueParserTest()
31       : parser_(nullptr) {
32   }
33 
34   // We believe CUE - all songs with proper CUE entries should be valid.
validate_songs(SongList songs)35   bool validate_songs(SongList songs) {
36     foreach(const Song& song, songs) {
37       if(!song.is_valid()) {
38         return false;
39       }
40     }
41 
42     return true;
43   }
44 
to_nanosec(int minutes,int seconds,int frames)45   qlonglong to_nanosec(int minutes, int seconds, int frames) {
46     return (minutes * 60 * 75 + seconds * 75 + frames) * kNsecPerSec / 75;
47   }
48 
49   CueParser parser_;
50 };
51 
TEST_F(CueParserTest,ParsesASong)52 TEST_F(CueParserTest, ParsesASong) {
53   QFile file(":testdata/onesong.cue");
54   file.open(QIODevice::ReadOnly);
55 
56   SongList song_list = parser_.Load(&file, "CUEPATH", QDir(""));
57 
58   // one song
59   ASSERT_EQ(1, song_list.size());
60 
61   // with the specified metadata
62   Song first_song = song_list.at(0);
63   ASSERT_EQ("Un soffio caldo", first_song.title());
64   ASSERT_EQ("Zucchero", first_song.artist());
65   ASSERT_EQ("Zucchero himself", first_song.albumartist());
66   ASSERT_EQ("", first_song.album());
67   ASSERT_EQ(to_nanosec(0, 1, 15), first_song.beginning_nanosec());
68   ASSERT_EQ(1, first_song.track());
69   ASSERT_EQ("CUEPATH", first_song.cue_path());
70 
71   validate_songs(song_list);
72 }
73 
TEST_F(CueParserTest,ParsesTwoSongs)74 TEST_F(CueParserTest, ParsesTwoSongs) {
75   QFile file(":testdata/twosongs.cue");
76   file.open(QIODevice::ReadOnly);
77 
78   SongList song_list = parser_.Load(&file, "", QDir(""));
79 
80   // two songs
81   ASSERT_EQ(2, song_list.size());
82 
83   // with the specified metadata
84   Song first_song = song_list.at(0);
85   Song second_song = song_list.at(1);
86 
87   ASSERT_EQ("Un soffio caldo", first_song.title());
88   ASSERT_EQ("Chocabeck", first_song.album());
89   ASSERT_EQ("Zucchero himself", first_song.artist());
90   ASSERT_EQ("Zucchero himself", first_song.albumartist());
91   ASSERT_EQ(to_nanosec(0, 1, 0), first_song.beginning_nanosec());
92   ASSERT_EQ(second_song.beginning_nanosec() - first_song.beginning_nanosec(), first_song.length_nanosec());
93   ASSERT_EQ(1, first_song.track());
94 
95   ASSERT_EQ("Somewon Else's Tears", second_song.title());
96   ASSERT_EQ("Chocabeck", second_song.album());
97   ASSERT_EQ("Zucchero himself", second_song.artist());
98   ASSERT_EQ("Zucchero himself", second_song.albumartist());
99   ASSERT_EQ(to_nanosec(5, 3, 68), second_song.beginning_nanosec());
100   ASSERT_EQ(2, second_song.track());
101 
102   validate_songs(song_list);
103 }
104 
TEST_F(CueParserTest,SkipsBrokenSongs)105 TEST_F(CueParserTest, SkipsBrokenSongs) {
106   QFile file(":testdata/brokensong.cue");
107   file.open(QIODevice::ReadOnly);
108 
109   SongList song_list = parser_.Load(&file, "", QDir(""));
110 
111   // two songs (the broken one is not in the list)
112   ASSERT_EQ(2, song_list.size());
113 
114   // with the specified metadata
115   Song first_song = song_list.at(0);
116   Song second_song = song_list.at(1);
117 
118   ASSERT_EQ("Un soffio caldo", first_song.title());
119   ASSERT_EQ("Chocabeck", first_song.album());
120   ASSERT_EQ("Zucchero himself", first_song.artist());
121   ASSERT_EQ("Zucchero himself", first_song.albumartist());
122   ASSERT_EQ(to_nanosec(0, 1, 0), first_song.beginning_nanosec());
123   // includes the broken song too; this entry will span from it's
124   // INDEX (beginning) to the end of the next correct song
125   ASSERT_EQ(second_song.beginning_nanosec() - first_song.beginning_nanosec(), first_song.length_nanosec());
126   ASSERT_EQ(1, first_song.track());
127 
128   ASSERT_EQ("Somewon Else's Tears", second_song.title());
129   ASSERT_EQ("Chocabeck", second_song.album());
130   ASSERT_EQ("Zucchero himself", second_song.artist());
131   ASSERT_EQ("Zucchero himself", second_song.albumartist());
132   ASSERT_EQ(to_nanosec(5, 0, 0), second_song.beginning_nanosec());
133   ASSERT_EQ(2, second_song.track());
134 
135   validate_songs(song_list);
136 }
137 
TEST_F(CueParserTest,UsesAllMetadataInformation)138 TEST_F(CueParserTest, UsesAllMetadataInformation) {
139   QFile file(":testdata/fullmetadata.cue");
140   file.open(QIODevice::ReadOnly);
141 
142   SongList song_list = parser_.Load(&file, "", QDir(""));
143 
144   // two songs
145   ASSERT_EQ(2, song_list.size());
146 
147   // with the specified metadata
148   Song first_song = song_list.at(0);
149   Song second_song = song_list.at(1);
150 
151   ASSERT_TRUE(first_song.url().toString().endsWith("a_file.mp3"));
152   ASSERT_EQ("Un soffio caldo", first_song.title());
153   ASSERT_EQ("Album", first_song.album());
154   ASSERT_EQ("Zucchero", first_song.artist());
155   ASSERT_EQ("Zucchero himself", first_song.albumartist());
156   ASSERT_EQ("Some guy", first_song.composer());
157   ASSERT_EQ(to_nanosec(0, 1, 0), first_song.beginning_nanosec());
158   ASSERT_EQ(second_song.beginning_nanosec() - first_song.beginning_nanosec(), first_song.length_nanosec());
159   ASSERT_EQ(1, first_song.track());
160 
161   ASSERT_TRUE(second_song.url().toString().endsWith("a_file.mp3"));
162   ASSERT_EQ("Hey you!", second_song.title());
163   ASSERT_EQ("Album", second_song.album());
164   ASSERT_EQ("Zucchero himself", second_song.artist());
165   ASSERT_EQ("Zucchero himself", second_song.albumartist());
166   ASSERT_EQ("Some other guy", second_song.composer());
167   ASSERT_EQ(to_nanosec(0, 2, 0), second_song.beginning_nanosec());
168   ASSERT_EQ(2, second_song.track());
169 
170   validate_songs(song_list);
171 }
172 
TEST_F(CueParserTest,AcceptsMultipleFileBasedCues)173 TEST_F(CueParserTest, AcceptsMultipleFileBasedCues) {
174   QFile file(":testdata/manyfiles.cue");
175   file.open(QIODevice::ReadOnly);
176 
177   SongList song_list = parser_.Load(&file, "CUEPATH", QDir(""));
178 
179   // five songs
180   ASSERT_EQ(5, song_list.size());
181 
182   // with the specified metadata
183   Song first_song = song_list.at(0);
184   Song second_song = song_list.at(1);
185   Song third_song = song_list.at(2);
186   Song fourth_song = song_list.at(3);
187   Song fifth_song = song_list.at(4);
188 
189   ASSERT_TRUE(first_song.url().toString().endsWith("files/longer_one.mp3"));
190   ASSERT_EQ("A1Song1", first_song.title());
191   ASSERT_EQ("Artist One Album", first_song.album());
192   ASSERT_EQ("Artist One", first_song.artist());
193   ASSERT_EQ("Artist One", first_song.albumartist());
194   ASSERT_EQ(to_nanosec(0, 1, 0), first_song.beginning_nanosec());
195   ASSERT_EQ(second_song.beginning_nanosec() - first_song.beginning_nanosec(), first_song.length_nanosec());
196   ASSERT_EQ(-1, first_song.track());
197   ASSERT_EQ("CUEPATH", first_song.cue_path());
198 
199   ASSERT_TRUE(second_song.url().toString().endsWith("files/longer_one.mp3"));
200   ASSERT_EQ("A1Song2", second_song.title());
201   ASSERT_EQ("Artist One Album", second_song.album());
202   ASSERT_EQ("Artist One", second_song.artist());
203   ASSERT_EQ("Artist One", second_song.albumartist());
204   ASSERT_EQ(to_nanosec(5, 3, 68), second_song.beginning_nanosec());
205   ASSERT_EQ(-1, second_song.track());
206 
207   ASSERT_TRUE(third_song.url().toString().endsWith("files/longer_two_p1.mp3"));
208   ASSERT_EQ("A2P1Song1", third_song.title());
209   ASSERT_EQ("Artist Two Album", third_song.album());
210   ASSERT_EQ("Artist X", third_song.artist());
211   ASSERT_EQ("Artist Two", third_song.albumartist());
212   ASSERT_EQ(to_nanosec(0, 0, 12), third_song.beginning_nanosec());
213   ASSERT_EQ(fourth_song.beginning_nanosec() - third_song.beginning_nanosec(), third_song.length_nanosec());
214   ASSERT_EQ(-1, third_song.track());
215   ASSERT_EQ("CUEPATH", third_song.cue_path());
216 
217   ASSERT_TRUE(fourth_song.url().toString().endsWith("files/longer_two_p1.mp3"));
218   ASSERT_EQ("A2P1Song2", fourth_song.title());
219   ASSERT_EQ("Artist Two Album", fourth_song.album());
220   ASSERT_EQ("Artist Two", fourth_song.artist());
221   ASSERT_EQ("Artist Two", fourth_song.albumartist());
222   ASSERT_EQ(to_nanosec(4, 0, 13), fourth_song.beginning_nanosec());
223   ASSERT_EQ(-1, fourth_song.track());
224 
225   ASSERT_TRUE(fifth_song.url().toString().endsWith("files/longer_two_p2.mp3"));
226   ASSERT_EQ("A2P2Song1", fifth_song.title());
227   ASSERT_EQ("Artist Two Album", fifth_song.album());
228   ASSERT_EQ("Artist Two", fifth_song.artist());
229   ASSERT_EQ("Artist Two", fifth_song.albumartist());
230   ASSERT_EQ(to_nanosec(0, 1, 0), fifth_song.beginning_nanosec());
231   ASSERT_EQ(-1, fifth_song.track());
232   ASSERT_EQ("CUEPATH", fifth_song.cue_path());
233 
234   validate_songs(song_list);
235 }
236 
TEST_F(CueParserTest,SkipsBrokenSongsInMultipleFileBasedCues)237 TEST_F(CueParserTest, SkipsBrokenSongsInMultipleFileBasedCues) {
238   QFile file(":testdata/manyfilesbroken.cue");
239   file.open(QIODevice::ReadOnly);
240 
241   SongList song_list = parser_.Load(&file, "", QDir(""));
242 
243   // four songs
244   ASSERT_EQ(4, song_list.size());
245 
246   // with the specified metadata
247   Song first_song = song_list.at(0);
248   Song second_song = song_list.at(1);
249   Song third_song = song_list.at(2);
250   Song fourth_song = song_list.at(3);
251 
252   // A* - broken song in the middle
253   ASSERT_TRUE(first_song.url().toString().endsWith("file1.mp3"));
254   ASSERT_EQ("Artist One", first_song.artist());
255   ASSERT_EQ("Artist One Album", first_song.album());
256   ASSERT_EQ("A1", first_song.title());
257   ASSERT_EQ(to_nanosec(0, 1, 15), first_song.beginning_nanosec());
258   ASSERT_EQ(second_song.beginning_nanosec() - first_song.beginning_nanosec(), first_song.length_nanosec());
259   ASSERT_EQ(-1, first_song.track());
260 
261   ASSERT_TRUE(second_song.url().toString().endsWith("file1.mp3"));
262   ASSERT_EQ("Artist One", second_song.artist());
263   ASSERT_EQ("Artist One Album", second_song.album());
264   ASSERT_EQ("A3", second_song.title());
265   ASSERT_EQ(to_nanosec(1, 0, 16), second_song.beginning_nanosec());
266   ASSERT_EQ(-1, second_song.track());
267 
268   // all B* songs are broken
269 
270   // C* - broken song at the end
271   ASSERT_TRUE(third_song.url().toString().endsWith("file3.mp3"));
272   ASSERT_EQ("Artist Three", third_song.artist());
273   ASSERT_EQ("Artist Three Album", third_song.album());
274   ASSERT_EQ("C1", third_song.title());
275   ASSERT_EQ(to_nanosec(0, 1, 0), third_song.beginning_nanosec());
276   ASSERT_EQ(-1, third_song.track());
277 
278   // D* - broken song at the beginning
279   ASSERT_TRUE(fourth_song.url().toString().endsWith("file4.mp3"));
280   ASSERT_EQ("Artist Four", fourth_song.artist());
281   ASSERT_EQ("Artist Four Album", fourth_song.album());
282   ASSERT_EQ("D2", fourth_song.title());
283   ASSERT_EQ(to_nanosec(15, 55, 66), fourth_song.beginning_nanosec());
284   ASSERT_EQ(-1, fourth_song.track());
285 
286   validate_songs(song_list);
287 }
288 
TEST_F(CueParserTest,SkipsDataFiles)289 TEST_F(CueParserTest, SkipsDataFiles) {
290   QFile file(":testdata/withdatafiles.cue");
291   file.open(QIODevice::ReadOnly);
292 
293   SongList song_list = parser_.Load(&file, "", QDir(""));
294 
295   // two songs
296   ASSERT_EQ(2, song_list.size());
297 
298   // with the specified metadata
299   Song first_song = song_list.at(0);
300   Song second_song = song_list.at(1);
301 
302   ASSERT_TRUE(first_song.url().toString().endsWith("file1.mp3"));
303   ASSERT_EQ("Artist One", first_song.artist());
304   ASSERT_EQ("Artist One Album", first_song.album());
305   ASSERT_EQ("A1", first_song.title());
306   ASSERT_EQ(to_nanosec(0, 1, 0), first_song.beginning_nanosec());
307   ASSERT_EQ(-1, first_song.track());
308 
309   ASSERT_TRUE(second_song.url().toString().endsWith("file4.mp3"));
310   ASSERT_EQ("Artist Four", second_song.artist());
311   ASSERT_EQ("Artist Four Album", second_song.album());
312   ASSERT_EQ("D1", second_song.title());
313   ASSERT_EQ(to_nanosec(1, 1, 0), second_song.beginning_nanosec());
314   ASSERT_EQ(-1, second_song.track());
315 
316   validate_songs(song_list);
317 }
318