inLimbo
TUI Music Player that keeps you in Limbo.
 
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
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...