changeset 2199:8b3c2fe608c9

APEv2 writing implemented
author Eugene Zagidullin <e.asphyx@gmail.com>
date Fri, 30 Nov 2007 03:56:33 +0300
parents 32f9f1e4a9ec
children d10f13536b94
files src/demac/ape.c src/demac/ape.h src/demac/apev2.c src/demac/apev2.h src/demac/plugin.c
diffstat 5 files changed, 176 insertions(+), 24 deletions(-) [+]
line wrap: on
line diff
--- a/src/demac/ape.c	Thu Nov 29 04:17:51 2007 +0300
+++ b/src/demac/ape.c	Fri Nov 30 03:56:33 2007 +0300
@@ -74,6 +74,18 @@
     return tmp[0] | (tmp[1] << 8) | (tmp[2] << 16) | (tmp[3] << 24);
 }
 
+int put_le32(uint32_t val, VFSFile *vfd)
+{
+    unsigned char tmp[4];
+    
+    tmp[0] = (val & 0x000000ff);
+    tmp[1] = (val & 0x0000ff00) >> 8;
+    tmp[2] = (val & 0x00ff0000) >> 16;
+    tmp[3] = (val & 0xff000000) >> 24;
+    
+    return aud_vfs_fwrite(tmp, 1, 4, vfd);
+}
+
 uint64_t get_le64(VFSFile *vfd)
 {
     unsigned char tmp[8];
--- a/src/demac/ape.h	Thu Nov 29 04:17:51 2007 +0300
+++ b/src/demac/ape.h	Fri Nov 30 03:56:33 2007 +0300
@@ -290,6 +290,7 @@
 
 uint16_t get_le16(VFSFile *vfd);
 uint32_t get_le32(VFSFile *vfd);
+int put_le32(uint32_t val, VFSFile *vfd);
 uint64_t get_le64(VFSFile *vfd);
 
 int ape_read_header(APEContext *ape, VFSFile *pb, int probe_only);
--- a/src/demac/apev2.c	Thu Nov 29 04:17:51 2007 +0300
+++ b/src/demac/apev2.c	Fri Nov 30 03:56:33 2007 +0300
@@ -37,6 +37,16 @@
 #define MIN(a,b) ((a) < (b) ? (a) : (b))
 #endif
 
+#define FLAGS_HEADER_EXISTS (1 << 31)
+#define FLAGS_HEADER (1 << 29)
+#define APE_SIGNATURE MKTAG64('A', 'P', 'E', 'T', 'A', 'G', 'E', 'X')
+
+typedef struct {
+  int tag_items;
+  int tag_size;
+  VFSFile *vfd;
+} iterator_pvt_t;
+
 mowgli_dictionary_t* parse_apev2_tag(VFSFile *vfd) {
   unsigned char tmp[TMP_BUFSIZE+1];
   unsigned char tmp2[TMP_BUFSIZE+1];
@@ -45,12 +55,12 @@
   long tag_size, item_size;
   int item_flags;
   int tag_items;
-  int tag_flags;
+  unsigned int tag_flags;
   mowgli_dictionary_t *dict;
 
   aud_vfs_fseek(vfd, -32, SEEK_END);
   signature = get_le64(vfd);
-  if (signature != MKTAG64('A', 'P', 'E', 'T', 'A', 'G', 'E', 'X')) {
+  if (signature != APE_SIGNATURE) {
 #ifdef DEBUG
     fprintf(stderr, "** demac: apev2.c: APE tag not found\n");
 #endif
@@ -85,23 +95,125 @@
 #endif
       
       /* read key */
-      for(p = tmp; p <= tmp+TMP_BUFSIZE; p++) {
-        aud_vfs_fread(p, 1, 1, vfd);
-	if(*p == '\0') break;
-      }
-      *(p+1) = '\0';
+      if (item_size > 0 && item_size < tag_size) { /* be bulletproof */
+          for(p = tmp; p <= tmp+TMP_BUFSIZE; p++) {
+            aud_vfs_fread(p, 1, 1, vfd);
+            if(*p == '\0') break;
+          }
+          *(p+1) = '\0';
 
-      /* read item */
-      aud_vfs_fread(tmp2, 1, MIN(item_size, TMP_BUFSIZE), vfd);
-      tmp2[item_size] = '\0';
+          /* read item */
+          aud_vfs_fread(tmp2, 1, MIN(item_size, TMP_BUFSIZE), vfd);
+          tmp2[item_size] = '\0';
 #ifdef DEBUG
-      fprintf(stderr, "%s: \"%s\", f:%08x\n", tmp, tmp2, item_flags);
+          fprintf(stderr, "%s: \"%s\", f:%08x\n", tmp, tmp2, item_flags);
 #endif
-      /* APEv2 stores all items in utf-8 */
-      gchar *item = ((tag_version == 1000 ) ? aud_str_to_utf8((gchar*)tmp2) : g_strdup((gchar*)tmp2));
+          /* APEv2 stores all items in utf-8 */
+          gchar *item = ((tag_version == 1000 ) ? aud_str_to_utf8((gchar*)tmp2) : g_strdup((gchar*)tmp2));
       
-      mowgli_dictionary_add(dict, (char*)tmp, item);
+          mowgli_dictionary_add(dict, (char*)tmp, item);
+    }
   }
-  
+
   return dict;
 }
+
+static void write_header_or_footer(guint32 version, guint32 size, guint32 items, guint32 flags, VFSFile *vfd) {
+  guint64 filling = 0;
+
+  aud_vfs_fwrite("APETAGEX", 1, 8, vfd);
+  put_le32(version, vfd);
+  put_le32(size, vfd);
+  put_le32(items, vfd);
+  put_le32(flags, vfd);
+  aud_vfs_fwrite(&filling, 1, 8, vfd);
+}
+
+static int foreach_cb (mowgli_dictionary_elem_t *delem, void *privdata) {
+    iterator_pvt_t *s = (iterator_pvt_t*)privdata;
+    guint32 item_size, item_flags=0;
+    if (s->vfd == NULL) {
+
+          if(strlen((char*)(delem->data)) != 0) {
+              s->tag_items++;
+              s->tag_size += strlen((char*)(delem->key)) + strlen((char*)(delem->data)) + 9; /* length in bytes not symbols */
+          }
+
+    } else {
+
+          if( (item_size = strlen((char*)delem->data)) != 0 ) {
+#ifdef DEBUG
+              fprintf(stderr, "Writing field %s = %s\n", (char*)delem->key, (char*)delem->data);
+#endif
+              put_le32(item_size, s->vfd);
+              put_le32(item_flags, s->vfd); /* all set to zero */
+              aud_vfs_fwrite(delem->key, 1, strlen((char*)delem->key) + 1, s->vfd); /* null-terminated */
+              aud_vfs_fwrite(delem->data, 1, item_size, s->vfd);
+          }
+    }
+
+    return 1;
+}
+
+gboolean write_apev2_tag(VFSFile *vfd, mowgli_dictionary_t *tag) {
+  guint64 signature;
+  guint32 tag_version;
+  guint32 tag_size, tag_items = 0, tag_flags;
+  long file_size;
+
+  if (vfd == NULL || tag == NULL) return FALSE;
+
+  aud_vfs_fseek(vfd, -32, SEEK_END);
+  signature = get_le64(vfd);
+
+  /* strip existing tag */
+  if (signature == APE_SIGNATURE) {
+      tag_version = get_le32(vfd);
+      tag_size = get_le32(vfd);
+      tag_items = get_le32(vfd);
+      tag_flags = get_le32(vfd);
+      aud_vfs_fseek(vfd, 0, SEEK_END);
+      file_size = aud_vfs_ftell(vfd);
+      file_size -= (long)tag_size;
+
+      /* also strip header */
+      if((tag_version >= 2000) && (tag_flags | FLAGS_HEADER_EXISTS)) {
+          aud_vfs_fseek(vfd, -((long)tag_size)-32, SEEK_END);
+          signature = get_le64(vfd); /* be bulletproof: check header also */
+          if (signature == APE_SIGNATURE) {
+#ifdef DEBUG
+              fprintf(stderr, "stripping also header\n");
+#endif
+              file_size -= 32;
+          }
+      }
+#ifdef DEBUG
+      fprintf(stderr, "stripping existing tag\n");
+#endif
+      if(aud_vfs_truncate(vfd, file_size) < 0) return FALSE;
+  }
+  aud_vfs_fseek(vfd, 0, SEEK_END);
+
+  iterator_pvt_t state;
+  memset(&state, 0, sizeof(iterator_pvt_t));
+  
+  state.tag_size = 32; /* footer size */
+  mowgli_dictionary_foreach(tag, foreach_cb, &state); /* let's count tag size */
+  tag_size = state.tag_size;
+  tag_items = state.tag_items;
+
+  if(tag_items == 0) {
+#ifdef DEBUG
+      fprintf(stderr, "tag stripped, all done\n");
+#endif
+      return TRUE; /* old tag is stripped, new one is empty */
+  }
+
+  write_header_or_footer(2000, tag_size, tag_items, FLAGS_HEADER | FLAGS_HEADER_EXISTS, vfd); /* header */
+  state.vfd = vfd;
+  mowgli_dictionary_foreach(tag, foreach_cb, &state);
+  write_header_or_footer(2000, tag_size, tag_items, FLAGS_HEADER_EXISTS, vfd); /* footer */
+
+  return TRUE;
+}
+
--- a/src/demac/apev2.h	Thu Nov 29 04:17:51 2007 +0300
+++ b/src/demac/apev2.h	Fri Nov 30 03:56:33 2007 +0300
@@ -5,5 +5,6 @@
 #include <audacious/vfs.h>
 
 mowgli_dictionary_t* parse_apev2_tag(VFSFile *vfd);
+gboolean write_apev2_tag(VFSFile *vfd, mowgli_dictionary_t *tag);
 
 #endif
--- a/src/demac/plugin.c	Thu Nov 29 04:17:51 2007 +0300
+++ b/src/demac/plugin.c	Fri Nov 30 03:56:33 2007 +0300
@@ -389,17 +389,43 @@
     }
 }
 
+static void insert_str_tuple_field_to_dictionary(Tuple *tuple, int fieldn, mowgli_dictionary_t *dict, char *key) {
+    
+    if(mowgli_dictionary_find(dict, key) != NULL) g_free(mowgli_dictionary_delete(dict, key));
+    
+    gchar *tmp = (gchar*)aud_tuple_get_string(tuple, fieldn, NULL);
+    if(tmp != NULL && strlen(tmp) != 0) mowgli_dictionary_add(dict, key, g_strdup(tmp));
+}
+
+static void insert_int_tuple_field_to_dictionary(Tuple *tuple, int fieldn, mowgli_dictionary_t *dict, char *key) {
+    int val;
+
+    if(mowgli_dictionary_find(dict, key) != NULL) g_free(mowgli_dictionary_delete(dict, key));
+    
+    if(aud_tuple_get_value_type(tuple, fieldn, NULL) == TUPLE_INT && (val = aud_tuple_get_int(tuple, fieldn, NULL)) >= 0) {
+        gchar *tmp = g_strdup_printf("%d", val);
+        mowgli_dictionary_add(dict, key, tmp);
+    }
+}
+
 static gboolean demac_update_song_tuple(Tuple *tuple, VFSFile *vfd) {
-    fprintf(stderr, "demac_update_song_tuple(): stub\n");
-    fprintf(stderr, "Title: %s\n", aud_tuple_get_string(tuple, FIELD_TITLE, NULL));
-    fprintf(stderr, "Artist: %s\n", aud_tuple_get_string(tuple, FIELD_ARTIST, NULL));
-    fprintf(stderr, "Album: %s\n", aud_tuple_get_string(tuple, FIELD_ALBUM, NULL));
-    fprintf(stderr, "Comment: %s\n", aud_tuple_get_string(tuple, FIELD_COMMENT, NULL));
-    fprintf(stderr, "Genre: %s\n", aud_tuple_get_string(tuple, FIELD_GENRE, NULL));
-    fprintf(stderr, "Year: %d\n", aud_tuple_get_int(tuple, FIELD_YEAR, NULL));
-    fprintf(stderr, "Track: %d\n", aud_tuple_get_int(tuple, FIELD_TRACK_NUMBER, NULL));
+
+    mowgli_dictionary_t *tag = parse_apev2_tag(vfd);
+    if (tag == NULL) tag = mowgli_dictionary_create(g_ascii_strcasecmp);
 
-    return TRUE;
+    insert_str_tuple_field_to_dictionary(tuple, FIELD_TITLE, tag, "Title");
+    insert_str_tuple_field_to_dictionary(tuple, FIELD_ARTIST, tag, "Artist");
+    insert_str_tuple_field_to_dictionary(tuple, FIELD_ALBUM, tag, "Album");
+    insert_str_tuple_field_to_dictionary(tuple, FIELD_COMMENT, tag, "Comment");
+    insert_str_tuple_field_to_dictionary(tuple, FIELD_GENRE, tag, "Genre");
+    
+    insert_int_tuple_field_to_dictionary(tuple, FIELD_YEAR, tag, "Year");
+    insert_int_tuple_field_to_dictionary(tuple, FIELD_TRACK_NUMBER, tag, "Track");
+
+    gboolean ret = write_apev2_tag(vfd, tag);
+    mowgli_dictionary_destroy(tag, destroy_cb, NULL);
+
+    return ret;
 }
 
 static gchar *fmts[] = { "ape", NULL };