Skip to content

Commit

Permalink
feat: [MAJOR] CHANGELOG v9.4
Browse files Browse the repository at this point in the history
  • Loading branch information
nots1dd committed Jul 6, 2024
1 parent bc9453f commit d811b35
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 65 deletions.
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@ A fast and interactive console music player written in C++ for UNIX Systems.

### Beautiful TUI

* Ncurses library for making the beautiful TUI
* Ncurses library for making the beautiful and fast TUI

* Dynamic Scrolling, string search, clean status bar and more!

### Smooth Audio

* SFML for the audio API for smooth audio quality and features

* Modular Code for easy usage of all SFML features

### Song Controls

* Pretty much all basic and advanced song controls are implemented with the exception queues

* Songs with or without metadata are all displayed with equal song controls!

## Demo

[![Demo Video](https://github.com/nots1dd/Litemus/assets/140317709/cce9fc82-14f5-4983-bfa8-a5b714d20910)](https://github.com/nots1dd/Litemus/assets/140317709/cce9fc82-14f5-4983-bfa8-a5b714d20910)
Expand All @@ -34,7 +40,7 @@ A fast and interactive console music player written in C++ for UNIX Systems.

-> A UNIX based filesystem (x86_64 arch)

-> A directory with `mp3` files which have proper *embedded metadata* (random mp3 files will not work and may lead to some unexepcted results)
-> A directory with `mp3 / wav / flac` files which have proper *embedded metadata* (without metadata also works fine)

-> Dependencies: `cmake`, `ncurses`, `sfml`, `nlohmann-json` and `ffmpeg` (for building this project)

Expand All @@ -53,6 +59,13 @@ This has only been tried and tested on Arch Linux 6.9 kernel (x86-64)

This will generate a `./build/Litemus` executable. Run it to get the Litemus experience!

> **Tip:**
>
> A simpler way is to just use the `build.sh` script that does everything for you (including adding the `lmus` alias to your shell rc!)
>
> NOTE: Run `chmod +x build.sh` in order for it to execute. ALWAYS BE CAREFUL OF WHAT YOU ARE EXECUTING!!
>
## Installation

There is currently no means of installing this on any Linux distro other than building it from source.
Expand All @@ -69,10 +82,10 @@ If you encounter any issues with building Litemus from this repository in any di
## Future

- [ ] Have clean windows for showing session details, lyrics view
- [x] Have clean windows for showing session details, lyrics view
- [ ] Possibly have an audio visualizer (text based) integrated
- [ ] Make the code more modular and easy to read, modify
- [ ] Add other audio formats (.wav, .flac)
- [x] Make the code more modular and easy to read, modify
- [x] Add other audio formats (.wav, .flac)
- [ ] Have custom keybinds

## LICENSE
Expand Down
30 changes: 30 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/sh

# Check the shell
SHELL_NAME=$(basename "$SHELL")

# Determine the shell rc file
case "$SHELL_NAME" in
zsh)
RC_FILE="$HOME/.zshrc"
;;
bash)
RC_FILE="$HOME/.bashrc"
;;
ksh)
RC_FILE="$HOME/.kshrc"
;;
*)
echo "Unsupported shell: $SHELL_NAME"
exit 1
;;
esac

# Create build directory and build the project
cmake -S . -B build/
cmake --build build/

# Add alias to the appropriate shell rc file
echo "alias lmus=$(pwd)/build/Litemus" >> "$RC_FILE"

echo -e "\n[SUCCESS] Alias added to $RC_FILE. Please restart your terminal or source the rc file to apply the changes."
15 changes: 15 additions & 0 deletions changelog/v9_4.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Change type: MAJOR

Summary

-> Integration of more audio formats done (.wav, .flac integrated for now)

-> Lmus cache metadata extraction issues resolved

-> debug.log file created with efficient showcase of all fields of metadata extraction

-> Toggling of help window and session details window now is cleaner (focus toggles to songMenu), and pressing 1 focuses back to artistMenu with all artists

-> Changing the title of the artists window to help window when switching to help win and session details

-> Added a `build.sh` script that creates the `build/` directory, builds it, and finally addes the `lmus` alias to the respective shell rc
11 changes: 11 additions & 0 deletions headers/directoryUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,20 @@ inline void changeDirectory(const string& path) {
cout << GREEN << "[SUCCESS] Directory changed successfully." << "[ " << path << " ]" << RESET << endl;
}

// Function to check if a directory exists
inline bool directory_exists(const char *dir_path) {
struct stat sb;
return stat(dir_path, &sb) == 0 && S_ISDIR(sb.st_mode);
}

// Function to delete a directory and its contents
inline void deleteDirectory(const string& path) {
try {
filesystem::remove_all(path);
cout << GREEN << "[SUCCESS] Directory deleted successfully: " << path << RESET << endl;
} catch (const filesystem::filesystem_error& e) {
cerr << YELLOW << BOLD << "[ERROR] Failed to delete directory: " << path << " (" << e.what() << ")" << RESET << endl;
}
}

#endif // DIRECTORY_UTILS_HPP
2 changes: 1 addition & 1 deletion headers/keyHandlers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ void handleKeyEvent_tab(MENU* artistMenu, MENU* songMenu, WINDOW* artist_menu_wi
void handleKeyEvent_slash(MENU* artistMenu, MENU* songMenu, bool showingArtists);
void displayLyricsWindow(WINDOW *artist_menu_win, std::string& currentLyrics, std::string& currentSong, std::string& currentArtist, int menu_height, int menu_width, sf::Music &music, WINDOW *status_win, bool firstEnterPressed, bool showingLyrics, WINDOW *song_menu_win, MENU *songMenu, std::string& currentGenre, bool showingArtists);
void quitFunc(sf::Music& music, std::vector<std::string>& allArtists, std::vector<std::string>& songTitles, ITEM** artistItems, ITEM** songItems, MENU* artistMenu, MENU* songMenu);
void printSessionDetails(WINDOW* menu_win, const std::string& songsDirectory, int artistsSize, int songsSize);
void printSessionDetails(WINDOW* menu_win, const std::string& songsDirectory, const std::string& cacheDir, const std::string& cacheDebugFile, int artistsSize, int songsSize);

#endif
2 changes: 1 addition & 1 deletion headers/ncurses_helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

void ncursesSetup();
void ncursesWinControl(WINDOW* artist_menu_win, WINDOW* song_menu_win, WINDOW* status_win, WINDOW* title_win, const std::string& choice);
void ncursesWinLoop(MENU* artistMenu, MENU* songMenu, WINDOW* artist_menu_win, WINDOW* song_menu_win, WINDOW* status_win, WINDOW* title_win, const char* title_content);
void ncursesWinLoop(MENU* artistMenu, MENU* songMenu, WINDOW* artist_menu_win, WINDOW* song_menu_win, WINDOW* status_win, WINDOW* title_win, const char* title_content, bool showingArtMen);
void displayWindow(WINDOW* menu_win, const std::string window);
void updateStatusBar(WINDOW* status_win, const std::string& songName, const std::string& artistName, const std::string& songGenre, const sf::Music& music, bool firstEnterPressed, bool showingLyrics);
bool showExitConfirmation(WINDOW* parent_win);
Expand Down
9 changes: 4 additions & 5 deletions headers/src/keyHandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,15 +206,14 @@ void quitFunc(sf::Music& music, std::vector<std::string>& allArtists, std::vecto
free_menu(artistMenu);
}

void printSessionDetails(WINDOW* menu_win, const std::string& songsDirectory, int artistsSize, int songsSize) {
void printSessionDetails(WINDOW* menu_win, const std::string& songsDirectory, const std::string& cacheDir, const std::string& cacheDebugFile, int artistsSize, int songsSize) {
werase(menu_win);
mvwprintw(menu_win, 2, 10, "LiteMus Session Details");
std::string dirStr = "Directory: " + songsDirectory;
mvwprintw(menu_win, 4, 4, dirStr.c_str());
std::stringstream artStr;
artStr << "No of artists: " << artistsSize << std::endl << std::endl << " No of songs: " << songsSize;
artStr << "Directory: " << songsDirectory << std::endl << std::endl << " Cache Directory: " << cacheDir << std::endl << std::endl << " Debug File: " << cacheDebugFile << std::endl
<< std::endl << std::endl << " No of artists: " << artistsSize << std::endl << std::endl << " No of songs: " << songsSize;
std::string newartStr = artStr.str();
mvwprintw(menu_win, 6, 4, newartStr.c_str());
mvwprintw(menu_win, 4, 4, newartStr.c_str());
box(menu_win, 0, 0);
wrefresh(menu_win);
}
79 changes: 49 additions & 30 deletions headers/src/lmus_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const string YELLOW = "\033[33m";
const string BOLD = "\033[1m";

// FILE EXTENSION TO CACHE
const string extension = ".mp3";
const vector<string> extensions = {".mp3", ".wav", ".flac"};


struct SongMetadata {
string fileName;
Expand All @@ -30,21 +31,23 @@ struct SongMetadata {

// Function to get the list of inodes
vector<string> getInodes() {
const string output_cmd = "ls -i *" + extension + " | awk '{print $1}'";
string output = executeCommand(output_cmd);
vector<string> inodes;
stringstream ss(output);
string inode;

while (getline(ss, inode, '\n')) {
if (!inode.empty()) {
inodes.push_back(inode);
for (const string& ext : extensions) {
const string output_cmd = "ls -i *" + ext + " | awk '{print $1}'";
string output = executeCommand(output_cmd);
stringstream ss(output);
string inode;

while (getline(ss, inode, '\n')) {
if (!inode.empty()) {
inodes.push_back(inode);
}
}
}

return inodes;
}


// Function to get the file name from an inode
string getFileNameFromInode(const string& inode) {
string findCmd = "find . -type f -inum " + inode + " | sed 's|\\./||'";
Expand Down Expand Up @@ -72,7 +75,6 @@ string escapeSpecialCharacters(const string& fileName) {
return escapedFileName;
}

// Function to store metadata in JSON format
void storeMetadataJSON(const string& inode, const string& fileName, json& artistsArray, vector<SongMetadata>& songMetadata, const string debugFile) {
// Escape special characters in the filename
string escapedFileName = escapeSpecialCharacters(fileName);
Expand All @@ -85,41 +87,50 @@ void storeMetadataJSON(const string& inode, const string& fileName, json& artist

string artist = "Unknown_Artist";
string album = "Unknown_Album";
string title = "Unknown_Title";
string title = fileName;
int disc = 1;
int track = 1;
string genre = "Unknown_Genre";
string genre = "Unknown Genre";
string date = "Unknown_Date";
string lyrics = "";

// Log the metadata extraction process
ofstream logFile(debugFile, ios::trunc);
ofstream logFile(debugFile, ios::app);
if (logFile.is_open()) {
logFile << "Storing Metadata for: " << fileName << endl;
logFile << "Inode: " << inode << endl;

if (metadata["format"]["tags"].contains("artist")) {
artist = metadata["format"]["tags"]["artist"].get<string>();
if (metadata["format"]["tags"].contains("artist") || metadata["format"]["tags"].contains("ARTIST")) {
artist = metadata["format"]["tags"].contains("artist") ?
metadata["format"]["tags"]["artist"].get<string>() :
metadata["format"]["tags"]["ARTIST"].get<string>();
logFile << "Artist: extracted successfully +" << endl;
} else {
logFile << "Artist: extraction failed -" << endl;
}

if (metadata["format"]["tags"].contains("album")) {
album = metadata["format"]["tags"]["album"].get<string>();
if (metadata["format"]["tags"].contains("album") || metadata["format"]["tags"].contains("ALBUM")) {
album = metadata["format"]["tags"].contains("album") ?
metadata["format"]["tags"]["album"].get<string>() :
metadata["format"]["tags"]["ALBUM"].get<string>();
logFile << "Album: extracted successfully +" << endl;
} else {
logFile << "Album: extraction failed -" << endl;
}

if (metadata["format"]["tags"].contains("title")) {
title = metadata["format"]["tags"]["title"].get<string>();
if (metadata["format"]["tags"].contains("title") || metadata["format"]["tags"].contains("TITLE")) {
title = metadata["format"]["tags"].contains("title") ?
metadata["format"]["tags"]["title"].get<string>() :
metadata["format"]["tags"]["TITLE"].get<string>();
logFile << "Title: extracted successfully +" << endl;
} else {
logFile << "Title: extraction failed -" << endl;
}

if (metadata["format"]["tags"].contains("disc")) {
string discStr = metadata["format"]["tags"]["disc"].get<string>();
if (metadata["format"]["tags"].contains("disc") || metadata["format"]["tags"].contains("DISC")) {
string discStr = metadata["format"]["tags"].contains("disc") ?
metadata["format"]["tags"]["disc"].get<string>() :
metadata["format"]["tags"]["DISC"].get<string>();
try {
disc = std::stoi(discStr);
logFile << "Disc: extracted successfully +" << endl;
Expand All @@ -130,8 +141,10 @@ void storeMetadataJSON(const string& inode, const string& fileName, json& artist
logFile << "Disc: extraction failed -" << endl;
}

if (metadata["format"]["tags"].contains("track")) {
string trackStr = metadata["format"]["tags"]["track"].get<string>();
if (metadata["format"]["tags"].contains("track") || metadata["format"]["tags"].contains("TRACK")) {
string trackStr = metadata["format"]["tags"].contains("track") ?
metadata["format"]["tags"]["track"].get<string>() :
metadata["format"]["tags"]["TRACK"].get<string>();
try {
track = std::stoi(trackStr);
logFile << "Track: extracted successfully +" << endl;
Expand All @@ -142,22 +155,28 @@ void storeMetadataJSON(const string& inode, const string& fileName, json& artist
logFile << "Track: extraction failed -" << endl;
}

if (metadata["format"]["tags"].contains("genre")) {
genre = metadata["format"]["tags"]["genre"].get<string>();
if (metadata["format"]["tags"].contains("genre") || metadata["format"]["tags"].contains("GENRE")) {
genre = metadata["format"]["tags"].contains("genre") ?
metadata["format"]["tags"]["genre"].get<string>() :
metadata["format"]["tags"]["GENRE"].get<string>();
logFile << "Genre: extracted successfully +" << endl;
} else {
logFile << "Genre: extraction failed -" << endl;
}

if (metadata["format"]["tags"].contains("date")) {
date = metadata["format"]["tags"]["date"].get<string>();
if (metadata["format"]["tags"].contains("date") || metadata["format"]["tags"].contains("DATE")) {
date = metadata["format"]["tags"].contains("date") ?
metadata["format"]["tags"]["date"].get<string>() :
metadata["format"]["tags"]["DATE"].get<string>();
logFile << "Date: extracted successfully +" << endl;
} else {
logFile << "Date: extraction failed -" << endl;
}

if (metadata["format"]["tags"].contains("lyrics-XXX")) {
lyrics = metadata["format"]["tags"]["lyrics-XXX"].get<string>();
if (metadata["format"]["tags"].contains("lyrics-XXX") || metadata["format"]["tags"].contains("LYRICS-XXX")) {
lyrics = metadata["format"]["tags"].contains("lyrics-XXX") ?
metadata["format"]["tags"]["lyrics-XXX"].get<string>() :
metadata["format"]["tags"]["LYRICS-XXX"].get<string>();
logFile << "Lyrics: extracted successfully +" << endl;
} else {
logFile << "Lyrics: extraction failed -" << endl;
Expand Down
15 changes: 10 additions & 5 deletions headers/src/ncurses_helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ void displayWindow(WINDOW* menu_win, const std::string window) {
werase(menu_win);
// set_menu_fore(menu_win, A_NORMAL);
box(menu_win, 0, 0);
mvwprintw(menu_win, 0, 2, " HELP WIN: ");
wattron(menu_win, COLOR_PAIR(2));
mvwprintw(menu_win, 2, 2, "p - Toggle playback");
mvwprintw(menu_win, 3, 2, "Enter - Play selected song");
mvwprintw(menu_win, 4, 2, "Right Arrow - Seek forward 5s");
Expand All @@ -70,11 +70,17 @@ void displayWindow(WINDOW* menu_win, const std::string window) {
mvwprintw(menu_win, 16, 2, "m - Toggle mute");
mvwprintw(menu_win, 17, 2, "/ - String search in focused window");
mvwprintw(menu_win, 18, 2, "Tab - Toggle Focused Window");
wattroff(menu_win, COLOR_PAIR(2));

wattron(menu_win, COLOR_PAIR(3));
mvwprintw(menu_win, 20, 2, "2 - To show help menu");
mvwprintw(menu_win, 21, 2, "3 - Lyrics View");
mvwprintw(menu_win, 22, 2, "4 - Session Details");
init_pair(GREY_BACKGROUND_COLOR, COLOR_BLACK, COLOR_WHITE); // Grey background and black text for title
wattroff(menu_win, COLOR_PAIR(3));

wattron(menu_win, COLOR_PAIR(4) | A_BOLD);
mvwprintw(menu_win, 24, 2, "Press '1' to go back to the menu");
wattroff(menu_win, COLOR_PAIR(4) | A_BOLD);
wrefresh(menu_win);
}
else if (window == "warning") {
Expand All @@ -101,7 +107,7 @@ init_pair(GREY_BACKGROUND_COLOR, COLOR_BLACK, COLOR_WHITE); // Grey background
}
}

void ncursesWinLoop(MENU* artistMenu, MENU* songMenu, WINDOW* artist_menu_win, WINDOW* song_menu_win, WINDOW* status_win, WINDOW* title_win, const char* title_content) {
void ncursesWinLoop(MENU* artistMenu, MENU* songMenu, WINDOW* artist_menu_win, WINDOW* song_menu_win, WINDOW* status_win, WINDOW* title_win, const char* title_content, bool showingArtMen) {
ncursesWinControl(artist_menu_win, song_menu_win, status_win, title_win, "refresh");
box(artist_menu_win, 0, 0);
box(song_menu_win, 0, 0);
Expand All @@ -111,7 +117,7 @@ void ncursesWinLoop(MENU* artistMenu, MENU* songMenu, WINDOW* artist_menu_win, W
wbkgd(title_win, COLOR_PAIR(5) | A_BOLD);
wattron(title_win, COLOR_PAIR(6));
mvwprintw(title_win, 0, 1, title_content); // Replace with your title
mvwprintw(artist_menu_win, 0, 2, " Artists: ");
showingArtMen ? mvwprintw(artist_menu_win, 0, 2, " Artists: ") : mvwprintw(artist_menu_win, 0, 2, " Help Window: ");
mvwprintw(song_menu_win, 0, 2, " Songs: ");
wattroff(title_win, COLOR_PAIR(5));
wattroff(title_win, COLOR_PAIR(6));
Expand Down Expand Up @@ -223,7 +229,6 @@ bool showExitConfirmation(WINDOW* parent_win) {
WINDOW* confirm_win = newwin(height, width, start_y, start_x);
wattron(confirm_win, COLOR_PAIR(COLOR_BLUE));
box(confirm_win, 0, 0);
mvwprintw(confirm_win, 0, 2, " !!! ");
mvwprintw(confirm_win, 1, 20, "Exit LITEMUS?");
mvwprintw(confirm_win, 4, 10, "Yes (Y/Q) No (N/esc)");

Expand Down
Loading

0 comments on commit d811b35

Please sign in to comment.