summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBenjamin Drung <bdrung@ubuntu.com>2010-08-04 01:29:13 +0200
committerBenjamin Drung <bdrung@ubuntu.com>2010-08-04 01:29:13 +0200
commitcfc3a681cfae56b9feb6451654c3f51001156842 (patch)
tree88bbd99e30d4bb600e53d417acf68a01f466333f /src
parent7fc1ac65d6f941ed101328823c9dbceea05ff017 (diff)
Imported Upstream version 2.4~beta2
Diffstat (limited to 'src')
-rw-r--r--src/audacious/Makefile1
-rw-r--r--src/audacious/audconfig.c1
-rw-r--r--src/audacious/chardet.c5
-rw-r--r--src/audacious/credits.c7
-rw-r--r--src/audacious/dbus-service.h6
-rw-r--r--src/audacious/dbus.c35
-rw-r--r--src/audacious/drct.c1
-rw-r--r--src/audacious/equalizer.c7
-rw-r--r--src/audacious/images/menu_randomize_playlist.pngbin767 -> 0 bytes
-rw-r--r--src/audacious/interface.c49
-rw-r--r--src/audacious/interface.h14
-rw-r--r--src/audacious/main.c22
-rw-r--r--src/audacious/main.h1
-rw-r--r--src/audacious/mpris_tracklist.xml4
-rw-r--r--src/audacious/playback.c3
-rw-r--r--src/audacious/playlist-new.c38
-rw-r--r--src/audacious/plugin-registry.c8
-rw-r--r--src/audacious/plugin.h133
-rw-r--r--src/audacious/visualization.c4
-rw-r--r--src/libaudcore/tuple_compiler.c4
-rw-r--r--src/libaudcore/vfs_buffer.c2
-rw-r--r--src/libaudgui/equalizer.c6
-rw-r--r--src/libaudtag/id3/id3v22.c5
-rw-r--r--src/libaudtag/id3/id3v24.c184
24 files changed, 339 insertions, 201 deletions
diff --git a/src/audacious/Makefile b/src/audacious/Makefile
index afe9e16..e4c5a91 100644
--- a/src/audacious/Makefile
+++ b/src/audacious/Makefile
@@ -75,7 +75,6 @@ DATA = images/about-logo.png \
images/menu_playlist.png \
images/menu_plugin.png \
images/menu_queue_toggle.png \
- images/menu_randomize_playlist.png \
images/playback.png \
images/playlist.png \
images/plugins.png \
diff --git a/src/audacious/audconfig.c b/src/audacious/audconfig.c
index 8653e70..a0bc200 100644
--- a/src/audacious/audconfig.c
+++ b/src/audacious/audconfig.c
@@ -292,6 +292,7 @@ static void save_output_path (void)
plugin_get_path (plugin_by_header (current_output_plugin), & path,
& type, & number);
+ g_free (cfg.output_path);
cfg.output_path = (path != NULL) ? g_strdup (path) : NULL;
cfg.output_number = number;
}
diff --git a/src/audacious/chardet.c b/src/audacious/chardet.c
index c9a2c3b..f79bc96 100644
--- a/src/audacious/chardet.c
+++ b/src/audacious/chardet.c
@@ -100,6 +100,9 @@ cd_chardet_to_utf8(const gchar * str, gssize len, gsize * arg_bytes_read,
#ifdef USE_CHARDET
if (libguess_validate_utf8(str, len))
+#else
+ if (g_utf8_validate(str, len, NULL))
+#endif
{
if (len < 0)
len = strlen (str);
@@ -115,7 +118,7 @@ cd_chardet_to_utf8(const gchar * str, gssize len, gsize * arg_bytes_read,
return ret;
}
-
+#ifdef USE_CHARDET
if (cfg.chardet_detector)
det = cfg.chardet_detector;
diff --git a/src/audacious/credits.c b/src/audacious/credits.c
index 4ae19fc..e53a4cb 100644
--- a/src/audacious/credits.c
+++ b/src/audacious/credits.c
@@ -103,6 +103,7 @@ static const gchar *credit_text[] = {
"Juho Heikkinen",
"Joseph Jezak",
"Henrik Johansson",
+ "Mikael Magnusson",
"Rodrigo Martins de Matos Ventura",
"Diego Pettenò",
"Mike Ryan",
@@ -193,6 +194,9 @@ static const gchar *translators_text[] = {
N_("Catalan:"),
"Ernest Adrogué",
NULL,
+ N_("Chinese:"),
+ "Chi Chiu Tsu",
+ NULL,
N_("Croatian:"),
"Marin Glibic",
NULL,
@@ -280,6 +284,9 @@ static const gchar *translators_text[] = {
"Andrej Herceg",
NULL,
N_("Spanish:"),
+ "Cosme Domínguez Díaz",
+ "Jeki Sinneo Leinos",
+ "Francisco Javier F. Serrador",
"Gustavo D. Vranjes",
NULL,
N_("Swedish:"),
diff --git a/src/audacious/dbus-service.h b/src/audacious/dbus-service.h
index 0475e1b..581556a 100644
--- a/src/audacious/dbus-service.h
+++ b/src/audacious/dbus-service.h
@@ -90,9 +90,15 @@ enum {
LAST_SIG
};
+enum {
+ TRACKLIST_CHANGE_SIG,
+ LAST_TRACKLIST_SIG
+};
+
gboolean mpris_emit_track_change(MprisPlayer *obj);
gboolean mpris_emit_status_change(MprisPlayer *obj, PlaybackStatus status);
gboolean mpris_emit_caps_change(MprisPlayer *obj);
+gboolean mpris_emit_tracklist_change(MprisTrackList *obj, gint playlist);
// MPRIS /TrackList
gboolean mpris_tracklist_get_metadata(MprisTrackList *obj, gint pos,
diff --git a/src/audacious/dbus.c b/src/audacious/dbus.c
index 7b4778a..128f62d 100644
--- a/src/audacious/dbus.c
+++ b/src/audacious/dbus.c
@@ -87,6 +87,7 @@ struct MprisMetadataRequest
static DBusGConnection *dbus_conn = NULL;
static guint signals[LAST_SIG] = { 0 };
+static guint tracklist_signals[LAST_TRACKLIST_SIG] = { 0 };
static GThread *main_thread;
static GMutex *info_mutex;
@@ -99,6 +100,8 @@ G_DEFINE_TYPE (MprisTrackList, mpris_tracklist, G_TYPE_OBJECT)
#define DBUS_TYPE_G_STRING_VALUE_HASHTABLE (dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE))
+static void mpris_playlist_update_hook(gpointer unused, MprisTrackList *obj);
+
void audacious_rc_class_init(RemoteObjectClass * klass)
{
}
@@ -119,6 +122,8 @@ void mpris_player_class_init(MprisPlayerClass * klass)
void mpris_tracklist_class_init(MprisTrackListClass * klass)
{
+ tracklist_signals[TRACKLIST_CHANGE_SIG] = g_signal_new("track_list_change", G_OBJECT_CLASS_TYPE(klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT);
}
void audacious_rc_init(RemoteObject * object)
@@ -191,6 +196,20 @@ void mpris_tracklist_init(MprisTrackList * object)
// Register DBUS path
dbus_g_connection_register_g_object(dbus_conn, AUDACIOUS_DBUS_PATH_MPRIS_TRACKLIST, G_OBJECT(object));
+
+ // Add signals
+ DBusGProxy *proxy = object->proxy;
+ if (proxy != NULL)
+ {
+ dbus_g_proxy_add_signal(proxy, "TrackListChange", G_TYPE_INT, G_TYPE_INVALID);
+ }
+ else
+ {
+ /* XXX / FIXME: Why does this happen? -- ccr */
+ g_warning("in mpris_tracklist_init object->proxy == NULL, not adding some signals.");
+ }
+
+ hook_associate("playlist update", (HookFunction) mpris_playlist_update_hook, object);
}
void init_dbus()
@@ -850,6 +869,18 @@ gboolean mpris_emit_status_change(MprisPlayer * obj, PlaybackStatus status)
}
// MPRIS /TrackList
+gboolean mpris_emit_tracklist_change(MprisTrackList * obj, gint playlist)
+{
+ g_signal_emit(obj, tracklist_signals[TRACKLIST_CHANGE_SIG], 0, playlist_entry_count(playlist));
+ return TRUE;
+}
+
+static void mpris_playlist_update_hook(gpointer unused, MprisTrackList * obj)
+{
+ gint playlist = playlist_get_active();
+
+ mpris_emit_tracklist_change(obj, playlist);
+}
gboolean mpris_tracklist_get_metadata(MprisTrackList * obj, gint pos, GHashTable * *metadata, GError * *error)
{
@@ -971,7 +1002,7 @@ gboolean audacious_rc_show_playlist(RemoteObject * obj, gboolean show, GError **
gboolean audacious_rc_get_tuple_fields(RemoteObject * obj, gchar *** fields, GError ** error)
{
- gchar **res = g_new0(gchar *, FIELD_LAST);
+ gchar **res = g_new0(gchar *, FIELD_LAST + 1);
gint i;
for (i = 0; i < FIELD_LAST; i++)
{
@@ -1256,7 +1287,7 @@ gboolean audacious_rc_clear(RemoteObject * obj, GError * *error)
gboolean audacious_rc_auto_advance(RemoteObject * obj, gboolean * is_advance, GError ** error)
{
- *is_advance = cfg.no_playlist_advance;
+ *is_advance = !cfg.no_playlist_advance;
return TRUE;
}
diff --git a/src/audacious/drct.c b/src/audacious/drct.c
index eab2af9..bf54653 100644
--- a/src/audacious/drct.c
+++ b/src/audacious/drct.c
@@ -327,6 +327,7 @@ static void activate_temp (void)
playlist_insert (playlists);
playlist_set_title (playlists, title);
+ playlist_set_active (playlists);
}
void drct_pl_open_temp (const gchar * filename)
diff --git a/src/audacious/equalizer.c b/src/audacious/equalizer.c
index def49dd..af13771 100644
--- a/src/audacious/equalizer.c
+++ b/src/audacious/equalizer.c
@@ -28,8 +28,11 @@
#define Q 1.2247449
/* Center frequencies for band-pass filters (Hz) */
-static const gfloat CF[EQ_BANDS] = {60, 170, 310, 600, 1000, 3000, 6000, 12000,
- 14000, 16000};
+/* These are not the historical WinAmp frequencies, because the IIR filters used
+ * here are designed for each frequency to be twice the previous. Using WinAmp
+ * frequencies leads to too much gain in some bands and too little in others. */
+static const gfloat CF[EQ_BANDS] = {31.25, 62.5, 125, 250, 500, 1000, 2000,
+ 4000, 8000, 16000};
static GStaticMutex mutex = G_STATIC_MUTEX_INIT;
static gboolean active;
diff --git a/src/audacious/images/menu_randomize_playlist.png b/src/audacious/images/menu_randomize_playlist.png
deleted file mode 100644
index d6405c2..0000000
--- a/src/audacious/images/menu_randomize_playlist.png
+++ /dev/null
Binary files differ
diff --git a/src/audacious/interface.c b/src/audacious/interface.c
index 2e51740..30f6cd2 100644
--- a/src/audacious/interface.c
+++ b/src/audacious/interface.c
@@ -19,7 +19,7 @@
* Audacious or using our public API to be a derived work.
*/
-#include <glib.h>
+#include <string.h>
#include <gtk/gtk.h>
#include <libaudcore/hook.h>
@@ -29,6 +29,7 @@
#include "debug.h"
#include "i18n.h"
#include "interface.h"
+#include "plugins.h"
#include "ui_preferences.h"
static Interface *current_interface = NULL;
@@ -185,39 +186,33 @@ interface_show_about_window(gboolean show)
}
}
-/* void interface_run_gtk_plugin (GtkWidget * parent, const gchar * name) */
-void interface_run_gtk_plugin (void * parent, const gchar * name)
+static gboolean delete_cb (GtkWidget * window, GdkEvent * event, PluginHandle *
+ plugin)
{
- if (interface_cbs.run_gtk_plugin != NULL)
- interface_cbs.run_gtk_plugin(parent, name);
- else {
- GtkWidget *win;
-
- g_return_if_fail(parent != NULL);
- g_return_if_fail(name != NULL);
-
- win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- gtk_window_set_title(GTK_WINDOW(win), _(name));
- gtk_container_add(GTK_CONTAINER(win), parent);
- gtk_widget_show_all(win);
+ vis_plugin_enable (plugin, FALSE);
+ return TRUE;
+}
- g_object_set_data(G_OBJECT(parent), "parentwin", win);
+void interface_add_plugin_widget (PluginHandle * plugin, GtkWidget * widget)
+{
+ if (interface_cbs.run_gtk_plugin != NULL)
+ interface_cbs.run_gtk_plugin (widget, plugin_get_name (plugin));
+ else
+ {
+ GtkWidget * window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title ((GtkWindow *) window, plugin_get_name (plugin));
+ gtk_container_add ((GtkContainer *) window, widget);
+ g_signal_connect (window, "delete-event", (GCallback) delete_cb, plugin);
+ gtk_widget_show_all (window);
}
}
-/* void interface_stop_gtk_plugin (GtkWidget * parent) */
-void interface_stop_gtk_plugin (void * parent)
+void interface_remove_plugin_widget (PluginHandle * plugin, GtkWidget * widget)
{
if (interface_cbs.stop_gtk_plugin != NULL)
- interface_cbs.stop_gtk_plugin(parent);
- else {
- GtkWidget *win;
-
- g_return_if_fail(parent != NULL);
-
- win = g_object_get_data(G_OBJECT(parent), "parentwin");
- gtk_widget_destroy(win);
- }
+ interface_cbs.stop_gtk_plugin (widget);
+ else
+ gtk_widget_destroy (gtk_widget_get_parent (widget));
}
void
diff --git a/src/audacious/interface.h b/src/audacious/interface.h
index 52c4649..e03df24 100644
--- a/src/audacious/interface.h
+++ b/src/audacious/interface.h
@@ -30,9 +30,6 @@
#define __AUDACIOUS2_INTERFACE_H__
#include <glib.h>
-#include <mowgli.h>
-
-#include <audacious/plugins.h>
#include <audacious/types.h>
typedef struct {
@@ -74,6 +71,11 @@ struct _Interface {
InterfaceOps *ops;
};
+#ifdef _AUDACIOUS_CORE
+
+#include <gtk/gtk.h>
+#include <audacious/plugins.h>
+
PluginHandle * interface_get_default (void);
void interface_set_default (PluginHandle * plugin);
gboolean interface_load (PluginHandle * plugin);
@@ -87,9 +89,8 @@ void interface_hide_filebrowser(void);
void interface_toggle_visibility(void);
void interface_show_error_message(const gchar * markup);
void interface_show_jump_to_track(void);
-/* void interface_run_gtk_plugin (GtkWidget * parent, const gchar * name); */
-void interface_run_gtk_plugin (void * parent, const gchar * name);
-/* void interface_stop_gtk_plugin (GtkWidget * parent); */
+void interface_add_plugin_widget (PluginHandle * plugin, GtkWidget * widget);
+void interface_remove_plugin_widget (PluginHandle * plugin, GtkWidget * widget);
void interface_stop_gtk_plugin (void * parent);
void interface_toggle_shuffle(void);
void interface_toggle_repeat(void);
@@ -97,3 +98,4 @@ void interface_toggle_repeat(void);
void register_interface_hooks(void);
#endif
+#endif
diff --git a/src/audacious/main.c b/src/audacious/main.c
index 3fa932c..c32fdcc 100644
--- a/src/audacious/main.c
+++ b/src/audacious/main.c
@@ -88,6 +88,7 @@ gchar * aud_paths[BMP_PATH_COUNT];
#ifdef USE_DBUS
MprisPlayer *mpris;
+MprisTrackList *mpris_tracklist;
#endif
static void print_version(void)
@@ -123,9 +124,13 @@ static void aud_init_paths()
gchar *xdg_data_home;
gchar *xdg_cache_home;
- xdg_config_home = (getenv("XDG_CONFIG_HOME") == NULL ? g_build_filename(g_get_home_dir(), ".config", NULL) : g_strdup(getenv("XDG_CONFIG_HOME")));
- xdg_data_home = (getenv("XDG_DATA_HOME") == NULL ? g_build_filename(g_get_home_dir(), ".local", "share", NULL) : g_strdup(getenv("XDG_DATA_HOME")));
- xdg_cache_home = (getenv("XDG_CACHE_HOME") == NULL ? g_build_filename(g_get_home_dir(), ".cache", NULL) : g_strdup(getenv("XDG_CACHE_HOME")));
+ xdg_config_home = (getenv ("XDG_CONFIG_HOME") == NULL) ? g_build_filename
+ (getenv ("HOME"), ".config", NULL) : g_strdup (getenv ("XDG_CONFIG_HOME"));
+ xdg_data_home = (getenv ("XDG_DATA_HOME") == NULL) ? g_build_filename
+ (getenv ("HOME"), ".local", "share", NULL) : g_strdup (getenv
+ ("XDG_DATA_HOME"));
+ xdg_cache_home = (getenv ("XDG_CACHE_HOME") == NULL) ? g_build_filename
+ (getenv ("HOME"), ".cache", NULL) : g_strdup (getenv ("XDG_CACHE_HOME"));
aud_paths[BMP_PATH_USER_DIR] = g_build_filename(xdg_config_home, "audacious", NULL);
aud_paths[BMP_PATH_USER_SKIN_DIR] = g_build_filename(xdg_data_home, "audacious", "Skins", NULL);
@@ -178,11 +183,6 @@ static void parse_cmd_line_options(gint * argc, gchar *** argv)
memset(&options, '\0', sizeof(AudCmdLineOpt));
options.session = -1;
- /* If audacious2 is run with no arguments, bring it to the top. This is
- handy when the user forgets that Audacious is already running. */
- if (*argc == 1)
- options.mainwin = 1;
-
context = g_option_context_new(_("- play multimedia files"));
g_option_context_add_main_entries(context, cmd_entries, PACKAGE_NAME);
g_option_context_add_group(context, gtk_get_option_group(FALSE));
@@ -410,6 +410,9 @@ void iface_plugin_set_active (PluginHandle * plugin)
g_message ("Unloading %s.", plugin_get_name (current_iface));
interface_unload ();
+ current_iface = plugin;
+ interface_set_default (plugin);
+
g_message ("Starting %s.", plugin_get_name (plugin));
if (! interface_load (plugin))
{
@@ -417,9 +420,6 @@ void iface_plugin_set_active (PluginHandle * plugin)
exit (EXIT_FAILURE);
}
- current_iface = plugin;
- interface_set_default (plugin);
-
g_message ("Loading visualizers.");
vis_init ();
}
diff --git a/src/audacious/main.h b/src/audacious/main.h
index 649e876..fccbe24 100644
--- a/src/audacious/main.h
+++ b/src/audacious/main.h
@@ -52,6 +52,7 @@ extern gchar *aud_paths[];
#ifdef USE_DBUS
extern MprisPlayer *mpris;
+extern MprisTrackList *mpris_tracklist;
#endif
void aud_quit(void);
diff --git a/src/audacious/mpris_tracklist.xml b/src/audacious/mpris_tracklist.xml
index 9fe2ec9..826fde2 100644
--- a/src/audacious/mpris_tracklist.xml
+++ b/src/audacious/mpris_tracklist.xml
@@ -43,5 +43,9 @@
<method name="Random">
<arg type="b" direction="in" />
</method>
+
+ <signal name="TrackListChange">
+ <arg type="i" />
+ </signal>
</interface>
</node>
diff --git a/src/audacious/playback.c b/src/audacious/playback.c
index 3b8dd84..ff6b97b 100644
--- a/src/audacious/playback.c
+++ b/src/audacious/playback.c
@@ -533,11 +533,10 @@ void playback_seek (gint time)
if (current_playback->plugin->mseek != NULL)
current_playback->plugin->mseek (current_playback, time);
- else
+ else if (current_playback->plugin->seek != NULL)
{
fprintf (stderr, "%s should be updated to provide mseek().\n",
current_playback->plugin->description);
- g_return_if_fail (current_playback->plugin->seek != NULL);
current_playback->plugin->seek (current_playback, time / 1000);
}
diff --git a/src/audacious/playlist-new.c b/src/audacious/playlist-new.c
index 750f626..fd9c16f 100644
--- a/src/audacious/playlist-new.c
+++ b/src/audacious/playlist-new.c
@@ -235,6 +235,12 @@ static void entry_set_tuple (struct playlist * playlist, struct entry * entry,
}
}
+static void entry_set_failed (struct playlist * playlist, struct entry * entry)
+{
+ entry_set_tuple (playlist, entry, tuple_new_from_filename (entry->filename));
+ entry->failed = TRUE;
+}
+
static struct entry *entry_new(gchar * filename, InputPlugin * decoder, Tuple * tuple)
{
struct entry *entry = g_malloc(sizeof(struct entry));
@@ -266,15 +272,15 @@ static void entry_free(struct entry *entry)
g_free(entry);
}
-static void entry_check_has_decoder (struct entry * entry)
+static void entry_check_has_decoder (struct playlist * playlist, struct entry *
+ entry)
{
if (entry->decoder != NULL || entry->failed)
return;
entry->decoder = file_find_decoder (entry->filename, FALSE);
-
- if (entry->decoder == NULL)
- entry->failed = TRUE;
+ if (! entry->decoder)
+ entry_set_failed (playlist, entry);
}
static struct playlist *playlist_new(void)
@@ -380,10 +386,11 @@ void scan_receive (void)
SCAN_DEBUG ("receive (#%d): %d\n", i, scan_positions[i]);
entry = index_get (active_playlist->entries, scan_positions[i]);
- entry_set_tuple (active_playlist, entry, scan_tuples[i]);
- if (! scan_tuples[i])
- entry->failed = TRUE;
+ if (scan_tuples[i])
+ entry_set_tuple (active_playlist, entry, scan_tuples[i]);
+ else
+ entry_set_failed (active_playlist, entry);
scan_filenames[i] = NULL;
scan_tuples[i] = NULL;
@@ -422,8 +429,7 @@ static gboolean scan_next (void * unused)
if (entry->tuple)
continue;
- entry_check_has_decoder (entry);
-
+ entry_check_has_decoder (active_playlist, entry);
if (entry->failed)
continue;
@@ -544,7 +550,7 @@ static gboolean scan_threaded (struct playlist * playlist, struct entry * entry)
scan_next (NULL);
- if (entry->tuple != NULL || entry->failed)
+ if (entry->tuple)
return TRUE;
for (i = 0; i < SCAN_THREADS; i ++)
@@ -574,17 +580,19 @@ FOUND:
static void check_scanned (struct playlist * playlist, struct entry * entry)
{
- if (entry->tuple != NULL || entry->failed)
+ if (entry->tuple)
return;
if (scan_threaded (playlist, entry))
return;
- entry_check_has_decoder (entry);
+ entry_check_has_decoder (playlist, entry);
+ if (entry->failed)
+ return;
+
entry_set_tuple (playlist, entry, file_read_tuple (entry->filename,
entry->decoder));
-
if (! entry->tuple)
- entry->failed = TRUE;
+ entry_set_failed (playlist, entry);
queue_update (PLAYLIST_UPDATE_METADATA);
}
@@ -1019,7 +1027,7 @@ InputPlugin *playlist_entry_get_decoder(gint playlist_num, gint entry_num)
LOOKUP_PLAYLIST_ENTRY_RET (NULL);
- entry_check_has_decoder (entry);
+ entry_check_has_decoder (playlist, entry);
return entry->decoder;
}
diff --git a/src/audacious/plugin-registry.c b/src/audacious/plugin-registry.c
index 01bb0ff..15ec913 100644
--- a/src/audacious/plugin-registry.c
+++ b/src/audacious/plugin-registry.c
@@ -106,7 +106,7 @@ static PluginHandle * plugin_new (ModuleData * module, gint type, gint number,
plugin->number = number;
plugin->confirmed = confirmed;
plugin->header = header;
- plugin->name = 0;
+ plugin->name = NULL;
plugin->priority = 0;
plugin->has_about = FALSE;
plugin->has_configure = FALSE;
@@ -478,6 +478,7 @@ void plugin_register (const gchar * path, gint type, gint number, void * header)
if (type == PLUGIN_TYPE_INPUT)
{
InputPlugin * ip = header;
+ g_free (plugin->name);
plugin->name = g_strdup (ip->description);
plugin->priority = ip->priority;
plugin->has_about = (ip->about != NULL);
@@ -501,6 +502,7 @@ void plugin_register (const gchar * path, gint type, gint number, void * header)
else if (type == PLUGIN_TYPE_OUTPUT)
{
OutputPlugin * op = header;
+ g_free (plugin->name);
plugin->name = g_strdup (op->description);
plugin->priority = 10 - op->probe_priority;
plugin->has_about = (op->about != NULL);
@@ -509,6 +511,7 @@ void plugin_register (const gchar * path, gint type, gint number, void * header)
else if (type == PLUGIN_TYPE_EFFECT)
{
EffectPlugin * ep = header;
+ g_free (plugin->name);
plugin->name = g_strdup (ep->description);
plugin->priority = ep->order;
plugin->has_about = (ep->about != NULL);
@@ -517,6 +520,7 @@ void plugin_register (const gchar * path, gint type, gint number, void * header)
else if (type == PLUGIN_TYPE_VIS)
{
VisPlugin * vp = header;
+ g_free (plugin->name);
plugin->name = g_strdup (vp->description);
plugin->has_about = (vp->about != NULL);
plugin->has_configure = (vp->configure != NULL);
@@ -524,11 +528,13 @@ void plugin_register (const gchar * path, gint type, gint number, void * header)
else if (type == PLUGIN_TYPE_IFACE)
{
Interface * i = header;
+ g_free (plugin->name);
plugin->name = g_strdup (i->desc);
}
else if (type == PLUGIN_TYPE_GENERAL)
{
GeneralPlugin * gp = header;
+ g_free (plugin->name);
plugin->name = g_strdup (gp->description);
plugin->has_about = (gp->about != NULL);
plugin->has_configure = (gp->configure != NULL);
diff --git a/src/audacious/plugin.h b/src/audacious/plugin.h
index 9f52676..c8b4534 100644
--- a/src/audacious/plugin.h
+++ b/src/audacious/plugin.h
@@ -334,51 +334,114 @@ struct _InputPlayback {
struct _InputPlugin {
PLUGIN_COMMON_FIELDS
- gboolean have_subtune; /**< Plugin supports/uses subtunes. */
- gchar **vfs_extensions; /**< Filename extension to be associated to this plugin. */
- gint priority; /* 0 = first, 10 = last */
-
- gint (*is_our_file_from_vfs) (const gchar *filename, VFSFile *fd);
- Tuple *(*get_song_tuple) (const gchar * filename);
- Tuple *(*probe_for_tuple) (const gchar *uri, VFSFile *fd);
-
- /**
- * Plugin can provide this function for file metadata (aka tag)
- * writing functionality when there is no reason to provide its
- * own custom file info dialog.
- *
- * - In current Audacious version, if plugin provides file_info_box(), the latter will be used in any case.
- * - Each field in tuple means operation on one and only one tag field:
- * - Set this field to appropriate value, if non-empty string or positive number provided.
- * - Set this field to blank (or just delete, at plugins`s discretion), if empty string or negative number provided.
+ /* Nonzero if the files handled by the plugin may contain more than one
+ * song. When reading the tuple for such a file, the plugin should set the
+ * FIELD_SUBSONG_NUM field to the number of songs in the file. For all
+ * other files, the field should be left unset.
*
- * @param[in] tuple Tuple with the desired metadata.
- * @param[in] fd VFS file descriptor pointing to file to modify.
+ * Example:
+ * 1. User adds a file named "somefile.xxx" to the playlist. Having
+ * determined that this plugin can handle the file, Audacious opens the file
+ * and calls probe_for_tuple(). probe_for_tuple() sees that there are 3
+ * songs in the file and sets FIELD_SUBSONG_NUM to 3.
+ * 2. For each song in the file, Audacious opens the file and calls
+ * probe_for_tuple() -- this time, however, a question mark and song number
+ * are appended to the file name passed: "somefile.sid?2" refers to the
+ * second song in the file "somefile.sid".
+ * 3. When one of the songs is played, Audacious opens the file and calls
+ * play() with a file name modified in this way.
*/
+ gboolean have_subtune;
+
+ /* Pointer to an array (terminated with NULL) of file extensions associated
+ * with file types the plugin can handle. */
+ const gchar * const * vfs_extensions;
+
+ /* How quickly the plugin should be tried in searching for a plugin to
+ * handle a file which could not be identified from its extension. Plugins
+ * with priority 0 are tried first, 10 last. */
+ gint priority;
+
+ /* Must return nonzero if the plugin can handle this file. If the file
+ * could not be opened, "file" will be NULL. (This is normal in the case of
+ * special URI schemes like cdda:// that do not represent actual files.) */
+ /* Bug: The return value should be a gboolean, not a gint. */
+ gint (* is_our_file_from_vfs) (const gchar * filename, VFSFile * file);
+
+ /* Deprecated. */
+ Tuple * (* get_song_tuple) (const gchar * filename); /* Use probe_for_tuple. */
+
+ /* Must return a tuple containing metadata for this file, or NULL if no
+ * metadata could be read. If the file could not be opened, "file" will be
+ * NULL. Audacious takes over one reference to the tuple returned. */
+ Tuple * (* probe_for_tuple) (const gchar * filename, VFSFile * file);
+
+ /* Optional. Must write metadata from a tuple to this file. Must return
+ * nonzero on success or zero on failure. "file" will never be NULL. */
+ /* Bug: This function does not support special URI schemes like cdda://,
+ * since no file name is passed. */
gboolean (* update_song_tuple) (const Tuple * tuple, VFSFile * file);
- void (*file_info_box) (const gchar * filename);
- /* Warning: Check for file == NULL. */
+ /* Optional, and not recommended. Must show a window with information about
+ * this file. If this function is provided, update_song_tuple should not be. */
+ /* Bug: Implementing this function duplicates user interface code and code
+ * to open the file in each and every plugin. */
+ void (* file_info_box) (const gchar * filename);
+
+ /* Optional. Must try to read an "album art" image embedded in this file.
+ * Must return nonzero on success or zero on failure. If the file could not
+ * be opened, "file" will be NULL. On success, must fill "data" with a
+ * pointer to a block of data allocated with g_malloc and "size" with the
+ * size in bytes of that block. The data may be in any format supported by
+ * GTK. Audacious will free the data when it is no longer needed. */
gboolean (* get_song_image) (const gchar * filename, VFSFile * file,
void * * data, gint * size);
- /* Warning: Check for file == NULL. */
+ /* Must try to play this file. "playback" is a structure containing output-
+ * related functions which the plugin may make use of. It also contains a
+ * "data" pointer which the plugin may use to refer private data associated
+ * with the playback state. This pointer can then be used from pause,
+ * mseek, and stop. If the file could not be opened, "file" will be NULL.
+ * "start_time" is the position in milliseconds at which to start from, or
+ * -1 to start from the beginning of the file. "stop_time" is the position
+ * in milliseconds at which to end playback, or -1 to play to the end of the
+ * file. "paused" specifies whether playback should immediately be paused.
+ * Must return nonzero if some of the file was successfully played or zero
+ * on failure. */
gboolean (* play) (InputPlayback * playback, const gchar * filename,
VFSFile * file, gint start_time, gint stop_time, gboolean pause);
- void (*pause) (InputPlayback * playback, gshort paused);
- void (*mseek) (InputPlayback * playback, gulong millisecond);
- void (*stop) (InputPlayback * playback);
-
- /* advanced: for plugins that do not use Audacious's output system */
- gint (*get_time) (InputPlayback * playback);
- gint (*get_volume) (gint * l, gint * r);
- gint (*set_volume) (gint l, gint r);
-
- /* deprecated */
- gint (*is_our_file) (const gchar * filename);
- void (*play_file) (InputPlayback * playback);
- void (*seek) (InputPlayback * playback, gint time);
+ /* Must pause or unpause a file currently being played. This function will
+ * be called from a different thread than play, but it will not be called
+ * before the plugin calls set_pb_ready or after stop is called. */
+ /* Bug: paused should be a gboolean, not a gshort. */
+ /* Bug: There is no way to indicate success or failure. */
+ void (* pause) (InputPlayback * playback, gshort paused);
+
+ /* Optional. Must seek to the given position in milliseconds within a file
+ * currently being played. This function will be called from a different
+ * thread than play, but it will not be called before the plugin calls
+ * set_pb_ready or after stop is called. */
+ /* Bug: time should be a gint, not a gulong. */
+ /* Bug: There is no way to indicate success or failure. */
+ void (* mseek) (InputPlayback * playback, gulong time);
+
+ /* Must signal a currently playing song to stop and cause play to return.
+ * This function will be called from a different thread than play. It will
+ * only be called once. It should not join the thread from which play is
+ * called. */
+ void (* stop) (InputPlayback * playback);
+
+ /* Advanced, for plugins that do not use Audacious's output system. Use at
+ * your own risk. */
+ gint (* get_time) (InputPlayback * playback);
+ gint (* get_volume) (gint * l, gint * r);
+ gint (* set_volume) (gint l, gint r);
+
+ /* Deprecated. */
+ gint (* is_our_file) (const gchar * filename); /* Use is_our_file_from_vfs. */
+ void (* play_file) (InputPlayback * playback); /* Use play. */
+ void (* seek) (InputPlayback * playback, gint time); /* Use mseek. */
};
struct _GeneralPlugin {
diff --git a/src/audacious/visualization.c b/src/audacious/visualization.c
index 39a1293..30a821b 100644
--- a/src/audacious/visualization.c
+++ b/src/audacious/visualization.c
@@ -228,7 +228,7 @@ static void vis_load (PluginHandle * plugin)
AUDDBG ("Adding %s to interface.\n", plugin_get_name (plugin));
g_signal_connect (vis->widget, "destroy", (GCallback)
gtk_widget_destroyed, & vis->widget);
- interface_run_gtk_plugin (vis->widget, plugin_get_name (plugin));
+ interface_add_plugin_widget (plugin, vis->widget);
}
if (playback_get_playing ())
@@ -257,7 +257,7 @@ static void vis_unload (PluginHandle * plugin)
if (vis->widget != NULL)
{
AUDDBG ("Removing %s from interface.\n", plugin_get_name (plugin));
- interface_stop_gtk_plugin (vis->widget);
+ interface_remove_plugin_widget (plugin, vis->widget);
g_return_if_fail (vis->widget == NULL); /* not destroyed? */
}
diff --git a/src/libaudcore/tuple_compiler.c b/src/libaudcore/tuple_compiler.c
index 9eac1b2..18f84a5 100644
--- a/src/libaudcore/tuple_compiler.c
+++ b/src/libaudcore/tuple_compiler.c
@@ -118,9 +118,7 @@ void tuple_evalctx_free(TupleEvalContext *ctx)
tuple_evalctx_free_function(ctx->functions[i]);
g_free(ctx->functions);
-
- /* Zero context */
- memset(ctx, 0, sizeof(TupleEvalContext));
+ g_free(ctx);
}
diff --git a/src/libaudcore/vfs_buffer.c b/src/libaudcore/vfs_buffer.c
index a977fc7..e023e67 100644
--- a/src/libaudcore/vfs_buffer.c
+++ b/src/libaudcore/vfs_buffer.c
@@ -190,7 +190,7 @@ buffer_vfs_fsize_impl(VFSFile * file)
handle = (VFSBuffer *) file->handle;
- return (off_t)handle->end;
+ return (off_t) (handle->end - handle->data);
}
static VFSConstructor buffer_const = {
diff --git a/src/libaudgui/equalizer.c b/src/libaudgui/equalizer.c
index 795a105..6b642df 100644
--- a/src/libaudgui/equalizer.c
+++ b/src/libaudgui/equalizer.c
@@ -137,9 +137,9 @@ static GtkWidget * create_slider (const gchar * name, gfloat * setting)
static GtkWidget * create_window (void)
{
- static const gchar * names[AUD_EQUALIZER_NBANDS] = {N_("60 Hz"),
- N_("170 Hz"), N_("310 Hz"), N_("600 Hz"), N_("1 kHz"), N_("3 kHz"),
- N_("6 kHz"), N_("12 kHz"), N_("14 kHz"), N_("16 kHz")};
+ const gchar * const names[AUD_EQUALIZER_NBANDS] = {N_("31 Hz"), N_("63 Hz"),
+ N_("125 Hz"), N_("250 Hz"), N_("500 Hz"), N_("1 kHz"), N_("2 kHz"),
+ N_("4 kHz"), N_("8 kHz"), N_("16 kHz")};
GtkWidget * window, * vbox, * hbox;
gint i;
diff --git a/src/libaudtag/id3/id3v22.c b/src/libaudtag/id3/id3v22.c
index 69b09ab..ceec8e2 100644
--- a/src/libaudtag/id3/id3v22.c
+++ b/src/libaudtag/id3/id3v22.c
@@ -21,7 +21,6 @@
* using our public API to be a derived work.
*/
-#define DEBUG
#include <glib.h>
#include <libaudcore/audstrings.h>
@@ -96,7 +95,7 @@ static gboolean validate_header (ID3v2Header * header)
if ((header->version != 2))
return FALSE;
- header->size = GUINT32_FROM_BE (header->size);
+ header->size = unsyncsafe32(GUINT32_FROM_BE(header->size));
AUDDBG ("Found ID3v2 header:\n");
AUDDBG (" magic = %.3s\n", header->magic);
@@ -545,7 +544,7 @@ static gboolean parse_pic (const guchar * data, gint size, gchar * * mime,
static gboolean id3v22_read_image (VFSFile * handle, void * * image_data, gint *
image_size)
{
- gint version, header_size, data_size, parsed, i;
+ gint version, header_size, data_size, parsed;
gboolean syncsafe;
gsize offset;
gboolean found = FALSE;
diff --git a/src/libaudtag/id3/id3v24.c b/src/libaudtag/id3/id3v24.c
index 457559a..5d59a69 100644
--- a/src/libaudtag/id3/id3v24.c
+++ b/src/libaudtag/id3/id3v24.c
@@ -109,13 +109,6 @@ GenericFrame;
#define ID3_FRAME_SYNCSAFE 0x0002
#define ID3_FRAME_HAS_LENGTH 0x0001
-#define TAG_SIZE 1
-
-static mowgli_dictionary_t * frames = NULL;
-static mowgli_list_t * frameIDs = NULL;
-
-#define write_syncsafe_int32(x) vfs_fput_be32 (syncsafe32 (x))
-
static gboolean skip_extended_header_3 (VFSFile * handle, gint * _size)
{
guint32 size;
@@ -378,7 +371,7 @@ static void free_generic_frame (GenericFrame * frame)
}
static void read_all_frames (VFSFile * handle, gint version, gboolean syncsafe,
- gint data_size)
+ gint data_size, mowgli_dictionary_t * dict)
{
gint pos;
@@ -393,21 +386,29 @@ static void read_all_frames (VFSFile * handle, gint version, gboolean syncsafe,
& frame_size, key, & data, & size))
break;
+ pos += frame_size;
+
+ if (mowgli_dictionary_retrieve (dict, key) != NULL)
+ {
+ AUDDBG ("Discarding duplicate frame %s.\n", key);
+ g_free (data);
+ continue;
+ }
+
frame = g_malloc (sizeof (GenericFrame));
strcpy (frame->key, key);
frame->data = data;
frame->size = size;
- mowgli_dictionary_add (frames, frame->key, frame);
- mowgli_node_add (frame->key, mowgli_node_create (), frameIDs);
-
- pos += frame_size;
+ mowgli_dictionary_add (dict, key, frame);
}
}
static gboolean write_frame (VFSFile * handle, GenericFrame * frame, gint *
frame_size)
{
+ AUDDBG ("Writing frame %s, size %d\n", frame->key, frame->size);
+
ID3v2FrameHeader header;
memcpy (header.key, frame->key, 4);
@@ -426,24 +427,28 @@ static gboolean write_frame (VFSFile * handle, GenericFrame * frame, gint *
return TRUE;
}
-static guint32 writeAllFramesToFile (VFSFile * fd)
+typedef struct {
+ VFSFile * file;
+ gint written_size;
+} WriteState;
+
+static gint write_frame_cb (mowgli_dictionary_elem_t * elem, void * user)
{
- guint32 size = 0;
- mowgli_node_t *n, *tn;
- MOWGLI_LIST_FOREACH_SAFE(n, tn, frameIDs->head)
- {
- GenericFrame *frame = (GenericFrame *) mowgli_dictionary_retrieve(frames, (gchar *) (n->data));
- if (frame)
- {
- gint frame_size;
+ WriteState * state = user;
+ gint size;
+ if (! write_frame (state->file, elem->data, & size))
+ return -1;
+ state->written_size += size;
+ return 0;
+}
- if (! write_frame (fd, frame, & frame_size))
- break;
+static gint writeAllFramesToFile (VFSFile * fd, mowgli_dictionary_t * dict)
+{
+ WriteState state = {fd, 0};
+ mowgli_dictionary_foreach (dict, write_frame_cb, & state);
- size += frame_size;
- }
- }
- return size;
+ AUDDBG ("Total frame bytes written = %d.\n", state.written_size);
+ return state.written_size;
}
static gboolean write_header (VFSFile * handle, gint size, gboolean is_footer)
@@ -722,16 +727,16 @@ static void decode_genre (Tuple * tuple, const guchar * data, gint size)
return;
}
-static GenericFrame * add_generic_frame (gint id, gint size)
+static GenericFrame * add_generic_frame (gint id, gint size,
+ mowgli_dictionary_t * dict)
{
- GenericFrame * frame = mowgli_dictionary_retrieve (frames, id3_frames[id]);
+ GenericFrame * frame = mowgli_dictionary_retrieve (dict, id3_frames[id]);
if (frame == NULL)
{
frame = g_malloc (sizeof (GenericFrame));
strcpy (frame->key, id3_frames[id]);
- mowgli_dictionary_add (frames, frame->key, frame);
- mowgli_node_add (frame->key, mowgli_node_create (), frameIDs);
+ mowgli_dictionary_add (dict, frame->key, frame);
}
else
g_free (frame->data);
@@ -741,36 +746,69 @@ static GenericFrame * add_generic_frame (gint id, gint size)
return frame;
}
-static void add_text_frame (gint id, const gchar * text)
+static void remove_frame (gint id, mowgli_dictionary_t * dict)
{
+ GenericFrame * frame = mowgli_dictionary_retrieve (dict, id3_frames[id]);
+ if (frame == NULL)
+ return;
+
+ AUDDBG ("Deleting frame %s.\n", id3_frames[id]);
+ mowgli_dictionary_delete (dict, id3_frames[id]);
+ free_generic_frame (frame);
+}
+
+static void add_text_frame (gint id, const gchar * text, mowgli_dictionary_t *
+ dict)
+{
+ if (text == NULL)
+ {
+ remove_frame (id, dict);
+ return;
+ }
+
+ AUDDBG ("Adding text frame %s = %s.\n", id3_frames[id], text);
gint length = strlen (text);
- GenericFrame * frame = add_generic_frame (id, length + 1);
+ GenericFrame * frame = add_generic_frame (id, length + 1, dict);
frame->data[0] = 3; /* UTF-8 encoding */
memcpy (frame->data + 1, text, length);
}
-static void add_comment_frame (const gchar * text)
+static void add_comment_frame (const gchar * text, mowgli_dictionary_t * dict)
{
+ if (text == NULL)
+ {
+ remove_frame (ID3_COMMENT, dict);
+ return;
+ }
+
+ AUDDBG ("Adding comment frame = %s.\n", text);
gint length = strlen (text);
- GenericFrame * frame = add_generic_frame (ID3_COMMENT, length + 5);
+ GenericFrame * frame = add_generic_frame (ID3_COMMENT, length + 5, dict);
frame->data[0] = 3; /* UTF-8 encoding */
strcpy ((gchar *) frame->data + 1, "eng"); /* well, it *might* be English */
memcpy (frame->data + 5, text, length);
}
-static void add_frameFromTupleStr (const Tuple * tuple, int field, int id3_field)
+static void add_frameFromTupleStr (const Tuple * tuple, gint field, gint
+ id3_field, mowgli_dictionary_t * dict)
{
- add_text_frame (id3_field, tuple_get_string (tuple, field, NULL));
+ add_text_frame (id3_field, tuple_get_string (tuple, field, NULL), dict);
}
-static void add_frameFromTupleInt (const Tuple * tuple, int field, int id3_field)
+static void add_frameFromTupleInt (const Tuple * tuple, gint field, gint
+ id3_field, mowgli_dictionary_t * dict)
{
- gchar scratch[16];
+ if (tuple_get_value_type (tuple, field, NULL) != TUPLE_INT)
+ {
+ remove_frame (id3_field, dict);
+ return;
+ }
+ gchar scratch[16];
snprintf (scratch, sizeof scratch, "%d", tuple_get_int (tuple, field, NULL));
- add_text_frame (id3_field, scratch);
+ add_text_frame (id3_field, scratch, dict);
}
static gboolean id3v24_can_handle_file (VFSFile * handle)
@@ -944,12 +982,6 @@ static void free_frame_cb (mowgli_dictionary_elem_t * element, void * unused)
free_generic_frame (element->data);
}
-static void free_frame_dictionary (void)
-{
- mowgli_dictionary_destroy (frames, free_frame_cb, NULL);
- frames = NULL;
-}
-
static gboolean id3v24_write_tag (const Tuple * tuple, VFSFile * f)
{
gint version, header_size, data_size, footer_size;
@@ -960,70 +992,50 @@ static gboolean id3v24_write_tag (const Tuple * tuple, VFSFile * f)
& data_size, & footer_size))
return FALSE;
- if (frameIDs != NULL)
- {
- mowgli_node_t *n, *tn;
- MOWGLI_LIST_FOREACH_SAFE(n, tn, frameIDs->head)
- {
- mowgli_node_delete(n, frameIDs);
- }
- }
- frameIDs = mowgli_list_create();
-
//read all frames into generic frames;
- frames = mowgli_dictionary_create(strcasecmp);
- read_all_frames (f, version, syncsafe, data_size);
+ mowgli_dictionary_t * dict = mowgli_dictionary_create (strcasecmp);
+ read_all_frames (f, version, syncsafe, data_size, dict);
//make the new frames from tuple and replace in the dictionary the old frames with the new ones
- if (tuple_get_string(tuple, FIELD_ARTIST, NULL))
- add_frameFromTupleStr(tuple, FIELD_ARTIST, ID3_ARTIST);
-
- if (tuple_get_string(tuple, FIELD_TITLE, NULL))
- add_frameFromTupleStr(tuple, FIELD_TITLE, ID3_TITLE);
-
- if (tuple_get_string(tuple, FIELD_ALBUM, NULL))
- add_frameFromTupleStr(tuple, FIELD_ALBUM, ID3_ALBUM);
-
- if (tuple_get_string (tuple, FIELD_COMMENT, NULL) != NULL)
- add_comment_frame (tuple_get_string (tuple, FIELD_COMMENT, NULL));
-
- if (tuple_get_string(tuple, FIELD_GENRE, NULL))
- add_frameFromTupleStr(tuple, FIELD_GENRE, ID3_GENRE);
-
- if (tuple_get_int(tuple, FIELD_YEAR, NULL) != 0)
- add_frameFromTupleInt(tuple, FIELD_YEAR, ID3_YEAR);
-
- if (tuple_get_int(tuple, FIELD_TRACK_NUMBER, NULL) != 0)
- add_frameFromTupleInt(tuple, FIELD_TRACK_NUMBER, ID3_TRACKNR);
+ add_frameFromTupleStr (tuple, FIELD_TITLE, ID3_TITLE, dict);
+ add_frameFromTupleStr (tuple, FIELD_ARTIST, ID3_ARTIST, dict);
+ add_frameFromTupleStr (tuple, FIELD_ALBUM, ID3_ALBUM, dict);
+ add_frameFromTupleInt (tuple, FIELD_YEAR, ID3_YEAR, dict);
+ add_frameFromTupleInt (tuple, FIELD_TRACK_NUMBER, ID3_TRACKNR, dict);
+ add_frameFromTupleStr (tuple, FIELD_GENRE, ID3_GENRE, dict);
+ add_comment_frame (tuple_get_string (tuple, FIELD_COMMENT, NULL), dict);
if (! offset)
{
if (! cut_beginning_tag (f, header_size + data_size + footer_size))
- return FALSE;
+ goto ERROR;
}
else
{
if (offset + header_size + data_size + footer_size != vfs_fsize (f))
- return FALSE;
-
+ goto ERROR;
if (vfs_ftruncate (f, offset))
- return FALSE;
+ goto ERROR;
}
offset = vfs_fsize (f);
if (offset < 0 || vfs_fseek (f, offset, SEEK_SET) || ! write_header (f, 0,
FALSE))
- return FALSE;
+ goto ERROR;
- data_size = writeAllFramesToFile (f);
- free_frame_dictionary ();
+ data_size = writeAllFramesToFile (f, dict);
if (! write_header (f, data_size, TRUE) || vfs_fseek (f, offset, SEEK_SET)
|| ! write_header (f, data_size, FALSE))
- return FALSE;
+ goto ERROR;
+ mowgli_dictionary_destroy (dict, free_frame_cb, NULL);
return TRUE;
+
+ERROR:
+ mowgli_dictionary_destroy (dict, free_frame_cb, NULL);
+ return FALSE;
}
tag_module_t id3v24 =