1 /* id3.cpp */
2
3 /* Copyright (C) 2011-2020 Michael Lugmair (Lucio Carreras)
4 *
5 * This file is part of sayonara player
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "Tagging.h"
22 #include "Tagging/TaggingCover.h"
23 #include "Tagging/TaggingExtraFields.h"
24 #include "Tagging/TaggingUtils.h"
25 #include "Tagging/Models/Discnumber.h"
26 #include "Tagging/Models/Popularimeter.h"
27
28 #include "Utils/Algorithm.h"
29 #include "Utils/Utils.h"
30 #include "Utils/FileUtils.h"
31 #include "Utils/MetaData/MetaData.h"
32 #include "Utils/Logger/Logger.h"
33
34 #include <QFileInfo>
35 #include <QRegExp>
36 #include <QStringList>
37 #include <QDateTime>
38
39 using namespace Tagging::Utils;
40 namespace FileUtils = ::Util::File;
41
42 namespace
43 {
44 struct ReadingProperties
45 {
46 TagLib::AudioProperties::ReadStyle readStyle {TagLib::AudioProperties::ReadStyle::Fast};
47 bool readAudioProperties {true};
48 };
49
getReadingProperties(Tagging::Quality quality)50 ReadingProperties getReadingProperties(Tagging::Quality quality)
51 {
52 ReadingProperties readingProperties;
53
54 switch(quality)
55 {
56 case Tagging::Quality::Quality:
57 readingProperties.readStyle = TagLib::AudioProperties::Accurate;
58 break;
59 case Tagging::Quality::Standard:
60 readingProperties.readStyle = TagLib::AudioProperties::Average;
61 break;
62 case Tagging::Quality::Fast:
63 readingProperties.readStyle = TagLib::AudioProperties::Fast;
64 break;
65 case Tagging::Quality::Dirty:
66 readingProperties.readStyle = TagLib::AudioProperties::Fast;
67 readingProperties.readAudioProperties = false;
68 break;
69 };
70
71 return readingProperties;
72 }
73
getTitleFromFilename(const QString & filepath)74 QString getTitleFromFilename(const QString& filepath)
75 {
76 const auto filename = FileUtils::getFilenameOfPath(filepath);
77 return (filename.size() > 4)
78 ? filename.left(filename.length() - 4)
79 : filename;
80 }
81
setDate(MetaData & track)82 void setDate(MetaData& track)
83 {
84 const auto fileInfo = QFileInfo(track.filepath());
85 QDateTime createDate;
86
87 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
88 createDate = fileInfo.birthTime();
89 if(!createDate.isValid())
90 {
91 createDate = fileInfo.metadataChangeTime();
92 }
93 #else
94 createDate = fileInfo.created();
95 #endif
96
97 if(!createDate.isValid())
98 {
99 createDate = fileInfo.lastModified();
100 }
101
102 track.setCreatedDate(::Util::dateToInt(createDate));
103 track.setModifiedDate(Util::dateToInt(fileInfo.lastModified()));
104 }
105
extractGenres(const QString & genreString)106 QStringList extractGenres(const QString& genreString)
107 {
108 auto genres = genreString.split(QRegExp(",|/|;"));
109 Util::Algorithm::transform(genres, [](const auto& genre) {
110 return genre.trimmed();
111 });
112
113 genres.removeDuplicates();
114 genres.removeAll("");
115
116 return genres;
117 }
118 }
119
getMetaDataOfFile(MetaData & track,Quality quality)120 bool Tagging::Utils::getMetaDataOfFile(MetaData& track, Quality quality)
121 {
122 const auto fileInfo = QFileInfo(track.filepath());
123 track.setFilesize(static_cast<Filesize>(fileInfo.size()));
124 setDate(track);
125
126 if(fileInfo.size() <= 0)
127 {
128 return false;
129 }
130
131 const auto readingProperties = getReadingProperties(quality);
132 auto fileRef = TagLib::FileRef(TagLib::FileName(track.filepath().toUtf8()),
133 readingProperties.readAudioProperties,
134 readingProperties.readStyle);
135
136 if(!isValidFile(fileRef))
137 {
138 spLog(Log::Warning, "Tagging") << "Cannot open tags for " << track.filepath() << ": Err 1";
139 return false;
140 }
141
142 const auto parsedTag = getParsedTagFromFileRef(fileRef);
143 if(!parsedTag.tag)
144 {
145 return false;
146 }
147
148 const auto artist = convertString(parsedTag.tag->artist());
149 const auto album = convertString(parsedTag.tag->album());
150 const auto title = convertString(parsedTag.tag->title());
151 const auto genre = convertString(parsedTag.tag->genre());
152 const auto comment = convertString(parsedTag.tag->comment());
153 const auto year = parsedTag.tag->year();
154 const auto trackNumber = parsedTag.tag->track();
155 const auto bitrate = (quality != Quality::Dirty)
156 ? fileRef.audioProperties()->bitrate() * 1000
157 : 0;
158
159 const auto length = (quality != Quality::Dirty)
160 ? fileRef.audioProperties()->length() * 1000
161 : 0;
162
163 const auto genres = extractGenres(genre);
164
165 track.setAlbum(album);
166 track.setArtist(artist);
167 track.setTitle(title.isEmpty() ? getTitleFromFilename(track.filepath()) : title);
168 track.setDurationMs(length);
169 track.setYear(Year(year));
170 track.setTrackNumber(static_cast<TrackNum>(trackNumber));
171 track.setBitrate(Bitrate(bitrate));
172 track.setGenres(genres);
173 track.setComment(comment);
174
175 Tagging::readAlbumArtist(track, parsedTag);
176 Tagging::readDiscnumber(track, parsedTag);
177 Tagging::readPopularimeter(track, parsedTag);
178
179 const auto hasCover = static_cast<int>(Tagging::hasCover(parsedTag));
180 track.addCustomField("has-album-art", "", QString::number(hasCover));
181
182 return true;
183 }
184
setMetaDataOfFile(const MetaData & md)185 bool Tagging::Utils::setMetaDataOfFile(const MetaData& md)
186 {
187 const auto filepath = md.filepath();
188 const auto fileInfo = QFileInfo(filepath);
189 if(fileInfo.size() <= 0)
190 {
191 return false;
192 }
193
194 auto fileRef = TagLib::FileRef(TagLib::FileName(filepath.toUtf8()));
195 if(!isValidFile(fileRef))
196 {
197 spLog(Log::Warning, "Tagging") << "Cannot open tags for " << md.filepath() << ": Err 2";
198 return false;
199 }
200
201 const auto album = convertString(md.album());
202 const auto artist = convertString(md.artist());
203 const auto title = convertString(md.title());
204 const auto genre = convertString(md.genresToString());
205 const auto comment = convertString(md.comment());
206
207 const auto parsedTag = getParsedTagFromFileRef(fileRef);
208 if(!parsedTag.tag)
209 {
210 return false;
211 }
212
213 parsedTag.tag->setAlbum(album);
214 parsedTag.tag->setArtist(artist);
215 parsedTag.tag->setTitle(title);
216 parsedTag.tag->setGenre(genre);
217 parsedTag.tag->setYear(md.year());
218 parsedTag.tag->setTrack(md.trackNumber());
219 parsedTag.tag->setComment(comment);
220
221 Tagging::writePopularimeter(parsedTag, Models::Popularimeter("sayonara player", md.rating(), 0));
222 Tagging::writeDiscnumber(parsedTag, Models::Discnumber(md.discnumber(), md.discCount()));
223 Tagging::writeAlbumArtist(parsedTag, md.albumArtist());
224
225 const auto success = fileRef.save();
226 if(!success)
227 {
228 spLog(Log::Warning, "Tagging") << "Could not save " << md.filepath();
229 }
230
231 return success;
232 }
233