1 /***************************************************************************
2 * Copyright (C) 2008-2016 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>
37 #include <boost/filesystem.hpp>
40 #include "utility/string.h"
41 #include "utility/wide_string.h"
45 TagLib::StringList
tagList(const MPD::MutableSong
&s
, MPD::Song::GetFunction f
)
47 TagLib::StringList result
;
49 for (std::string value
; !(value
= (s
.*f
)(idx
)).empty(); ++idx
)
50 result
.append(ToWString(value
));
54 void readCommonTags(mpd_song
*s
, TagLib::Tag
*tag
)
56 Tags::setAttribute(s
, "Title", tag
->title().to8Bit(true));
57 Tags::setAttribute(s
, "Artist", tag
->artist().to8Bit(true));
58 Tags::setAttribute(s
, "Album", tag
->album().to8Bit(true));
59 Tags::setAttribute(s
, "Date", boost::lexical_cast
<std::string
>(tag
->year()));
60 Tags::setAttribute(s
, "Track", boost::lexical_cast
<std::string
>(tag
->track()));
61 Tags::setAttribute(s
, "Genre", tag
->genre().to8Bit(true));
62 Tags::setAttribute(s
, "Comment", tag
->comment().to8Bit(true));
65 void readID3v1Tags(mpd_song
*s
, TagLib::ID3v1::Tag
*tag
)
67 readCommonTags(s
, tag
);
70 void readID3v2Tags(mpd_song
*s
, TagLib::ID3v2::Tag
*tag
)
72 auto readFrame
= [s
](const TagLib::ID3v2::FrameList
&fields
, const char *name
) {
73 for (const auto &field
: fields
)
75 if (auto textFrame
= dynamic_cast<TagLib::ID3v2::TextIdentificationFrame
*>(field
))
77 auto values
= textFrame
->fieldList();
78 for (const auto &value
: values
)
79 Tags::setAttribute(s
, name
, value
.to8Bit(true));
82 Tags::setAttribute(s
, name
, field
->toString().to8Bit(true));
85 auto &frames
= tag
->frameListMap();
86 readFrame(frames
["TIT2"], "Title");
87 readFrame(frames
["TPE1"], "Artist");
88 readFrame(frames
["TPE2"], "AlbumArtist");
89 readFrame(frames
["TALB"], "Album");
90 readFrame(frames
["TDRC"], "Date");
91 readFrame(frames
["TRCK"], "Track");
92 readFrame(frames
["TCON"], "Genre");
93 readFrame(frames
["TCOM"], "Composer");
94 readFrame(frames
["TPE3"], "Performer");
95 readFrame(frames
["TPOS"], "Disc");
96 readFrame(frames
["COMM"], "Comment");
99 void readXiphComments(mpd_song
*s
, TagLib::Ogg::XiphComment
*tag
)
101 auto readField
= [s
](const TagLib::StringList
&fields
, const char *name
) {
102 for (const auto &field
: fields
)
103 Tags::setAttribute(s
, name
, field
.to8Bit(true));
105 auto &fields
= tag
->fieldListMap();
106 readField(fields
["TITLE"], "Title");
107 readField(fields
["ARTIST"], "Artist");
108 readField(fields
["ALBUMARTIST"], "AlbumArtist");
109 readField(fields
["ALBUM"], "Album");
110 readField(fields
["DATE"], "Date");
111 readField(fields
["TRACKNUMBER"], "Track");
112 readField(fields
["GENRE"], "Genre");
113 readField(fields
["COMPOSER"], "Composer");
114 readField(fields
["PERFORMER"], "Performer");
115 readField(fields
["DISCNUMBER"], "Disc");
116 readField(fields
["COMMENT"], "Comment");
119 void writeCommonTags(const MPD::MutableSong
&s
, TagLib::Tag
*tag
)
121 tag
->setTitle(ToWString(s
.getTitle()));
122 tag
->setArtist(ToWString(s
.getArtist()));
123 tag
->setAlbum(ToWString(s
.getAlbum()));
125 tag
->setYear(boost::lexical_cast
<TagLib::uint
>(s
.getDate()));
126 } catch (boost::bad_lexical_cast
&) {
127 std::cerr
<< "writeCommonTags: couldn't write 'year' tag to '" << s
.getURI() << "' as it's not a positive integer\n";
130 tag
->setTrack(boost::lexical_cast
<TagLib::uint
>(s
.getTrack()));
131 } catch (boost::bad_lexical_cast
&) {
132 std::cerr
<< "writeCommonTags: couldn't write 'track' tag to '" << s
.getURI() << "' as it's not a positive integer\n";
134 tag
->setGenre(ToWString(s
.getGenre()));
135 tag
->setComment(ToWString(s
.getComment()));
138 void writeID3v2Tags(const MPD::MutableSong
&s
, TagLib::ID3v2::Tag
*tag
)
140 auto writeID3v2
= [&](const TagLib::ByteVector
&type
, const TagLib::StringList
&list
) {
141 tag
->removeFrames(type
);
144 if (type
== "COMM") // comment needs to be handled separately
146 auto frame
= new TagLib::ID3v2::CommentsFrame(TagLib::String::UTF8
);
147 // apparently there can't be multiple comments,
148 // so if there is more than one, join them.
149 frame
->setText(join(list
, TagLib::String(MPD::Song::TagsSeparator
, TagLib::String::UTF8
)));
150 tag
->addFrame(frame
);
154 auto frame
= new TagLib::ID3v2::TextIdentificationFrame(type
, TagLib::String::UTF8
);
155 frame
->setText(list
);
156 tag
->addFrame(frame
);
160 writeID3v2("TIT2", tagList(s
, &MPD::Song::getTitle
));
161 writeID3v2("TPE1", tagList(s
, &MPD::Song::getArtist
));
162 writeID3v2("TPE2", tagList(s
, &MPD::Song::getAlbumArtist
));
163 writeID3v2("TALB", tagList(s
, &MPD::Song::getAlbum
));
164 writeID3v2("TDRC", tagList(s
, &MPD::Song::getDate
));
165 writeID3v2("TRCK", tagList(s
, &MPD::Song::getTrack
));
166 writeID3v2("TCON", tagList(s
, &MPD::Song::getGenre
));
167 writeID3v2("TCOM", tagList(s
, &MPD::Song::getComposer
));
168 writeID3v2("TPE3", tagList(s
, &MPD::Song::getPerformer
));
169 writeID3v2("TPOS", tagList(s
, &MPD::Song::getDisc
));
170 writeID3v2("COMM", tagList(s
, &MPD::Song::getComment
));
173 void writeXiphComments(const MPD::MutableSong
&s
, TagLib::Ogg::XiphComment
*tag
)
175 auto writeXiph
= [&](const TagLib::String
&type
, const TagLib::StringList
&list
) {
176 tag
->removeField(type
);
177 for (auto it
= list
.begin(); it
!= list
.end(); ++it
)
178 tag
->addField(type
, *it
, false);
180 // remove field previously used as album artist
181 tag
->removeField("ALBUM ARTIST");
182 // remove field TRACK, some taggers use it as TRACKNUMBER
183 tag
->removeField("TRACK");
184 // remove field DISC, some taggers use it as DISCNUMBER
185 tag
->removeField("DISC");
186 // remove field DESCRIPTION, it's displayed as COMMENT
187 tag
->removeField("DESCRIPTION");
188 writeXiph("TITLE", tagList(s
, &MPD::Song::getTitle
));
189 writeXiph("ARTIST", tagList(s
, &MPD::Song::getArtist
));
190 writeXiph("ALBUMARTIST", tagList(s
, &MPD::Song::getAlbumArtist
));
191 writeXiph("ALBUM", tagList(s
, &MPD::Song::getAlbum
));
192 writeXiph("DATE", tagList(s
, &MPD::Song::getDate
));
193 writeXiph("TRACKNUMBER", tagList(s
, &MPD::Song::getTrack
));
194 writeXiph("GENRE", tagList(s
, &MPD::Song::getGenre
));
195 writeXiph("COMPOSER", tagList(s
, &MPD::Song::getComposer
));
196 writeXiph("PERFORMER", tagList(s
, &MPD::Song::getPerformer
));
197 writeXiph("DISCNUMBER", tagList(s
, &MPD::Song::getDisc
));
198 writeXiph("COMMENT", tagList(s
, &MPD::Song::getComment
));
201 Tags::ReplayGainInfo
getReplayGain(TagLib::Ogg::XiphComment
*tag
)
203 auto first_or_empty
= [](const TagLib::StringList
&list
) {
206 result
= list
.front().to8Bit(true);
209 auto &fields
= tag
->fieldListMap();
210 return Tags::ReplayGainInfo(
211 first_or_empty(fields
["REPLAYGAIN_REFERENCE_LOUDNESS"]),
212 first_or_empty(fields
["REPLAYGAIN_TRACK_GAIN"]),
213 first_or_empty(fields
["REPLAYGAIN_TRACK_PEAK"]),
214 first_or_empty(fields
["REPLAYGAIN_ALBUM_GAIN"]),
215 first_or_empty(fields
["REPLAYGAIN_ALBUM_PEAK"])
223 void setAttribute(mpd_song
*s
, const char *name
, const std::string
&value
)
225 mpd_pair pair
= { name
, value
.c_str() };
226 mpd_song_feed(s
, &pair
);
229 bool extendedSetSupported(const TagLib::File
*f
)
231 return dynamic_cast<const TagLib::MPEG::File
*>(f
)
232 || dynamic_cast<const TagLib::Ogg::Vorbis::File
*>(f
)
233 || dynamic_cast<const TagLib::FLAC::File
*>(f
);
236 ReplayGainInfo
readReplayGain(TagLib::File
*f
)
238 ReplayGainInfo result
;
239 if (auto ogg_file
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>(f
))
241 if (auto xiph
= ogg_file
->tag())
242 result
= getReplayGain(xiph
);
244 else if (auto flac_file
= dynamic_cast<TagLib::FLAC::File
*>(f
))
246 if (auto xiph
= flac_file
->xiphComment())
247 result
= getReplayGain(xiph
);
252 void read(mpd_song
*s
)
254 TagLib::FileRef
f(mpd_song_get_uri(s
));
258 setAttribute(s
, "Time", boost::lexical_cast
<std::string
>(f
.audioProperties()->length()));
260 if (auto mpeg_file
= dynamic_cast<TagLib::MPEG::File
*>(f
.file()))
262 // prefer id3v2 only if available
263 if (auto id3v2
= mpeg_file
->ID3v2Tag())
264 readID3v2Tags(s
, id3v2
);
265 else if (auto id3v1
= mpeg_file
->ID3v1Tag())
266 readID3v1Tags(s
, id3v1
);
268 else if (auto ogg_file
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>(f
.file()))
270 if (auto xiph
= ogg_file
->tag())
271 readXiphComments(s
, xiph
);
273 else if (auto flac_file
= dynamic_cast<TagLib::FLAC::File
*>(f
.file()))
275 if (auto xiph
= flac_file
->xiphComment())
276 readXiphComments(s
, xiph
);
279 readCommonTags(s
, f
.tag());
282 bool write(MPD::MutableSong
&s
)
284 std::string old_name
;
285 if (s
.isFromDatabase())
286 old_name
+= Config
.mpd_music_dir
;
287 old_name
+= s
.getURI();
289 TagLib::FileRef
f(old_name
.c_str());
294 if (auto mpeg_file
= dynamic_cast<TagLib::MPEG::File
*>(f
.file()))
296 writeID3v2Tags(s
, mpeg_file
->ID3v2Tag(true));
297 // write id3v2.4 tags only
298 if (!mpeg_file
->save(TagLib::MPEG::File::ID3v2
, true, 4, false))
300 // do not call generic save() as it will duplicate tags
303 else if (auto ogg_file
= dynamic_cast<TagLib::Ogg::Vorbis::File
*>(f
.file()))
305 writeXiphComments(s
, ogg_file
->tag());
307 else if (auto flac_file
= dynamic_cast<TagLib::FLAC::File
*>(f
.file()))
309 writeXiphComments(s
, flac_file
->xiphComment(true));
312 writeCommonTags(s
, f
.tag());
314 if (!saved
&& !f
.save())
317 // TODO: move this somewhere else
318 if (!s
.getNewName().empty())
320 std::string new_name
;
321 if (s
.isFromDatabase())
322 new_name
+= Config
.mpd_music_dir
;
323 new_name
+= s
.getDirectory();
325 new_name
+= s
.getNewName();
326 boost::filesystem::rename(old_name
, new_name
);
333 #endif // HAVE_TAGLIB_H