From 26df10f8fb7e49b05bad08cfbd93bf21a0cebc14 Mon Sep 17 00:00:00 2001 From: Goldenkrew3000 Date: Sat, 14 Feb 2026 01:39:20 +1000 Subject: [PATCH] localMusicHandler can now recursively search and add music files and metadata to an sqlite database --- src/configHandler.h | 2 +- src/localMusicHandler.cpp | 219 +++++++++++++++++++++++++++++++++++++- src/localMusicHandler.hpp | 8 +- 3 files changed, 225 insertions(+), 4 deletions(-) diff --git a/src/configHandler.h b/src/configHandler.h index 71296c2..c3ef84a 100644 --- a/src/configHandler.h +++ b/src/configHandler.h @@ -67,7 +67,7 @@ typedef struct { char* lv2_reverb_filter_name; // LV2 Calf Reverb LV2 Name // Local Settings - char* local_rootdir; // Local Music Directory Root + char* local_rootdir; // Local Music Root Directory } configHandler_config_t; int configHandler_Read(configHandler_config_t** config); diff --git a/src/localMusicHandler.cpp b/src/localMusicHandler.cpp index d221991..ec18496 100644 --- a/src/localMusicHandler.cpp +++ b/src/localMusicHandler.cpp @@ -12,13 +12,228 @@ #include #include #include -#include "localMusicHandler.hpp" extern "C" { #include #include #include + #include "external/sqlite3/sqlite3.h" } +#include +#include +#include +#include "configHandler.h" +#include "localMusicHandler.hpp" + +/* + * I'm sorry for this messy and probably unreliable code + * This is the first time I have ever written something like this + * And why C++? Easy to store all of the temporary data without a ton of boilerplate + */ + +extern configHandler_config_t* configObj; +std::vector localMusicHandler_allFiles; +class localMusicHandler_AudioObject { + public: + std::string path; + std::string songTitle; + std::string albumTitle; + std::string artistTitle; + std::string track; + std::string totalTracks; + uint64_t filesize; + std::string uid; +}; +std::deque localMusicHandler_audioItems; +static sqlite3* sqlite_db = NULL; +static char* sqlite_errorMsg = NULL; void localMusicHandler_scan() { - // + printf("[LocalMusicHandler] Scanning local music directory recursively for files.\n"); + + // TODO clear all vectors + + // Scan music directory (defined in config file) for all files + localMusicHandler_scanDirectory(configObj->local_rootdir); + printf("[LocalMusicHandler] Found %d files.\n", localMusicHandler_allFiles.size()); + + // Scan each file to find only music files, and pull ID3 tags + for (int i = 0; i < localMusicHandler_allFiles.size(); i++) { + localMusicHandler_scanFile(i); + } + printf("[LocalMusicHandler] Found %d songs.\n", localMusicHandler_audioItems.size()); + + // Generate a unique ID for each music file + printf("[LocalMusicHandler] Generating unique IDs for each song.\n"); + for (int i = 0; i < localMusicHandler_audioItems.size(); i++) { + // TODO: Technically there is a VERY SMALL chance that 2 id's repeat in the DB + // Figure out what to do about that later + localMusicHandler_generateUid(i); + } + + // Store in database + + + localMusicHandler_initDatabase(); + + for (int i = 0; i < localMusicHandler_audioItems.size(); i++) { + localMusicHandler_moveSongsToDatabase(i); + } + //localMusicHandler_moveSongsToDatabase(0); + printf("DOINEDONEODNE\n"); +} + +void localMusicHandler_scanDirectory(char* directory) { + struct dirent* dp; + DIR* dir = opendir(directory); + char path[1000]; // TODO Prevent potential buffer overflow + + while ((dp = readdir(dir)) != NULL) { + if (strcmp(dp->d_name, ".") != 0 && strcmp(dp->d_name, "..") != 0) { + sprintf(path, "%s/%s", directory, dp->d_name); + + struct stat statbuf; + stat(path, &statbuf); + + if (S_ISDIR(statbuf.st_mode)) { + localMusicHandler_scanDirectory(path); + } else if (S_ISREG(statbuf.st_mode)) { + localMusicHandler_allFiles.push_back(path); + } + } + } + + closedir(dir); +} + +void localMusicHandler_scanFile(int idx) { + AVFormatContext* ctx = NULL; + AVDictionaryEntry* tag = NULL; + static int rc = -1; + + rc = avformat_open_input(&ctx, localMusicHandler_allFiles[idx].c_str(), NULL, NULL); + if (rc < 0) { + printf("[LocalMusicHandler] avformat_open_input() failed on idx %d (%s).\n", idx, localMusicHandler_allFiles[idx].c_str()); + return; + } + + // Ignore files that aren't audio + if ( + strcmp(ctx->iformat->name, "lrc") == 0 || // .lrc files + strcmp(ctx->iformat->name, "image2") == 0 // Pictures + ) { + avformat_close_input(&ctx); + return; + } + + localMusicHandler_AudioObject audioObject; + audioObject.path = localMusicHandler_allFiles[idx].c_str(); + + // Get file size (Using libav for this since the file is already opened using it) + audioObject.filesize = 0; // If the following fetch fails, set it to a known value beforehand + if (ctx->pb) { + uint64_t fsize = avio_size(ctx->pb); + if (fsize > 0) { + audioObject.filesize = fsize; + } + } + + // Set all strings to known good values before fetching tags that possible don't exist + // NOTE: Honestly don't know if C++ does this by default, but I am not trusting it either way + audioObject.songTitle = ""; + audioObject.albumTitle = ""; + audioObject.artistTitle = ""; + audioObject.track = ""; + audioObject.totalTracks = ""; + + while ((tag = av_dict_get(ctx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + if (strcmp(tag->key, "title") == 0) { + audioObject.songTitle = tag->value; + } else if (strcmp(tag->key, "album") == 0) { + audioObject.albumTitle = tag->value; + } else if (strcmp(tag->key, "artist") == 0) { + audioObject.artistTitle = tag->value; + } else if (strcmp(tag->key, "track") == 0) { + audioObject.track = tag->value; + } else if (strcmp(tag->key, "totaltracks") == 0) { + audioObject.totalTracks = tag->value; + } + } + + localMusicHandler_audioItems.push_back(audioObject); + avformat_close_input(&ctx); +} + +void localMusicHandler_generateUid(int idx) { + // TODO: Add other operating support here, such as in libopensubsonic/crypto.c + char uuidBytes[20]; + char uuidString[40]; + for (int i = 0; i < 20; i++) { + uuidBytes[i] = arc4random() & 0xFF; + } + snprintf(uuidString, 40, "%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x%.2x", + uuidBytes[0], uuidBytes[1], uuidBytes[2], uuidBytes[3], uuidBytes[4], + uuidBytes[5], uuidBytes[6], uuidBytes[7], uuidBytes[8], uuidBytes[9], + uuidBytes[10], uuidBytes[11], uuidBytes[12], uuidBytes[13], uuidBytes[14], + uuidBytes[15], uuidBytes[16], uuidBytes[17], uuidBytes[18], uuidBytes[19] + ); + localMusicHandler_audioItems[idx].uid = uuidString; +} + +void localMusicHandler_initDatabase() { + // TOOD cleanup + static int rc = 0; + char* dbPath = NULL; + rc = asprintf(&dbPath, "%s/.config/ossp/local.db", getenv("HOME")); + if (rc == -1) { printf("[LocalMusicHandler] asprintf() failed.\n"); return; } + + struct stat st; + if (stat(dbPath, &st) == 0) { + printf("[LocalMusicHandler] Database found, is %ld bytes.\n", st.st_size); + } else { + printf("[LocalMusicHandler] Database does not exist, creating.\n"); + } + + rc = sqlite3_open(dbPath, &sqlite_db); + if (rc) { + printf("[LocalMusicHandler] Could not create database: %s\n", sqlite3_errmsg(sqlite_db)); + } else { + printf("[LocalMusicHandler] Created database.\n"); + } + + const char* sqlQuery = "CREATE TABLE local_songs(uid TEXT, songTitle TEXT, albumTitle TEXT, artistTitle TEXT, track TEXT, totalTracks TEXT, path TEXT, filesize INT)"; + rc = sqlite3_exec(sqlite_db, sqlQuery, NULL, 0, &sqlite_errorMsg); + if (rc != SQLITE_OK) { + printf("[LocalMusicHandler] Could not make table: %s\n", sqlite_errorMsg); + sqlite3_free(sqlite_errorMsg); + return; // TODO actually handle error + } + printf("[LocalMusicHandler] Made table.\n"); + + sqlite3_close(sqlite_db); +} + +void localMusicHandler_moveSongsToDatabase(int idx) { + const char* sqlQuery = "INSERT INTO local_songs VALUES(?, ?, ?, ?, ?, ?, ?, ?)"; + + sqlite3_stmt *stmt; + if (sqlite3_prepare_v2(sqlite_db, sqlQuery, -1, &stmt, NULL) != SQLITE_OK) { + fprintf(stderr, "Prepare error: %s\n", sqlite3_errmsg(sqlite_db)); + return; + } + + sqlite3_bind_text(stmt, 1, localMusicHandler_audioItems[idx].uid.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 2, localMusicHandler_audioItems[idx].songTitle.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 3, localMusicHandler_audioItems[idx].albumTitle.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 4, localMusicHandler_audioItems[idx].artistTitle.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 5, localMusicHandler_audioItems[idx].track.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 6, localMusicHandler_audioItems[idx].totalTracks.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_text(stmt, 7, localMusicHandler_audioItems[idx].path.c_str(), -1, SQLITE_TRANSIENT); + sqlite3_bind_int64(stmt, 8, localMusicHandler_audioItems[idx].filesize); + + if (sqlite3_step(stmt) != SQLITE_DONE) { + fprintf(stderr, "Execution error: %s\n", sqlite3_errmsg(sqlite_db)); + } + + sqlite3_finalize(stmt); + } diff --git a/src/localMusicHandler.hpp b/src/localMusicHandler.hpp index 07e1154..ca092d9 100644 --- a/src/localMusicHandler.hpp +++ b/src/localMusicHandler.hpp @@ -11,7 +11,13 @@ extern "C" { #endif // __cplusplus -// +void localMusicHandler_scan(); +void localMusicHandler_scanDirectory(char* directory); +void localMusicHandler_scanFile(int idx); +void localMusicHandler_generateUid(int idx); + +void localMusicHandler_initDatabase(); +void localMusicHandler_moveSongsToDatabase(int idx); #ifdef __cplusplus }