? Makefile ? Makefile.in ? aclocal.m4 ? autom4te.cache ? compile ? config.guess ? config.h ? config.h.in ? config.log ? config.status ? config.sub ? configure ? db-artwork-debug.c ? db-artwork-debug.h ? depcomp ? install-sh ? interdiff.diff ? intltool-extract ? intltool-extract.in ? intltool-merge ? intltool-merge.in ? intltool-update ? intltool-update.in ? libgpod-1.0.pc ? libgpod-artworkdb-write-support-v2.diff ? libgpod-artworkdb-write-support-v3.diff ? libgpod-artworkdb-write-support-v4.diff ? libgpod-artworkdb-write-support.diff ? libgpod-v1-v2.diff ? libtool ? ltmain.sh ? missing ? stamp-h1 ? po/Makefile ? po/Makefile.in ? po/POTFILES ? po/de.gmo ? po/fr.gmo ? po/he.gmo ? po/it.gmo ? po/ja.gmo ? po/sv.gmo ? src/.deps ? src/.libs ? src/Makefile ? src/Makefile.in ? src/db-artwork-debug.lo ? src/db-artwork-parser.lo ? src/db-artwork-writer.lo ? src/db-image-parser.lo ? src/db-parse-context.lo ? src/itdb_itunesdb.lo ? src/itdb_playlist.lo ? src/itdb_track.lo ? src/ithumb-writer.lo ? src/libgpod.la ? tests/.deps ? tests/.libs ? tests/Makefile ? tests/Makefile.in ? tests/test-itdb ? tests/test-thumbnails ? tests/test-write-thumbnails Index: src/Makefile.am =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/Makefile.am,v retrieving revision 1.2 diff -p -u -r1.2 Makefile.am --- src/Makefile.am 19 Sep 2005 10:30:50 -0000 1.2 +++ src/Makefile.am 22 Sep 2005 07:42:32 -0000 @@ -1,4 +1,9 @@ lib_LTLIBRARIES = libgpod.la +if HAVE_GDKPIXBUF +ARTWORKDB_WRITER_FILES= db-artwork-writer.c ithumb-writer.c +else +ARTWORKDB_WRITER_FILES= +endif libgpod_la_SOURCES = \ itdb.h \ @@ -13,7 +18,8 @@ libgpod_la_SOURCES = \ db-artwork-debug.c \ db-artwork-debug.h \ db-image-parser.c \ - db-image-parser.h + db-image-parser.h \ + $(ARTWORKDB_WRITER_FILES) libgpod_la_headers = itdb.h @@ -24,5 +30,5 @@ libgpod_la_LDFLAGS = -version-info $(LIB libgpodincludedir = $(includedir)/gpod-1.0/gpod libgpodinclude_HEADERS = $(libgpod_la_headers) -INCLUDES=$(LIBGPOD_CFLAGS) -LIBS=$(LIBGPOD_LIBS) +INCLUDES=$(LIBGPOD_CFLAGS) $(GDKPIXBUF_CFLAGS) +LIBS=$(LIBGPOD_LIBS) $(GDKPIXBUF_LIBS) Index: src/db-artwork-parser.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/db-artwork-parser.c,v retrieving revision 1.1 diff -p -u -r1.1 db-artwork-parser.c --- src/db-artwork-parser.c 19 Sep 2005 10:30:50 -0000 1.1 +++ src/db-artwork-parser.c 22 Sep 2005 07:42:32 -0000 @@ -28,7 +28,6 @@ #include "db-image-parser.h" #include "db-itunes-parser.h" #include "db-parse-context.h" -/*#include "image-parser.h"*/ typedef int (*ParseListItem)(DBParseContext *ctx, Itdb_iTunesDB *db, GError *error); @@ -89,15 +88,11 @@ parse_mhod_3 (DBParseContext *ctx, GErro } #endif -#define FULL_THUMB_SIDE_LEN 0x8c -#define NOW_PLAYING_THUMB_SIDE_LEN 0x38 - static int parse_mhni (DBParseContext *ctx, iPodSong *song, GError *error) { MhniHeader *mhni; - int width; - int height; + Itdb_Image *thumb; mhni = db_parse_context_get_m_header (ctx, MhniHeader, "mhni"); if (mhni == NULL) { @@ -121,16 +116,9 @@ parse_mhni (DBParseContext *ctx, iPodSon g_free (mhod_ctx); } #else - width = (GINT_FROM_LE (mhni->image_dimensions) & 0xffff0000) >> 16; - height = (GINT_FROM_LE (mhni->image_dimensions) & 0x0000ffff); - - if ((width == FULL_THUMB_SIDE_LEN) || (width == FULL_THUMB_SIDE_LEN)) { - song->full_size_thumbnail = ipod_image_new_from_mhni (mhni, song->itdb->mountpoint); - } else if ((width == NOW_PLAYING_THUMB_SIDE_LEN) || (width == NOW_PLAYING_THUMB_SIDE_LEN)) { - song->now_playing_thumbnail = ipod_image_new_from_mhni (mhni, song->itdb->mountpoint); - } else { - g_print ("Unrecognized image size: %08x\n", - GINT_FROM_LE (mhni->image_dimensions)); + thumb = ipod_image_new_from_mhni (mhni, song->itdb->mountpoint); + if (thumb != NULL) { + song->thumbnails = g_list_append (song->thumbnails, thumb); } #endif return 0; @@ -189,7 +177,12 @@ parse_mhii (DBParseContext *ctx, Itdb_iT return -1; } - song->orig_image = ipod_image_new_from_mhii (mhii); + if (song->artwork_size != GINT_FROM_LE (mhii->orig_img_size)-1) { + 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); #endif cur_offset = ctx->header_len; @@ -336,21 +329,20 @@ parse_mhfd (DBParseContext *ctx, Itdb_iT G_GNUC_INTERNAL char * -ipod_db_get_artwork_db_path (Itdb_iTunesDB *db) +ipod_db_get_artwork_db_path (const char *mount_point) { - return g_build_filename (G_DIR_SEPARATOR_S, db->mountpoint, - "iPod_Control", "Artwork", "ArtworkDB", - NULL); + const char *paths[] = {"iPod_Control", "Artwork", "ArtworkDB", NULL}; + return itdb_resolve_path (mount_point, paths); } static char * ipod_db_get_photo_db_path (const char *mount_point) { + const char *paths[] = {"Photos", "Photo Database", NULL}; g_return_val_if_fail (mount_point != NULL, NULL); - return g_build_filename (G_DIR_SEPARATOR_S, mount_point, - "Photos", "Photo Database", - NULL); + return itdb_resolve_path (mount_point, paths); + } @@ -360,7 +352,7 @@ ipod_parse_artwork_db (Itdb_iTunesDB *db DBParseContext *ctx; char *filename; - filename = ipod_db_get_artwork_db_path (db); + filename = ipod_db_get_artwork_db_path (db->mountpoint); ctx = db_parse_context_new_from_file (filename); g_free (filename); if (ctx == NULL) { @@ -368,11 +360,13 @@ ipod_parse_artwork_db (Itdb_iTunesDB *db } parse_mhfd (ctx, db, NULL); - g_free (ctx); + db_parse_context_destroy (ctx, TRUE); return 0; error: - /* FIXME: needs to destroy ctx and to release the mmap'ed memory*/ + if (ctx != NULL) { + db_parse_context_destroy (ctx, TRUE); + } return -1; } @@ -393,6 +387,7 @@ ipod_parse_photo_db (const char *mount_p } parse_mhfd (ctx, NULL, NULL); - g_free (ctx); + db_parse_context_destroy (ctx, TRUE); + return 0; } Index: src/db-artwork-parser.h =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/db-artwork-parser.h,v retrieving revision 1.1 diff -p -u -r1.1 db-artwork-parser.h --- src/db-artwork-parser.h 19 Sep 2005 10:30:50 -0000 1.1 +++ src/db-artwork-parser.h 22 Sep 2005 07:42:32 -0000 @@ -30,10 +30,13 @@ #define iPodSong Itdb_Track -int ipod_parse_photo_db (const char *filename); -int ipod_parse_artwork_db (Itdb_iTunesDB *db); -int ipod_write_artwork_db (Itdb_iTunesDB *db); +#define IPOD_THUMBNAIL_FULL_SIZE_CORRELATION_ID 1016 +#define IPOD_THUMBNAIL_NOW_PLAYING_CORRELATION_ID 1017 -G_GNUC_INTERNAL char *ipod_db_get_artwork_db_path (Itdb_iTunesDB *db); +G_GNUC_INTERNAL int ipod_parse_photo_db (const char *filename); +G_GNUC_INTERNAL int ipod_parse_artwork_db (Itdb_iTunesDB *db); +G_GNUC_INTERNAL int ipod_write_artwork_db (Itdb_iTunesDB *db, + const char *mount_point); +G_GNUC_INTERNAL char *ipod_db_get_artwork_db_path (const char *mount_point); #endif Index: src/db-artwork-writer.c =================================================================== RCS file: src/db-artwork-writer.c diff -N src/db-artwork-writer.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/db-artwork-writer.c 22 Sep 2005 07:42:33 -0000 @@ -0,0 +1,769 @@ +/* + * Copyright (C) 2005 Christophe Fergeau + * + * + * 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! + * + */ + +#include + +#include "db-artwork-debug.h" +#include "db-artwork-parser.h" +#include "db-itunes-parser.h" +#include "db-image-parser.h" + +#include +#include +#include +#include +#include +#include +#include + +#define IPOD_MMAP_SIZE 2 * 1024 * 1024 + +struct iPodMmapBuffer { + int fd; + void *mmap_area; + size_t size; + int ref_count; +}; + +struct _iPodBuffer { + struct iPodMmapBuffer *mmap; + off_t offset; +}; + +typedef struct _iPodBuffer iPodBuffer; + +static void +ipod_mmap_buffer_destroy (struct iPodMmapBuffer *buf) +{ + munmap (buf->mmap_area, buf->size); + close (buf->fd); + g_free (buf); +} + +static void +ipod_buffer_destroy (iPodBuffer *buffer) +{ + buffer->mmap->ref_count--; + if (buffer->mmap->ref_count == 0) { + g_print ("Destroying mmap buffer\n"); + ipod_mmap_buffer_destroy (buffer->mmap); + } + g_free (buffer); +} + +static void * +ipod_buffer_get_pointer (iPodBuffer *buffer) +{ + return &((unsigned char *)buffer->mmap->mmap_area)[buffer->offset]; +} + +static int +ipod_buffer_grow_file (struct iPodMmapBuffer *mmap_buf, off_t new_size) +{ + int result; + + result = lseek (mmap_buf->fd, new_size, SEEK_SET); + if (result == (off_t)-1) { + g_print ("Failed to grow file to %lu: %s\n", + new_size, strerror (errno)); + return -1; + } + result = 0; + result = write (mmap_buf->fd, &result, 1); + if (result != 1) { + g_print ("Failed to write a byte at %lu: %s\n", + new_size, strerror (errno)); + return -1; + } + + return 0; +} + +static int +ipod_buffer_maybe_grow (iPodBuffer *buffer, off_t offset) +{ + void *new_address; + + if (buffer->offset + offset <= buffer->mmap->size) { + return 0; + } + + /* Don't allow libc to move the current mapping since this would + * force us to be very careful wrt pointers in the rest of the code + */ + new_address = mremap (buffer->mmap->mmap_area, buffer->mmap->size, + buffer->mmap->size + IPOD_MMAP_SIZE, 0); + if (new_address == MAP_FAILED) { + g_print ("Failed to mremap buffer: %s\n", strerror (errno)); + return -1; + } + if (ipod_buffer_grow_file (buffer->mmap, + buffer->mmap->size + IPOD_MMAP_SIZE) != 0) { + return -1; + } + buffer->mmap->size += IPOD_MMAP_SIZE; + + return 0; +} + +static iPodBuffer * +ipod_buffer_get_sub_buffer (iPodBuffer *buffer, off_t offset) +{ + iPodBuffer *sub_buffer; + + if (ipod_buffer_maybe_grow (buffer, offset) != 0) { + return NULL; + } + sub_buffer = g_new0 (iPodBuffer, 1); + if (sub_buffer == NULL) { + return NULL; + } + sub_buffer->mmap = buffer->mmap; + sub_buffer->offset = buffer->offset + offset; + + buffer->mmap->ref_count++; + + return sub_buffer; +} + +static iPodBuffer * +ipod_buffer_new (const char *filename) +{ + int fd; + struct iPodMmapBuffer *mmap_buf; + iPodBuffer *buffer; + void *mmap_area; + + fd = open (filename, O_RDWR | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd == -1) { + g_print ("Failed to open %s: %s\n", + filename, strerror (errno)); + return NULL; + } + + mmap_area = mmap (0, IPOD_MMAP_SIZE, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (mmap_area == MAP_FAILED) { + g_print ("Failed to mmap %s in memory: %s\n", filename, + strerror (errno)); + close (fd); + return NULL; + } + mmap_buf = g_new0 (struct iPodMmapBuffer, 1); + if (mmap_buf == NULL) { + munmap (mmap_area, IPOD_MMAP_SIZE); + close (fd); + return NULL; + } + mmap_buf->mmap_area = mmap_area; + mmap_buf->size = IPOD_MMAP_SIZE; + mmap_buf->ref_count = 1; + mmap_buf->fd = fd; + + if (ipod_buffer_grow_file (mmap_buf, IPOD_MMAP_SIZE) != 0) { + ipod_mmap_buffer_destroy (mmap_buf); + return NULL; + } + + buffer = g_new0 (iPodBuffer, 1); + if (buffer == NULL) { + ipod_mmap_buffer_destroy (mmap_buf); + return NULL; + } + buffer->mmap = mmap_buf; + + return buffer; +} + +enum MhsdType { + MHSD_TYPE_MHLI = 1, + MHSD_TYPE_MHLA = 2, + MHSD_TYPE_MHLF = 3 +}; + +enum iPodThumbnailType { + IPOD_THUMBNAIL_FULL_SIZE, + IPOD_THUMBNAIL_NOW_PLAYING +}; + +#define IPOD_THUMBNAIL_FULL_SIZE_SIZE (140*140*2) +#define IPOD_THUMBNAIL_NOW_PLAYING_SIZE (56*56*2) + + +#define RETURN_SIZE_FOR(id, size) if (strncmp (id, header_id, 4) == 0) return (size) + +/* Returns the "real" size for a header, ie the size iTunes uses for it + * (padding included) + */ +static int +get_padded_header_size (gchar header_id[4]) +{ + RETURN_SIZE_FOR ("mhni", 0x4c); + RETURN_SIZE_FOR ("mhii", 0x98); + RETURN_SIZE_FOR ("mhsd", 0x60); + RETURN_SIZE_FOR ("mhfd", 0x84); + RETURN_SIZE_FOR ("mhli", 0x5c); + RETURN_SIZE_FOR ("mhla", 0x5c); + RETURN_SIZE_FOR ("mhlf", 0x5c); + RETURN_SIZE_FOR ("mhif", 0x7c); + + return 0; +} + +static void * +init_header (iPodBuffer *buffer, gchar header_id[4], guint header_len) +{ + MHeader *mh; + int padded_size; + + padded_size = get_padded_header_size (header_id); + if (padded_size != 0) { + header_len = padded_size; + } + g_assert (header_len > sizeof (MHeader)); + if (ipod_buffer_maybe_grow (buffer, header_len) != 0) { + return NULL; + } + mh = (MHeader*)ipod_buffer_get_pointer (buffer); + memset (mh, 0, header_len); + strncpy ((char *)mh->header_id, header_id, 4); + mh->header_len = GINT_TO_LE (header_len); + + return mh; +} + +static int +write_mhod_type_3 (enum iPodThumbnailType type, iPodBuffer *buffer) +{ + MhodHeaderArtworkType3 *mhod; + unsigned int total_bytes; + char *filename; + int len; + gunichar2 *utf16; + int i; + mhod = (MhodHeaderArtworkType3 *)init_header (buffer, "mhod", + sizeof (MhodHeaderArtworkType3)); + if (mhod == NULL) { + return -1; + } + + total_bytes = sizeof (MhodHeaderArtworkType3); + mhod->total_len = GINT_TO_LE (total_bytes); + /* Modify header length, since iTunes only puts the length of + * MhodHeader in header_len + */ + mhod->header_len = GINT_TO_LE (sizeof (MhodHeader)); + mhod->type = GINT_TO_LE (3); + mhod->mhod_version = GINT_TO_LE (2); + switch (type) { + case ITDB_IMAGE_FULL_SCREEN: + filename = g_strdup_printf (":F%04u_1.ithmb", + IPOD_THUMBNAIL_FULL_SIZE_CORRELATION_ID); + break; + case ITDB_IMAGE_NOW_PLAYING: + filename = g_strdup_printf (":F%04u_1.ithmb", + IPOD_THUMBNAIL_NOW_PLAYING_CORRELATION_ID); + break; + default: + g_assert_not_reached (); + } + + len = strlen (filename); + + /* number of bytes of the string encoded in UTF-16 */ + mhod->string_len = GINT_TO_LE (2*len); + + /* Make sure we have enough free space to write the string */ + if (ipod_buffer_maybe_grow (buffer, total_bytes + 2*len) != 0) { + g_free (filename); + return -1; + } + utf16 = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL); + g_free (filename); + if (utf16 == NULL) { + return -1; + } + memcpy (mhod->string, utf16, 2*len); + g_free (utf16); + for (i = 0; i < len; i++) { + mhod->string[i] = GINT_TO_LE (mhod->string[i]); + } + total_bytes += 2*len; + mhod->total_len = GINT_TO_LE (total_bytes); + + dump_mhod_type_3 (mhod); + + return total_bytes; +} + +static int +write_mhni (Itdb_Image *image, iPodBuffer *buffer) +{ + MhniHeader *mhni; + unsigned int total_bytes; + int bytes_written; + iPodBuffer *sub_buffer; + unsigned int dim; + + if (image == NULL) { + return -1; + } + + mhni = (MhniHeader *)init_header (buffer, "mhni", + sizeof (MhniHeader)); + if (mhni == NULL) { + return -1; + } + total_bytes = GINT_FROM_LE (mhni->header_len); + mhni->total_len = GINT_TO_LE (total_bytes); + + + switch (image->type) { + case ITDB_IMAGE_NOW_PLAYING: + mhni->correlation_id = GINT_TO_LE (IPOD_THUMBNAIL_NOW_PLAYING_CORRELATION_ID); + break; + case ITDB_IMAGE_FULL_SCREEN: + mhni->correlation_id = GINT_TO_LE (IPOD_THUMBNAIL_FULL_SIZE_CORRELATION_ID); + break; + } + dim = image->width & 0xffff; + dim <<= 16; + dim |= image->height & 0xffff; + mhni->image_dimensions = GINT_TO_LE (dim); + mhni->image_size = GINT_TO_LE (image->size); + mhni->ithmb_offset = GINT_TO_LE (image->offset); + + sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes); + if (sub_buffer == NULL) { + return -1; + } + bytes_written = write_mhod_type_3 (image->type, sub_buffer); + ipod_buffer_destroy (sub_buffer); + if (bytes_written == -1) { + return -1; + } + total_bytes += bytes_written; + mhni->total_len = GINT_TO_LE (total_bytes); + /* Only update number of children when all went well to try to get + * something somewhat consistent when there are errors + */ + mhni->num_children = GINT_TO_LE (1); + + dump_mhni (mhni); + + return total_bytes; +} + +static int +write_mhod (Itdb_Image *image, iPodBuffer *buffer) +{ + MhodHeader *mhod; + unsigned int total_bytes; + int bytes_written; + iPodBuffer *sub_buffer; + + if (image == NULL) { + return -1; + } + + mhod = (MhodHeader *)init_header (buffer, "mhod", + sizeof (MhodHeader)); + if (mhod == NULL) { + return -1; + } + total_bytes = sizeof (MhodHeader); + mhod->total_len = GINT_TO_LE (total_bytes); + mhod->type = GINT_TO_LE (MHOD_TYPE_LOCATION); + sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes); + if (sub_buffer == NULL) { + return -1; + } + bytes_written = write_mhni (image, sub_buffer); + ipod_buffer_destroy (sub_buffer); + if (bytes_written == -1) { + return -1; + } + total_bytes += bytes_written; + mhod->total_len = GINT_TO_LE (total_bytes); + + dump_mhod (mhod); + + return total_bytes; +} + +static int +write_mhii (Itdb_Track *song, iPodBuffer *buffer) +{ + MhiiHeader *mhii; + unsigned int total_bytes; + int bytes_written; + int num_children; + GList *it; + + mhii = (MhiiHeader *)init_header (buffer, "mhii", sizeof (MhiiHeader)); + if (mhii == NULL) { + return -1; + } + 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); + /* 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) { + iPodBuffer *sub_buffer; + Itdb_Image *thumb; + + mhii->num_children = GINT_TO_LE (num_children); + mhii->total_len = GINT_TO_LE (total_bytes); + sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes); + if (sub_buffer == NULL) { + return -1; + } + thumb = (Itdb_Image *)it->data; + bytes_written = write_mhod (thumb, sub_buffer); + ipod_buffer_destroy (sub_buffer); + if (bytes_written == -1) { + return -1; + } + total_bytes += bytes_written; + num_children++; + } + + mhii->num_children = GINT_TO_LE (num_children); + mhii->total_len = GINT_TO_LE (total_bytes); + + dump_mhii (mhii); + + return total_bytes; +} + +static int +write_mhli (Itdb_iTunesDB *db, iPodBuffer *buffer) +{ + GList *it; + MhliHeader *mhli; + unsigned int total_bytes; + int num_thumbs; + + mhli = (MhliHeader *)init_header (buffer, "mhli", sizeof (MhliHeader)); + if (mhli == NULL) { + return -1; + } + + num_thumbs = 0; + total_bytes = GINT_FROM_LE (mhli->header_len); + for (it = db->tracks; it != NULL; it = it->next) { + Itdb_Track *song; + int bytes_written; + iPodBuffer *sub_buffer; + + song = (Itdb_Track*)it->data; + if (song->image_id == 0) { + continue; + } + sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes); + if (sub_buffer == NULL) { + continue; + } + bytes_written = write_mhii (song, sub_buffer); + ipod_buffer_destroy (sub_buffer); + if (bytes_written != -1) { + num_thumbs++; + total_bytes += bytes_written; + } + } + + mhli->num_children = GINT_TO_LE (num_thumbs); + + dump_mhl ((MhlHeader *)mhli, "mhli"); + + return total_bytes; +} + +static int +write_mhla (Itdb_iTunesDB *db, iPodBuffer *buffer) +{ + MhlaHeader *mhla; + + mhla = (MhlaHeader *)init_header (buffer, "mhla", sizeof (MhlaHeader)); + if (mhla == NULL) { + return -1; + } + + dump_mhl ((MhlHeader *)mhla, "mhla"); + + return GINT_FROM_LE (mhla->header_len); +} + +static int +write_mhif (Itdb_iTunesDB *db, iPodBuffer *buffer, enum iPodThumbnailType type) +{ + MhifHeader *mhif; + + mhif = (MhifHeader *)init_header (buffer, "mhif", sizeof (MhifHeader)); + if (mhif == NULL) { + return -1; + } + mhif->total_len = mhif->header_len; + switch (type) { + case ITDB_IMAGE_FULL_SCREEN: + mhif->correlation_id = GINT_TO_LE (IPOD_THUMBNAIL_FULL_SIZE_CORRELATION_ID); + mhif->image_size = GINT_TO_LE (IPOD_THUMBNAIL_FULL_SIZE_SIZE); + break; + case ITDB_IMAGE_NOW_PLAYING: + mhif->correlation_id = GINT_TO_LE (IPOD_THUMBNAIL_NOW_PLAYING_CORRELATION_ID); + mhif->image_size = GINT_TO_LE (IPOD_THUMBNAIL_NOW_PLAYING_SIZE); + break; + } + + dump_mhif (mhif); + + return GINT_FROM_LE (mhif->header_len); +} + + +static int +write_mhlf (Itdb_iTunesDB *db, iPodBuffer *buffer) +{ + MhlfHeader *mhlf; + unsigned int total_bytes; + int bytes_written; + iPodBuffer *sub_buffer; + + mhlf = (MhlfHeader *)init_header (buffer, "mhlf", sizeof (MhlfHeader)); + if (mhlf == NULL) { + return -1; + } + + total_bytes = GINT_FROM_LE (mhlf->header_len); + + sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes); + if (sub_buffer == NULL) { + return -1; + } + bytes_written = write_mhif (db, sub_buffer, IPOD_THUMBNAIL_FULL_SIZE); + ipod_buffer_destroy (sub_buffer); + if (bytes_written == -1) { + return -1; + } + total_bytes += bytes_written; + + /* Only update number of children when all went well to try to get + * something somewhat consistent when there are errors + */ + /* For the ArworkDB file, there are only 2 physical file storing + * thumbnails: F1016_1.ithmb for the bigger thumbnails (39200 bytes) + * and F1017_1.ithmb for the 'now playing' thumbnails (6272) + */ + mhlf->num_files = GINT_TO_LE (1); + + sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes); + if (sub_buffer == NULL) { + return -1; + } + bytes_written = write_mhif (db, sub_buffer, IPOD_THUMBNAIL_NOW_PLAYING); + ipod_buffer_destroy (sub_buffer); + if (bytes_written == -1) { + return -1; + } + total_bytes += bytes_written; + + /* Only update number of children when all went well to try to get + * something somewhat consistent when there are errors + */ + /* For the ArworkDB file, there are only 2 physical file storing + * thumbnails: F1016_1.ithmb for the bigger thumbnails (39200 bytes) + * and F1017_1.ithmb for the 'now playing' thumbnails (6272) + */ + mhlf->num_files = GINT_TO_LE (2); + + dump_mhl ((MhlHeader *)mhlf, "mhlf"); + + return total_bytes; +} + + +static int +write_mhsd (Itdb_iTunesDB *db, iPodBuffer *buffer, enum MhsdType type) +{ + MhsdHeader *mhsd; + unsigned int total_bytes; + int bytes_written; + iPodBuffer *sub_buffer; + + g_assert (type >= MHSD_TYPE_MHLI); + g_assert (type <= MHSD_TYPE_MHLF); + mhsd = (MhsdHeader *)init_header (buffer, "mhsd", sizeof (MhsdHeader)); + if (mhsd == NULL) { + return -1; + } + total_bytes = GINT_FROM_LE (mhsd->header_len); + mhsd->total_len = GINT_TO_LE (total_bytes); + mhsd->index = GINT_TO_LE (type); + bytes_written = -1; + + sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes); + if (sub_buffer == NULL) { + return -1; + } + switch (type) { + case MHSD_TYPE_MHLI: + bytes_written = write_mhli (db, sub_buffer); + break; + case MHSD_TYPE_MHLA: + bytes_written = write_mhla (db, sub_buffer); + break; + case MHSD_TYPE_MHLF: + bytes_written = write_mhlf (db, sub_buffer); + break; + } + ipod_buffer_destroy (sub_buffer); + if (bytes_written == -1) { + return -1; + } else { + total_bytes += bytes_written; + mhsd->total_len = GINT_TO_LE (total_bytes); + } + + dump_mhsd (mhsd); + + return total_bytes; +} + +static int +write_mhfd (Itdb_iTunesDB *db, iPodBuffer *buffer, int id_max) +{ + MhfdHeader *mhfd; + unsigned int total_bytes; + int bytes_written; + int i; + + mhfd = (MhfdHeader *)init_header (buffer, "mhfd", sizeof (MhfdHeader)); + if (mhfd == NULL) { + return -1; + } + total_bytes = GINT_FROM_LE (mhfd->header_len); + mhfd->total_len = GINT_TO_LE (total_bytes); + mhfd->unknown2 = GINT_TO_LE (1); + mhfd->unknown4 = GINT_TO_LE (id_max); + mhfd->unknown7 = GINT_TO_LE (2); + for (i = 1 ; i <= 3; i++) { + iPodBuffer *sub_buffer; + + sub_buffer = ipod_buffer_get_sub_buffer (buffer, total_bytes); + if (sub_buffer == NULL) { + continue; + } + bytes_written = write_mhsd (db, sub_buffer, i); + ipod_buffer_destroy (sub_buffer); + if (bytes_written == -1) { + return -1; + } + total_bytes += bytes_written; + mhfd->total_len = GINT_TO_LE (total_bytes); + mhfd->num_children = GINT_TO_LE (i); + } + + dump_mhfd (mhfd); + + return total_bytes; +} + +static unsigned int +ipod_artwork_db_set_ids (Itdb_iTunesDB *db) +{ + GList *it; + unsigned int id; + + id = 64; + for (it = db->tracks; it != NULL; it = it->next) { + Itdb_Track *song; + + song = (Itdb_Track *)it->data; + if (song->thumbnails != NULL) { + song->image_id = id; + id++; + } + } + + return id; +} + +int +ipod_write_artwork_db (Itdb_iTunesDB *db, const char *mount_point) +{ + iPodBuffer *buf; + int bytes_written; + int result; + char *filename; + int id_max; + + /* 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 + */ + itdb_write_ithumb_files (db, mount_point); + g_print ("%s\n", G_GNUC_FUNCTION); + /* Now we can update the ArtworkDB file */ + id_max = ipod_artwork_db_set_ids (db); + + filename = ipod_db_get_artwork_db_path (mount_point); + buf = ipod_buffer_new (filename); + if (buf == NULL) { + g_print ("Couldn't create %s\n", filename); + g_free (filename); + return -1; + } + bytes_written = write_mhfd (db, buf, id_max); + + /* Refcount of the mmap buffer should drop to 0 and this should + * sync buffered data to disk + * FIXME: it's probably less error-prone to add a ipod_buffer_mmap_sync + * call... + */ + ipod_buffer_destroy (buf); + + if (bytes_written == -1) { + g_print ("Failed to save %s\n", filename); + g_free (filename); + /* FIXME: maybe should unlink the file we may have created */ + return -1; + } + + result = truncate (filename, bytes_written); + if (result != 0) { + g_print ("Failed to truncate %s: %s\n", + filename, strerror (errno)); + g_free (filename); + return -1; + } + g_free (filename); + return 0; +} Index: src/db-image-parser.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/db-image-parser.c,v retrieving revision 1.1 diff -p -u -r1.1 db-image-parser.c --- src/db-image-parser.c 19 Sep 2005 10:30:50 -0000 1.1 +++ src/db-image-parser.c 22 Sep 2005 07:42:33 -0000 @@ -29,21 +29,9 @@ #include #include +#include "db-artwork-parser.h" #include "db-image-parser.h" -#define RED_BITS 5 -#define RED_SHIFT 11 -#define RED_MASK (((1 << RED_BITS)-1) << RED_SHIFT) - -#define GREEN_BITS 6 -#define GREEN_SHIFT 5 -#define GREEN_MASK (((1 << GREEN_BITS)-1) << GREEN_SHIFT) - -#define BLUE_BITS 5 -#define BLUE_SHIFT 0 -#define BLUE_MASK (((1 << BLUE_BITS)-1) << BLUE_SHIFT) - - static unsigned char * unpack_RGB_565 (gushort *pixels, unsigned int bytes_len) { @@ -72,49 +60,6 @@ unpack_RGB_565 (gushort *pixels, unsigne return result; } -#if 0 -G_GNUC_UNUSED static void -pack_RGB_565 (GdkPixbuf *pixbuf, gushort **pixels565, unsigned int *bytes_len) -{ - guchar *pixels; - gushort *result; - gint row_stride; - gint channels; - gint width; - gint height; - gint w; - gint h; - - g_object_get (G_OBJECT (pixbuf), - "rowstride", &row_stride, "n-channels", &channels, - "height", &height, "width", &width, - "pixels", &pixels, NULL); - - result = g_malloc0 (width * height * 2); - - for (h = 0; h < height; h++) { - for (w = 0; w < width; w++) { - gint r; - gint g; - gint b; - - r = pixels[(h*row_stride + w)*channels]; - g = pixels[(h*row_stride + w)*channels + 1]; - b = pixels[(h*row_stride + w)*channels + 2]; - r >>= (8 - RED_BITS); - g >>= (8 - GREEN_BITS); - b >>= (8 - BLUE_BITS); - r = (r << RED_SHIFT) & RED_MASK; - g = (g << GREEN_SHIFT) & GREEN_MASK; - b = (b << BLUE_SHIFT) & BLUE_MASK; - result[h*height + w] = (GINT16_TO_LE (r | g | b)); - } - } - - *pixels565 = result; - *bytes_len = width * height * 2; -} -#endif static unsigned char * get_pixel_data (Itdb_Image *image) @@ -186,6 +131,18 @@ itdb_image_get_rgb_data (Itdb_Image *ima */ } +G_GNUC_INTERNAL char * +ipod_image_get_ithmb_filename (const char *mount_point, gint correlation_id) +{ + char *paths[] = {"iPod_Control", "Artwork", NULL, NULL}; + char *filename; + + 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; +} + G_GNUC_INTERNAL Itdb_Image * ipod_image_new_from_mhni (MhniHeader *mhni, const char *mount_point) { @@ -195,26 +152,25 @@ ipod_image_new_from_mhni (MhniHeader *mh if (img == NULL) { return NULL; } - img->filename = g_strdup_printf ("%s/iPod_Control/Artwork/F%04u_1.ithmb", mount_point, GINT_FROM_LE (mhni->correlation_id)); + img->filename = ipod_image_get_ithmb_filename (mount_point, + GINT_FROM_LE (mhni->correlation_id)); img->size = GINT_FROM_LE (mhni->image_size); img->offset = GINT_FROM_LE (mhni->ithmb_offset); img->width = (GINT_FROM_LE (mhni->image_dimensions) & 0xffff0000) >> 16; img->height = (GINT_FROM_LE (mhni->image_dimensions) & 0x0000ffff); - return img; -} - -G_GNUC_INTERNAL Itdb_Image * -ipod_image_new_from_mhii (MhiiHeader *mhii) -{ - Itdb_Image *img; - - img = g_new0 (Itdb_Image, 1); - if (img == NULL) { + if (mhni->correlation_id == IPOD_THUMBNAIL_FULL_SIZE_CORRELATION_ID) { + img->type = ITDB_IMAGE_FULL_SCREEN; + } else if (mhni->correlation_id == IPOD_THUMBNAIL_NOW_PLAYING_CORRELATION_ID) + { + img->type = ITDB_IMAGE_NOW_PLAYING; + + } else { + g_print ("Unrecognized image size: %08x\n", + GINT_FROM_LE (mhni->image_dimensions)); + g_free (img); return NULL; } - img->size = GINT_FROM_LE (mhii->orig_img_size); - img->id = GINT_FROM_LE (mhii->image_id); - return img; + return img; } Index: src/db-image-parser.h =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/db-image-parser.h,v retrieving revision 1.1 diff -p -u -r1.1 db-image-parser.h --- src/db-image-parser.h 19 Sep 2005 10:30:50 -0000 1.1 +++ src/db-image-parser.h 22 Sep 2005 07:42:33 -0000 @@ -28,7 +28,25 @@ #include "db-itunes-parser.h" #include "itdb.h" -Itdb_Image *ipod_image_new_from_mhni (MhniHeader *mhni, const char *mount_point); -Itdb_Image *ipod_image_new_from_mhii (MhiiHeader *mhii); +#define RED_BITS 5 +#define RED_SHIFT 11 +#define RED_MASK (((1 << RED_BITS)-1) << RED_SHIFT) + +#define GREEN_BITS 6 +#define GREEN_SHIFT 5 +#define GREEN_MASK (((1 << GREEN_BITS)-1) << GREEN_SHIFT) + +#define BLUE_BITS 5 +#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, + const char *mount_point); +G_GNUC_INTERNAL char *ipod_image_get_ithmb_filename (const char *mount_point, + gint correlation_id); + +G_GNUC_INTERNAL int itdb_write_ithumb_files (Itdb_iTunesDB *db, + const char *mount_point); + #endif Index: src/db-parse-context.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/db-parse-context.c,v retrieving revision 1.1 diff -p -u -r1.1 db-parse-context.c --- src/db-parse-context.c 19 Sep 2005 10:30:50 -0000 1.1 +++ src/db-parse-context.c 22 Sep 2005 07:42:33 -0000 @@ -53,6 +53,16 @@ db_parse_context_new (const unsigned cha return result; } +void +db_parse_context_destroy (DBParseContext *ctx, gboolean unmap) +{ + g_return_if_fail (ctx != NULL); + + if (unmap) { + munmap ((void*)ctx->buffer, ctx->total_len); + } + g_free (ctx); +} static void db_parse_context_set_header_len (DBParseContext *ctx, off_t len) Index: src/db-parse-context.h =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/db-parse-context.h,v retrieving revision 1.1 diff -p -u -r1.1 db-parse-context.h --- src/db-parse-context.h 19 Sep 2005 10:30:50 -0000 1.1 +++ src/db-parse-context.h 22 Sep 2005 07:42:33 -0000 @@ -49,5 +49,6 @@ G_GNUC_INTERNAL DBParseContext *db_parse G_GNUC_INTERNAL void *db_parse_context_get_m_header_internal (DBParseContext *ctx, const char *id, off_t size) G_GNUC_INTERNAL; G_GNUC_INTERNAL DBParseContext *db_parse_context_new_from_file (const char *filename) G_GNUC_INTERNAL; +G_GNUC_INTERNAL void db_parse_context_destroy (DBParseContext *ctx, gboolean unmap); #endif Index: src/itdb.h =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/itdb.h,v retrieving revision 1.7 diff -p -u -r1.7 itdb.h --- src/itdb.h 20 Sep 2005 13:20:32 -0000 1.7 +++ src/itdb.h 22 Sep 2005 07:42:33 -0000 @@ -307,6 +307,12 @@ typedef struct SPLRules GList *rules; } SPLRules; +enum ItdbImageType { + ITDB_IMAGE_FULL_SCREEN, + ITDB_IMAGE_NOW_PLAYING +}; + + /* 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), @@ -320,13 +326,14 @@ typedef struct SPLRules * on the iPod */ struct _Itdb_Image { - char *filename; + enum ItdbImageType type; + char *filename; off_t offset; size_t size; unsigned int width; unsigned int height; - unsigned int id; }; + typedef struct _Itdb_Image Itdb_Image; typedef void (* ItdbUserDataDestroyFunc) (gpointer userdata); @@ -573,9 +580,9 @@ typedef struct guint32 unk228, unk232, unk236, unk240; /* This is for Cover Art support */ - Itdb_Image *full_size_thumbnail; - Itdb_Image *now_playing_thumbnail; - Itdb_Image *orig_image; + GList *thumbnails; + unsigned int image_id; + char *orig_image_filename; /* below is for use by application */ guint64 usertype; @@ -681,6 +688,9 @@ void itdb_spl_update_all (Itdb_iTunesDB /* 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); /* time functions */ guint64 itdb_time_get_mac_time (void); Index: src/itdb_itunesdb.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/itdb_itunesdb.c,v retrieving revision 1.9 diff -p -u -r1.9 itdb_itunesdb.c --- src/itdb_itunesdb.c 21 Sep 2005 15:41:30 -0000 1.9 +++ src/itdb_itunesdb.c 22 Sep 2005 07:42:36 -0000 @@ -898,6 +898,7 @@ Itdb_iTunesDB *itdb_new (void) itdb->version = 0x09; itdb->id = ((guint64)g_rand_int (grand) << 32) | ((guint64)g_rand_int (grand)); + g_rand_free (grand); return itdb; } @@ -3301,6 +3302,14 @@ gboolean itdb_write (Itdb_iTunesDB *itdb if (!mp) mp = itdb->mountpoint; + /* First, let's try to write the .ithmb files containing the artwork data + * since this operation modifies the 'artwork_count' and 'artwork_size' + * field in the Itdb_Track contained in the database. + * Errors happening during that operation are considered non fatal since + * they shouldn't corrupt the main database. + */ + ipod_write_artwork_db (itdb, mp); + itunes_path = itdb_resolve_path (mp, db); if(!itunes_path) Index: src/itdb_private.h =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/itdb_private.h,v retrieving revision 1.2 diff -p -u -r1.2 itdb_private.h --- src/itdb_private.h 19 Sep 2005 12:33:24 -0000 1.2 +++ src/itdb_private.h 22 Sep 2005 07:42:36 -0000 @@ -99,5 +99,6 @@ typedef struct } FExport; -gboolean itdb_spl_action_known (SPLAction action); +G_GNUC_INTERNAL gboolean itdb_spl_action_known (SPLAction action); + #endif Index: src/itdb_track.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/src/itdb_track.c,v retrieving revision 1.2 diff -p -u -r1.2 itdb_track.c --- src/itdb_track.c 19 Sep 2005 09:02:52 -0000 1.2 +++ src/itdb_track.c 22 Sep 2005 07:42:36 -0000 @@ -29,6 +29,7 @@ #include "itdb_private.h" #include +#include /* Generate a new Itdb_Track structure */ Itdb_Track *itdb_track_new (void) @@ -144,6 +145,22 @@ void itdb_track_add (Itdb_iTunesDB *itdb 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) { @@ -158,6 +175,8 @@ void itdb_track_free (Itdb_Track *track) g_free (track->filetype); g_free (track->grouping); g_free (track->ipod_path); + itdb_track_free_generated_thumbnails (track); + g_free (track->orig_image_filename); if (track->userdata && track->userdata_destroy) (*track->userdata_destroy) (track->userdata); g_free (track); @@ -292,3 +311,30 @@ Itdb_Track *itdb_track_id_tree_by_id (GT 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; +} + + +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; +} Index: src/ithumb-writer.c =================================================================== RCS file: src/ithumb-writer.c diff -N src/ithumb-writer.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/ithumb-writer.c 22 Sep 2005 07:42:36 -0000 @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2005 Christophe Fergeau + * + * + * 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! + * + */ + +#include "itdb.h" +#include "itdb_private.h" +#include "db-image-parser.h" + +#include +#include +#include +#include + +#define FULL_THUMB_SIDE_LEN 0x8c +#define NOW_PLAYING_THUMB_SIDE_LEN 0x38 + +#define IPOD_THUMBNAIL_FULL_SIZE_CORRELATION_ID 1016 +#define IPOD_THUMBNAIL_NOW_PLAYING_CORRELATION_ID 1017 + +struct _iThumbWriter { + off_t cur_offset; + FILE *f; + guint correlation_id; + enum ItdbImageType type; + int size; + GHashTable *cache; +}; +typedef struct _iThumbWriter iThumbWriter; + +/* The iPod expect square thumbnails with 2 specific side sizes (0x38 and 0x8c + * respectively for small and fullscreen thumbnails), the 'size' parameter is + * here to specify which size we are interested in in case the pixbuf is non + * square + */ +static void +pack_RGB_565 (GdkPixbuf *pixbuf, int size, + gushort **pixels565, unsigned int *bytes_len) +{ + guchar *pixels; + gushort *result; + gint row_stride; + gint channels; + gint width; + gint height; + gint w; + gint h; + + g_return_if_fail (pixels565 != NULL); + *pixels565 = NULL; + g_return_if_fail (bytes_len != NULL); + + g_object_get (G_OBJECT (pixbuf), + "rowstride", &row_stride, "n-channels", &channels, + "height", &height, "width", &width, + "pixels", &pixels, NULL); + g_return_if_fail ((width <= size) && (height <= size)); + result = g_malloc0 (size * size * 2); + + for (h = 0; h < height; h++) { + for (w = 0; w < width; w++) { + gint r; + gint g; + gint b; + + r = pixels[h*row_stride + w*channels]; + g = pixels[h*row_stride + w*channels + 1]; + b = pixels[h*row_stride + w*channels + 2]; + r >>= (8 - RED_BITS); + g >>= (8 - GREEN_BITS); + b >>= (8 - BLUE_BITS); + r = (r << RED_SHIFT) & RED_MASK; + g = (g << GREEN_SHIFT) & GREEN_MASK; + b = (b << BLUE_SHIFT) & BLUE_MASK; + result[h*size + w] = (GINT16_TO_LE (r | g | b)); + } + } + + *pixels565 = result; + *bytes_len = size * size * 2; +} + + + +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; + + return result; +} + +static Itdb_Image * +ithumb_writer_write_thumbnail (iThumbWriter *writer, + const char *filename) +{ + GdkPixbuf *thumb; + gushort *pixels; + Itdb_Image *image; + + image = g_hash_table_lookup (writer->cache, filename); + if (image != NULL) { + return itdb_image_dup (image); + } + + image = g_new0 (Itdb_Image, 1); + if (image == NULL) { + return NULL; + } + + thumb = gdk_pixbuf_new_from_file_at_scale (filename, writer->size, -1, + TRUE, NULL); + if (thumb == NULL) { + g_free (image); + return NULL; + } + g_object_get (G_OBJECT (thumb), "height", &image->height, NULL); + if (image->height > writer->size) { + g_object_unref (thumb); + thumb = gdk_pixbuf_new_from_file_at_scale (filename, + -1, writer->size, + TRUE, 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->type; + pack_RGB_565 (thumb, writer->size, &pixels, &image->size); + 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); + + return image; +} + + +#define FULL_THUMB_SIDE_LEN 0x8c +#define NOW_PLAYING_THUMB_SIDE_LEN 0x38 + +#define FULL_THUMB_CORRELATION_ID 1016 +#define NOW_PLAYING_THUMB_CORRELATION_ID 1017 + + + +static iThumbWriter * +ithumb_writer_new (const char *mount_point, enum ItdbImageType type, + int correlation_id, int size) +{ + char *filename; + iThumbWriter *writer; + writer = g_new0 (iThumbWriter, 1); + if (writer == NULL) { + return NULL; + } + writer->correlation_id = correlation_id; + writer->size = size; + writer->type = type; + writer->cache = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + if (writer->cache == NULL) { + g_free (writer); + return NULL; + } + filename = ipod_image_get_ithmb_filename (mount_point, correlation_id); + writer->f = fopen (filename, "w"); + if (writer->f == NULL) { + g_print ("Error opening %s: %s\n", filename, strerror (errno)); + g_free (filename); + g_hash_table_destroy (writer->cache); + g_free (writer); + return NULL; + } + g_free (filename); + + return writer; +} + +static void +ithumb_writer_free (iThumbWriter *writer) +{ + g_return_if_fail (writer != NULL); + g_hash_table_destroy (writer->cache); + fclose (writer->f); + g_free (writer); +} + +G_GNUC_INTERNAL int +itdb_write_ithumb_files (Itdb_iTunesDB *db, const char *mount_point) +{ + GList *it; + iThumbWriter *fullsize_writer; + iThumbWriter *nowplaying_writer; + g_print ("%s\n", G_GNUC_FUNCTION); + + fullsize_writer = ithumb_writer_new (mount_point, + ITDB_IMAGE_FULL_SCREEN, + FULL_THUMB_CORRELATION_ID, + FULL_THUMB_SIDE_LEN); + if (fullsize_writer == NULL) { + return -1; + } + + nowplaying_writer = ithumb_writer_new (mount_point, + ITDB_IMAGE_NOW_PLAYING, + NOW_PLAYING_THUMB_CORRELATION_ID, + NOW_PLAYING_THUMB_SIDE_LEN); + if (nowplaying_writer == NULL) { + ithumb_writer_free (fullsize_writer); + return -1; + } + + for (it = db->tracks; it != NULL; it = it->next) { + Itdb_Track *song; + Itdb_Image *thumb; + + song = (Itdb_Track *)it->data; + song->artwork_count = 0; + itdb_track_free_generated_thumbnails (song); + if (song->orig_image_filename == NULL) { + continue; + } + thumb = ithumb_writer_write_thumbnail (nowplaying_writer, + song->orig_image_filename); + if (thumb != NULL) { + song->thumbnails = g_list_append (song->thumbnails, + thumb); + song->artwork_count++; + } + thumb = ithumb_writer_write_thumbnail (fullsize_writer, + song->orig_image_filename); + if (thumb != NULL) { + song->thumbnails = g_list_append (song->thumbnails, + thumb); + song->artwork_count++; + } + } + + ithumb_writer_free (nowplaying_writer); + ithumb_writer_free (fullsize_writer); + + return 0; +} Index: tests/Makefile.am =================================================================== RCS file: /cvsroot/gtkpod/libgpod/tests/Makefile.am,v retrieving revision 1.2 diff -p -u -r1.2 Makefile.am --- tests/Makefile.am 19 Sep 2005 10:30:50 -0000 1.2 +++ tests/Makefile.am 22 Sep 2005 07:42:36 -0000 @@ -1,5 +1,5 @@ if HAVE_GDKPIXBUF -TESTTHUMBS=test-thumbnails +TESTTHUMBS=test-thumbnails test-write-thumbnails else TESTTHUMBS= endif @@ -16,4 +16,8 @@ if HAVE_GDKPIXBUF test_thumbnails_SOURCES = test-covers.c test_thumbnails_CFLAGS = $(GDKPIXBUF_CFLAGS) $(AM_CFLAGS) test_thumbnails_LDADD = $(top_builddir)/src/libgpod.la $(GDKPIXBUF_LIBS) + +test_write_thumbnails_SOURCES = test-write-covers.c +test_write_thumbnails_CFLAGS = $(GDKPIXBUF_CFLAGS) $(AM_CFLAGS) +test_write_thumbnails_LDADD = $(top_builddir)/src/libgpod.la $(GDKPIXBUF_LIBS) endif Index: tests/test-covers.c =================================================================== RCS file: /cvsroot/gtkpod/libgpod/tests/test-covers.c,v retrieving revision 1.1 diff -p -u -r1.1 test-covers.c --- tests/test-covers.c 19 Sep 2005 10:30:50 -0000 1.1 +++ tests/test-covers.c 22 Sep 2005 07:42:36 -0000 @@ -36,14 +36,25 @@ ipod_image_to_gdk_pixbuf (Itdb_Image *im { GdkPixbuf *result; guchar *pixels; + int row_stride; + + if (image->type == ITDB_IMAGE_FULL_SCREEN) { + row_stride = 140; + } else if (image->type == ITDB_IMAGE_NOW_PLAYING) { + row_stride = 56; + } else { + return NULL; + } pixels = itdb_image_get_rgb_data (image); if (pixels == NULL) { return NULL; } + result = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, FALSE, 8, image->width, image->height, - image->width * 3, + /*image->width * 3, */ + row_stride * 3, (GdkPixbufDestroyNotify)g_free, NULL); return result; @@ -59,28 +70,31 @@ save_itdb_image (Itdb_Image *image, cons if (thumb != NULL) { gdk_pixbuf_save (thumb, filename, "png", NULL, NULL); gdk_pixbuf_unref (thumb); - g_print ("Saved %s\n", filename); +/* g_print ("Saved %s\n", filename); */ } } static void save_song_thumbnails (Itdb_Track *song) { - if (song->full_size_thumbnail != NULL) { + GList *it; + + for (it = song->thumbnails; it != NULL; it = it->next) { + Itdb_Image *image; gchar *filename; - - filename = g_strdup_printf ("/tmp/fullsize%016llx.png", - song->dbid); - save_itdb_image (song->full_size_thumbnail,filename); - g_free (filename); - } - if (song->now_playing_thumbnail != NULL) { - gchar *filename; - - filename = g_strdup_printf ("/tmp/nowplaying%016llx.png", - song->dbid); - save_itdb_image (song->now_playing_thumbnail, filename); - g_free (filename); + image = (Itdb_Image *)it->data; + filename = NULL; + if (image->type == ITDB_IMAGE_FULL_SCREEN) { + filename = g_strdup_printf ("/tmp/fullsize%016llx.png", + song->dbid); + } else if (image->type == ITDB_IMAGE_NOW_PLAYING) { + filename = g_strdup_printf ("/tmp/nowplaying%016llx.png", + song->dbid); + } + if (filename != NULL) { + save_itdb_image (image, filename); + g_free (filename); + } } } Index: tests/test-write-covers.c =================================================================== RCS file: tests/test-write-covers.c diff -N tests/test-write-covers.c --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ tests/test-write-covers.c 22 Sep 2005 07:42:36 -0000 @@ -0,0 +1,126 @@ +/* Copyright (c) 2005, Christophe Fergeau + * All rights reserved. + + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "itdb.h" + +#include +#include +#include + +static GList * +get_cover_list (const char *dirname) +{ + GDir *dir; + const char *filename; + GList *result; + + dir = g_dir_open (dirname, 0, NULL); + if (dir == NULL) { + return NULL; + } + result = NULL; + filename = g_dir_read_name (dir); + while (filename != NULL) { + const char *ext; + ext = strrchr (filename, '.'); + if (ext != NULL) { + if ((g_ascii_strcasecmp (ext, ".png") == 0) + || (g_ascii_strcasecmp (ext, ".jpg") == 0) + || (g_ascii_strcasecmp (ext, ".jpeg") == 0)) { + char *fullpath; + fullpath = g_build_filename (dirname, filename, + NULL); + result = g_list_prepend (result, fullpath); + } + } + filename = g_dir_read_name (dir); + } + g_dir_close (dir); + + return g_list_reverse (result); +} + + +int +main (int argc, char **argv) +{ + Itdb_iTunesDB *db; + GList *it; + GList *covers; + int nb_covers; + + if (argc < 3) { + g_print ("Usage: %s mountpoint image-dir\n", argv[0]); + return 1; + } + + setlocale (LC_ALL, ""); + g_type_init (); + covers = get_cover_list (argv[2]); + if (covers == NULL) { + g_print ("Error, %s should be a directory containing pictures\n", argv[2]); + return 1; + } + nb_covers = g_list_length (covers); + db = itdb_parse (argv[1], NULL); + if (db == NULL) { + g_print ("Error reading iPod database\n"); + return 1; + } + for (it = db->tracks; it != NULL; it = it->next) { + Itdb_Track *song; + const char *coverpath; + + song = (Itdb_Track*)it->data; + itdb_track_remove_thumbnail (song); + + coverpath = g_list_nth_data (covers, + g_random_int_range (0, nb_covers)); + itdb_track_set_thumbnail (song, coverpath); +/* g_print ("%s - %s - %s gets %s\n", + song->artist, song->album, song->title, coverpath);*/ + + } +/* if (db->tracks != NULL) { + Itdb_Track *song; + const char *coverpath; + + song = (Itdb_Track *)db->tracks->data; + coverpath = g_list_nth_data (covers, + g_random_int_range (0, nb_covers)); + itdb_track_remove_thumbnail (song); + itdb_track_set_thumbnail (song, coverpath); + }*/ + + itdb_write (db, NULL, NULL); + itdb_free (db); + g_list_foreach (covers, (GFunc)g_free, NULL); + g_list_free (covers); + + return 0; +}