diff --git a/gst-libs/gst/Makefile.am b/gst-libs/gst/Makefile.am index f7bb92e462..0634d154c3 100644 --- a/gst-libs/gst/Makefile.am +++ b/gst-libs/gst/Makefile.am @@ -11,7 +11,7 @@ SUBDIRS = \ app \ allocators -noinst_HEADERS = gettext.h gst-i18n-plugin.h glib-compat-private.h +noinst_HEADERS = gettext.h gst-i18n-app.h gst-i18n-plugin.h glib-compat-private.h # dependencies: audio: tag diff --git a/gst-libs/gst/gst-i18n-app.h b/gst-libs/gst/gst-i18n-app.h new file mode 100644 index 0000000000..56140e5ab3 --- /dev/null +++ b/gst-libs/gst/gst-i18n-app.h @@ -0,0 +1,44 @@ +/* GStreamer + * Copyright (C) 2004 Thomas Vander Stichele + * + * gst-i18n-app.h: internationalization macros for the GStreamer tools + * + * 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. + */ + + +#ifndef __GST_I18N_APP_H__ +#define __GST_I18N_APP_H__ + +#ifdef ENABLE_NLS + +#include /* some people need it and some people don't */ +#include "gettext.h" /* included with gettext distribution and copied */ + +/* we want to use shorthand _() for translating and N_() for marking */ +#define _(String) gettext (String) +#define N_(String) gettext_noop (String) +/* FIXME: if we need it, we can add Q_ as well, like in glib */ + +#else + +#define _(String) String +#define N_(String) String +#define ngettext(Singular,Plural,Count) ((Count>1)?Plural:Singular) + +#endif + +#endif /* __GST_I18N_APP_H__ */ diff --git a/tools/.gitignore b/tools/.gitignore index b6276e635b..38a1d257ba 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -1 +1,2 @@ -gst-discoverer-* +gst-discoverer-?.0 +gst-play-?.0 diff --git a/tools/Makefile.am b/tools/Makefile.am index b4745ea4c0..37e6d03d2d 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -2,10 +2,13 @@ man_MANS = \ gst-discoverer-@GST_API_VERSION@.1 bin_PROGRAMS = \ - gst-discoverer-@GST_API_VERSION@ + gst-discoverer-@GST_API_VERSION@ \ + gst-play-@GST_API_VERSION@ gst_discoverer_@GST_API_VERSION@_SOURCES = gst-discoverer.c +gst_play_@GST_API_VERSION@_SOURCES = gst-play.c + CLEANFILES = $(bin_SCRIPTS) $(bin_PROGRAMS) EXTRA_DIST = gst-discoverer-1.0.1 diff --git a/tools/gst-play.c b/tools/gst-play.c new file mode 100644 index 0000000000..317d3ab43b --- /dev/null +++ b/tools/gst-play.c @@ -0,0 +1,466 @@ +/* GStreamer command line playback testing utility + * + * Copyright (C) 2013 Tim-Philipp Müller + * Copyright (C) 2013 Collabora Ltd. + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include + +GST_DEBUG_CATEGORY (play_debug); +#define GST_CAT_DEFAULT play_debug + +typedef struct +{ + gchar **uris; + guint num_uris; + gint cur_idx; + + GstElement *playbin; + + GMainLoop *loop; + guint bus_watch; + guint timeout; + + /* missing plugin messages */ + GList *missing; + + gboolean buffering; + gboolean is_live; +} GstPlay; + +static gboolean play_bus_msg (GstBus * bus, GstMessage * msg, gpointer data); +static gboolean play_next (GstPlay * play); +static gboolean play_timeout (gpointer user_data); +static void play_reset (GstPlay * play); + +static GstPlay * +play_new (gchar ** uris, const gchar * audio_sink, const gchar * video_sink) +{ + GstElement *sink; + GstPlay *play; + + play = g_new0 (GstPlay, 1); + + play->uris = uris; + play->num_uris = g_strv_length (uris); + play->cur_idx = -1; + + play->playbin = gst_element_factory_make ("playbin", "playbin"); + + if (audio_sink != NULL) { + sink = gst_element_factory_make (audio_sink, NULL); + if (sink != NULL) + g_object_set (play->playbin, "audio-sink", sink, NULL); + else + g_warning ("Couldn't create specified audio sink '%s'", audio_sink); + } + if (video_sink != NULL) { + sink = gst_element_factory_make (video_sink, NULL); + if (sink != NULL) + g_object_set (play->playbin, "video-sink", sink, NULL); + else + g_warning ("Couldn't create specified video sink '%s'", video_sink); + } + + play->loop = g_main_loop_new (NULL, FALSE); + + play->bus_watch = gst_bus_add_watch (GST_ELEMENT_BUS (play->playbin), + play_bus_msg, play); + + /* FIXME: make configurable incl. 0 for disable */ + play->timeout = g_timeout_add (100, play_timeout, play); + + play->missing = NULL; + play->buffering = FALSE; + play->is_live = FALSE; + + return play; +} + +static void +play_free (GstPlay * play) +{ + play_reset (play); + + gst_element_set_state (play->playbin, GST_STATE_NULL); + gst_object_unref (play->playbin); + + g_source_remove (play->bus_watch); + g_source_remove (play->timeout); + g_main_loop_unref (play->loop); + + g_strfreev (play->uris); + g_free (play); +} + +/* reset for new file/stream */ +static void +play_reset (GstPlay * play) +{ + g_list_foreach (play->missing, (GFunc) gst_message_unref, NULL); + play->missing = NULL; + + play->buffering = FALSE; + play->is_live = FALSE; +} + +/* returns TRUE if something was installed and we should restart playback */ +static gboolean +play_install_missing_plugins (GstPlay * play) +{ + /* FIXME: implement: try to install any missing plugins we haven't + * tried to install before */ + return FALSE; +} + +static gboolean +play_bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data) +{ + GstPlay *play = user_data; + + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ASYNC_DONE: + g_print ("Prerolled.\r"); + if (play->missing != NULL && play_install_missing_plugins (play)) { + g_print ("New plugins installed, trying again...\n"); + --play->cur_idx; + play_next (play); + } + break; + case GST_MESSAGE_BUFFERING:{ + gint percent; + + if (!play->buffering) + g_print ("\n"); + + gst_message_parse_buffering (msg, &percent); + g_print ("%s %d%% \r", _("Buffering..."), percent); + + /* no state management needed for live pipelines */ + if (play->is_live) + break; + + if (percent == 100) { + /* a 100% message means buffering is done */ + if (play->buffering) { + play->buffering = FALSE; + gst_element_set_state (play->playbin, GST_STATE_PLAYING); + } + } else { + /* buffering... */ + if (!play->buffering) { + gst_element_set_state (play->playbin, GST_STATE_PAUSED); + play->buffering = TRUE; + } + } + break; + } + case GST_MESSAGE_LATENCY: + g_print ("Redistribute latency...\n"); + gst_bin_recalculate_latency (GST_BIN (play->playbin)); + break; + case GST_MESSAGE_REQUEST_STATE:{ + GstState state; + gchar *name; + + name = gst_object_get_path_string (GST_MESSAGE_SRC (msg)); + + gst_message_parse_request_state (msg, &state); + + g_print ("Setting state to %s as requested by %s...\n", + gst_element_state_get_name (state), name); + + gst_element_set_state (play->playbin, state); + g_free (name); + break; + } + case GST_MESSAGE_EOS: + /* print final position at end */ + play_timeout (play); + g_print ("\n"); + /* and switch to next item in list */ + if (!play_next (play)) { + g_print ("Reached end of play list.\n"); + g_main_loop_quit (play->loop); + } + break; + case GST_MESSAGE_WARNING:{ + GError *err; + gchar *dbg = NULL; + + gst_message_parse_warning (msg, &err, &dbg); + g_printerr ("WARNING %s\n", err->message); + if (dbg != NULL) + g_printerr ("WARNING debug information: %s\n", dbg); + g_error_free (err); + g_free (dbg); + break; + } + case GST_MESSAGE_ERROR:{ + GError *err; + gchar *dbg; + + gst_message_parse_error (msg, &err, &dbg); + g_printerr ("ERROR %s for %s\n", err->message, play->uris[play->cur_idx]); + if (dbg != NULL) + g_printerr ("ERROR debug information: %s\n", dbg); + g_error_free (err); + g_free (dbg); + + if (play->missing != NULL && play_install_missing_plugins (play)) { + g_print ("New plugins installed, trying again...\n"); + --play->cur_idx; + play_next (play); + break; + } + /* try next item in list then */ + if (!play_next (play)) { + g_print ("Reached end of play list.\n"); + g_main_loop_quit (play->loop); + } + break; + } + default: + if (gst_is_missing_plugin_message (msg)) { + gchar *desc; + + desc = gst_missing_plugin_message_get_description (msg); + g_print ("Missing plugin: %s\n", desc); + g_free (desc); + play->missing = g_list_append (play->missing, gst_message_ref (msg)); + } + break; + } + + return TRUE; +} + +static gboolean +play_timeout (gpointer user_data) +{ + GstPlay *play = user_data; + gint64 pos = -1, dur = -1; + + if (play->buffering) + return TRUE; + + gst_element_query_position (play->playbin, GST_FORMAT_TIME, &pos); + gst_element_query_duration (play->playbin, GST_FORMAT_TIME, &dur); + + if (pos >= 0 && dur > 0) { + gchar dstr[32], pstr[32]; + + /* FIXME: pretty print in nicer format */ + g_snprintf (pstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (pos)); + pstr[9] = '\0'; + g_snprintf (dstr, 32, "%" GST_TIME_FORMAT, GST_TIME_ARGS (dur)); + dstr[9] = '\0'; + g_print ("%s / %s\r", pstr, dstr); + } + + return TRUE; +} + +/* returns FALSE if we have reached the end of the playlist */ +static gboolean +play_next (GstPlay * play) +{ + GstStateChangeReturn sret; + const gchar *next_uri; + gchar *loc; + + if (++play->cur_idx >= play->num_uris) + return FALSE; + + gst_element_set_state (play->playbin, GST_STATE_READY); + play_reset (play); + + next_uri = play->uris[play->cur_idx]; + if (gst_uri_has_protocol (next_uri, "file")) { + loc = g_filename_from_uri (next_uri, NULL, NULL); + } else if (gst_uri_has_protocol (next_uri, "pushfile")) { + loc = g_filename_from_uri (next_uri + 4, NULL, NULL); + } else { + loc = g_strdup (next_uri); + } + g_print ("Now playing %s\n", loc); + g_free (loc); + + g_object_set (play->playbin, "uri", next_uri, NULL); + + sret = gst_element_set_state (play->playbin, GST_STATE_PLAYING); + switch (sret) { + case GST_STATE_CHANGE_FAILURE: + /* ignore, we should get an error message posted on the bus */ + break; + case GST_STATE_CHANGE_NO_PREROLL: + g_print ("Pipeline is live.\n"); + play->is_live = TRUE; + break; + case GST_STATE_CHANGE_ASYNC: + g_print ("Prerolling...\r"); + break; + default: + break; + } + + return TRUE; +} + +static void +do_play (GstPlay * play) +{ + gint i; + + /* dump playlist */ + for (i = 0; i < play->num_uris; ++i) + GST_INFO ("%4u : %s", i, play->uris[i]); + + if (!play_next (play)) + return; + + g_main_loop_run (play->loop); +} + +static void +add_to_playlist (GPtrArray * playlist, const gchar * filename) +{ + GDir *dir; + gchar *uri; + + if (gst_uri_is_valid (filename)) { + g_ptr_array_add (playlist, g_strdup (filename)); + return; + } + + if ((dir = g_dir_open (filename, 0, NULL))) { + const gchar *entry; + + /* FIXME: sort entries for each directory? */ + while ((entry = g_dir_read_name (dir))) { + gchar *path; + + path = g_strconcat (filename, G_DIR_SEPARATOR_S, entry, NULL); + add_to_playlist (playlist, path); + g_free (path); + } + + g_dir_close (dir); + return; + } + + uri = gst_filename_to_uri (filename, NULL); + if (uri != NULL) + g_ptr_array_add (playlist, uri); + else + g_warning ("Could not make URI out of filename '%s'", filename); +} + +int +main (int argc, char **argv) +{ + GstPlay *play; + GPtrArray *playlist; + gboolean print_version = FALSE; + gchar **filenames = NULL; + gchar *audio_sink = NULL; + gchar *video_sink = NULL; + gchar **uris; + guint num, i; + GError *err = NULL; + GOptionContext *ctx; + GOptionEntry options[] = { + {"version", 0, 0, G_OPTION_ARG_NONE, &print_version, + N_("Print version information and exit"), NULL}, + {"videosink", 0, 0, G_OPTION_ARG_STRING, &video_sink, + N_("Video sink to use (default is autovideosink)"), NULL}, + {"audiosink", 0, 0, G_OPTION_ARG_STRING, &audio_sink, + N_("Audio sink to use (default is autoaudiosink)"), NULL}, + {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL}, + {NULL} + }; + +#ifdef ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); +#endif + + g_set_prgname ("gst-play-" GST_API_VERSION); + + ctx = g_option_context_new ("FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ..."); + g_option_context_add_main_entries (ctx, options, GETTEXT_PACKAGE); + g_option_context_add_group (ctx, gst_init_get_option_group ()); + if (!g_option_context_parse (ctx, &argc, &argv, &err)) { + g_print ("Error initializing: %s\n", GST_STR_NULL (err->message)); + return 1; + } + g_option_context_free (ctx); + + GST_DEBUG_CATEGORY_INIT (play_debug, "play", 0, "gst-play"); + + if (print_version) { + gchar *version_str; + + version_str = gst_version_string (); + g_print ("%s version %s\n", g_get_prgname (), PACKAGE_VERSION); + g_print ("%s\n", version_str); + g_print ("%s\n", GST_PACKAGE_ORIGIN); + g_free (version_str); + return 0; + } + + if (filenames == NULL || *filenames == NULL) { + g_printerr (_("Usage: %s FILE1|URI1 [FILE2|URI2] [FILE3|URI3] ..."), + "gst-play-" GST_API_VERSION); + g_printerr ("\n\n"), + g_printerr ("%s\n\n", + _("You must provide at least one filename or URI to play.")); + return 1; + } + + playlist = g_ptr_array_new (); + + /* fill playlist */ + num = g_strv_length (filenames); + for (i = 0; i < num; ++i) { + GST_LOG ("command line argument: %s", filenames[i]); + add_to_playlist (playlist, filenames[i]); + } + g_strfreev (filenames); + + g_ptr_array_add (playlist, NULL); + + /* play */ + uris = (gchar **) g_ptr_array_free (playlist, FALSE); + play = play_new (uris, audio_sink, video_sink); + + do_play (play); + + /* clean up */ + play_free (play); + + return 0; +}