mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-03 13:02:29 +00:00
validate: Implement frame by frame writing in the media descriptor writer
+ Add an option to fully parse media files in the gst-validate-media-check tool
This commit is contained in:
parent
ba38d09961
commit
fbcee57902
4 changed files with 272 additions and 139 deletions
|
@ -20,6 +20,7 @@
|
|||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include <gst/validate/validate.h>
|
||||
#include "media-descriptor-writer.h"
|
||||
#include <string.h>
|
||||
|
||||
|
@ -47,13 +48,22 @@ enum
|
|||
|
||||
struct _GstMediaDescriptorWriterPrivate
|
||||
{
|
||||
GList *serialized_string;
|
||||
guint stream_id;
|
||||
GstElement *pipeline;
|
||||
GstCaps *raw_caps;
|
||||
GMainLoop *loop;
|
||||
|
||||
GList *parsers;
|
||||
};
|
||||
|
||||
static void
|
||||
finalize (GstMediaDescriptorWriter * writer)
|
||||
{
|
||||
if (writer->priv->raw_caps)
|
||||
gst_caps_unref (writer->priv->raw_caps);
|
||||
|
||||
if (writer->priv->parsers)
|
||||
gst_plugin_feature_list_free (writer->priv->parsers);
|
||||
|
||||
G_OBJECT_CLASS (gst_media_descriptor_writer_parent_class)->
|
||||
finalize (G_OBJECT (writer));
|
||||
}
|
||||
|
@ -84,11 +94,13 @@ gst_media_descriptor_writer_init (GstMediaDescriptorWriter * writer)
|
|||
{
|
||||
GstMediaDescriptorWriterPrivate *priv;
|
||||
|
||||
|
||||
writer->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (writer,
|
||||
GST_TYPE_MEDIA_DESCRIPTOR_WRITER, GstMediaDescriptorWriterPrivate);
|
||||
|
||||
priv->serialized_string = NULL;
|
||||
priv->stream_id = 0;
|
||||
writer->priv->parsers =
|
||||
gst_element_factory_list_get_elements (GST_ELEMENT_FACTORY_TYPE_PARSER,
|
||||
GST_RANK_MARGINAL);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -249,16 +261,245 @@ gst_media_descriptor_writer_add_stream (GstMediaDescriptorWriter * writer,
|
|||
gst_discoverer_stream_info_get_tags (info));
|
||||
}
|
||||
|
||||
if (caps != NULL)
|
||||
gst_caps_unref (caps);
|
||||
if (writer->priv->raw_caps == NULL)
|
||||
writer->priv->raw_caps = gst_caps_copy (caps);
|
||||
else {
|
||||
writer->priv->raw_caps = gst_caps_merge (writer->priv->raw_caps,
|
||||
gst_caps_copy (caps));
|
||||
}
|
||||
gst_caps_unref (caps);
|
||||
g_free (capsstr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static GstPadProbeReturn
|
||||
_uridecodebin_probe (GstPad * pad, GstPadProbeInfo * info, GstMediaDescriptorWriter *writer)
|
||||
{
|
||||
gst_media_descriptor_writer_add_frame (writer, pad, info->data);
|
||||
|
||||
return GST_PAD_PROBE_OK;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_find_stream_id (GstPad *pad, GstEvent **event, GstMediaDescriptorWriter *writer)
|
||||
{
|
||||
if (GST_EVENT_TYPE (*event) == GST_EVENT_STREAM_START) {
|
||||
GList *tmp;
|
||||
StreamNode *snode = NULL;
|
||||
const gchar *stream_id;
|
||||
|
||||
gst_event_parse_stream_start (*event, &stream_id);
|
||||
for (tmp = ((GstMediaDescriptor *) writer)->filenode->streams; tmp;
|
||||
tmp = tmp->next) {
|
||||
if (g_strcmp0 (((StreamNode *) tmp->data)->id, stream_id) == 0) {
|
||||
snode = tmp->data;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!snode || snode->pad) {
|
||||
GST_VALIDATE_REPORT (writer, FILE_CHECK_FAILURE,
|
||||
"Got pad %s:%s where Discoverer found no stream ID",
|
||||
GST_DEBUG_PAD_NAME (pad));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
snode->pad = gst_object_ref (pad);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static inline GstElement *
|
||||
_get_parser (GstMediaDescriptorWriter *writer, GstPad *pad)
|
||||
{
|
||||
GList *parsers1, *parsers;
|
||||
GstElement *parser = NULL;
|
||||
GstElementFactory *parserfact = NULL;
|
||||
GstCaps *format;
|
||||
|
||||
format = gst_pad_get_current_caps (pad);
|
||||
|
||||
GST_DEBUG ("Getting list of parsers for format %" GST_PTR_FORMAT, format);
|
||||
parsers1 =
|
||||
gst_element_factory_list_filter (writer->priv->parsers, format,
|
||||
GST_PAD_SRC, FALSE);
|
||||
parsers =
|
||||
gst_element_factory_list_filter (parsers1, format, GST_PAD_SINK, FALSE);
|
||||
gst_plugin_feature_list_free (parsers1);
|
||||
|
||||
if (G_UNLIKELY (parsers == NULL)) {
|
||||
GST_DEBUG ("Couldn't find any compatible parsers");
|
||||
goto beach;
|
||||
}
|
||||
|
||||
/* Just pick the first one */
|
||||
parserfact = parsers->data;
|
||||
if (parserfact)
|
||||
parser = gst_element_factory_create (parserfact, NULL);
|
||||
|
||||
gst_plugin_feature_list_free (parsers);
|
||||
|
||||
beach:
|
||||
if (format)
|
||||
gst_caps_unref (format);
|
||||
|
||||
return parser;
|
||||
}
|
||||
|
||||
static void
|
||||
pad_added_cb (GstElement * decodebin, GstPad * pad, GstMediaDescriptorWriter *writer)
|
||||
{
|
||||
GList *tmp;
|
||||
StreamNode *snode = NULL;
|
||||
GstPad *sinkpad, *srcpad;
|
||||
|
||||
/* Try to plug a parser so we have as much info as possible
|
||||
* about the encoded stream. */
|
||||
GstElement *parser = _get_parser (writer, pad);
|
||||
GstElement *fakesink = gst_element_factory_make ("fakesink", NULL);
|
||||
|
||||
if (parser) {
|
||||
sinkpad = gst_element_get_static_pad (parser, "sink");
|
||||
gst_bin_add (GST_BIN (writer->priv->pipeline), parser);
|
||||
gst_element_sync_state_with_parent (parser);
|
||||
gst_pad_link (pad, sinkpad);
|
||||
|
||||
srcpad = gst_element_get_static_pad (parser, "src");
|
||||
} else {
|
||||
srcpad = pad;
|
||||
}
|
||||
|
||||
sinkpad = gst_element_get_static_pad (fakesink, "sink");
|
||||
gst_bin_add (GST_BIN (writer->priv->pipeline), fakesink);
|
||||
gst_element_sync_state_with_parent (fakesink);
|
||||
gst_pad_link (srcpad, sinkpad);
|
||||
gst_pad_sticky_events_foreach (pad, (GstPadStickyEventsForeachFunction) _find_stream_id,
|
||||
writer);
|
||||
|
||||
for (tmp = ((GstMediaDescriptor *) writer)->filenode->streams; tmp; tmp = tmp->next) {
|
||||
snode = tmp->data;
|
||||
if (snode->pad == pad && srcpad != pad) {
|
||||
gst_object_unref (pad);
|
||||
snode->pad = gst_object_ref (srcpad);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_BUFFER,
|
||||
(GstPadProbeCallback) _uridecodebin_probe, writer, NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
bus_callback (GstBus * bus, GstMessage * message, GstMediaDescriptorWriter *writer)
|
||||
{
|
||||
GMainLoop *loop = writer->priv->loop;
|
||||
|
||||
switch (GST_MESSAGE_TYPE (message)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (writer->priv->pipeline),
|
||||
GST_DEBUG_GRAPH_SHOW_ALL, "gst-validate-media-check.error");
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_EOS:
|
||||
GST_INFO ("Got EOS!");
|
||||
g_main_loop_quit (loop);
|
||||
break;
|
||||
case GST_MESSAGE_STATE_CHANGED:
|
||||
if (GST_MESSAGE_SRC (message) == GST_OBJECT (writer->priv->pipeline)) {
|
||||
GstState oldstate, newstate, pending;
|
||||
|
||||
gst_message_parse_state_changed (message, &oldstate, &newstate,
|
||||
&pending);
|
||||
|
||||
GST_DEBUG ("State changed (old: %s, new: %s, pending: %s)",
|
||||
gst_element_state_get_name (oldstate),
|
||||
gst_element_state_get_name (newstate),
|
||||
gst_element_state_get_name (pending));
|
||||
|
||||
if (newstate == GST_STATE_PLAYING) {
|
||||
GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (writer->priv->pipeline),
|
||||
GST_DEBUG_GRAPH_SHOW_ALL,
|
||||
"gst-validate-media-descriptor-writer.playing");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case GST_MESSAGE_BUFFERING:{
|
||||
gint percent;
|
||||
|
||||
gst_message_parse_buffering (message, &percent);
|
||||
g_print ("%s %d%% \r", "Buffering...", percent);
|
||||
|
||||
/* no state management needed for live pipelines */
|
||||
if (percent == 100) {
|
||||
gst_element_set_state (writer->priv->pipeline, GST_STATE_PLAYING);
|
||||
} else {
|
||||
gst_element_set_state (writer->priv->pipeline, GST_STATE_PAUSED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_run_frame_analisis (GstMediaDescriptorWriter *writer, GstValidateRunner *runner,
|
||||
const gchar *uri)
|
||||
{
|
||||
GstBus *bus;
|
||||
GstStateChangeReturn sret;
|
||||
GstValidateMonitor *monitor;
|
||||
|
||||
GstElement *uridecodebin = gst_element_factory_make ("uridecodebin", NULL);
|
||||
|
||||
writer->priv->pipeline = gst_pipeline_new ("frame-analisis");
|
||||
|
||||
monitor = gst_validate_monitor_factory_create (
|
||||
GST_OBJECT_CAST (writer->priv->pipeline), runner, NULL);
|
||||
gst_validate_reporter_set_handle_g_logs (GST_VALIDATE_REPORTER (monitor));
|
||||
|
||||
g_object_set (uridecodebin, "uri", uri, "caps", writer->priv->raw_caps, NULL);
|
||||
g_signal_connect (uridecodebin, "pad-added", G_CALLBACK (pad_added_cb), writer);
|
||||
gst_bin_add (GST_BIN (writer->priv->pipeline), uridecodebin);
|
||||
|
||||
writer->priv->loop = g_main_loop_new (NULL, FALSE);
|
||||
bus = gst_element_get_bus (writer->priv->pipeline);
|
||||
gst_bus_add_signal_watch (bus);
|
||||
g_signal_connect (bus, "message", (GCallback) bus_callback, writer);
|
||||
sret = gst_element_set_state (writer->priv->pipeline, GST_STATE_PLAYING);
|
||||
switch (sret) {
|
||||
case GST_STATE_CHANGE_FAILURE:
|
||||
/* ignore, we should get an error message posted on the bus */
|
||||
g_print ("Pipeline failed to go to PLAYING state\n");
|
||||
return FALSE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
g_main_loop_run (writer->priv->loop);
|
||||
sret = gst_element_set_state (writer->priv->pipeline, GST_STATE_NULL);
|
||||
gst_object_unref (writer->priv->pipeline);
|
||||
writer->priv->pipeline = NULL;
|
||||
g_main_loop_unref (writer->priv->loop);
|
||||
writer->priv->loop = NULL;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GstMediaDescriptorWriter *
|
||||
gst_media_descriptor_writer_new_discover (GstValidateRunner * runner,
|
||||
const gchar * uri, GError ** err)
|
||||
const gchar * uri, gboolean full, GError ** err)
|
||||
{
|
||||
GList *tmp, *streams;
|
||||
GstDiscovererInfo *info;
|
||||
|
@ -299,11 +540,18 @@ gst_media_descriptor_writer_new_discover (GstValidateRunner * runner,
|
|||
(streaminfo));
|
||||
|
||||
streams = gst_discoverer_info_get_stream_list (info);
|
||||
for (tmp = streams; tmp; tmp = tmp->next)
|
||||
for (tmp = streams; tmp; tmp = tmp->next) {
|
||||
gst_media_descriptor_writer_add_stream (writer, tmp->data);
|
||||
}
|
||||
|
||||
if (streams == NULL)
|
||||
writer->priv->raw_caps = gst_caps_copy (((GstMediaDescriptor *) writer)->filenode->caps);
|
||||
gst_discoverer_stream_info_list_free(streams);
|
||||
|
||||
|
||||
if (full == TRUE)
|
||||
_run_frame_analisis (writer, runner, uri);
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
|
@ -466,7 +714,7 @@ gst_media_descriptor_writer_add_frame (GstMediaDescriptorWriter
|
|||
g_return_val_if_fail (((GstMediaDescriptor *) writer)->filenode, FALSE);
|
||||
|
||||
((GstMediaDescriptor *) writer)->filenode->frame_detection = TRUE;
|
||||
|
||||
GST_MEDIA_DESCRIPTOR_LOCK (writer);
|
||||
for (tmp = ((GstMediaDescriptor *) writer)->filenode->streams; tmp;
|
||||
tmp = tmp->next) {
|
||||
StreamNode *streamnode = (StreamNode *) tmp->data;
|
||||
|
@ -495,9 +743,11 @@ gst_media_descriptor_writer_add_frame (GstMediaDescriptorWriter
|
|||
fnode->str_close = NULL;
|
||||
|
||||
streamnode->frames = g_list_append (streamnode->frames, fnode);
|
||||
GST_MEDIA_DESCRIPTOR_UNLOCK (writer);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
GST_MEDIA_DESCRIPTOR_UNLOCK (writer);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@ typedef struct {
|
|||
|
||||
GstMediaDescriptorWriter * gst_media_descriptor_writer_new_discover (GstValidateRunner *runner,
|
||||
const gchar *uri,
|
||||
gboolean full,
|
||||
GError **err);
|
||||
|
||||
GstMediaDescriptorWriter * gst_media_descriptor_writer_new (GstValidateRunner *runner,
|
||||
|
|
|
@ -124,6 +124,10 @@ GType gst_media_descriptor_get_type (void);
|
|||
#define GST_IS_MEDIA_DESCRIPTOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MEDIA_DESCRIPTOR))
|
||||
#define GST_MEDIA_DESCRIPTOR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_MEDIA_DESCRIPTOR, GstMediaDescriptorClass))
|
||||
|
||||
#define GST_MEDIA_DESCRIPTOR_GET_LOCK(obj) (&GST_MEDIA_DESCRIPTOR(obj)->lock)
|
||||
#define GST_MEDIA_DESCRIPTOR_LOCK(obj) g_mutex_lock(GST_MEDIA_DESCRIPTOR_GET_LOCK(obj))
|
||||
#define GST_MEDIA_DESCRIPTOR_UNLOCK(obj) g_mutex_unlock(GST_MEDIA_DESCRIPTOR_GET_LOCK(obj))
|
||||
|
||||
typedef struct _GstMediaDescriptorPrivate GstMediaDescriptorPrivate;
|
||||
|
||||
typedef struct {
|
||||
|
@ -131,6 +135,8 @@ typedef struct {
|
|||
|
||||
FileNode *filenode;
|
||||
|
||||
GMutex lock;
|
||||
|
||||
GstMediaDescriptorPrivate *priv;
|
||||
} GstMediaDescriptor;
|
||||
|
||||
|
|
|
@ -35,141 +35,14 @@
|
|||
#include <gst/validate/media-descriptor.h>
|
||||
#include <gst/pbutils/encoding-profile.h>
|
||||
|
||||
/* move this into some utils file */
|
||||
#if 0
|
||||
static gboolean
|
||||
_parse_encoding_profile (const gchar * option_name, const gchar * value,
|
||||
gpointer udata, GError ** error)
|
||||
{
|
||||
GstCaps *caps;
|
||||
char *preset_name = NULL;
|
||||
gchar **restriction_format, **preset_v;
|
||||
|
||||
guint i, presence = 0;
|
||||
GstCaps *restrictioncaps = NULL;
|
||||
gchar **strpresence_v, **strcaps_v = g_strsplit (value, ":", 0);
|
||||
|
||||
if (strcaps_v[0] && *strcaps_v[0]) {
|
||||
caps = gst_caps_from_string (strcaps_v[0]);
|
||||
if (caps == NULL) {
|
||||
g_printerr ("Could not parse caps %s", strcaps_v[0]);
|
||||
return FALSE;
|
||||
}
|
||||
encoding_profile =
|
||||
GST_ENCODING_PROFILE (gst_encoding_container_profile_new
|
||||
("User profile", "User profile", caps, NULL));
|
||||
gst_caps_unref (caps);
|
||||
} else {
|
||||
encoding_profile = NULL;
|
||||
}
|
||||
|
||||
for (i = 1; strcaps_v[i]; i++) {
|
||||
GstEncodingProfile *profile = NULL;
|
||||
gchar *strcaps, *strpresence;
|
||||
|
||||
restriction_format = g_strsplit (strcaps_v[i], "->", 0);
|
||||
if (restriction_format[1]) {
|
||||
restrictioncaps = gst_caps_from_string (restriction_format[0]);
|
||||
strcaps = g_strdup (restriction_format[1]);
|
||||
} else {
|
||||
restrictioncaps = NULL;
|
||||
strcaps = g_strdup (restriction_format[0]);
|
||||
}
|
||||
g_strfreev (restriction_format);
|
||||
|
||||
preset_v = g_strsplit (strcaps, "+", 0);
|
||||
if (preset_v[1]) {
|
||||
strpresence = preset_v[1];
|
||||
g_free (strcaps);
|
||||
strcaps = g_strdup (preset_v[0]);
|
||||
} else {
|
||||
strpresence = preset_v[0];
|
||||
}
|
||||
|
||||
strpresence_v = g_strsplit (strpresence, "|", 0);
|
||||
if (strpresence_v[1]) { /* We have a presence */
|
||||
gchar *endptr;
|
||||
|
||||
if (preset_v[1]) { /* We have preset and presence */
|
||||
preset_name = g_strdup (strpresence_v[0]);
|
||||
} else { /* We have a presence but no preset */
|
||||
g_free (strcaps);
|
||||
strcaps = g_strdup (strpresence_v[0]);
|
||||
}
|
||||
|
||||
presence = strtoll (strpresence_v[1], &endptr, 10);
|
||||
if (endptr == strpresence_v[1]) {
|
||||
g_printerr ("Wrong presence %s\n", strpresence_v[1]);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
} else { /* We have no presence */
|
||||
if (preset_v[1]) { /* Not presence but preset */
|
||||
preset_name = g_strdup (preset_v[1]);
|
||||
g_free (strcaps);
|
||||
strcaps = g_strdup (preset_v[0]);
|
||||
} /* Else we have no presence nor preset */
|
||||
}
|
||||
g_strfreev (strpresence_v);
|
||||
g_strfreev (preset_v);
|
||||
|
||||
GST_DEBUG ("Creating preset with restrictions: %" GST_PTR_FORMAT
|
||||
", caps: %s, preset %s, presence %d", restrictioncaps, strcaps,
|
||||
preset_name ? preset_name : "none", presence);
|
||||
|
||||
caps = gst_caps_from_string (strcaps);
|
||||
g_free (strcaps);
|
||||
if (caps == NULL) {
|
||||
g_warning ("Could not create caps for %s", strcaps_v[i]);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (g_str_has_prefix (strcaps_v[i], "audio/")) {
|
||||
profile = GST_ENCODING_PROFILE (gst_encoding_audio_profile_new (caps,
|
||||
preset_name, restrictioncaps, presence));
|
||||
} else if (g_str_has_prefix (strcaps_v[i], "video/") ||
|
||||
g_str_has_prefix (strcaps_v[i], "image/")) {
|
||||
profile = GST_ENCODING_PROFILE (gst_encoding_video_profile_new (caps,
|
||||
preset_name, restrictioncaps, presence));
|
||||
}
|
||||
|
||||
g_free (preset_name);
|
||||
gst_caps_unref (caps);
|
||||
if (restrictioncaps)
|
||||
gst_caps_unref (restrictioncaps);
|
||||
|
||||
if (profile == NULL) {
|
||||
g_warning ("No way to create a preset for caps: %s", strcaps_v[i]);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (encoding_profile) {
|
||||
if (gst_encoding_container_profile_add_profile
|
||||
(GST_ENCODING_CONTAINER_PROFILE (encoding_profile),
|
||||
profile) == FALSE) {
|
||||
g_warning ("Can not create a preset for caps: %s", strcaps_v[i]);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
} else {
|
||||
encoding_profile = profile;
|
||||
}
|
||||
}
|
||||
g_strfreev (strcaps_v);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
int
|
||||
main (int argc, gchar ** argv)
|
||||
{
|
||||
GOptionContext *ctx;
|
||||
|
||||
GError *err = NULL;
|
||||
guint ret = 0;
|
||||
GError *err = NULL;
|
||||
gboolean full = FALSE;
|
||||
gchar *output_file = NULL;
|
||||
gchar *expected_file = NULL;
|
||||
gchar *output = NULL;
|
||||
|
@ -181,6 +54,9 @@ main (int argc, gchar ** argv)
|
|||
{"output-file", 'o', 0, G_OPTION_ARG_FILENAME,
|
||||
&output_file, "The output file to store the results",
|
||||
NULL},
|
||||
{"full", 'f', 0, G_OPTION_ARG_NONE,
|
||||
&full, "Fully analize the file frame by frame",
|
||||
NULL},
|
||||
{"expected-results", 'e', 0, G_OPTION_ARG_FILENAME,
|
||||
&expected_file, "Path to file containing the expected results "
|
||||
"(or the last results found) for comparison with new results",
|
||||
|
@ -217,7 +93,7 @@ main (int argc, gchar ** argv)
|
|||
g_option_context_free (ctx);
|
||||
|
||||
runner = gst_validate_runner_new ();
|
||||
writer = gst_media_descriptor_writer_new_discover (runner, argv[1], NULL);
|
||||
writer = gst_media_descriptor_writer_new_discover (runner, argv[1], full, NULL);
|
||||
if (writer == NULL) {
|
||||
g_print ("Could not discover file: %s", argv[1]);
|
||||
return 1;
|
||||
|
|
Loading…
Reference in a new issue