1 /***************************************************************************
2     copyright            : (C) 2012 by Tsuda Kageyu
3     email                : tsuda.kageyu@gmail.com
4  ***************************************************************************/
5 
6 /***************************************************************************
7  *   This library is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU Lesser General Public License version   *
9  *   2.1 as published by the Free Software Foundation.                     *
10  *                                                                         *
11  *   This library is distributed in the hope that it will be useful, but   *
12  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
14  *   Lesser General Public License for more details.                       *
15  *                                                                         *
16  *   You should have received a copy of the GNU Lesser General Public      *
17  *   License along with this library; if not, write to the Free Software   *
18  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA         *
19  *   02110-1301  USA                                                       *
20  *                                                                         *
21  *   Alternatively, this file is available under the Mozilla Public        *
22  *   License Version 1.1.  You may obtain a copy of the License at         *
23  *   http://www.mozilla.org/MPL/                                           *
24  ***************************************************************************/
25 
26 #include <tdebug.h>
27 #include <tfile.h>
28 
29 #include "infotag.h"
30 #include "riffutils.h"
31 
32 using namespace TagLib;
33 using namespace RIFF::Info;
34 
35 namespace
36 {
37   const RIFF::Info::StringHandler defaultStringHandler;
38   const RIFF::Info::StringHandler *stringHandler = &defaultStringHandler;
39 }
40 
41 class RIFF::Info::Tag::TagPrivate
42 {
43 public:
44   FieldListMap fieldListMap;
45 };
46 
47 ////////////////////////////////////////////////////////////////////////////////
48 // StringHandler implementation
49 ////////////////////////////////////////////////////////////////////////////////
50 
StringHandler()51 StringHandler::StringHandler()
52 {
53 }
54 
~StringHandler()55 StringHandler::~StringHandler()
56 {
57 }
58 
parse(const ByteVector & data) const59 String RIFF::Info::StringHandler::parse(const ByteVector &data) const
60 {
61   return String(data, String::UTF8);
62 }
63 
render(const String & s) const64 ByteVector RIFF::Info::StringHandler::render(const String &s) const
65 {
66   return s.data(String::UTF8);
67 }
68 
69 ////////////////////////////////////////////////////////////////////////////////
70 // public members
71 ////////////////////////////////////////////////////////////////////////////////
72 
Tag(const ByteVector & data)73 RIFF::Info::Tag::Tag(const ByteVector &data) :
74   TagLib::Tag(),
75   d(new TagPrivate())
76 {
77   parse(data);
78 }
79 
Tag()80 RIFF::Info::Tag::Tag() :
81   TagLib::Tag(),
82   d(new TagPrivate())
83 {
84 }
85 
~Tag()86 RIFF::Info::Tag::~Tag()
87 {
88   delete d;
89 }
90 
title() const91 String RIFF::Info::Tag::title() const
92 {
93   return fieldText("INAM");
94 }
95 
artist() const96 String RIFF::Info::Tag::artist() const
97 {
98   return fieldText("IART");
99 }
100 
album() const101 String RIFF::Info::Tag::album() const
102 {
103   return fieldText("IPRD");
104 }
105 
comment() const106 String RIFF::Info::Tag::comment() const
107 {
108   return fieldText("ICMT");
109 }
110 
genre() const111 String RIFF::Info::Tag::genre() const
112 {
113   return fieldText("IGNR");
114 }
115 
year() const116 unsigned int RIFF::Info::Tag::year() const
117 {
118   return fieldText("ICRD").substr(0, 4).toInt();
119 }
120 
track() const121 unsigned int RIFF::Info::Tag::track() const
122 {
123   return fieldText("IPRT").toInt();
124 }
125 
setTitle(const String & s)126 void RIFF::Info::Tag::setTitle(const String &s)
127 {
128   setFieldText("INAM", s);
129 }
130 
setArtist(const String & s)131 void RIFF::Info::Tag::setArtist(const String &s)
132 {
133   setFieldText("IART", s);
134 }
135 
setAlbum(const String & s)136 void RIFF::Info::Tag::setAlbum(const String &s)
137 {
138   setFieldText("IPRD", s);
139 }
140 
setComment(const String & s)141 void RIFF::Info::Tag::setComment(const String &s)
142 {
143   setFieldText("ICMT", s);
144 }
145 
setGenre(const String & s)146 void RIFF::Info::Tag::setGenre(const String &s)
147 {
148   setFieldText("IGNR", s);
149 }
150 
setYear(unsigned int i)151 void RIFF::Info::Tag::setYear(unsigned int i)
152 {
153   if(i != 0)
154     setFieldText("ICRD", String::number(i));
155   else
156     d->fieldListMap.erase("ICRD");
157 }
158 
setTrack(unsigned int i)159 void RIFF::Info::Tag::setTrack(unsigned int i)
160 {
161   if(i != 0)
162     setFieldText("IPRT", String::number(i));
163   else
164     d->fieldListMap.erase("IPRT");
165 }
166 
isEmpty() const167 bool RIFF::Info::Tag::isEmpty() const
168 {
169   return d->fieldListMap.isEmpty();
170 }
171 
fieldListMap() const172 FieldListMap RIFF::Info::Tag::fieldListMap() const
173 {
174   return d->fieldListMap;
175 }
176 
fieldText(const ByteVector & id) const177 String RIFF::Info::Tag::fieldText(const ByteVector &id) const
178 {
179   if(d->fieldListMap.contains(id))
180     return String(d->fieldListMap[id]);
181   else
182     return String();
183 }
184 
setFieldText(const ByteVector & id,const String & s)185 void RIFF::Info::Tag::setFieldText(const ByteVector &id, const String &s)
186 {
187   // id must be four-byte long pure ascii string.
188   if(!isValidChunkName(id))
189     return;
190 
191   if(!s.isEmpty())
192     d->fieldListMap[id] = s;
193   else
194     removeField(id);
195 }
196 
removeField(const ByteVector & id)197 void RIFF::Info::Tag::removeField(const ByteVector &id)
198 {
199   if(d->fieldListMap.contains(id))
200     d->fieldListMap.erase(id);
201 }
202 
render() const203 ByteVector RIFF::Info::Tag::render() const
204 {
205   ByteVector data("INFO");
206 
207   FieldListMap::ConstIterator it = d->fieldListMap.begin();
208   for(; it != d->fieldListMap.end(); ++it) {
209     ByteVector text = stringHandler->render(it->second);
210     if(text.isEmpty())
211       continue;
212 
213     data.append(it->first);
214     data.append(ByteVector::fromUInt(text.size() + 1, false));
215     data.append(text);
216 
217     do {
218       data.append('\0');
219     } while(data.size() & 1);
220   }
221 
222   if(data.size() == 4)
223     return ByteVector();
224   else
225     return data;
226 }
227 
setStringHandler(const StringHandler * handler)228 void RIFF::Info::Tag::setStringHandler(const StringHandler *handler)
229 {
230   if(handler)
231     stringHandler = handler;
232   else
233     stringHandler = &defaultStringHandler;
234 }
235 
236 ////////////////////////////////////////////////////////////////////////////////
237 // protected members
238 ////////////////////////////////////////////////////////////////////////////////
239 
parse(const ByteVector & data)240 void RIFF::Info::Tag::parse(const ByteVector &data)
241 {
242   unsigned int p = 4;
243   while(p < data.size()) {
244     const unsigned int size = data.toUInt(p + 4, false);
245     if(size > data.size() - p - 8)
246       break;
247 
248     const ByteVector id = data.mid(p, 4);
249     if(isValidChunkName(id)) {
250       const String text = stringHandler->parse(data.mid(p + 8, size));
251       d->fieldListMap[id] = text;
252     }
253 
254     p += ((size + 1) & ~1) + 8;
255   }
256 }
257