/* XMMS2 - X Music Multiplexer System * Copyright (C) 2003-2007 XMMS2 Team * * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!! * * This library 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 library 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. */ /** * @file * xforms */ #include #include "xmmspriv/xmms_plugin.h" #include "xmmspriv/xmms_xform.h" #include "xmmspriv/xmms_streamtype.h" #include "xmmspriv/xmms_medialib.h" #include "xmms/xmms_ipc.h" #include "xmms/xmms_log.h" #include "xmms/xmms_object.h" struct xmms_xform_object_St { xmms_object_t obj; }; struct xmms_xform_St { xmms_object_t obj; struct xmms_xform_St *prev; const xmms_xform_plugin_t *plugin; xmms_medialib_entry_t entry; void *priv; xmms_stream_type_t *out_type; GList *goal_hints; gboolean eos; gboolean error; char *buffer; gint buffered; gint buffersize; gboolean metadata_collected; gboolean metadata_changed; GHashTable *metadata; GHashTable *privdata; GQueue *hotspots; GList *browse_list; GHashTable *browse_hash; gint browse_index; /** used for line reading */ struct { gchar buf[XMMS_XFORM_MAX_LINE_SIZE]; gchar *bufend; } lr; }; typedef struct xmms_xform_hotspot_St { guint pos; gchar *key; xmms_object_cmd_value_t *obj; } xmms_xform_hotspot_t; #define READ_CHUNK 4096 struct xmms_xform_plugin_St { xmms_plugin_t plugin; xmms_xform_methods_t methods; GList *in_types; }; xmms_xform_t *xmms_xform_find (xmms_xform_t *prev, xmms_medialib_entry_t entry, GList *goal_hints); const char *xmms_xform_shortname (xmms_xform_t *xform); static xmms_xform_t *add_effects (xmms_xform_t *last, xmms_medialib_entry_t entry, GList *goal_formats); static void xmms_xform_destroy (xmms_object_t *object); void xmms_xform_browse_add_entry_property_str (xmms_xform_t *xform, const gchar *key, const gchar *value) { xmms_object_cmd_value_t *val = xmms_object_cmd_value_str_new (value); xmms_xform_browse_add_entry_property (xform, key, val); } void xmms_xform_browse_add_entry_property_int (xmms_xform_t *xform, const gchar *key, gint value) { xmms_object_cmd_value_t *val = xmms_object_cmd_value_int_new (value); xmms_xform_browse_add_entry_property (xform, key, val); } void xmms_xform_browse_add_symlink_args (xmms_xform_t *xform, const gchar *basename, const gchar *url, gint nargs, char **args) { GString *s; gchar *eurl; gchar bname[32]; int i; if (!basename) { snprintf (bname, sizeof (bname), "%d", xform->browse_index++); basename = bname; } xmms_xform_browse_add_entry (xform, basename, 0); eurl = xmms_medialib_url_encode (url); s = g_string_new (eurl); for (i = 0; i < nargs; i++) { g_string_append (s, i == 0 ? "?" : "&"); g_string_append (s, args[i]); } xmms_xform_browse_add_entry_property_str (xform, "realpath", s->str); g_free (eurl); g_string_free (s, TRUE); } void xmms_xform_browse_add_symlink (xmms_xform_t *xform, const gchar *basename, const gchar *url) { xmms_xform_browse_add_symlink_args (xform, basename, url, 0, NULL); } void xmms_xform_browse_add_entry_property (xmms_xform_t *xform, const gchar *key, xmms_object_cmd_value_t *val) { g_return_if_fail (xform); g_return_if_fail (xform->browse_hash); g_return_if_fail (key); g_return_if_fail (val); g_hash_table_insert (xform->browse_hash, g_strdup (key), val); } void xmms_xform_browse_add_entry (xmms_xform_t *xform, const gchar *filename, guint32 flags) { const gchar *url; gchar *efile, *eurl, *t; int l; g_return_if_fail (filename); t = strchr (filename, '/'); g_return_if_fail (!t); /* filenames can't contain '/', can they? */ url = xmms_xform_get_url (xform); g_return_if_fail (url); xform->browse_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, xmms_object_cmd_value_free); eurl = xmms_medialib_url_encode (url); efile = xmms_medialib_url_encode (filename); /* can't use g_build_filename as we need to preserve slashes stuff like file:/// */ l = strlen (url); if (l && url[l - 1] == '/') { t = g_strdup_printf ("%s%s", eurl, efile); } else { t = g_strdup_printf ("%s/%s", eurl, efile); } xmms_xform_browse_add_entry_property_str (xform, "path", t); xmms_xform_browse_add_entry_property_int (xform, "isdir", !!(flags & XMMS_XFORM_BROWSE_FLAG_DIR)); xform->browse_list = g_list_prepend (xform->browse_list, xmms_object_cmd_value_dict_new (xform->browse_hash)); g_free (t); g_free (efile); g_free (eurl); } static gint xmms_browse_list_sortfunc (gconstpointer a, gconstpointer b) { xmms_object_cmd_value_t *val1, *val2, *tmp1, *tmp2; val1 = (xmms_object_cmd_value_t *) a; val2 = (xmms_object_cmd_value_t *) b; g_return_val_if_fail (val1->type == XMMS_OBJECT_CMD_ARG_DICT, 0); g_return_val_if_fail (val2->type == XMMS_OBJECT_CMD_ARG_DICT, 0); tmp1 = g_hash_table_lookup (val1->value.dict, "intsort"); tmp2 = g_hash_table_lookup (val2->value.dict, "intsort"); if (tmp1 && tmp2) { g_return_val_if_fail (tmp1->type == XMMS_OBJECT_CMD_ARG_INT32, 0); g_return_val_if_fail (tmp2->type == XMMS_OBJECT_CMD_ARG_INT32, 0); return tmp1->value.int32 > tmp2->value.int32; } tmp1 = g_hash_table_lookup (val1->value.dict, "path"); tmp2 = g_hash_table_lookup (val2->value.dict, "path"); g_return_val_if_fail (!!tmp1, 0); g_return_val_if_fail (!!tmp2, 0); g_return_val_if_fail (tmp1->type == XMMS_OBJECT_CMD_ARG_STRING, 0); g_return_val_if_fail (tmp2->type == XMMS_OBJECT_CMD_ARG_STRING, 0); return g_utf8_collate (tmp1->value.string, tmp2->value.string); } GList * xmms_xform_browse_method (xmms_xform_t *xform, const gchar *url, xmms_error_t *error) { GList *list = NULL; if (xform->plugin->methods.browse) { if (!xform->plugin->methods.browse (xform, url, error)) { return NULL; } list = xform->browse_list; xform->browse_list = NULL; list = g_list_sort (list, xmms_browse_list_sortfunc); } else { xmms_error_set (error, XMMS_ERROR_GENERIC, "Couldn't handle that URL"); } return list; } GList * xmms_xform_browse (xmms_xform_object_t *obj, const gchar *url, xmms_error_t *error) { GList *list = NULL; gchar *durl; xmms_xform_t *xform = NULL; xmms_xform_t *xform2 = NULL; xform = xmms_xform_new (NULL, NULL, 0, NULL); durl = g_strdup (url); xmms_medialib_decode_url (durl); XMMS_DBG ("url = %s", durl); xmms_xform_outdata_type_add (xform, XMMS_STREAM_TYPE_MIMETYPE, "application/x-url", XMMS_STREAM_TYPE_URL, durl, XMMS_STREAM_TYPE_END); xform2 = xmms_xform_find (xform, 0, NULL); if (xform2) { XMMS_DBG ("found xform %s", xmms_xform_shortname (xform2)); } else { xmms_error_set (error, XMMS_ERROR_GENERIC, "Couldn't handle that URL"); xmms_object_unref (xform); g_free (durl); return NULL; } list = xmms_xform_browse_method (xform2, durl, error); xmms_object_unref (xform); xmms_object_unref (xform2); g_free (durl); return list; } XMMS_CMD_DEFINE (browse, xmms_xform_browse, xmms_xform_object_t *, LIST, STRING, NONE); static void xmms_xform_object_destroy (xmms_object_t *obj) { xmms_ipc_object_unregister (XMMS_IPC_OBJECT_XFORM); } xmms_xform_object_t * xmms_xform_object_init (void) { xmms_xform_object_t *obj; obj = xmms_object_new (xmms_xform_object_t, xmms_xform_object_destroy); xmms_ipc_object_register (XMMS_IPC_OBJECT_XFORM, XMMS_OBJECT (obj)); xmms_object_cmd_add (XMMS_OBJECT (obj), XMMS_IPC_CMD_BROWSE, XMMS_CMD_FUNC (browse)); return obj; } static void xmms_xform_destroy (xmms_object_t *object) { xmms_xform_t *xform = (xmms_xform_t *)object; XMMS_DBG ("Freeing xform '%s'", xmms_xform_shortname (xform)); /* The 'destroy' method is not mandatory */ if (xform->plugin && xform->plugin->methods.destroy && xform->entry) { xform->plugin->methods.destroy (xform); } g_hash_table_destroy (xform->metadata); g_hash_table_destroy (xform->privdata); g_queue_free (xform->hotspots); g_free (xform->buffer); xmms_object_unref (xform->out_type); xmms_object_unref (xform->plugin); if (xform->prev) { xmms_object_unref (xform->prev); } } xmms_xform_t * xmms_xform_new (xmms_xform_plugin_t *plugin, xmms_xform_t *prev, xmms_medialib_entry_t entry, GList *goal_hints) { xmms_xform_t *xform; xform = xmms_object_new (xmms_xform_t, xmms_xform_destroy); xmms_object_ref (plugin); xform->plugin = plugin; xform->entry = entry; xform->goal_hints = goal_hints; xform->lr.bufend = &xform->lr.buf[0]; if (prev) { xmms_object_ref (prev); xform->prev = prev; } xform->metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, xmms_object_cmd_value_free); xform->privdata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, xmms_object_cmd_value_free); xform->hotspots = g_queue_new (); if (plugin && entry) { if (!plugin->methods.init (xform)) { if (prev) { xmms_object_unref (prev); } return NULL; } g_return_val_if_fail (xform->out_type, NULL); } xform->buffer = g_malloc (READ_CHUNK); xform->buffersize = READ_CHUNK; return xform; } xmms_medialib_entry_t xmms_xform_entry_get (xmms_xform_t *xform) { return xform->entry; } gpointer xmms_xform_private_data_get (xmms_xform_t *xform) { return xform->priv; } void xmms_xform_private_data_set (xmms_xform_t *xform, gpointer data) { xform->priv = data; } void xmms_xform_outdata_type_add (xmms_xform_t *xform, ...) { va_list ap; va_start (ap, xform); xform->out_type = xmms_stream_type_parse (ap); va_end (ap); } void xmms_xform_outdata_type_set (xmms_xform_t *xform, xmms_stream_type_t *type) { xmms_object_ref (type); xform->out_type = type; } void xmms_xform_outdata_type_copy (xmms_xform_t *xform) { xmms_object_ref (xform->prev->out_type); xform->out_type = xform->prev->out_type; } const char * xmms_xform_indata_find_str (xmms_xform_t *xform, xmms_stream_type_key_t key) { const gchar *r; r = xmms_stream_type_get_str (xform->prev->out_type, key); if (r) { return r; } else if (xform->prev) { return xmms_xform_indata_find_str (xform->prev, key); } return NULL; } const char * xmms_xform_indata_get_str (xmms_xform_t *xform, xmms_stream_type_key_t key) { return xmms_stream_type_get_str (xform->prev->out_type, key); } gint xmms_xform_indata_get_int (xmms_xform_t *xform, xmms_stream_type_key_t key) { return xmms_stream_type_get_int (xform->prev->out_type, key); } xmms_stream_type_t * xmms_xform_outtype_get (xmms_xform_t *xform) { return xform->out_type; } xmms_stream_type_t * xmms_xform_intype_get (xmms_xform_t *xform) { return xmms_xform_outtype_get (xform->prev); } const char * xmms_xform_outtype_get_str (xmms_xform_t *xform, xmms_stream_type_key_t key) { return xmms_stream_type_get_str (xform->out_type, key); } gint xmms_xform_outtype_get_int (xmms_xform_t *xform, xmms_stream_type_key_t key) { return xmms_stream_type_get_int (xform->out_type, key); } void xmms_xform_metadata_set_int (xmms_xform_t *xform, const char *key, int val) { XMMS_DBG ("Setting '%s' to %d", key, val); g_hash_table_insert (xform->metadata, g_strdup (key), xmms_object_cmd_value_int_new (val)); xform->metadata_changed = TRUE; } void xmms_xform_metadata_set_str (xmms_xform_t *xform, const char *key, const char *val) { const char *old; old = xmms_xform_metadata_get_str (xform, key); if (old && strcmp (old, val) == 0) return; g_hash_table_insert (xform->metadata, g_strdup (key), xmms_object_cmd_value_str_new (val)); xform->metadata_changed = TRUE; } static const xmms_object_cmd_value_t * xmms_xform_metadata_get_val (xmms_xform_t *xform, const char *key) { xmms_object_cmd_value_t *val; for (; xform; xform = xform->prev) { val = g_hash_table_lookup (xform->metadata, key); if (val) return val; } return NULL; } gboolean xmms_xform_metadata_has_val (xmms_xform_t *xform, const gchar *key) { return !!xmms_xform_metadata_get_val (xform, key); } gint32 xmms_xform_metadata_get_int (xmms_xform_t *xform, const char *key) { const xmms_object_cmd_value_t *val; val = xmms_xform_metadata_get_val (xform, key); if (val && val->type == XMMS_OBJECT_CMD_ARG_INT32) { return val->value.int32; } else { return -1; } } const gchar * xmms_xform_metadata_get_str (xmms_xform_t *xform, const char *key) { const xmms_object_cmd_value_t *val; val = xmms_xform_metadata_get_val (xform, key); if (val && val->type == XMMS_OBJECT_CMD_ARG_STRING) { return val->value.string; } else { return NULL; } } typedef struct { xmms_medialib_session_t *session; xmms_medialib_entry_t entry; guint32 source; } metadata_festate_t; static void add_metadatum (gpointer _key, gpointer _value, gpointer user_data) { xmms_object_cmd_value_t *value = (xmms_object_cmd_value_t *)_value; gchar *key = (gchar *)_key; metadata_festate_t *st = (metadata_festate_t *)user_data; if (value->type == XMMS_OBJECT_CMD_ARG_STRING) { xmms_medialib_entry_property_set_str_source (st->session, st->entry, key, value->value.string, st->source); } else if (value->type == XMMS_OBJECT_CMD_ARG_INT32) { xmms_medialib_entry_property_set_int_source (st->session, st->entry, key, value->value.int32, st->source); } else { XMMS_DBG ("Unknown type?!?"); } } static void xmms_xform_metadata_collect_one (xmms_xform_t *xform, metadata_festate_t *info) { gchar *src; XMMS_DBG ("Collecting metadata from %s", xmms_xform_shortname (xform)); src = g_strdup_printf ("plugin/%s", xmms_xform_shortname (xform)); info->source = xmms_medialib_source_to_id (info->session, src); g_hash_table_foreach (xform->metadata, add_metadatum, info); g_free (src); xform->metadata_changed = FALSE; } static void xmms_xform_metadata_collect_r (xmms_xform_t *xform, metadata_festate_t *info, GString *namestr) { if (xform->prev) xmms_xform_metadata_collect_r (xform->prev, info, namestr); if (xform->plugin) { if (namestr->len) g_string_append (namestr, ":"); g_string_append (namestr, xmms_xform_shortname (xform)); } if (xform->metadata_changed) xmms_xform_metadata_collect_one (xform, info); xform->metadata_collected = TRUE; } static void xmms_xform_metadata_collect (xmms_xform_t *start, GString *namestr) { metadata_festate_t info; guint times_played; info.entry = start->entry; info.session = xmms_medialib_begin_write (); times_played = xmms_medialib_entry_property_get_int (info.session, info.entry, XMMS_MEDIALIB_ENTRY_PROPERTY_TIMESPLAYED); xmms_medialib_entry_cleanup (info.session, info.entry); xmms_xform_metadata_collect_r (start, &info, namestr); xmms_medialib_entry_property_set_str (info.session, info.entry, XMMS_MEDIALIB_ENTRY_PROPERTY_CHAIN, namestr->str); xmms_medialib_entry_property_set_int (info.session, info.entry, XMMS_MEDIALIB_ENTRY_PROPERTY_TIMESPLAYED, times_played + 1); xmms_medialib_entry_property_set_int (info.session, info.entry, XMMS_MEDIALIB_ENTRY_PROPERTY_LASTSTARTED, time (NULL)); xmms_medialib_entry_status_set (info.session, info.entry, XMMS_MEDIALIB_ENTRY_STATUS_OK); xmms_medialib_end (info.session); xmms_medialib_entry_send_update (info.entry); } static void xmms_xform_metadata_update (xmms_xform_t *xform) { metadata_festate_t info; info.entry = xform->entry; info.session = xmms_medialib_begin_write (); xmms_xform_metadata_collect_one (xform, &info); xmms_medialib_end (info.session); xmms_medialib_entry_send_update (info.entry); } static void xmms_xform_privdata_set_val (xmms_xform_t *xform, char *key, xmms_object_cmd_value_t *val) { xmms_xform_hotspot_t *hs; hs = g_new0 (xmms_xform_hotspot_t, 1); hs->pos = xform->buffered; hs->key = key; hs->obj = val; g_queue_push_tail (xform->hotspots, hs); } void xmms_xform_privdata_set_none (xmms_xform_t *xform) { xmms_xform_privdata_set_val (xform, NULL, xmms_object_cmd_value_none_new ()); } void xmms_xform_privdata_set_int (xmms_xform_t *xform, const char *key, int val) { xmms_xform_privdata_set_val (xform, g_strdup (key), xmms_object_cmd_value_int_new (val)); } void xmms_xform_privdata_set_str (xmms_xform_t *xform, const gchar *key, const gchar *val) { const char *old; if (xmms_xform_privdata_get_str (xform, key, &old) && strcmp (old, val) == 0) return; xmms_xform_privdata_set_val (xform, g_strdup (key), xmms_object_cmd_value_str_new (val)); } void xmms_xform_privdata_set_bin (xmms_xform_t *xform, const gchar *key, gpointer data, gssize len) { GString *bin; bin = g_string_new_len (data, len); xmms_xform_privdata_set_val (xform, g_strdup (key), xmms_object_cmd_value_bin_new (bin)); } static const xmms_object_cmd_value_t * xmms_xform_privdata_get_val (xmms_xform_t *xform, const gchar *key) { guint i; xmms_xform_hotspot_t *hs; xmms_object_cmd_value_t *val = NULL; /* privdata is always got from the previous xform */ xform = xform->prev; /* check if we have unhandled current (pos 0) hotspots for this key */ for (i=0; (hs = g_queue_peek_nth (xform->hotspots, i)) != NULL; i++) { if (hs->pos != 0) { break; } else if (hs->key && !strcmp (key, hs->key)) { val = hs->obj; } } if (!val) { val = g_hash_table_lookup (xform->privdata, key); } return val; } gboolean xmms_xform_privdata_has_val (xmms_xform_t *xform, const gchar *key) { return !!xmms_xform_privdata_get_val (xform, key); } gboolean xmms_xform_privdata_get_int (xmms_xform_t *xform, const gchar *key, gint32 *val) { const xmms_object_cmd_value_t *obj; obj = xmms_xform_privdata_get_val (xform, key); if (obj && obj->type == XMMS_OBJECT_CMD_ARG_INT32) { *val = obj->value.int32; return TRUE; } return FALSE; } gboolean xmms_xform_privdata_get_str (xmms_xform_t *xform, const gchar *key, const gchar **val) { const xmms_object_cmd_value_t *obj; obj = xmms_xform_privdata_get_val (xform, key); if (obj && obj->type == XMMS_OBJECT_CMD_ARG_STRING) { *val = obj->value.string; return TRUE; } return FALSE; } gboolean xmms_xform_privdata_get_bin (xmms_xform_t *xform, const gchar *key, gpointer *data, gssize *datalen) { const xmms_object_cmd_value_t *obj; obj = xmms_xform_privdata_get_val (xform, key); if (obj && obj->type == XMMS_OBJECT_CMD_ARG_BIN) { GString *bin = obj->value.bin; *data = bin->str; *datalen = bin->len; return TRUE; } return FALSE; } const char * xmms_xform_shortname (xmms_xform_t *xform) { return (xform->plugin) ? xmms_plugin_shortname_get ((xmms_plugin_t *) xform->plugin) : "unknown"; } gint xmms_xform_this_peek (xmms_xform_t *xform, gpointer buf, gint siz, xmms_error_t *err) { while (xform->buffered < siz) { gint res; if (xform->buffered + READ_CHUNK > xform->buffersize) { xform->buffersize *= 2; xform->buffer = g_realloc (xform->buffer, xform->buffersize); } res = xform->plugin->methods.read (xform, &xform->buffer[xform->buffered], READ_CHUNK, err); if (res < -1) { XMMS_DBG ("Read method of %s returned bad value (%d) - BUG IN PLUGIN", xmms_xform_shortname (xform), res); res = -1; } if (res == 0) { xform->eos = TRUE; break; } else if (res == -1) { xform->error = TRUE; return -1; } else { xform->buffered += res; } } /* might have eosed */ siz = MIN (siz, xform->buffered); memcpy (buf, xform->buffer, siz); return siz; } static void xmms_xform_hotspot_callback (gpointer data, gpointer user_data) { xmms_xform_hotspot_t *hs = data; gint *read = user_data; hs->pos -= *read; } static gint xmms_xform_hotspots_update (xmms_xform_t *xform) { xmms_xform_hotspot_t *hs; gint ret = -1; hs = g_queue_peek_head (xform->hotspots); while (hs != NULL && hs->pos == 0) { g_queue_pop_head (xform->hotspots); if (hs->key) { g_hash_table_insert (xform->privdata, hs->key, hs->obj); } hs = g_queue_peek_head (xform->hotspots); } if (hs != NULL) { ret = hs->pos; } return ret; } gint xmms_xform_this_read (xmms_xform_t *xform, gpointer buf, gint siz, xmms_error_t *err) { gint read = 0; gint nexths; if (xform->error) { xmms_error_set (err, XMMS_ERROR_GENERIC, "Read on errored xform"); return -1; } /* update hotspots */ nexths = xmms_xform_hotspots_update (xform); if (nexths >= 0) { siz = MIN (siz, nexths); } if (xform->buffered) { read = MIN (siz, xform->buffered); memcpy (buf, xform->buffer, read); xform->buffered -= read; /* buffer edited, update hotspot positions */ g_queue_foreach (xform->hotspots, &xmms_xform_hotspot_callback, &read); if (xform->buffered) { /* unless we are _peek:ing often this should be fine */ memmove (xform->buffer, &xform->buffer[read], xform->buffered); } } if (xform->eos) { return read; } while (read < siz) { gint res; res = xform->plugin->methods.read (xform, buf + read, siz - read, err); if (xform->metadata_collected && xform->metadata_changed) xmms_xform_metadata_update (xform); if (res < -1) { XMMS_DBG ("Read method of %s returned bad value (%d) - BUG IN PLUGIN", xmms_xform_shortname (xform), res); res = -1; } if (res == 0) { xform->eos = TRUE; break; } else if (res == -1) { xform->error = TRUE; return -1; } else { if (read == 0) xmms_xform_hotspots_update (xform); if (!g_queue_is_empty (xform->hotspots)) { if (xform->buffered + res > xform->buffersize) { xform->buffersize = MAX (xform->buffersize * 2, xform->buffersize + res); xform->buffer = g_realloc (xform->buffer, xform->buffersize); } g_memmove (xform->buffer + xform->buffered, buf + read, res); xform->buffered += res; break; } read += res; } } return read; } gint64 xmms_xform_this_seek (xmms_xform_t *xform, gint64 offset, xmms_xform_seek_mode_t whence, xmms_error_t *err) { gint64 res; if (xform->error) { xmms_error_set (err, XMMS_ERROR_GENERIC, "Seek on errored xform"); return -1; } if (!xform->plugin->methods.seek) { XMMS_DBG ("Seek not implemented in '%s'", xmms_xform_shortname (xform)); xmms_error_set (err, XMMS_ERROR_GENERIC, "Seek not implemented"); return -1; } if (xform->buffered && whence == XMMS_XFORM_SEEK_CUR) { offset -= xform->buffered; } res = xform->plugin->methods.seek (xform, offset, whence, err); if (res != -1) { xmms_xform_hotspot_t *hs; xform->eos = FALSE; xform->buffered = 0; /* flush the hotspot queue on seek */ while ((hs = g_queue_pop_head (xform->hotspots)) != NULL) { g_free (hs->key); xmms_object_cmd_value_free (hs->obj); g_free (hs); } } return res; } gint xmms_xform_peek (xmms_xform_t *xform, gpointer buf, gint siz, xmms_error_t *err) { g_return_val_if_fail (xform->prev, -1); return xmms_xform_this_peek (xform->prev, buf, siz, err); } gchar * xmms_xform_read_line (xmms_xform_t *xform, gchar *line, xmms_error_t *err) { gchar *p; g_return_val_if_fail (xform, NULL); g_return_val_if_fail (line, NULL); p = strchr (xform->lr.buf, '\n'); if (!p) { gint l, r; l = (XMMS_XFORM_MAX_LINE_SIZE - 1) - (xform->lr.bufend - xform->lr.buf); if (l) { r = xmms_xform_read (xform, xform->lr.bufend, l, err); if (r < 0) { return NULL; } xform->lr.bufend += r; } if (xform->lr.bufend <= xform->lr.buf) return NULL; *(xform->lr.bufend) = '\0'; p = strchr (xform->lr.buf, '\n'); if (!p) { p = xform->lr.bufend; } } if (p > xform->lr.buf && *(p-1) == '\r') { *(p-1) = '\0'; } else { *p = '\0'; } strcpy (line, xform->lr.buf); memmove (xform->lr.buf, p + 1, xform->lr.bufend - p); xform->lr.bufend -= (p - xform->lr.buf) + 1; *xform->lr.bufend = '\0'; return line; } gint xmms_xform_read (xmms_xform_t *xform, gpointer buf, gint siz, xmms_error_t *err) { g_return_val_if_fail (xform->prev, -1); return xmms_xform_this_read (xform->prev, buf, siz, err); } gint64 xmms_xform_seek (xmms_xform_t *xform, gint64 offset, xmms_xform_seek_mode_t whence, xmms_error_t *err) { g_return_val_if_fail (xform->prev, -1); return xmms_xform_this_seek (xform->prev, offset, whence, err); } const gchar * xmms_xform_get_url (xmms_xform_t *xform) { const gchar *url = NULL; xmms_xform_t *x; x = xform; while (!url && x) { url = xmms_xform_indata_get_str (x, XMMS_STREAM_TYPE_URL); x = x->prev; } return url; } static void xmms_xform_plugin_destroy (xmms_object_t *obj) { xmms_xform_plugin_t *plugin = (xmms_xform_plugin_t *)obj; xmms_stream_type_t *in_type; GList *n; for (n = plugin->in_types; n; n = g_list_next (n)) { in_type = (xmms_stream_type_t *)n->data; xmms_object_unref (in_type); } xmms_plugin_destroy ((xmms_plugin_t *)obj); } xmms_plugin_t * xmms_xform_plugin_new (void) { xmms_xform_plugin_t *res; res = xmms_object_new (xmms_xform_plugin_t, xmms_xform_plugin_destroy); return (xmms_plugin_t *)res; } void xmms_xform_plugin_methods_set (xmms_xform_plugin_t *plugin, xmms_xform_methods_t *methods) { g_return_if_fail (plugin); g_return_if_fail (plugin->plugin.type == XMMS_PLUGIN_TYPE_XFORM); XMMS_DBG ("Registering xform '%s'", xmms_plugin_shortname_get ((xmms_plugin_t *)plugin)); memcpy (&plugin->methods, methods, sizeof (xmms_xform_methods_t)); } gboolean xmms_xform_plugin_verify (xmms_plugin_t *_plugin) { xmms_xform_plugin_t *plugin = (xmms_xform_plugin_t *)_plugin; g_return_val_if_fail (plugin, FALSE); g_return_val_if_fail (plugin->plugin.type == XMMS_PLUGIN_TYPE_XFORM, FALSE); /* more checks */ return TRUE; } void xmms_xform_plugin_indata_add (xmms_xform_plugin_t *plugin, ...) { xmms_stream_type_t *t; va_list ap; va_start (ap, plugin); t = xmms_stream_type_parse (ap); va_end (ap); plugin->in_types = g_list_prepend (plugin->in_types, t); } static gboolean xmms_xform_plugin_supports (xmms_xform_plugin_t *plugin, xmms_stream_type_t *st) { GList *t; for (t = plugin->in_types; t; t = g_list_next (t)) { if (xmms_stream_type_match (t->data, st)) { return TRUE; } } return FALSE; } typedef struct match_state_St { xmms_xform_plugin_t *match; xmms_stream_type_t *out_type; } match_state_t; static gboolean xmms_xform_match (xmms_plugin_t *_plugin, gpointer user_data) { xmms_xform_plugin_t *plugin = (xmms_xform_plugin_t *)_plugin; match_state_t *state = (match_state_t *)user_data; g_assert (_plugin->type == XMMS_PLUGIN_TYPE_XFORM); g_assert (!state->match); if (!plugin->in_types) { XMMS_DBG ("Skipping plugin '%s'", xmms_plugin_shortname_get (_plugin)); return TRUE; } XMMS_DBG ("Trying plugin '%s'", xmms_plugin_shortname_get (_plugin)); if (xmms_xform_plugin_supports (plugin, state->out_type)) { XMMS_DBG ("Plugin '%s' matched", xmms_plugin_shortname_get (_plugin)); state->match = plugin; return FALSE; } return TRUE; } xmms_xform_t * xmms_xform_find (xmms_xform_t *prev, xmms_medialib_entry_t entry, GList *goal_hints) { match_state_t state; xmms_xform_t *xform = NULL; state.out_type = prev->out_type; state.match = NULL; xmms_plugin_foreach (XMMS_PLUGIN_TYPE_XFORM, xmms_xform_match, &state); if (state.match) { xform = xmms_xform_new (state.match, prev, entry, goal_hints); } else { XMMS_DBG ("Found no matching plugin..."); } return xform; } gboolean xmms_xform_iseos (xmms_xform_t *xform) { if (!xform->prev) return TRUE; return xform->prev->eos; } const xmms_stream_type_t * xmms_xform_get_out_stream_type (xmms_xform_t *xform) { return xform->out_type; } const GList * xmms_xform_goal_hints_get (xmms_xform_t *xform) { return xform->goal_hints; } static gboolean has_goalformat (xmms_xform_t *xform, GList *goal_formats) { GList *n; for (n = goal_formats; n; n = g_list_next (n)) { xmms_stream_type_t *goal_type = n->data; if (xmms_stream_type_match (goal_type, xmms_xform_get_out_stream_type (xform))) { return TRUE; } } XMMS_DBG ("Not in one of %d goal-types", g_list_length (goal_formats)); return FALSE; } static void outdata_type_metadata_collect (xmms_xform_t *xform) { gint val; const char *mime; xmms_stream_type_t *type; type = xform->out_type; mime = xmms_stream_type_get_str (type, XMMS_STREAM_TYPE_MIMETYPE); if (strcmp (mime, "audio/pcm") != 0) { return; } val = xmms_stream_type_get_int (type, XMMS_STREAM_TYPE_FMT_FORMAT); if (val != -1) { xmms_xform_metadata_set_str (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_SAMPLE_FMT, xmms_sample_name_get ((xmms_sample_format_t)val)); } val = xmms_stream_type_get_int (type, XMMS_STREAM_TYPE_FMT_SAMPLERATE); if (val != -1) { xmms_xform_metadata_set_int (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_SAMPLERATE, val); } val = xmms_stream_type_get_int (type, XMMS_STREAM_TYPE_FMT_CHANNELS); if (val != -1) { xmms_xform_metadata_set_int (xform, XMMS_MEDIALIB_ENTRY_PROPERTY_CHANNELS, val); } } xmms_xform_t * chain_setup (xmms_medialib_entry_t entry, const gchar *url, GList *goal_formats) { xmms_xform_t *xform, *last; gchar *durl, *args; if (!entry) { entry = 1; /* FIXME: this is soooo ugly, don't do this */ } xform = xmms_xform_new (NULL, NULL, 0, goal_formats); durl = g_strdup (url); args = strchr (durl, '?'); if (args) { gchar **params; int i; *args = 0; args++; xmms_medialib_decode_url (args); params = g_strsplit (args, "&", 0); for (i = 0; params && params[i]; i++) { gchar *v; v = strchr (params[i], '='); if (v) { *v = 0; v++; xmms_xform_metadata_set_str (xform, params[i], v); } else { xmms_xform_metadata_set_int (xform, params[i], 1); } } g_strfreev (params); } xmms_medialib_decode_url (durl); xmms_xform_outdata_type_add (xform, XMMS_STREAM_TYPE_MIMETYPE, "application/x-url", XMMS_STREAM_TYPE_URL, durl, XMMS_STREAM_TYPE_END); g_free (durl); last = xform; do { xform = xmms_xform_find (last, entry, goal_formats); if (!xform) { xmms_log_error ("Couldn't set up chain for '%s' (%d)", url, entry); xmms_object_unref (last); return NULL; } xmms_object_unref (last); last = xform; } while (!has_goalformat (xform, goal_formats)); outdata_type_metadata_collect (last); return last; } void chain_finalize (xmms_xform_t *xform, xmms_medialib_entry_t entry, const gchar *url) { GString *namestr; namestr = g_string_new (""); xmms_xform_metadata_collect (xform, namestr); xmms_log_info ("Successfully setup chain for '%s' (%d) containing %s", url, entry, namestr->str); g_string_free (namestr, TRUE); } gchar * get_url_for_entry (xmms_medialib_entry_t entry) { xmms_medialib_session_t *session; gchar *url = NULL; session = xmms_medialib_begin (); url = xmms_medialib_entry_property_get_str (session, entry, XMMS_MEDIALIB_ENTRY_PROPERTY_URL); xmms_medialib_end (session); if (!url) { xmms_log_error ("Couldn't get url for entry (%d)", entry); } return url; } xmms_xform_t * xmms_xform_chain_setup (xmms_medialib_entry_t entry, GList *goal_formats) { gchar *url; xmms_xform_t *last; if (!(url = get_url_for_entry (entry))) { return NULL; } last = chain_setup (entry, url, goal_formats); if (!last) { g_free (url); return NULL; } last = add_effects (last, entry, goal_formats); if (!last) { g_free (url); return NULL; } chain_finalize (last, entry, url); g_free (url); return last; } xmms_xform_t * xmms_xform_chain_setup_without_effects (xmms_medialib_entry_t entry, GList *goal_formats) { gchar *url; xmms_xform_t *xform; if (!(url = get_url_for_entry (entry))) { return NULL; } xform = chain_setup (entry, url, goal_formats); if (!xform) { g_free (url); return NULL; } chain_finalize (xform, entry, url); g_free (url); return xform; } xmms_xform_t * xmms_xform_chain_setup_url (xmms_medialib_entry_t entry, const gchar *url, GList *goal_formats) { xmms_xform_t *last; last = chain_setup (entry, url, goal_formats); if (!last) { return NULL; } last = add_effects (last, entry, goal_formats); if (!last) { return NULL; } chain_finalize (last, entry, url); return last; } xmms_xform_t * xmms_xform_chain_setup_url_without_effects (xmms_medialib_entry_t entry, const gchar *url, GList *goal_formats) { xmms_xform_t *last; last = chain_setup (entry, url, goal_formats); if (!last) { return NULL; } chain_finalize (last, entry, url); return last; } xmms_config_property_t * xmms_xform_plugin_config_property_register (xmms_xform_plugin_t *xform_plugin, const gchar *name, const gchar *default_value, xmms_object_handler_t cb, gpointer userdata) { return xmms_plugin_config_property_register ((xmms_plugin_t *) xform_plugin, name, default_value, cb, userdata); } xmms_config_property_t * xmms_xform_config_lookup (xmms_xform_t *xform, const gchar *path) { g_return_val_if_fail (xform->plugin, NULL); return xmms_plugin_config_lookup ((xmms_plugin_t *) xform->plugin, path); } static xmms_xform_t * add_effects (xmms_xform_t *last, xmms_medialib_entry_t entry, GList *goal_formats) { xmms_xform_t *xform; gint effect_no = 0; while (42) { xmms_config_property_t *cfg; xmms_xform_plugin_t *plugin; gchar key[64]; const gchar *name; g_snprintf (key, sizeof (key), "effect.order.%i", effect_no++); cfg = xmms_config_lookup (key); if (!cfg) { /* this is just a ugly hack to have a configvalue to set */ xmms_config_property_register (key, "", NULL, NULL); break; } name = xmms_config_property_get_string (cfg); if (!name[0]) break; plugin = (xmms_xform_plugin_t *)xmms_plugin_find (XMMS_PLUGIN_TYPE_XFORM, name); if (!plugin) { xmms_log_error ("Couldn't find any effect named '%s'", name); continue; } if (!xmms_xform_plugin_supports (plugin, last->out_type)) { xmms_log_info ("Skipping effect '%s' that doesn't support format", xmms_plugin_shortname_get ((xmms_plugin_t *)plugin)); xmms_object_unref (plugin); continue; } xform = xmms_xform_new (plugin, last, entry, goal_formats); if (xform) { xmms_object_unref (last); last = xform; } xmms_xform_plugin_config_property_register (plugin, "enabled", "0", NULL, NULL); xmms_object_unref (plugin); } return last; }