1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
31 #include <vorbisfile.h>
33 #include <textidentificationframe.h>
34 #include <commentsframe.h>
35 #include <xiphcomment.h>
39 #include "utility/string.h"
40 #include "utility/wide_string.h"
44 TagLib::StringList
tagList(const MPD::MutableSong
&s
, MPD::Song::GetFunction f
)
46 TagLib::StringList result
;
48 for (std::string value
; !(value
= (s
.*f
)(idx
)).empty(); ++idx
)
49 result
.append(ToWString(value
));
53 void readCommonTags(MPD::MutableSong
&s
, TagLib::Tag
*tag
)
55 s
.setTitle(tag
->title().to8Bit(true));
56 s
.setArtist(tag
->artist().to8Bit(true));
57 s
.setAlbum(tag
->album().to8Bit(true));
58 s
.setDate(boost::lexical_cast
<std::string
>(tag
->year()));
59 s
.setTrack(boost::lexical_cast
<std::string
>(tag
->track()));
60 s
.setGenre(tag
->genre().to8Bit(true));
61 s
.setComment(tag
->comment().to8Bit(true));
64 void readID3v1Tags(MPD::MutableSong
&s
, TagLib::ID3v1::Tag
*tag
)
66 readCommonTags(s
, tag
);
69 void readID3v2Tags(MPD::MutableSong
&s
, TagLib::ID3v2::Tag
*tag
)
71 auto readFrame
= [&s
](const TagLib::ID3v2::FrameList
&list
, MPD::MutableSong::SetFunction f
) {
73 for (auto it
= list
.begin(); it
!= list
.end(); ++it
, ++idx
)
75 if (auto textFrame
= dynamic_cast<TagLib::ID3v2::TextIdentificationFrame
*>(*it
))
77 auto values
= textFrame
->fieldList();
78 for (auto value
= values
.begin(); value
!= values
.end(); ++value
, ++idx
)
79 (s
.*f
)(value
->to8Bit(true), idx
);
82 (s
.*f
)((*it
)->toString().to8Bit(true), idx
);
85 auto &frames
= tag
->frameListMap();
86 readFrame(frames
["TIT2"], &MPD::MutableSong::setTitle
);
87 readFrame(frames
["TPE1"], &MPD::MutableSong::setArtist
);
88 readFrame(frames
["TPE2"], &MPD::MutableSong::setAlbumArtist
);
89 readFrame(frames
["TALB"], &MPD::MutableSong::setAlbum
);
90 readFrame(frames
["TDRC"], &MPD::MutableSong::setDate
);
91 readFrame(frames
["TRCK"], &MPD::MutableSong::setTrack
);
92 readFrame(frames
["TCON"], &MPD::MutableSong::setGenre
);
93 readFrame(frames
["TCOM"], &MPD::MutableSong::setComposer
);
94 readFrame(frames
["TPE3"], &MPD::MutableSong::setPerformer
);
95 readFrame(frames
["TPOS"], &MPD::MutableSong::setDisc
);
96 readFrame(frames
["COMM"], &MPD::MutableSong::setComment
);
99 void readXiphComments(MPD::MutableSong
&s
, TagLib::Ogg::XiphComment
*tag
)
101 auto readField
= [&s
](const TagLib::StringList
&list
, MPD::MutableSong::SetFunction f
) {
103 for (auto it
= list
.begin(); it
!= list
.end(); ++it
, ++idx
)
104 (s
.*f
)(it
->to8Bit(true), idx
);
106 auto &fields
= tag
->fieldListMap();
107 readField(fields
["TITLE"], &MPD::MutableSong::setTitle
);
108 readField(fields
["ARTIST"], &MPD::MutableSong::setArtist
);
109 readField(fields
["ALBUMARTIST"], &MPD::MutableSong::setAlbumArtist
);
110 readField(fields
["ALBUM"], &MPD::MutableSong::setAlbum
);
111 readField(fields
["DATE"], &MPD::MutableSong::setDate
);
112 readField(fields
["TRACKNUMBER"], &MPD::MutableSong::setTrack
);
113 readField(fields
["GENRE"], &MPD::MutableSong::setGenre
);
114 readField(fields
["COMPOSER"], &MPD::MutableSong::setComposer
);
115 readField(fields
["PERFORMER"], &MPD::MutableSong::setPerformer
);
116 readField(fields
["DISCNUMBER"], &MPD::MutableSong::setDisc
);
117 readField(fields
["COMMENT"], &MPD::MutableSong::setComment
);
120 void clearID3v1Tags(TagLib::ID3v1::Tag
*tag
)
122 tag
->setTitle(TagLib::String::null
);
123 tag
->setArtist(TagLib::String::null
);
124 tag
->setAlbum(TagLib::String::null
);
127 tag
->setGenre(TagLib::String::null
);
128 tag
->setComment(TagLib::String::null
);
131 void writeCommonTags(const MPD::MutableSong
&s
, TagLib::Tag
*tag
)
133 tag
->setTitle(ToWString(s
.getTitle()));
134 tag
->setArtist(ToWString(s
.getArtist()));
135 tag
->setAlbum(ToWString(s
.getAlbum()));
137 tag
->setYear(boost::lexical_cast
<TagLib::uint
>(s
.getDate()));
138 } catch (boost::bad_lexical_cast
&) {
139 std::cerr
<< "writeCommonTags: couldn't write 'year' tag to '" << s
.getURI() << "' as it's not a positive integer\n";
142 tag
->setTrack(boost::lexical_cast
<TagLib::uint
>(s
.getTrack()));
143 } catch (boost::bad_lexical_cast
&) {
144 std::cerr
<< "writeCommonTags: couldn't write 'track' tag to '" << s
.getURI() << "' as it's not a positive integer\n";
146 tag
->setGenre(ToWString(s
.getGenre()));
147 tag
->setComment(ToWString(s
.getComment()));
150 void writeID3v2Tags(const MPD::MutableSong
&s
, TagLib::ID3v2::Tag
*tag
)
152 auto writeID3v2
= [&](const TagLib::ByteVector
&type
, const TagLib::StringList
&list
) {
153 tag
->removeFrames(type
);
156 if (type
== "COMM") // comment needs to be handled separately
158 auto frame
= new TagLib::ID3v2::CommentsFrame(TagLib::String::UTF8
);
159 // apparently there can't be multiple comments,
160 // so if there is more than one, join them.
161 frame
->setText(join(list
, TagLib::String(Config
.tags_separator
, TagLib::String::UTF8
)));
162 tag
->addFrame(frame
);
166 auto frame
= new TagLib::ID3v2::TextIdentificationFrame(type
, TagLib::String::UTF8
);
167 frame
->setText(list
);
168 tag
->addFrame(frame
);
172 writeID3v2("TIT2", tagList(s
, &MPD::Song::getTitle
));
173 writeID3v2("TPE1", tagList(s
, &MPD::Song::getArtist
));
174 writeID3v2("TPE2", tagList(s
, &MPD::Song::getAlbumArtist
));
175 writeID3v2("TALB", tagList(s
, &MPD::Song::getAlbum
));
176 writeID3v2("TDRC", tagList(s
, &MPD::Song::getDate
));
177 writeID3v2("TRCK", tagList(s
, &MPD::Song::getTrack
));
178 writeID3v2("TCON", tagList(s
, &MPD::Song::getGenre
));
179 writeID3v2("TCOM", tagList(s
, &MPD::Song::getComposer
));
180 writeID3v2("TPE3", tagList(s
, &MPD::Song::getPerformer
));
181 writeID3v2("TPOS", tagList(s
, &MPD::Song::getDisc
));
182 writeID3v2("COMM", tagList(s
, &MPD::Song::getComment
));
185 void writeXiphComments(const MPD::MutableSong
&s
, TagLib::Ogg::XiphComment
*tag
)
187 auto writeXiph
= [&](const TagLib::String
&type
, const TagLib::StringList
&list
) {
188 tag
->removeField(type
);
189 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
190 tag
->addField(type
, *it
, false);
192 // remove field previously used as album artist
193 tag
->removeField("ALBUM ARTIST");
194 // remove field TRACK, some taggers use it as TRACKNUMBER
195 tag
->removeField("TRACK");
196 // remove field DISC, some taggers use it as DISCNUMBER
197 tag
->removeField("DISC");
198 writeXiph("TITLE", tagList(s
, &MPD::Song::getTitle
));
199 writeXiph("ARTIST", tagList(s
, &MPD::Song::getArtist
));
200 writeXiph("ALBUMARTIST", tagList(s
, &MPD::Song::getAlbumArtist
));
201 writeXiph("ALBUM", tagList(s
, &MPD::Song::getAlbum
));
202 writeXiph("DATE", tagList(s
, &MPD::Song::getDate
));
203 writeXiph("TRACKNUMBER", tagList(s
, &MPD::Song::getTrack
));
204 writeXiph("GENRE", tagList(s
, &MPD::Song::getGenre
));
205 writeXiph("COMPOSER", tagList(s
, &MPD::Song::getComposer
));
206 writeXiph("PERFORMER", tagList(s
, &MPD::Song::getPerformer
));
207 writeXiph("DISCNUMBER", tagList(s
, &MPD::Song::getDisc
));
208 writeXiph("COMMENT", tagList(s
, &MPD::Song::getComment
));
211 Tags::ReplayGainInfo
getReplayGain(TagLib::Ogg::XiphComment
*tag
)
213 auto first_or_empty
= [](const TagLib::StringList
&list
) {
216 result
= list
.front().to8Bit(true);
219 auto &fields
= tag
->fieldListMap();
220 return Tags::ReplayGainInfo(
221 first_or_empty(fields
["REPLAYGAIN_REFERENCE_LOUDNESS"]),
222 first_or_empty(fields
["REPLAYGAIN_TRACK_GAIN"]),
223 first_or_empty(fields
["REPLAYGAIN_TRACK_PEAK"]),
224 first_or_empty(fields
["REPLAYGAIN_ALBUM_GAIN"]),
225 first_or_empty(fields
["REPLAYGAIN_ALBUM_PEAK"])
233 bool extendedSetSupported(const TagLib::File
*f
)
235 return dynamic_cast<const TagLib::MPEG::File
*>(f
)
236 || dynamic_cast<const TagLib::Ogg::Vorbis::File
*>(f
)
237 || dynamic_cast<const TagLib::FLAC::File
*>(f
);
240 ReplayGainInfo
readReplayGain(TagLib::File
*f
)
242 ReplayGainInfo result
;
243 if (auto ogg_file
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>(f
))
245 if (auto xiph
= ogg_file
->tag())
246 result
= getReplayGain(xiph
);
248 else if (auto flac_file
= dynamic_cast<TagLib::FLAC::File
*>(f
))
250 if (auto xiph
= flac_file
->xiphComment())
251 result
= getReplayGain(xiph
);
256 void read(MPD::MutableSong
&s
)
258 TagLib::FileRef
f(s
.getURI().c_str());
262 s
.setDuration(f
.audioProperties()->length());
264 if (auto mpeg_file
= dynamic_cast<TagLib::MPEG::File
*>(f
.file()))
266 if (auto id3v1
= mpeg_file
->ID3v1Tag())
267 readID3v1Tags(s
, id3v1
);
268 if (auto id3v2
= mpeg_file
->ID3v2Tag())
269 readID3v2Tags(s
, id3v2
);
271 else if (auto ogg_file
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>(f
.file()))
273 if (auto xiph
= ogg_file
->tag())
274 readXiphComments(s
, xiph
);
276 else if (auto flac_file
= dynamic_cast<TagLib::FLAC::File
*>(f
.file()))
278 if (auto xiph
= flac_file
->xiphComment())
279 readXiphComments(s
, xiph
);
282 readCommonTags(s
, f
.tag());
285 bool write(MPD::MutableSong
&s
)
287 std::string old_name
;
288 if (s
.isFromDatabase())
289 old_name
+= Config
.mpd_music_dir
;
290 old_name
+= s
.getURI();
292 TagLib::FileRef
f(old_name
.c_str());
296 if (auto mp3_file
= dynamic_cast<TagLib::MPEG::File
*>(f
.file()))
298 clearID3v1Tags(mp3_file
->ID3v1Tag());
299 writeID3v2Tags(s
, mp3_file
->ID3v2Tag(true));
301 else if (auto ogg_file
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>(f
.file()))
303 writeXiphComments(s
, ogg_file
->tag());
305 else if (auto flac_file
= dynamic_cast<TagLib::FLAC::File
*>(f
.file()))
307 writeXiphComments(s
, flac_file
->xiphComment(true));
310 writeCommonTags(s
, f
.tag());
315 if (!s
.getNewName().empty())
317 std::string new_name
;
318 if (s
.isFromDatabase())
319 new_name
+= Config
.mpd_music_dir
;
320 new_name
+= s
.getDirectory() + "/" + s
.getNewName();
321 if (std::rename(old_name
.c_str(), new_name
.c_str()) == 0 && !s
.isFromDatabase())
324 /*if (myTinyTagEditor == myPlaylist)
326 // if we rename local file, it won't get updated
327 // so just remove it from playlist and add again
328 size_t pos = myPlaylist->main().choice();
329 Mpd.StartCommandsList();
331 int id = Mpd.AddSong("file://" + new_name);
334 s = myPlaylist->main().back().value();
335 Mpd.Move(s.getPosition(), pos);
337 Mpd.CommitCommandsList();
339 else // only myBrowser->main()
340 myBrowser->GetDirectory(myBrowser->CurrentDir());*/
348 #endif // HAVE_TAGLIB_H