From d1b17206d01ebd9c94aa20778cac4b0eed6bfa4a Mon Sep 17 00:00:00 2001 From: Jorg Schuler Date: Mon, 28 Nov 2005 16:20:40 +0000 Subject: New API for thumbnail support: see src/itdb.h for details. * src/itdb.h: Introduced Itdb_Artwork and ItdbThumbType and changed Itdb_Image to Itdb_Thumb throughout the source. * src/itdb_artwork.c: new file as backend for Itdb_Artwork support (new, free, duplicate, get_thumb_by_type, add_thumbnail, remove_thumbnail, remove_thumbnails), as well as for the Itdb_Thumb support (new, free, duplicate, get_gdk_pixbuf, get_filename) * src/itdb_track.c: new functions for artwork support (set_thumbnails, remove_thumbnails) * src/ithumb-writer.c: added support to write thumbnails in addition to existing thumbnails * src/db-artwork-parcer.c: (mhod3_get_ithmb_filename) * src/itdb_itunesdb.c: (update_artwork_info) * tests/test-covers.c: updated to new API. * tests/test-write-covers.c: updated to new API. Known issues: iTunes wipes off our thumbnails. git-svn-id: https://gtkpod.svn.sf.net/svnroot/gtkpod/libgpod/trunk@1180 f01d2545-417e-4e96-918e-98f8d0dbbcb6 --- ChangeLog | 29 +++ src/Makefile.am | 1 + src/db-artwork-parser.c | 24 +-- src/db-artwork-writer.c | 50 ++--- src/db-image-parser.c | 157 +--------------- src/db-image-parser.h | 5 +- src/itdb.h | 190 +++++++++++++------ src/itdb_artwork.c | 429 +++++++++++++++++++++++++++++++++++++++++++ src/itdb_itunesdb.c | 29 ++- src/itdb_track.c | 122 ++++--------- src/ithumb-writer.c | 456 ++++++++++++++++++++++++++++++++++------------ tests/test-covers.c | 24 +-- tests/test-write-covers.c | 4 +- 13 files changed, 1054 insertions(+), 466 deletions(-) create mode 100644 src/itdb_artwork.c diff --git a/ChangeLog b/ChangeLog index 17f7a6c..2c2ff0a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,32 @@ +2005-11-28 Jorg Schuler + + New API for thumbnail support: see src/itdb.h for details. + + * src/itdb.h: Introduced Itdb_Artwork and ItdbThumbType and + changed Itdb_Image to Itdb_Thumb throughout the source. + + * src/itdb_artwork.c: new file as backend for Itdb_Artwork support + (new, free, duplicate, get_thumb_by_type, add_thumbnail, + remove_thumbnail, remove_thumbnails), as well as for the + Itdb_Thumb support (new, free, duplicate, get_gdk_pixbuf, + get_filename) + + * src/itdb_track.c: new functions for artwork support + (set_thumbnails, remove_thumbnails) + + * src/ithumb-writer.c: added support to write thumbnails in + addition to existing thumbnails + + * src/db-artwork-parcer.c: (mhod3_get_ithmb_filename) + + * src/itdb_itunesdb.c: (update_artwork_info) + + * tests/test-covers.c: updated to new API. + + * tests/test-write-covers.c: updated to new API. + + Known issues: iTunes wipes off our thumbnails. + 2005-11-24 Jorg Schuler * overall changes to support itdb_image_get_gdk_pixbuf(). Run diff --git a/src/Makefile.am b/src/Makefile.am index 29378ea..dfc0a66 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,6 +2,7 @@ lib_LTLIBRARIES = libgpod.la libgpod_la_SOURCES = \ itdb.h \ + itdb_artwork.c \ itdb_itunesdb.c \ itdb_playlist.c \ itdb_private.h \ diff --git a/src/db-artwork-parser.c b/src/db-artwork-parser.c index 8be8be7..fe88b7e 100644 --- a/src/db-artwork-parser.c +++ b/src/db-artwork-parser.c @@ -108,28 +108,19 @@ static char * mhod3_get_ithmb_filename (MhodHeaderArtworkType3 *mhod3, Itdb_iTunesDB *db) { - char *paths[] = {"iPod_Control", "Artwork", NULL, NULL}; char *filename; - char *result; g_assert (mhod3 != NULL); g_assert (db != NULL); filename = get_utf16_string (mhod3->string, mhod3->string_len); - if ((filename == NULL) || (strlen (filename) < 2)) { - return NULL; - } - - paths[2] = filename+1; - result = itdb_resolve_path (db->mountpoint, (const char **)paths); - g_free (filename); - return result; + return filename; } static int parse_mhod_3 (DBParseContext *ctx, Itdb_iTunesDB *db, - Itdb_Image *image, GError *error) + Itdb_Thumb *thumb, GError *error) { MhodHeader *mhod; MhodHeaderArtworkType3 *mhod3; @@ -147,7 +138,7 @@ parse_mhod_3 (DBParseContext *ctx, Itdb_iTunesDB *db, if ((GINT_FROM_LE (mhod3->type) & 0x00FFFFFF) != MHOD_ARTWORK_TYPE_FILE_NAME) { return -1; } - image->filename = mhod3_get_ithmb_filename (mhod3, db); + thumb->filename = mhod3_get_ithmb_filename (mhod3, db); dump_mhod_type_3 (mhod3); return 0; } @@ -157,7 +148,7 @@ parse_mhni (DBParseContext *ctx, iPodSong *song, GError *error) { MhniHeader *mhni; DBParseContext *mhod_ctx; - Itdb_Image *thumb; + Itdb_Thumb *thumb; mhni = db_parse_context_get_m_header (ctx, MhniHeader, "mhni"); if (mhni == NULL) { @@ -168,7 +159,8 @@ parse_mhni (DBParseContext *ctx, iPodSong *song, GError *error) thumb = ipod_image_new_from_mhni (mhni, song->itdb); if (thumb != NULL) { - song->thumbnails = g_list_append (song->thumbnails, thumb); + song->artwork->thumbnails = + g_list_append (song->artwork->thumbnails, thumb); } mhod_ctx = db_parse_context_get_sub_context (ctx, ctx->header_len); @@ -249,8 +241,8 @@ parse_mhii (DBParseContext *ctx, Itdb_iTunesDB *db, GError *error) g_warning ("iTunesDB and ArtworkDB artwork sizes don't match (%d %d)", song->artwork_size , GINT_FROM_LE (mhii->orig_img_size)); } - song->artwork_size = GINT_FROM_LE (mhii->orig_img_size)-1; - song->image_id = GINT_FROM_LE (mhii->image_id); + song->artwork->artwork_size = GINT_FROM_LE (mhii->orig_img_size)-1; + song->artwork->id = GINT_FROM_LE (mhii->image_id); #endif cur_offset = ctx->header_len; diff --git a/src/db-artwork-writer.c b/src/db-artwork-writer.c index 9a6924c..280c2b7 100644 --- a/src/db-artwork-writer.c +++ b/src/db-artwork-writer.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #define IPOD_MMAP_SIZE 2 * 1024 * 1024 @@ -284,7 +285,7 @@ init_header (iPodBuffer *buffer, gchar header_id[4], guint header_len) static int -write_mhod_type_3 (Itdb_Image *image, iPodBuffer *buffer) +write_mhod_type_3 (Itdb_Thumb *thumb, iPodBuffer *buffer) { MhodHeaderArtworkType3 *mhod; unsigned int total_bytes; @@ -292,7 +293,7 @@ write_mhod_type_3 (Itdb_Image *image, iPodBuffer *buffer) gunichar2 *utf16; int i; - g_assert (image->filename != NULL); + g_assert (thumb->filename != NULL); mhod = (MhodHeaderArtworkType3 *)init_header (buffer, "mhod", sizeof (MhodHeaderArtworkType3)); @@ -308,7 +309,7 @@ write_mhod_type_3 (Itdb_Image *image, iPodBuffer *buffer) mhod->type = GINT_TO_LE (3); mhod->mhod_version = GINT_TO_LE (2); - len = strlen (image->filename); + len = strlen (thumb->filename); /* number of bytes of the string encoded in UTF-16 */ mhod->string_len = GINT_TO_LE (2*len); @@ -317,7 +318,7 @@ write_mhod_type_3 (Itdb_Image *image, iPodBuffer *buffer) if (ipod_buffer_maybe_grow (buffer, total_bytes + 2*len) != 0) { return -1; } - utf16 = g_utf8_to_utf16 (image->filename, -1, NULL, NULL, NULL); + utf16 = g_utf8_to_utf16 (thumb->filename, -1, NULL, NULL, NULL); if (utf16 == NULL) { return -1; } @@ -335,14 +336,14 @@ write_mhod_type_3 (Itdb_Image *image, iPodBuffer *buffer) } static int -write_mhni (Itdb_Image *image, int correlation_id, iPodBuffer *buffer) +write_mhni (Itdb_Thumb *thumb, int correlation_id, iPodBuffer *buffer) { MhniHeader *mhni; unsigned int total_bytes; int bytes_written; iPodBuffer *sub_buffer; - if (image == NULL) { + if (thumb == NULL) { return -1; } @@ -355,16 +356,16 @@ write_mhni (Itdb_Image *image, int correlation_id, iPodBuffer *buffer) mhni->total_len = GINT_TO_LE (total_bytes); mhni->correlation_id = GINT_TO_LE (correlation_id); - mhni->image_width = GINT16_TO_LE (image->width); - mhni->image_height = GINT16_TO_LE (image->height); - mhni->image_size = GINT32_TO_LE (image->size); - mhni->ithmb_offset = GINT32_TO_LE (image->offset); + mhni->image_width = GINT16_TO_LE (thumb->width); + mhni->image_height = GINT16_TO_LE (thumb->height); + mhni->image_size = GINT32_TO_LE (thumb->size); + mhni->ithmb_offset = GINT32_TO_LE (thumb->offset); sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes); if (sub_buffer == NULL) { return -1; } - bytes_written = write_mhod_type_3 (image, sub_buffer); + bytes_written = write_mhod_type_3 (thumb, sub_buffer); ipod_buffer_destroy (sub_buffer); if (bytes_written == -1) { return -1; @@ -382,14 +383,14 @@ write_mhni (Itdb_Image *image, int correlation_id, iPodBuffer *buffer) } static int -write_mhod (Itdb_Image *image, int correlation_id, iPodBuffer *buffer) +write_mhod (Itdb_Thumb *thumb, int correlation_id, iPodBuffer *buffer) { MhodHeader *mhod; unsigned int total_bytes; int bytes_written; iPodBuffer *sub_buffer; - if (image == NULL) { + if (thumb == NULL) { return -1; } @@ -405,7 +406,7 @@ write_mhod (Itdb_Image *image, int correlation_id, iPodBuffer *buffer) if (sub_buffer == NULL) { return -1; } - bytes_written = write_mhni (image, correlation_id, sub_buffer); + bytes_written = write_mhni (thumb, correlation_id, sub_buffer); ipod_buffer_destroy (sub_buffer); if (bytes_written == -1) { return -1; @@ -433,16 +434,16 @@ write_mhii (Itdb_Track *song, iPodBuffer *buffer) } total_bytes = GINT_FROM_LE (mhii->header_len); mhii->song_id = GINT64_TO_LE (song->dbid); - mhii->image_id = GUINT_TO_LE (song->image_id); + mhii->image_id = GUINT_TO_LE (song->artwork->id); /* Adding 1 to artwork_size since this is what iTunes 4.9 does (there * is a 1 difference between the artwork size in iTunesDB and the * artwork size in ArtworkDB) */ mhii->orig_img_size = GINT_TO_LE (song->artwork_size)+1; num_children = 0; - for (it = song->thumbnails; it != NULL; it = it->next) { + for (it = song->artwork->thumbnails; it != NULL; it = it->next) { iPodBuffer *sub_buffer; - Itdb_Image *thumb; + Itdb_Thumb *thumb; const IpodArtworkFormat *img_info; mhii->num_children = GINT_TO_LE (num_children); @@ -451,12 +452,15 @@ write_mhii (Itdb_Track *song, iPodBuffer *buffer) if (sub_buffer == NULL) { return -1; } - thumb = (Itdb_Image *)it->data; + thumb = (Itdb_Thumb *)it->data; img_info = ipod_get_artwork_info_from_type ( song->itdb->device, thumb->type); if (img_info == NULL) { return -1; } +/* printf("correlation id: %d, type: %d\n", */ +/* img_info->correlation_id, thumb->type); */ +/* printf("title: %s\n", song->title); */ bytes_written = write_mhod (thumb, img_info->correlation_id, sub_buffer); ipod_buffer_destroy (sub_buffer); @@ -496,7 +500,7 @@ write_mhli (Itdb_iTunesDB *db, iPodBuffer *buffer) iPodBuffer *sub_buffer; song = (Itdb_Track*)it->data; - if (song->image_id == 0) { + if (song->artwork->id == 0) { continue; } sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes); @@ -719,8 +723,8 @@ ipod_artwork_db_set_ids (Itdb_iTunesDB *db) Itdb_Track *song; song = (Itdb_Track *)it->data; - if (song->thumbnails != NULL) { - song->image_id = id; + if (song->artwork->thumbnails != NULL) { + song->artwork->id = id; id++; } } @@ -740,9 +744,9 @@ ipod_write_artwork_db (Itdb_iTunesDB *db) /* First, let's write the .ithmb files, this will create the various * thumbnails as well, and update the Itdb_Track items contained in * the database appropriately (ie set the 'artwork_count' and - * 'artwork_size' fields, as well as the 2 Itdb_Image fields + * 'artwork_size' fields, as well as the 2 Itdb_Thumb fields */ - itdb_write_ithumb_files (db, db->mountpoint); + itdb_write_ithumb_files (db); /* Now we can update the ArtworkDB file */ id_max = ipod_artwork_db_set_ids (db); diff --git a/src/db-image-parser.c b/src/db-image-parser.c index 372013c..ba4cda5 100644 --- a/src/db-image-parser.c +++ b/src/db-image-parser.c @@ -31,155 +31,8 @@ #include "db-artwork-parser.h" #include "db-image-parser.h" -#if HAVE_GDKPIXBUF -#include -#endif #include -static unsigned char * -unpack_RGB_565 (gushort *pixels, unsigned int bytes_len) -{ - unsigned char *result; - unsigned int i; - - result = g_malloc ((bytes_len/2) * 3); - if (result == NULL) { - return NULL; - } - for (i = 0; i < bytes_len/2; i++) { - gushort cur_pixel; - - cur_pixel = GINT16_FROM_LE (pixels[i]); - /* Unpack pixels */ - result[3*i] = (cur_pixel & RED_MASK) >> RED_SHIFT; - result[3*i+1] = (cur_pixel & GREEN_MASK) >> GREEN_SHIFT; - result[3*i+2] = (cur_pixel & BLUE_MASK) >> BLUE_SHIFT; - - /* Normalize color values so that they use a [0..255] range */ - result[3*i] <<= (8 - RED_BITS); - result[3*i+1] <<= (8 - GREEN_BITS); - result[3*i+2] <<= (8 - BLUE_BITS); - } - - return result; -} - - -static unsigned char * -get_pixel_data (Itdb_Image *image) -{ - unsigned char *result; - FILE *f; - int res; - - f = NULL; - result = g_malloc (image->size); - if (result == NULL) { - return NULL; - } - - f = fopen (image->filename, "r"); - if (f == NULL) { - g_print ("Failed to open %s: %s\n", - image->filename, strerror (errno)); - goto end; - } - - res = fseek (f, image->offset, SEEK_SET); - if (res != 0) { - g_print ("Seek to %d failed on %s: %s\n", - image->offset, image->filename, strerror (errno)); - goto end; - } - - res = fread (result, image->size, 1, f); - if (res != 1) { - g_print ("Failed to read %u bytes from %s: %s\n", - image->size, image->filename, strerror (errno)); - goto end; - } - fclose (f); - - return result; - - end: - if (f != NULL) { - fclose (f); - } - g_free (result); - - return NULL; -} - -unsigned char * -itdb_image_get_rgb_data (Itdb_Image *image) -{ - void *pixels565; - void *pixels; - - pixels565 = get_pixel_data (image); - if (pixels565 == NULL) { - return NULL; - } - - pixels = unpack_RGB_565 (pixels565, image->size); - g_free (pixels565); - - return pixels; - -} - -/* Convert the pixeldata in @image to a GdkPixbuf. - Since we want to have gdk-pixbuf dependency optional, a generic - gpointer is returned which you have to cast to (GdkPixbuf *) - yourself. If gdk-pixbuf is not installed the NULL pointer is - returned. - The returned GdkPixbuf must be freed with gdk_pixbuf_unref() after - use. */ -gpointer -itdb_image_get_gdk_pixbuf (Itdb_iTunesDB *itdb, Itdb_Image *image) -{ -#if HAVE_GDKPIXBUF - GdkPixbuf *result; - guchar *pixels; - const IpodArtworkFormat *img_info; - - g_return_val_if_fail (itdb, NULL); - g_return_val_if_fail (image, NULL); - - pixels = itdb_image_get_rgb_data (image); - if (pixels == NULL) - { - return NULL; - } - - img_info = ipod_get_artwork_info_from_type (itdb->device, - image->type); - - if (img_info == NULL) - { - g_print (_("Unable to obtain image info on image (type: %d, filename: '%s'\n)"), image->type, image->filename); - g_free (pixels); - return NULL; - } - - result = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, FALSE, - 8, image->width, image->height, - img_info->width*3, - (GdkPixbufDestroyNotify)g_free, - NULL); - - /* !! do not g_free(pixels) here: it will be freed when doing a - * gdk_pixbuf_unref() on the GdkPixbuf !! */ - - return result; -#else - return NULL; -#endif -} - - - static int image_type_from_corr_id (IpodDevice *ipod, int corr_id) { @@ -230,12 +83,12 @@ ipod_get_artwork_info_from_type (IpodDevice *ipod, int image_type) return formats; } -G_GNUC_INTERNAL Itdb_Image * +G_GNUC_INTERNAL Itdb_Thumb * ipod_image_new_from_mhni (MhniHeader *mhni, Itdb_iTunesDB *db) { - Itdb_Image *img; - img = g_new0 (Itdb_Image, 1); + Itdb_Thumb *img; + img = g_new0 (Itdb_Thumb, 1); if (img == NULL) { return NULL; } @@ -246,8 +99,8 @@ ipod_image_new_from_mhni (MhniHeader *mhni, Itdb_iTunesDB *db) img->type = image_type_from_corr_id (db->device, mhni->correlation_id); if ((img->type != IPOD_COVER_SMALL) && (img->type != IPOD_COVER_LARGE)) { - g_warning ("Unexpected cover type in mhni: %ux%u (%d)\n", - img->width, img->height, mhni->correlation_id); + g_warning ("Unexpected cover type in mhni: type %d, size: %ux%u (%d), offset: %d\n", + img->type, img->width, img->height, mhni->correlation_id, img->offset); g_free (img); return NULL; } diff --git a/src/db-image-parser.h b/src/db-image-parser.h index 8f3ef1c..3e42c62 100644 --- a/src/db-image-parser.h +++ b/src/db-image-parser.h @@ -40,11 +40,10 @@ #define BLUE_SHIFT 0 #define BLUE_MASK (((1 << BLUE_BITS)-1) << BLUE_SHIFT) -G_GNUC_INTERNAL Itdb_Image *ipod_image_new_from_mhni (MhniHeader *mhni, +G_GNUC_INTERNAL Itdb_Thumb *ipod_image_new_from_mhni (MhniHeader *mhni, Itdb_iTunesDB *db); -G_GNUC_INTERNAL int itdb_write_ithumb_files (Itdb_iTunesDB *db, - const char *mount_point); +G_GNUC_INTERNAL int itdb_write_ithumb_files (Itdb_iTunesDB *db); G_GNUC_INTERNAL const IpodArtworkFormat *ipod_get_artwork_info_from_type ( IpodDevice *ipod, int image_type); diff --git a/src/itdb.h b/src/itdb.h index f1a7e61..d91aae0 100644 --- a/src/itdb.h +++ b/src/itdb.h @@ -1,4 +1,4 @@ -/* Time-stamp: <2005-11-24 21:34:51 jcs> +/* Time-stamp: <2005-11-29 00:56:25 jcs> | | Copyright (C) 2002-2005 Jorg Schuler | Part of the gtkpod project. @@ -53,9 +53,78 @@ G_BEGIN_DECLS #define G_GNUC_INTERNAL #endif +typedef void (* ItdbUserDataDestroyFunc) (gpointer userdata); +typedef gpointer (* ItdbUserDataDuplicateFunc) (gpointer userdata); + +typedef struct _Itdb_Artwork Itdb_Artwork; +typedef struct _Itdb_Thumb Itdb_Thumb; +typedef struct _SPLPref SPLPref; +typedef struct _SPLRule SPLRule; +typedef struct _SPLRules SPLRules; +typedef struct _Itdb_iTunesDB Itdb_iTunesDB; +typedef struct _Itdb_Playlist Itdb_Playlist; +typedef struct _Itdb_Track Itdb_Track; + + +/* ------------------------------------------------------------ *\ + * + * Thumbnail-relevant definitions + * +\* ------------------------------------------------------------ */ + +/* Types of thumbnails in Itdb_Image */ +typedef enum { + ITDB_THUMB_COVER_SMALL, + ITDB_THUMB_COVER_LARGE, + ITDB_THUMB_PHOTO_SMALL, + ITDB_THUMB_PHOTO_LARGE, + ITDB_THUMB_PHOTO_FULL_SCREEN, + ITDB_THUMB_PHOTO_TV_SCREEN +} ItdbThumbType; + + +/* The Itdb_Thumb structure can represent two slightly different + thumbnails: + + - a thumbnail before it's transferred to the iPod. + + offset and size are 0 + + width and height, if unequal 0, will indicate the size on the + iPod. width and height are set the first time a pixbuf is + requested for this thumbnail. + + type is set according to the type this thumbnail represents + + filename point to a 'real' image file. + + - a thumbnail (big or small) stored on a database in the iPod. In + these cases, id corresponds to the ID originally used in the + database, filename points to a .ithmb file on the iPod + */ +struct _Itdb_Thumb { + ItdbThumbType type; + gchar *filename; + guint32 offset; + guint32 size; + gint16 width; + gint16 height; +}; + +struct _Itdb_Artwork { + GList *thumbnails; /* list of Itdb_Thumbs */ + guint32 artwork_size; /* Size in bytes of the original source image */ + guint id; /* some kind of ID, starting with + * 0x40... libgpod will set this on sync. */ +}; + + +/* ------------------------------------------------------------ *\ + * + * Smart Playlists (Rules) + * +\* ------------------------------------------------------------ */ -/* one star is how much (track->rating) */ -#define ITDB_RATING_STEP 20 /* Most of the knowledge about smart playlists has been provided by Samuel "Otto" Wood (sam dot wood at gmail dot com) who let me dig @@ -258,7 +327,7 @@ typedef enum { /* Maximum string length that iTunes writes to the database */ #define SPL_MAXSTRINGLENGTH 255 -typedef struct SPLPref +struct _SPLPref { guint8 liveupdate; /* "live Updating" check box */ guint8 checkrules; /* "Match X of the following @@ -270,9 +339,9 @@ typedef struct SPLPref type" */ guint8 matchcheckedonly; /* "Match only checked songs" check box */ -} SPLPref; +}; -typedef struct SPLRule +struct _SPLRule { guint32 field; guint32 action; @@ -298,47 +367,29 @@ typedef struct SPLRule guint32 unk060; guint32 unk064; guint32 unk068; -} SPLRule; +}; -typedef struct SPLRules +struct _SPLRules { guint32 unk004; guint32 match_operator; /* "All" (logical AND): SPLMATCH_AND, "Any" (logical OR): SPLMATCH_OR */ GList *rules; -} SPLRules; +}; -/* This structure can represent two slightly different images: +/* ------------------------------------------------------------ *\ + * + * iTunesDB, Playlists, Tracks + * +\* ------------------------------------------------------------ */ - - an image before it's transferred to the iPod (it will then be - scaled as necessary to generate the 2 thumbnails needed by the - iPod), for such images, filename points to a 'real' image file, - offset is not significant, size, width and height may or may not - be set and id corresponds to the image id to write in mhii - records of the photo database - - - a thumbnail (big or small) stored on a database in the iPod. For - such images, id isn't significant, filename point to a .ithmb - file on the iPod - */ -struct _Itdb_Image { - int type; - char *filename; - guint32 offset; - guint32 size; - gint16 width; - gint16 height; -}; - -typedef struct _Itdb_Image Itdb_Image; - -typedef void (* ItdbUserDataDestroyFunc) (gpointer userdata); -typedef gpointer (* ItdbUserDataDuplicateFunc) (gpointer userdata); +/* one star is how much (track->rating) */ +#define ITDB_RATING_STEP 20 -typedef struct +struct _Itdb_iTunesDB { GList *tracks; GList *playlists; @@ -355,10 +406,10 @@ typedef struct ItdbUserDataDuplicateFunc userdata_duplicate; /* function called to free userdata */ ItdbUserDataDestroyFunc userdata_destroy; -} Itdb_iTunesDB; +}; -typedef struct +struct _Itdb_Playlist { Itdb_iTunesDB *itdb; /* pointer to iTunesDB (for convenience) */ gchar *name; /* name of playlist in UTF8 */ @@ -395,7 +446,7 @@ typedef struct ItdbUserDataDuplicateFunc userdata_duplicate; /* function called to free userdata */ ItdbUserDataDestroyFunc userdata_destroy; -} Itdb_Playlist; +}; /* @@ -468,7 +519,7 @@ typedef enum http://ipodlinux.org/ITunesDB. http://ipodlinux.org/ITunesDB is the best source for information about the iTunesDB and related files. */ -typedef struct +struct _Itdb_Track { Itdb_iTunesDB *itdb; /* pointer to iTunesDB (for convenience) */ gchar *title; /* title (utf8) */ @@ -539,13 +590,15 @@ typedef struct 0x4d503320 -> 0x4d = 'M', 0x50 = 'P', 0x33 = '3', 0x20 = . (always left set to 0 by itdb)*/ - guint16 artwork_count; /* The number of album artwork items - associated with this song. */ + guint16 artwork_count; /* The number of album artwork items + associated with this song. libgpod + updates this value when syncing */ guint32 artwork_size; /* The total size of artwork (in bytes) attached to this song, when it is converted to JPEG format. Observed in iPodDB version 0x0b and with an iPod - Photo. */ + Photo. libgpod updates this value when + syncing */ float samplerate2; /* The sample rate of the song expressed as an IEEE 32 bit floating point number. It's uncertain why this is @@ -614,9 +667,7 @@ typedef struct guint32 chapterdata_raw_length; /* This is for Cover Art support */ - GList *thumbnails; - unsigned int image_id; - char *orig_image_filename; + struct _Itdb_Artwork *artwork; /* below is for use by application */ guint64 usertype; @@ -625,11 +676,16 @@ typedef struct ItdbUserDataDuplicateFunc userdata_duplicate; /* function called to free userdata */ ItdbUserDataDestroyFunc userdata_destroy; -} Itdb_Track; +}; /* (gtkpod note: don't forget to add fields read from the file to * copy_new_info() in file.c!) */ -/* Error codes */ + +/* ------------------------------------------------------------ *\ + * + * Error codes + * +\* ------------------------------------------------------------ */ typedef enum { ITDB_FILE_ERROR_SEEK, /* file corrupt: illegal seek occured */ @@ -639,10 +695,18 @@ typedef enum ITDB_FILE_ERROR_ITDB_CORRUPT /* iTunesDB in memory corrupt */ } ItdbFileError; + /* Error domain */ #define ITDB_FILE_ERROR itdb_file_error_quark () GQuark itdb_file_error_quark (void); + +/* ------------------------------------------------------------ *\ + * + * Public functions + * +\* ------------------------------------------------------------ */ + /* functions for reading/writing database, general itdb functions */ Itdb_iTunesDB *itdb_parse (const gchar *mp, GError **error); Itdb_iTunesDB *itdb_parse_file (const gchar *filename, GError **error); @@ -710,7 +774,7 @@ Itdb_Playlist *itdb_playlist_mpl (Itdb_iTunesDB *itdb); gboolean itdb_playlist_is_mpl (Itdb_Playlist *pl); void itdb_playlist_set_mpl (Itdb_Playlist *pl); -/* playlist functions for podcast playlist */ +/* playlist functions for podcasts playlist */ Itdb_Playlist *itdb_playlist_podcasts (Itdb_iTunesDB *itdb); gboolean itdb_playlist_is_podcasts (Itdb_Playlist *pl); void itdb_playlist_set_podcasts (Itdb_Playlist *pl); @@ -730,13 +794,33 @@ void itdb_spl_update_all (Itdb_iTunesDB *itdb); void itdb_spl_update_live (Itdb_iTunesDB *itdb); /* thumbnails functions */ -unsigned char *itdb_image_get_rgb_data (Itdb_Image *image); -int itdb_track_set_thumbnail (Itdb_Track *song, const char *filename); -void itdb_track_remove_thumbnail (Itdb_Track *song); -void itdb_track_free_generated_thumbnails (Itdb_Track *track); +/* itdb_track_... */ +gboolean itdb_track_set_thumbnails (Itdb_Track *track, + const gchar *filename); +void itdb_track_remove_thumbnails (Itdb_Track *track); +/* itdb_artwork_... */ +Itdb_Artwork *itdb_artwork_new (void); +Itdb_Artwork *itdb_artwork_duplicate (Itdb_Artwork *artwork); +void itdb_artwork_free (Itdb_Artwork *artwork); +Itdb_Thumb *itdb_artwork_get_thumb_by_type (Itdb_Artwork *artwork, + ItdbThumbType type); +gboolean itdb_artwork_add_thumbnail (Itdb_Artwork *artwork, + ItdbThumbType type, + const gchar *filename); +void itdb_artwork_remove_thumbnail (Itdb_Artwork *artwork, + Itdb_Thumb *thumb); +void itdb_artwork_remove_thumbnails (Itdb_Artwork *artwork); +/* itdb_thumb_... */ /* the following funciton returns a pointer to a GdkPixbuf if gdk-pixbuf is installed -- a NULL pointer otherwise. */ -gpointer itdb_image_get_gdk_pixbuf (Itdb_iTunesDB *itdb, Itdb_Image *image); +gpointer itdb_thumb_get_gdk_pixbuf (IpodDevice *device, + Itdb_Thumb *thumb); +Itdb_Thumb *itdb_thumb_duplicate (Itdb_Thumb *thumb); +void itdb_thumb_free (Itdb_Thumb *thumb); +Itdb_Thumb *itdb_thumb_new (void); +gchar *itdb_thumb_get_filename (IpodDevice *device, Itdb_Thumb *thumb); + + /* time functions */ guint64 itdb_time_get_mac_time (void); time_t itdb_time_mac_to_host (guint64 mactime); diff --git a/src/itdb_artwork.c b/src/itdb_artwork.c new file mode 100644 index 0000000..69b432a --- /dev/null +++ b/src/itdb_artwork.c @@ -0,0 +1,429 @@ +/* Time-stamp: <2005-11-29 00:56:32 jcs> +| +| Copyright (C) 2002-2005 Jorg Schuler +| Part of the gtkpod project. +| +| URL: http://www.gtkpod.org/ +| URL: http://gtkpod.sourceforge.net/ +| +| The code contained in this file is free software; you can redistribute +| it and/or modify it under the terms of the GNU Lesser General Public +| License as published by the Free Software Foundation; either version +| 2.1 of the License, or (at your option) any later version. +| +| This file is distributed in the hope that it will be useful, +| but WITHOUT ANY WARRANTY; without even the implied warranty of +| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +| Lesser General Public License for more details. +| +| You should have received a copy of the GNU Lesser General Public +| License along with this code; if not, write to the Free Software +| Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +| +| iTunes and iPod are trademarks of Apple +| +| This product is not supported/written/published by Apple! +| +| $Id$ +*/ + +#include + +#include "itdb_private.h" +#include "db-image-parser.h" +#include +#include +#include +#if HAVE_GDKPIXBUF +#include +#endif +#include + + +Itdb_Artwork *itdb_artwork_new (void) +{ + Itdb_Artwork *artwork = g_new0 (Itdb_Artwork, 1); + return artwork; +} + + +void itdb_artwork_free (Itdb_Artwork *artwork) +{ + g_return_if_fail (artwork); + itdb_artwork_remove_thumbnails (artwork); + g_free (artwork); +} + + +static GList *dup_thumbnails (GList *thumbnails) +{ + GList *it; + GList *result; + + result = NULL; + for (it = thumbnails; it != NULL; it = it->next) + { + Itdb_Thumb *new_thumb; + Itdb_Thumb *thumb; + + thumb = (Itdb_Thumb *)it->data; + g_return_val_if_fail (thumb, NULL); + + new_thumb = itdb_thumb_duplicate (thumb); + + result = g_list_prepend (result, new_thumb); + } + + return g_list_reverse (result); +} + +Itdb_Artwork *itdb_artwork_duplicate (Itdb_Artwork *artwork) +{ + Itdb_Artwork *dup; + g_return_val_if_fail (artwork, NULL); + + dup = itdb_artwork_new (); + dup->thumbnails = dup_thumbnails (artwork->thumbnails); + dup->artwork_size = artwork->artwork_size; + dup->id = artwork->id; + return dup; +} + + +/* Remove @thumb in @artwork */ +void +itdb_artwork_remove_thumbnail (Itdb_Artwork *artwork, Itdb_Thumb *thumb) +{ + g_return_if_fail (artwork); + g_return_if_fail (thumb); + + artwork->thumbnails = g_list_remove (artwork->thumbnails, thumb); +} + + +/* Remove all thumbnails in @artwork */ +void +itdb_artwork_remove_thumbnails (Itdb_Artwork *artwork) +{ + g_return_if_fail (artwork); + + while (artwork->thumbnails) + { + Itdb_Thumb *thumb = artwork->thumbnails->data; + g_return_if_fail (thumb); + itdb_artwork_remove_thumbnail (artwork, thumb); + } + artwork->artwork_size = 0; + artwork->id = 0; +} + + + + +/* Append thumbnail of type @type to existing thumbnails in @artwork */ +gboolean +itdb_artwork_add_thumbnail (Itdb_Artwork *artwork, + ItdbThumbType type, + const char *filename) +{ +#ifdef HAVE_GDKPIXBUF +/* This operation doesn't make sense when we can't save thumbnail files */ + struct stat statbuf; + Itdb_Thumb *thumb; + + g_return_val_if_fail (artwork, FALSE); + g_return_val_if_fail (filename, FALSE); + + if (g_stat (filename, &statbuf) != 0) { + return FALSE; + } + + artwork->artwork_size = statbuf.st_size; + + thumb = itdb_thumb_new (); + thumb->filename = g_strdup (filename); + thumb->type = type; + artwork->thumbnails = g_list_append (artwork->thumbnails, thumb); + + return TRUE; +#else + return FALSE; +#endif +} + + +/* Return a pointer to the Itdb_Thumb of type @type or NULL when it + * does not exist */ +Itdb_Thumb *itdb_artwork_get_thumb_by_type (Itdb_Artwork *artwork, + ItdbThumbType type) +{ + GList *gl; + + g_return_val_if_fail (artwork, NULL); + + for (gl=artwork->thumbnails; gl; gl=gl->next) + { + Itdb_Thumb *thumb = gl->data; + g_return_val_if_fail (thumb, NULL); + if (thumb->type == type) return thumb; + } + return NULL; +} + + +/* Get filename of thumbnail. If it's a thumbnail on the iPod, return + the full path to the ithmb file. Otherwise return the full path to + the original file. + g_free() when not needed any more. +*/ +gchar *itdb_thumb_get_filename (IpodDevice *device, Itdb_Thumb *thumb) +{ + gchar *paths[] = {"iPod_Control", "Artwork", NULL, NULL}; + gchar *filename, *mountpoint; + + g_return_val_if_fail (device, NULL); + g_return_val_if_fail (thumb, NULL); + + /* thumbnail not transferred to the iPod */ + if (thumb->size == 0) + return g_strdup (thumb->filename); + + if (strlen (thumb->filename) < 2) + { + g_print (_("Illegal filename: '%s'.\n"), thumb->filename); + return NULL; + } + + g_object_get (G_OBJECT (device), + "mount-point", &mountpoint, + NULL); + + if (!mountpoint) + { + g_print (_("Mountpoint not set.\n")); + return NULL; + } + + paths[2] = thumb->filename+1; + filename = itdb_resolve_path (mountpoint, (const char **)paths); + + return filename; +} + + +static guchar * +unpack_RGB_565 (guint16 *pixels, guint bytes_len) +{ + guchar *result; + guint i; + + result = g_malloc ((bytes_len/2) * 3); + if (result == NULL) { + return NULL; + } + for (i = 0; i < bytes_len/2; i++) { + guint16 cur_pixel; + + cur_pixel = GINT16_FROM_LE (pixels[i]); + /* Unpack pixels */ + result[3*i] = (cur_pixel & RED_MASK) >> RED_SHIFT; + result[3*i+1] = (cur_pixel & GREEN_MASK) >> GREEN_SHIFT; + result[3*i+2] = (cur_pixel & BLUE_MASK) >> BLUE_SHIFT; + + /* Normalize color values so that they use a [0..255] range */ + result[3*i] <<= (8 - RED_BITS); + result[3*i+1] <<= (8 - GREEN_BITS); + result[3*i+2] <<= (8 - BLUE_BITS); + } + + return result; +} + + +static guchar * +get_pixel_data (IpodDevice *device, Itdb_Thumb *thumb) +{ + gchar *filename = NULL; + guchar *result = NULL; + FILE *f = NULL; + gint res; + + g_return_val_if_fail (thumb, NULL); + g_return_val_if_fail (thumb->filename, NULL); + + result = g_malloc (thumb->size); + + filename = itdb_thumb_get_filename (device, thumb); + + if (!filename) + { + g_print (_("Could not find on iPod: '%s'\n"), + thumb->filename); + goto error; + } + + f = fopen (filename, "r"); + if (f == NULL) { + g_print ("Failed to open %s: %s\n", + filename, strerror (errno)); + goto error; + } + + res = fseek (f, thumb->offset, SEEK_SET); + if (res != 0) { + g_print ("Seek to %d failed on %s: %s\n", + thumb->offset, thumb->filename, strerror (errno)); + goto error; + } + + res = fread (result, thumb->size, 1, f); + if (res != 1) { + g_print ("Failed to read %u bytes from %s: %s\n", + thumb->size, thumb->filename, strerror (errno)); + goto error; + } + + goto cleanup; + + error: + g_free (result); + result = NULL; + cleanup: + if (f != NULL) { + fclose (f); + } + g_free (filename); + + return result; +} + +static guchar * +itdb_thumb_get_rgb_data (IpodDevice *device, Itdb_Thumb *thumb) +{ + void *pixels565; + guchar *pixels; + + g_return_val_if_fail (device, NULL); + g_return_val_if_fail (thumb, NULL); + + /* no rgb pixel data available (FIXME: calculate from real + * image file) */ + if (thumb->size == 0) return NULL; + + pixels565 = get_pixel_data (device, thumb); + if (pixels565 == NULL) { + return NULL; + } + + pixels = unpack_RGB_565 (pixels565, thumb->size); + g_free (pixels565); + + return pixels; + +} + + + +/* Convert the pixeldata in @thumb to a GdkPixbuf. + Since we want to have gdk-pixbuf dependency optional, a generic + gpointer is returned which you have to cast to (GdkPixbuf *) + yourself. If gdk-pixbuf is not installed the NULL pointer is + returned. + The returned GdkPixbuf must be freed with gdk_pixbuf_unref() after + use. */ +gpointer +itdb_thumb_get_gdk_pixbuf (IpodDevice *device, Itdb_Thumb *thumb) +{ +#if HAVE_GDKPIXBUF + GdkPixbuf *pixbuf; + guchar *pixels; + const IpodArtworkFormat *img_info; + + g_return_val_if_fail (device, NULL); + g_return_val_if_fail (thumb, NULL); + + img_info = ipod_get_artwork_info_from_type (device, thumb->type); + + if (img_info == NULL) + { + g_print (_("Unable to obtain image info on thumb (type: %d, filename: '%s'\n)"), thumb->type, thumb->filename); + return NULL; + } + + if (thumb->size == 0) + { /* pixbuf has not yet been transfered to the iPod */ + gint width, height; + + pixbuf = gdk_pixbuf_new_from_file_at_size (thumb->filename, + img_info->width, + img_info->height, + NULL); + if (!pixbuf) + return NULL; + + + /* !! cannot write directly to &thumb->width/height because + g_object_get() returns a gint, but thumb->width/height are + gint16 !! */ + g_object_get (G_OBJECT (pixbuf), + "width", &width, + "height", &height, + NULL); + + thumb->width = width; + thumb->height = height; + + return pixbuf; + } + + /* pixbuf is already on the iPod -> read from there */ + pixels = itdb_thumb_get_rgb_data (device, thumb); + if (pixels == NULL) + { + return NULL; + } + + pixbuf = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, FALSE, + 8, thumb->width, thumb->height, + img_info->width*3, + (GdkPixbufDestroyNotify)g_free, + NULL); + + + /* !! do not g_free(pixels) here: it will be freed when doing a + * gdk_pixbuf_unref() on the GdkPixbuf !! */ + + return pixbuf; +#else + return NULL; +#endif +} + + +Itdb_Thumb *itdb_thumb_new (void) +{ + Itdb_Thumb *thumb = g_new0 (Itdb_Thumb, 1); + return thumb; +} + + +void itdb_thumb_free (Itdb_Thumb *thumb) +{ + g_return_if_fail (thumb); + + g_free (thumb->filename); + g_free (thumb); +} + +Itdb_Thumb *itdb_thumb_duplicate (Itdb_Thumb *thumb) +{ + Itdb_Thumb *new_thumb; + + g_return_val_if_fail (thumb, NULL); + + new_thumb = itdb_thumb_new (); + memcpy (new_thumb, thumb, sizeof (Itdb_Thumb)); + new_thumb->filename = g_strdup (thumb->filename); + + return new_thumb; +} diff --git a/src/itdb_itunesdb.c b/src/itdb_itunesdb.c index 195321e..a74cf9c 100644 --- a/src/itdb_itunesdb.c +++ b/src/itdb_itunesdb.c @@ -1,4 +1,4 @@ -/* Time-stamp: <2005-11-20 16:06:58 jcs> +/* Time-stamp: <2005-11-27 18:31:02 jcs> | | Copyright (C) 2002-2005 Jorg Schuler | Part of the gtkpod project. @@ -2667,6 +2667,25 @@ static void mk_mhlt (FExport *fexp, guint32 num) } +static void update_artwork_info (Itdb_Track *track) +{ + GList *gl; + + track->artwork_count = 0; + /* count the number of valid thumbnails */ + for (gl=track->artwork->thumbnails; gl; gl=gl->next) + { + Itdb_Thumb *thumb = gl->data; + g_return_if_fail (thumb); + if (thumb->size != 0) ++track->artwork_count; + } + if (track->artwork_count != 0) + track->artwork_size = track->artwork->artwork_size; + else + track->artwork_size = 0; +} + + /* Write out the mhit header. Size will be written later */ static void mk_mhit (WContents *cts, Itdb_Track *track) { @@ -2711,6 +2730,7 @@ static void mk_mhit (WContents *cts, Itdb_Track *track) else put8int (cts, 0); put8int (cts, track->app_rating); put16lint (cts, track->BPM); + update_artwork_info (track); put16lint (cts, track->artwork_count); put16lint (cts, track->unk126); put32lint (cts, track->artwork_size); @@ -3578,6 +3598,10 @@ gboolean itdb_write_file (Itdb_iTunesDB *itdb, const gchar *filename, if (!filename) filename = itdb->filename; +#if HAVE_GDKPIXBUF + ipod_write_artwork_db (itdb); +#endif + fexp = g_new0 (FExport, 1); fexp->itdb = itdb; fexp->itunesdb = wcontents_new (filename); @@ -3650,9 +3674,6 @@ gboolean itdb_write (Itdb_iTunesDB *itdb, GError **error) * Errors happening during that operation are considered non fatal since * they shouldn't corrupt the main database. */ -#if HAVE_GDKPIXBUF - ipod_write_artwork_db (itdb); -#endif itunes_path = itdb_resolve_path (itdb->mountpoint, db); diff --git a/src/itdb_track.c b/src/itdb_track.c index 66d7b7e..6b0bcf0 100644 --- a/src/itdb_track.c +++ b/src/itdb_track.c @@ -1,4 +1,4 @@ -/* Time-stamp: <2005-11-12 22:57:11 jcs> +/* Time-stamp: <2005-11-28 22:31:30 jcs> | | Copyright (C) 2002-2005 Jorg Schuler | Part of the gtkpod project. @@ -50,6 +50,8 @@ Itdb_Track *itdb_track_new (void) { Itdb_Track *track = g_new0 (Itdb_Track, 1); + track->artwork = itdb_artwork_new (); + track->visible = 1; return track; } @@ -79,7 +81,7 @@ static void itdb_track_set_defaults (Itdb_Track *tr) g_return_if_fail (tr->itdb); /* The exact meaning of unk126 is unknown, but always seems to be - 0xffff for MP3/AAC songs, 0x0 for uncompressed songs (like WAVE + 0xffff for MP3/AAC tracks, 0x0 for uncompressed tracks (like WAVE format), 0x1 for Audible. */ if (tr->unk126 == 0) { @@ -107,9 +109,9 @@ static void itdb_track_set_defaults (Itdb_Track *tr) tr->unk126 = 0x00; /* default value */ } } - /* The exact meaning of unk144 is unknown, but MP3 songs appear to + /* The exact meaning of unk144 is unknown, but MP3 tracks appear to be always 0x0000000c or 0x0100000c (if played one or more times - in iTunes), AAC songs are always 0x01000033, Audible files are + in iTunes), AAC tracks are always 0x01000033, Audible files are 0x01000029, WAV files are 0x0. */ if (tr->unk144 == 0) { @@ -160,7 +162,7 @@ static void itdb_track_set_defaults (Itdb_Track *tr) } } } - /* The sample rate of the song expressed as an IEEE 32 bit + /* The sample rate of the track expressed as an IEEE 32 bit floating point number. It's uncertain why this is here. itdb will set this when adding a track */ tr->samplerate2 = tr->samplerate; @@ -206,22 +208,6 @@ void itdb_track_add (Itdb_iTunesDB *itdb, Itdb_Track *track, gint32 pos) else itdb->tracks = g_list_insert (itdb->tracks, track, pos); } -void -itdb_track_free_generated_thumbnails (Itdb_Track *track) -{ - GList *it; - - for (it = track->thumbnails; it != NULL; it = it->next) { - Itdb_Image *image; - - image = (Itdb_Image *)it->data; - g_free (image->filename); - g_free (image); - } - g_list_free (track->thumbnails); - track->thumbnails = NULL; -} - /* Free the memory taken by @track */ void itdb_track_free (Itdb_Track *track) { @@ -242,8 +228,8 @@ void itdb_track_free (Itdb_Track *track) g_free (track->subtitle); g_free (track->ipod_path); g_free (track->chapterdata_raw); - itdb_track_free_generated_thumbnails (track); - g_free (track->orig_image_filename); + itdb_artwork_remove_thumbnails (track->artwork); + g_free (track->artwork); if (track->userdata && track->userdata_destroy) (*track->userdata_destroy) (track->userdata); g_free (track); @@ -276,30 +262,6 @@ void itdb_track_unlink (Itdb_Track *track) track->itdb = NULL; } -static GList *dup_thumbnails (GList *thumbnails) -{ - GList *it; - GList *result; - - result = NULL; - for (it = thumbnails; it != NULL; it = it->next) - { - Itdb_Image *new_image; - Itdb_Image *image; - - image = (Itdb_Image *)it->data; - g_return_val_if_fail (image, NULL); - - new_image = g_new (Itdb_Image, 1); - memcpy (new_image, image, sizeof (Itdb_Image)); - new_image->filename = g_strdup (image->filename); - - result = g_list_prepend (result, new_image); - } - - return g_list_reverse (result); -} - /* Duplicate an existing track */ Itdb_Track *itdb_track_duplicate (Itdb_Track *tr) { @@ -338,8 +300,7 @@ Itdb_Track *itdb_track_duplicate (Itdb_Track *tr) } /* Copy thumbnail data */ - tr_dup->orig_image_filename = g_strdup (tr->orig_image_filename); - tr_dup->thumbnails = dup_thumbnails (tr->thumbnails); + tr_dup->artwork = itdb_artwork_duplicate (tr->artwork); /* Copy userdata */ if (tr->userdata && tr->userdata_duplicate) @@ -349,6 +310,35 @@ Itdb_Track *itdb_track_duplicate (Itdb_Track *tr) } +gboolean itdb_track_set_thumbnails (Itdb_Track *track, + const gchar *filename) +{ + gboolean result; + + g_return_val_if_fail (track, FALSE); + g_return_val_if_fail (filename, FALSE); + + itdb_artwork_remove_thumbnails (track->artwork); + result = itdb_artwork_add_thumbnail (track->artwork, + ITDB_THUMB_COVER_SMALL, + filename); + if (result == TRUE) + result = itdb_artwork_add_thumbnail (track->artwork, + ITDB_THUMB_COVER_LARGE, + filename); + if (result == FALSE) + itdb_artwork_remove_thumbnails (track->artwork); + + return result; +} + + +void itdb_track_remove_thumbnails (Itdb_Track *track) +{ + g_return_if_fail (track); + itdb_artwork_remove_thumbnails (track->artwork); +} + /* Returns the track with the ID @id or NULL if the ID cannot be * found. */ @@ -419,38 +409,4 @@ Itdb_Track *itdb_track_id_tree_by_id (GTree *idtree, guint32 id) return (Itdb_Track *)g_tree_lookup (idtree, &id); } -void -itdb_track_remove_thumbnail (Itdb_Track *song) -{ - itdb_track_free_generated_thumbnails (song); - g_free (song->orig_image_filename); - song->orig_image_filename = NULL; - song->image_id = 0; -} - -#ifdef HAVE_GDKPIXBUF -/* This operation doesn't make sense when we can't save thumbnail files */ -int -itdb_track_set_thumbnail (Itdb_Track *song, const char *filename) -{ - struct stat statbuf; - - g_return_val_if_fail (song != NULL, -1); - - if (g_stat (filename, &statbuf) != 0) { - return -1; - } - itdb_track_remove_thumbnail (song); - song->artwork_size = statbuf.st_size; - song->orig_image_filename = g_strdup (filename); - - return 0; -} -#else -int -itdb_track_set_thumbnail (Itdb_Track *song, const char *filename) -{ - return -1; -} -#endif diff --git a/src/ithumb-writer.c b/src/ithumb-writer.c index 075a9dc..7fb3bed 100644 --- a/src/ithumb-writer.c +++ b/src/ithumb-writer.c @@ -35,6 +35,12 @@ #include #include +#include +#include +#include +#include + + struct _iThumbWriter { off_t cur_offset; FILE *f; @@ -48,11 +54,11 @@ typedef struct _iThumbWriter iThumbWriter; * here to specify which size we are interested in in case the pixbuf is non * square */ -static gushort * +static guint16 * pack_RGB_565 (GdkPixbuf *pixbuf, int dst_width, int dst_height) { guchar *pixels; - gushort *result; + guint16 *result; gint row_stride; gint channels; gint width; @@ -66,9 +72,7 @@ pack_RGB_565 (GdkPixbuf *pixbuf, int dst_width, int dst_height) "pixels", &pixels, NULL); g_return_val_if_fail ((width <= dst_width) && (height <= dst_height), NULL); result = g_malloc0 (dst_width * dst_height * 2); - if (result == NULL) { - return NULL; - } + for (h = 0; h < height; h++) { for (w = 0; w < width; w++) { gint r; @@ -92,117 +96,147 @@ pack_RGB_565 (GdkPixbuf *pixbuf, int dst_width, int dst_height) -static Itdb_Image * -itdb_image_dup (Itdb_Image *image) -{ - Itdb_Image *result; - result = g_new0 (Itdb_Image, 1); - if (result == NULL) { - return NULL; - } - result->type = image->type; - result->height = image->height; - result->width = image->width; - result->offset = image->offset; - result->size = image->size; +static char * +ipod_image_get_ithmb_filename (const char *mount_point, gint correlation_id) +{ + char *paths[] = {"iPod_Control", "Artwork", NULL, NULL}; + char *filename, *buf; - return result; -} + buf = g_strdup_printf ("F%04u_1.ithmb", correlation_id); -static Itdb_Image * -ithumb_writer_write_thumbnail (iThumbWriter *writer, - const char *filename) -{ - GdkPixbuf *thumb; - gushort *pixels; - Itdb_Image *image; + paths[2] = buf; - image = g_hash_table_lookup (writer->cache, filename); - if (image != NULL) { - return itdb_image_dup (image); - } + filename = itdb_resolve_path (mount_point, (const char **)paths); - image = g_new0 (Itdb_Image, 1); - if (image == NULL) { - return NULL; + /* itdb_resolve_path() only returns existing paths */ + if (!filename) + { + gchar *path; + paths[2] = NULL; + path = itdb_resolve_path (mount_point, (const char **)paths); + if (path) + { + filename = g_build_filename (path, buf, NULL); + } + g_free (path); } - thumb = gdk_pixbuf_new_from_file_at_size (filename, - writer->img_info->width, - writer->img_info->height, - NULL); - if (thumb == NULL) { - g_free (image); - return NULL; - } - g_object_get (G_OBJECT (thumb), - "height", &image->height, - "width", &image->width, - NULL); - image->offset = writer->cur_offset; - image->type = writer->img_info->type; - image->size = writer->img_info->width * writer->img_info->height * 2; - /* FIXME: under certain conditions (probably related to writer->offset - * getting too big), this should be :F%04u_2.ithmb and so on - */ - image->filename = g_strdup_printf (":F%04u_1.ithmb", - writer->img_info->correlation_id); - pixels = pack_RGB_565 (thumb, writer->img_info->width, - writer->img_info->height); - g_object_unref (G_OBJECT (thumb)); - if (pixels == NULL) { - g_free (image); - return NULL; - } - if (fwrite (pixels, image->size, 1, writer->f) != 1) { - g_free (image); - g_free (pixels); - g_print ("Error writing to file: %s\n", strerror (errno)); - return NULL; - } - g_free (pixels); - writer->cur_offset += image->size; - g_hash_table_insert (writer->cache, g_strdup (filename), image); + g_free (buf); - return image; + return filename; } -static char * -ipod_image_get_ithmb_filename (const char *mount_point, gint correlation_id) + +static gboolean +ithumb_writer_write_thumbnail (iThumbWriter *writer, + Itdb_Thumb *thumb) { - char *paths[] = {"iPod_Control", "Artwork", NULL, NULL}; - char *filename; + GdkPixbuf *pixbuf; + guint16 *pixels; + gchar *filename; + gint width, height; + + Itdb_Thumb *old_thumb; + + g_return_val_if_fail (writer, FALSE); + g_return_val_if_fail (thumb, FALSE); + + /* If the same filename was written before, just use the old + thumbnail to save space on the iPod */ + old_thumb = g_hash_table_lookup (writer->cache, thumb->filename); + if (old_thumb != NULL) + { + g_free (thumb->filename); + memcpy (thumb, old_thumb, sizeof (Itdb_Thumb)); + thumb->filename = g_strdup (old_thumb->filename); + return TRUE; + } + + filename = g_strdup (thumb->filename); + + pixbuf = gdk_pixbuf_new_from_file_at_size (filename, + writer->img_info->width, + writer->img_info->height, + NULL); + if (pixbuf == NULL) { + return FALSE; + } + + /* !! cannot write directly to &thumb->width/height because + g_object_get() returns a gint, but thumb->width/height are + gint16 !! */ + g_object_get (G_OBJECT (pixbuf), + "width", &width, + "height", &height, + NULL); + + thumb->width = width; + thumb->height = height; + thumb->offset = writer->cur_offset; + thumb->size = writer->img_info->width * writer->img_info->height * 2; +/* printf("offset: %d type: %d, size: %d\n", thumb->offset, thumb->type, thumb->size); */ + /* FIXME: under certain conditions (probably related to + * writer->offset getting too big), this should be :F%04u_2.ithmb + * and so on + */ + thumb->filename = g_strdup_printf (":F%04u_1.ithmb", + writer->img_info->correlation_id); + pixels = pack_RGB_565 (pixbuf, writer->img_info->width, + writer->img_info->height); + g_object_unref (G_OBJECT (pixbuf)); + + if (pixels == NULL) + { + return FALSE; + } + if (fwrite (pixels, thumb->size, 1, writer->f) != 1) { + g_free (pixels); + g_print ("Error writing to file: %s\n", strerror (errno)); + return FALSE; + } + g_free (pixels); + writer->cur_offset += thumb->size; + g_hash_table_insert (writer->cache, filename, thumb); - paths[2] = g_strdup_printf ("F%04u_1.ithmb", correlation_id); - filename = itdb_resolve_path (mount_point, (const char **)paths); - g_free (paths[2]); - return filename; + /* !! filename is g_free()d when destroying the hash table. Do not + do it here */ + + return TRUE; } +static void +write_thumbnail (gpointer _writer, gpointer _artwork) +{ + iThumbWriter *writer = _writer; + Itdb_Artwork *artwork = _artwork; + Itdb_Thumb *thumb; + + thumb = itdb_artwork_get_thumb_by_type (artwork, + writer->img_info->type); + + /* size == 0 indicates a thumbnail not yet written to the + thumbnail file */ + if (thumb && (thumb->size == 0)) + { + ithumb_writer_write_thumbnail (writer, thumb); + } +} static iThumbWriter * ithumb_writer_new (const char *mount_point, const IpodArtworkFormat *info) { char *filename; iThumbWriter *writer; + writer = g_new0 (iThumbWriter, 1); - if (writer == NULL) { - return NULL; - } + writer->img_info = g_memdup (info, sizeof (IpodArtworkFormat)); - if (writer->img_info == NULL) { - g_free (writer); - return NULL; - } + writer->cache = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - if (writer->cache == NULL) { - g_free (writer->img_info); - g_free (writer); - return NULL; - } + filename = ipod_image_get_ithmb_filename (mount_point, info->correlation_id); if (filename == NULL) { @@ -211,7 +245,7 @@ ithumb_writer_new (const char *mount_point, const IpodArtworkFormat *info) g_free (writer); return NULL; } - writer->f = fopen (filename, "w"); + writer->f = fopen (filename, "ab"); if (writer->f == NULL) { g_print ("Error opening %s: %s\n", filename, strerror (errno)); g_free (filename); @@ -220,6 +254,7 @@ ithumb_writer_new (const char *mount_point, const IpodArtworkFormat *info) g_free (writer); return NULL; } + writer->cur_offset = ftell (writer->f); g_free (filename); return writer; @@ -236,33 +271,222 @@ ithumb_writer_free (iThumbWriter *writer) } -static void -write_thumbnail (gpointer data, gpointer user_data) +static gboolean ithumb_rearrange_thumbnail_file (gpointer _key, + gpointer _thumbs, + gpointer _user_data) { - iThumbWriter *writer; - Itdb_Track *song; - Itdb_Image *thumb; + auto gint offset_sort (gconstpointer a, gconstpointer b); + gint offset_sort (gconstpointer a, gconstpointer b) + { + return (((Itdb_Thumb *)a)->offset - + ((Itdb_Thumb *)b)->offset); + } + gchar *filename = _key; + GList *thumbs = _thumbs; + gboolean *result = _user_data; + gint fd = -1; + guint32 size = 0; + guint32 tnf_num, tn_num, i; + GList *gl; + struct stat statbuf; + void *buf = NULL; + +/* printf ("%s: %d\n", filename, g_list_length (thumbs)); */ + + /* check if an error occured */ + if (*result == FALSE) + goto out; + + /* check if all thumbnails have the same size */ + for (gl=thumbs; gl; gl=gl->next) + { + Itdb_Thumb *img = gl->data; + + if (size == 0) + size = img->size; + if (size != img->size) + { + *result = FALSE; + goto out; + } + } + /* OK, all thumbs are the same size @size, let's see how many + * thumbnails are in the actual file */ +/* printf (" %d\n", size); */ + if (g_stat (filename, &statbuf) != 0) + { + *result = FALSE; + goto out; + } + tnf_num = statbuf.st_size / size; + + /* check if the file size is a multiple of @size */ + if (tnf_num*size != statbuf.st_size) + { + *result = FALSE; + goto out; + } + + tn_num = g_list_length (thumbs); + + /* We're finished if the number here and the number of thumbnails + * in our list is the same */ + if (tn_num == tnf_num) + goto out; + + fd = open (filename, O_RDWR, 0); + if (fd == -1) + { + *result = FALSE; + goto out; + } + + /* Performance note: for performance reaons the list should be + ordered in reverse order of offsets because of frequent use + g_list_last(), and instead of using g_list_nth_data() the list + should be crawled by element from the end -- I will do that + eventually unless someone beats me to it. */ + + /* Sort the list of thumbs according to img->offset */ + thumbs = g_list_sort (thumbs, offset_sort); + + buf = g_malloc (size); + + for (i=0; ioffset) + { /* We found an open space -> copy the last element here */ + gl = g_list_last (thumbs); + img = gl->data; + thumbs = g_list_delete_link (thumbs, gl); + thumbs = g_list_insert (thumbs, img, i); + + /* actually copy the data */ + if (lseek (fd, img->offset, SEEK_SET) != img->offset) + { + *result = FALSE; + goto out; + } + if (read (fd, buf, size) != size) + { + *result = FALSE; + goto out; + } + if (lseek (fd, offset, SEEK_SET) != offset) + { + *result = FALSE; + goto out; + } + if (write (fd, buf, size) != size) + { + *result = FALSE; + goto out; + } + + img->offset = offset; + } + } + /* truncate the file */ + if (ftruncate (fd, tn_num*size) == -1) + { + *result = FALSE; + goto out; + } + + out: + if (fd != -1) close (fd); + if (buf) g_free (buf); + g_list_free (thumbs); + return TRUE; +} + + +/* The actual image data of thumbnails is not read into memory. As a + consequence, writing the thumbnail file is not as straight-forward + as e.g. writing the iTunesDB where all data is held in memory. - song = (Itdb_Track *)user_data; - writer = (iThumbWriter *)data; + To avoid the need to read large amounts from the iPod and back, or + have to large files exist on the iPod (reading from the original + thumbnail fail and writing to the new thumbnail file), the + modifications are done in place. - thumb = ithumb_writer_write_thumbnail (writer, - song->orig_image_filename); - if (thumb != NULL) { - song->thumbnails = g_list_append (song->thumbnails, thumb); - song->artwork_count++; + It is assumed that all thumbnails have the same data size. If not, + FALSE is returned. + + If a thumbnail has been removed, a slot in the file is opened. This + slot is filled by copying data from the end of the file and + adjusting the corresponding Itdb_Image offset pointer. When all + slots are filled, the file is truncated to the new length. +*/ +static gboolean +ithmb_rearrange_existing_thumbnails (Itdb_iTunesDB *itdb, + const IpodArtworkFormat *info) +{ + GList *gl; + GHashTable *filenamehash; + gboolean result = TRUE; + + g_return_val_if_fail (itdb, FALSE); + g_return_val_if_fail (info, FALSE); + g_return_val_if_fail (itdb->mountpoint, FALSE); + + filenamehash = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + /* Create a hash with all filenames used for thumbnails. + This will usually be a number of "F%04d_%d.ithmb" files. A + GList is kept with pointers to all images in a given file which + allows to adjust the offset pointers */ + for (gl=itdb->tracks; gl; gl=gl->next) + { + Itdb_Thumb *thumb; + Itdb_Track *track = gl->data; + g_return_val_if_fail (track, FALSE); + + thumb = itdb_artwork_get_thumb_by_type (track->artwork, + info->type); + if (thumb && thumb->filename && (thumb->size != 0)) + { + GList *thumbs; + gchar *filename = itdb_thumb_get_filename (itdb->device, + thumb); + if (filename) + { + thumbs = g_hash_table_lookup (filenamehash, filename); + thumbs = g_list_append (thumbs, thumb); + g_hash_table_insert (filenamehash, filename, thumbs); + } } + } + + g_hash_table_foreach_remove (filenamehash, + ithumb_rearrange_thumbnail_file, &result); + g_hash_table_destroy (filenamehash); + + return result; } +#endif G_GNUC_INTERNAL int -itdb_write_ithumb_files (Itdb_iTunesDB *db, const char *mount_point) +itdb_write_ithumb_files (Itdb_iTunesDB *db) { +#ifdef HAVE_GDKPIXBUF GList *writers; GList *it; + gchar *mount_point; const IpodArtworkFormat *format; /* g_print ("%s\n", G_GNUC_FUNCTION);*/ + g_return_val_if_fail (db, -1); + + mount_point = db->mountpoint; + /* FIXME: support writing to directory rather than writing to + iPod */ + if (mount_point == NULL) + return -1; if (db->device == NULL) { return -1; @@ -281,6 +505,8 @@ itdb_write_ithumb_files (Itdb_iTunesDB *db, const char *mount_point) switch (format->type) { case IPOD_COVER_SMALL: case IPOD_COVER_LARGE: + ithmb_rearrange_existing_thumbnails (db, + format); writer = ithumb_writer_new (mount_point, format); if (writer != NULL) { writers = g_list_prepend (writers, writer); @@ -297,26 +523,20 @@ itdb_write_ithumb_files (Itdb_iTunesDB *db, const char *mount_point) } for (it = db->tracks; it != NULL; it = it->next) { - Itdb_Track *song; + Itdb_Track *track; - song = (Itdb_Track *)it->data; - song->artwork_count = 0; - itdb_track_free_generated_thumbnails (song); - if (song->orig_image_filename == NULL) { - continue; - } - g_list_foreach (writers, write_thumbnail, song); + track = it->data; + g_return_val_if_fail (track, -1); + track->artwork_count = 0; + + g_list_foreach (writers, write_thumbnail, track->artwork); } g_list_foreach (writers, (GFunc)ithumb_writer_free, NULL); g_list_free (writers); return 0; -} #else -G_GNUC_INTERNAL int -itdb_write_ithumb_files (Itdb_iTunesDB *db, const char *mount_point) -{ return -1; -} #endif +} diff --git a/tests/test-covers.c b/tests/test-covers.c index 5816ce2..0a79cff 100644 --- a/tests/test-covers.c +++ b/tests/test-covers.c @@ -33,14 +33,14 @@ static void -save_itdb_image (Itdb_iTunesDB *itdb, Itdb_Image *image, const char *filename) +save_itdb_thumb (Itdb_iTunesDB *itdb, Itdb_Thumb *thumb, const char *filename) { - GdkPixbuf *thumb; + GdkPixbuf *pixbuf; - thumb = itdb_image_get_gdk_pixbuf (itdb, image); - if (thumb != NULL) { - gdk_pixbuf_save (thumb, filename, "png", NULL, NULL); - gdk_pixbuf_unref (thumb); + pixbuf = itdb_thumb_get_gdk_pixbuf (itdb->device, thumb); + if (pixbuf != NULL) { + gdk_pixbuf_save (pixbuf, filename, "png", NULL, NULL); + gdk_pixbuf_unref (pixbuf); /* g_print ("Saved %s\n", filename); */ } } @@ -49,20 +49,20 @@ save_song_thumbnails (Itdb_Track *song) { GList *it; - for (it = song->thumbnails; it != NULL; it = it->next) { - Itdb_Image *image; + for (it = song->artwork->thumbnails; it != NULL; it = it->next) { + Itdb_Thumb *thumb; gchar *filename; - image = (Itdb_Image *)it->data; - g_return_if_fail (image); + thumb = (Itdb_Thumb *)it->data; + g_return_if_fail (thumb); filename = NULL; filename = g_strdup_printf ("%s-%s-%s-%d-%016"G_GINT64_MODIFIER"x.png", song->artist, song->album, - song->title, image->type, + song->title, thumb->type, song->dbid); if (filename != NULL) { - save_itdb_image (song->itdb, image, filename); + save_itdb_thumb (song->itdb, thumb, filename); g_free (filename); } } diff --git a/tests/test-write-covers.c b/tests/test-write-covers.c index 88c0216..dab7fc0 100644 --- a/tests/test-write-covers.c +++ b/tests/test-write-covers.c @@ -97,11 +97,11 @@ main (int argc, char **argv) const char *coverpath; song = (Itdb_Track*)it->data; - itdb_track_remove_thumbnail (song); + itdb_artwork_remove_thumbnails (song->artwork); coverpath = g_list_nth_data (covers, g_random_int_range (0, nb_covers)); - itdb_track_set_thumbnail (song, coverpath); + itdb_track_set_thumbnails (song, coverpath); /* g_print ("%s - %s - %s gets %s\n", song->artist, song->album, song->title, coverpath);*/ -- cgit