[gtk/wip/otte/lottie: 7/9] ottie: Add a command-line tool




commit a79802e6120f6b0bb7131af1d154789866d297bd
Author: Benjamin Otte <otte redhat com>
Date:   Sat Dec 19 06:39:34 2020 +0100

    ottie: Add a command-line tool
    
    Supports:
    
     * Taking a screenie:
       ottie image file.lottie image.png
    
     * Recording a rendernode:
       ottie node file.lottie render.node
    
     * Encoding an image:
       ottie video file.lottie video.webm

 meson.build             |   1 +
 ottie/tools/meson.build |  19 +++
 ottie/tools/ottie.c     | 416 ++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 436 insertions(+)
---
diff --git a/meson.build b/meson.build
index 06e169d914..a502ecff8d 100644
--- a/meson.build
+++ b/meson.build
@@ -681,6 +681,7 @@ subdir('gdk')
 subdir('gsk')
 subdir('ottie')
 subdir('gtk')
+subdir('ottie/tools')
 subdir('modules')
 if get_option('demos')
   subdir('demos')
diff --git a/ottie/tools/meson.build b/ottie/tools/meson.build
new file mode 100644
index 0000000000..771ac79da4
--- /dev/null
+++ b/ottie/tools/meson.build
@@ -0,0 +1,19 @@
+# Installed tools
+ottie_tools = [
+  ['ottie', ['ottie.c']],
+]
+
+foreach tool: ottie_tools
+  tool_name = tool.get(0)
+  tool_srcs = tool.get(1)
+
+  exe = executable(tool_name,
+    sources: tool_srcs,
+    include_directories: [confinc],
+    c_args: common_cflags,
+    dependencies: [libgtk_dep],
+    install: true,
+  )
+
+  set_variable(tool_name.underscorify(), exe) # used in testsuites
+endforeach
diff --git a/ottie/tools/ottie.c b/ottie/tools/ottie.c
new file mode 100644
index 0000000000..dd44322e1d
--- /dev/null
+++ b/ottie/tools/ottie.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright © 2020 Benjamin Otte
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include <ottie/ottie.h>
+#include <gtk/gtk.h>
+
+#include <glib/gi18n.h>
+
+static GskRenderNode *
+snapshot_paintable (GdkPaintable *paintable,
+                    int           width,
+                    int           height)
+{
+  GtkSnapshot *snapshot;
+
+  snapshot = gtk_snapshot_new ();
+  gdk_paintable_snapshot (paintable, snapshot, width, height);
+  return gtk_snapshot_free_to_node (snapshot);
+}
+
+static void
+draw_paintable (GdkPaintable    *paintable,
+                cairo_surface_t *surface)
+{
+  cairo_t *cr;
+  GskRenderNode *node;
+
+  node = snapshot_paintable (paintable, 
+                             cairo_image_surface_get_width (surface),
+                             cairo_image_surface_get_height (surface));
+
+  cr = cairo_create (surface);
+  cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
+  cairo_paint (cr);
+  cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+
+  if (node)
+    {
+      gsk_render_node_draw (node, cr);
+      gsk_render_node_unref (node);
+    }
+
+  cairo_destroy (cr);
+}
+
+static gboolean
+save_paintable_to_png (GdkPaintable *paintable,
+                       const char   *filename,
+                       int           width,
+                       int           height)
+{
+  cairo_surface_t *surface;
+  cairo_status_t status;
+
+  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+  draw_paintable (paintable, surface);
+
+  status = cairo_surface_write_to_png (surface, filename);
+  if (status != CAIRO_STATUS_SUCCESS)
+    g_printerr (_("Failed to save to \"%s\": %s\n"), filename, cairo_status_to_string (status));
+
+  cairo_surface_destroy (surface);
+
+  return status == CAIRO_STATUS_SUCCESS;
+}
+
+static gboolean
+save_paintable_to_node (GdkPaintable *paintable,
+                        const char   *filename,
+                        int           width,
+                        int           height)
+{
+  GskRenderNode *node;
+  GError *error = NULL;
+  gboolean result;
+
+  node = snapshot_paintable (GDK_PAINTABLE (paintable), width, height);
+  if (node == NULL)
+    result = g_file_set_contents (filename, "", 0, &error);
+  else
+    {
+      result = gsk_render_node_write_to_file (node, filename, &error);
+      gsk_render_node_unref (node);
+    }
+
+  if (!result)
+    {
+      g_printerr ("%s\n", error->message);
+      g_error_free (error);
+    }
+
+  return result;
+}
+
+static int
+usage (void)
+{
+  g_print (_("Usage:\n"
+             "ottie [COMMAND] [OPTION…] FILEs\n"
+             "  Perform various tasks on a Lottie file.\n"
+             "\n"
+             "ottie image [OPTION…] FILE IMAGE-FILE\n"
+             "  Save a PNG of the given input file.\n"
+             "  --time=[timestamp]  Forward to [timestamp] seconds\n"
+             "  --size=[max]        Resize larger dimension to [max]\n"
+             "\n"
+             "ottie node [OPTION…] FILE NODE-FILE\n"
+             "  Save a rendernode file of the given input file.\n"
+             "  --time=[timestamp]  Forward to [timestamp] seconds\n"
+             "  --size=[max]        Resize larger dimension to [max]\n"
+             "\n"
+             "ottie video [OPTION…] FILE VIDEO-FILE\n"
+             "  Save a WebM of the given input file.\n"
+             "  --size=[max]        Resize larger dimension to [max]\n"
+             "\n"
+             "ottie show [OPTION…] FILE\n"
+             "  Show a small video player for the given file.\n"
+             "\n"
+             "Perform various tasks on Lottie files.\n"));
+
+  return 1;
+}
+
+static void
+paintable_get_size (OttiePaintable *paintable,
+                    int             desired,
+                    int            *out_width,
+                    int            *out_height)
+{
+  int width, height;
+
+  width = gdk_paintable_get_intrinsic_width (GDK_PAINTABLE (paintable));
+  height = gdk_paintable_get_intrinsic_height (GDK_PAINTABLE (paintable));
+  
+  if (desired > 0)
+    {
+      if (width > height)
+        {
+          height = (height * desired + width - 1) / width;
+          width = desired;
+        }
+      else
+        {
+          width = (width * desired + width - 1) / height;
+          height = desired;
+        }
+    }
+
+  *out_width = width;
+  *out_height = height;
+}
+
+static int
+do_image (int         argc,
+          const char *argv[],
+          gboolean    do_node)
+{
+  OttieCreation *ottie;
+  OttiePaintable *paintable;
+  int width, height;
+  int result = 0;
+  double timestamp = 0;
+  int desired_size = -1;
+
+  while (TRUE)
+    {
+      if (argc == 0)
+        return usage();
+
+      if (strncmp (argv[0], "--time=", strlen ("--time=")) == 0)
+        {
+          timestamp = atof (argv[0] + strlen ("--time="));
+        }
+      else if (strncmp (argv[0], "--size=", strlen ("--size=")) == 0)
+        {
+          desired_size = atoi (argv[0] + strlen ("--size="));
+        }
+      else
+        break;
+
+      argc--;
+      argv++;
+    }
+  
+  if (argc != 2)
+    return usage();
+
+  ottie = ottie_creation_new_for_filename (argv[0]);
+  if (ottie == NULL)
+    {
+      g_printerr ("Someone figure out error handling for loading ottie files.\n");
+      return 1;
+    }
+  while (ottie_creation_is_loading (ottie))
+    g_main_context_iteration (NULL, TRUE);
+
+  paintable = ottie_paintable_new (ottie);
+  ottie_paintable_set_timestamp (paintable, round (timestamp * G_USEC_PER_SEC));
+
+  paintable_get_size (paintable, desired_size, &width, &height);
+
+  if (do_node)
+    {
+      if (!save_paintable_to_node (GDK_PAINTABLE (paintable), argv[1], width, height))
+        result = 1;
+    }
+  else
+    {
+      if (!save_paintable_to_png (GDK_PAINTABLE (paintable), argv[1], width, height))
+        result = 1;
+    }
+
+  g_object_unref (paintable);
+
+  return result;
+}
+
+static int
+do_video (int         argc,
+          const char *argv[])
+{
+  OttieCreation *ottie;
+  OttiePaintable *paintable;
+  int width, height;
+  int result = 0;
+  double timestamp;
+  int desired_size = -1;
+  GSubprocess *process;
+  GError *error = NULL;
+  GOutputStream *pipe;
+  cairo_surface_t *surface;
+  char *width_string, *height_string;
+  char *location_string;
+
+  while (TRUE)
+    {
+      if (argc == 0)
+        return usage();
+
+      if (strncmp (argv[0], "--size=", strlen ("--size=")) == 0)
+        {
+          desired_size = atoi (argv[0] + strlen ("--size="));
+        }
+      else
+        break;
+
+      argc--;
+      argv++;
+    }
+
+  if (argc != 2)
+    return usage();
+
+  ottie = ottie_creation_new_for_filename (argv[0]);
+  if (ottie == NULL)
+    {
+      g_printerr ("Someone figure out error handling for loading ottie files.\n");
+      return 1;
+    }
+  while (ottie_creation_is_loading (ottie))
+    g_main_context_iteration (NULL, TRUE);
+
+  paintable = ottie_paintable_new (ottie);
+  paintable_get_size (paintable, desired_size, &width, &height);
+
+  width_string = g_strdup_printf ("width=%d", width);
+  height_string = g_strdup_printf ("height=%d", height);
+  location_string = g_strdup_printf ("location=%s", argv[1]);
+  process = g_subprocess_new (G_SUBPROCESS_FLAGS_STDIN_PIPE,
+                              &error,
+                              "gst-launch-1.0",
+                              "fdsrc", 
+                              "!", "rawvideoparse", "use-sink-caps=false", width_string, height_string,
+                              G_BYTE_ORDER == G_LITTLE_ENDIAN ? "format=bgra" : "format=argb",
+                              "!", "videoconvert",
+                              "!", "vp9enc",
+                              "!", "webmmux",
+                              "!", "filesink", location_string,
+                              NULL);
+  g_free (width_string);
+  g_free (height_string);
+  g_free (location_string);
+
+  if (process == NULL)
+    {
+      g_printerr ("%s\n", error->message);
+      g_error_free (error);
+      g_object_unref (paintable);
+      return 1;
+    }
+
+  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+  pipe = g_subprocess_get_stdin_pipe (process);
+
+  for (timestamp = 0; timestamp <= ottie_paintable_get_duration (paintable); timestamp += G_USEC_PER_SEC / 
25)
+    {
+      ottie_paintable_set_timestamp (paintable, timestamp);
+      draw_paintable (GDK_PAINTABLE (paintable), surface);
+      if (!g_output_stream_write_all (pipe,
+                                      cairo_image_surface_get_data (surface),
+                                      width * height * 4,
+                                      NULL,
+                                      NULL,
+                                      &error))
+        {
+          g_printerr ("%s\n", error->message);
+          g_clear_error (&error);
+          result = 1;
+          break;
+        }
+    }
+
+  if (!g_output_stream_close (pipe, NULL, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      g_error_free (error);
+      result = 1;
+    }
+  if (!g_subprocess_wait (process, NULL, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      g_error_free (error);
+      result = 1;
+    }
+  else if (!g_subprocess_get_successful (process))
+    {
+      g_printerr ("Encoder failed to write video.\n");
+      result = 1;
+    }
+
+  g_object_unref (process);
+  g_object_unref (paintable);
+
+  return result;
+}
+
+static int
+do_show (int         argc,
+         const char *argv[])
+{
+  OttiePlayer *player;
+  GtkWidget *video, *window;
+
+  if (argc != 1)
+    return usage();
+
+  player = ottie_player_new_for_filename (argv[0]);
+  window = gtk_window_new ();
+  gtk_window_set_title (GTK_WINDOW (window), argv[0]);
+  g_signal_connect (window, "destroy", G_CALLBACK (gtk_window_destroy), NULL);
+
+  video = gtk_video_new ();
+  gtk_video_set_loop (GTK_VIDEO (video), TRUE);
+  gtk_video_set_autoplay (GTK_VIDEO (video), TRUE);
+  gtk_video_set_media_stream (GTK_VIDEO (video), GTK_MEDIA_STREAM (player));
+  gtk_window_set_child (GTK_WINDOW (window), video);
+
+  gtk_widget_show (window);
+
+  while (g_list_model_get_n_items (gtk_window_get_toplevels ()) > 0)
+    g_main_context_iteration (NULL, TRUE);
+
+  return 0;
+}
+
+int
+main (int         argc,
+      const char *argv[])
+{
+  int result;
+
+  g_set_prgname ("ottie");
+
+  gtk_init ();
+
+  if (argc < 3)
+    return usage ();
+
+  if (strcmp (argv[2], "--help") == 0)
+    return usage ();
+
+  argv++;
+  argc--;
+
+  if (strcmp (argv[0], "image") == 0)
+    result = do_image (argc - 1, argv + 1, FALSE);
+  else if (strcmp (argv[0], "node") == 0)
+    result = do_image (argc - 1, argv + 1, TRUE);
+  else if (strcmp (argv[0], "video") == 0)
+    result = do_video (argc - 1, argv + 1);
+  else if (strcmp (argv[0], "view") == 0 ||
+           strcmp (argv[0], "show") == 0)
+    result = do_show (argc - 1, argv + 1);
+  else
+    return usage ();
+
+  return result;
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]