Update of /cvsroot/gtkpod/libgpod/src In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv17627/src
Modified Files: Makefile.am db-artwork-parser.c db-artwork-writer.c db-image-parser.c db-image-parser.h itdb.h itdb_itunesdb.c itdb_track.c ithumb-writer.c Added Files: itdb_artwork.c Log Message: 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. --- NEW FILE: itdb_artwork.c --- /* Time-stamp: <2005-11-29 00:56:32 jcs> | | Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net> | 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: itdb_artwork.c,v 1.1 2005/11/28 16:20:40 jcsjcs Exp $ */ #include <config.h> #include "itdb_private.h" #include "db-image-parser.h" #include <errno.h> #include <stdio.h> #include <string.h> #if HAVE_GDKPIXBUF #include <gdk-pixbuf/gdk-pixbuf.h> #endif #include <glib/gi18n-lib.h> 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; } Index: Makefile.am =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/Makefile.am,v retrieving revision 1.9 retrieving revision 1.10 diff -u -d -r1.9 -r1.10 --- Makefile.am 21 Nov 2005 21:49:37 -0000 1.9 +++ Makefile.am 28 Nov 2005 16:20:40 -0000 1.10 @@ -2,6 +2,7 @@ libgpod_la_SOURCES = \ itdb.h \ + itdb_artwork.c \ itdb_itunesdb.c \ itdb_playlist.c \ itdb_private.h \ Index: db-artwork-parser.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/db-artwork-parser.c,v retrieving revision 1.8 retrieving revision 1.9 diff -u -d -r1.8 -r1.9 --- db-artwork-parser.c 24 Nov 2005 13:31:23 -0000 1.8 +++ db-artwork-parser.c 28 Nov 2005 16:20:40 -0000 1.9 @@ -108,28 +108,19 @@ 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 @@ 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 @@ { 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 @@ 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 @@ 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; Index: db-artwork-writer.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/db-artwork-writer.c,v retrieving revision 1.10 retrieving revision 1.11 diff -u -d -r1.10 -r1.11 --- db-artwork-writer.c 24 Nov 2005 13:31:24 -0000 1.10 +++ db-artwork-writer.c 28 Nov 2005 16:20:40 -0000 1.11 @@ -38,6 +38,7 @@ #include <unistd.h> #include <sys/mman.h> #include <sys/stat.h> +#include <stdio.h> #include <sys/types.h> #define IPOD_MMAP_SIZE 2 * 1024 * 1024 @@ -284,7 +285,7 @@ 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 @@ 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 @@ 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 @@ 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 @@ } 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 @@ 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 @@ } 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 @@ 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 @@ } 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 @@ 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 @@ 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 @@ 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 @@ /* 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); Index: db-image-parser.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/db-image-parser.c,v retrieving revision 1.7 retrieving revision 1.8 diff -u -d -r1.7 -r1.8 --- db-image-parser.c 24 Nov 2005 13:31:24 -0000 1.7 +++ db-image-parser.c 28 Nov 2005 16:20:40 -0000 1.8 @@ -31,155 +31,8 @@ #include "db-artwork-parser.h" #include "db-image-parser.h" -#if HAVE_GDKPIXBUF -#include <gdk-pixbuf/gdk-pixbuf.h> -#endif #include <glib/gi18n-lib.h> -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 @@ 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 @@ 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; } Index: db-image-parser.h =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/db-image-parser.h,v retrieving revision 1.4 retrieving revision 1.5 diff -u -d -r1.4 -r1.5 --- db-image-parser.h 24 Nov 2005 13:31:24 -0000 1.4 +++ db-image-parser.h 28 Nov 2005 16:20:40 -0000 1.5 @@ -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); Index: itdb.h =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/itdb.h,v retrieving revision 1.23 retrieving revision 1.24 diff -u -d -r1.23 -r1.24 --- itdb.h 24 Nov 2005 13:31:24 -0000 1.23 +++ itdb.h 28 Nov 2005 16:20:40 -0000 1.24 @@ -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 <jcsjcs at users sourceforge net> | Part of the gtkpod project. @@ -53,9 +53,78 @@ #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 @@ /* 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 @@ type" */ guint8 matchcheckedonly; /* "Match only checked songs" check box */ -} SPLPref; +}; -typedef struct SPLRule +struct _SPLRule { guint32 field; guint32 action; @@ -298,47 +367,29 @@ 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: - - 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; +/* ------------------------------------------------------------ *\ + * + * iTunesDB, Playlists, Tracks + * +\* ------------------------------------------------------------ */ -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 @@ 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 @@ ItdbUserDataDuplicateFunc userdata_duplicate; /* function called to free userdata */ ItdbUserDataDestroyFunc userdata_destroy; -} Itdb_Playlist; +}; /* @@ -468,7 +519,7 @@ 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 @@ 0x4d503320 -> 0x4d = 'M', 0x50 = 'P', 0x33 = '3', 0x20 = <space>. (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 @@ 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 @@ 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 @@ 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 @@ 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_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); Index: itdb_itunesdb.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/itdb_itunesdb.c,v retrieving revision 1.33 retrieving revision 1.34 diff -u -d -r1.33 -r1.34 --- itdb_itunesdb.c 24 Nov 2005 13:31:24 -0000 1.33 +++ itdb_itunesdb.c 28 Nov 2005 16:20:40 -0000 1.34 @@ -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 <jcsjcs at users sourceforge net> | Part of the gtkpod project. @@ -2667,6 +2667,25 @@ } +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 @@ 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 @@ 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 @@ * 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); Index: itdb_track.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/itdb_track.c,v retrieving revision 1.12 retrieving revision 1.13 diff -u -d -r1.12 -r1.13 --- itdb_track.c 21 Nov 2005 20:09:53 -0000 1.12 +++ itdb_track.c 28 Nov 2005 16:20:40 -0000 1.13 @@ -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 <jcsjcs at users sourceforge net> | Part of the gtkpod project. @@ -50,6 +50,8 @@ { Itdb_Track *track = g_new0 (Itdb_Track, 1); + track->artwork = itdb_artwork_new (); + track->visible = 1; return track; } @@ -79,7 +81,7 @@ 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 @@ 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 @@ } } } - /* 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 @@ 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 @@ 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 @@ 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 @@ } /* 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 @@ } +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 @@ 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 Index: ithumb-writer.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/ithumb-writer.c,v retrieving revision 1.5 retrieving revision 1.6 diff -u -d -r1.5 -r1.6 --- ithumb-writer.c 23 Nov 2005 18:21:52 -0000 1.5 +++ ithumb-writer.c 28 Nov 2005 16:20:40 -0000 1.6 @@ -35,6 +35,12 @@ #include <string.h> #include <gdk-pixbuf/gdk-pixbuf.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + + struct _iThumbWriter { off_t cur_offset; FILE *f; @@ -48,11 +54,11 @@ * 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 @@ "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 @@ -static Itdb_Image * -itdb_image_dup (Itdb_Image *image) + +static char * +ipod_image_get_ithmb_filename (const char *mount_point, gint correlation_id) { - Itdb_Image *result; + char *paths[] = {"iPod_Control", "Artwork", NULL, NULL}; + char *filename, *buf; - result = g_new0 (Itdb_Image, 1); - if (result == NULL) { - return NULL; + buf = g_strdup_printf ("F%04u_1.ithmb", correlation_id); + + paths[2] = buf; + + filename = itdb_resolve_path (mount_point, (const char **)paths); + + /* 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); } - result->type = image->type; - result->height = image->height; - result->width = image->width; - result->offset = image->offset; - result->size = image->size; - return result; + g_free (buf); + + return filename; } -static Itdb_Image * + + +static gboolean ithumb_writer_write_thumbnail (iThumbWriter *writer, - const char *filename) + Itdb_Thumb *thumb) { - GdkPixbuf *thumb; - gushort *pixels; - Itdb_Image *image; + GdkPixbuf *pixbuf; + guint16 *pixels; + gchar *filename; + gint width, height; - image = g_hash_table_lookup (writer->cache, filename); - if (image != NULL) { - return itdb_image_dup (image); - } + Itdb_Thumb *old_thumb; - image = g_new0 (Itdb_Image, 1); - if (image == NULL) { - return NULL; - } + g_return_val_if_fail (writer, FALSE); + g_return_val_if_fail (thumb, FALSE); - 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; - } + /* 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); - writer->cur_offset += image->size; - g_hash_table_insert (writer->cache, g_strdup (filename), image); + 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); - return image; -} + /* !! filename is g_free()d when destroying the hash table. Do not + do it here */ + return TRUE; +} -static char * -ipod_image_get_ithmb_filename (const char *mount_point, gint correlation_id) +static void +write_thumbnail (gpointer _writer, gpointer _artwork) { - char *paths[] = {"iPod_Control", "Artwork", NULL, NULL}; - char *filename; + iThumbWriter *writer = _writer; + Itdb_Artwork *artwork = _artwork; + Itdb_Thumb *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; -} + 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 @@ 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 @@ g_free (writer); return NULL; } + writer->cur_offset = ftell (writer->f); g_free (filename); return writer; @@ -236,33 +271,222 @@ } -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; - song = (Itdb_Track *)user_data; - writer = (iThumbWriter *)data; +/* printf ("%s: %d\n", filename, g_list_length (thumbs)); */ - thumb = ithumb_writer_write_thumbnail (writer, - song->orig_image_filename); - if (thumb != NULL) { - song->thumbnails = g_list_append (song->thumbnails, thumb); - song->artwork_count++; + /* 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; i<tn_num; ++i) + { + guint offset = i * size; + Itdb_Thumb *img = g_list_nth_data (thumbs, i); + if (offset != img->offset) + { /* 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. + + 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. + + 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 @@ 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 @@ } 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 +} ------------------------------------------------------- This SF.net email is sponsored by: Splunk Inc. Do you grep through log files for problems? Stop! Download the new AJAX search engine that makes searching your log files as easy as surfing the web. DOWNLOAD SPLUNK! http://ads.osdn.com/?ad_id=7637&alloc_id=16865&op=click _______________________________________________ gtkpod-cvs2 mailing list gtkpod-cvs2@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/gtkpod-cvs2