inLimbo
TUI Music Player that keeps you in Limbo.
 
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
Loading...
Searching...
No Matches
taglib_parser.h
Go to the documentation of this file.
1
10#pragma once
11
12#include <filesystem>
13#include <fstream>
14#include <iostream>
15#include <string>
16#include <sys/stat.h>
17#ifndef __EMSCRIPTEN__
18#include <taglib/fileref.h>
19#include <taglib/tag.h>
20#include <taglib/id3v2tag.h>
21#include <taglib/mpegfile.h>
22#include <taglib/attachedpictureframe.h>
23#include <taglib/tpropertymap.h>
24#include <taglib/flacfile.h>
25#include <png.h>
26#endif
27#include <unordered_map>
28
29namespace fs = std::filesystem;
30
37
39{
40 std::string title = "Unknown Title";
41 std::string artist = "<Unknown Artist>";
42 std::string album = "Unknown Album";
43 std::string genre = "Unknown Genre";
44 std::string comment = "No Comment";
45 std::string fileType = "NULL";
46 unsigned int year = 0;
47 unsigned int track = 0;
48 unsigned int discNumber = 0;
49 std::string lyrics = "No Lyrics";
50 std::unordered_map<std::string, std::string> additionalProperties;
51 std::string filePath;
52 float duration = 0.0f;
53 int bitrate = 0;
54
59 template <class Archive>
64};
65
74{
75public:
80 explicit TagLibParser(const std::string& debugString);
81
88 auto parseFile(const std::string& filePath, Metadata& metadata) -> bool;
89
96 auto parseFromInode(ino_t inode, const std::string& directory) -> std::unordered_map<std::string, Metadata>;
97};
98
103void printMetadata(const Metadata& metadata);
104
110void sendErrMsg(std::string debugLogBoolStr, std::string errMsg);
111
112// variable to keep track of user's preference of debug logs
113std::string debugLogBoolStr;
114
116
118
119// Constructor with parameter
120TagLibParser::TagLibParser(const std::string& debugString) { debugLogBoolStr = debugString; debugLogBool = debugLogBoolStr == "true" ? true : false; }
121
122void sendErrMsg(std::string debugLogBoolStr, std::string errMsg)
123{
124
125 if (debugLogBool)
126 {
127 std::cerr << errMsg << std::endl;
128 }
129
130 return;
131}
132
133#ifndef __EMSCRIPTEN__ // TagLib-specific implementations
134
135// Function to parse metadata from a file
136auto TagLibParser::parseFile(const std::string& filePath, Metadata& metadata) -> bool {
137 if (debugLogBool) {
138 std::cout << "-- [TAG PARSE] Parsing file: " << filePath << std::endl;
139 }
140
141 TagLib::FileRef file(filePath.c_str());
142 std::string errMsg;
143
144 // If file is invalid or cannot be opened
145 if (file.isNull()) {
146 errMsg = "Error: Failed to open file: " + filePath;
148 if (debugLogBool) {
149 std::cout << "[TAG PARSE] " << errMsg << std::endl;
150 }
151 metadata.title = filePath.substr(filePath.find_last_of("/\\") + 1); // Use the file name
152 return false;
153 }
154
155 // If file has no tag information
156 if (!file.tag()) {
157 errMsg = "Error: No tag information found in file: " + filePath;
159 if (debugLogBool) {
160 std::cout << "[TAG PARSE] " << errMsg << std::endl;
161 }
162
163 // Fallback metadata in case there is no tag information
164 metadata.title = filePath.substr(filePath.find_last_of("/\\") + 1); // Use the file name
165 return true;
166 }
167
168 // If the file contains tag data, continue processing
169 TagLib::Tag* tag = file.tag();
170 metadata.title = tag->title().isEmpty() ? filePath.substr(filePath.find_last_of("/\\") + 1) : tag->title().to8Bit(true);
171 metadata.artist = tag->artist().isEmpty() ? "<Unknown Artist>" : tag->artist().to8Bit(true);
172 metadata.album = tag->album().isEmpty() ? "Unknown Album" : tag->album().to8Bit(true);
173 metadata.genre = tag->genre().isEmpty() ? "Unknown Genre" : tag->genre().to8Bit(true);
174 metadata.comment = tag->comment().isEmpty() ? "No Comment" : tag->comment().to8Bit(true);
175 metadata.year = tag->year() == 0 ? 0 : tag->year();
176
177 // Track logic
178 metadata.track = tag->track();
179 if (debugLogBool) {
180 std::cout << "[TAG PARSE] Track: " << (tag->track() == 0 ? "(No Track, using fallback)" : std::to_string(tag->track())) << std::endl;
181
182 }
183
184 if (tag->track() == 0) {
185 if (metadata.artist == "<Unknown Artist>") {
187 metadata.track = unknownArtistTracks;
188 }
189 }
190 if (debugLogBool) std::cout << "[TAG PARSE] Assigned track number: " << metadata.track << std::endl;
191
192 // Audio Properties
193 TagLib::AudioProperties *audioProperties = file.audioProperties();
194 if (audioProperties) {
195 metadata.duration = audioProperties->length(); // Duration in seconds
196 metadata.bitrate = (audioProperties->bitrate() == 0) ? -1 : audioProperties->bitrate();
197
198 if (debugLogBool) {
199 std::cout << "[TAG PARSE] Audio properties found:" << std::endl;
200 std::cout << "[TAG PARSE] Duration: " << metadata.duration << " seconds" << std::endl;
201 std::cout << "[TAG PARSE] Bitrate: " << metadata.bitrate << " kbps" << std::endl;
202 }
203 } else {
204 if (debugLogBool) {
205 std::cout << "[TAG PARSE] No audio properties found." << std::endl;
206 }
207 metadata.duration = 0.0f;
208 metadata.bitrate = 0;
209 }
210
211 // Keep track of file path
212 metadata.filePath = filePath;
213 if (debugLogBool) {
214 std::cout << "[TAG PARSE] File Path: " << metadata.filePath << std::endl;
215 }
216
217 // Extract additional properties such as lyrics and disc number
218 TagLib::PropertyMap properties = file.file()->properties();
219 if (properties.contains("DISCNUMBER")) {
220 metadata.discNumber = properties["DISCNUMBER"].toString().toInt();
221 if (debugLogBool) {
222 std::cout << "[TAG PARSE] Disc Number: " << metadata.discNumber << std::endl;
223 }
224 } else {
225 metadata.discNumber = 0;
226 if (debugLogBool) {
227 std::cout << "[TAG PARSE] Disc Number: (Not Available)" << std::endl;
228 }
229 }
230
231 if (properties.contains("LYRICS")) {
232 metadata.lyrics = properties["LYRICS"].toString().to8Bit(true);
233 } else {
234 metadata.lyrics = "No Lyrics";
235 if (debugLogBool) {
236 std::cout << "[TAG PARSE] Lyrics: (Not Available)" << std::endl;
237 }
238 }
239
240 // Populate additional properties if needed
241 if (!properties.isEmpty()) {
242 if (debugLogBool) std::cout << "[TAG PARSE] Additional properties found!" << std::endl;
243 for (const auto& prop : properties) {
244 std::string key = prop.first.to8Bit(true);
245 std::string value = prop.second.toString().to8Bit(true);
246 metadata.additionalProperties[key] = value;
247 }
248 }
249
250 return true;
251}
252
253// Function to parse metadata based on inode
254auto TagLibParser::parseFromInode(ino_t inode, const std::string& directory) -> std::unordered_map<std::string, Metadata> {
255 std::unordered_map<std::string, Metadata> metadataMap;
256 std::string tempErrMsg;
257
258 for (const auto& entry : fs::recursive_directory_iterator(directory)) {
259 struct stat fileStat;
260 if (stat(entry.path().c_str(), &fileStat) == 0) {
261 if (fileStat.st_ino == inode) {
262 Metadata metadata;
263 if (parseFile(entry.path().string(), metadata)) {
264 metadataMap[entry.path().string()] = metadata;
265 } else {
266 tempErrMsg = "Error: Unable to parse metadata for file: " + entry.path().string();
267 sendErrMsg(debugLogBoolStr, tempErrMsg);
268 }
269 }
270 } else {
271 tempErrMsg = "Error: Unable to stat file: " + entry.path().string();
272 sendErrMsg(debugLogBoolStr, tempErrMsg);
273 }
274 }
275
276 return metadataMap;
277}
278
279#else // Stub implementations for Emscripten
280
287bool TagLibParser::parseFile(const std::string& filePath, Metadata& metadata) {
288 sendErrMsg(debugLogBoolStr, "TagLib is not available in this build.");
289 return false;
290}
291
298std::unordered_map<std::string, Metadata> TagLibParser::parseFromInode(ino_t inode, const std::string& directory) {
299 sendErrMsg(debugLogBoolStr, "TagLib is not available in this build.");
300 return {};
301}
302
303#endif // __EMSCRIPTEN__
304
305// Function to print metadata
306void printMetadata(const Metadata& metadata) {
307 std::cout << "Title: " << metadata.title << std::endl;
308 std::cout << "Artist: " << metadata.artist << std::endl;
309 std::cout << "Album: " << metadata.album << std::endl;
310 std::cout << "Genre: " << metadata.genre << std::endl;
311 std::cout << "Comment: " << metadata.comment << std::endl;
312 std::cout << "Year: " << metadata.year << std::endl;
313 std::cout << "Track: " << metadata.track << std::endl;
314 std::cout << "Disc Number: " << metadata.discNumber << std::endl;
315 std::cout << "Lyrics: " << std::endl << metadata.lyrics << std::endl;
316 std::cout << "+++++++++++++++++++++++++++" << std::endl;
317}
318
325auto extractThumbnail(const std::string& audioFilePath, const std::string& outputImagePath) -> bool {
326 // Determine the file type based on the extension
327 std::string extension = audioFilePath.substr(audioFilePath.find_last_of('.') + 1);
328
329 if (extension == "mp3") {
330 // Handle MP3 files (ID3v2 tag)
331 TagLib::MPEG::File mpegFile(audioFilePath.c_str());
332 if (!mpegFile.isValid()) {
333 std::cerr << "Error: Could not open MP3 file." << std::endl;
334 return false;
335 }
336
337 TagLib::ID3v2::Tag* id3v2Tag = mpegFile.ID3v2Tag();
338 if (!id3v2Tag) {
339 std::cerr << "Error: No ID3v2 tags found in the MP3 file." << std::endl;
340 return false;
341 }
342
343 const TagLib::ID3v2::FrameList& frameList = id3v2Tag->frameListMap()["APIC"];
344 if (frameList.isEmpty()) {
345 std::cerr << "Error: No embedded album art found in the MP3 file." << std::endl;
346 return false;
347 }
348
349 auto* apicFrame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(frameList.front());
350 if (!apicFrame) {
351 std::cerr << "Error: Failed to retrieve album art from MP3." << std::endl;
352 return false;
353 }
354
355 const auto& pictureData = apicFrame->picture();
356 std::ofstream outputFile(outputImagePath, std::ios::binary);
357 if (!outputFile) {
358 std::cerr << "Error: Could not create output image file." << std::endl;
359 return false;
360 }
361
362 outputFile.write(reinterpret_cast<const char*>(pictureData.data()), pictureData.size());
363 outputFile.close();
364 } else if (extension == "flac") {
365 // Handle FLAC files
366 TagLib::FLAC::File flacFile(audioFilePath.c_str(), true);
367 if (!flacFile.isValid()) {
368 std::cerr << "Error: Could not open FLAC file." << std::endl;
369 return false;
370 }
371
372 const TagLib::List<TagLib::FLAC::Picture*>& pictureList = flacFile.pictureList();
373 if (pictureList.isEmpty()) {
374 std::cerr << "Error: No album art found in the FLAC file." << std::endl;
375 return false;
376 }
377
378 const auto& pictureData = pictureList.front()->data();
379 std::ofstream outputFile(outputImagePath, std::ios::binary);
380 if (!outputFile) {
381 std::cerr << "Error: Could not create output image file." << std::endl;
382 return false;
383 }
384
385 outputFile.write(reinterpret_cast<const char*>(pictureData.data()), pictureData.size());
386 outputFile.close();
387 } else {
388 std::cerr << "Error: Unsupported file format. Only MP3 and FLAC are supported." << std::endl;
389 return false;
390 }
391
392 return true;
393}
auto parseFile(const std::string &filePath, Metadata &metadata) -> bool
Parse metadata from an audio file.
Definition taglib_parser.h:136
auto parseFromInode(ino_t inode, const std::string &directory) -> std::unordered_map< std::string, Metadata >
Parse metadata from files in a directory based on inode.
Definition taglib_parser.h:254
TagLibParser(const std::string &debugString)
Constructor for TagLibParser.
Definition taglib_parser.h:120
A structure to hold metadata information for a song.
Definition taglib_parser.h:39
unsigned int year
Definition taglib_parser.h:46
std::string filePath
Definition taglib_parser.h:51
unsigned int track
Definition taglib_parser.h:47
std::string title
Definition taglib_parser.h:40
std::string comment
Definition taglib_parser.h:44
std::string lyrics
Definition taglib_parser.h:49
float duration
Definition taglib_parser.h:52
unsigned int discNumber
Definition taglib_parser.h:48
void serialize(Archive &ar)
Serialization function for Metadata.
Definition taglib_parser.h:60
std::string fileType
Definition taglib_parser.h:45
std::string artist
Definition taglib_parser.h:41
int bitrate
Definition taglib_parser.h:53
std::unordered_map< std::string, std::string > additionalProperties
Definition taglib_parser.h:50
std::string album
Definition taglib_parser.h:42
std::string genre
Definition taglib_parser.h:43
void printMetadata(const Metadata &metadata)
Prints the metadata of a song to the console.
Definition taglib_parser.h:306
std::string debugLogBoolStr
Definition taglib_parser.h:113
int unknownArtistTracks
Definition taglib_parser.h:117
bool debugLogBool
Definition taglib_parser.h:115
auto extractThumbnail(const std::string &audioFilePath, const std::string &outputImagePath) -> bool
Extracts the thumbnail (album art) from an audio file and saves it to an image file....
Definition taglib_parser.h:325
void sendErrMsg(std::string debugLogBoolStr, std::string errMsg)
Sends an error message based on the debug log setting.
Definition taglib_parser.h:122