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