summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJorg Schuler <jcsjcs@users.sourceforge.net>2005-09-22 18:16:50 +0000
committerJorg Schuler <jcsjcs@users.sourceforge.net>2005-09-22 18:16:50 +0000
commit32fdf71f74fee4e0774d7ae5167447c70cd83b7c (patch)
treedd5f9c7eb78249d02e1a80a8a7447c2bcc0fd4c9 /src
parentfba33d9b3d03cb92df15904c045fcfe93d251fa4 (diff)
downloadlibgpod-tmz-32fdf71f74fee4e0774d7ae5167447c70cd83b7c.tar.gz
libgpod-tmz-32fdf71f74fee4e0774d7ae5167447c70cd83b7c.tar.xz
libgpod-tmz-32fdf71f74fee4e0774d7ae5167447c70cd83b7c.zip
* applied Christophe Fergeau's patch which adds cover art writing
support to libgpod -> bump to version 104 git-svn-id: https://gtkpod.svn.sf.net/svnroot/gtkpod/libgpod/trunk@1098 f01d2545-417e-4e96-918e-98f8d0dbbcb6
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am12
-rw-r--r--src/db-artwork-parser.c51
-rw-r--r--src/db-artwork-parser.h11
-rw-r--r--src/db-artwork-writer.c769
-rw-r--r--src/db-image-parser.c96
-rw-r--r--src/db-image-parser.h22
-rw-r--r--src/db-parse-context.c10
-rw-r--r--src/db-parse-context.h1
-rw-r--r--src/itdb.h20
-rw-r--r--src/itdb_itunesdb.c9
-rw-r--r--src/itdb_private.h3
-rw-r--r--src/itdb_track.c46
-rw-r--r--src/ithumb-writer.c288
13 files changed, 1225 insertions, 113 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 1a46d92..917d9fa 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -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 $(LIBGPOD_CURRENT):$(LIBGPOD_REVISION):$(LIBG
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)
diff --git a/src/db-artwork-parser.c b/src/db-artwork-parser.c
index 69f3b83..8c41dd6 100644
--- a/src/db-artwork-parser.c
+++ b/src/db-artwork-parser.c
@@ -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, GError *error)
}
#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, iPodSong *song, GError *error)
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_iTunesDB *db, GError *error)
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_iTunesDB *db, GError **error)
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_point)
}
parse_mhfd (ctx, NULL, NULL);
- g_free (ctx);
+ db_parse_context_destroy (ctx, TRUE);
+
return 0;
}
diff --git a/src/db-artwork-parser.h b/src/db-artwork-parser.h
index 06b0e09..aaeabff 100644
--- a/src/db-artwork-parser.h
+++ b/src/db-artwork-parser.h
@@ -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
diff --git a/src/db-artwork-writer.c b/src/db-artwork-writer.c
new file mode 100644
index 0000000..cb97b7a
--- /dev/null
+++ b/src/db-artwork-writer.c
@@ -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 <config.h>
+
+#include "db-artwork-debug.h"
+#include "db-artwork-parser.h"
+#include "db-itunes-parser.h"
+#include "db-image-parser.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#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;
+}
diff --git a/src/db-image-parser.c b/src/db-image-parser.c
index a87485f..b04cec6 100644
--- a/src/db-image-parser.c
+++ b/src/db-image-parser.c
@@ -29,21 +29,9 @@
#include <glib.h>
#include <glib-object.h>
+#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, unsigned int bytes_len)
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 *image)
*/
}
+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 *mhni, const char *mount_point)
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;
}
diff --git a/src/db-image-parser.h b/src/db-image-parser.h
index 0c36cc4..4c0002e 100644
--- a/src/db-image-parser.h
+++ b/src/db-image-parser.h
@@ -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
diff --git a/src/db-parse-context.c b/src/db-parse-context.c
index 4f4264e..ffdd7c4 100644
--- a/src/db-parse-context.c
+++ b/src/db-parse-context.c
@@ -53,6 +53,16 @@ db_parse_context_new (const unsigned char *buffer, off_t len)
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)
diff --git a/src/db-parse-context.h b/src/db-parse-context.h
index b442c36..528856f 100644
--- a/src/db-parse-context.h
+++ b/src/db-parse-context.h
@@ -49,5 +49,6 @@ G_GNUC_INTERNAL DBParseContext *db_parse_context_get_next_child (DBParseContext
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
diff --git a/src/itdb.h b/src/itdb.h
index 35a6c42..1b19c6d 100644
--- a/src/itdb.h
+++ b/src/itdb.h
@@ -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 *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);
/* time functions */
guint64 itdb_time_get_mac_time (void);
diff --git a/src/itdb_itunesdb.c b/src/itdb_itunesdb.c
index 7b034ee..e8eacc3 100644
--- a/src/itdb_itunesdb.c
+++ b/src/itdb_itunesdb.c
@@ -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, const gchar *mp, GError **error)
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)
diff --git a/src/itdb_private.h b/src/itdb_private.h
index fa94d5f..abbd1d0 100644
--- a/src/itdb_private.h
+++ b/src/itdb_private.h
@@ -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
diff --git a/src/itdb_track.c b/src/itdb_track.c
index 41372c0..9aa1b30 100644
--- a/src/itdb_track.c
+++ b/src/itdb_track.c
@@ -29,6 +29,7 @@
#include "itdb_private.h"
#include <string.h>
+#include <glib/gstdio.h>
/* Generate a new Itdb_Track structure */
Itdb_Track *itdb_track_new (void)
@@ -144,6 +145,22 @@ void itdb_track_add (Itdb_iTunesDB *itdb, Itdb_Track *track, gint32 pos)
else itdb->tracks = g_list_insert (itdb->tracks, track, pos);
}
+void
+itdb_track_free_generated_thumbnails (Itdb_Track *track)
+{
+ GList *it;
+
+ for (it = track->thumbnails; it != NULL; it = it->next) {
+ Itdb_Image *image;
+
+ image = (Itdb_Image *)it->data;
+ g_free (image->filename);
+ g_free (image);
+ }
+ g_list_free (track->thumbnails);
+ track->thumbnails = NULL;
+}
+
/* Free the memory taken by @track */
void itdb_track_free (Itdb_Track *track)
{
@@ -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 (GTree *idtree, guint32 id)
return (Itdb_Track *)g_tree_lookup (idtree, &id);
}
+
+void
+itdb_track_remove_thumbnail (Itdb_Track *song)
+{
+ itdb_track_free_generated_thumbnails (song);
+ g_free (song->orig_image_filename);
+ song->orig_image_filename = NULL;
+ song->image_id = 0;
+}
+
+
+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;
+}
diff --git a/src/ithumb-writer.c b/src/ithumb-writer.c
new file mode 100644
index 0000000..99c2f7a
--- /dev/null
+++ b/src/ithumb-writer.c
@@ -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 <errno.h>
+#include <locale.h>
+#include <string.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#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;
+}