inLimbo
TUI Music Player that keeps you in Limbo.
 
Loading...
Searching...
No Matches
songmap.hpp
Go to the documentation of this file.
1
20
21#pragma once
22
23#include "taglib_parser.h"
25#include <cereal/archives/binary.hpp>
26#include <cereal/types/map.hpp>
27#include <cereal/types/string.hpp>
28#include <cereal/types/vector.hpp>
29#include <fstream>
30#include <iostream>
31#include <map>
32#include <optional>
33#include <set>
34#include <string>
35#include <vector>
36
45struct Song
46{
47 unsigned int inode;
49
58 Song(unsigned int inode, const Metadata& metadata) : inode(inode), metadata(metadata) {}
59
63 Song() : inode(0), metadata() {}
64
73 template <class Archive> void serialize(Archive& ar) { ar(inode, metadata); }
74};
75
90{
91private:
100 std::map<std::string, std::map<std::string, std::map<unsigned int, std::map<unsigned int, Song>>>>
101 tree;
102
103public:
112 void addSong(const Song& song)
113 {
114 tree[song.metadata.artist][song.metadata.album][song.metadata.discNumber][song.metadata.track] =
115 song;
116 }
117
125 void display() const
126 {
127 int totalArtists = 0, totalAlbums = 0, totalDiscs = 0, totalSongs = 0;
128 std::set<std::string> uniqueGenres; // To count unique genres
129
130 std::cout << "\n=== Music Library Overview ===\n";
131 std::cout << "─────────────────────────────────────────────────────────────────────────────\n";
132
133 for (const auto& artistPair : tree)
134 {
135 totalArtists++;
136 std::cout << "\n🎤 Artist: " << artistPair.first << "\n";
137 for (const auto& albumPair : artistPair.second)
138 {
139 totalAlbums++;
140 std::cout << " ├─── 📀 Album: " << albumPair.first << "\n";
141 for (const auto& discPair : albumPair.second)
142 {
143 totalDiscs++;
144 std::cout << " │ 💿 Disc " << discPair.first << "\n";
145 for (const auto& trackPair : discPair.second)
146 {
147 totalSongs++;
148 const auto& song = trackPair.second;
149 uniqueGenres.insert(song.metadata.genre);
150 std::cout << " │ │ Track " << std::setw(2) << trackPair.first << ": "
151 << song.metadata.title << "\n";
152 }
153 }
154 }
155 }
156
157 std::cout << "\n=== Library Summary ===\n";
158 std::cout << "─────────────────────────────────────────────────────────────────────────────\n";
159 std::cout << "🎤 Total Artists: " << totalArtists << "\n";
160 std::cout << "📀 Total Albums: " << totalAlbums << "\n";
161 std::cout << "💿 Total Discs: " << totalDiscs << "\n";
162 std::cout << "🎵 Total Songs: " << totalSongs << "\n";
163 std::cout << "🎼 Unique Genres: " << uniqueGenres.size() << "\n";
164 std::cout << "=========================\n";
165 }
166
172 void printAllArtists() const
173 {
174 std::cout << "\n=== Artists List ===\n";
175 for (const auto& artistPair : tree)
176 {
177 std::cout << "- " << artistPair.first << "\n";
178 }
179 std::cout << "=====================\n";
180 }
181
189 void printSongs(const std::vector<Song>& songs)
190 {
191 if (songs.empty())
192 {
193 std::cout << "No songs found.\n";
194 return;
195 }
196
197 // Group songs by album
198 std::map<std::string, std::vector<Song>> albums;
199 for (const auto& song : songs)
200 {
201 albums[song.metadata.album].push_back(song);
202 }
203
204 std::cout << "\n" << "\033[1mSongs List\033[0m:\n";
205 std::cout << "─────────────────────────────────────\n";
206
207 for (const auto& albumPair : albums)
208 {
209 const auto& album = albumPair.first;
210 const auto& albumSongs = albumPair.second;
211
212 std::cout << "├─── 📀 Album: " << album << "\n";
213 std::cout << "└─ Total Songs: " << albumSongs.size() << "\n";
214
215 for (const auto& song : albumSongs)
216 {
217 std::cout << " │ Track: " << song.metadata.title << "\n";
218 }
219
220 std::cout << "─────────────────────────────────────\n";
221 }
222 }
223
233 auto getSongsByArtist(const std::string& artist)
234 {
235 std::vector<Song> result;
236 auto artistIt = tree.find(artist);
237 if (artistIt != tree.end())
238 {
239 for (const auto& albumPair : artistIt->second)
240 {
241 for (const auto& discPair : albumPair.second)
242 {
243 for (const auto& trackPair : discPair.second)
244 {
245 result.push_back(trackPair.second);
246 }
247 }
248 }
249 }
250 printSongs(result);
251 return result;
252 }
253
263 [[nodiscard]] auto getSongsByAlbum(const std::string& artist, const std::string& album) const
264 {
265 std::vector<Song> result;
266 auto artistIt = tree.find(artist);
267 if (artistIt != tree.end())
268 {
269 auto albumIt = artistIt->second.find(album);
270 if (albumIt != artistIt->second.end())
271 {
272 for (const auto& discPair : albumIt->second)
273 {
274 for (const auto& trackPair : discPair.second)
275 {
276 result.push_back(trackPair.second);
277 }
278 }
279 }
280 }
281 return result;
282 }
283
290 {
291 std::map<std::string, std::vector<Song>> genreMap;
292
293 // Populate genreMap with songs grouped by genre
294 for (const auto& artistPair : tree)
295 {
296 for (const auto& albumPair : artistPair.second)
297 {
298 for (const auto& discPair : albumPair.second)
299 {
300 for (const auto& trackPair : discPair.second)
301 {
302 genreMap[trackPair.second.metadata.genre].push_back(trackPair.second);
303 }
304 }
305 }
306 }
307
308 std::cout << "\n=== Songs Grouped by Genre ===\n";
309 std::cout << "──────────────────────────────────────\n";
310
311 // Print each genre and its corresponding songs
312 for (const auto& genrePair : genreMap)
313 {
314 const auto& genre = genrePair.first;
315 const auto& songs = genrePair.second;
316
317 std::cout << "\n🎶 Genre: " << genre << "\n";
318 std::cout << "──────────────────────────────────────\n";
319
320 for (const auto& song : songs)
321 {
322 std::cout << "├─── " << song.metadata.title << " by " << song.metadata.artist
323 << " (Album: " << song.metadata.album << ")\n";
324 }
325
326 std::cout << "──────────────────────────────────────\n";
327 }
328 }
329
337 [[nodiscard]] auto returnSongMap() const { return tree; }
338
347 template <class Archive> void serialize(Archive& ar) { ar(tree); }
348
357 void saveToFile(const std::string& filename) const
358 {
359 std::ofstream file(filename, std::ios::binary);
360 if (!file)
361 {
362 throw std::runtime_error("Failed to open file for saving.");
363 }
364 cereal::BinaryOutputArchive archive(file);
365 archive(*this); // Serialize the SongTree
366 }
367
376 void loadFromFile(const std::string& filename)
377 {
378 std::ifstream file(filename, std::ios::binary);
379 if (!file)
380 {
381 throw std::runtime_error("Failed to open file for loading.");
382 }
383 cereal::BinaryInputArchive archive(file);
384 archive(*this); // Deserialize the SongTree
385 }
386
396 void printSongInfo(const std::string& input)
397 {
398 bool isFilePath = input.find('/') != std::string::npos || input.find('\\') != std::string::npos;
399
400 std::optional<Song> foundSong;
401
402 if (isFilePath)
403 {
404 // If input is a file path, retrieve its metadata using inode
405 std::cout << std::endl << "> Taking argument as a possible audio file path..." << std::endl;
406 struct stat fileStat;
407 if (stat(input.c_str(), &fileStat) == 0)
408 {
409 unsigned int inode = fileStat.st_ino;
410 for (const auto& artistPair : tree)
411 {
412 for (const auto& albumPair : artistPair.second)
413 {
414 for (const auto& discPair : albumPair.second)
415 {
416 for (const auto& trackPair : discPair.second)
417 {
418 if (trackPair.second.inode == inode)
419 {
420 foundSong = trackPair.second;
421 break;
422 }
423 }
424 }
425 }
426 }
427 }
428 }
429 else
430 {
431 // If input is a song name, search by title
432 for (const auto& artistPair : tree)
433 {
434 for (const auto& albumPair : artistPair.second)
435 {
436 for (const auto& discPair : albumPair.second)
437 {
438 for (const auto& trackPair : discPair.second)
439 {
440 if (levenshteinDistance(trackPair.second.metadata.title, input) < 3) // Recommended limit of correction (else it will go bonkers)
441 {
442 foundSong = trackPair.second;
443 break;
444 }
445 }
446 }
447 }
448 }
449 }
450
451 if (foundSong)
452 {
453 const auto& song = *foundSong;
454 std::cout << "\n🎵 Song Information:\n";
455 std::cout << "──────────────────────────────────────\n";
456 std::cout << "Title : " << song.metadata.title << "\n";
457 std::cout << "Artist : " << song.metadata.artist << "\n";
458 std::cout << "Album : " << song.metadata.album << "\n";
459 std::cout << "Disc : " << song.metadata.discNumber << "\n";
460 std::cout << "Track : " << song.metadata.track << "\n";
461 std::cout << "Genre : " << song.metadata.genre << "\n";
462 std::cout << "Inode : " << song.inode << "\n";
463
464 if (!song.metadata.additionalProperties.empty())
465 {
466 std::cout << "Additional Properties:\n";
467 for (const auto& prop : song.metadata.additionalProperties)
468 {
469 if (prop.first == "LYRICS")
470 {
471 std::cout << "\n📜 Lyrics:\n";
472 std::cout << "──────────────────────────────────────\n";
473
474 // Wrap and format the lyrics
475 const std::string& lyrics = prop.second;
476 size_t lineLength = 80;
477 size_t start = 0;
478 while (start < lyrics.size())
479 {
480 size_t end = start + lineLength;
481 if (end > lyrics.size()) end = lyrics.size();
482 std::cout << lyrics.substr(start, end - start) << "\n";
483 start = end;
484 }
485 std::cout << "──────────────────────────────────────\n";
486 }
487 else
488 {
489 std::cout << " - " << prop.first << " : " << prop.second << "\n";
490 }
491 }
492 }
493 else
494 {
495 std::cout << "No additional properties found!" << std::endl;
496 }
497 std::cout << "──────────────────────────────────────\n";
498 }
499 else
500 {
501 std::cout << "⚠️ Song not found: " << input << "\n";
502 }
503 }
504};
Represents a hierarchical tree structure to store songs by artist, album, disc number,...
Definition songmap.hpp:90
auto returnSongMap() const
Returns the internal song map.
Definition songmap.hpp:337
void printSongInfo(const std::string &input)
Prints metadata and additional properties of a song.
Definition songmap.hpp:396
auto getSongsByArtist(const std::string &artist)
Retrieves all songs by a specific artist.
Definition songmap.hpp:233
void printSongs(const std::vector< Song > &songs)
Prints the songs by album.
Definition songmap.hpp:189
void serialize(Archive &ar)
Serializes the SongTree object.
Definition songmap.hpp:347
void display() const
Displays all songs in the tree hierarchically.
Definition songmap.hpp:125
void addSong(const Song &song)
Adds a song to the tree.
Definition songmap.hpp:112
void saveToFile(const std::string &filename) const
Saves the SongTree to a file.
Definition songmap.hpp:357
auto getSongsByAlbum(const std::string &artist, const std::string &album) const
Retrieves all songs from a specific album by a given artist.
Definition songmap.hpp:263
void loadFromFile(const std::string &filename)
Loads a SongTree from a file.
Definition songmap.hpp:376
void getSongsByGenreAndPrint() const
Groups and prints songs by genre.
Definition songmap.hpp:289
void printAllArtists() const
Prints all the artists in the song tree.
Definition songmap.hpp:172
auto levenshteinDistance(const std::string &s1, const std::string &s2) -> size_t
Computes the Levenshtein distance between two strings.
Definition levenshtein.hpp:36
A structure to hold metadata information for a song.
Definition taglib_parser.h:39
unsigned int track
Definition taglib_parser.h:47
unsigned int discNumber
Definition taglib_parser.h:48
std::string artist
Definition taglib_parser.h:41
std::string album
Definition taglib_parser.h:42
Represents a song with associated metadata and inode.
Definition songmap.hpp:46
Song()
Default constructor for a Song, initializing with default values.
Definition songmap.hpp:63
unsigned int inode
Definition songmap.hpp:47
Metadata metadata
Definition songmap.hpp:48
void serialize(Archive &ar)
Serializes the Song object.
Definition songmap.hpp:73
Song(unsigned int inode, const Metadata &metadata)
Constructs a Song with the given inode and metadata.
Definition songmap.hpp:58
A header file for the TagLibParser class and Metadata structure, used to parse metadata from audio fi...