mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-24 02:31:03 +00:00
e54ceff720
It lacks to many feature and the code is too bad, desactivation until someone comes and fix it... The code should be removed if it never happens
1736 lines
44 KiB
C
1736 lines
44 KiB
C
/* GStreamer Editing Services
|
|
* Copyright (C) 2010 Brandon Lewis <brandon.lewis@collabora.co.uk>
|
|
* 2010 Nokia Corporation
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 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
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <gtk/gtk.h>
|
|
#include <glib.h>
|
|
#include <glib/gprintf.h>
|
|
#include <ges/ges.h>
|
|
|
|
/* Application Data ********************************************************/
|
|
|
|
/**
|
|
* Contains most of the application data so that signal handlers
|
|
* and other callbacks have easy access.
|
|
*/
|
|
|
|
typedef struct App
|
|
{
|
|
/* back-end objects */
|
|
GESTimeline *timeline;
|
|
GESPipeline *pipeline;
|
|
GESLayer *layer;
|
|
GESTrack *audio_track;
|
|
GESTrack *video_track;
|
|
guint audio_tracks;
|
|
guint video_tracks;
|
|
|
|
/* application state */
|
|
gchar *pending_uri;
|
|
int n_objects;
|
|
|
|
int n_selected;
|
|
GList *selected_objects;
|
|
GType selected_type;
|
|
gboolean first_selected;
|
|
gboolean last_selected;
|
|
|
|
gboolean ignore_input;
|
|
GstState state;
|
|
|
|
GtkListStore *model;
|
|
GtkTreeSelection *selection;
|
|
|
|
/* widgets */
|
|
GtkWidget *main_window;
|
|
GtkWidget *add_effect_dlg;
|
|
GtkWidget *properties;
|
|
GtkWidget *filesource_properties;
|
|
GtkWidget *text_properties;
|
|
GtkWidget *generic_duration;
|
|
GtkWidget *background_properties;
|
|
GtkWidget *audio_effect_entry;
|
|
GtkWidget *video_effect_entry;
|
|
|
|
GtkHScale *duration;
|
|
GtkHScale *in_point;
|
|
GtkHScale *volume;
|
|
|
|
GtkAction *add_file;
|
|
GtkAction *add_effect;
|
|
GtkAction *add_test;
|
|
GtkAction *add_title;
|
|
GtkAction *add_transition;
|
|
GtkAction *delete;
|
|
GtkAction *play;
|
|
GtkAction *stop;
|
|
GtkAction *move_up;
|
|
GtkAction *move_down;
|
|
GtkToggleAction *audio_track_action;
|
|
GtkToggleAction *video_track_action;
|
|
|
|
GtkComboBox *halign;
|
|
GtkComboBox *valign;
|
|
GtkComboBox *background_type;
|
|
|
|
GtkEntry *text;
|
|
GtkEntry *seconds;
|
|
|
|
GtkSpinButton *frequency;
|
|
} App;
|
|
|
|
static int n_instances = 0;
|
|
|
|
/* Prototypes for auto-connected signal handlers ***************************/
|
|
|
|
/**
|
|
* These are declared non-static for signal auto-connection
|
|
*/
|
|
|
|
gboolean window_delete_event_cb (GtkObject * window, GdkEvent * event,
|
|
App * app);
|
|
void new_activate_cb (GtkMenuItem * item, App * app);
|
|
void open_activate_cb (GtkMenuItem * item, App * app);
|
|
void save_as_activate_cb (GtkMenuItem * item, App * app);
|
|
void launch_project_activate_cb (GtkMenuItem * item, App * app);
|
|
void quit_item_activate_cb (GtkMenuItem * item, App * app);
|
|
void delete_activate_cb (GtkAction * item, App * app);
|
|
void play_activate_cb (GtkAction * item, App * app);
|
|
void stop_activate_cb (GtkAction * item, App * app);
|
|
void move_up_activate_cb (GtkAction * item, App * app);
|
|
void move_down_activate_cb (GtkAction * item, App * app);
|
|
void add_effect_activate_cb (GtkAction * item, App * app);
|
|
void add_file_activate_cb (GtkAction * item, App * app);
|
|
void add_text_activate_cb (GtkAction * item, App * app);
|
|
void add_test_activate_cb (GtkAction * item, App * app);
|
|
void audio_track_activate_cb (GtkToggleAction * item, App * app);
|
|
void video_track_activate_cb (GtkToggleAction * item, App * app);
|
|
void add_transition_activate_cb (GtkAction * item, App * app);
|
|
void app_selection_changed_cb (GtkTreeSelection * selection, App * app);
|
|
void halign_changed_cb (GtkComboBox * widget, App * app);
|
|
void valign_changed_cb (GtkComboBox * widget, App * app);
|
|
void background_type_changed_cb (GtkComboBox * widget, App * app);
|
|
void frequency_value_changed_cb (GtkSpinButton * widget, App * app);
|
|
void on_apply_effect_cb (GtkButton * button, App * app);
|
|
void on_cancel_add_effect_cb (GtkButton * button, App * app);
|
|
gboolean add_effect_dlg_delete_event_cb (GtkWidget * widget, GdkEvent * event,
|
|
gpointer * app);
|
|
|
|
gboolean
|
|
duration_scale_change_value_cb (GtkRange * range,
|
|
GtkScrollType unused, gdouble value, App * app);
|
|
|
|
gboolean
|
|
in_point_scale_change_value_cb (GtkRange * range,
|
|
GtkScrollType unused, gdouble value, App * app);
|
|
|
|
gboolean
|
|
volume_change_value_cb (GtkRange * range,
|
|
GtkScrollType unused, gdouble value, App * app);
|
|
|
|
/* UI state functions *******************************************************/
|
|
|
|
/**
|
|
* Update properties of UI elements that depend on more than one thing.
|
|
*/
|
|
|
|
static void
|
|
update_effect_sensitivity (App * app)
|
|
{
|
|
GList *i;
|
|
gboolean ok = TRUE;
|
|
|
|
/* effects will work for multiple FileSource */
|
|
for (i = app->selected_objects; i; i = i->next) {
|
|
if (!GES_IS_URI_CLIP (i->data)) {
|
|
ok = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
gtk_action_set_sensitive (app->add_effect,
|
|
ok && (app->n_selected > 0) && (app->state != GST_STATE_PLAYING)
|
|
&& (app->state != GST_STATE_PAUSED));
|
|
}
|
|
|
|
static void
|
|
update_delete_sensitivity (App * app)
|
|
{
|
|
/* delete will work for multiple items */
|
|
gtk_action_set_sensitive (app->delete,
|
|
(app->n_selected > 0) && (app->state != GST_STATE_PLAYING)
|
|
&& (app->state != GST_STATE_PAUSED));
|
|
}
|
|
|
|
static void
|
|
update_add_transition_sensitivity (App * app)
|
|
{
|
|
gtk_action_set_sensitive (app->add_transition,
|
|
(app->state != GST_STATE_PLAYING) && (app->state != GST_STATE_PAUSED));
|
|
}
|
|
|
|
static void
|
|
update_move_up_down_sensitivity (App * app)
|
|
{
|
|
gboolean can_move;
|
|
|
|
can_move = (app->n_selected == 1) &&
|
|
(app->state != GST_STATE_PLAYING) && (app->state != GST_STATE_PAUSED);
|
|
|
|
gtk_action_set_sensitive (app->move_up, can_move && (!app->first_selected));
|
|
gtk_action_set_sensitive (app->move_down, can_move && (!app->last_selected));
|
|
}
|
|
|
|
static void
|
|
update_play_sensitivity (App * app)
|
|
{
|
|
gboolean valid;
|
|
|
|
g_object_get (app->layer, "valid", &valid, NULL);
|
|
|
|
gtk_action_set_sensitive (app->play, (app->n_objects && valid));
|
|
}
|
|
|
|
/* Backend callbacks ********************************************************/
|
|
|
|
static void
|
|
test_source_notify_volume_changed_cb (GESClip * clip, GParamSpec *
|
|
unused G_GNUC_UNUSED, App * app)
|
|
{
|
|
gdouble volume;
|
|
|
|
g_object_get (G_OBJECT (clip), "volume", &volume, NULL);
|
|
|
|
gtk_range_set_value (GTK_RANGE (app->volume), volume);
|
|
}
|
|
|
|
static void
|
|
layer_notify_valid_changed_cb (GObject * object, GParamSpec * unused
|
|
G_GNUC_UNUSED, App * app)
|
|
{
|
|
update_play_sensitivity (app);
|
|
}
|
|
|
|
static gboolean
|
|
find_row_for_object (GtkListStore * model, GtkTreeIter * ret, GESClip * clip)
|
|
{
|
|
gtk_tree_model_get_iter_first ((GtkTreeModel *) model, ret);
|
|
|
|
while (gtk_list_store_iter_is_valid (model, ret)) {
|
|
GESClip *clip2;
|
|
gtk_tree_model_get ((GtkTreeModel *) model, ret, 2, &clip2, -1);
|
|
if (clip2 == clip) {
|
|
gst_object_unref (clip2);
|
|
return TRUE;
|
|
}
|
|
gst_object_unref (clip2);
|
|
gtk_tree_model_iter_next ((GtkTreeModel *) model, ret);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/* this callback is registered for every clip, and updates the
|
|
* corresponding duration cell in the model */
|
|
static void
|
|
clip_notify_duration_cb (GESClip * clip,
|
|
GParamSpec * arg G_GNUC_UNUSED, App * app)
|
|
{
|
|
GtkTreeIter iter;
|
|
guint64 duration = 0;
|
|
|
|
g_object_get (clip, "duration", &duration, NULL);
|
|
find_row_for_object (app->model, &iter, clip);
|
|
gtk_list_store_set (app->model, &iter, 1, duration, -1);
|
|
}
|
|
|
|
/* these guys are only connected to filesources that are the target of the
|
|
* current selection */
|
|
|
|
static void
|
|
filesource_notify_duration_cb (GESClip * clip,
|
|
GParamSpec * arg G_GNUC_UNUSED, App * app)
|
|
{
|
|
guint64 duration, max_inpoint;
|
|
duration = GES_TIMELINE_ELEMENT_DURATION (clip);
|
|
max_inpoint = GES_TIMELINE_ELEMENT_MAX_DURATION (clip) - duration;
|
|
|
|
gtk_range_set_value (GTK_RANGE (app->duration), duration);
|
|
gtk_range_set_fill_level (GTK_RANGE (app->in_point), max_inpoint);
|
|
|
|
if (max_inpoint < GES_TIMELINE_ELEMENT_INPOINT (clip))
|
|
g_object_set (clip, "in-point", max_inpoint, NULL);
|
|
|
|
}
|
|
|
|
static void
|
|
filesource_notify_max_duration_cb (GESClip * clip,
|
|
GParamSpec * arg G_GNUC_UNUSED, App * app)
|
|
{
|
|
gtk_range_set_range (GTK_RANGE (app->duration), 0, (gdouble)
|
|
GES_TIMELINE_ELEMENT_MAX_DURATION (clip));
|
|
gtk_range_set_range (GTK_RANGE (app->in_point), 0, (gdouble)
|
|
GES_TIMELINE_ELEMENT_MAX_DURATION (clip));
|
|
}
|
|
|
|
static void
|
|
filesource_notify_in_point_cb (GESClip * clip,
|
|
GParamSpec * arg G_GNUC_UNUSED, App * app)
|
|
{
|
|
gtk_range_set_value (GTK_RANGE (app->in_point),
|
|
GES_TIMELINE_ELEMENT_INPOINT (clip));
|
|
}
|
|
|
|
static void
|
|
app_update_first_last_selected (App * app)
|
|
{
|
|
GtkTreePath *path;
|
|
|
|
/* keep track of whether the first or last items are selected */
|
|
path = gtk_tree_path_new_from_indices (0, -1);
|
|
app->first_selected =
|
|
gtk_tree_selection_path_is_selected (app->selection, path);
|
|
gtk_tree_path_free (path);
|
|
|
|
path = gtk_tree_path_new_from_indices (app->n_objects - 1, -1);
|
|
app->last_selected =
|
|
gtk_tree_selection_path_is_selected (app->selection, path);
|
|
gtk_tree_path_free (path);
|
|
}
|
|
|
|
static void
|
|
object_count_changed (App * app)
|
|
{
|
|
app_update_first_last_selected (app);
|
|
update_move_up_down_sensitivity (app);
|
|
update_play_sensitivity (app);
|
|
}
|
|
|
|
static void
|
|
title_source_text_changed_cb (GESClip * clip,
|
|
GParamSpec * arg G_GNUC_UNUSED, App * app)
|
|
{
|
|
GtkTreeIter iter;
|
|
gchar *text;
|
|
|
|
g_object_get (clip, "text", &text, NULL);
|
|
if (text) {
|
|
find_row_for_object (app->model, &iter, clip);
|
|
gtk_list_store_set (app->model, &iter, 0, text, -1);
|
|
}
|
|
}
|
|
|
|
static void
|
|
layer_object_added_cb (GESLayer * layer, GESClip * clip, App * app)
|
|
{
|
|
GtkTreeIter iter;
|
|
gchar *description;
|
|
|
|
GST_INFO ("layer clip added cb %p %p %p", layer, clip, app);
|
|
|
|
gtk_list_store_append (app->model, &iter);
|
|
|
|
if (GES_IS_URI_CLIP (clip)) {
|
|
g_object_get (G_OBJECT (clip), "uri", &description, NULL);
|
|
gtk_list_store_set (app->model, &iter, 0, description, 2, clip, -1);
|
|
}
|
|
|
|
else if (GES_IS_TITLE_CLIP (clip)) {
|
|
gtk_list_store_set (app->model, &iter, 2, clip, -1);
|
|
g_signal_connect (G_OBJECT (clip), "notify::text",
|
|
G_CALLBACK (title_source_text_changed_cb), app);
|
|
title_source_text_changed_cb (clip, NULL, app);
|
|
}
|
|
|
|
else if (GES_IS_TEST_CLIP (clip)) {
|
|
gtk_list_store_set (app->model, &iter, 2, clip, 0, "Test Source", -1);
|
|
}
|
|
|
|
else if (GES_IS_BASE_TRANSITION_CLIP (clip)) {
|
|
gtk_list_store_set (app->model, &iter, 2, clip, 0, "Transition", -1);
|
|
}
|
|
|
|
g_signal_connect (G_OBJECT (clip), "notify::duration",
|
|
G_CALLBACK (clip_notify_duration_cb), app);
|
|
clip_notify_duration_cb (clip, NULL, app);
|
|
|
|
app->n_objects++;
|
|
object_count_changed (app);
|
|
}
|
|
|
|
static void
|
|
layer_object_removed_cb (GESLayer * layer, GESClip * clip, App * app)
|
|
{
|
|
GtkTreeIter iter;
|
|
|
|
GST_INFO ("layer clip removed cb %p %p %p", layer, clip, app);
|
|
|
|
if (!find_row_for_object (GTK_LIST_STORE (app->model), &iter, clip)) {
|
|
g_print ("clip deleted but we don't own it");
|
|
return;
|
|
}
|
|
app->n_objects--;
|
|
object_count_changed (app);
|
|
|
|
gtk_list_store_remove (app->model, &iter);
|
|
}
|
|
|
|
static void
|
|
layer_object_moved_cb (GESClip * layer, GESClip * clip,
|
|
gint old, gint new, App * app)
|
|
{
|
|
GtkTreeIter a, b;
|
|
GtkTreePath *path;
|
|
|
|
/* we can take the old position as given, but the new position might have to
|
|
* be adjusted. */
|
|
new = new < 0 ? (app->n_objects - 1) : new;
|
|
|
|
path = gtk_tree_path_new_from_indices (old, -1);
|
|
gtk_tree_model_get_iter (GTK_TREE_MODEL (app->model), &a, path);
|
|
gtk_tree_path_free (path);
|
|
|
|
path = gtk_tree_path_new_from_indices (new, -1);
|
|
gtk_tree_model_get_iter (GTK_TREE_MODEL (app->model), &b, path);
|
|
gtk_tree_path_free (path);
|
|
|
|
gtk_list_store_swap (app->model, &a, &b);
|
|
app_selection_changed_cb (app->selection, app);
|
|
update_move_up_down_sensitivity (app);
|
|
}
|
|
|
|
static void
|
|
pipeline_state_changed_cb (App * app)
|
|
{
|
|
gboolean playing_or_paused;
|
|
|
|
if (app->state == GST_STATE_PLAYING)
|
|
gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PAUSE);
|
|
else
|
|
gtk_action_set_stock_id (app->play, GTK_STOCK_MEDIA_PLAY);
|
|
|
|
update_delete_sensitivity (app);
|
|
update_add_transition_sensitivity (app);
|
|
update_move_up_down_sensitivity (app);
|
|
|
|
playing_or_paused = (app->state == GST_STATE_PLAYING) ||
|
|
(app->state == GST_STATE_PAUSED);
|
|
|
|
gtk_action_set_sensitive (app->add_file, !playing_or_paused);
|
|
gtk_action_set_sensitive (app->add_title, !playing_or_paused);
|
|
gtk_action_set_sensitive (app->add_test, !playing_or_paused);
|
|
gtk_action_set_sensitive ((GtkAction *) app->audio_track_action,
|
|
!playing_or_paused);
|
|
gtk_action_set_sensitive ((GtkAction *) app->video_track_action,
|
|
!playing_or_paused);
|
|
gtk_widget_set_sensitive (app->properties, !playing_or_paused);
|
|
}
|
|
|
|
static void
|
|
project_bus_message_cb (GstBus * bus, GstMessage * message,
|
|
GMainLoop * mainloop)
|
|
{
|
|
switch (GST_MESSAGE_TYPE (message)) {
|
|
case GST_MESSAGE_ERROR:
|
|
g_printerr ("ERROR\n");
|
|
g_main_loop_quit (mainloop);
|
|
break;
|
|
case GST_MESSAGE_EOS:
|
|
g_printerr ("Done\n");
|
|
g_main_loop_quit (mainloop);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
bus_message_cb (GstBus * bus, GstMessage * message, App * app)
|
|
{
|
|
const GstStructure *s;
|
|
s = gst_message_get_structure (message);
|
|
|
|
switch (GST_MESSAGE_TYPE (message)) {
|
|
case GST_MESSAGE_ERROR:
|
|
g_print ("ERROR\n");
|
|
break;
|
|
case GST_MESSAGE_EOS:
|
|
gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY);
|
|
break;
|
|
case GST_MESSAGE_STATE_CHANGED:
|
|
if (s && GST_MESSAGE_SRC (message) == GST_OBJECT_CAST (app->pipeline)) {
|
|
GstState old, new, pending;
|
|
gst_message_parse_state_changed (message, &old, &new, &pending);
|
|
app->state = new;
|
|
pipeline_state_changed_cb (app);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Static UI Callbacks ******************************************************/
|
|
|
|
static gboolean
|
|
check_time (const gchar * time)
|
|
{
|
|
static GRegex *re = NULL;
|
|
|
|
if (!re) {
|
|
if (NULL == (re =
|
|
g_regex_new ("^[0-9][0-9]:[0-5][0-9]:[0-5][0-9](\\.[0-9]+)?$",
|
|
G_REGEX_EXTENDED, 0, NULL)))
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_regex_match (re, time, 0, NULL))
|
|
return TRUE;
|
|
return FALSE;
|
|
}
|
|
|
|
static guint64
|
|
str_to_time (const gchar * str)
|
|
{
|
|
guint64 ret;
|
|
guint64 h, m;
|
|
gdouble s;
|
|
gchar buf[15];
|
|
|
|
buf[0] = str[0];
|
|
buf[1] = str[1];
|
|
buf[2] = '\0';
|
|
|
|
h = strtoull (buf, NULL, 10);
|
|
|
|
buf[0] = str[3];
|
|
buf[1] = str[4];
|
|
buf[2] = '\0';
|
|
|
|
m = strtoull (buf, NULL, 10);
|
|
|
|
strncpy (buf, &str[6], sizeof (buf));
|
|
s = strtod (buf, NULL);
|
|
|
|
ret = (h * 3600 * GST_SECOND) +
|
|
(m * 60 * GST_SECOND) + ((guint64) (s * GST_SECOND));
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
text_notify_text_changed_cb (GtkEntry * widget, GParamSpec * unused, App * app)
|
|
{
|
|
GList *tmp;
|
|
const gchar *text;
|
|
|
|
if (app->ignore_input)
|
|
return;
|
|
|
|
text = gtk_entry_get_text (widget);
|
|
|
|
for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
|
|
g_object_set (G_OBJECT (tmp->data), "text", text, NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
seconds_notify_text_changed_cb (GtkEntry * widget, GParamSpec * unused,
|
|
App * app)
|
|
{
|
|
GList *tmp;
|
|
const gchar *text;
|
|
|
|
if (app->ignore_input)
|
|
return;
|
|
|
|
text = gtk_entry_get_text (app->seconds);
|
|
|
|
if (!check_time (text)) {
|
|
gtk_entry_set_icon_from_stock (app->seconds,
|
|
GTK_ENTRY_ICON_SECONDARY, GTK_STOCK_DIALOG_WARNING);
|
|
} else {
|
|
gtk_entry_set_icon_from_stock (app->seconds,
|
|
GTK_ENTRY_ICON_SECONDARY, NULL);
|
|
for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
|
|
g_object_set (GES_CLIP (tmp->data), "duration",
|
|
(guint64) str_to_time (text), NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
duration_cell_func (GtkTreeViewColumn * column, GtkCellRenderer * renderer,
|
|
GtkTreeModel * model, GtkTreeIter * iter, gpointer user)
|
|
{
|
|
gchar buf[30];
|
|
guint64 duration;
|
|
|
|
gtk_tree_model_get (model, iter, 1, &duration, -1);
|
|
g_snprintf (buf, sizeof (buf), "%u:%02u:%02u.%09u", GST_TIME_ARGS (duration));
|
|
g_object_set (renderer, "text", &buf, NULL);
|
|
}
|
|
|
|
/* UI Initialization ********************************************************/
|
|
|
|
static void
|
|
connect_to_filesource (GESClip * clip, App * app)
|
|
{
|
|
g_signal_connect (G_OBJECT (clip), "notify::max-duration",
|
|
G_CALLBACK (filesource_notify_max_duration_cb), app);
|
|
filesource_notify_max_duration_cb (clip, NULL, app);
|
|
|
|
g_signal_connect (G_OBJECT (clip), "notify::duration",
|
|
G_CALLBACK (filesource_notify_duration_cb), app);
|
|
filesource_notify_duration_cb (clip, NULL, app);
|
|
|
|
g_signal_connect (G_OBJECT (clip), "notify::in-point",
|
|
G_CALLBACK (filesource_notify_in_point_cb), app);
|
|
filesource_notify_in_point_cb (clip, NULL, app);
|
|
}
|
|
|
|
static void
|
|
disconnect_from_filesource (GESClip * clip, App * app)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
|
|
filesource_notify_duration_cb, app);
|
|
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
|
|
filesource_notify_max_duration_cb, app);
|
|
}
|
|
|
|
static void
|
|
connect_to_title_source (GESClip * clip, App * app)
|
|
{
|
|
GESTitleClip *titleclip;
|
|
titleclip = GES_TITLE_CLIP (clip);
|
|
gtk_combo_box_set_active (app->halign,
|
|
ges_title_clip_get_halignment (titleclip));
|
|
gtk_combo_box_set_active (app->valign,
|
|
ges_title_clip_get_valignment (titleclip));
|
|
gtk_entry_set_text (app->text, ges_title_clip_get_text (titleclip));
|
|
}
|
|
|
|
static void
|
|
disconnect_from_title_source (GESClip * clip, App * app)
|
|
{
|
|
}
|
|
|
|
static void
|
|
connect_to_test_source (GESClip * clip, App * app)
|
|
{
|
|
GObjectClass *klass;
|
|
GParamSpecDouble *pspec;
|
|
|
|
GESTestClip *testclip;
|
|
testclip = GES_TEST_CLIP (clip);
|
|
gtk_combo_box_set_active (app->background_type,
|
|
ges_test_clip_get_vpattern (testclip));
|
|
|
|
g_signal_connect (G_OBJECT (testclip), "notify::volume",
|
|
G_CALLBACK (test_source_notify_volume_changed_cb), app);
|
|
test_source_notify_volume_changed_cb (clip, NULL, app);
|
|
|
|
klass = G_OBJECT_GET_CLASS (G_OBJECT (testclip));
|
|
|
|
pspec = G_PARAM_SPEC_DOUBLE (g_object_class_find_property (klass, "volume"));
|
|
gtk_range_set_range (GTK_RANGE (app->volume), pspec->minimum, pspec->maximum);
|
|
|
|
pspec = G_PARAM_SPEC_DOUBLE (g_object_class_find_property (klass, "freq"));
|
|
gtk_spin_button_set_range (app->frequency, pspec->minimum, pspec->maximum);
|
|
gtk_spin_button_set_value (app->frequency,
|
|
ges_test_clip_get_frequency (GES_TEST_CLIP (clip)));
|
|
}
|
|
|
|
static void
|
|
disconnect_from_test_source (GESClip * clip, App * app)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (G_OBJECT (clip),
|
|
test_source_notify_volume_changed_cb, app);
|
|
}
|
|
|
|
static void
|
|
connect_to_object (GESClip * clip, App * app)
|
|
{
|
|
gchar buf[30];
|
|
guint64 duration;
|
|
|
|
app->ignore_input = TRUE;
|
|
|
|
duration = GES_TIMELINE_ELEMENT_DURATION (clip);
|
|
g_snprintf (buf, sizeof (buf), "%02u:%02u:%02u.%09u",
|
|
GST_TIME_ARGS (duration));
|
|
gtk_entry_set_text (app->seconds, buf);
|
|
|
|
if (GES_IS_URI_CLIP (clip)) {
|
|
connect_to_filesource (clip, app);
|
|
} else if (GES_IS_TITLE_CLIP (clip)) {
|
|
connect_to_title_source (clip, app);
|
|
} else if (GES_IS_TEST_CLIP (clip)) {
|
|
connect_to_test_source (clip, app);
|
|
}
|
|
|
|
app->ignore_input = FALSE;
|
|
}
|
|
|
|
static void
|
|
disconnect_from_object (GESClip * clip, App * app)
|
|
{
|
|
if (GES_IS_URI_CLIP (clip)) {
|
|
disconnect_from_filesource (clip, app);
|
|
} else if (GES_IS_TITLE_CLIP (clip)) {
|
|
disconnect_from_title_source (clip, app);
|
|
} else if (GES_IS_TEST_CLIP (clip)) {
|
|
disconnect_from_test_source (clip, app);
|
|
}
|
|
}
|
|
|
|
static GtkListStore *
|
|
get_video_patterns (void)
|
|
{
|
|
GEnumClass *enum_class;
|
|
GESTestClip *tr;
|
|
GESTestClipClass *klass;
|
|
GParamSpec *pspec;
|
|
GEnumValue *v;
|
|
GtkListStore *m;
|
|
GtkTreeIter i;
|
|
|
|
m = gtk_list_store_new (1, G_TYPE_STRING);
|
|
|
|
tr = ges_test_clip_new ();
|
|
klass = GES_TEST_CLIP_GET_CLASS (tr);
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_CLASS (klass), "vpattern");
|
|
|
|
enum_class = G_ENUM_CLASS (g_type_class_ref (pspec->value_type));
|
|
|
|
for (v = enum_class->values; v->value_nick != NULL; v++) {
|
|
gtk_list_store_append (m, &i);
|
|
gtk_list_store_set (m, &i, 0, v->value_name, -1);
|
|
}
|
|
|
|
g_type_class_unref (enum_class);
|
|
gst_object_unref (tr);
|
|
|
|
return m;
|
|
}
|
|
|
|
#define GET_WIDGET(dest,name,type) {\
|
|
if (!(dest =\
|
|
type(gtk_builder_get_object(builder, name))))\
|
|
goto fail;\
|
|
}
|
|
|
|
|
|
static void
|
|
layer_added_cb (GESTimeline * timeline, GESLayer * layer, App * app)
|
|
{
|
|
if (!GES_IS_SIMPLE_LAYER (layer)) {
|
|
GST_ERROR ("This timeline contains a layer type other than "
|
|
"GESSimpleLayer. Timeline editing disabled");
|
|
return;
|
|
}
|
|
|
|
if (!(app->layer)) {
|
|
app->layer = layer;
|
|
}
|
|
|
|
if (layer != app->layer) {
|
|
GST_ERROR ("This demo doesn't support editing timelines with multiple"
|
|
" layers");
|
|
return;
|
|
}
|
|
|
|
g_signal_connect (app->layer, "clip-added",
|
|
G_CALLBACK (layer_object_added_cb), app);
|
|
g_signal_connect (app->layer, "clip-removed",
|
|
G_CALLBACK (layer_object_removed_cb), app);
|
|
g_signal_connect (app->layer, "object-moved",
|
|
G_CALLBACK (layer_object_moved_cb), app);
|
|
g_signal_connect (app->layer, "notify::valid",
|
|
G_CALLBACK (layer_notify_valid_changed_cb), app);
|
|
}
|
|
|
|
static void
|
|
update_track_actions (App * app)
|
|
{
|
|
g_signal_handlers_disconnect_by_func (app->audio_track_action,
|
|
audio_track_activate_cb, app);
|
|
g_signal_handlers_disconnect_by_func (app->video_track_action,
|
|
video_track_activate_cb, app);
|
|
gtk_toggle_action_set_active (app->audio_track_action, app->audio_tracks);
|
|
gtk_toggle_action_set_active (app->video_track_action, app->video_tracks);
|
|
gtk_action_set_sensitive ((GtkAction *) app->audio_track_action,
|
|
app->audio_tracks <= 1);
|
|
gtk_action_set_sensitive ((GtkAction *) app->video_track_action,
|
|
app->video_tracks <= 1);
|
|
g_signal_connect (G_OBJECT (app->audio_track_action), "activate",
|
|
G_CALLBACK (audio_track_activate_cb), app);
|
|
g_signal_connect (G_OBJECT (app->video_track_action), "activate",
|
|
G_CALLBACK (video_track_activate_cb), app);
|
|
}
|
|
|
|
static void
|
|
track_added_cb (GESTimeline * timeline, GESTrack * track, App * app)
|
|
{
|
|
if (track->type == GES_TRACK_TYPE_AUDIO) {
|
|
app->audio_tracks++;
|
|
if (!app->audio_track)
|
|
app->audio_track = track;
|
|
}
|
|
if (track->type == GES_TRACK_TYPE_VIDEO) {
|
|
app->video_tracks++;
|
|
if (!app->video_track)
|
|
app->video_track = track;
|
|
}
|
|
|
|
|
|
update_track_actions (app);
|
|
}
|
|
|
|
static void
|
|
track_removed_cb (GESTimeline * timeline, GESTrack * track, App * app)
|
|
{
|
|
if (track->type == GES_TRACK_TYPE_AUDIO)
|
|
app->audio_tracks--;
|
|
if (track->type == GES_TRACK_TYPE_VIDEO)
|
|
app->video_tracks--;
|
|
|
|
update_track_actions (app);
|
|
}
|
|
|
|
static gboolean
|
|
create_ui (App * app)
|
|
{
|
|
GtkBuilder *builder;
|
|
GtkTreeView *timeline;
|
|
GtkTreeViewColumn *duration_col;
|
|
GtkCellRenderer *duration_renderer;
|
|
GtkCellRenderer *background_type_renderer;
|
|
GtkListStore *backgrounds;
|
|
GstBus *bus;
|
|
|
|
/* construct widget tree */
|
|
|
|
builder = gtk_builder_new ();
|
|
gtk_builder_add_from_file (builder, "ges-ui.glade", NULL);
|
|
|
|
/* get a bunch of widgets from the XML tree */
|
|
|
|
GET_WIDGET (timeline, "timeline_treeview", GTK_TREE_VIEW);
|
|
GET_WIDGET (app->properties, "properties", GTK_WIDGET);
|
|
GET_WIDGET (app->filesource_properties, "filesource_properties", GTK_WIDGET);
|
|
GET_WIDGET (app->text_properties, "text_properties", GTK_WIDGET);
|
|
GET_WIDGET (app->main_window, "window", GTK_WIDGET);
|
|
GET_WIDGET (app->add_effect_dlg, "add_effect_dlg", GTK_WIDGET);
|
|
GET_WIDGET (app->audio_effect_entry, "entry1", GTK_WIDGET);
|
|
GET_WIDGET (app->video_effect_entry, "entry2", GTK_WIDGET);
|
|
GET_WIDGET (app->duration, "duration_scale", GTK_HSCALE);
|
|
GET_WIDGET (app->in_point, "in_point_scale", GTK_HSCALE);
|
|
GET_WIDGET (app->halign, "halign", GTK_COMBO_BOX);
|
|
GET_WIDGET (app->valign, "valign", GTK_COMBO_BOX);
|
|
GET_WIDGET (app->text, "text", GTK_ENTRY);
|
|
GET_WIDGET (duration_col, "duration_column", GTK_TREE_VIEW_COLUMN);
|
|
GET_WIDGET (duration_renderer, "duration_renderer", GTK_CELL_RENDERER);
|
|
GET_WIDGET (app->add_file, "add_file", GTK_ACTION);
|
|
GET_WIDGET (app->add_effect, "add_effect", GTK_ACTION);
|
|
GET_WIDGET (app->add_title, "add_text", GTK_ACTION);
|
|
GET_WIDGET (app->add_test, "add_test", GTK_ACTION);
|
|
GET_WIDGET (app->add_transition, "add_transition", GTK_ACTION);
|
|
GET_WIDGET (app->delete, "delete", GTK_ACTION);
|
|
GET_WIDGET (app->play, "play", GTK_ACTION);
|
|
GET_WIDGET (app->stop, "stop", GTK_ACTION);
|
|
GET_WIDGET (app->move_up, "move_up", GTK_ACTION);
|
|
GET_WIDGET (app->move_down, "move_down", GTK_ACTION);
|
|
GET_WIDGET (app->seconds, "seconds", GTK_ENTRY);
|
|
GET_WIDGET (app->generic_duration, "generic_duration", GTK_WIDGET);
|
|
GET_WIDGET (app->background_type, "background_type", GTK_COMBO_BOX);
|
|
GET_WIDGET (app->background_properties, "background_properties", GTK_WIDGET);
|
|
GET_WIDGET (app->frequency, "frequency", GTK_SPIN_BUTTON);
|
|
GET_WIDGET (app->volume, "volume", GTK_HSCALE);
|
|
GET_WIDGET (app->audio_track_action, "audio_track", GTK_TOGGLE_ACTION);
|
|
GET_WIDGET (app->video_track_action, "video_track", GTK_TOGGLE_ACTION);
|
|
|
|
/* get text notifications */
|
|
|
|
g_signal_connect (app->text, "notify::text",
|
|
G_CALLBACK (text_notify_text_changed_cb), app);
|
|
|
|
g_signal_connect (app->seconds, "notify::text",
|
|
G_CALLBACK (seconds_notify_text_changed_cb), app);
|
|
|
|
/* we care when the tree selection changes */
|
|
|
|
if (!(app->selection = gtk_tree_view_get_selection (timeline)))
|
|
goto fail;
|
|
|
|
gtk_tree_selection_set_mode (app->selection, GTK_SELECTION_MULTIPLE);
|
|
|
|
g_signal_connect (app->selection, "changed",
|
|
G_CALLBACK (app_selection_changed_cb), app);
|
|
|
|
/* create the model for the treeview */
|
|
|
|
if (!(app->model =
|
|
gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_UINT64, G_TYPE_OBJECT)))
|
|
goto fail;
|
|
|
|
gtk_tree_view_set_model (timeline, GTK_TREE_MODEL (app->model));
|
|
|
|
/* register custom cell data function */
|
|
|
|
gtk_tree_view_column_set_cell_data_func (duration_col, duration_renderer,
|
|
duration_cell_func, NULL, NULL);
|
|
|
|
/* initialize combo boxes */
|
|
|
|
if (!(backgrounds = get_video_patterns ()))
|
|
goto fail;
|
|
|
|
if (!(background_type_renderer = gtk_cell_renderer_text_new ()))
|
|
goto fail;
|
|
|
|
gtk_combo_box_set_model (app->background_type, (GtkTreeModel *)
|
|
backgrounds);
|
|
|
|
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (app->background_type),
|
|
background_type_renderer, FALSE);
|
|
|
|
gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (app->background_type),
|
|
background_type_renderer, "text", 0);
|
|
|
|
g_signal_connect (app->timeline, "layer-added", G_CALLBACK
|
|
(layer_added_cb), app);
|
|
g_signal_connect (app->timeline, "track-added", G_CALLBACK
|
|
(track_added_cb), app);
|
|
g_signal_connect (app->timeline, "track-removed", G_CALLBACK
|
|
(track_removed_cb), app);
|
|
|
|
/* register callbacks on GES objects */
|
|
bus = gst_pipeline_get_bus (GST_PIPELINE (app->pipeline));
|
|
gst_bus_add_signal_watch (bus);
|
|
g_signal_connect (bus, "message", G_CALLBACK (bus_message_cb), app);
|
|
|
|
/* success */
|
|
gtk_builder_connect_signals (builder, app);
|
|
gst_object_unref (G_OBJECT (builder));
|
|
return TRUE;
|
|
|
|
fail:
|
|
gst_object_unref (G_OBJECT (builder));
|
|
return FALSE;
|
|
}
|
|
|
|
#undef GET_WIDGET
|
|
|
|
/* application methods ******************************************************/
|
|
|
|
static void selection_foreach (GtkTreeModel * model, GtkTreePath * path,
|
|
GtkTreeIter * iter, gpointer user);
|
|
|
|
static void
|
|
app_toggle_playpause (App * app)
|
|
{
|
|
if (app->state != GST_STATE_PLAYING) {
|
|
gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PLAYING);
|
|
} else {
|
|
gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PAUSED);
|
|
}
|
|
}
|
|
|
|
static void
|
|
app_stop_playback (App * app)
|
|
{
|
|
if ((app->state != GST_STATE_NULL) && (app->state != GST_STATE_READY)) {
|
|
gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_READY);
|
|
}
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
GList *objects;
|
|
guint n;
|
|
} select_info;
|
|
|
|
static void
|
|
app_update_selection (App * app)
|
|
{
|
|
GList *cur;
|
|
GType type;
|
|
select_info info = { NULL, 0 };
|
|
|
|
/* clear old selection */
|
|
for (cur = app->selected_objects; cur; cur = cur->next) {
|
|
disconnect_from_object (cur->data, app);
|
|
gst_object_unref (cur->data);
|
|
cur->data = NULL;
|
|
}
|
|
g_list_free (app->selected_objects);
|
|
app->selected_objects = NULL;
|
|
app->n_selected = 0;
|
|
|
|
/* get new selection */
|
|
gtk_tree_selection_selected_foreach (GTK_TREE_SELECTION (app->selection),
|
|
selection_foreach, &info);
|
|
app->selected_objects = info.objects;
|
|
app->n_selected = info.n;
|
|
|
|
type = G_TYPE_NONE;
|
|
if (app->selected_objects) {
|
|
type = G_TYPE_FROM_INSTANCE (app->selected_objects->data);
|
|
for (cur = app->selected_objects; cur; cur = cur->next) {
|
|
if (type != G_TYPE_FROM_INSTANCE (cur->data)) {
|
|
type = G_TYPE_NONE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type != G_TYPE_NONE) {
|
|
for (cur = app->selected_objects; cur; cur = cur->next) {
|
|
connect_to_object (cur->data, app);
|
|
}
|
|
}
|
|
|
|
app->selected_type = type;
|
|
app_update_first_last_selected (app);
|
|
}
|
|
|
|
static void
|
|
selection_foreach (GtkTreeModel * model, GtkTreePath * path, GtkTreeIter
|
|
* iter, gpointer user)
|
|
{
|
|
select_info *info = (select_info *) user;
|
|
GESClip *clip;
|
|
|
|
gtk_tree_model_get (model, iter, 2, &clip, -1);
|
|
info->objects = g_list_append (info->objects, clip);
|
|
|
|
info->n++;
|
|
return;
|
|
}
|
|
|
|
static GList *
|
|
app_get_selected_objects (App * app)
|
|
{
|
|
return g_list_copy (app->selected_objects);
|
|
}
|
|
|
|
static void
|
|
app_delete_objects (App * app, GList * objects)
|
|
{
|
|
GList *cur;
|
|
|
|
for (cur = objects; cur; cur = cur->next) {
|
|
ges_layer_remove_clip (app->layer, GES_CLIP (cur->data));
|
|
cur->data = NULL;
|
|
}
|
|
|
|
g_list_free (objects);
|
|
}
|
|
|
|
/* the following two methods assume exactly one clip is selected and that the
|
|
* requested action is valid */
|
|
|
|
static void
|
|
app_move_selected_up (App * app)
|
|
{
|
|
GList *objects, *tmp;
|
|
gint pos;
|
|
|
|
objects = ges_layer_get_clips (app->layer);
|
|
pos = g_list_index (objects, app->selected_objects->data);
|
|
|
|
ges_simple_layer_move_object (GES_SIMPLE_LAYER (app->layer),
|
|
GES_CLIP (app->selected_objects->data), pos - 1);
|
|
|
|
for (tmp = objects; tmp; tmp = tmp->next) {
|
|
gst_object_unref (tmp->data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
app_add_effect_on_selected_clips (App * app, const gchar * bin_desc)
|
|
{
|
|
GList *objects, *tmp;
|
|
GESTrackElement *effect = NULL;
|
|
|
|
/* No crash if the video is playing */
|
|
gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_PAUSED);
|
|
objects = ges_layer_get_clips (app->layer);
|
|
|
|
for (tmp = objects; tmp; tmp = tmp->next) {
|
|
effect = GES_TRACK_ELEMENT (ges_effect_new (bin_desc));
|
|
ges_container_add (GES_CONTAINER (tmp->data),
|
|
GES_TIMELINE_ELEMENT (effect));
|
|
gst_object_unref (tmp->data);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
add_effect_dlg_delete_event_cb (GtkWidget * widget, GdkEvent * event,
|
|
gpointer * app)
|
|
{
|
|
gtk_widget_hide_all (((App *) app)->add_effect_dlg);
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
on_cancel_add_effect_cb (GtkButton * button, App * app)
|
|
{
|
|
gtk_widget_hide_all (app->add_effect_dlg);
|
|
}
|
|
|
|
void
|
|
on_apply_effect_cb (GtkButton * button, App * app)
|
|
{
|
|
const gchar *effect;
|
|
|
|
effect = gtk_entry_get_text (GTK_ENTRY (app->video_effect_entry));
|
|
if (g_strcmp0 (effect, ""))
|
|
app_add_effect_on_selected_clips (app, effect);
|
|
|
|
gtk_entry_set_text (GTK_ENTRY (app->video_effect_entry), "");
|
|
|
|
effect = gtk_entry_get_text (GTK_ENTRY (app->audio_effect_entry));
|
|
if (g_strcmp0 (effect, ""))
|
|
app_add_effect_on_selected_clips (app, effect);
|
|
|
|
gtk_entry_set_text (GTK_ENTRY (app->audio_effect_entry), "");
|
|
|
|
gtk_widget_hide_all (app->add_effect_dlg);
|
|
}
|
|
|
|
static void
|
|
app_move_selected_down (App * app)
|
|
{
|
|
GList *objects, *tmp;
|
|
gint pos;
|
|
|
|
objects = ges_layer_get_clips (app->layer);
|
|
pos = g_list_index (objects, app->selected_objects->data);
|
|
|
|
ges_simple_layer_move_object (GES_SIMPLE_LAYER (app->layer),
|
|
GES_CLIP (app->selected_objects->data), pos - 1);
|
|
|
|
for (tmp = objects; tmp; tmp = tmp->next) {
|
|
gst_object_unref (tmp->data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
app_add_file (App * app, gchar * uri)
|
|
{
|
|
GESClip *clip;
|
|
|
|
GST_DEBUG ("adding file %s", uri);
|
|
|
|
clip = GES_CLIP (ges_uri_clip_new (uri));
|
|
|
|
ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
|
|
}
|
|
|
|
static void
|
|
app_launch_project (App * app, gchar * uri)
|
|
{
|
|
GESTimeline *timeline;
|
|
GMainLoop *mainloop;
|
|
GESPipeline *pipeline;
|
|
GstBus *bus;
|
|
GESProject *project;
|
|
|
|
uri = g_strsplit (uri, "//", 2)[1];
|
|
printf ("we will launch this uri : %s\n", uri);
|
|
project = ges_project_new (uri);
|
|
timeline = GES_TIMELINE (ges_asset_extract (GES_ASSET (project), NULL));
|
|
pipeline = ges_pipeline_new ();
|
|
bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
|
|
mainloop = g_main_loop_new (NULL, FALSE);
|
|
|
|
ges_pipeline_add_timeline (pipeline, timeline);
|
|
ges_pipeline_set_mode (pipeline, TIMELINE_MODE_PREVIEW_VIDEO);
|
|
gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
|
|
gst_bus_add_signal_watch (bus);
|
|
g_signal_connect (bus, "message", G_CALLBACK (project_bus_message_cb),
|
|
mainloop);
|
|
g_main_loop_run (mainloop);
|
|
g_object_unref (project);
|
|
}
|
|
|
|
static void
|
|
app_add_title (App * app)
|
|
{
|
|
GESClip *clip;
|
|
|
|
GST_DEBUG ("adding title");
|
|
|
|
clip = GES_CLIP (ges_title_clip_new ());
|
|
g_object_set (G_OBJECT (clip), "duration", GST_SECOND, NULL);
|
|
|
|
ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
|
|
}
|
|
|
|
static void
|
|
app_add_test (App * app)
|
|
{
|
|
GESClip *clip;
|
|
|
|
GST_DEBUG ("adding test");
|
|
|
|
clip = GES_CLIP (ges_test_clip_new ());
|
|
g_object_set (G_OBJECT (clip), "duration", GST_SECOND, NULL);
|
|
|
|
ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
|
|
}
|
|
|
|
static void
|
|
app_add_transition (App * app)
|
|
{
|
|
GESClip *clip;
|
|
|
|
GST_DEBUG ("adding transition");
|
|
|
|
clip = GES_CLIP (ges_transition_clip_new
|
|
(GES_VIDEO_STANDARD_TRANSITION_TYPE_CROSSFADE));
|
|
g_object_set (G_OBJECT (clip), "duration", GST_SECOND, NULL);
|
|
|
|
ges_simple_layer_add_object (GES_SIMPLE_LAYER (app->layer), clip, -1);
|
|
}
|
|
|
|
static void
|
|
app_save_to_uri (App * app, gchar * uri)
|
|
{
|
|
ges_timeline_save_to_uri (app->timeline, uri, NULL, FALSE, NULL);
|
|
}
|
|
|
|
static void
|
|
app_add_audio_track (App * app)
|
|
{
|
|
if (app->audio_tracks)
|
|
return;
|
|
|
|
app->audio_track = GES_TRACK (ges_audio_track_new ());
|
|
ges_timeline_add_track (app->timeline, app->audio_track);
|
|
}
|
|
|
|
static void
|
|
app_remove_audio_track (App * app)
|
|
{
|
|
if (!app->audio_tracks)
|
|
return;
|
|
|
|
ges_timeline_remove_track (app->timeline, app->audio_track);
|
|
app->audio_track = NULL;
|
|
}
|
|
|
|
static void
|
|
app_add_video_track (App * app)
|
|
{
|
|
if (app->video_tracks)
|
|
return;
|
|
|
|
app->video_track = GES_TRACK (ges_video_track_new ());
|
|
ges_timeline_add_track (app->timeline, app->video_track);
|
|
}
|
|
|
|
static void
|
|
app_remove_video_track (App * app)
|
|
{
|
|
if (!app->video_tracks)
|
|
return;
|
|
|
|
ges_timeline_remove_track (app->timeline, app->video_track);
|
|
app->video_track = NULL;
|
|
}
|
|
|
|
static void
|
|
app_dispose (App * app)
|
|
{
|
|
if (app) {
|
|
if (app->pipeline) {
|
|
gst_element_set_state (GST_ELEMENT (app->pipeline), GST_STATE_NULL);
|
|
gst_object_unref (app->pipeline);
|
|
}
|
|
|
|
g_free (app);
|
|
}
|
|
|
|
n_instances--;
|
|
|
|
if (n_instances == 0) {
|
|
gtk_main_quit ();
|
|
}
|
|
}
|
|
|
|
static App *
|
|
app_init (void)
|
|
{
|
|
App *ret;
|
|
ret = g_new0 (App, 1);
|
|
n_instances++;
|
|
|
|
ret->selected_type = G_TYPE_NONE;
|
|
|
|
if (!ret)
|
|
return NULL;
|
|
|
|
if (!(ret->timeline = ges_timeline_new ()))
|
|
goto fail;
|
|
|
|
if (!(ret->pipeline = ges_pipeline_new ()))
|
|
goto fail;
|
|
|
|
if (!ges_pipeline_add_timeline (ret->pipeline, ret->timeline))
|
|
goto fail;
|
|
|
|
if (!(create_ui (ret)))
|
|
goto fail;
|
|
|
|
return ret;
|
|
|
|
fail:
|
|
app_dispose (ret);
|
|
return NULL;
|
|
}
|
|
|
|
static App *
|
|
app_new (void)
|
|
{
|
|
App *ret;
|
|
GESTrack *a = NULL, *v = NULL;
|
|
|
|
ret = app_init ();
|
|
|
|
/* add base audio and video track */
|
|
|
|
if (!(a = GES_TRACK (ges_audio_track_new ())))
|
|
goto fail;
|
|
|
|
if (!(ges_timeline_add_track (ret->timeline, a)))
|
|
goto fail;
|
|
|
|
if (!(v = GES_TRACK (ges_video_track_new ())))
|
|
goto fail;
|
|
|
|
if (!(ges_timeline_add_track (ret->timeline, v)))
|
|
goto fail;
|
|
|
|
if (!(ret->layer = (GESLayer *) ges_simple_layer_new ()))
|
|
goto fail;
|
|
|
|
if (!(ges_timeline_add_layer (ret->timeline, ret->layer)))
|
|
goto fail;
|
|
|
|
ret->audio_track = a;
|
|
ret->video_track = v;
|
|
return ret;
|
|
|
|
fail:
|
|
|
|
if (a)
|
|
gst_object_unref (a);
|
|
if (v)
|
|
gst_object_unref (v);
|
|
app_dispose (ret);
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
load_file_async (App * app)
|
|
{
|
|
ges_timeline_load_from_uri (app->timeline, app->pending_uri, NULL);
|
|
|
|
g_free (app->pending_uri);
|
|
app->pending_uri = NULL;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
app_new_from_uri (gchar * uri)
|
|
{
|
|
App *ret;
|
|
|
|
ret = app_init ();
|
|
ret->pending_uri = g_strdup (uri);
|
|
g_idle_add ((GSourceFunc) load_file_async, ret);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* UI callbacks ************************************************************/
|
|
|
|
gboolean
|
|
window_delete_event_cb (GtkObject * window, GdkEvent * event, App * app)
|
|
{
|
|
app_dispose (app);
|
|
return FALSE;
|
|
}
|
|
|
|
void
|
|
new_activate_cb (GtkMenuItem * item, App * app)
|
|
{
|
|
app_new ();
|
|
}
|
|
|
|
void
|
|
launch_project_activate_cb (GtkMenuItem * item, App * app)
|
|
{
|
|
GtkFileChooserDialog *dlg;
|
|
GtkFileFilter *filter;
|
|
|
|
GST_DEBUG ("add file signal handler");
|
|
|
|
filter = gtk_file_filter_new ();
|
|
gtk_file_filter_set_name (filter, "pitivi projects");
|
|
gtk_file_filter_add_pattern (filter, "*.xptv");
|
|
dlg = (GtkFileChooserDialog *)
|
|
gtk_file_chooser_dialog_new ("Preview Project...",
|
|
GTK_WINDOW (app->main_window),
|
|
GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
GTK_STOCK_CANCEL,
|
|
GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
|
|
gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (dlg), filter);
|
|
|
|
g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
|
|
|
|
if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
|
|
gchar *uri;
|
|
uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
|
|
gtk_widget_destroy ((GtkWidget *) dlg);
|
|
app_launch_project (app, uri);
|
|
}
|
|
}
|
|
|
|
void
|
|
open_activate_cb (GtkMenuItem * item, App * app)
|
|
{
|
|
GtkFileChooserDialog *dlg;
|
|
|
|
GST_DEBUG ("add file signal handler");
|
|
|
|
dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Open Project...",
|
|
GTK_WINDOW (app->main_window),
|
|
GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
GTK_STOCK_CANCEL,
|
|
GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
|
|
|
|
g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
|
|
|
|
if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
|
|
gchar *uri;
|
|
uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
|
|
app_new_from_uri (uri);
|
|
g_free (uri);
|
|
}
|
|
gtk_widget_destroy ((GtkWidget *) dlg);
|
|
}
|
|
|
|
void
|
|
save_as_activate_cb (GtkMenuItem * item, App * app)
|
|
{
|
|
GtkFileChooserDialog *dlg;
|
|
|
|
GST_DEBUG ("save as signal handler");
|
|
|
|
dlg = (GtkFileChooserDialog *)
|
|
gtk_file_chooser_dialog_new ("Save project as...",
|
|
GTK_WINDOW (app->main_window), GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_OK,
|
|
NULL);
|
|
|
|
g_object_set (G_OBJECT (dlg), "select-multiple", FALSE, NULL);
|
|
|
|
if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
|
|
gchar *uri;
|
|
uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (dlg));
|
|
app_save_to_uri (app, uri);
|
|
g_free (uri);
|
|
}
|
|
gtk_widget_destroy ((GtkWidget *) dlg);
|
|
}
|
|
|
|
void
|
|
quit_item_activate_cb (GtkMenuItem * item, App * app)
|
|
{
|
|
gtk_main_quit ();
|
|
}
|
|
|
|
void
|
|
delete_activate_cb (GtkAction * item, App * app)
|
|
{
|
|
/* get a gslist of selected track elements */
|
|
GList *objects = NULL;
|
|
|
|
objects = app_get_selected_objects (app);
|
|
app_delete_objects (app, objects);
|
|
}
|
|
|
|
void
|
|
add_effect_activate_cb (GtkAction * item, App * app)
|
|
{
|
|
gtk_widget_show_all (app->add_effect_dlg);
|
|
}
|
|
|
|
void
|
|
add_file_activate_cb (GtkAction * item, App * app)
|
|
{
|
|
GtkFileChooserDialog *dlg;
|
|
|
|
GST_DEBUG ("add file signal handler");
|
|
|
|
dlg = (GtkFileChooserDialog *) gtk_file_chooser_dialog_new ("Add File...",
|
|
GTK_WINDOW (app->main_window),
|
|
GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
GTK_STOCK_CANCEL,
|
|
GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
|
|
|
|
g_object_set (G_OBJECT (dlg), "select-multiple", TRUE, NULL);
|
|
|
|
if (gtk_dialog_run ((GtkDialog *) dlg) == GTK_RESPONSE_OK) {
|
|
GSList *uris;
|
|
GSList *cur;
|
|
uris = gtk_file_chooser_get_uris (GTK_FILE_CHOOSER (dlg));
|
|
for (cur = uris; cur; cur = cur->next)
|
|
app_add_file (app, cur->data);
|
|
g_slist_free (uris);
|
|
}
|
|
gtk_widget_destroy ((GtkWidget *) dlg);
|
|
}
|
|
|
|
void
|
|
add_text_activate_cb (GtkAction * item, App * app)
|
|
{
|
|
app_add_title (app);
|
|
}
|
|
|
|
void
|
|
add_test_activate_cb (GtkAction * item, App * app)
|
|
{
|
|
app_add_test (app);
|
|
}
|
|
|
|
void
|
|
add_transition_activate_cb (GtkAction * item, App * app)
|
|
{
|
|
app_add_transition (app);
|
|
}
|
|
|
|
void
|
|
play_activate_cb (GtkAction * item, App * app)
|
|
{
|
|
app_toggle_playpause (app);
|
|
}
|
|
|
|
void
|
|
stop_activate_cb (GtkAction * item, App * app)
|
|
{
|
|
app_stop_playback (app);
|
|
}
|
|
|
|
void
|
|
move_up_activate_cb (GtkAction * item, App * app)
|
|
{
|
|
app_move_selected_up (app);
|
|
}
|
|
|
|
void
|
|
move_down_activate_cb (GtkAction * item, App * app)
|
|
{
|
|
app_move_selected_down (app);
|
|
}
|
|
|
|
void
|
|
audio_track_activate_cb (GtkToggleAction * item, App * app)
|
|
{
|
|
if (gtk_toggle_action_get_active (item)) {
|
|
app_add_audio_track (app);
|
|
} else {
|
|
app_remove_audio_track (app);
|
|
}
|
|
}
|
|
|
|
void
|
|
video_track_activate_cb (GtkToggleAction * item, App * app)
|
|
{
|
|
if (gtk_toggle_action_get_active (item)) {
|
|
app_add_video_track (app);
|
|
} else {
|
|
app_remove_video_track (app);
|
|
}
|
|
}
|
|
|
|
void
|
|
app_selection_changed_cb (GtkTreeSelection * selection, App * app)
|
|
{
|
|
app_update_selection (app);
|
|
|
|
update_delete_sensitivity (app);
|
|
update_effect_sensitivity (app);
|
|
update_add_transition_sensitivity (app);
|
|
update_move_up_down_sensitivity (app);
|
|
|
|
gtk_widget_set_visible (app->properties, app->n_selected > 0);
|
|
|
|
gtk_widget_set_visible (app->filesource_properties,
|
|
app->selected_type == GES_TYPE_URI_CLIP);
|
|
|
|
gtk_widget_set_visible (app->text_properties,
|
|
app->selected_type == GES_TYPE_TITLE_CLIP);
|
|
|
|
gtk_widget_set_visible (app->generic_duration,
|
|
app->selected_type != G_TYPE_NONE &&
|
|
app->selected_type != G_TYPE_INVALID);
|
|
|
|
gtk_widget_set_visible (app->background_properties,
|
|
app->selected_type == GES_TYPE_TEST_CLIP);
|
|
}
|
|
|
|
gboolean
|
|
duration_scale_change_value_cb (GtkRange * range, GtkScrollType unused,
|
|
gdouble value, App * app)
|
|
{
|
|
GList *i;
|
|
|
|
for (i = app->selected_objects; i; i = i->next) {
|
|
guint64 duration, maxduration;
|
|
maxduration = GES_TIMELINE_ELEMENT_MAX_DURATION (i->data);
|
|
duration = (value < maxduration ? (value > 0 ? value : 0) : maxduration);
|
|
g_object_set (G_OBJECT (i->data), "duration", (guint64) duration, NULL);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
in_point_scale_change_value_cb (GtkRange * range, GtkScrollType unused,
|
|
gdouble value, App * app)
|
|
{
|
|
GList *i;
|
|
|
|
for (i = app->selected_objects; i; i = i->next) {
|
|
guint64 in_point, maxduration;
|
|
maxduration = GES_TIMELINE_ELEMENT_MAX_DURATION (i->data) -
|
|
GES_TIMELINE_ELEMENT_DURATION (i->data);
|
|
in_point = (value < maxduration ? (value > 0 ? value : 0) : maxduration);
|
|
g_object_set (G_OBJECT (i->data), "in-point", (guint64) in_point, NULL);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
halign_changed_cb (GtkComboBox * widget, App * app)
|
|
{
|
|
GList *tmp;
|
|
int active;
|
|
|
|
if (app->ignore_input)
|
|
return;
|
|
|
|
active = gtk_combo_box_get_active (app->halign);
|
|
|
|
for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
|
|
g_object_set (G_OBJECT (tmp->data), "halignment", active, NULL);
|
|
}
|
|
}
|
|
|
|
void
|
|
valign_changed_cb (GtkComboBox * widget, App * app)
|
|
{
|
|
GList *tmp;
|
|
int active;
|
|
|
|
if (app->ignore_input)
|
|
return;
|
|
|
|
active = gtk_combo_box_get_active (app->valign);
|
|
|
|
for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
|
|
g_object_set (G_OBJECT (tmp->data), "valignment", active, NULL);
|
|
}
|
|
}
|
|
|
|
void
|
|
background_type_changed_cb (GtkComboBox * widget, App * app)
|
|
{
|
|
GList *tmp;
|
|
gint p;
|
|
|
|
if (app->ignore_input)
|
|
return;
|
|
|
|
p = gtk_combo_box_get_active (widget);
|
|
|
|
for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
|
|
g_object_set (G_OBJECT (tmp->data), "vpattern", (gint) p, NULL);
|
|
}
|
|
}
|
|
|
|
void
|
|
frequency_value_changed_cb (GtkSpinButton * widget, App * app)
|
|
{
|
|
GList *tmp;
|
|
gdouble value;
|
|
|
|
if (app->ignore_input)
|
|
return;
|
|
|
|
value = gtk_spin_button_get_value (widget);
|
|
|
|
for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
|
|
g_object_set (G_OBJECT (tmp->data), "freq", (gdouble) value, NULL);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
volume_change_value_cb (GtkRange * widget, GtkScrollType unused, gdouble
|
|
value, App * app)
|
|
{
|
|
GList *tmp;
|
|
|
|
value = value >= 0 ? (value <= 2.0 ? value : 2.0) : 0;
|
|
|
|
for (tmp = app->selected_objects; tmp; tmp = tmp->next) {
|
|
g_object_set (G_OBJECT (tmp->data), "volume", (gdouble) value, NULL);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/* main *********************************************************************/
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
App *app;
|
|
|
|
/* intialize GStreamer and GES */
|
|
gst_init (&argc, &argv);
|
|
ges_init ();
|
|
|
|
/* initialize UI */
|
|
gtk_init (&argc, &argv);
|
|
|
|
if ((app = app_new ())) {
|
|
gtk_main ();
|
|
}
|
|
|
|
return 0;
|
|
}
|