Implement start of Playback Engine, refactored most classes to leverage KotoCartographer, etc.

This commit is contained in:
Joshua Strobl 2021-03-23 19:50:09 +02:00
parent a77efdb0aa
commit 05d90afc58
25 changed files with 1015 additions and 304 deletions

View file

@ -65,7 +65,7 @@ void koto_cartographer_add_playlist(KotoCartographer *self, KotoPlaylist *playli
g_object_get(playlist, "uuid", &playlist_uuid, NULL); g_object_get(playlist, "uuid", &playlist_uuid, NULL);
if ((playlist_uuid != NULL) && (!koto_cartographer_has_playlist_by_uuid(self, playlist_uuid))) { // Don't have this album if ((playlist_uuid != NULL) && (!koto_cartographer_has_playlist_by_uuid(self, playlist_uuid))) { // Don't have this album
g_hash_table_replace(self->artists, playlist_uuid, playlist); g_hash_table_replace(self->playlists, playlist_uuid, playlist);
} }
} }
@ -74,7 +74,7 @@ void koto_cartographer_add_track(KotoCartographer *self, KotoIndexedTrack *track
g_object_get(track, "uuid", &track_uuid, NULL); g_object_get(track, "uuid", &track_uuid, NULL);
if ((track_uuid != NULL) && (!koto_cartographer_has_playlist_by_uuid(self, track_uuid))) { // Don't have this album if ((track_uuid != NULL) && (!koto_cartographer_has_playlist_by_uuid(self, track_uuid))) { // Don't have this album
g_hash_table_replace(self->artists, track_uuid, track); g_hash_table_replace(self->tracks, track_uuid, track);
} }
} }

View file

@ -37,7 +37,9 @@ void close_db() {
int create_db_tables() { int create_db_tables() {
char *tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, path string, type int, name string, art_path string);" char *tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, path string, type int, name string, art_path string);"
"CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, path string, artist_id string, name string, art_path string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" "CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, path string, artist_id string, name string, art_path string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, path string, type int, artist_id string, album_id string, file_name string, name string, disc int, position int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"; "CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, path string, type int, artist_id string, album_id string, file_name string, name string, disc int, position int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string);"
"CREATE TABLE IF NOT EXISTS playlist_tracks(playlist_id string PRIMARY KEY, track_id string, position int, current int, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);";
gchar *create_tables_errmsg = NULL; gchar *create_tables_errmsg = NULL;
int rc = sqlite3_exec(koto_db, tables_creation_queries, 0,0, &create_tables_errmsg); int rc = sqlite3_exec(koto_db, tables_creation_queries, 0,0, &create_tables_errmsg);

View file

@ -19,23 +19,25 @@
#include <magic.h> #include <magic.h>
#include <sqlite3.h> #include <sqlite3.h>
#include <stdio.h> #include <stdio.h>
#include "../db/cartographer.h"
#include "../playlist/current.h" #include "../playlist/current.h"
#include "../playlist/playlist.h" #include "../playlist/playlist.h"
#include "structs.h" #include "structs.h"
#include "koto-utils.h" #include "koto-utils.h"
extern KotoCartographer *koto_maps;
extern KotoCurrentPlaylist *current_playlist; extern KotoCurrentPlaylist *current_playlist;
extern sqlite3 *koto_db; extern sqlite3 *koto_db;
struct _KotoIndexedAlbum { struct _KotoIndexedAlbum {
GObject parent_instance; GObject parent_instance;
KotoIndexedArtist *artist;
gchar *uuid; gchar *uuid;
gchar *path; gchar *path;
gchar *name; gchar *name;
gchar *art_path; gchar *art_path;
GHashTable *tracks; gchar *artist_uuid;
GList *tracks;
gboolean has_album_art; gboolean has_album_art;
gboolean do_initial_index; gboolean do_initial_index;
@ -45,12 +47,12 @@ G_DEFINE_TYPE(KotoIndexedAlbum, koto_indexed_album, G_TYPE_OBJECT);
enum { enum {
PROP_0, PROP_0,
PROP_ARTIST,
PROP_UUID, PROP_UUID,
PROP_DO_INITIAL_INDEX, PROP_DO_INITIAL_INDEX,
PROP_PATH, PROP_PATH,
PROP_ALBUM_NAME, PROP_ALBUM_NAME,
PROP_ART_PATH, PROP_ART_PATH,
PROP_ARTIST_UUID,
N_PROPERTIES N_PROPERTIES
}; };
@ -65,14 +67,6 @@ static void koto_indexed_album_class_init(KotoIndexedAlbumClass *c) {
gobject_class->set_property = koto_indexed_album_set_property; gobject_class->set_property = koto_indexed_album_set_property;
gobject_class->get_property = koto_indexed_album_get_property; gobject_class->get_property = koto_indexed_album_get_property;
props[PROP_ARTIST] = g_param_spec_object(
"artist",
"Artist associated with Album",
"Artist associated with Album",
KOTO_TYPE_INDEXED_ARTIST,
G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
props[PROP_UUID] = g_param_spec_string( props[PROP_UUID] = g_param_spec_string(
"uuid", "uuid",
"UUID to Album in database", "UUID to Album in database",
@ -113,37 +107,36 @@ static void koto_indexed_album_class_init(KotoIndexedAlbumClass *c) {
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
); );
props[PROP_ARTIST_UUID] = g_param_spec_string(
"artist-uuid",
"UUID of Artist associated with Album",
"UUID of Artist associated with Album",
NULL,
G_PARAM_CONSTRUCT_ONLY|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props); g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
} }
static void koto_indexed_album_init(KotoIndexedAlbum *self) { static void koto_indexed_album_init(KotoIndexedAlbum *self) {
self->has_album_art = FALSE; self->has_album_art = FALSE;
self->tracks = NULL;
} }
void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedTrack *track) { void koto_indexed_album_add_track(KotoIndexedAlbum *self, KotoIndexedTrack *track) {
if (track == NULL) { // Not a file if (track == NULL) { // Not a file
return; return;
} }
if (self->tracks == NULL) { // No HashTable yet gchar *track_uuid;
self->tracks = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable g_object_get(track, "uuid", &track_uuid, NULL);
if (g_list_index(self->tracks, track_uuid) == -1) {
self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_indexed_album_sort_tracks, NULL);
} }
gchar *track_name;
g_object_get(track, "parsed-name", &track_name, NULL);
if (g_hash_table_contains(self->tracks, track_name)) { // If we have this file already
g_free(track_name);
return;
}
g_hash_table_insert(self->tracks, track_name, track); // Add the file
} }
void koto_indexed_album_commit(KotoIndexedAlbum *self) { void koto_indexed_album_commit(KotoIndexedAlbum *self) {
gchar *artist_uuid;
g_object_get(self->artist, "uuid", &artist_uuid, NULL); // Get the artist UUID
if (self->art_path == NULL) { // If art_path isn't defined when committing if (self->art_path == NULL) { // If art_path isn't defined when committing
koto_indexed_album_set_album_art(self, ""); // Set to an empty string koto_indexed_album_set_album_art(self, ""); // Set to an empty string
} }
@ -154,7 +147,7 @@ void koto_indexed_album_commit(KotoIndexedAlbum *self) {
"ON CONFLICT(id) DO UPDATE SET path=excluded.path, name=excluded.name, art_path=excluded.art_path;", "ON CONFLICT(id) DO UPDATE SET path=excluded.path, name=excluded.name, art_path=excluded.art_path;",
self->uuid, self->uuid,
self->path, self->path,
artist_uuid, self->artist_uuid,
self->name, self->name,
self->art_path self->art_path
); );
@ -314,7 +307,7 @@ void koto_indexed_album_find_tracks(KotoIndexedAlbum *self, magic_t magic_cookie
KotoIndexedTrack *track = koto_indexed_track_new(self, full_path, cd); KotoIndexedTrack *track = koto_indexed_track_new(self, full_path, cd);
if (track != NULL) { // Is a file if (track != NULL) { // Is a file
koto_indexed_album_add_file(self, track); // Add our file koto_indexed_album_add_track(self, track); // Add our file
} }
} }
@ -326,9 +319,6 @@ static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue
KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj); KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj);
switch (prop_id) { switch (prop_id) {
case PROP_ARTIST:
g_value_set_object(val, self->artist);
break;
case PROP_UUID: case PROP_UUID:
g_value_set_string(val, self->uuid); g_value_set_string(val, self->uuid);
break; break;
@ -344,6 +334,38 @@ static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue
case PROP_ART_PATH: case PROP_ART_PATH:
g_value_set_string(val, koto_indexed_album_get_album_art(self)); g_value_set_string(val, koto_indexed_album_get_album_art(self));
break; break;
case PROP_ARTIST_UUID:
g_value_set_string(val, self->artist_uuid);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){
KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj);
switch (prop_id) {
case PROP_UUID:
self->uuid = g_strdup(g_value_get_string(val));
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]);
break;
case PROP_DO_INITIAL_INDEX:
self->do_initial_index = g_value_get_boolean(val);
break;
case PROP_PATH: // Path to the album
koto_indexed_album_update_path(self, g_value_get_string(val));
break;
case PROP_ALBUM_NAME: // Name of album
koto_indexed_album_set_album_name(self, g_value_get_string(val));
break;
case PROP_ART_PATH: // Path to art
koto_indexed_album_set_album_art(self, g_value_get_string(val));
break;
case PROP_ARTIST_UUID:
koto_indexed_album_set_artist_uuid(self, g_value_get_string(val));
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break; break;
@ -351,15 +373,11 @@ static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue
} }
gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self) { gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self) {
return g_strdup((self->has_album_art) ? self->art_path : ""); return g_strdup((self->has_album_art && (self->art_path != NULL) && (strcmp(self->art_path, "") != 0)) ? self->art_path : "");
} }
GList* koto_indexed_album_get_files(KotoIndexedAlbum *self) { GList* koto_indexed_album_get_tracks(KotoIndexedAlbum *self) {
if (self->tracks == NULL) { // No HashTable yet return self->tracks; // Return
self->tracks = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
}
return g_hash_table_get_values(self->tracks);
} }
void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art) { void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art) {
@ -381,22 +399,9 @@ void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedTrack *tr
return; return;
} }
if (self->tracks == NULL) { // No HashTable yet gchar *track_uuid;
self->tracks = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable g_object_get(track, "parsed-name", &track_uuid, NULL);
} self->tracks = g_list_remove(self->tracks, track_uuid);
gchar *track_name;
g_object_get(track, "parsed-name", &track_name, NULL);
g_hash_table_remove(self->tracks, track_name);
}
void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *track_name) {
if (track_name == NULL) {
return;
}
KotoIndexedTrack *track = g_hash_table_lookup(self->tracks, track_name); // Get the files
koto_indexed_album_remove_file(self, track);
} }
void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name) { void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name) {
@ -411,49 +416,77 @@ void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *albu
self->name = g_strdup(album_name); self->name = g_strdup(album_name);
} }
void koto_indexed_album_set_artist_uuid(KotoIndexedAlbum *self, const gchar *artist_uuid) {
if (artist_uuid == NULL) {
return;
}
if (self->artist_uuid != NULL) {
g_free(self->artist_uuid);
}
self->artist_uuid = g_strdup(artist_uuid);
}
void koto_indexed_album_set_as_current_playlist(KotoIndexedAlbum *self) { void koto_indexed_album_set_as_current_playlist(KotoIndexedAlbum *self) {
if (self->tracks == NULL) { // No files to add to the playlist if (self->tracks == NULL) { // No files to add to the playlist
return; return;
} }
KotoPlaylist *new_album_playlist = koto_playlist_new(); // Create a new playlist KotoPlaylist *new_album_playlist = koto_playlist_new(); // Create a new playlist
g_object_set(new_album_playlist, "ephemeral", TRUE, NULL); // Set as ephemeral / temporary
GList *tracks_list_uuids = g_hash_table_get_keys(self->tracks); // Get the UUIDs GList *t;
for (guint i = 0; i < g_list_length(tracks_list_uuids); i++) { // For each of the tracks for (t = self->tracks; t != NULL; t = t->next) { // For each of the tracks
koto_playlist_add_track_by_uuid(new_album_playlist, (gchar*) g_list_nth_data(tracks_list_uuids, i)); // Add the UUID koto_playlist_add_track_by_uuid(new_album_playlist, (gchar*) t->data); // Add the UUID
} }
g_list_free(tracks_list_uuids); // Free the uuids
koto_current_playlist_set_playlist(current_playlist, new_album_playlist); // Set our new current playlist koto_current_playlist_set_playlist(current_playlist, new_album_playlist); // Set our new current playlist
} }
static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){ gint koto_indexed_album_sort_tracks(gconstpointer track1_uuid, gconstpointer track2_uuid, gpointer user_data) {
KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj); (void) user_data;
KotoIndexedTrack *track1 = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) track1_uuid);
KotoIndexedTrack *track2 = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) track2_uuid);
switch (prop_id) { if ((track1 == NULL) && (track2 == NULL)) { // Neither tracks actually exist
case PROP_ARTIST: return 0;
self->artist = (KotoIndexedArtist*) g_value_get_object(val); } else if ((track1 != NULL) && (track2 == NULL)) { // Only track2 does not exist
break; return -1;
case PROP_UUID: } else if ((track1 == NULL) && (track2 != NULL)) { // Only track1 does not exist
self->uuid = g_strdup(g_value_get_string(val)); return 1;
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]); }
break;
case PROP_DO_INITIAL_INDEX: guint *track1_disc = (guint*) 1;
self->do_initial_index = g_value_get_boolean(val); guint *track2_disc = (guint*) 2;
break;
case PROP_PATH: // Path to the album g_object_get(track1, "cd", &track1_disc, NULL);
koto_indexed_album_update_path(self, g_value_get_string(val)); g_object_get(track2, "cd", &track2_disc, NULL);
break;
case PROP_ALBUM_NAME: // Name of album if (track1_disc < track2_disc) { // Track 2 is in a later CD / Disc
koto_indexed_album_set_album_name(self, g_value_get_string(val)); return -1;
break; } else if (track1_disc > track2_disc) { // Track1 is later
case PROP_ART_PATH: // Path to art return 1;
koto_indexed_album_set_album_art(self, g_value_get_string(val)); }
break;
default: guint16 *track1_pos;
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); guint16 *track2_pos;
break;
g_object_get(track1, "position", &track1_pos, NULL);
g_object_get(track2, "position", &track2_pos, NULL);
if (track1_pos == track2_pos) { // Identical positions (like reported as 0)
gchar *track1_name;
gchar *track2_name;
g_object_get(track1, "parsed-name", &track1_name, NULL);
g_object_get(track2, "parsed-name", &track2_name, NULL);
return g_utf8_collate(track1_name, track2_name);
} else if (track1_pos < track2_pos) {
return -1;
} else {
return 1;
} }
} }
@ -478,8 +511,11 @@ void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar* new_pat
} }
KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar *path) { KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar *path) {
gchar *artist_uuid = NULL;
g_object_get(artist, "uuid", &artist_uuid, NULL);
KotoIndexedAlbum* album = g_object_new(KOTO_TYPE_INDEXED_ALBUM, KotoIndexedAlbum* album = g_object_new(KOTO_TYPE_INDEXED_ALBUM,
"artist", artist, "artist-uuid", artist_uuid,
"uuid", g_strdup(g_uuid_string_random()), "uuid", g_strdup(g_uuid_string_random()),
"do-initial-index", TRUE, "do-initial-index", TRUE,
"path", path, "path", path,
@ -493,8 +529,11 @@ KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar
} }
KotoIndexedAlbum* koto_indexed_album_new_with_uuid(KotoIndexedArtist *artist, const gchar *uuid) { KotoIndexedAlbum* koto_indexed_album_new_with_uuid(KotoIndexedArtist *artist, const gchar *uuid) {
gchar *artist_uuid = NULL;
g_object_get(artist, "uuid", &artist_uuid, NULL);
return g_object_new(KOTO_TYPE_INDEXED_ALBUM, return g_object_new(KOTO_TYPE_INDEXED_ALBUM,
"artist", artist, "artist-uuid", artist,
"uuid", g_strdup(uuid), "uuid", g_strdup(uuid),
"do-initial-index", FALSE, "do-initial-index", FALSE,
NULL NULL

View file

@ -29,7 +29,7 @@ struct _KotoIndexedArtist {
gboolean has_artist_art; gboolean has_artist_art;
gchar *artist_name; gchar *artist_name;
GHashTable *albums; GList *albums;
}; };
G_DEFINE_TYPE(KotoIndexedArtist, koto_indexed_artist, G_TYPE_OBJECT); G_DEFINE_TYPE(KotoIndexedArtist, koto_indexed_artist, G_TYPE_OBJECT);
@ -108,7 +108,7 @@ void koto_indexed_artist_commit(KotoIndexedArtist *self) {
static void koto_indexed_artist_init(KotoIndexedArtist *self) { static void koto_indexed_artist_init(KotoIndexedArtist *self) {
self->has_artist_art = FALSE; self->has_artist_art = FALSE;
self->albums = NULL; // Set to null initially maybe self->albums = NULL; // Create a new GList
} }
static void koto_indexed_artist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) { static void koto_indexed_artist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
@ -130,7 +130,7 @@ static void koto_indexed_artist_get_property(GObject *obj, guint prop_id, GValue
} }
} }
static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){ static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj); KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj);
switch (prop_id) { switch (prop_id) {
@ -150,45 +150,20 @@ static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const
} }
} }
void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) { void koto_indexed_artist_add_album(KotoIndexedArtist *self, gchar *album_uuid) {
if (album == NULL) { // No album really defined if ((album_uuid == NULL) || (strcmp(album_uuid, "") == 0)) { // No album UUID really defined
return; return;
} }
if (self->albums == NULL) { // No HashTable yet gchar *uuid = g_strdup(album_uuid); // Duplicate our UUID
self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
if (g_list_index(self->albums, uuid) == -1) {
self->albums = g_list_append(self->albums, uuid); // Push to end of list
} }
gchar *album_uuid;
g_object_get(album, "uuid", &album_uuid, NULL);
if (album_uuid == NULL) {
g_free(album_uuid);
return;
}
if (g_hash_table_contains(self->albums, album_uuid)) { // If we have this album already
g_free(album_uuid);
return;
}
g_hash_table_insert(self->albums, album_uuid, album); // Add the album
} }
GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self) { GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self) {
if (self->albums == NULL) { // No HashTable yet return g_list_copy(self->albums);
self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
}
return g_hash_table_get_values(self->albums);
}
KotoIndexedAlbum* koto_indexed_artist_get_album(KotoIndexedArtist *self, gchar *album_uuid) {
if (self->albums == NULL) {
return NULL;
}
return g_hash_table_lookup(self->albums, album_uuid);
} }
void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) { void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) {
@ -196,14 +171,9 @@ void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum
return; return;
} }
if (self->albums == NULL) { // No HashTable yet
self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
}
gchar *album_uuid; gchar *album_uuid;
g_object_get(album, "uuid", &album_uuid, NULL); g_object_get(album, "uuid", &album_uuid, NULL);
self->albums = g_list_remove(self->albums, album_uuid);
g_hash_table_remove(self->albums, album_uuid);
} }
void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path) { void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path) {

View file

@ -20,10 +20,12 @@
#include <stdio.h> #include <stdio.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <taglib/tag_c.h> #include <taglib/tag_c.h>
#include "../db/cartographer.h"
#include "../db/db.h" #include "../db/db.h"
#include "structs.h"
#include "../koto-utils.h" #include "../koto-utils.h"
#include "structs.h"
extern KotoCartographer *koto_maps;
extern sqlite3 *koto_db; extern sqlite3 *koto_db;
struct _KotoIndexedLibrary { struct _KotoIndexedLibrary {
@ -76,14 +78,15 @@ void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist
koto_indexed_library_get_artists(self); // Call to generate if needed koto_indexed_library_get_artists(self); // Call to generate if needed
gchar *artist_name; gchar *artist_name;
g_object_get(artist, "name", &artist_name, NULL); gchar *artist_uuid;
g_object_get(artist, "name", &artist_name, "uuid", &artist_uuid, NULL);
if (g_hash_table_contains(self->music_artists, artist_name)) { // Already have the artist if (g_hash_table_contains(self->music_artists, artist_name)) { // Already have the artist
g_free(artist_name); g_free(artist_name);
return; return;
} }
g_hash_table_insert(self->music_artists, artist_name, artist); // Add the artist by its name (this needs to be done so we can get the artist when doing the depth of 2 indexing for the album) g_hash_table_insert(self->music_artists, artist_name, artist_uuid); // Add the artist by its name (this needs to be done so we can get the artist when doing the depth of 2 indexing for the album)
} }
KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar *artist_name) { KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar *artist_name) {
@ -93,7 +96,14 @@ KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gch
koto_indexed_library_get_artists(self); // Call to generate if needed koto_indexed_library_get_artists(self); // Call to generate if needed
return g_hash_table_lookup(self->music_artists, (KotoIndexedArtist*) artist_name); gchar *artist_uuid = g_hash_table_lookup(self->music_artists, artist_name); // Get the UUID from our music artists
if (artist_uuid != NULL) {
KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); // Return any artist from cartographer
return artist;
} else {
return NULL;
}
} }
GHashTable* koto_indexed_library_get_artists(KotoIndexedLibrary *self) { GHashTable* koto_indexed_library_get_artists(KotoIndexedLibrary *self) {
@ -162,10 +172,9 @@ void koto_indexed_library_set_path(KotoIndexedLibrary *self, gchar *path) {
} }
int process_artists(void *data, int num_columns, char **fields, char **column_names) { int process_artists(void *data, int num_columns, char **fields, char **column_names) {
(void) num_columns; (void) column_names; // Don't need these (void) num_columns; (void) column_names; // Don't need any of the params
KotoIndexedLibrary *self = (KotoIndexedLibrary*) data;
KotoIndexedLibrary *library = (KotoIndexedLibrary*) data;
gchar *artist_uuid = g_strdup(koto_utils_unquote_string(fields[0])); // First column is UUID gchar *artist_uuid = g_strdup(koto_utils_unquote_string(fields[0])); // First column is UUID
gchar *artist_path = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is path gchar *artist_path = g_strdup(koto_utils_unquote_string(fields[1])); // Second column is path
gchar *artist_name = g_strdup(koto_utils_unquote_string(fields[3])); // Fourth column is artist name gchar *artist_name = g_strdup(koto_utils_unquote_string(fields[3])); // Fourth column is artist name
@ -177,7 +186,8 @@ int process_artists(void *data, int num_columns, char **fields, char **column_na
"name", artist_name, // Set name "name", artist_name, // Set name
NULL); NULL);
koto_indexed_library_add_artist(self, artist); // Add the artist koto_cartographer_add_artist(koto_maps, artist); // Add the artist to our global cartographer
koto_indexed_library_add_artist(library, artist);
int albums_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM albums WHERE artist_id=\"%s\"", artist_uuid), process_albums, artist, NULL); // Process our albums int albums_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM albums WHERE artist_id=\"%s\"", artist_uuid), process_albums, artist, NULL); // Process our albums
if (albums_rc != SQLITE_OK) { // Failed to get our albums if (albums_rc != SQLITE_OK) { // Failed to get our albums
@ -211,7 +221,8 @@ int process_albums(void *data, int num_columns, char **fields, char **column_nam
"art-path", album_art, // Set art path if any "art-path", album_art, // Set art path if any
NULL); NULL);
koto_indexed_artist_add_album(artist, album); // Add the album koto_cartographer_add_album(koto_maps, album); // Add the album to our global cartographer
koto_indexed_artist_add_album(artist, album_uuid); // Add the album
int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE album_id=\"%s\"", album_uuid), process_tracks, album, NULL); // Process our tracks int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE album_id=\"%s\"", album_uuid), process_tracks, album, NULL); // Process our tracks
if (tracks_rc != SQLITE_OK) { // Failed to get our tracks if (tracks_rc != SQLITE_OK) { // Failed to get our tracks
@ -241,8 +252,8 @@ int process_tracks(void *data, int num_columns, char **fields, char **column_nam
gchar *album_uuid = g_strdup(koto_utils_unquote_string(fields[4])); gchar *album_uuid = g_strdup(koto_utils_unquote_string(fields[4]));
gchar *file_name = g_strdup(koto_utils_unquote_string(fields[5])); gchar *file_name = g_strdup(koto_utils_unquote_string(fields[5]));
gchar *name = g_strdup(koto_utils_unquote_string(fields[6])); gchar *name = g_strdup(koto_utils_unquote_string(fields[6]));
guint *disc_num = (guint*) g_ascii_strtoull(fields[6], NULL, 10); guint *disc_num = (guint*) g_ascii_strtoull(fields[7], NULL, 10);
guint *position = (guint*) g_ascii_strtoull(fields[7], NULL, 10); guint *position = (guint*) g_ascii_strtoull(fields[8], NULL, 10);
KotoIndexedTrack *track = koto_indexed_track_new_with_uuid(track_uuid); // Create our file KotoIndexedTrack *track = koto_indexed_track_new_with_uuid(track_uuid); // Create our file
g_object_set(track, g_object_set(track,
@ -255,7 +266,8 @@ int process_tracks(void *data, int num_columns, char **fields, char **column_nam
"position", position, "position", position,
NULL); NULL);
koto_indexed_album_add_file(album, track); // Add the file koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer
koto_indexed_album_add_track(album, track); // Add the track
g_free(track_uuid); g_free(track_uuid);
g_free(path); g_free(path);
@ -341,21 +353,25 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) {
NULL NULL
); );
koto_cartographer_add_artist(koto_maps, artist); // Add the artist to cartographer
koto_indexed_library_add_artist(self, artist); // Add the artist koto_indexed_library_add_artist(self, artist); // Add the artist
index_folder(self, full_path, depth); // Index this directory index_folder(self, full_path, depth); // Index this directory
g_free(artist_name); g_free(artist_name);
} else if (depth == 2) { // If we are following FOLDER/ARTIST/ALBUM then this would be album } else if (depth == 2) { // If we are following FOLDER/ARTIST/ALBUM then this would be album
gchar *artist_name = g_path_get_basename(path); // Get the last entry from our path which is probably the artist gchar *artist_name = g_path_get_basename(path); // Get the last entry from our path which is probably the artist
KotoIndexedArtist *artist = koto_indexed_library_get_artist(self, artist_name); // Get the artist KotoIndexedArtist *artist = koto_indexed_library_get_artist(self, artist_name); // Get the artist
if (artist == NULL) { if (artist == NULL) {
g_message("Failed to get artist by name of: %s", artist_name);
continue; continue;
} }
KotoIndexedAlbum *album = koto_indexed_album_new(artist, full_path); KotoIndexedAlbum *album = koto_indexed_album_new(artist, full_path);
koto_cartographer_add_album(koto_maps, album); // Add our album to the cartographer
koto_indexed_artist_add_album(artist, album); // Add the album gchar *album_uuid = NULL;
g_object_get(album, "uuid", &album_uuid, NULL);
koto_indexed_artist_add_album(artist, album_uuid); // Add the album
g_free(artist_name); g_free(artist_name);
} }
} }
@ -367,9 +383,13 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) {
} }
void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) { void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) {
(void)artist_key; (void) artist_ptr; (void) data;
(void)data; KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, (gchar*) artist_key);
KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_ptr;
if (artist == NULL) {
return;
}
gchar *artist_name; gchar *artist_name;
g_object_get(artist, "name", &artist_name, NULL); g_object_get(artist, "name", &artist_name, NULL);
g_debug("Artist: %s", artist_name); g_debug("Artist: %s", artist_name);
@ -382,32 +402,45 @@ void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) {
GList *a; GList *a;
for (a = albums; a != NULL; a = a->next) { for (a = albums; a != NULL; a = a->next) {
KotoIndexedAlbum *album = (KotoIndexedAlbum*) a->data; gchar *album_uuid = a->data;
KotoIndexedAlbum *album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid);
if (album == NULL) {
continue;
}
gchar *artwork = koto_indexed_album_get_album_art(album); gchar *artwork = koto_indexed_album_get_album_art(album);
gchar *album_name; gchar *album_name;
g_object_get(album, "name", &album_name, NULL); g_object_get(album, "name", &album_name, NULL);
g_debug("Album Art: %s", artwork); g_debug("Album Art: %s", artwork);
g_debug("Album Name: %s", album_name); g_debug("Album Name: %s", album_name);
GList *tracks = koto_indexed_album_get_files(album); // Get the files for the album g_list_foreach(koto_indexed_album_get_tracks(album), output_track, NULL);
GList *t;
for (t = tracks; t != NULL; t = t->next) {
KotoIndexedTrack *track = (KotoIndexedTrack*) t->data;
gchar *filepath;
gchar *parsed_name;
guint *pos;
g_object_get(track,
"path", &filepath,
"parsed-name", &parsed_name,
"position", &pos,
NULL);
g_debug("File Path: %s", filepath);
g_debug("Parsed Name: %s", parsed_name);
g_debug("Position: %d", GPOINTER_TO_INT(pos));
g_free(filepath);
g_free(parsed_name);
}
} }
} }
void output_track(gpointer data, gpointer user_data) {
(void) user_data;
g_message("Track UUID: %s", g_strdup(data));
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) data);
if (track == NULL) {
return;
}
gchar *filepath;
gchar *parsed_name;
guint *pos;
g_object_get(track,
"path", &filepath,
"parsed-name", &parsed_name,
"position", &pos,
NULL);
g_debug("File Path: %s", filepath);
g_debug("Parsed Name: %s", parsed_name);
g_debug("Position: %d", GPOINTER_TO_INT(pos));
g_free(filepath);
g_free(parsed_name);
}

View file

@ -36,6 +36,7 @@ G_DECLARE_FINAL_TYPE (KotoIndexedAlbum, koto_indexed_album, KOTO, INDEXED_ALBUM,
#define KOTO_TYPE_INDEXED_TRACK koto_indexed_track_get_type() #define KOTO_TYPE_INDEXED_TRACK koto_indexed_track_get_type()
G_DECLARE_FINAL_TYPE(KotoIndexedTrack, koto_indexed_track, KOTO, INDEXED_TRACK, GObject); G_DECLARE_FINAL_TYPE(KotoIndexedTrack, koto_indexed_track, KOTO, INDEXED_TRACK, GObject);
#define KOTO_IS_INDEXED_TRACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_INDEXED_TRACK))
/** /**
* Library Functions * Library Functions
@ -54,6 +55,7 @@ int process_tracks(void *data, int num_columns, char **fields, char **column_nam
void read_from_db(KotoIndexedLibrary *self); void read_from_db(KotoIndexedLibrary *self);
void start_indexing(KotoIndexedLibrary *self); void start_indexing(KotoIndexedLibrary *self);
void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth); void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth);
void output_track(gpointer data, gpointer user_data);
/** /**
* Artist Functions * Artist Functions
@ -62,11 +64,10 @@ void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth);
KotoIndexedArtist* koto_indexed_artist_new(const gchar *path); KotoIndexedArtist* koto_indexed_artist_new(const gchar *path);
KotoIndexedArtist* koto_indexed_artist_new_with_uuid(const gchar *uuid); KotoIndexedArtist* koto_indexed_artist_new_with_uuid(const gchar *uuid);
void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); void koto_indexed_artist_add_album(KotoIndexedArtist *self, gchar *album_uuid);
void koto_indexed_artist_commit(KotoIndexedArtist *self); void koto_indexed_artist_commit(KotoIndexedArtist *self);
guint koto_indexed_artist_find_album_with_name(gconstpointer *album_data, gconstpointer *album_name_data); guint koto_indexed_artist_find_album_with_name(gconstpointer *album_data, gconstpointer *album_name_data);
GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self); GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self);
KotoIndexedAlbum* koto_indexed_artist_get_album(KotoIndexedArtist *self, gchar *album_uuid);
void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album);
void koto_indexed_artist_remove_album_by_name(KotoIndexedArtist *self, gchar *album_name); void koto_indexed_artist_remove_album_by_name(KotoIndexedArtist *self, gchar *album_name);
void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name); void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name);
@ -80,18 +81,19 @@ void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data);
KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar *path); KotoIndexedAlbum* koto_indexed_album_new(KotoIndexedArtist *artist, const gchar *path);
KotoIndexedAlbum* koto_indexed_album_new_with_uuid(KotoIndexedArtist *artist, const gchar *uuid); KotoIndexedAlbum* koto_indexed_album_new_with_uuid(KotoIndexedArtist *artist, const gchar *uuid);
void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedTrack *track); void koto_indexed_album_add_track(KotoIndexedAlbum *self, KotoIndexedTrack *track);
void koto_indexed_album_commit(KotoIndexedAlbum *self); void koto_indexed_album_commit(KotoIndexedAlbum *self);
void koto_indexed_album_find_album_art(KotoIndexedAlbum *self); void koto_indexed_album_find_album_art(KotoIndexedAlbum *self);
void koto_indexed_album_find_tracks(KotoIndexedAlbum *self, magic_t magic_cookie, const gchar *path); void koto_indexed_album_find_tracks(KotoIndexedAlbum *self, magic_t magic_cookie, const gchar *path);
gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self); gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self);
GList* koto_indexed_album_get_files(KotoIndexedAlbum *self); GList* koto_indexed_album_get_tracks(KotoIndexedAlbum *self);
void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedTrack *track); void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedTrack *track);
void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *track_name);
void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art); void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art);
void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name); void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name);
void koto_indexed_album_set_artist_uuid(KotoIndexedAlbum *self, const gchar *artist_uuid);
void koto_indexed_album_set_as_current_playlist(KotoIndexedAlbum *self); void koto_indexed_album_set_as_current_playlist(KotoIndexedAlbum *self);
void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar *path); void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar *path);
gint koto_indexed_album_sort_tracks(gconstpointer track1_uuid, gconstpointer track2_uuid, gpointer user_data);
/** /**
* File / Track Functions * File / Track Functions
@ -102,6 +104,7 @@ KotoIndexedTrack* koto_indexed_track_new_with_uuid(const gchar *uuid);
void koto_indexed_track_commit(KotoIndexedTrack *self); void koto_indexed_track_commit(KotoIndexedTrack *self);
void koto_indexed_track_parse_name(KotoIndexedTrack *self); void koto_indexed_track_parse_name(KotoIndexedTrack *self);
void koto_indexed_track_save_to_playlist(KotoIndexedTrack *self, gchar *playlist_uuid, guint position, gint current);
void koto_indexed_track_set_file_name(KotoIndexedTrack *self, gchar *new_file_name); void koto_indexed_track_set_file_name(KotoIndexedTrack *self, gchar *new_file_name);
void koto_indexed_track_set_cd(KotoIndexedTrack *self, guint cd); void koto_indexed_track_set_cd(KotoIndexedTrack *self, guint cd);
void koto_indexed_track_set_parsed_name(KotoIndexedTrack *self, gchar *new_parsed_name); void koto_indexed_track_set_parsed_name(KotoIndexedTrack *self, gchar *new_parsed_name);

View file

@ -18,9 +18,11 @@
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <sqlite3.h> #include <sqlite3.h>
#include <taglib/tag_c.h> #include <taglib/tag_c.h>
#include "../db/cartographer.h"
#include "structs.h" #include "structs.h"
#include "koto-utils.h" #include "koto-utils.h"
extern KotoCartographer *koto_maps;
extern sqlite3 *koto_db; extern sqlite3 *koto_db;
struct _KotoIndexedTrack { struct _KotoIndexedTrack {
@ -32,10 +34,9 @@ struct _KotoIndexedTrack {
gchar *file_name; gchar *file_name;
gchar *parsed_name; gchar *parsed_name;
gchar *artist;
gchar *album;
guint *cd; guint *cd;
guint *position; guint *position;
guint *playback_position;
gboolean acquired_metadata_from_id3; gboolean acquired_metadata_from_id3;
gboolean do_initial_index; gboolean do_initial_index;
@ -49,13 +50,12 @@ enum {
PROP_ALBUM_UUID, PROP_ALBUM_UUID,
PROP_UUID, PROP_UUID,
PROP_DO_INITIAL_INDEX, PROP_DO_INITIAL_INDEX,
PROP_ARTIST,
PROP_ALBUM,
PROP_PATH, PROP_PATH,
PROP_FILE_NAME, PROP_FILE_NAME,
PROP_PARSED_NAME, PROP_PARSED_NAME,
PROP_CD, PROP_CD,
PROP_POSITION, PROP_POSITION,
PROP_PLAYBACK_POSITION,
N_PROPERTIES N_PROPERTIES
}; };
@ -110,22 +110,6 @@ static void koto_indexed_track_class_init(KotoIndexedTrackClass *c) {
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
); );
props[PROP_ARTIST] = g_param_spec_string(
"artist",
"Name of Artist",
"Name of Artist",
NULL,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
props[PROP_ALBUM] = g_param_spec_string(
"album",
"Name of Album",
"Name of Album",
NULL,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
props[PROP_FILE_NAME] = g_param_spec_string( props[PROP_FILE_NAME] = g_param_spec_string(
"file-name", "file-name",
"Name of File", "Name of File",
@ -162,6 +146,16 @@ static void koto_indexed_track_class_init(KotoIndexedTrackClass *c) {
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
); );
props[PROP_PLAYBACK_POSITION] = g_param_spec_uint(
"playback-position",
"Current playback position",
"Current playback position",
0,
G_MAXUINT16,
0,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props); g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
} }
@ -182,12 +176,6 @@ static void koto_indexed_track_get_property(GObject *obj, guint prop_id, GValue
case PROP_UUID: case PROP_UUID:
g_value_set_string(val, self->uuid); g_value_set_string(val, self->uuid);
break; break;
case PROP_ARTIST:
g_value_set_string(val, self->artist);
break;
case PROP_ALBUM:
g_value_set_string(val, self->album);
break;
case PROP_PATH: case PROP_PATH:
g_value_set_string(val, self->path); g_value_set_string(val, self->path);
break; break;
@ -203,6 +191,9 @@ static void koto_indexed_track_get_property(GObject *obj, guint prop_id, GValue
case PROP_POSITION: case PROP_POSITION:
g_value_set_uint(val, GPOINTER_TO_UINT(self->position)); g_value_set_uint(val, GPOINTER_TO_UINT(self->position));
break; break;
case PROP_PLAYBACK_POSITION:
g_value_set_uint(val, GPOINTER_TO_UINT(self->playback_position));
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break; break;
@ -228,12 +219,6 @@ static void koto_indexed_track_set_property(GObject *obj, guint prop_id, const G
case PROP_DO_INITIAL_INDEX: case PROP_DO_INITIAL_INDEX:
self->do_initial_index = g_value_get_boolean(val); self->do_initial_index = g_value_get_boolean(val);
break; break;
case PROP_ARTIST:
self->artist = g_strdup(g_value_get_string(val));
break;
case PROP_ALBUM:
self->album = g_strdup(g_value_get_string(val));
break;
case PROP_PATH: case PROP_PATH:
koto_indexed_track_update_path(self, g_value_get_string(val)); // Update the path koto_indexed_track_update_path(self, g_value_get_string(val)); // Update the path
break; break;
@ -249,6 +234,9 @@ static void koto_indexed_track_set_property(GObject *obj, guint prop_id, const G
case PROP_POSITION: case PROP_POSITION:
koto_indexed_track_set_position(self, g_value_get_uint(val)); koto_indexed_track_set_position(self, g_value_get_uint(val));
break; break;
case PROP_PLAYBACK_POSITION:
self->playback_position = GUINT_TO_POINTER(g_value_get_uint(val));
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break; break;
@ -293,14 +281,22 @@ void koto_indexed_track_commit(KotoIndexedTrack *self) {
void koto_indexed_track_parse_name(KotoIndexedTrack *self) { void koto_indexed_track_parse_name(KotoIndexedTrack *self) {
gchar *copied_file_name = g_strdelimit(g_strdup(self->file_name), "_", ' '); // Replace _ with whitespace for starters gchar *copied_file_name = g_strdelimit(g_strdup(self->file_name), "_", ' '); // Replace _ with whitespace for starters
if (self->artist != NULL && strcmp(self->artist, "") != 0) { // If we have artist set KotoIndexedArtist *artist = NULL;
gchar **split = g_strsplit(copied_file_name, self->artist, -1); // Split whenever we encounter the artist artist = koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid);
copied_file_name = g_strjoinv("", split); // Remove the artist
g_strfreev(split);
split = g_strsplit(copied_file_name, g_utf8_strdown(self->artist, -1), -1); // Lowercase album name and split by that if (artist != NULL) { // If we have artist
copied_file_name = g_strjoinv("", split); // Remove the artist gchar *artist_name = NULL;
g_strfreev(split); g_object_get(artist, "name", &artist_name, NULL);
if (artist_name != NULL && (strcmp(artist_name, "") != 0)) {
gchar **split = g_strsplit(copied_file_name, artist_name, -1); // Split whenever we encounter the artist
copied_file_name = g_strjoinv("", split); // Remove the artist
g_strfreev(split);
split = g_strsplit(copied_file_name, g_utf8_strdown(artist_name, -1), -1); // Lowercase album name and split by that
copied_file_name = g_strjoinv("", split); // Remove the artist
g_strfreev(split);
}
} }
gchar *file_without_ext = koto_utils_get_filename_without_extension(copied_file_name); gchar *file_without_ext = koto_utils_get_filename_without_extension(copied_file_name);
@ -339,6 +335,28 @@ void koto_indexed_track_parse_name(KotoIndexedTrack *self) {
g_free(file_without_ext); g_free(file_without_ext);
} }
void koto_indexed_track_save_to_playlist(KotoIndexedTrack *self, gchar *playlist_uuid, guint position, gint current) {
gchar *commit_op = g_strdup_printf(
"INSERT INTO playlist_tracks(playlist_id, track_id, position, current)"
"VALUES('%s', '%s', quote(\"%d\"), quote(\"%d\")"
"ON CONFLICT(playlist_id, track_id) DO UPDATE SET position=excluded.position, current=excluded.current;",
playlist_uuid,
self->uuid,
position,
current
);
gchar *commit_op_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
if (rc != SQLITE_OK) {
g_warning("Failed to save track to playlist: %s", commit_op_errmsg);
}
g_free(commit_op);
g_free(commit_op_errmsg);
}
void koto_indexed_track_set_file_name(KotoIndexedTrack *self, gchar *new_file_name) { void koto_indexed_track_set_file_name(KotoIndexedTrack *self, gchar *new_file_name) {
if (new_file_name == NULL) { if (new_file_name == NULL) {
return; return;
@ -402,9 +420,8 @@ void koto_indexed_track_update_metadata(KotoIndexedTrack *self) {
self->acquired_metadata_from_id3 = TRUE; self->acquired_metadata_from_id3 = TRUE;
TagLib_Tag *tag = taglib_file_tag(t_file); // Get our tag TagLib_Tag *tag = taglib_file_tag(t_file); // Get our tag
koto_indexed_track_set_parsed_name(self, taglib_tag_title(tag)); // Set the title of the file koto_indexed_track_set_parsed_name(self, taglib_tag_title(tag)); // Set the title of the file
self->artist = g_strdup(taglib_tag_artist((tag))); // Set the artist
self->album = g_strdup(taglib_tag_album(tag)); // Set the album
koto_indexed_track_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer koto_indexed_track_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer
koto_indexed_track_set_file_name(self, g_path_get_basename(self->path)); // Update our file name
} else { } else {
koto_indexed_track_set_file_name(self, g_path_get_basename(self->path)); // Update our file name koto_indexed_track_set_file_name(self, g_path_get_basename(self->path)); // Update our file name
} }
@ -432,29 +449,14 @@ void koto_indexed_track_update_path(KotoIndexedTrack *self, const gchar *new_pat
} }
KotoIndexedTrack* koto_indexed_track_new(KotoIndexedAlbum *album, const gchar *path, guint *cd) { KotoIndexedTrack* koto_indexed_track_new(KotoIndexedAlbum *album, const gchar *path, guint *cd) {
KotoIndexedArtist *artist;
gchar *artist_uuid; gchar *artist_uuid;
gchar *artist_name;
gchar *album_uuid; gchar *album_uuid;
gchar *album_name;
g_object_get(album, "artist", &artist, NULL); // Get the artist from our Album g_object_get(album, "artist-uuid", &artist_uuid, "uuid", &album_uuid, NULL); // Get the artist and album uuids from our Album
g_object_get(artist,
"name", &artist_name,
"uuid", &artist_uuid, // Get the artist UUID from the artist
NULL);
g_object_get(album,
"name", &album_name,
"uuid", &album_uuid, // Get the album UUID from the album
NULL);
KotoIndexedTrack *track = g_object_new(KOTO_TYPE_INDEXED_TRACK, KotoIndexedTrack *track = g_object_new(KOTO_TYPE_INDEXED_TRACK,
"artist-uuid", artist_uuid, "artist-uuid", artist_uuid,
"album-uuid", album_uuid, "album-uuid", album_uuid,
"artist", artist_name,
"album", album_name,
"do-initial-index", TRUE, "do-initial-index", TRUE,
"uuid", g_uuid_string_random(), "uuid", g_uuid_string_random(),
"path", path, "path", path,

View file

@ -338,6 +338,10 @@ void koto_button_set_text(KotoButton *self, gchar *text) {
} }
void koto_button_show_image(KotoButton *self, gboolean use_alt) { void koto_button_show_image(KotoButton *self, gboolean use_alt) {
if (!KOTO_IS_BUTTON(self)) {
return;
}
if (use_alt && ((self->alt_icon_name == NULL) || (strcmp(self->alt_icon_name, "") == 0))) { // Don't have an alt icon set if (use_alt && ((self->alt_icon_name == NULL) || (strcmp(self->alt_icon_name, "") == 0))) { // Don't have an alt icon set
return; return;
} else if (!use_alt && ((self->icon_name == NULL) || (strcmp(self->icon_name, "") == 0))) { // Don't have icon set } else if (!use_alt && ((self->icon_name == NULL) || (strcmp(self->icon_name, "") == 0))) { // Don't have icon set

View file

@ -36,8 +36,8 @@ typedef enum {
#define NUM_BUILTIN_SIZES 7 #define NUM_BUILTIN_SIZES 7
#define KOTO_TYPE_BUTTON (koto_button_get_type()) #define KOTO_TYPE_BUTTON (koto_button_get_type())
G_DECLARE_FINAL_TYPE (KotoButton, koto_button, KOTO, BUTTON, GtkBox) G_DECLARE_FINAL_TYPE (KotoButton, koto_button, KOTO, BUTTON, GtkBox)
#define KOTO_IS_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_BUTTON))
guint koto_get_pixbuf_size(KotoButtonPixbufSize size); guint koto_get_pixbuf_size(KotoButtonPixbufSize size);

View file

@ -58,8 +58,7 @@ static void koto_expander_get_property(GObject *obj, guint prop_id, GValue *val,
static void koto_expander_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec); static void koto_expander_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
static void koto_expander_class_init(KotoExpanderClass *c) { static void koto_expander_class_init(KotoExpanderClass *c) {
GObjectClass *gobject_class; GObjectClass *gobject_class = G_OBJECT_CLASS(c);
gobject_class = G_OBJECT_CLASS(c);
gobject_class->set_property = koto_expander_set_property; gobject_class->set_property = koto_expander_set_property;
gobject_class->get_property = koto_expander_get_property; gobject_class->get_property = koto_expander_get_property;

View file

@ -15,14 +15,19 @@
* limitations under the License. * limitations under the License.
*/ */
#include <gstreamer-1.0/gst/gst.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "playback/engine.h"
#include "koto-button.h" #include "koto-button.h"
#include "koto-config.h" #include "koto-config.h"
#include "koto-playerbar.h" #include "koto-playerbar.h"
extern KotoPlaybackEngine *playback_engine;
struct _KotoPlayerBar { struct _KotoPlayerBar {
GObject parent_instance; GObject parent_instance;
GtkWidget *main; GtkWidget *main;
GtkWidget *controls;
/* Sections */ /* Sections */
GtkWidget *playback_section; GtkWidget *playback_section;
@ -39,6 +44,8 @@ struct _KotoPlayerBar {
KotoButton *eq_button; KotoButton *eq_button;
GtkWidget *volume_button; GtkWidget *volume_button;
GtkWidget *progress_bar;
/* Selected Playback Section */ /* Selected Playback Section */
GtkWidget *playback_details_section; GtkWidget *playback_details_section;
@ -65,23 +72,38 @@ static void koto_playerbar_class_init(KotoPlayerBarClass *c) {
static void koto_playerbar_constructed(GObject *obj) { static void koto_playerbar_constructed(GObject *obj) {
KotoPlayerBar *self = KOTO_PLAYERBAR(obj); KotoPlayerBar *self = KOTO_PLAYERBAR(obj);
self->main = gtk_center_box_new(); self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_center_box_set_baseline_position(GTK_CENTER_BOX(self->main), GTK_BASELINE_POSITION_CENTER); self->progress_bar = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0, 120, 1); // Default to 120 as random max
gtk_scale_set_draw_value(GTK_SCALE(self->progress_bar), FALSE);
gtk_scale_set_digits(GTK_SCALE(self->progress_bar), 0);
gtk_range_set_increments(GTK_RANGE(self->progress_bar), 1, 1);
self->controls = gtk_center_box_new();
gtk_center_box_set_baseline_position(GTK_CENTER_BOX(self->controls), GTK_BASELINE_POSITION_CENTER);
gtk_widget_add_css_class(self->main, "player-bar"); gtk_widget_add_css_class(self->main, "player-bar");
self->playback_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); self->playback_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
self->primary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); self->primary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
self->secondary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); self->secondary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_center_box_set_start_widget(GTK_CENTER_BOX(self->main), GTK_WIDGET(self->primary_controls_section)); gtk_center_box_set_start_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->primary_controls_section));
gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->main), GTK_WIDGET(self->playback_section)); gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->playback_section));
gtk_center_box_set_end_widget(GTK_CENTER_BOX(self->main), GTK_WIDGET(self->secondary_controls_section)); gtk_center_box_set_end_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->secondary_controls_section));
koto_playerbar_create_playback_details(self); koto_playerbar_create_playback_details(self);
koto_playerbar_create_primary_controls(self); koto_playerbar_create_primary_controls(self);
koto_playerbar_create_secondary_controls(self); koto_playerbar_create_secondary_controls(self);
gtk_box_prepend(GTK_BOX(self->main), self->progress_bar);
gtk_box_append(GTK_BOX(self->main), self->controls);
gtk_widget_set_hexpand(GTK_WIDGET(self->main), TRUE); gtk_widget_set_hexpand(GTK_WIDGET(self->main), TRUE);
// Set up the bindings
g_signal_connect(playback_engine, "duration-changed", G_CALLBACK(koto_playerbar_handle_duration_change), self);
g_signal_connect(playback_engine, "play-state-changed", G_CALLBACK(koto_playerbar_handle_engine_state_change), self);
g_signal_connect(playback_engine, "progress-changed", G_CALLBACK(koto_playerbar_handle_progress_change), self);
} }
static void koto_playerbar_init(KotoPlayerBar *self) { static void koto_playerbar_init(KotoPlayerBar *self) {
@ -139,6 +161,19 @@ void koto_playerbar_create_primary_controls(KotoPlayerBar* bar) {
if (bar->forward_button != NULL) { if (bar->forward_button != NULL) {
gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->forward_button)); gtk_box_append(GTK_BOX(bar->primary_controls_section), GTK_WIDGET(bar->forward_button));
} }
GtkGesture *back_controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
g_signal_connect(back_controller, "pressed", G_CALLBACK(koto_playerbar_go_backwards), NULL);
gtk_widget_add_controller(GTK_WIDGET(bar->back_button), GTK_EVENT_CONTROLLER(back_controller));
GtkGesture *controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
g_signal_connect(controller, "pressed", G_CALLBACK(koto_playerbar_toggle_play_pause), NULL);
gtk_widget_add_controller(GTK_WIDGET(bar->play_pause_button), GTK_EVENT_CONTROLLER(controller));
GtkGesture *forwards_controller = gtk_gesture_click_new(); // Create a new GtkGestureClick
g_signal_connect(forwards_controller, "pressed", G_CALLBACK(koto_playerbar_go_forwards), NULL);
gtk_widget_add_controller(GTK_WIDGET(bar->forward_button), GTK_EVENT_CONTROLLER(forwards_controller));
} }
void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) { void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) {
@ -170,6 +205,83 @@ void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar) {
} }
} }
void koto_playerbar_go_backwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
(void) gesture; (void) n_press; (void) x; (void) y; (void) data;
koto_playback_engine_backwards(playback_engine);
}
void koto_playerbar_go_forwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
(void) gesture; (void) n_press; (void) x; (void) y; (void) data;
koto_playback_engine_forwards(playback_engine);
}
void koto_playerbar_handle_duration_change(KotoPlaybackEngine *engine, gpointer user_data) {
if (!KOTO_IS_PLAYBACK_ENGINE(engine)) {
return;
}
KotoPlayerBar *bar = user_data;
koto_playerbar_set_progressbar_duration(bar, koto_playback_engine_get_duration(engine));
}
void koto_playerbar_handle_engine_state_change(KotoPlaybackEngine *engine, gpointer user_data) {
if (!KOTO_IS_PLAYBACK_ENGINE(engine)) {
return;
}
KotoPlayerBar *bar = user_data;
if (!KOTO_IS_PLAYERBAR(bar)) {
return;
}
GstState new_state = koto_playback_engine_get_state(playback_engine);
if (new_state == GST_STATE_PLAYING) { // Now playing
koto_button_show_image(bar->play_pause_button, TRUE); // Set to TRUE to show pause as the next action
} else if (new_state == GST_STATE_PAUSED) { // Now paused
koto_button_show_image(bar->play_pause_button, FALSE); // Set to FALSE to show play as the next action
}
}
void koto_playerbar_handle_progress_change(KotoPlaybackEngine *engine, gpointer user_data) {
if (!KOTO_IS_PLAYBACK_ENGINE(engine)) {
return;
}
KotoPlayerBar *bar = user_data;
koto_playerbar_set_progressbar_value(bar, koto_playback_engine_get_progress(engine));
}
void koto_playerbar_reset_progressbar(KotoPlayerBar* bar) {
gtk_range_set_range(GTK_RANGE(bar->progress_bar), 0, 0); // Reset range
gtk_range_set_value(GTK_RANGE(bar->progress_bar), 0); // Set value to 0
}
void koto_playerbar_set_progressbar_duration(KotoPlayerBar* bar, gint64 duration) {
if (duration < 0) {
return;
}
gtk_range_set_range(GTK_RANGE(bar->progress_bar), 0, duration);
}
void koto_playerbar_set_progressbar_value(KotoPlayerBar* bar, gint64 progress) {
if (progress < 0) {
return;
}
gtk_range_set_value(GTK_RANGE(bar->progress_bar), progress);
}
void koto_playerbar_toggle_play_pause(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data) {
(void) gesture; (void) n_press; (void) x; (void) y; (void) data;
koto_playback_engine_toggle(playback_engine);
}
GtkWidget* koto_playerbar_get_main(KotoPlayerBar* bar) { GtkWidget* koto_playerbar_get_main(KotoPlayerBar* bar) {
return bar->main; return bar->main;
} }

View file

@ -16,18 +16,29 @@
*/ */
#pragma once #pragma once
#include <gstreamer-1.0/gst/gst.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "playback/engine.h"
G_BEGIN_DECLS G_BEGIN_DECLS
#define KOTO_TYPE_PLAYERBAR (koto_playerbar_get_type()) #define KOTO_TYPE_PLAYERBAR (koto_playerbar_get_type())
G_DECLARE_FINAL_TYPE (KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GObject) G_DECLARE_FINAL_TYPE (KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GObject)
#define KOTO_IS_PLAYERBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYERBAR))
KotoPlayerBar* koto_playerbar_new (void); KotoPlayerBar* koto_playerbar_new (void);
GtkWidget* koto_playerbar_get_main(KotoPlayerBar* bar); GtkWidget* koto_playerbar_get_main(KotoPlayerBar* bar);
void koto_playerbar_create_playback_details(KotoPlayerBar* bar); void koto_playerbar_create_playback_details(KotoPlayerBar* bar);
void koto_playerbar_create_primary_controls(KotoPlayerBar* bar); void koto_playerbar_create_primary_controls(KotoPlayerBar* bar);
void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar); void koto_playerbar_create_secondary_controls(KotoPlayerBar* bar);
void koto_playerbar_go_backwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
void koto_playerbar_go_forwards(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
void koto_playerbar_handle_duration_change(KotoPlaybackEngine *engine, gpointer user_data);
void koto_playerbar_handle_engine_state_change(KotoPlaybackEngine *engine, gpointer user_data);
void koto_playerbar_handle_progress_change(KotoPlaybackEngine *engine, gpointer user_data);
void koto_playerbar_reset_progressbar(KotoPlayerBar* bar);
void koto_playerbar_set_progressbar_duration(KotoPlayerBar* bar, gint64 duration);
void koto_playerbar_set_progressbar_value(KotoPlayerBar* bar, gint64 progress);
void koto_playerbar_toggle_play_pause(GtkGestureClick *gesture, int n_press, double x, double y, gpointer data);
G_END_DECLS G_END_DECLS

View file

@ -18,6 +18,7 @@
#include <gtk-4.0/gdk/x11/gdkx.h> #include <gtk-4.0/gdk/x11/gdkx.h>
#include "indexer/structs.h" #include "indexer/structs.h"
#include "pages/music/music-local.h" #include "pages/music/music-local.h"
#include "playback/engine.h"
#include "playlist/current.h" #include "playlist/current.h"
#include "koto-config.h" #include "koto-config.h"
#include "koto-nav.h" #include "koto-nav.h"
@ -25,6 +26,7 @@
#include "koto-window.h" #include "koto-window.h"
extern KotoCurrentPlaylist *current_playlist; extern KotoCurrentPlaylist *current_playlist;
extern KotoPlaybackEngine *playback_engine;
struct _KotoWindow { struct _KotoWindow {
GtkApplicationWindow parent_instance; GtkApplicationWindow parent_instance;
@ -51,6 +53,7 @@ static void koto_window_class_init (KotoWindowClass *klass) {
static void koto_window_init (KotoWindow *self) { static void koto_window_init (KotoWindow *self) {
current_playlist = koto_current_playlist_new(); current_playlist = koto_current_playlist_new();
playback_engine = koto_playback_engine_new();
GtkCssProvider* provider = gtk_css_provider_new(); GtkCssProvider* provider = gtk_css_provider_new();
gtk_css_provider_load_from_resource(provider, "/com/github/joshstrobl/koto/style.css"); gtk_css_provider_load_from_resource(provider, "/com/github/joshstrobl/koto/style.css");

View file

@ -16,6 +16,7 @@
*/ */
#include <glib/gi18n.h> #include <glib/gi18n.h>
#include <gstreamer-1.0/gst/gst.h>
#include "db/cartographer.h" #include "db/cartographer.h"
#include "db/db.h" #include "db/db.h"
@ -53,6 +54,8 @@ int main (int argc, char *argv[]) {
textdomain (GETTEXT_PACKAGE); textdomain (GETTEXT_PACKAGE);
gtk_init(); gtk_init();
gst_init(&argc, &argv);
koto_maps = koto_cartographer_new(); // Create our new cartographer and their collection of maps koto_maps = koto_cartographer_new(); // Create our new cartographer and their collection of maps
open_db(); // Open our database open_db(); // Open our database

View file

@ -11,6 +11,7 @@ koto_sources = [
'pages/music/artist-view.c', 'pages/music/artist-view.c',
'pages/music/disc-view.c', 'pages/music/disc-view.c',
'pages/music/music-local.c', 'pages/music/music-local.c',
'playback/engine.c',
'playlist/current.c', 'playlist/current.c',
'playlist/playlist.c', 'playlist/playlist.c',
'main.c', 'main.c',
@ -26,6 +27,8 @@ koto_sources = [
koto_deps = [ koto_deps = [
dependency('glib-2.0', version: '>= 2.66'), dependency('glib-2.0', version: '>= 2.66'),
dependency('gio-2.0', version: '>= 2.66'), dependency('gio-2.0', version: '>= 2.66'),
dependency('gstreamer-1.0', version: '>= 1.18'),
dependency('gstreamer-player-1.0', version: '>= 1.18'),
dependency('gtk4', version: '>= 4.0'), dependency('gtk4', version: '>= 4.0'),
dependency('libmagic', version: '>=5.39'), dependency('libmagic', version: '>=5.39'),
dependency('sqlite3', version: '>=3.34'), dependency('sqlite3', version: '>=3.34'),

View file

@ -17,13 +17,16 @@
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../koto-button.h" #include "../../db/cartographer.h"
#include "../../indexer/structs.h" #include "../../indexer/structs.h"
#include "../../koto-button.h"
#include "album-view.h" #include "album-view.h"
#include "disc-view.h" #include "disc-view.h"
#include "koto-config.h" #include "koto-config.h"
#include "koto-utils.h" #include "koto-utils.h"
extern KotoCartographer *koto_maps;
struct _KotoAlbumView { struct _KotoAlbumView {
GObject parent_instance; GObject parent_instance;
KotoIndexedAlbum *album; KotoIndexedAlbum *album;
@ -176,10 +179,15 @@ void koto_album_view_set_album(KotoAlbumView *self, KotoIndexedAlbum *album) {
gtk_box_prepend(GTK_BOX(self->album_tracks_box), self->album_label); // Prepend our new label to the album + tracks box gtk_box_prepend(GTK_BOX(self->album_tracks_box), self->album_label); // Prepend our new label to the album + tracks box
GHashTable *discs = g_hash_table_new(g_str_hash, g_str_equal); GHashTable *discs = g_hash_table_new(g_str_hash, g_str_equal);
GList *tracks = koto_indexed_album_get_files(album); // Get the tracks for this album GList *tracks = koto_indexed_album_get_tracks(album); // Get the tracks for this album
for (guint i = 0; i < g_list_length(tracks); i++) { for (guint i = 0; i < g_list_length(tracks); i++) {
KotoIndexedTrack *track = (KotoIndexedTrack*) g_list_nth_data(tracks, i); // Get the KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) g_list_nth_data(tracks, i)); // Get the track by its UUID
if (track == NULL) { // Track doesn't exist
continue;
}
guint *disc_number; guint *disc_number;
g_object_get(track, "cd", &disc_number, NULL); g_object_get(track, "cd", &disc_number, NULL);
gchar *disc_num_as_str = g_strdup_printf("%u", GPOINTER_TO_UINT(disc_number)); gchar *disc_num_as_str = g_strdup_printf("%u", GPOINTER_TO_UINT(disc_number));

View file

@ -17,12 +17,15 @@
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../db/cartographer.h"
#include "../../indexer/structs.h" #include "../../indexer/structs.h"
#include "album-view.h" #include "album-view.h"
#include "artist-view.h" #include "artist-view.h"
#include "koto-config.h" #include "koto-config.h"
#include "koto-utils.h" #include "koto-utils.h"
extern KotoCartographer *koto_maps;
struct _KotoArtistView { struct _KotoArtistView {
GObject parent_instance; GObject parent_instance;
KotoIndexedArtist *artist; KotoIndexedArtist *artist;
@ -160,8 +163,11 @@ void koto_artist_view_add_artist(KotoArtistView *self, KotoIndexedArtist *artist
GList *a; GList *a;
for (a = albums; a != NULL; a = a->next) { for (a = albums; a != NULL; a = a->next) {
KotoIndexedAlbum *album = (KotoIndexedAlbum*) a->data; KotoIndexedAlbum *album = koto_cartographer_get_album_by_uuid(koto_maps, (gchar*) a->data);
koto_artist_view_add_album(self, album); // Add the album
if (album != NULL) {
koto_artist_view_add_album(self, album); // Add the album
}
} }
} }

View file

@ -16,10 +16,13 @@
*/ */
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../db/cartographer.h"
#include "../../indexer/structs.h" #include "../../indexer/structs.h"
#include "../../koto-track-item.h" #include "../../koto-track-item.h"
#include "disc-view.h" #include "disc-view.h"
extern KotoCartographer *koto_maps;
struct _KotoDiscView { struct _KotoDiscView {
GtkBox parent_instance; GtkBox parent_instance;
KotoIndexedAlbum *album; KotoIndexedAlbum *album;
@ -138,25 +141,26 @@ void koto_disc_view_set_album(KotoDiscView *self, KotoIndexedAlbum *album) {
self->list = gtk_list_box_new(); // Create our list of our tracks self->list = gtk_list_box_new(); // Create our list of our tracks
gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->list), GTK_SELECTION_MULTIPLE); gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->list), GTK_SELECTION_MULTIPLE);
gtk_list_box_set_sort_func(GTK_LIST_BOX(self->list), koto_album_view_sort_tracks, NULL, NULL); // Ensure we can sort our tracks
gtk_widget_add_css_class(self->list, "track-list"); gtk_widget_add_css_class(self->list, "track-list");
gtk_widget_set_size_request(self->list, 600, -1); gtk_widget_set_size_request(self->list, 600, -1);
gtk_box_append(GTK_BOX(self), self->list); gtk_box_append(GTK_BOX(self), self->list);
GList *t; g_list_foreach(koto_indexed_album_get_tracks(self->album), koto_disc_view_list_tracks, self);
for (t = koto_indexed_album_get_files(self->album); t != NULL; t = t->next) { // For each file / track }
KotoIndexedTrack *track = (KotoIndexedTrack*) t->data;
guint *disc_number; void koto_disc_view_list_tracks(gpointer data, gpointer selfptr) {
g_object_get(track, "cd", &disc_number, NULL); // get the disc number KotoDiscView *self = (KotoDiscView*) selfptr;
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) data); // Get the track by its UUID
if (GPOINTER_TO_UINT(self->disc_number) != GPOINTER_TO_UINT(disc_number)) { // Track does not belong to this CD guint *disc_number;
continue; g_object_get(track, "cd", &disc_number, NULL); // get the disc number
}
KotoTrackItem *track_item = koto_track_item_new(track); // Create our new track item if (GPOINTER_TO_UINT(self->disc_number) != GPOINTER_TO_UINT(disc_number)) { // Track does not belong to this CD
gtk_list_box_prepend(GTK_LIST_BOX(self->list), GTK_WIDGET(track_item)); // Add to our tracks list box return;
} }
KotoTrackItem *track_item = koto_track_item_new(track); // Create our new track item
gtk_list_box_append(GTK_LIST_BOX(self->list), GTK_WIDGET(track_item)); // Add to our tracks list box
} }
void koto_disc_view_set_disc_number(KotoDiscView *self, guint disc_number) { void koto_disc_view_set_disc_number(KotoDiscView *self, guint disc_number) {
@ -176,38 +180,6 @@ void koto_disc_view_set_disc_label_visible(KotoDiscView *self, gboolean visible)
(visible) ? gtk_widget_show(self->header) : gtk_widget_hide(self->header); (visible) ? gtk_widget_show(self->header) : gtk_widget_hide(self->header);
} }
int koto_album_view_sort_tracks(GtkListBoxRow *track1, GtkListBoxRow *track2, gpointer user_data) {
(void) user_data;
KotoTrackItem *track1_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(track1));
KotoTrackItem *track2_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(track2));
KotoIndexedTrack *track1_file;
KotoIndexedTrack *track2_file;
g_object_get(track1_item, "track", &track1_file, NULL);
g_object_get(track2_item, "track", &track2_file, NULL);
guint16 *track1_pos;
guint16 *track2_pos;
g_object_get(track1_file, "position", &track1_pos, NULL);
g_object_get(track2_file, "position", &track2_pos, NULL);
if (track1_pos == track2_pos) { // Identical positions (like reported as 0)
gchar *track1_name;
gchar *track2_name;
g_object_get(track1_file, "parsed-name", &track1_name, NULL);
g_object_get(track2_file, "parsed-name", &track2_name, NULL);
return g_utf8_collate(track1_name, track2_name);
} else if (track1_pos < track2_pos) {
return -1;
} else {
return 1;
}
}
KotoDiscView* koto_disc_view_new(KotoIndexedAlbum *album, guint *disc_number) { KotoDiscView* koto_disc_view_new(KotoIndexedAlbum *album, guint *disc_number) {
return g_object_new(KOTO_TYPE_DISC_VIEW, return g_object_new(KOTO_TYPE_DISC_VIEW,
"disc", disc_number, "disc", disc_number,

View file

@ -28,9 +28,9 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE(KotoDiscView, koto_disc_view, KOTO, DISC_VIEW, GtkBox) G_DECLARE_FINAL_TYPE(KotoDiscView, koto_disc_view, KOTO, DISC_VIEW, GtkBox)
KotoDiscView* koto_disc_view_new(KotoIndexedAlbum *album, guint *disc); KotoDiscView* koto_disc_view_new(KotoIndexedAlbum *album, guint *disc);
void koto_disc_view_list_tracks(gpointer data, gpointer selfptr);
void koto_disc_view_set_album(KotoDiscView *self, KotoIndexedAlbum *album); void koto_disc_view_set_album(KotoDiscView *self, KotoIndexedAlbum *album);
void koto_disc_view_set_disc_label_visible(KotoDiscView *self, gboolean visible); void koto_disc_view_set_disc_label_visible(KotoDiscView *self, gboolean visible);
void koto_disc_view_set_disc_number(KotoDiscView *self, guint disc_number); void koto_disc_view_set_disc_number(KotoDiscView *self, guint disc_number);
int koto_album_view_sort_tracks(GtkListBoxRow *track1, GtkListBoxRow *track2, gpointer user_data);
G_END_DECLS G_END_DECLS

View file

@ -17,11 +17,14 @@
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <gtk-4.0/gtk/gtk.h> #include <gtk-4.0/gtk/gtk.h>
#include "../../db/cartographer.h"
#include "../../indexer/structs.h" #include "../../indexer/structs.h"
#include "koto-button.h" #include "koto-button.h"
#include "koto-config.h" #include "koto-config.h"
#include "music-local.h" #include "music-local.h"
extern KotoCartographer *koto_maps;
enum { enum {
PROP_0, PROP_0,
PROP_LIB, PROP_LIB,
@ -175,8 +178,11 @@ void koto_page_music_local_set_library(KotoPageMusicLocal *self, KotoIndexedLibr
g_hash_table_iter_init(&artist_list_iter, artists); g_hash_table_iter_init(&artist_list_iter, artists);
while (g_hash_table_iter_next(&artist_list_iter, &artist_key, &artist_data)) { // For each of the music artists while (g_hash_table_iter_next(&artist_list_iter, &artist_key, &artist_data)) { // For each of the music artists
KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_data; // Cast our data as a KotoIndexedArtist KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, (gchar*) artist_data); // Cast our data as a KotoIndexedArtist
koto_page_music_local_add_artist(self, artist);
if (artist != NULL) {
koto_page_music_local_add_artist(self, artist);
}
} }
} }

314
src/playback/engine.c Normal file
View file

@ -0,0 +1,314 @@
/* engine.c
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <glib-2.0/glib.h>
#include <gstreamer-1.0/gst/gst.h>
#include <gstreamer-1.0/gst/player/player.h>
#include "../db/cartographer.h"
#include "../playlist/current.h"
#include "../indexer/structs.h"
#include "engine.h"
enum {
SIGNAL_DURATION_CHANGE,
SIGNAL_PLAY_STATE_CHANGE,
SIGNAL_PROGRESS_CHANGE,
N_SIGNALS
};
static glong NS = 1000000000;
static guint playback_engine_signals[N_SIGNALS] = { 0 };
extern KotoCartographer *koto_maps;
extern KotoCurrentPlaylist *current_playlist;
KotoPlaybackEngine *playback_engine;
struct _KotoPlaybackEngine {
GObject parent_class;
GstElement *player;
GstElement *playbin;
GstElement *suppress_video;
GstBus *monitor;
gboolean is_paused;
gboolean is_playing;
gboolean is_muted;
gboolean is_repeat_enabled;
gboolean is_shuffle_enabled;
gboolean requested_playing;
guint playback_position;
gdouble volume;
};
struct _KotoPlaybackEngineClass {
GObjectClass parent_class;
void (* duration_changed) (KotoPlaybackEngine *engine, gint64 duration);
void (* play_state_changed) (KotoPlaybackEngine *engine);
void (* progress_changed) (KotoPlaybackEngine *engine, gint64 progress);
};
G_DEFINE_TYPE(KotoPlaybackEngine, koto_playback_engine, G_TYPE_OBJECT);
static void koto_playback_engine_class_init(KotoPlaybackEngineClass *c) {
c->play_state_changed = NULL;
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS(c);
playback_engine_signals[SIGNAL_DURATION_CHANGE] = g_signal_new(
"duration-changed",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoPlaybackEngineClass, duration_changed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
0
);
playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE] = g_signal_new(
"play-state-changed",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoPlaybackEngineClass, play_state_changed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
0
);
playback_engine_signals[SIGNAL_PROGRESS_CHANGE] = g_signal_new(
"progress-changed",
G_TYPE_FROM_CLASS(gobject_class),
G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET(KotoPlaybackEngineClass, progress_changed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
0
);
}
static void koto_playback_engine_init(KotoPlaybackEngine *self) {
self->player = gst_pipeline_new("player");
self->playbin = gst_element_factory_make("playbin", NULL);
self->suppress_video = gst_element_factory_make("fakesink", "suppress-video");
g_object_set(self->playbin, "video-sink", self->suppress_video, NULL);
g_object_set(self->playbin, "volume", 0.5, NULL);
gst_bin_add(GST_BIN(self->player), self->playbin);
self->monitor = gst_bus_new(); // Get the bus for the playbin
if (GST_IS_BUS(self->monitor)) {
gst_bus_add_watch(self->monitor, koto_playback_engine_monitor_changed, self);
gst_element_set_bus(self->playbin, self->monitor); // Set our bus to monitor changes
}
self->is_muted = FALSE;
self->is_playing = FALSE;
self->is_paused = FALSE;
self->is_repeat_enabled = FALSE;
self->is_shuffle_enabled = FALSE;
self->requested_playing = FALSE;
if (KOTO_IS_CURRENT_PLAYLIST(current_playlist)) {
g_signal_connect(current_playlist, "notify::current-playlist", G_CALLBACK(koto_playback_engine_current_playlist_changed), NULL);
}
}
void koto_playback_engine_backwards(KotoPlaybackEngine *self) {
KotoPlaylist *playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist
if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently
return;
}
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_previous(playlist));
}
void koto_playback_engine_current_playlist_changed() {
if (!KOTO_IS_PLAYBACK_ENGINE(playback_engine)) {
return;
}
KotoPlaylist *playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist
if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently
return;
}
koto_playback_engine_set_track_by_uuid(playback_engine, koto_playlist_get_current_uuid(playlist)); // Get the current UUID
}
void koto_playback_engine_forwards(KotoPlaybackEngine *self) {
KotoPlaylist *playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist
if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently
return;
}
koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist));
}
gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self) {
gint64 duration = 0;
if (gst_element_query_duration(self->playbin, GST_FORMAT_TIME, &duration)) {
duration = duration / NS; // Divide by NS to get seconds
}
return duration;
}
GstState koto_playback_engine_get_state(KotoPlaybackEngine *self) {
GstState current_state;
GstStateChangeReturn ret = gst_element_get_state(self->playbin, &current_state, NULL, GST_SECOND); // Get the current state, allowing up to a second to get it
if (ret != GST_STATE_CHANGE_SUCCESS) { // Got the data we need
return GST_STATE_NULL;
}
return current_state;
}
gint64 koto_playback_engine_get_progress(KotoPlaybackEngine *self) {
gint64 progress = 0;
if (gst_element_query_position(self->playbin, GST_FORMAT_TIME, &progress)) {
progress = progress / NS; // Divide by NS to get seconds
}
return progress;
}
gboolean koto_playback_engine_monitor_changed(GstBus *bus, GstMessage *msg, gpointer user_data) {
(void) bus;
KotoPlaybackEngine *self = user_data;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ASYNC_DONE: { // Finished loading
if (self->requested_playing) {
self->is_playing = TRUE;
self->is_paused = FALSE;
g_timeout_add(50, koto_playback_engine_update_progress, self);
gst_element_set_state(self->playbin, GST_STATE_PLAYING); // Make double sure the state is playing
g_signal_emit(self, playback_engine_signals[SIGNAL_DURATION_CHANGE], 0); // Emit our duration signal
g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our playing signal
}
break;
}
case GST_MESSAGE_STATE_CHANGED: { // State changed
GstState old_state;
GstState requested_state;
GstState new_state;
gst_message_parse_state_changed(msg, &old_state, &requested_state, &new_state);
if ((old_state == GST_STATE_PLAYING) && (requested_state == GST_STATE_PAUSED)) {
self->is_playing = FALSE;
self->is_paused = TRUE;
g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our paused signal
} else if (requested_state == GST_STATE_PLAYING && !self->is_playing) {
self->is_playing = TRUE;
self->is_paused = FALSE;
g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our paused signal
} else if (((old_state == GST_STATE_PLAYING) || (old_state == GST_STATE_PAUSED)) && (new_state == GST_STATE_NULL)) { // If we're freeing resources
gst_bus_post(self->monitor, gst_message_new_reset_time(GST_OBJECT(self->playbin), 0));
}
break;
}
case GST_MESSAGE_EOS: { // Reached end of stream
koto_playback_engine_forwards(self); // Go to the next track
break;
}
default:
break;
}
return TRUE;
}
void koto_playback_engine_play(KotoPlaybackEngine *self) {
self->requested_playing = TRUE;
gst_element_set_state(self->playbin, GST_STATE_PLAYING); // Set our state to play
}
void koto_playback_engine_pause(KotoPlaybackEngine *self) {
self->requested_playing = FALSE;
gst_element_set_state(self->playbin, GST_STATE_PAUSED); // Set our state to paused
}
void koto_playback_engine_set_track_by_uuid(KotoPlaybackEngine *self, gchar *track_uuid) {
if (track_uuid == NULL) {
return;
}
KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track from cartographer
if (!KOTO_IS_INDEXED_TRACK(track)) { // Not a track
return;
}
gchar *track_file_path = NULL;
g_object_get(track, "path", &track_file_path, NULL); // Get the path to the track
koto_playback_engine_stop(self); // Stop current track
gchar *gst_filename = gst_filename_to_uri(track_file_path, NULL); // Get the gst supported file naem
g_object_set(self->playbin, "uri", gst_filename, NULL);
g_free(gst_filename); // Free the filename
koto_playback_engine_play(self); // Play the new track
}
void koto_playback_engine_stop(KotoPlaybackEngine *self) {
gst_element_set_state(self->playbin, GST_STATE_NULL);
GstPad *pad = gst_element_get_static_pad(self->playbin, "audio-sink"); // Get the static pad of the audio element
if (!GST_IS_PAD(pad)) {
return;
}
gst_pad_set_offset(pad, 0); // Change offset
}
void koto_playback_engine_toggle(KotoPlaybackEngine *self) {
if (koto_playback_engine_get_state(self) == GST_STATE_PLAYING) { // Currently playing
koto_playback_engine_pause(self); // Pause
} else {
koto_playback_engine_play(self); // Play
}
}
gboolean koto_playback_engine_update_progress(gpointer user_data) {
KotoPlaybackEngine *self = user_data;
if (self->is_playing) { // Is playing
g_signal_emit(self, playback_engine_signals[SIGNAL_PROGRESS_CHANGE], 0); // Emit our progress change signal
}
return self->is_playing;
}
KotoPlaybackEngine* koto_playback_engine_new() {
return g_object_new(KOTO_TYPE_PLAYBACK_ENGINE, NULL);
}

63
src/playback/engine.h Normal file
View file

@ -0,0 +1,63 @@
/* engine.h
*
* Copyright 2021 Joshua Strobl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <glib-2.0/glib-object.h>
#include <gstreamer-1.0/gst/gst.h>
#include <gstreamer-1.0/gst/player/player.h>
#include "../playlist/current.h"
G_BEGIN_DECLS
/**
* Type Definition
**/
#define KOTO_TYPE_PLAYBACK_ENGINE (koto_playback_engine_get_type())
#define KOTO_PLAYBACK_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), KOTO_TYPE_PLAYBACK_ENGINE, KotoPlaybackEngine))
#define KOTO_IS_PLAYBACK_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYBACK_ENGINE))
typedef struct _KotoPlaybackEngine KotoPlaybackEngine;
typedef struct _KotoPlaybackEngineClass KotoPlaybackEngineClass;
GLIB_AVAILABLE_IN_ALL
GType koto_playback_engine_get_type(void) G_GNUC_CONST;
/**
* Playback Engine Functions
**/
KotoPlaybackEngine* koto_playback_engine_new();
void koto_playback_engine_backwards(KotoPlaybackEngine *self);
void koto_playback_engine_current_playlist_changed();
void koto_playback_engine_forwards(KotoPlaybackEngine *self);
gint64 koto_playback_engine_get_duration(KotoPlaybackEngine *self);
GstState koto_playback_engine_get_state(KotoPlaybackEngine *self);
gint64 koto_playback_engine_get_progress(KotoPlaybackEngine *self);
void koto_playback_engine_mute(KotoPlaybackEngine *self);
gboolean koto_playback_engine_monitor_changed(GstBus *bus, GstMessage *msg, gpointer user_data);
void koto_playback_engine_pause(KotoPlaybackEngine *self);
void koto_playback_engine_play(KotoPlaybackEngine *self);
void koto_playback_engine_toggle(KotoPlaybackEngine *self);
void koto_playback_engine_set_position(KotoPlaybackEngine *self, guint position);
void koto_playback_engine_set_repeat(KotoPlaybackEngine *self, gboolean enable);
void koto_playback_engine_set_shuffle(KotoPlaybackEngine *self, gboolean enable);
void koto_playback_engine_set_track_by_uuid(KotoPlaybackEngine *self, gchar *track_uuid);
void koto_playback_engine_set_volume(KotoPlaybackEngine *self, gdouble volume);
void koto_playback_engine_stop(KotoPlaybackEngine *self);
void koto_playback_engine_update_duration(KotoPlaybackEngine *self);
gboolean koto_playback_engine_update_progress(gpointer user_data);

View file

@ -18,6 +18,14 @@
#include <glib-2.0/glib-object.h> #include <glib-2.0/glib-object.h>
#include "current.h" #include "current.h"
enum {
PROP_0,
PROP_CURRENT_PLAYLIST,
N_PROPERTIES
};
static GParamSpec *props[N_PROPERTIES] = { NULL, };
KotoCurrentPlaylist *current_playlist = NULL; KotoCurrentPlaylist *current_playlist = NULL;
struct _KotoCurrentPlaylist { struct _KotoCurrentPlaylist {
@ -27,14 +35,56 @@ struct _KotoCurrentPlaylist {
G_DEFINE_TYPE(KotoCurrentPlaylist, koto_current_playlist, G_TYPE_OBJECT); G_DEFINE_TYPE(KotoCurrentPlaylist, koto_current_playlist, G_TYPE_OBJECT);
static void koto_current_playlist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
static void koto_current_playlist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
static void koto_current_playlist_class_init(KotoCurrentPlaylistClass *c) { static void koto_current_playlist_class_init(KotoCurrentPlaylistClass *c) {
(void) c; GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS(c);
gobject_class->set_property = koto_current_playlist_set_property;
gobject_class->get_property = koto_current_playlist_get_property;
props[PROP_CURRENT_PLAYLIST] = g_param_spec_object(
"current-playlist",
"Current Playlist",
"Current Playlist",
KOTO_TYPE_PLAYLIST,
G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
} }
static void koto_current_playlist_init(KotoCurrentPlaylist *self) { static void koto_current_playlist_init(KotoCurrentPlaylist *self) {
self->current_playlist = NULL; self->current_playlist = NULL;
} }
void koto_current_playlist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
KotoCurrentPlaylist *self = KOTO_CURRENT_PLAYLIST(obj);
switch (prop_id) {
case PROP_CURRENT_PLAYLIST:
g_value_set_object(val, self->current_playlist);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
void koto_current_playlist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec) {
KotoCurrentPlaylist *self = KOTO_CURRENT_PLAYLIST(obj);
switch (prop_id) {
case PROP_CURRENT_PLAYLIST:
koto_current_playlist_set_playlist(self, (KotoPlaylist*) g_value_get_object(val));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
KotoPlaylist* koto_current_playlist_get_playlist(KotoCurrentPlaylist *self) { KotoPlaylist* koto_current_playlist_get_playlist(KotoCurrentPlaylist *self) {
return self->current_playlist; return self->current_playlist;
} }
@ -44,15 +94,26 @@ void koto_current_playlist_set_playlist(KotoCurrentPlaylist *self, KotoPlaylist
return; return;
} }
if (KOTO_IS_PLAYLIST(self->current_playlist)) { if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist
// TODO: Save current playlist state if needed return;
}
if (self->current_playlist != NULL && KOTO_IS_PLAYLIST(self->current_playlist)) {
gboolean *is_temp = FALSE;
g_object_get(self->current_playlist, "ephemeral", &is_temp, NULL); // Get the current ephemeral value
if (is_temp) { // Is a temporary playlist
koto_playlist_unmap(self->current_playlist); // Unmap the playlist if needed
} else { // Not a temporary playlist
koto_playlist_commit(self->current_playlist); // Save the current playlist
}
g_object_unref(self->current_playlist); // Unreference g_object_unref(self->current_playlist); // Unreference
} }
self->current_playlist = playlist; self->current_playlist = playlist;
g_object_ref(playlist); // Increment the reference g_object_ref(playlist); // Increment the reference
g_object_notify(G_OBJECT(self), "current-playlist-changed"); g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CURRENT_PLAYLIST]);
koto_playlist_debug(self->current_playlist);
} }
KotoCurrentPlaylist* koto_current_playlist_new() { KotoCurrentPlaylist* koto_current_playlist_new() {

View file

@ -18,8 +18,10 @@
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <magic.h> #include <magic.h>
#include <sqlite3.h> #include <sqlite3.h>
#include "../db/cartographer.h"
#include "playlist.h" #include "playlist.h"
extern KotoCartographer *koto_maps;
extern sqlite3 *koto_db; extern sqlite3 *koto_db;
struct _KotoPlaylist { struct _KotoPlaylist {
@ -28,8 +30,11 @@ struct _KotoPlaylist {
gchar *name; gchar *name;
gchar *art_path; gchar *art_path;
guint current_position; guint current_position;
gboolean ephemeral;
gboolean is_shuffle_enabled;
GQueue *tracks; GQueue *tracks;
GQueue *played_tracks;
}; };
G_DEFINE_TYPE(KotoPlaylist, koto_playlist, G_TYPE_OBJECT); G_DEFINE_TYPE(KotoPlaylist, koto_playlist, G_TYPE_OBJECT);
@ -39,6 +44,7 @@ enum {
PROP_UUID, PROP_UUID,
PROP_NAME, PROP_NAME,
PROP_ART_PATH, PROP_ART_PATH,
PROP_EPHEMERAL,
N_PROPERTIES, N_PROPERTIES,
}; };
@ -76,6 +82,14 @@ static void koto_playlist_class_init(KotoPlaylistClass *c) {
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
); );
props[PROP_EPHEMERAL] = g_param_spec_boolean(
"ephemeral",
"Is the playlist ephemeral (temporary)",
"Is the playlist ephemeral (temporary)",
FALSE,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props); g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
} }
@ -92,6 +106,9 @@ static void koto_playlist_get_property(GObject *obj, guint prop_id, GValue *val,
case PROP_ART_PATH: case PROP_ART_PATH:
g_value_set_string(val, self->art_path); g_value_set_string(val, self->art_path);
break; break;
case PROP_EPHEMERAL:
g_value_set_boolean(val, self->ephemeral);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break; break;
@ -111,6 +128,9 @@ static void koto_playlist_set_property(GObject *obj, guint prop_id, const GValue
case PROP_ART_PATH: case PROP_ART_PATH:
koto_playlist_set_artwork(self, g_value_get_string(val)); koto_playlist_set_artwork(self, g_value_get_string(val));
break; break;
case PROP_EPHEMERAL:
self->ephemeral = g_value_get_boolean(val);
break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break; break;
@ -119,6 +139,7 @@ static void koto_playlist_set_property(GObject *obj, guint prop_id, const GValue
static void koto_playlist_init(KotoPlaylist *self) { static void koto_playlist_init(KotoPlaylist *self) {
self->current_position = 0; // Default to 0 self->current_position = 0; // Default to 0
self->played_tracks = g_queue_new(); // Set as an empty GQueue
self->tracks = g_queue_new(); // Set as an empty GQueue self->tracks = g_queue_new(); // Set as an empty GQueue
} }
@ -135,14 +156,45 @@ void koto_playlist_add_track_by_uuid(KotoPlaylist *self, const gchar *uuid) {
// TODO: Add to table // TODO: Add to table
} }
void koto_playlist_debug(KotoPlaylist *self) { void koto_playlist_commit(KotoPlaylist *self) {
g_queue_foreach(self->tracks, koto_playlist_debug_foreach, NULL); if (self->ephemeral) { // Temporary playlist
return;
}
gchar *commit_op = g_strdup_printf(
"INSERT INTO playlist_meta(id, name, art_path)"
"VALUES('%s', quote(\"%s\"), quote(\"%s\")"
"ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path;",
self->uuid,
self->name,
self->art_path
);
gchar *commit_op_errmsg = NULL;
int rc = sqlite3_exec(koto_db, commit_op, 0, 0, &commit_op_errmsg);
if (rc != SQLITE_OK) {
g_warning("Failed to save playlist: %s", commit_op_errmsg);
} else { // Successfully saved our playlist
g_queue_foreach(self->tracks, koto_playlist_commit_tracks, self); // Iterate over each track to save it
}
g_free(commit_op);
g_free(commit_op_errmsg);
} }
void koto_playlist_debug_foreach(gpointer data, gpointer user_data) { void koto_playlist_commit_tracks(gpointer data, gpointer user_data) {
(void) user_data; KotoIndexedTrack *track = koto_cartographer_get_track_by_uuid(koto_maps, data); // Get the track
gchar *uuid = data;
g_message("UUID in Playlist: %s", uuid); if (track == NULL) { // Not a track
KotoPlaylist *self = user_data;
gchar *playlist_uuid = self->uuid; // Get the playlist UUID
gchar *current_track = g_queue_peek_nth(self->tracks, self->current_position); // Get the UUID of the current track
koto_indexed_track_save_to_playlist(track, playlist_uuid, g_queue_index(self->tracks, data), (data == current_track) ? 1 : 0); // Call to save the playlist to the track
g_free(playlist_uuid);
g_free(current_track);
}
} }
gchar* koto_playlist_get_artwork(KotoPlaylist *self) { gchar* koto_playlist_get_artwork(KotoPlaylist *self) {
@ -165,6 +217,38 @@ gchar* koto_playlist_get_name(KotoPlaylist *self) {
return (self->name == NULL) ? NULL : g_strdup(self->name); return (self->name == NULL) ? NULL : g_strdup(self->name);
} }
gchar* koto_playlist_get_random_track(KotoPlaylist *self) {
gchar *track_uuid = NULL;
guint tracks_len = g_queue_get_length(self->tracks);
if (tracks_len == g_queue_get_length(self->played_tracks)) { // Played all tracks
track_uuid = g_list_nth_data(self->tracks->head, 0); // Get the first
g_queue_clear(self->played_tracks); // Clear our played tracks
} else { // Have not played all tracks
GRand* rando_calrissian = g_rand_new(); // Create a new RNG
guint attempt = 0;
while (track_uuid != NULL) { // Haven't selected a track yet
attempt++;
gint32 *selected_item = g_rand_int_range(rando_calrissian, 0, (gint32) tracks_len);
gchar *selected_track = g_queue_peek_nth(self->tracks, (guint) selected_item); // Get the UUID of the selected item
if (g_queue_index(self->played_tracks, selected_track) == -1) { // Haven't played the track
track_uuid = selected_track;
break;
} else { // Failed to get the track
if (attempt > tracks_len / 2) {
break; // Give up
}
}
}
g_rand_free(rando_calrissian); // Free rando
}
return track_uuid;
}
GQueue* koto_playlist_get_tracks(KotoPlaylist *self) { GQueue* koto_playlist_get_tracks(KotoPlaylist *self) {
return self->tracks; return self->tracks;
} }
@ -174,6 +258,10 @@ gchar* koto_playlist_get_uuid(KotoPlaylist *self) {
} }
gchar* koto_playlist_go_to_next(KotoPlaylist *self) { gchar* koto_playlist_go_to_next(KotoPlaylist *self) {
if (self->is_shuffle_enabled) { // Shuffling enabled
return koto_playlist_get_random_track(self); // Get a random track
}
gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID
if (current_uuid == self->tracks->tail->data) { // Current UUID matches the last item in the playlist if (current_uuid == self->tracks->tail->data) { // Current UUID matches the last item in the playlist
@ -185,6 +273,10 @@ gchar* koto_playlist_go_to_next(KotoPlaylist *self) {
} }
gchar* koto_playlist_go_to_previous(KotoPlaylist *self) { gchar* koto_playlist_go_to_previous(KotoPlaylist *self) {
if (self->is_shuffle_enabled) { // Shuffling enabled
return koto_playlist_get_random_track(self); // Get a random track
}
gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID gchar *current_uuid = koto_playlist_get_current_uuid(self); // Get the current UUID
if (current_uuid == self->tracks->head->data) { // Current UUID matches the first item in the playlist if (current_uuid == self->tracks->head->data) { // Current UUID matches the first item in the playlist
@ -280,6 +372,10 @@ void koto_playlist_set_uuid(KotoPlaylist *self, const gchar *uuid) {
return; return;
} }
void koto_playlist_unmap(KotoPlaylist *self) {
koto_cartographer_remove_playlist_by_uuid(koto_maps, self->uuid); // Remove from our cartographer
}
KotoPlaylist* koto_playlist_new() { KotoPlaylist* koto_playlist_new() {
return g_object_new(KOTO_TYPE_PLAYLIST, return g_object_new(KOTO_TYPE_PLAYLIST,
"uuid", g_uuid_string_random(), "uuid", g_uuid_string_random(),

View file

@ -37,8 +37,8 @@ KotoPlaylist* koto_playlist_new();
KotoPlaylist* koto_playlist_new_with_uuid(const gchar *uuid); KotoPlaylist* koto_playlist_new_with_uuid(const gchar *uuid);
void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedTrack *track); void koto_playlist_add_track(KotoPlaylist *self, KotoIndexedTrack *track);
void koto_playlist_add_track_by_uuid(KotoPlaylist *self, const gchar *uuid); void koto_playlist_add_track_by_uuid(KotoPlaylist *self, const gchar *uuid);
void koto_playlist_debug(KotoPlaylist *self); void koto_playlist_commit(KotoPlaylist *self);
void koto_playlist_debug_foreach(gpointer data, gpointer user_data); void koto_playlist_commit_tracks(gpointer data, gpointer user_data);
gchar* koto_playlist_get_artwork(KotoPlaylist *self); gchar* koto_playlist_get_artwork(KotoPlaylist *self);
guint koto_playlist_get_current_position(KotoPlaylist *self); guint koto_playlist_get_current_position(KotoPlaylist *self);
gchar* koto_playlist_get_current_uuid(KotoPlaylist *self); gchar* koto_playlist_get_current_uuid(KotoPlaylist *self);
@ -55,5 +55,6 @@ void koto_playlist_save_state(KotoPlaylist *self);
void koto_playlist_set_name(KotoPlaylist *self, const gchar *name); void koto_playlist_set_name(KotoPlaylist *self, const gchar *name);
void koto_playlist_set_position(KotoPlaylist *self, guint pos); void koto_playlist_set_position(KotoPlaylist *self, guint pos);
void koto_playlist_set_uuid(KotoPlaylist *self, const gchar *uuid); void koto_playlist_set_uuid(KotoPlaylist *self, const gchar *uuid);
void koto_playlist_unmap(KotoPlaylist *self);
G_END_DECLS G_END_DECLS