1 // $Id: helpers.cpp,v 1.12 2002/09/21 17:23:32 t1mpy Exp $
2 
3 // id3lib: a C++ library for creating and manipulating id3v1/v2 tags
4 // Copyright 1999, 2000  Scott Thomas Haug
5 
6 // Lots of hacking added to this file by Scott Wheeler (scott@slackorama.net)
7 // 11/02/2001
8 
9 // This library is free software; you can redistribute it and/or modify it
10 // under the terms of the GNU Library General Public License as published by
11 // the Free Software Foundation; either version 2 of the License, or (at your
12 // option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful, but WITHOUT
15 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
17 // License for more details.
18 //
19 // You should have received a copy of the GNU Library General Public License
20 // along with this library; if not, write to the Free Software Foundation,
21 // Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 
23 // The id3lib authors encourage improvements and optimisations to be sent to
24 // the id3lib coordinator.  Please see the README file for details on where to
25 // send such submissions.  See the AUTHORS file for a list of people who have
26 // contributed to id3lib.  See the ChangeLog file for a list of changes to
27 // id3lib.  These files are distributed with id3lib at
28 // http://download.sourceforge.net/id3lib/
29 
30 #if defined HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 
34 
35 
36 #include <ctype.h>
37 
38 #include "helpers.h"
39 #include "tag_impl.h" //has <stdio.h> "tag.h" "header_tag.h" "frame.h" "field.h" "spec.h" "id3lib_strings.h" "utils.h"
40 
41 using namespace dami;
42 
getString(const ID3_Frame * frame,ID3_FieldID fldName)43 String id3::v2::getString(const ID3_Frame* frame, ID3_FieldID fldName)
44 {
45   if (!frame)
46   {
47     return "";
48   }
49   ID3_Field* fp = frame->GetField(fldName);
50   if (!fp)
51   {
52     return "";
53   }
54   ID3_TextEnc enc = fp->GetEncoding();
55   fp->SetEncoding(ID3TE_ASCII);
56 
57   String text(fp->GetRawText(), fp->Size());
58 
59   fp->SetEncoding(enc);
60   return text;
61 }
62 
getStringAtIndex(const ID3_Frame * frame,ID3_FieldID fldName,size_t nIndex)63 String id3::v2::getStringAtIndex(const ID3_Frame* frame, ID3_FieldID fldName,
64                                  size_t nIndex)
65 {
66   if (!frame)
67   {
68     return "";
69   }
70   String text;
71   ID3_Field* fp = frame->GetField(fldName);
72   if (fp && fp->GetNumTextItems() < nIndex)
73   {
74     ID3_TextEnc enc = fp->GetEncoding();
75     fp->SetEncoding(ID3TE_ASCII);
76 
77     text = fp->GetRawTextItem(nIndex);
78 
79     fp->SetEncoding(enc);
80   }
81   return text;
82 }
83 
removeFrames(ID3_TagImpl & tag,ID3_FrameID id)84 size_t id3::v2::removeFrames(ID3_TagImpl& tag, ID3_FrameID id)
85 {
86   size_t numRemoved = 0;
87   ID3_Frame* frame = NULL;
88 
89   while ((frame = tag.Find(id)) != NULL)
90   {
91     frame = tag.RemoveFrame(frame);
92     delete frame;
93     numRemoved++;
94   }
95 
96   return numRemoved;
97 }
98 
getFrameText(const ID3_TagImpl & tag,ID3_FrameID id)99 String id3::v2::getFrameText(const ID3_TagImpl& tag, ID3_FrameID id)
100 {
101   ID3_Frame* frame = tag.Find(id);
102   return getString(frame, ID3FN_TEXT);
103 }
104 
setFrameText(ID3_TagImpl & tag,ID3_FrameID id,String text)105 ID3_Frame* id3::v2::setFrameText(ID3_TagImpl& tag, ID3_FrameID id, String text)
106 {
107   ID3_Frame* frame = tag.Find(id);
108   if (!frame)
109   {
110     frame = new ID3_Frame(id);
111     if(!tag.AttachFrame(frame)) return NULL;
112   }
113   frame->GetField(ID3FN_TEXT)->Set(text.c_str());
114 
115   return frame;
116 }
117 
118 ////////////////////////////////////////////////////////////
119 
hasArtist(const ID3_TagImpl & tag)120 ID3_Frame* id3::v2::hasArtist(const ID3_TagImpl& tag)
121 {
122   ID3_Frame* fp = NULL;
123   (fp = tag.Find(ID3FID_LEADARTIST)) ||
124   (fp = tag.Find(ID3FID_BAND))       ||
125   (fp = tag.Find(ID3FID_CONDUCTOR))  ||
126   (fp = tag.Find(ID3FID_COMPOSER));
127   return fp;
128 }
129 
getArtist(const ID3_TagImpl & tag)130 String id3::v2::getArtist(const ID3_TagImpl& tag)
131 {
132   ID3_Frame* frame = hasArtist(tag);
133   return getString(frame, ID3FN_TEXT);
134 }
135 
setArtist(ID3_TagImpl & tag,String text)136 ID3_Frame* id3::v2::setArtist(ID3_TagImpl& tag, String text)
137 {
138   removeArtists(tag);
139   return setFrameText(tag, ID3FID_LEADARTIST, text);
140 }
141 
removeArtists(ID3_TagImpl & tag)142 size_t id3::v2::removeArtists(ID3_TagImpl& tag)
143 {
144   size_t numRemoved = 0;
145   ID3_Frame* frame = NULL;
146 
147   while ((frame = hasArtist(tag)) != NULL)
148   {
149     frame = tag.RemoveFrame(frame);
150     delete frame;
151     numRemoved++;
152   }
153 
154   return numRemoved;
155 }
156 
157 ////////////////////////////////////////////////////////////
158 
hasAlbum(const ID3_TagImpl & tag)159 ID3_Frame* id3::v2::hasAlbum(const ID3_TagImpl& tag)
160 {
161   ID3_Frame* frame = tag.Find(ID3FID_ALBUM);
162   return(frame);
163 }
164 
getAlbum(const ID3_TagImpl & tag)165 String id3::v2::getAlbum(const ID3_TagImpl& tag)
166 {
167   return getFrameText(tag, ID3FID_ALBUM);
168 }
169 
setAlbum(ID3_TagImpl & tag,String text)170 ID3_Frame* id3::v2::setAlbum(ID3_TagImpl& tag, String text)
171 {
172   return setFrameText(tag, ID3FID_ALBUM, text);
173 }
174 
removeAlbums(ID3_TagImpl & tag)175 size_t id3::v2::removeAlbums(ID3_TagImpl& tag)
176 {
177   return removeFrames(tag, ID3FID_ALBUM);
178 }
179 
180 ////////////////////////////////////////////////////////////
181 
hasTitle(const ID3_TagImpl & tag)182 ID3_Frame* id3::v2::hasTitle(const ID3_TagImpl& tag)
183 {
184   ID3_Frame* frame = tag.Find(ID3FID_TITLE);
185   return(frame);
186 }
187 
getTitle(const ID3_TagImpl & tag)188 String id3::v2::getTitle(const ID3_TagImpl& tag)
189 {
190   return getFrameText(tag, ID3FID_TITLE);
191 }
192 
setTitle(ID3_TagImpl & tag,String text)193 ID3_Frame* id3::v2::setTitle(ID3_TagImpl& tag, String text)
194 {
195   return setFrameText(tag, ID3FID_TITLE, text);
196 }
197 
removeTitles(ID3_TagImpl & tag)198 size_t id3::v2::removeTitles(ID3_TagImpl& tag)
199 {
200   return removeFrames(tag, ID3FID_TITLE);
201 }
202 
203 ////////////////////////////////////////////////////////////
204 
hasYear(const ID3_TagImpl & tag)205 ID3_Frame* id3::v2::hasYear(const ID3_TagImpl& tag)
206 {
207   ID3_Frame* frame = tag.Find(ID3FID_YEAR);
208   return(frame);
209 }
210 
getYear(const ID3_TagImpl & tag)211 String id3::v2::getYear(const ID3_TagImpl& tag)
212 {
213   return getFrameText(tag, ID3FID_YEAR);
214 }
215 
setYear(ID3_TagImpl & tag,String text)216 ID3_Frame* id3::v2::setYear(ID3_TagImpl& tag, String text)
217 {
218   return setFrameText(tag, ID3FID_YEAR, text);
219 }
220 
removeYears(ID3_TagImpl & tag)221 size_t id3::v2::removeYears(ID3_TagImpl& tag)
222 {
223   return removeFrames(tag, ID3FID_YEAR);
224 }
225 
226 ////////////////////////////////////////////////////////////
227 
hasV1Comment(const ID3_TagImpl & tag)228 ID3_Frame* id3::v2::hasV1Comment(const ID3_TagImpl& tag)
229 {
230   ID3_Frame* frame = NULL;
231   (frame = tag.Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, STR_V1_COMMENT_DESC)) ||
232   (frame = tag.Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, ""                 )) ||
233   (frame = tag.Find(ID3FID_COMMENT));
234   return(frame);
235 }
236 
hasComment(const ID3_TagImpl & tag)237 ID3_Frame* id3::v2::hasComment(const ID3_TagImpl& tag)
238 {
239   ID3_Frame* frame = tag.Find(ID3FID_COMMENT);
240   return(frame);
241 }
242 
getV1Comment(const ID3_TagImpl & tag)243 String id3::v2::getV1Comment(const ID3_TagImpl& tag)
244 {
245   ID3_Frame* frame;
246   (frame = tag.Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, STR_V1_COMMENT_DESC)) ||
247   (frame = tag.Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, ""                 )) ||
248   (frame = tag.Find(ID3FID_COMMENT));
249   return getString(frame, ID3FN_TEXT);
250 }
251 
getComment(const ID3_TagImpl & tag,String desc)252 String id3::v2::getComment(const ID3_TagImpl& tag, String desc)
253 {
254   ID3_Frame* frame = tag.Find(ID3FID_COMMENT, ID3FN_DESCRIPTION, desc.c_str());
255   return getString(frame, ID3FN_TEXT);
256 }
257 
setComment(ID3_TagImpl & tag,String text,String desc,String lang)258 ID3_Frame* id3::v2::setComment(ID3_TagImpl& tag, String text, String desc,
259                                String lang)
260 {
261   ID3D_NOTICE( "id3::v2::setComment: trying to find frame with description = " << desc );
262   ID3_Frame* frame = NULL;
263   // See if there is already a comment with this description
264   for (ID3_TagImpl::iterator iter = tag.begin(); iter != tag.end(); ++iter)
265   {
266     frame = *iter;
267     if (frame == NULL)
268     {
269       continue;
270     }
271     if (frame->GetID() == ID3FID_COMMENT)
272     {
273       String tmpDesc = getString(frame, ID3FN_DESCRIPTION);
274       if (tmpDesc == desc)
275       {
276         ID3D_NOTICE( "id3::v2::setComment: found frame with description = " << desc );
277         break;
278       }
279     }
280     frame = NULL;
281   }
282   if (frame == NULL)
283   {
284     ID3D_NOTICE( "id3::v2::setComment: creating new comment frame" );
285     frame = new ID3_Frame(ID3FID_COMMENT);
286     if(!tag.AttachFrame(frame)) return NULL;
287   }
288   if (!frame)
289   {
290     ID3D_WARNING( "id3::v2::setComment: ack! no frame" );
291   }
292   else
293   {
294     frame->GetField(ID3FN_LANGUAGE)->Set(lang.c_str());
295     frame->GetField(ID3FN_DESCRIPTION)->Set(desc.c_str());
296     frame->GetField(ID3FN_TEXT)->Set(text.c_str());
297   }
298 
299   return frame;
300 }
301 
302 // Remove all comments from the tag
removeAllComments(ID3_TagImpl & tag)303 size_t id3::v2::removeAllComments(ID3_TagImpl& tag)
304 {
305   return removeFrames(tag, ID3FID_COMMENT);
306 }
307 
308 // Remove all comments from the tag with the given description
removeComments(ID3_TagImpl & tag,String desc)309 size_t id3::v2::removeComments(ID3_TagImpl& tag, String desc)
310 {
311   size_t numRemoved = 0;
312 
313   for (ID3_TagImpl::iterator iter = tag.begin(); iter != tag.end(); ++iter)
314   {
315     ID3_Frame* frame = *iter;
316     if (frame == NULL)
317     {
318       continue;
319     }
320     if (frame->GetID() == ID3FID_COMMENT)
321     {
322       // See if the description we have matches the description of the
323       // current comment.  If so, remove the comment
324       String tmpDesc = getString(frame, ID3FN_DESCRIPTION);
325       if (tmpDesc == desc)
326       {
327         frame = tag.RemoveFrame(frame);
328         delete frame;
329         numRemoved++;
330       }
331     }
332   }
333 
334   return numRemoved;
335 }
336 
337 ////////////////////////////////////////////////////////////
338 
hasTrack(const ID3_TagImpl & tag)339 ID3_Frame* id3::v2::hasTrack(const ID3_TagImpl& tag)
340 {
341   ID3_Frame* frame = tag.Find(ID3FID_TRACKNUM);
342   return(frame);
343 }
344 
getTrack(const ID3_TagImpl & tag)345 String id3::v2::getTrack(const ID3_TagImpl& tag)
346 {
347   return getFrameText(tag, ID3FID_TRACKNUM);
348 }
349 
getTrackNum(const ID3_TagImpl & tag)350 size_t id3::v2::getTrackNum(const ID3_TagImpl& tag)
351 {
352   String sTrack = getTrack(tag);
353   return ::atoi(sTrack.c_str());
354 }
355 
setTrack(ID3_TagImpl & tag,uchar trk,uchar ttl)356 ID3_Frame* id3::v2::setTrack(ID3_TagImpl& tag, uchar trk, uchar ttl)
357 {
358   ID3_Frame* frame = NULL;
359   String track = toString((size_t)trk);
360   if (ttl > 0)
361   {
362     track += "/";
363     track += toString((size_t)ttl);
364   }
365   setFrameText(tag, ID3FID_TRACKNUM, track);
366 
367   return frame;
368 }
369 
removeTracks(ID3_TagImpl & tag)370 size_t id3::v2::removeTracks(ID3_TagImpl& tag)
371 {
372   return removeFrames(tag, ID3FID_TRACKNUM);
373 }
374 
375 ////////////////////////////////////////////////////////////
376 
hasGenre(const ID3_TagImpl & tag)377 ID3_Frame* id3::v2::hasGenre(const ID3_TagImpl& tag)
378 {
379   ID3_Frame* frame = tag.Find(ID3FID_CONTENTTYPE);
380   return(frame);
381 }
382 
getGenre(const ID3_TagImpl & tag)383 String id3::v2::getGenre(const ID3_TagImpl& tag)
384 {
385   return getFrameText(tag, ID3FID_CONTENTTYPE);
386 }
387 
getGenreNum(const ID3_TagImpl & tag)388 size_t id3::v2::getGenreNum(const ID3_TagImpl& tag)
389 {
390   String sGenre = getGenre(tag);
391   size_t ulGenre = 0xFF;
392   size_t size = sGenre.size();
393 
394   // If the genre string begins with "(ddd)", where "ddd" is a number, then
395   // "ddd" is the genre number---get it
396   size_t i = 0;
397   if (i < size && size && sGenre[i] == '(')
398   {
399     ++i;
400     while (i < size && isdigit(sGenre[i]))
401     {
402       ++i;
403     }
404     if (i < size && sGenre[i] == ')')
405     {
406       // if the genre number is greater than 255, its invalid.
407       ulGenre = min(0xFF, atoi(&sGenre[1]));
408     }
409   }
410 
411   return ulGenre;
412 }
413 
setGenre(ID3_TagImpl & tag,size_t genre)414 ID3_Frame* id3::v2::setGenre(ID3_TagImpl& tag, size_t genre)
415 {
416   String sGenre = "(";
417   sGenre += toString(genre) + ")";
418   return setFrameText(tag, ID3FID_CONTENTTYPE, sGenre);
419 }
420 
removeGenres(ID3_TagImpl & tag)421 size_t id3::v2::removeGenres(ID3_TagImpl& tag)
422 {
423   return removeFrames(tag, ID3FID_CONTENTTYPE);
424 }
425 
426 ////////////////////////////////////////////////////////////
427 
hasLyrics(const ID3_TagImpl & tag)428 ID3_Frame* id3::v2::hasLyrics(const ID3_TagImpl& tag)
429 {
430   ID3_Frame* frame = tag.Find(ID3FID_UNSYNCEDLYRICS);
431   return(frame);
432 }
433 
getLyrics(const ID3_TagImpl & tag)434 String id3::v2::getLyrics(const ID3_TagImpl& tag)
435 {
436   return getFrameText(tag, ID3FID_UNSYNCEDLYRICS);
437 }
438 
setLyrics(ID3_TagImpl & tag,String text,String desc,String lang)439 ID3_Frame* id3::v2::setLyrics(ID3_TagImpl& tag, String text, String desc,
440                               String lang)
441 {
442   ID3_Frame* frame = NULL;
443   // See if there is already a comment with this description
444   for (ID3_TagImpl::iterator iter = tag.begin(); iter != tag.end(); ++iter)
445   {
446     frame = *iter;
447     if (frame == NULL)
448     {
449       continue;
450     }
451     if (frame->GetID() == ID3FID_COMMENT)
452     {
453       String tmpDesc = getString(frame, ID3FN_DESCRIPTION);
454       if (tmpDesc == desc)
455       {
456         break;
457       }
458     }
459     frame = NULL;
460   }
461   if (frame == NULL)
462   {
463     frame = new ID3_Frame(ID3FID_UNSYNCEDLYRICS);
464     if(!tag.AttachFrame(frame)) return NULL;
465   }
466   frame->GetField(ID3FN_LANGUAGE)->Set(lang.c_str());
467   frame->GetField(ID3FN_DESCRIPTION)->Set(desc.c_str());
468   frame->GetField(ID3FN_TEXT)->Set(text.c_str());
469 
470   return frame;
471 }
472 
removeLyrics(ID3_TagImpl & tag)473 size_t id3::v2::removeLyrics(ID3_TagImpl& tag)
474 {
475   return removeFrames(tag, ID3FID_UNSYNCEDLYRICS);
476 }
477 
getLyricist(const ID3_TagImpl & tag)478 String id3::v2::getLyricist(const ID3_TagImpl& tag)
479 {
480   return getFrameText(tag, ID3FID_LYRICIST);
481 }
482 
setLyricist(ID3_TagImpl & tag,String text)483 ID3_Frame* id3::v2::setLyricist(ID3_TagImpl& tag, String text)
484 {
485   return setFrameText(tag, ID3FID_LYRICIST, text);
486 }
487 
removeLyricists(ID3_TagImpl & tag)488 size_t id3::v2::removeLyricists(ID3_TagImpl& tag)
489 {
490   return removeFrames(tag, ID3FID_LYRICIST);
491 }
492 
493 ////////////////////////////////////////////////////////////
494 
hasSyncLyrics(const ID3_TagImpl & tag,String lang,String desc)495 ID3_Frame* id3::v2::hasSyncLyrics(const ID3_TagImpl& tag, String lang, String desc)
496 {
497   ID3_Frame* frame=NULL;
498   (frame = tag.Find(ID3FID_SYNCEDLYRICS, ID3FN_LANGUAGE, lang)) ||
499   (frame = tag.Find(ID3FID_SYNCEDLYRICS, ID3FN_DESCRIPTION, desc));
500   return(frame);
501 }
502 
setSyncLyrics(ID3_TagImpl & tag,BString data,ID3_TimeStampFormat format,String desc,String lang,ID3_ContentType type)503 ID3_Frame* id3::v2::setSyncLyrics(ID3_TagImpl& tag, BString data,
504                                   ID3_TimeStampFormat format, String desc,
505                                   String lang, ID3_ContentType type)
506 {
507   ID3_Frame* frame = NULL;
508 
509   // check if a SYLT frame of this language or descriptor already exists
510   (frame = tag.Find(ID3FID_SYNCEDLYRICS, ID3FN_LANGUAGE, lang)) ||
511   (frame = tag.Find(ID3FID_SYNCEDLYRICS, ID3FN_DESCRIPTION, desc));
512 
513   if (!frame)
514   {
515     frame = new ID3_Frame(ID3FID_SYNCEDLYRICS);
516     if(!tag.AttachFrame(frame)) return NULL;
517   }
518   frame->GetField(ID3FN_LANGUAGE)->Set(lang.c_str());
519   frame->GetField(ID3FN_DESCRIPTION)->Set(desc.c_str());
520   frame->GetField(ID3FN_TIMESTAMPFORMAT)->Set(format);
521   frame->GetField(ID3FN_CONTENTTYPE)->Set(type);
522   frame->GetField(ID3FN_DATA)->Set(data.data(), data.size());
523 
524   return frame;
525 }
526 
getSyncLyrics(const ID3_TagImpl & tag,String lang,String desc)527 BString id3::v2::getSyncLyrics(const ID3_TagImpl& tag, String lang, String desc)
528 {
529   // check if a SYLT frame of this language or descriptor exists
530   ID3_Frame* frame = NULL;
531   (frame = tag.Find(ID3FID_SYNCEDLYRICS, ID3FN_LANGUAGE, lang)) ||
532   (frame = tag.Find(ID3FID_SYNCEDLYRICS, ID3FN_DESCRIPTION, desc)) ||
533   (frame = tag.Find(ID3FID_SYNCEDLYRICS));
534 
535   // get the lyrics size
536   ID3_Field* fld = frame->GetField(ID3FN_DATA);
537   return BString(reinterpret_cast<const BString::value_type *>(fld->GetRawBinary()), fld->Size());
538 }
539 
540