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
);
}