tags: writeID3v2Tags: write comment tag properly
[ncmpcpp.git] / src / tags.cpp
blob03a8330eb2aec983dcb7b21a1d2aaa18ce40e92c
1 /***************************************************************************
2 * Copyright (C) 2008-2014 by Andrzej Rybczak *
3 * electricityispower@gmail.com *
4 * *
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. *
9 * *
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. *
14 * *
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 ***************************************************************************/
21 #include "tags.h"
23 #ifdef HAVE_TAGLIB_H
25 // taglib includes
26 #include <id3v1tag.h>
27 #include <id3v2tag.h>
28 #include <fileref.h>
29 #include <flacfile.h>
30 #include <mpegfile.h>
31 #include <vorbisfile.h>
32 #include <tag.h>
33 #include <textidentificationframe.h>
34 #include <commentsframe.h>
35 #include <xiphcomment.h>
37 #include "global.h"
38 #include "settings.h"
39 #include "utility/string.h"
40 #include "utility/wide_string.h"
42 namespace {//
44 TagLib::StringList tagList(const MPD::MutableSong &s, MPD::Song::GetFunction f)
46 TagLib::StringList result;
47 unsigned idx = 0;
48 for (std::string value; !(value = (s.*f)(idx)).empty(); ++idx)
49 result.append(ToWString(value));
50 return result;
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) {
72 unsigned idx = 0;
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);
81 else
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) {
102 unsigned idx = 0;
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);
125 tag->setYear(0);
126 tag->setTrack(0);
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()));
136 try {
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";
141 try {
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);
154 if (!list.isEmpty())
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);
164 else
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) {
214 std::string result;
215 if (!list.isEmpty())
216 result = list.front().to8Bit(true);
217 return result;
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"])
231 namespace Tags {//
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);
253 return result;
256 void read(MPD::MutableSong &s)
258 TagLib::FileRef f(s.getURI().c_str());
259 if (f.isNull())
260 return;
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);
281 else
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());
293 if (f.isNull())
294 return false;
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));
309 else
310 writeCommonTags(s, f.tag());
312 if (!f.save())
313 return false;
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())
323 // FIXME
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();
330 Mpd.Delete(pos);
331 int id = Mpd.AddSong("file://" + new_name);
332 if (id >= 0)
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());*/
343 return true;
348 #endif // HAVE_TAGLIB_H