Implemented ID3 tag parsing in our KotoIndexedFile leveraging taglib, refactored our mimetype code.

Implemented KotoIndexedArtist as well as KotoIndexedAlbum, including moving the file indexing capabilities into the KotoIndexedAlbum (subject to change).

Added g_debug messages instead of using g_message for debug purposes. Re-introduced Koto Utils with new koto_utils_get_filename_without_extension, since that is used to get the extensionless file name across both album fetching and base file name fetching.
This commit is contained in:
Joshua Strobl 2021-02-16 17:15:10 +02:00
parent 9b6fa4593a
commit 304c7635da
11 changed files with 815 additions and 125 deletions

289
src/indexer/album.c Normal file
View file

@ -0,0 +1,289 @@
/* album.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 <magic.h>
#include <stdio.h>
#include "album.h"
#include "file.h"
#include "koto-utils.h"
struct _KotoIndexedAlbum {
GObject parent_instance;
gchar *path;
gchar *name;
gchar *art_path;
GHashTable *files;
gboolean has_album_art;
};
G_DEFINE_TYPE(KotoIndexedAlbum, koto_indexed_album, G_TYPE_OBJECT);
enum {
PROP_0,
PROP_PATH,
PROP_ALBUM_NAME,
PROP_ART_PATH,
N_PROPERTIES
};
static GParamSpec *props[N_PROPERTIES] = { NULL, };
static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
static void koto_indexed_album_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
static void koto_indexed_album_class_init(KotoIndexedAlbumClass *c) {
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS(c);
gobject_class->set_property = koto_indexed_album_set_property;
gobject_class->get_property = koto_indexed_album_get_property;
props[PROP_PATH] = g_param_spec_string(
"path",
"Path",
"Path to Album",
NULL,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
props[PROP_ALBUM_NAME] = g_param_spec_string(
"name",
"Name",
"Name of Album",
NULL,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
props[PROP_ART_PATH] = g_param_spec_string(
"art-path",
"Path to Artwork",
"Path to Artwork",
NULL,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
}
static void koto_indexed_album_init(KotoIndexedAlbum *self) {
self->has_album_art = FALSE;
}
void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedFile *file) {
if (file == NULL) { // Not a file
return;
}
if (self->files == NULL) { // No HashTable yet
self->files = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
}
gchar *file_name;
g_object_get(file, "parsed-name", &file_name, NULL);
if (g_hash_table_contains(self->files, file_name)) { // If we have this file already
g_free(file_name);
return;
}
g_hash_table_insert(self->files, file_name, file); // Add the file
}
static void koto_indexed_album_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
KotoIndexedAlbum *self = KOTO_INDEXED_ALBUM(obj);
switch (prop_id) {
case PROP_PATH:
g_value_set_string(val, self->path);
break;
case PROP_ALBUM_NAME:
g_value_set_string(val, self->name);
break;
case PROP_ART_PATH:
g_value_set_string(val, koto_indexed_album_get_album_art(self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self) {
return g_strdup((self->has_album_art) ? self->art_path : "");
}
GList* koto_indexed_album_get_files(KotoIndexedAlbum *self) {
if (self->files == NULL) { // No HashTable yet
self->files = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
}
return g_hash_table_get_values(self->files);
}
void koto_indexed_album_set_album_art(KotoIndexedAlbum *self, const gchar *album_art) {
if (album_art == NULL) { // Not valid album art
return;
}
if (self->art_path != NULL) {
g_free(self->art_path);
}
self->art_path = g_strdup(album_art);
self->has_album_art = TRUE;
}
void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedFile *file) {
if (file == NULL) { // Not a file
return;
}
if (self->files == NULL) { // No HashTable yet
self->files = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
}
gchar *file_name;
g_object_get(file, "parsed-name", &file_name, NULL);
g_hash_table_remove(self->files, file_name);
}
void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *file_name) {
if (file_name == NULL) {
return;
}
KotoIndexedFile *file = g_hash_table_lookup(self->files, file_name); // Get the files
koto_indexed_album_remove_file(self, file);
}
void koto_indexed_album_set_album_name(KotoIndexedAlbum *self, const gchar *album_name) {
if (album_name == NULL) { // Not valid album name
return;
}
if (self->name != NULL) {
g_free(self->name);
}
self->name = g_strdup(album_name);
}
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_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;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
void koto_indexed_album_update_path(KotoIndexedAlbum *self, const gchar* new_path) {
if (new_path == NULL) {
return;
}
DIR *dir = opendir(new_path); // Attempt to open our directory
if (dir == NULL) {
return;
}
if (self->path != NULL) {
g_free(self->path);
}
self->path = g_strdup(new_path);
koto_indexed_album_set_album_name(self, g_path_get_basename(self->path)); // Update our album name based on the base name
magic_t magic_cookie = magic_open(MAGIC_MIME);
if (magic_cookie == NULL) {
return;
}
if (magic_load(magic_cookie, NULL) != 0) {
magic_close(magic_cookie);
return;
}
struct dirent *entry;
while ((entry = readdir(dir))) {
if (g_str_has_prefix(entry->d_name, ".")) { // Reference to parent dir, self, or a hidden item
continue; // Skip
}
if (entry->d_type != DT_REG) { // If this is not a regular file
continue; // Skip
}
gchar *full_path = g_strdup_printf("%s%s%s", self->path, G_DIR_SEPARATOR_S, entry->d_name);
const char *mime_type = magic_file(magic_cookie, full_path);
if (mime_type == NULL) { // Failed to get the mimetype
g_free(full_path);
continue; // Skip
}
if (g_str_has_prefix(mime_type, "image/") && !self->has_album_art) { // Is an image file and doesn't have album art yet
gchar *album_art_no_ext = g_strdup(koto_utils_get_filename_without_extension(entry->d_name)); // Get the name of the file without the extension
gchar *lower_art = g_strdup(g_utf8_strdown(album_art_no_ext, -1)); // Lowercase
if (
(g_strrstr(lower_art, "Small") == NULL) && // Not Small
(g_strrstr(lower_art, "back") == NULL) // Not back
) {
koto_indexed_album_set_album_art(self, full_path);
}
g_free(album_art_no_ext);
g_free(lower_art);
} else if (g_str_has_prefix(mime_type, "audio/")) { // Is an audio file
KotoIndexedFile *file = koto_indexed_file_new(full_path);
if (file != NULL) { // Is a file
koto_indexed_album_add_file(self, file); // Add our file
}
}
g_free(full_path);
}
magic_close(magic_cookie);
}
KotoIndexedAlbum* koto_indexed_album_new(const gchar *path) {
return g_object_new(KOTO_TYPE_INDEXED_ALBUM,
"path", path,
NULL
);
}

View file

@ -17,17 +17,22 @@
#pragma once #pragma once
#include <glib-2.0/glib-object.h> #include <glib-2.0/glib-object.h>
#include "file.h"
G_BEGIN_DECLS G_BEGIN_DECLS
#define KOTO_INDEXED_ALBUM_TYPE koto_indexed_album_get_type() #define KOTO_TYPE_INDEXED_ALBUM koto_indexed_album_get_type()
G_DECLARE_FINAL_TYPE (KotoIndexedAlbum, koto_indexed_album, KOTO, INDEXED_ALBUM, GObject); G_DECLARE_FINAL_TYPE (KotoIndexedAlbum, koto_indexed_album, KOTO, INDEXED_ALBUM, GObject);
KotoIndexedAlbum* koto_indexed_album_new(const gchar *path); KotoIndexedAlbum* koto_indexed_album_new(const gchar *path);
void add_song(KotoIndexedAlbum *self, KotoIndexedFile *file); void koto_indexed_album_add_file(KotoIndexedAlbum *self, KotoIndexedFile *file);
KotoIndexedFile** get_songs(KotoIndexedAlbum *self); gchar* koto_indexed_album_get_album_art(KotoIndexedAlbum *self);
void remove_song(KotoIndexedAlbum *self, KotoIndexedFile *file); GList* koto_indexed_album_get_files(KotoIndexedAlbum *self);
void remove_song_by_name(KotoIndexedAlbum *self, gchar *file_name); void koto_indexed_album_remove_file(KotoIndexedAlbum *self, KotoIndexedFile *file);
void koto_indexed_album_remove_file_by_name(KotoIndexedAlbum *self, const gchar *file_name);
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_update_path(KotoIndexedAlbum *self, const gchar *path);
G_END_DECLS G_END_DECLS

196
src/indexer/artist.c Normal file
View file

@ -0,0 +1,196 @@
/* artist.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 "album.h"
#include "artist.h"
struct _KotoIndexedArtist {
GObject parent_instance;
gchar *path;
gboolean has_artist_art;
gchar *artist_name;
GHashTable *albums;
};
G_DEFINE_TYPE(KotoIndexedArtist, koto_indexed_artist, G_TYPE_OBJECT);
enum {
PROP_0,
PROP_PATH,
PROP_ARTIST_NAME,
N_PROPERTIES
};
static GParamSpec *props[N_PROPERTIES] = { NULL, };
static void koto_indexed_artist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec);
static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec);
static void koto_indexed_artist_class_init(KotoIndexedArtistClass *c) {
GObjectClass *gobject_class;
gobject_class = G_OBJECT_CLASS(c);
gobject_class->set_property = koto_indexed_artist_set_property;
gobject_class->get_property = koto_indexed_artist_get_property;
props[PROP_PATH] = g_param_spec_string(
"path",
"Path",
"Path to Artist",
NULL,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
props[PROP_ARTIST_NAME] = g_param_spec_string(
"name",
"Name",
"Name of Artist",
NULL,
G_PARAM_CONSTRUCT|G_PARAM_EXPLICIT_NOTIFY|G_PARAM_READWRITE
);
g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
}
static void koto_indexed_artist_init(KotoIndexedArtist *self) {
self->has_artist_art = FALSE;
self->albums = NULL; // Set to null initially maybe
}
static void koto_indexed_artist_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj);
switch (prop_id) {
case PROP_PATH:
g_value_set_string(val, self->path);
break;
case PROP_ARTIST_NAME:
g_value_set_string(val, self->artist_name);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
static void koto_indexed_artist_set_property(GObject *obj, guint prop_id, const GValue *val, GParamSpec *spec){
KotoIndexedArtist *self = KOTO_INDEXED_ARTIST(obj);
switch (prop_id) {
case PROP_PATH:
koto_indexed_artist_update_path(self, g_value_get_string(val));
break;
case PROP_ARTIST_NAME:
koto_indexed_artist_set_artist_name(self, g_value_get_string(val));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
break;
}
}
void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) {
if (album == NULL) { // No album really defined
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_name;
g_object_get(album, "name", &album_name, NULL);
if (album_name == NULL) {
g_free(album_name);
return;
}
if (g_hash_table_contains(self->albums, album_name)) { // If we have this album already
g_free(album_name);
return;
}
g_hash_table_insert(self->albums, album_name, album); // Add the album
}
GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self) {
if (self->albums == NULL) { // No HashTable yet
self->albums = g_hash_table_new(g_str_hash, g_str_equal); // Create a new HashTable
}
return g_hash_table_get_values(self->albums);
}
void koto_indexed_artist_remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album) {
if (album == NULL) { // No album defined
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_name;
g_object_get(album, "name", &album_name, NULL);
g_hash_table_remove(self->albums, album_name);
}
void koto_indexed_artist_remove_album_by_name(KotoIndexedArtist *self, gchar *album_name) {
if (album_name == NULL) {
return;
}
KotoIndexedAlbum *album = g_hash_table_lookup(self->albums, album_name); // Get the album
koto_indexed_artist_remove_album(self, album);
}
void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path) {
if (new_path == NULL) { // No path really
return;
}
if (self->path != NULL) { // Already have a path set
g_free(self->path); // Free
}
self->path = g_strdup(new_path);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PATH]);
}
void koto_indexed_artist_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name) {
if (artist_name == NULL) { // No artist name
return;
}
if (self->artist_name != NULL) { // Has artist name
g_free(self->artist_name);
}
self->artist_name = g_strdup(artist_name);
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_NAME]);
}
KotoIndexedArtist* koto_indexed_artist_new(const gchar *path) {
return g_object_new(KOTO_TYPE_INDEXED_ARTIST,
"path", path,
"name", g_path_get_basename(path),
NULL
);
}

View file

@ -17,17 +17,22 @@
#pragma once #pragma once
#include <glib-2.0/glib-object.h> #include <glib-2.0/glib-object.h>
#include "album.h"
G_BEGIN_DECLS G_BEGIN_DECLS
#define KOTO_INDEXED_ARTIST_TYPE koto_indexed_artist_get_type() #define KOTO_TYPE_INDEXED_ARTIST koto_indexed_artist_get_type()
G_DECLARE_FINAL_TYPE (KotoIndexedArtist, koto_indexed_artist, KOTO, INDEXED_ARTIST, GObject); G_DECLARE_FINAL_TYPE (KotoIndexedArtist, koto_indexed_artist, KOTO, INDEXED_ARTIST, GObject);
KotoIndexedArtist* koto_indexed_artist_new(const gchar *path); KotoIndexedArtist* koto_indexed_artist_new(const gchar *path);
void add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); void koto_indexed_artist_add_album(KotoIndexedArtist *self, KotoIndexedAlbum *album);
KotoIndexedAlbum** get_albums(KotoIndexedArtist *self); guint koto_indexed_artist_find_album_with_name(gconstpointer *album_data, gconstpointer *album_name_data);
void remove_album(KotoIndexedArtist *self, KotoIndexedAlbum *album); GList* koto_indexed_artist_get_albums(KotoIndexedArtist *self);
void remove_album_by_name(KotoIndexedArtist *self, gchar *album_name); 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_set_artist_name(KotoIndexedArtist *self, const gchar *artist_name);
void koto_indexed_artist_update_path(KotoIndexedArtist *self, const gchar *new_path);
void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data);
G_END_DECLS G_END_DECLS

View file

@ -17,27 +17,14 @@
#include <dirent.h> #include <dirent.h>
#include <magic.h> #include <magic.h>
#include <stdio.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <taglib/tag_c.h>
#include "album.h"
#include "artist.h"
#include "file.h" #include "file.h"
#include "file-indexer.h" #include "file-indexer.h"
struct KotoIndexedAlbum {
GObject parent_instance;
gboolean has_album_art;
gchar *album_name;
gchar *art_path;
gchar *path;
GHashTable *songs;
};
struct _KotoIndexedArtist {
GObject parent_instance;
gboolean has_artist_art;
gchar *artist_name;
GHashTable *albums;
gchar *path;
};
struct _KotoIndexedLibrary { struct _KotoIndexedLibrary {
GObject parent_instance; GObject parent_instance;
gchar *path; gchar *path;
@ -73,12 +60,60 @@ static void koto_indexed_library_class_init(KotoIndexedLibraryClass *c) {
); );
g_object_class_install_properties(gobject_class, N_PROPERTIES, props); g_object_class_install_properties(gobject_class, N_PROPERTIES, props);
taglib_id3v2_set_default_text_encoding(TagLib_ID3v2_UTF8); // Ensure our id3v2 text encoding is UTF-8
} }
static void koto_indexed_library_init(KotoIndexedLibrary *self) { static void koto_indexed_library_init(KotoIndexedLibrary *self) {
self->music_artists = g_hash_table_new(g_str_hash, g_str_equal); self->music_artists = g_hash_table_new(g_str_hash, g_str_equal);
} }
void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist) {
if (artist == NULL) { // No artist
return;
}
if (self->music_artists == NULL) { // Not a HashTable
self->music_artists = g_hash_table_new(g_str_hash, g_str_equal);
}
gchar *artist_name;
g_object_get(artist, "name", &artist_name, NULL);
if (g_hash_table_contains(self->music_artists, artist_name)) { // Already have the artist
g_free(artist_name);
return;
}
g_hash_table_insert(self->music_artists, artist_name, artist); // Add the artist
}
KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar *artist_name) {
if (artist_name == NULL) {
return NULL;
}
if (self->music_artists == NULL) { // Not a HashTable
return NULL;
}
return g_hash_table_lookup(self->music_artists, (KotoIndexedArtist*) artist_name);
}
void koto_indexed_library_remove_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist) {
if (artist == NULL) {
return;
}
if (self->music_artists == NULL) { // Not a HashTable
return;
}
gchar *artist_name;
g_object_get(artist, "name", &artist_name, NULL);
g_hash_table_remove(self->music_artists, artist_name); // Remove the artist
}
static void koto_indexed_library_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) { static void koto_indexed_library_get_property(GObject *obj, guint prop_id, GValue *val, GParamSpec *spec) {
KotoIndexedLibrary *self = KOTO_INDEXED_LIBRARY(obj); KotoIndexedLibrary *self = KOTO_INDEXED_LIBRARY(obj);
@ -98,7 +133,6 @@ static void koto_indexed_library_set_property(GObject *obj, guint prop_id, const
switch (prop_id) { switch (prop_id) {
case PROP_PATH: case PROP_PATH:
self->path = g_strdup(g_value_get_string(val)); self->path = g_strdup(g_value_get_string(val));
g_message("Set to %s", self->path);
start_indexing(self); start_indexing(self);
break; break;
default: default:
@ -131,11 +165,14 @@ void start_indexing(KotoIndexedLibrary *self) {
return; return;
} }
index_folder(self, self->path); index_folder(self, self->path, 0);
magic_close(self->magic_cookie); magic_close(self->magic_cookie);
g_hash_table_foreach(self->music_artists, output_artists, NULL);
} }
void index_folder(KotoIndexedLibrary *self, gchar *path) { void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth) {
depth++;
DIR *dir = opendir(path); // Attempt to open our directory DIR *dir = opendir(path); // Attempt to open our directory
if (dir == NULL) { if (dir == NULL) {
@ -145,54 +182,88 @@ void index_folder(KotoIndexedLibrary *self, gchar *path) {
struct dirent *entry; struct dirent *entry;
while ((entry = readdir(dir))) { while ((entry = readdir(dir))) {
if (!g_str_has_prefix(entry->d_name, ".")) { // Not a reference to parent dir, self, or a hidden item if (g_str_has_prefix(entry->d_name, ".")) { // A reference to parent dir, self, or a hidden item
continue;
}
gchar *full_path = g_strdup_printf("%s%s%s", path, G_DIR_SEPARATOR_S, entry->d_name); gchar *full_path = g_strdup_printf("%s%s%s", path, G_DIR_SEPARATOR_S, entry->d_name);
if (entry->d_type == DT_DIR) { // Directory if (entry->d_type == DT_DIR) { // Directory
index_folder(self, full_path); // Index this directory if (depth == 1) { // If we are following FOLDER/ARTIST/ALBUM then this would be artist
} else if (entry->d_type == DT_REG) { // Regular file KotoIndexedArtist *artist = koto_indexed_artist_new(full_path); // Attempt to get the artist
index_file(self, full_path); // Index the file gchar *artist_name;
g_object_get(artist,
"name", &artist_name,
NULL
);
koto_indexed_library_add_artist(self, artist); // Add the artist
index_folder(self, full_path, depth); // Index this directory
g_free(artist_name);
} 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
KotoIndexedAlbum *album = koto_indexed_album_new(full_path);
KotoIndexedArtist *artist = koto_indexed_library_get_artist(self, artist_name); // Get the artist
if (artist == NULL) {
continue;
}
koto_indexed_artist_add_album(artist, album); // Add the album
g_free(artist_name);
}
} }
g_free(full_path); g_free(full_path);
} }
}
closedir(dir); // Close the directory closedir(dir); // Close the directory
} }
void index_file(KotoIndexedLibrary *self, gchar *path) { void output_artists(gpointer artist_key, gpointer artist_ptr, gpointer data) {
const char *mime_type = magic_file(self->magic_cookie, path); KotoIndexedArtist *artist = (KotoIndexedArtist*) artist_ptr;
gchar *artist_name;
g_object_get(artist, "name", &artist_name, NULL);
if (mime_type == NULL) { // Failed to get the mimetype g_debug("Artist: %s", artist_name);
return; GList *albums = koto_indexed_artist_get_albums(artist); // Get the albums for this artist
if (albums != NULL) {
g_debug("Length of Albums: %d", g_list_length(albums));
} }
gchar** mime_info = g_strsplit(mime_type, ";", 2); // Only care about our first item GList *a;
for (a = albums; a != NULL; a = a->next) {
KotoIndexedAlbum *album = (KotoIndexedAlbum*) a->data;
gchar *artwork = koto_indexed_album_get_album_art(album);
gchar *album_name;
g_object_get(album, "name", &album_name, NULL);
g_debug("Album Art: %s", artwork);
g_debug("Album Name: %s", album_name);
if ( GList *files = koto_indexed_album_get_files(album); // Get the files for the album
g_str_has_prefix(mime_info[0], "audio/") || // Is audio GList *f;
g_str_has_prefix(mime_info[0], "image/") // Is image
) {
//g_message("File Name: %s", path);
}
if (g_str_has_prefix(mime_info[0], "audio/")) { // Is an audio file for (f = files; f != NULL; f = f->next) {
KotoIndexedFile *file = koto_indexed_file_new(path); KotoIndexedFile *file = (KotoIndexedFile*) f->data;
gchar *filepath; gchar *filepath;
gchar *parsed_name; gchar *parsed_name;
guint *pos;
g_object_get(file, g_object_get(file,
"path", &filepath, "path", &filepath,
"parsed-name", &parsed_name, "parsed-name", &parsed_name,
"position", &pos,
NULL); 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(filepath);
g_free(parsed_name); g_free(parsed_name);
g_object_unref(file);
} }
}
g_strfreev(mime_info); // Free our mimeinfo
} }
KotoIndexedLibrary* koto_indexed_library_new(const gchar *path) { KotoIndexedLibrary* koto_indexed_library_new(const gchar *path) {

View file

@ -17,6 +17,7 @@
#pragma once #pragma once
#include <glib-2.0/glib-object.h> #include <glib-2.0/glib-object.h>
#include "artist.h"
G_BEGIN_DECLS G_BEGIN_DECLS
@ -25,8 +26,10 @@ G_DECLARE_FINAL_TYPE(KotoIndexedLibrary, koto_indexed_library, KOTO, INDEXED_LIB
KotoIndexedLibrary* koto_indexed_library_new(const gchar *path); KotoIndexedLibrary* koto_indexed_library_new(const gchar *path);
void koto_indexed_library_add_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist);
KotoIndexedArtist* koto_indexed_library_get_artist(KotoIndexedLibrary *self, gchar* artist_name);
void koto_indexed_library_remove_artist(KotoIndexedLibrary *self, KotoIndexedArtist *artist);
void start_indexing(KotoIndexedLibrary *self); void start_indexing(KotoIndexedLibrary *self);
void index_folder(KotoIndexedLibrary *self, gchar *path); void index_folder(KotoIndexedLibrary *self, gchar *path, guint depth);
void index_file(KotoIndexedLibrary *self, gchar *path);
G_END_DECLS G_END_DECLS

View file

@ -16,17 +16,19 @@
*/ */
#include <glib-2.0/glib.h> #include <glib-2.0/glib.h>
#include <taglib/tag_c.h>
#include "file.h" #include "file.h"
#include "koto-utils.h"
struct _KotoIndexedFile { struct _KotoIndexedFile {
GObject parent_instance; GObject parent_instance;
gchar *path; gchar *path;
gchar *file_name; gchar *file_name;
gchar *parsed_name; gchar *parsed_name;
gchar *artist; gchar *artist;
gchar *album; gchar *album;
guint *position;
guint position;
gboolean acquired_metadata_from_id3; gboolean acquired_metadata_from_id3;
}; };
@ -131,7 +133,7 @@ static void koto_indexed_file_get_property(GObject *obj, guint prop_id, GValue *
g_value_set_string(val, self->parsed_name); g_value_set_string(val, self->parsed_name);
break; break;
case PROP_POSITION: case PROP_POSITION:
g_value_set_uint(val, self->position); g_value_set_uint(val, GPOINTER_TO_UINT(self->position));
break; break;
default: default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec);
@ -144,8 +146,7 @@ static void koto_indexed_file_set_property(GObject *obj, guint prop_id, const GV
switch (prop_id) { switch (prop_id) {
case PROP_PATH: case PROP_PATH:
self->path = g_strdup(g_value_get_string(val)); // Update our path koto_indexed_file_update_path(self, g_value_get_string(val)); // Update the path
koto_indexed_file_set_file_name(self, g_path_get_basename(self->path)); // Update our file name
break; break;
case PROP_ARTIST: case PROP_ARTIST:
self->artist = g_strdup(g_value_get_string(val)); self->artist = g_strdup(g_value_get_string(val));
@ -160,7 +161,7 @@ static void koto_indexed_file_set_property(GObject *obj, guint prop_id, const GV
koto_indexed_file_set_parsed_name(self, g_strdup(g_value_get_string(val))); koto_indexed_file_set_parsed_name(self, g_strdup(g_value_get_string(val)));
break; break;
case PROP_POSITION: case PROP_POSITION:
self->position = g_value_get_uint(val); koto_indexed_file_set_position(self, g_value_get_uint(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);
@ -168,6 +169,65 @@ static void koto_indexed_file_set_property(GObject *obj, guint prop_id, const GV
} }
} }
void koto_indexed_file_parse_name(KotoIndexedFile *self) {
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
gchar **split = g_strsplit(copied_file_name, self->artist, -1); // Split whenever we encounter the artist
copied_file_name = g_strjoinv("", split); // Remove the artist
g_strfreev(split);
}
if (self->artist != NULL && strcmp(self->album, "") != 0) { // If we have album set
gchar **split = g_strsplit(copied_file_name, self->album, -1); // Split whenever we encounter the album
copied_file_name = g_strjoinv("", split); // Remove the album
g_strfreev(split);
split = g_strsplit(copied_file_name, g_utf8_strdown(self->album, g_utf8_strlen(self->album, -1)), -1); // Split whenever we encounter the album lowercased
copied_file_name = g_strjoin("", split, NULL); // Remove the lowercased album
g_strfreev(split);
}
if (g_str_match_string(copied_file_name, "-", FALSE)) { // Contains - like a heathen
gchar **split = g_strsplit(copied_file_name, " - ", -1); // Split whenever we encounter " - "
copied_file_name = g_strjoin("", split, NULL); // Remove entirely
g_strfreev(split);
if (g_str_match_string(copied_file_name, "-", FALSE)) { // If we have any stragglers
split = g_strsplit(copied_file_name, "-", -1); // Split whenever we encounter -
copied_file_name = g_strjoin("", split, NULL); // Remove entirely
g_strfreev(split);
}
}
gchar *file_without_ext = koto_utils_get_filename_without_extension(copied_file_name);
g_free(copied_file_name);
gchar **split = g_regex_split_simple("^([\\d]+)\\s", file_without_ext, G_REGEX_JAVASCRIPT_COMPAT, 0);
if (g_strv_length(split) > 1) { // Has positional info at the beginning of the file
gchar *num = split[1];
g_free(file_without_ext); // Free the prior name
file_without_ext = g_strdup(split[2]); // Set to our second item which is the rest of the song name without the prefixed numbers
if ((strcmp(num, "0") == 0) || (strcmp(num, "00") == 0)) { // Is exactly zero
koto_indexed_file_set_position(self, 0); // Set position to 0
} else { // Either starts with 0 (like 09) or doesn't start with it at all
guint64 potential_pos = g_ascii_strtoull(num, NULL, 10); // Attempt to convert
if (potential_pos != 0) { // Got a legitimate position
koto_indexed_file_set_position(self, potential_pos);
}
}
}
g_strfreev(split);
koto_indexed_file_set_parsed_name(self, file_without_ext);
g_free(file_without_ext);
}
void koto_indexed_file_set_file_name(KotoIndexedFile *self, gchar *new_file_name) { void koto_indexed_file_set_file_name(KotoIndexedFile *self, gchar *new_file_name) {
if (new_file_name == NULL) { if (new_file_name == NULL) {
return; return;
@ -206,64 +266,45 @@ void koto_indexed_file_set_parsed_name(KotoIndexedFile *self, gchar *new_parsed_
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PARSED_NAME]); g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PARSED_NAME]);
} }
void koto_indexed_file_parse_name(KotoIndexedFile *self) { void koto_indexed_file_set_position(KotoIndexedFile *self, guint pos) {
gchar *copied_file_name = g_strdelimit(g_strdup(self->file_name), "_", ' '); // Replace _ with whitespace for starters if (pos == 0) { // No position change really
return;
if (self->artist != NULL && strcmp(self->artist, "") != 0) { // If we have artist set
gchar **split = g_strsplit(copied_file_name, self->artist, -1); // Split whenever we encounter the artist
copied_file_name = g_strjoinv("", split); // Remove the artist
g_strfreev(split);
} }
if (self->artist != NULL && strcmp(self->album, "") != 0) { // If we have album set self->position = GUINT_TO_POINTER(pos);
gchar **split = g_strsplit(copied_file_name, self->album, -1); // Split whenever we encounter the album g_object_notify_by_pspec(G_OBJECT(self), props[PROP_POSITION]);
copied_file_name = g_strjoinv("", split); // Remove the album
g_strfreev(split);
split = g_strsplit(copied_file_name, g_utf8_strdown(self->album, g_utf8_strlen(self->album, -1)), -1); // Split whenever we encounter the album lowercased
copied_file_name = g_strjoin("", split, NULL); // Remove the lowercased album
g_strfreev(split);
} }
if (g_str_match_string(copied_file_name, "-", FALSE)) { // Contains - like a heathen void koto_indexed_file_update_metadata(KotoIndexedFile *self) {
gchar **split = g_strsplit(copied_file_name, " - ", -1); // Split whenever we encounter " - " TagLib_File *t_file = taglib_file_new(self->path); // Get a taglib file for this file
copied_file_name = g_strjoin("", split, NULL); // Remove entirely
g_strfreev(split);
if (g_str_match_string(copied_file_name, "-", FALSE)) { // If we have any stragglers if ((t_file != NULL) && taglib_file_is_valid(t_file)) { // If we got the taglib file and it is valid
split = g_strsplit(copied_file_name, "-", -1); // Split whenever we encounter - self->acquired_metadata_from_id3 = TRUE;
copied_file_name = g_strjoin("", split, NULL); // Remove entirely TagLib_Tag *tag = taglib_file_tag(t_file); // Get our tag
g_strfreev(split); koto_indexed_file_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_file_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer
} }
gchar **split = g_strsplit(copied_file_name, ".", -1); // Split every time we see . taglib_tag_free_strings(); // Free strings
g_free(copied_file_name); taglib_file_free(t_file); // Free the file
guint len_of_extension_split = g_strv_length(split);
if (len_of_extension_split == 2) { // Only have two elements
copied_file_name = g_strdup(split[0]); // Get the first element
} else {
gchar *new_parsed_name = "";
for (guint i = 0; i < len_of_extension_split - 1; i++) { // Iterate over everything except the last item
if (g_strcmp0(new_parsed_name, "") == 0) { // Currently empty
new_parsed_name = g_strdup(split[i]); // Just duplicate this string
} else {
gchar *tmp_copy = g_strdup(new_parsed_name);
g_free(new_parsed_name); // Free the old
new_parsed_name = g_strdup(g_strjoin(".", tmp_copy, split[i], NULL)); // Join the two strings with a . again and duplicate it, setting it to our new_parsed_name
g_free(tmp_copy); // Free our temporary copy
}
} }
copied_file_name = g_strdup(new_parsed_name); void koto_indexed_file_update_path(KotoIndexedFile *self, const gchar *new_path) {
g_free(new_parsed_name); if (new_path == NULL) {
return;
} }
g_strfreev(split); if (self->path != NULL) { // Already have a path
g_free(self->path); // Free it
}
koto_indexed_file_set_parsed_name(self, copied_file_name); self->path = g_strdup(new_path); // Duplicate the path and set it
g_free(copied_file_name); koto_indexed_file_update_metadata(self); // Attempt to get ID3 info
koto_indexed_file_set_file_name(self, g_path_get_basename(self->path)); // Update our file name
g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PATH]);
} }
KotoIndexedFile* koto_indexed_file_new(const gchar *path) { KotoIndexedFile* koto_indexed_file_new(const gchar *path) {

View file

@ -25,8 +25,11 @@ G_DECLARE_FINAL_TYPE(KotoIndexedFile, koto_indexed_file, KOTO, INDEXED_FILE, GOb
KotoIndexedFile* koto_indexed_file_new(const gchar *path); KotoIndexedFile* koto_indexed_file_new(const gchar *path);
void koto_indexed_file_parse_name(KotoIndexedFile *self);
void koto_indexed_file_set_file_name(KotoIndexedFile *self, gchar *new_file_name); void koto_indexed_file_set_file_name(KotoIndexedFile *self, gchar *new_file_name);
void koto_indexed_file_set_parsed_name(KotoIndexedFile *self, gchar *new_parsed_name); void koto_indexed_file_set_parsed_name(KotoIndexedFile *self, gchar *new_parsed_name);
void koto_indexed_file_parse_name(KotoIndexedFile *self); void koto_indexed_file_set_position(KotoIndexedFile *self, guint pos);
void koto_indexed_file_update_metadata(KotoIndexedFile *self);
void koto_indexed_file_update_path(KotoIndexedFile *self, const gchar *new_path);
G_END_DECLS G_END_DECLS

48
src/koto-utils.c Normal file
View file

@ -0,0 +1,48 @@
/* koto-utils.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>
gchar* koto_utils_get_filename_without_extension(gchar *filename) {
gchar *trimmed_file_name = g_strdup(filename);
gchar **split = g_strsplit(filename, ".", -1); // Split every time we see .
g_free(trimmed_file_name);
guint len_of_extension_split = g_strv_length(split);
if (len_of_extension_split == 2) { // Only have two elements
trimmed_file_name = g_strdup(split[0]); // Get the first element
} else {
gchar *new_parsed_name = "";
for (guint i = 0; i < len_of_extension_split - 1; i++) { // Iterate over everything except the last item
if (g_strcmp0(new_parsed_name, "") == 0) { // Currently empty
new_parsed_name = g_strdup(split[i]); // Just duplicate this string
} else {
gchar *tmp_copy = g_strdup(new_parsed_name);
g_free(new_parsed_name); // Free the old
new_parsed_name = g_strdup(g_strjoin(".", tmp_copy, split[i], NULL)); // Join the two strings with a . again and duplicate it, setting it to our new_parsed_name
g_free(tmp_copy); // Free our temporary copy
}
}
trimmed_file_name = g_strdup(new_parsed_name);
g_free(new_parsed_name);
}
gchar *stripped_file_name = g_strstrip(trimmed_file_name); // Strip leading and trailing whitespace
g_free(trimmed_file_name);
return stripped_file_name;
}

25
src/koto-utils.h Normal file
View file

@ -0,0 +1,25 @@
/* koto-utils.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.h>
G_BEGIN_DECLS
gchar* koto_utils_get_filename_without_extension(gchar *filename);
G_END_DECLS

View file

@ -1,4 +1,6 @@
koto_sources = [ koto_sources = [
'indexer/album.c',
'indexer/artist.c',
'indexer/file.c', 'indexer/file.c',
'indexer/file-indexer.c', 'indexer/file-indexer.c',
'main.c', 'main.c',
@ -7,6 +9,7 @@ koto_sources = [
'koto-headerbar.c', 'koto-headerbar.c',
'koto-nav.c', 'koto-nav.c',
'koto-playerbar.c', 'koto-playerbar.c',
'koto-utils.c',
'koto-window.c', 'koto-window.c',
] ]
@ -15,6 +18,7 @@ koto_deps = [
dependency('gio-2.0', version: '>= 2.66'), dependency('gio-2.0', version: '>= 2.66'),
dependency('gtk+-3.0', version: '>= 3.24'), dependency('gtk+-3.0', version: '>= 3.24'),
dependency('libmagic', version: '>=5.39'), dependency('libmagic', version: '>=5.39'),
dependency('taglib_c', version: '>=1.11'),
] ]
gnome = import('gnome') gnome = import('gnome')