Implement Album and Track metadata for duration and genres.

For tracks, we will fetch the duration from the ID3 tag information, alongside genres. These genres will be used for multiple purposes, e.g. search. At the moment however, it is used in koto_album_add_track to collate a list of genres that apply to the album based on the contents of it.

We can have multiple genres, we separate them by semi-column as is the case for TagLib. We will attempt to filter and rename some select genres to enforce consistency.

Changed position and playback position from guint to guint64.

Implemented multiple koto utility functions:

- koto_utils_join_string_list
- koto_utils_string_contains_substring
- koto_utils_string_to_string_list

Drastically simplified koto_utils_replace_string_all.

Implement koto_track_helpers_init to initialize our hashtable.

Fixed a segfault during first load of Koto where we were attempting to get the playback engine last used volume in koto_config_save, when the KotoPlaybackEngine was not yet initialized. Default it to 1.0.
This commit is contained in:
Joshua Strobl 2021-07-08 18:37:52 +03:00
parent 812cdc6677
commit 381cc9ce4c
15 changed files with 387 additions and 44 deletions

View file

@ -506,7 +506,12 @@ void koto_config_save(KotoConfig * self) {
gchar * playback_hash = g_strdup("playback"); gchar * playback_hash = g_strdup("playback");
gchar * ui_hash = g_strdup("ui"); gchar * ui_hash = g_strdup("ui");
gdouble current_playback_volume = koto_playback_engine_get_volume(playback_engine); // Get the last used volume in the playback engine gdouble current_playback_volume = 1.0;
if (KOTO_IS_PLAYBACK_ENGINE(playback_engine)) { // Have a playback engine (useful since it may not be initialized before the config performs saving on first application load)
current_playback_volume = koto_playback_engine_get_volume(playback_engine); // Get the last used volume in the playback engine
}
self->playback_last_used_volume = current_playback_volume; // Update our value so we have it during save self->playback_last_used_volume = current_playback_volume; // Update our value so we have it during save
int i; int i;

View file

@ -39,8 +39,8 @@ void close_db() {
int create_db_tables() { int create_db_tables() {
gchar * tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, name string, art_path string);" gchar * tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, name string, art_path string);"
"CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, 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, artist_id string, name string, art_path string, genres string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, artist_id string, album_id 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, artist_id string, album_id string, name string, disc int, position int, duration int, genres string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS libraries_albums(id string, album_id string, path string, PRIMARY KEY (id, album_id) FOREIGN KEY(album_id) REFERENCES albums(id) ON DELETE CASCADE);" "CREATE TABLE IF NOT EXISTS libraries_albums(id string, album_id string, path string, PRIMARY KEY (id, album_id) FOREIGN KEY(album_id) REFERENCES albums(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS libraries_artists(id string, artist_id string, path string, PRIMARY KEY(id, artist_id) FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" "CREATE TABLE IF NOT EXISTS libraries_artists(id string, artist_id string, path string, PRIMARY KEY(id, artist_id) FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"
"CREATE TABLE IF NOT EXISTS libraries_tracks(id string, track_id string, path string, PRIMARY KEY(id, track_id) FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);" "CREATE TABLE IF NOT EXISTS libraries_tracks(id string, track_id string, path string, PRIMARY KEY(id, track_id) FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);"

View file

@ -116,6 +116,7 @@ int process_albums(
gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[1])); gchar * artist_uuid = g_strdup(koto_utils_unquote_string(fields[1]));
gchar * album_name = g_strdup(koto_utils_unquote_string(fields[2])); gchar * album_name = g_strdup(koto_utils_unquote_string(fields[2]));
gchar * album_art = (fields[3] != NULL) ? g_strdup(koto_utils_unquote_string(fields[3])) : NULL; gchar * album_art = (fields[3] != NULL) ? g_strdup(koto_utils_unquote_string(fields[3])) : NULL;
gchar * album_genres = (fields[4] != NULL) ? g_strdup(koto_utils_unquote_string(fields[4])) : NULL;
KotoAlbum * album = koto_album_new_with_uuid(artist, album_uuid); // Create our album KotoAlbum * album = koto_album_new_with_uuid(artist, album_uuid); // Create our album
@ -125,6 +126,8 @@ int process_albums(
album_name, // Set name album_name, // Set name
"art-path", "art-path",
album_art, // Set art path if any album_art, // Set art path if any
"preparsed-genres",
album_genres,
NULL NULL
); );
@ -134,6 +137,7 @@ int process_albums(
g_free(album_uuid); g_free(album_uuid);
g_free(artist_uuid); g_free(artist_uuid);
g_free(album_name); g_free(album_name);
g_free(album_genres);
if (album_art != NULL) { if (album_art != NULL) {
g_free(album_art); g_free(album_art);
@ -224,7 +228,9 @@ int process_tracks(
gchar * album_uuid = g_strdup(koto_utils_unquote_string(fields[2])); gchar * album_uuid = g_strdup(koto_utils_unquote_string(fields[2]));
gchar * name = g_strdup(koto_utils_unquote_string(fields[3])); gchar * name = g_strdup(koto_utils_unquote_string(fields[3]));
guint * disc_num = (guint*) g_ascii_strtoull(fields[4], NULL, 10); guint * disc_num = (guint*) g_ascii_strtoull(fields[4], NULL, 10);
guint * position = (guint*) g_ascii_strtoull(fields[5], NULL, 10); guint64 * position = (guint64*) g_ascii_strtoull(fields[5], NULL, 10);
guint64 * duration = (guint64*) g_ascii_strtoull(fields[6], NULL, 10);
gchar * genres = g_strdup(koto_utils_unquote_string(fields[7]));
KotoTrack * track = koto_track_new_with_uuid(track_uuid); // Create our file KotoTrack * track = koto_track_new_with_uuid(track_uuid); // Create our file
@ -240,13 +246,22 @@ int process_tracks(
disc_num, disc_num,
"position", "position",
position, position,
"duration",
duration,
"preparsed-genres",
genres,
NULL NULL
); );
g_free(name);
int track_paths = sqlite3_exec(koto_db, g_strdup_printf("SELECT id, path FROM libraries_tracks WHERE track_id=\"%s\"", track_uuid), process_track_paths, track, NULL); // Process all pathes associated with the track int track_paths = sqlite3_exec(koto_db, g_strdup_printf("SELECT id, path FROM libraries_tracks WHERE track_id=\"%s\"", track_uuid), process_track_paths, track, NULL); // Process all pathes associated with the track
if (track_paths != SQLITE_OK) { // Failed to read the paths if (track_paths != SQLITE_OK) { // Failed to read the paths
g_warning("Failed to read paths associated with track %s: %s", track_uuid, sqlite3_errmsg(koto_db)); g_warning("Failed to read paths associated with track %s: %s", track_uuid, sqlite3_errmsg(koto_db));
g_free(track_uuid);
g_free(artist_uuid);
g_free(album_uuid);
return 1; return 1;
} }
@ -264,7 +279,6 @@ int process_tracks(
g_free(track_uuid); g_free(track_uuid);
g_free(artist_uuid); g_free(artist_uuid);
g_free(album_uuid); g_free(album_uuid);
g_free(name);
return 0; return 0;
} }

View file

@ -39,6 +39,7 @@ enum {
PROP_ALBUM_NAME, PROP_ALBUM_NAME,
PROP_ART_PATH, PROP_ART_PATH,
PROP_ARTIST_UUID, PROP_ARTIST_UUID,
PROP_ALBUM_PREPARED_GENRES,
N_PROPERTIES N_PROPERTIES
}; };
@ -64,6 +65,8 @@ struct _KotoAlbum {
gchar * art_path; gchar * art_path;
gchar * artist_uuid; gchar * artist_uuid;
GList * genres;
GList * tracks; GList * tracks;
GHashTable * paths; GHashTable * paths;
@ -147,6 +150,14 @@ static void koto_album_class_init(KotoAlbumClass * c) {
G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
); );
props[PROP_ALBUM_PREPARED_GENRES] = g_param_spec_string(
"preparsed-genres",
"Preparsed Genres",
"Preparsed Genres",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props); g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
album_signals[SIGNAL_TRACK_ADDED] = g_signal_new( album_signals[SIGNAL_TRACK_ADDED] = g_signal_new(
@ -178,6 +189,7 @@ static void koto_album_class_init(KotoAlbumClass * c) {
static void koto_album_init(KotoAlbum * self) { static void koto_album_init(KotoAlbum * self) {
self->has_album_art = FALSE; self->has_album_art = FALSE;
self->genres = NULL;
self->tracks = NULL; self->tracks = NULL;
self->paths = g_hash_table_new(g_str_hash, g_str_equal); self->paths = g_hash_table_new(g_str_hash, g_str_equal);
} }
@ -196,17 +208,34 @@ void koto_album_add_track(
gchar * track_uuid = koto_track_get_uuid(track); gchar * track_uuid = koto_track_get_uuid(track);
if (g_list_index(self->tracks, track_uuid) == -1) { // Haven't already added the track if (g_list_index(self->tracks, track_uuid) != -1) { // Have added it already
koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary return;
self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_track_helpers_sort_tracks_by_uuid, NULL);
g_signal_emit(
self,
album_signals[SIGNAL_TRACK_ADDED],
0,
track
);
} }
koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary
self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_track_helpers_sort_tracks_by_uuid, NULL);
GList * track_genres = koto_track_get_genres(track); // Get the genres for the track
GList * current_genre_list;
gchar * existing_genres_as_string = koto_utils_join_string_list(self->genres, ";");
for (current_genre_list = track_genres; current_genre_list != NULL; current_genre_list = current_genre_list->next) { // Iterate over each item in the track genres
gchar * track_genre = current_genre_list->data; // Get this genre
if (koto_utils_string_contains_substring(existing_genres_as_string, track_genre)) { // Genres list contains this genre
continue;
}
self->genres = g_list_append(self->genres, g_strdup(track_genre)); // Duplicate the genre and add it to our list
}
g_signal_emit(
self,
album_signals[SIGNAL_TRACK_ADDED],
0,
track
);
} }
void koto_album_commit(KotoAlbum * self) { void koto_album_commit(KotoAlbum * self) {
@ -214,18 +243,23 @@ void koto_album_commit(KotoAlbum * self) {
koto_album_set_album_art(self, ""); // Set to an empty string koto_album_set_album_art(self, ""); // Set to an empty string
} }
gchar * genres_string = koto_utils_join_string_list(self->genres, ";");
gchar * commit_op = g_strdup_printf( gchar * commit_op = g_strdup_printf(
"INSERT INTO albums(id, artist_id, name, art_path)" "INSERT INTO albums(id, artist_id, name, art_path, genres)"
"VALUES('%s', '%s', quote(\"%s\"), quote(\"%s\"))" "VALUES('%s', '%s', quote(\"%s\"), quote(\"%s\"), '%s')"
"ON CONFLICT(id) DO UPDATE SET artist_id=excluded.artist_id, name=excluded.name, art_path=excluded.art_path;", "ON CONFLICT(id) DO UPDATE SET artist_id=excluded.artist_id, name=excluded.name, art_path=excluded.art_path, genres=excluded.genres;",
self->uuid, self->uuid,
self->artist_uuid, self->artist_uuid,
self->name, self->name,
self->art_path self->art_path,
genres_string
); );
new_transaction(commit_op, "Failed to write our album to the database", FALSE); new_transaction(commit_op, "Failed to write our album to the database", FALSE);
g_free(genres_string);
GHashTableIter paths_iter; GHashTableIter paths_iter;
g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths
gpointer lib_uuid_ptr, album_rel_path_ptr; gpointer lib_uuid_ptr, album_rel_path_ptr;
@ -302,6 +336,14 @@ void koto_album_find_album_art(KotoAlbum * self) {
closedir(dir); closedir(dir);
} }
GList * koto_album_get_genres(KotoAlbum * self) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return NULL;
}
return self->genres;
}
static void koto_album_get_property( static void koto_album_get_property(
GObject * obj, GObject * obj,
guint prop_id, guint prop_id,
@ -357,6 +399,9 @@ static void koto_album_set_property(
case PROP_ARTIST_UUID: case PROP_ARTIST_UUID:
koto_album_set_artist_uuid(self, g_value_get_string(val)); koto_album_set_artist_uuid(self, g_value_get_string(val));
break; break;
case PROP_ALBUM_PREPARED_GENRES:
koto_album_set_preparsed_genres(self, g_strdup(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;
@ -573,6 +618,30 @@ void koto_album_set_as_current_playlist(KotoAlbum * self) {
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
} }
void koto_album_set_preparsed_genres(
KotoAlbum * self,
gchar * genrelist
) {
if (!KOTO_IS_ALBUM(self)) { // Not an album
return;
}
if (!koto_utils_is_string_valid(genrelist)) { // If it is an empty string
return;
}
GList * preparsed_genres_list = koto_utils_string_to_string_list(genrelist, ";");
if (g_list_length(preparsed_genres_list) == 0) { // No genres
g_list_free(preparsed_genres_list);
return;
}
// TODO: Do a pass on in first memory optimization phase to ensure string elements are freed.
g_list_free_full(self->genres, NULL); // Free the existing genres list
self->genres = preparsed_genres_list;
};
KotoAlbum * koto_album_new(gchar * artist_uuid) { KotoAlbum * koto_album_new(gchar * artist_uuid) {
if (!koto_utils_is_string_valid(artist_uuid)) { // Invalid artist UUID provided if (!koto_utils_is_string_valid(artist_uuid)) { // Invalid artist UUID provided
return NULL; return NULL;

View file

@ -72,12 +72,12 @@ void index_folder(
KotoAlbum * album = koto_album_new(artist_uuid); KotoAlbum * album = koto_album_new(artist_uuid);
koto_album_set_path(album, self, full_path); koto_album_set_path(album, self, full_path);
koto_album_commit(album); // Save to database immediately
koto_cartographer_add_album(koto_maps, album); // Add our album to the cartographer koto_cartographer_add_album(koto_maps, album); // Add our album to the cartographer
koto_artist_add_album(artist, album); // Add the album koto_artist_add_album(artist, album); // Add the album
index_folder(self, full_path, depth); // Index inside the album index_folder(self, full_path, depth); // Index inside the album
koto_album_commit(album); // Save to database immediately
g_free(artist_name); g_free(artist_name);
} else if (depth == 3) { // Possibly CD within album } else if (depth == 3) { // Possibly CD within album
gchar ** split = g_strsplit(full_path, G_DIR_SEPARATOR_S, -1); gchar ** split = g_strsplit(full_path, G_DIR_SEPARATOR_S, -1);

View file

@ -250,6 +250,11 @@ void koto_album_set_path(
const gchar * fixed_path const gchar * fixed_path
); );
void koto_album_set_preparsed_genres(
KotoAlbum * self,
gchar * genrelist
);
/** /**
* File / Track Functions * File / Track Functions
**/ **/
@ -267,13 +272,17 @@ void koto_track_commit(KotoTrack * self);
guint koto_track_get_disc_number(KotoTrack * self); guint koto_track_get_disc_number(KotoTrack * self);
guint64 koto_track_get_duration(KotoTrack * self);
GList * koto_track_get_genres(KotoTrack * self);
GVariant * koto_track_get_metadata_vardict(KotoTrack * self); GVariant * koto_track_get_metadata_vardict(KotoTrack * self);
gchar * koto_track_get_path(KotoTrack * self); gchar * koto_track_get_path(KotoTrack * self);
gchar * koto_track_get_name(KotoTrack * self); gchar * koto_track_get_name(KotoTrack * self);
guint koto_track_get_position(KotoTrack * self); guint64 koto_track_get_position(KotoTrack * self);
gchar * koto_track_get_uniqueish_key(KotoTrack * self); gchar * koto_track_get_uniqueish_key(KotoTrack * self);
@ -305,6 +314,16 @@ void koto_track_set_cd(
guint cd guint cd
); );
void koto_track_set_duration(
KotoTrack * self,
guint64 duration
);
void koto_track_set_genres(
KotoTrack * self,
char * genrelist
);
void koto_track_set_parsed_name( void koto_track_set_parsed_name(
KotoTrack * self, KotoTrack * self,
gchar * new_parsed_name gchar * new_parsed_name
@ -318,7 +337,12 @@ void koto_track_set_path(
void koto_track_set_position( void koto_track_set_position(
KotoTrack * self, KotoTrack * self,
guint pos guint64 pos
);
void koto_track_set_preparsed_genres(
KotoTrack * self,
gchar * genrelist
); );
void koto_track_update_metadata(KotoTrack * self); void koto_track_update_metadata(KotoTrack * self);

View file

@ -24,6 +24,25 @@
extern KotoCartographer * koto_maps; extern KotoCartographer * koto_maps;
GHashTable * genre_replacements;
void koto_track_helpers_init() {
genre_replacements = g_hash_table_new(g_str_hash, g_str_equal);
gchar * corrected_genre_hiphop = g_strdup("hip-hop");
gchar * correct_genre_indie = g_strdup("indie");
gchar * correct_genre_scifi = g_strdup("sci-fi");
g_hash_table_insert(genre_replacements, g_strdup("alternative/indie"), correct_genre_indie); // Change Alternative/Indie (lowercased) to indie
g_hash_table_insert(genre_replacements, g_strdup("rap-&-hip-hop"), corrected_genre_hiphop); // Change rap-&-hip-hop to just hip-hop
g_hash_table_insert(genre_replacements, g_strdup("science-fiction"), correct_genre_scifi); // Change science-fiction to sci-fi
}
gchar * koto_track_helpers_get_corrected_genre(gchar * original_genre) {
gchar * lookedup_genre = g_hash_table_lookup(genre_replacements, original_genre); // Look up the genre
return koto_utils_is_string_valid(lookedup_genre) ? lookedup_genre : original_genre;
}
gchar * koto_track_helpers_get_name_for_file( gchar * koto_track_helpers_get_name_for_file(
const gchar * path, const gchar * path,
gchar * optional_artist_name gchar * optional_artist_name

View file

@ -17,6 +17,10 @@
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
void koto_track_helpers_init();
gchar * koto_track_helpers_get_corrected_genre(gchar * original_genre);
gchar * koto_track_helpers_get_name_for_file( gchar * koto_track_helpers_get_name_for_file(
const gchar * path, const gchar * path,
gchar * optional_artist_name gchar * optional_artist_name

View file

@ -37,8 +37,10 @@ struct _KotoTrack {
gchar * parsed_name; gchar * parsed_name;
guint cd; guint cd;
guint position; guint64 position;
guint * playback_position; guint64 duration;
guint64 * playback_position;
GList * genres;
gboolean do_initial_index; gboolean do_initial_index;
}; };
@ -54,7 +56,9 @@ enum {
PROP_PARSED_NAME, PROP_PARSED_NAME,
PROP_CD, PROP_CD,
PROP_POSITION, PROP_POSITION,
PROP_DURATION,
PROP_PLAYBACK_POSITION, PROP_PLAYBACK_POSITION,
PROP_PREPARSED_GENRES,
N_PROPERTIES N_PROPERTIES
}; };
@ -133,31 +137,52 @@ static void koto_track_class_init(KotoTrackClass * c) {
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
); );
props[PROP_POSITION] = g_param_spec_uint( props[PROP_POSITION] = g_param_spec_uint64(
"position", "position",
"Position in Audiobook, Album, etc.", "Position in Audiobook, Album, etc.",
"Position in Audiobook, Album, etc.", "Position in Audiobook, Album, etc.",
0, 0,
G_MAXUINT16, G_MAXUINT64,
0, 0,
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( props[PROP_DURATION] = g_param_spec_uint64(
"duration",
"Duration of Track",
"Duration of Track",
0,
G_MAXUINT64,
0,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
);
props[PROP_PLAYBACK_POSITION] = g_param_spec_uint64(
"playback-position", "playback-position",
"Current playback position", "Current playback position",
"Current playback position", "Current playback position",
0, 0,
G_MAXUINT16, G_MAXUINT64,
0, 0,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE
); );
props[PROP_PREPARSED_GENRES] = g_param_spec_string(
"preparsed-genres",
"Preparsed Genres",
"Preparsed Genres",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props); g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
} }
static void koto_track_init(KotoTrack * self) { static void koto_track_init(KotoTrack * self) {
self->duration = 0; // Initialize our duration
self->genres = NULL; // Initialize our genres list
self->paths = g_hash_table_new(g_str_hash, g_str_equal); // Create our hash table of paths self->paths = g_hash_table_new(g_str_hash, g_str_equal); // Create our hash table of paths
self->position = 0; // Initialize our duration
} }
static void koto_track_get_property( static void koto_track_get_property(
@ -226,10 +251,16 @@ static void koto_track_set_property(
koto_track_set_cd(self, g_value_get_uint(val)); koto_track_set_cd(self, g_value_get_uint(val));
break; break;
case PROP_POSITION: case PROP_POSITION:
koto_track_set_position(self, g_value_get_uint(val)); koto_track_set_position(self, g_value_get_uint64(val));
break; break;
case PROP_PLAYBACK_POSITION: case PROP_PLAYBACK_POSITION:
self->playback_position = GUINT_TO_POINTER(g_value_get_uint(val)); self->playback_position = GUINT_TO_POINTER(g_value_get_uint64(val));
break;
case PROP_DURATION:
koto_track_set_duration(self, g_value_get_uint64(val));
break;
case PROP_PREPARSED_GENRES:
koto_track_set_preparsed_genres(self, g_strdup(g_value_get_string(val)));
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
@ -250,9 +281,12 @@ void koto_track_commit(KotoTrack * self) {
self->album_uuid = g_strdup(""); self->album_uuid = g_strdup("");
} }
gchar * commit_msg = "INSERT INTO tracks(id, artist_id, album_id, name, disc, position)" \ gchar * commit_msg = "INSERT INTO tracks(id, artist_id, album_id, name, disc, position, duration, genres)" \
"VALUES('%s', '%s', '%s', quote(\"%s\"), %d, %d)" \ "VALUES('%s', '%s', '%s', quote(\"%s\"), %d, %d, %d, '%s')" \
"ON CONFLICT(id) DO UPDATE SET album_id=excluded.album_id, artist_id=excluded.artist_id, name=excluded.name, disc=excluded.disc, position=excluded.position;"; "ON CONFLICT(id) DO UPDATE SET album_id=excluded.album_id, artist_id=excluded.artist_id, name=excluded.name, disc=excluded.disc, position=excluded.position, duration=excluded.duration, genres=excluded.genres;";
// Combine our list items into a semi-colon separated string
gchar * genres = koto_utils_join_string_list(self->genres, ";"); // Join our GList of strings into a single
gchar * commit_op = g_strdup_printf( gchar * commit_op = g_strdup_printf(
commit_msg, commit_msg,
@ -261,13 +295,17 @@ void koto_track_commit(KotoTrack * self) {
self->album_uuid, self->album_uuid,
g_strescape(self->parsed_name, NULL), g_strescape(self->parsed_name, NULL),
(int) self->cd, (int) self->cd,
(int) self->position (int) self->position,
(int) self->duration,
genres
); );
if (new_transaction(commit_op, "Failed to write our file to the database", FALSE) != SQLITE_OK) { if (new_transaction(commit_op, "Failed to write our file to the database", FALSE) != SQLITE_OK) {
g_message("Failed with song: %s", self->parsed_name); return;
} }
g_free(genres); // Free the genres string
GHashTableIter paths_iter; GHashTableIter paths_iter;
g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths
gpointer lib_uuid_ptr, track_rel_path_ptr; gpointer lib_uuid_ptr, track_rel_path_ptr;
@ -292,6 +330,14 @@ guint koto_track_get_disc_number(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->cd : 1; return KOTO_IS_TRACK(self) ? self->cd : 1;
} }
guint64 koto_track_get_duration(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->duration : 0;
}
GList * koto_track_get_genres(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->genres : NULL;
}
GVariant * koto_track_get_metadata_vardict(KotoTrack * self) { GVariant * koto_track_get_metadata_vardict(KotoTrack * self) {
if (!KOTO_IS_TRACK(self)) { if (!KOTO_IS_TRACK(self)) {
return NULL; return NULL;
@ -374,7 +420,7 @@ gchar * koto_track_get_path(KotoTrack * self) {
return path; return path;
} }
guint koto_track_get_position(KotoTrack * self) { guint64 koto_track_get_position(KotoTrack * self) {
return KOTO_IS_TRACK(self) ? self->position : 0; return KOTO_IS_TRACK(self) ? self->position : 0;
} }
@ -473,6 +519,10 @@ void koto_track_set_cd(
KotoTrack * self, KotoTrack * self,
guint cd guint cd
) { ) {
if (!KOTO_IS_TRACK(self)) {
return;
}
if (cd == 0) { // No change really if (cd == 0) { // No change really
return; return;
} }
@ -481,10 +531,68 @@ void koto_track_set_cd(
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CD]); g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CD]);
} }
void koto_track_set_duration(
KotoTrack * self,
guint64 duration
) {
if (!KOTO_IS_TRACK(self)) {
return;
}
if (duration == 0) {
return;
}
self->duration = duration;
}
void koto_track_set_genres(
KotoTrack * self,
char * genrelist
) {
if (!KOTO_IS_TRACK(self)) {
return;
}
if (!koto_utils_is_string_valid((gchar*) genrelist)) { // If it is an empty string
return;
}
gchar ** genres = g_strsplit(genrelist, ";", -1); // Split on semicolons for each genre, e.g. Electronic;Rock
guint len = g_strv_length(genres);
if (len == 0) { // No genres
g_strfreev(genres); // Free the list
return;
}
for (guint i = 0; i < len; i++) { // Iterate over each item
gchar * genre = genres[i]; // Get the genre
gchar * lowercased_genre = g_utf8_strdown(g_strstrip(genre), -1); // Lowercase the genre
gchar * lowercased_hyphenated_genre = koto_utils_replace_string_all(lowercased_genre, " ", "-");
g_free(lowercased_genre); // Free the lowercase genre string since we no longer need it
gchar * corrected_genre = koto_track_helpers_get_corrected_genre(lowercased_hyphenated_genre); // Get any corrected genre
if (g_list_index(self->genres, corrected_genre) == -1) { // Don't have this genre added
self->genres = g_list_append(self->genres, g_strdup(corrected_genre)); // Add the genre to our list
}
g_free(lowercased_hyphenated_genre); // Free our remaining string
}
g_strfreev(genres); // Free the list of genres locally
}
void koto_track_set_parsed_name( void koto_track_set_parsed_name(
KotoTrack * self, KotoTrack * self,
gchar * new_parsed_name gchar * new_parsed_name
) { ) {
if (!KOTO_IS_TRACK(self)) {
return;
}
if (!koto_utils_is_string_valid(new_parsed_name)) { if (!koto_utils_is_string_valid(new_parsed_name)) {
return; return;
} }
@ -529,7 +637,7 @@ void koto_track_set_path(
void koto_track_set_position( void koto_track_set_position(
KotoTrack * self, KotoTrack * self,
guint pos guint64 pos
) { ) {
if (pos == 0) { // No position change really if (pos == 0) { // No position change really
return; return;
@ -550,7 +658,11 @@ void koto_track_update_metadata(KotoTrack * self) {
if ((t_file != NULL) && taglib_file_is_valid(t_file)) { // If we got the taglib file and it is valid if ((t_file != NULL) && taglib_file_is_valid(t_file)) { // If we got the taglib file and it is valid
TagLib_Tag * tag = taglib_file_tag(t_file); // Get our tag TagLib_Tag * tag = taglib_file_tag(t_file); // Get our tag
koto_track_set_genres(self, taglib_tag_genre(tag)); // Set our genres to any genres listed for the track
koto_track_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer koto_track_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer
const TagLib_AudioProperties * tag_props = taglib_file_audioproperties(t_file); // Get the audio properties of the file
koto_track_set_duration(self, taglib_audioproperties_length(tag_props)); // Get the length of the track and set it as our duration
} else { // Failed to get tag info } else { // Failed to get tag info
guint64 position = koto_track_helpers_get_position_based_on_file_name(g_path_get_basename(optimal_track_path)); // Get the likely position guint64 position = koto_track_helpers_get_position_based_on_file_name(g_path_get_basename(optimal_track_path)); // Get the likely position
koto_track_set_position(self, position); // Set our position koto_track_set_position(self, position); // Set our position
@ -560,6 +672,30 @@ void koto_track_update_metadata(KotoTrack * self) {
taglib_file_free(t_file); // Free the file taglib_file_free(t_file); // Free the file
} }
void koto_track_set_preparsed_genres(
KotoTrack * self,
gchar * genrelist
) {
if (!KOTO_IS_TRACK(self)) {
return;
}
if (!koto_utils_is_string_valid(genrelist)) { // If it is an empty string
return;
}
GList * preparsed_genres_list = koto_utils_string_to_string_list(genrelist, ";");
if (g_list_length(preparsed_genres_list) == 0) { // No genres
g_list_free(preparsed_genres_list);
return;
}
// TODO: Do a pass on in first memory optimization phase to ensure string elements are freed.
g_list_free_full(self->genres, NULL); // Free the existing genres list
self->genres = preparsed_genres_list;
}
KotoTrack * koto_track_new( KotoTrack * koto_track_new(
const gchar * artist_uuid, const gchar * artist_uuid,
const gchar * album_uuid, const gchar * album_uuid,

View file

@ -133,6 +133,7 @@ void koto_button_show_image(
); );
void koto_button_unflatten(KotoButton * self); void koto_button_unflatten(KotoButton * self);
void koto_button_unset_pseudoactive_styling(KotoButton * self); void koto_button_unset_pseudoactive_styling(KotoButton * self);
G_END_DECLS G_END_DECLS

View file

@ -205,7 +205,7 @@ static void koto_expander_init(KotoExpander * self) {
// koto_expander_set_icon_name will set the icon for our inner KotoButton for this Expander // koto_expander_set_icon_name will set the icon for our inner KotoButton for this Expander
void koto_expander_set_icon_name( void koto_expander_set_icon_name(
KotoExpander *self, KotoExpander * self,
gchar * icon gchar * icon
) { ) {
if (!KOTO_IS_EXPANDER(self)) { // Not a KotoExpander if (!KOTO_IS_EXPANDER(self)) { // Not a KotoExpander

View file

@ -36,7 +36,7 @@ KotoExpander * koto_expander_new_with_button(
GtkWidget * koto_expander_get_content(KotoExpander * self); GtkWidget * koto_expander_get_content(KotoExpander * self);
void koto_expander_set_icon_name( void koto_expander_set_icon_name(
KotoExpander *self, KotoExpander * self,
gchar * icon gchar * icon
); );

View file

@ -20,6 +20,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
#include "koto-utils.h"
extern GtkWindow * main_window; extern GtkWindow * main_window;
@ -102,6 +103,32 @@ gchar * koto_utils_get_filename_without_extension(gchar * filename) {
return stripped_file_name; return stripped_file_name;
} }
gchar * koto_utils_join_string_list (
GList * list,
gchar * sep
) {
gchar * liststring = NULL;
GList * cur_list;
for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) { // For each item in the list
gchar * current_item = cur_list->data;
if (!koto_utils_is_string_valid(current_item)) { // Not a valid string
continue;
}
gchar * item_plus_sep = g_strdup_printf("%s%s", current_item, sep);
if (koto_utils_is_string_valid(liststring)) { // Is a valid string
gchar * new_string = g_strconcat(liststring, item_plus_sep, NULL);
g_free(liststring);
liststring = new_string;
} else { // Don't have any content yet
liststring = item_plus_sep;
}
}
return (liststring == NULL) ? g_strdup("") : liststring;
}
gboolean koto_utils_is_string_valid(gchar * str) { gboolean koto_utils_is_string_valid(gchar * str) {
return ((str != NULL) && (g_strcmp0(str, "") != 0)); return ((str != NULL) && (g_strcmp0(str, "") != 0));
} }
@ -123,15 +150,42 @@ gchar * koto_utils_replace_string_all(
gchar * find, gchar * find,
gchar * repl gchar * repl
) { ) {
gchar * cleaned_string = "";
gchar ** split = g_strsplit(str, find, -1); // Split on find gchar ** split = g_strsplit(str, find, -1); // Split on find
for (guint i = 0; i < g_strv_length(split); i++) { // For each split guint split_len = g_strv_length(split);
cleaned_string = g_strjoin(repl, cleaned_string, split[i], NULL); // Join the strings with our replace string
if (split_len == 1) { // Only one item
g_strfreev(split);
return g_strdup(str); // Just set to the string we were provided
} }
g_strfreev(split); return g_strdup(g_strjoinv(repl, split));
return cleaned_string; }
gboolean koto_utils_string_contains_substring(
gchar * s,
gchar * sub
) {
gchar ** separated_string = g_strsplit(s, sub, -1); // Split on our substring
gboolean contains = (g_strv_length(separated_string) > 1);
g_strfreev(separated_string);
return contains;
}
GList * koto_utils_string_to_string_list(
gchar * s,
gchar * sep
) {
GList * list = NULL;
gchar ** separated_strings = g_strsplit(s, sep, -1); // Split on separator for the string
for (guint i = 0; i < g_strv_length(separated_strings); i++) { // Iterate over each item
gchar * item = separated_strings[i];
list = g_list_append(list, g_strdup(item));
}
g_strfreev(separated_strings); // Free our strings
return list;
} }
gchar * koto_utils_unquote_string(gchar * s) { gchar * koto_utils_unquote_string(gchar * s) {

View file

@ -34,6 +34,11 @@ gchar * koto_utils_gboolean_to_string(gboolean b);
gchar * koto_utils_get_filename_without_extension(gchar * filename); gchar * koto_utils_get_filename_without_extension(gchar * filename);
gchar * koto_utils_join_string_list(
GList * list,
gchar * sep
);
gboolean koto_utils_is_string_valid(gchar * str); gboolean koto_utils_is_string_valid(gchar * str);
void koto_utils_mkdir(gchar * path); void koto_utils_mkdir(gchar * path);
@ -49,6 +54,16 @@ gchar * koto_utils_replace_string_all(
gchar * repl gchar * repl
); );
gboolean koto_utils_string_contains_substring(
gchar * s,
gchar * sub
);
GList * koto_utils_string_to_string_list(
gchar * s,
gchar * sep
);
gchar * koto_utils_unquote_string(gchar * s); gchar * koto_utils_unquote_string(gchar * s);
G_END_DECLS G_END_DECLS

View file

@ -22,6 +22,7 @@
#include "db/cartographer.h" #include "db/cartographer.h"
#include "db/db.h" #include "db/db.h"
#include "db/loaders.h" #include "db/loaders.h"
#include "indexer/track-helpers.h"
#include "playback/engine.h" #include "playback/engine.h"
#include "playback/media-keys.h" #include "playback/media-keys.h"
#include "playback/mimes.h" #include "playback/mimes.h"
@ -88,6 +89,7 @@ int main (
gtk_init(); gtk_init();
gst_init(&argc, &argv); gst_init(&argc, &argv);
koto_track_helpers_init(); // Init our track helpers (primarily our genre replacement hashtable)
koto_paths_setup(); // Set up our required paths koto_paths_setup(); // Set up our required paths
supported_mimes_hash = g_hash_table_new(g_str_hash, g_str_equal); supported_mimes_hash = g_hash_table_new(g_str_hash, g_str_equal);