OSDN Git Service

LinGui: change how x264 options are handled
[handbrake-jp/handbrake-jp-git.git] / gtk / src / settings.c
index 31678de..1e78fcb 100644 (file)
@@ -311,12 +311,13 @@ ghb_widget_value(GtkWidget *widget)
                return NULL;
        }
        value = g_malloc(sizeof(setting_value_t));
+
+       type = GTK_WIDGET_TYPE(widget);
        if (GTK_IS_ACTION(widget))
                name = gtk_action_get_name(GTK_ACTION(widget));
        else
                name = gtk_widget_get_name(widget);
        g_debug("ghb_widget_value widget (%s)\n", name);
-       type = GTK_OBJECT_TYPE(widget);
        if (type == GTK_TYPE_ENTRY)
        {
                const gchar *str = gtk_entry_get_text((GtkEntry*)widget);
@@ -336,7 +337,7 @@ ghb_widget_value(GtkWidget *widget)
                        g_debug("\tenable");
                        value->option = g_strdup("enable");
                        value->shortOpt = g_strdup("enable");
-                       value->svalue = g_strdup("enable");
+                       value->svalue = g_strdup("1");
                        value->ivalue = 1;
                        value->dvalue = 1;
                }
@@ -345,7 +346,7 @@ ghb_widget_value(GtkWidget *widget)
                        g_debug("\tdisable");
                        value->option = g_strdup("disable");
                        value->shortOpt = g_strdup("disable");
-                       value->svalue = g_strdup("disable");
+                       value->svalue = g_strdup("0");
                        value->ivalue = 0;
                        value->dvalue = 0;
                }
@@ -359,7 +360,7 @@ ghb_widget_value(GtkWidget *widget)
                        g_debug("\tenable");
                        value->option = g_strdup("enable");
                        value->shortOpt = g_strdup("enable");
-                       value->svalue = g_strdup("enable");
+                       value->svalue = g_strdup("1");
                        value->ivalue = 1;
                        value->dvalue = 1;
                }
@@ -368,7 +369,7 @@ ghb_widget_value(GtkWidget *widget)
                        g_debug("\tdisable");
                        value->option = g_strdup("disable");
                        value->shortOpt = g_strdup("disable");
-                       value->svalue = g_strdup("disable");
+                       value->svalue = g_strdup("0");
                        value->ivalue = 0;
                        value->dvalue = 0;
                }
@@ -382,7 +383,7 @@ ghb_widget_value(GtkWidget *widget)
                        g_debug("\tenable");
                        value->option = g_strdup("enable");
                        value->shortOpt = g_strdup("enable");
-                       value->svalue = g_strdup("enable");
+                       value->svalue = g_strdup("1");
                        value->ivalue = 1;
                        value->dvalue = 1;
                }
@@ -391,7 +392,7 @@ ghb_widget_value(GtkWidget *widget)
                        g_debug("\tdisable");
                        value->option = g_strdup("disable");
                        value->shortOpt = g_strdup("disable");
-                       value->svalue = g_strdup("disable");
+                       value->svalue = g_strdup("0");
                        value->ivalue = 0;
                        value->dvalue = 0;
                }
@@ -405,7 +406,7 @@ ghb_widget_value(GtkWidget *widget)
                        g_debug("\tenable");
                        value->option = g_strdup("enable");
                        value->shortOpt = g_strdup("enable");
-                       value->svalue = g_strdup("enable");
+                       value->svalue = g_strdup("1");
                        value->ivalue = 1;
                        value->dvalue = 1;
                }
@@ -414,7 +415,7 @@ ghb_widget_value(GtkWidget *widget)
                        g_debug("\tdisable");
                        value->option = g_strdup("disable");
                        value->shortOpt = g_strdup("disable");
-                       value->svalue = g_strdup("disable");
+                       value->svalue = g_strdup("0");
                        value->ivalue = 0;
                        value->dvalue = 0;
                }
@@ -477,7 +478,6 @@ ghb_widget_value(GtkWidget *widget)
                value->svalue = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
                value->option = g_strdup(value->svalue);
                value->shortOpt = g_strdup(value->svalue);
-               g_debug("text view (%s)\n", value->svalue);
                value->ivalue = 0;
                value->dvalue = 0;
                value->index = 0;
@@ -627,7 +627,7 @@ update_widget(GtkWidget *widget, const gchar *parm_svalue, gint parm_ivalue)
        GType type;
        gchar *value;
 
-       g_debug("update_widget\n");
+       g_debug("update_widget");
        // make a dup of setting value because the setting hash gets 
        // modified and thus the value pointer can become invalid.
        if (parm_svalue == NULL)
@@ -638,31 +638,31 @@ update_widget(GtkWidget *widget, const gchar *parm_svalue, gint parm_ivalue)
        {
                value = g_strdup(parm_svalue);
        }
-       g_debug("update widget value (%s)\n", value);
+       g_debug("update widget value (%s)", value);
        type = GTK_OBJECT_TYPE(widget);
        if (type == GTK_TYPE_ENTRY)
        {
-               g_debug("entry\n");
+               g_debug("entry");
                gtk_entry_set_text((GtkEntry*)widget, value);
        }
        else if (type == GTK_TYPE_RADIO_BUTTON)
        {
-               g_debug("radio button\n");
+               g_debug("radio button");
                gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), string_is_true(value));
        }
        else if (type == GTK_TYPE_CHECK_BUTTON)
        {
-               g_debug("check button\n");
+               g_debug("check button");
                gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), string_is_true(value));
        }
        else if (type == GTK_TYPE_TOGGLE_ACTION)
        {
-               g_debug("toggle action\n");
+               g_debug("toggle action");
                gtk_toggle_action_set_active(GTK_TOGGLE_ACTION(widget), string_is_true(value));
        }
        else if (type == GTK_TYPE_CHECK_MENU_ITEM)
        {
-               g_debug("check menu item\n");
+               g_debug("check menu item");
                gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(widget), string_is_true(value));
        }
        else if (type == GTK_TYPE_COMBO_BOX)
@@ -673,6 +673,7 @@ update_widget(GtkWidget *widget, const gchar *parm_svalue, gint parm_ivalue)
                gint ivalue;
                gboolean foundit = FALSE;
 
+               g_debug("combo");
                store = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
                if (gtk_tree_model_get_iter_first (store, &iter))
                {
@@ -705,7 +706,7 @@ update_widget(GtkWidget *widget, const gchar *parm_svalue, gint parm_ivalue)
        {
                gdouble val;
                
-               g_debug("spin\n");
+               g_debug("spin (%s)", value);
                val = g_strtod(value, NULL);
                gtk_spin_button_set_value((GtkSpinButton*)widget, val);
        }
@@ -713,18 +714,19 @@ update_widget(GtkWidget *widget, const gchar *parm_svalue, gint parm_ivalue)
        {
                gdouble val;
                
-               g_debug("hscale\n");
+               g_debug("hscale");
                val = g_strtod(value, NULL);
                gtk_range_set_value((GtkRange*)widget, val);
        }
        else if (type == GTK_TYPE_TEXT_VIEW)
        {
+               g_debug("textview (%s)", value);
                GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
                gtk_text_buffer_set_text (buffer, value, -1);
        }
        else
        {
-               g_debug("Attempt to set unknown widget type\n");
+               g_debug("Attempt to set unknown widget type");
        }
        g_free(value);
 }
@@ -753,7 +755,7 @@ ghb_ui_update_int(signal_user_data_t *ud, const gchar *name, gint ivalue)
 {
        GObject *object;
 
-       g_debug("ghb_ui_update ()\n");
+       g_debug("ghb_ui_update_int ()\n");
        object = GHB_OBJECT(ud->builder, name);
        if (object == NULL)
        {
@@ -1542,3 +1544,471 @@ ghb_presets_remove(GHashTable *settings, const gchar *name)
        return FALSE;
 }
 
+enum
+{
+       X264_OPT_DEBLOCK,
+       X264_OPT_INT,
+       X264_OPT_COMBO,
+       X264_OPT_BOOL,
+};
+
+struct x264_opt_map_s
+{
+       gchar **opt_syns;
+       gchar *name;
+       gchar *def_val;
+       gint type;
+       gboolean found;
+};
+
+static gchar *x264_ref_syns[] = {"ref", "frameref", NULL};
+static gchar *x264_mixed_syns[] = {"mixed-refs", NULL};
+static gchar *x264_bframes_syns[] = {"bframes", NULL};
+static gchar *x264_direct_syns[] = {"direct", "direct-pred", NULL};
+static gchar *x264_weightb_syns[] = {"weightb", "weight-b", NULL};
+static gchar *x264_brdo_syns[] = {"brdo", "b-rdo", NULL};
+static gchar *x264_bime_syns[] = {"bime", NULL};
+static gchar *x264_bpyramid_syns[] = {"b-pyramid", NULL};
+static gchar *x264_me_syns[] = {"me", NULL};
+static gchar *x264_merange_syns[] = {"merange", "me-range", NULL};
+static gchar *x264_subme_syns[] = {"subme", "subq", NULL};
+static gchar *x264_analyse_syns[] = {"analyse", "partitions", NULL};
+static gchar *x264_8x8dct_syns[] = {"8x8dct", NULL};
+static gchar *x264_deblock_syns[] = {"deblock", "filter", NULL};
+static gchar *x264_trellis_syns[] = {"trellis", NULL};
+static gchar *x264_pskip_syns[] = {"no-fast-pskip", NULL};
+static gchar *x264_decimate_syns[] = {"no-dct-decimate", NULL};
+static gchar *x264_cabac_syns[] = {"cabac", NULL};
+
+static gint
+find_syn_match(const gchar *opt, gchar **syns)
+{
+       gint ii;
+       for (ii = 0; syns[ii] != NULL; ii++)
+       {
+               if (strcmp(opt, syns[ii]) == 0)
+                       return ii;
+       }
+       return -1;
+}
+
+struct x264_opt_map_s x264_opt_map[] =
+{
+       {x264_ref_syns, "x264_refs", "1", X264_OPT_INT},
+       {x264_mixed_syns, "x264_mixed_refs", "0", X264_OPT_BOOL},
+       {x264_bframes_syns, "x264_bframes", "0", X264_OPT_INT},
+       {x264_direct_syns, "x264_direct", "spatial", X264_OPT_COMBO},
+       {x264_weightb_syns, "x264_weighted_bframes", "0", X264_OPT_BOOL},
+       {x264_brdo_syns, "x264_brdo", "0", X264_OPT_BOOL},
+       {x264_bime_syns, "x264_bime", "0", X264_OPT_BOOL},
+       {x264_bpyramid_syns, "x264_bpyramid", "0", X264_OPT_BOOL},
+       {x264_me_syns, "x264_me", "hex", X264_OPT_COMBO},
+       {x264_merange_syns, "x264_merange", "16", X264_OPT_INT},
+       {x264_subme_syns, "x264_subme", "5", X264_OPT_COMBO},
+       {x264_analyse_syns, "x264_analyse", "some", X264_OPT_COMBO},
+       {x264_8x8dct_syns, "x264_8x8dct", "0", X264_OPT_BOOL},
+       {x264_deblock_syns, "x264_deblock_alpha", "0,0", X264_OPT_DEBLOCK},
+       {x264_deblock_syns, "x264_deblock_beta", "0,0", X264_OPT_DEBLOCK},
+       {x264_trellis_syns, "x264_trellis", "0", X264_OPT_COMBO},
+       {x264_pskip_syns, "x264_no_fast_pskip", "0", X264_OPT_BOOL},
+       {x264_decimate_syns, "x264_no_dct_decimate", "0", X264_OPT_BOOL},
+       {x264_cabac_syns, "x264_cabac", "1", X264_OPT_BOOL},
+};
+#define X264_OPT_MAP_SIZE (sizeof(x264_opt_map)/sizeof(struct x264_opt_map_s))
+
+static const gchar*
+x264_opt_get_default(const gchar *opt)
+{
+       gint jj;
+       for (jj = 0; jj < X264_OPT_MAP_SIZE; jj++)
+       {
+               if (find_syn_match(opt, x264_opt_map[jj].opt_syns) >= 0)
+               {
+                       return x264_opt_map[jj].def_val;
+               }
+       }
+       return "";
+}
+
+static void
+x264_update_int(signal_user_data_t *ud, const gchar *name, const gchar *val)
+{
+       gdouble dvalue;
+       gchar *end;
+
+       if (val == NULL) return;
+       dvalue = g_strtod (val, &end);
+       ghb_ui_update_int(ud, name, dvalue);
+}
+
+static void
+x264_update_bool(signal_user_data_t *ud, const gchar *name, const gchar *val)
+{
+       if (val == NULL)
+               ghb_ui_update(ud, name, "1");
+       else
+               ghb_ui_update(ud, name, val);
+}
+
+static void
+x264_update_combo(signal_user_data_t *ud, const gchar *name, const gchar *val)
+{
+       GtkTreeModel *store;
+       GtkTreeIter iter;
+       gchar *shortOpt;
+       gint ivalue;
+       gboolean foundit = FALSE;
+       GtkWidget *widget;
+
+       if (val == NULL) return;
+       widget = GHB_WIDGET(ud->builder, name);
+       if (widget == NULL)
+       {
+               g_debug("Failed to find widget for key: %s\n", name);
+               return;
+       }
+       store = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
+       if (gtk_tree_model_get_iter_first (store, &iter))
+       {
+               do
+               {
+                       gtk_tree_model_get(store, &iter, 2, &shortOpt, 3, &ivalue, -1);
+                       if (strcmp(shortOpt, val) == 0)
+                       {
+                               gtk_combo_box_set_active_iter (GTK_COMBO_BOX(widget), &iter);
+                               g_free(shortOpt);
+                               foundit = TRUE;
+                               break;
+                       }
+                       g_free(shortOpt);
+               } while (gtk_tree_model_iter_next (store, &iter));
+       }
+       if (!foundit)
+       {
+               if (gtk_tree_model_get_iter_first (store, &iter))
+               {
+                       do
+                       {
+                               gtk_tree_model_get(store, &iter, 2, &shortOpt, 3, &ivalue, -1);
+                               if (strcmp(shortOpt, "custom") == 0)
+                               {
+                                       gtk_list_store_set(GTK_LIST_STORE(store), &iter, 4, val, -1);
+                                       gtk_combo_box_set_active_iter (GTK_COMBO_BOX(widget), &iter);
+                                       g_free(shortOpt);
+                                       foundit = TRUE;
+                                       break;
+                               }
+                               g_free(shortOpt);
+                       } while (gtk_tree_model_iter_next (store, &iter));
+               }
+       }
+       // Its possible the value hasn't changed. Since settings are only
+       // updated when the value changes, I'm initializing settings here as well.
+       ghb_widget_to_setting(ud->settings, widget);
+}
+
+static void
+x264_update_deblock(signal_user_data_t *ud, const gchar *xval)
+{
+       gdouble avalue, bvalue;
+       gchar *end;
+       gchar *val;
+       gchar *bval = NULL;
+
+       if (xval == NULL) return;
+       val = g_strdup(xval);
+       bvalue = avalue = 0;
+       if (val != NULL) 
+       {
+               gchar *pos = strchr(val, ',');
+               if (pos != NULL)
+               {
+                       bval = pos + 1;
+                       *pos = 0;
+               }
+               avalue = g_strtod (val, &end);
+               if (bval != NULL)
+               {
+                       bvalue = g_strtod (bval, &end);
+               }
+       }
+       g_free(val);
+       ghb_ui_update_int(ud, "x264_deblock_alpha", avalue);
+       ghb_ui_update_int(ud, "x264_deblock_beta", bvalue);
+}
+
+void
+ghb_x264_parse_options(signal_user_data_t *ud, const gchar *options)
+{
+       gchar **split = g_strsplit(options, ":", -1);
+       if (split == NULL) return;
+
+       gint ii;
+       gint jj;
+
+       for (jj = 0; jj < X264_OPT_MAP_SIZE; jj++)
+               x264_opt_map[jj].found = FALSE;
+
+       for (ii = 0; split[ii] != NULL; ii++)
+       {
+               gchar *val = NULL;
+               gchar *pos = strchr(split[ii], '=');
+               if (pos != NULL)
+               {
+                       val = pos + 1;
+                       *pos = 0;
+               }
+               for (jj = 0; jj < X264_OPT_MAP_SIZE; jj++)
+               {
+                       if (find_syn_match(split[ii], x264_opt_map[jj].opt_syns) >= 0)
+                       {
+                               x264_opt_map[jj].found = TRUE;
+                               switch(x264_opt_map[jj].type)
+                               {
+                               case X264_OPT_INT:
+                                       x264_update_int(ud, x264_opt_map[jj].name, val);
+                                       break;
+                               case X264_OPT_BOOL:
+                                       x264_update_bool(ud, x264_opt_map[jj].name, val);
+                                       break;
+                               case X264_OPT_COMBO:
+                                       x264_update_combo(ud, x264_opt_map[jj].name, val);
+                                       break;
+                               case X264_OPT_DEBLOCK:
+                                       // dirty little hack.  mark deblock_beta found as well
+                                       x264_opt_map[jj+1].found = TRUE;
+                                       x264_update_deblock(ud, val);
+                                       break;
+                               }
+                               break;
+                       }
+               }
+       }
+       // For any options not found in the option string, set ui to
+       // default values
+       for (jj = 0; jj < X264_OPT_MAP_SIZE; jj++)
+       {
+               if (!x264_opt_map[jj].found)
+               {
+                       gchar *val = strdup(x264_opt_map[jj].def_val);
+                       switch(x264_opt_map[jj].type)
+                       {
+                       case X264_OPT_INT:
+                               x264_update_int(ud, x264_opt_map[jj].name, val);
+                               break;
+                       case X264_OPT_BOOL:
+                               x264_update_bool(ud, x264_opt_map[jj].name, val);
+                               break;
+                       case X264_OPT_COMBO:
+                               x264_update_combo(ud, x264_opt_map[jj].name, val);
+                               break;
+                       case X264_OPT_DEBLOCK:
+                               x264_update_deblock(ud, val);
+                               break;
+                       }
+                       x264_opt_map[jj].found = TRUE;
+                       g_free(val);
+               }
+       }
+       g_strfreev(split);
+}
+
+gchar*
+get_deblock_val(signal_user_data_t *ud)
+{
+       const gchar *alpha, *beta;
+       gchar *result;
+       alpha = ghb_settings_get_string(ud->settings, "x264_deblock_alpha");
+       beta = ghb_settings_get_string(ud->settings, "x264_deblock_beta");
+       result = g_strdup_printf("%s,%s", alpha, beta);
+       return result;
+}
+
+void
+ghb_x264_opt_update(signal_user_data_t *ud, GtkWidget *widget)
+{
+       gint jj;
+       const gchar *name = gtk_widget_get_name(widget);
+       gchar **opt_syns = NULL;
+       const gchar *def_val = NULL;
+       gint type;
+
+       for (jj = 0; jj < X264_OPT_MAP_SIZE; jj++)
+       {
+               if (strcmp(name, x264_opt_map[jj].name) == 0)
+               {
+                       // found the options that needs updating
+                       opt_syns = x264_opt_map[jj].opt_syns;
+                       def_val = x264_opt_map[jj].def_val;
+                       type = x264_opt_map[jj].type;
+                       break;
+               }
+       }
+       if (opt_syns != NULL)
+       {
+               GString *x264opts = g_string_new("");
+               const gchar *options;
+               options = ghb_settings_get_string(ud->settings, "x264_options");
+               gchar **split = g_strsplit(options, ":", -1);
+               gint ii;
+               gboolean foundit = FALSE;
+
+               if (split == NULL) return;
+               for (ii = 0; split[ii] != NULL; ii++)
+               {
+                       gint syn;
+                       gchar *val = NULL;
+                       gchar *pos = strchr(split[ii], '=');
+                       if (pos != NULL)
+                       {
+                               val = pos + 1;
+                               *pos = 0;
+                       }
+                       syn = find_syn_match(split[ii], opt_syns);
+                       if (syn >= 0)
+                       { // Updating this option
+                               gchar *val;
+                               foundit = TRUE;
+                               if (type == X264_OPT_DEBLOCK)
+                                       val = get_deblock_val(ud);
+                               else
+                                       val = ghb_widget_string(widget);
+                               if (strcmp(def_val, val) != 0)
+                               {
+                                       g_string_append_printf(x264opts, "%s=%s:", opt_syns[syn], val);
+                               }
+                               g_free(val);
+                       }
+                       else if (val != NULL)
+                               g_string_append_printf(x264opts, "%s=%s:", split[ii], val);
+                       else
+                               g_string_append_printf(x264opts, "%s:", split[ii]);
+
+               }
+               if (!foundit)
+               {
+                       gchar *val;
+                       if (type == X264_OPT_DEBLOCK)
+                               val = get_deblock_val(ud);
+                       else
+                               val = ghb_widget_string(widget);
+                       if (strcmp(def_val, val) != 0)
+                       {
+                               g_string_append_printf(x264opts, "%s=%s:", opt_syns[0], val);
+                       }
+                       g_free(val);
+               }
+               // Update the options value
+               // strip the trailing ":"
+               gchar *result;
+               gint len;
+               result = g_string_free(x264opts, FALSE);
+               len = strlen(result);
+               if (len > 0) result[len - 1] = 0;
+               ghb_ui_update(ud, "x264_options", result);
+       }
+}
+
+static void
+x264_remove_opt(gchar **opts, gchar **opt_syns)
+{
+       gint ii;
+       for (ii = 0; opts[ii] != NULL; ii++)
+       {
+               gchar *opt;
+               opt = g_strdup(opts[ii]);
+               gchar *pos = strchr(opt, '=');
+               if (pos != NULL)
+               {
+                       *pos = 0;
+               }
+               if (find_syn_match(opt, opt_syns) >= 0)
+               {
+                       // Mark as deleted
+                       opts[ii][0] = 0;
+               }
+               g_free(opt);
+       }
+}
+
+// Construct the x264 options string
+// The result is allocated, so someone must free it at some point.
+gchar*
+ghb_sanitize_x264opts(signal_user_data_t *ud, const gchar *options)
+{
+       GString *x264opts = g_string_new("");
+       gchar **split = g_strsplit(options, ":", -1);
+
+       // Remove entries that match the defaults
+       gint ii;
+       for (ii = 0; split[ii] != NULL; ii++)
+       {
+               gchar *val = NULL;
+               gchar *opt = g_strdup(split[ii]);
+               gchar *pos = strchr(opt, '=');
+               if (pos != NULL)
+               {
+                       val = pos + 1;
+                       *pos = 0;
+               }
+               else
+               {
+                       val = "1";
+               }
+               const gchar *def_val = x264_opt_get_default(opt);
+               if (strcmp(val, def_val) == 0)
+               {
+                       // Matches the default, so remove it
+                       split[ii][0] = 0;
+               }
+               g_free(opt);
+       }
+       gint refs = ghb_settings_get_int(ud->settings, "x264_refs");
+       if (refs <= 1)
+       {
+               x264_remove_opt(split, x264_mixed_syns);
+       }
+       gint subme = ghb_settings_get_int(ud->settings, "x264_subme");
+       if (subme < 6)
+       {
+               x264_remove_opt(split, x264_brdo_syns);
+       }
+       gint bframes = ghb_settings_get_int(ud->settings, "x264_bframes");
+       if (bframes == 0)
+       {
+               x264_remove_opt(split, x264_weightb_syns);
+               x264_remove_opt(split, x264_brdo_syns);
+               x264_remove_opt(split, x264_bime_syns);
+       }
+       if (bframes <= 1)
+       {
+               x264_remove_opt(split, x264_bpyramid_syns);
+       }
+       const gchar *me = ghb_settings_get_string(ud->settings, "x264_me");
+       if (!(strcmp(me, "umh") == 0 || strcmp(me, "esa") == 0))
+       {
+               x264_remove_opt(split, x264_merange_syns);
+       }
+       if (!ghb_settings_get_bool(ud->settings, "x264_cabac"))
+       {
+               x264_remove_opt(split, x264_trellis_syns);
+       }
+       gint analyse = ghb_settings_get_int(ud->settings, "x264_analyse");
+       if (analyse == 1)
+       {
+               x264_remove_opt(split, x264_direct_syns);
+       }
+       for (ii = 0; split[ii] != NULL; ii++)
+       {
+               if (split[ii][0] != 0)
+                       g_string_append_printf(x264opts, "%s:", split[ii]);
+       }
+       // strip the trailing ":"
+       gchar *result;
+       gint len;
+       result = g_string_free(x264opts, FALSE);
+       len = strlen(result);
+       if (len > 0) result[len - 1] = 0;
+       return result;
+}
+