[gegl] initial gcut import



commit 4830622e2d212fa5f6173dbfc8ea5274ddcc161a
Author: Øyvind Kolås <pippin gimp org>
Date:   Thu Jul 13 19:55:09 2017 +0200

    initial gcut import

 Makefile.am         |    1 +
 configure.ac        |    1 +
 gcut/Makefile.am    |   59 ++
 gcut/clip.c         |  439 ++++++++
 gcut/default.edl    |   34 +
 gcut/gedl-ui.c      | 2855 +++++++++++++++++++++++++++++++++++++++++++++++++++
 gcut/gedl.c         | 1608 +++++++++++++++++++++++++++++
 gcut/gedl.h         |  259 +++++
 gcut/iconographer.c |  748 ++++++++++++++
 gcut/renderer.c     |  287 ++++++
 10 files changed, 6291 insertions(+), 0 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index e0a661c..2b6ff96 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,6 +10,7 @@ SUBDIRS=\
        libs/npd \
        seamless-clone \
        bin \
+       gcut \
        tools \
        operations \
        examples \
diff --git a/configure.ac b/configure.ac
index 27c77d6..9867a41 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1283,6 +1283,7 @@ dnl bin/node-editors/Makefile
 AC_CONFIG_FILES([
 Makefile
 bin/Makefile
+gcut/Makefile
 gegl/Makefile
 gegl/gegl-version.h
 gegl/buffer/Makefile
diff --git a/gcut/Makefile.am b/gcut/Makefile.am
new file mode 100644
index 0000000..e6ccedb
--- /dev/null
+++ b/gcut/Makefile.am
@@ -0,0 +1,59 @@
+if OS_WIN32
+no_undefined = -no-undefined
+endif
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir) \
+       -I$(top_builddir)/gegl \
+       -I$(top_srcdir)/gegl \
+       -I$(top_builddir)/gegl/buffer \
+       -I$(top_srcdir)/gegl/buffer \
+       -I$(top_builddir)/gegl/graph \
+       -I$(top_srcdir)/gegl/graph \
+       -I$(top_builddir)/gegl/module \
+       -I$(top_srcdir)/gegl/module \
+       -I$(top_builddir)/gegl/operation \
+       -I$(top_srcdir)/gegl/operation \
+       -I$(top_builddir)/gegl/opencl \
+       -I$(top_srcdir)/gegl/opencl \
+       -I$(top_builddir)/gegl/process \
+       -I$(top_srcdir)/gegl/process \
+       -I$(top_builddir)/gegl/property-types \
+       -I$(top_srcdir)/gegl/property-types
+
+AM_CFLAGS = \
+       $(DEP_CFLAGS) $(BABL_CFLAGS) $(PNG_CFLAGS) \
+        $(MRG_CFLAGS) $(GEXIV2_CFLAGS)
+
+AM_LDFLAGS =  \
+       $(no_undefined) ../gegl/libgegl-$(GEGL_API_VERSION).la \
+       $(DEP_LIBS) $(BABL_LIBS) $(PNG_LIBS) $(LIBSPIRO) $(MATH_LIB) \
+        $(MRG_LIBS) $(GEXIV2_LIBS)
+
+bin_PROGRAMS = gcut
+
+default.edl.inc: default.edl
+       cat $< | \
+       sed 's/\\/\\\\/g' | \
+       sed 's/\r/a/' | \
+       sed 's/"/\\"/g' | \
+       sed 's/^/"/' | \
+       sed 's/$$/\\n"/' > $@
+
+gcut_SOURCES =                 \
+       gedl.c                  \
+       gedl.h                  \
+       renderer.c              \
+       iconographer.c          \
+       clip.c
+
+
+if HAVE_MRG
+if HAVE_GEXIV2
+if HAVE_SDL
+gcut_SOURCES += gedl-ui.c
+AM_CFLAGS += $(SDL_CFLAGS)
+AM_LDFLAGS += $(SDL_LIBS)
+endif
+endif
+endif
diff --git a/gcut/clip.c b/gcut/clip.c
new file mode 100644
index 0000000..4a509cf
--- /dev/null
+++ b/gcut/clip.c
@@ -0,0 +1,439 @@
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <gegl.h>
+#include <gegl-audio-fragment.h>
+#include "gedl.h"
+
+Clip *clip_new (GeglEDL *edl)
+{
+  Clip *clip = g_malloc0 (sizeof (Clip));
+  clip->edl  = edl;
+  clip->gegl = gegl_node_new ();
+
+  clip->chain_loader = gegl_node_new_child (clip->gegl, "operation", "gegl:nop", NULL);
+
+  clip->full_loader  = gegl_node_new_child (clip->gegl, "operation", "gegl:ff-load", NULL);
+  clip->proxy_loader = gegl_node_new_child (clip->gegl, "operation", "gegl:ff-load", NULL);
+  clip->loader       = gegl_node_new_child (clip->gegl, "operation", "gegl:nop", NULL);
+
+  clip->nop_scaled = gegl_node_new_child (clip->gegl, "operation", "gegl:scale-size-keepaspect",
+                                       "y", 0.0, //
+                                       "x", 1.0 * edl->width,
+                                       "sampler", GEDL_SAMPLER,
+                                       NULL);
+  clip->nop_crop     = gegl_node_new_child (clip->gegl, "operation", "gegl:crop", "x", 0.0, "y", 0.0, 
"width", 1.0 * edl->width,
+                                        "height", 1.0 * edl->height, NULL);
+
+  clip->nop_store_buf = gegl_node_new_child (clip->gegl, "operation", "gegl:write-buffer", "buffer", 
edl->buffer, NULL);
+#if 0
+  clip->full_store_buf = gegl_node_new_child (clip->gegl, "operation", "gegl:write-buffer", "buffer", 
edl->buffer, NULL);
+  clip->preview_store_buf = gegl_node_new_child (clip->gegl, "operation", "gegl:write-buffer", "buffer", 
edl->buffer, NULL);
+#endif
+
+  gegl_node_link_many (clip->full_loader,
+                       clip->loader,
+                       clip->nop_scaled,
+                       clip->nop_crop,
+                       clip->nop_store_buf,
+                       NULL);
+
+  g_mutex_init (&clip->mutex);
+
+  return clip;
+}
+
+Clip *clip_get_prev (Clip *self)
+{
+  GList *l;
+  GeglEDL *edl;
+  if (!self)
+    return NULL;
+  edl = self->edl;
+  Clip *prev = NULL;
+
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    if (clip->is_meta)
+      continue;
+    if (clip == self)
+      return prev;
+    prev = clip;
+  }
+  return NULL;
+}
+Clip *clip_get_next (Clip *self)
+{
+  GList *l;
+  GeglEDL *edl;
+  int found = 0;
+  if (!self)
+    return NULL;
+  edl = self->edl;
+
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    if (clip->is_meta)
+      continue;
+    if (found)
+      return clip;
+    if (clip == self)
+      found = 1;
+  }
+  return NULL;
+}
+
+void clip_free (Clip *clip)
+{
+  if (clip->path)
+    g_free (clip->path);
+  clip->path = NULL;
+
+  if (clip->gegl)
+    g_object_unref (clip->gegl);
+  clip->gegl = NULL;
+  g_mutex_clear (&clip->mutex);
+  g_free (clip);
+}
+
+void clip_set_path (Clip *clip, const char *in_path)
+{
+  char *path = NULL;
+  clip->is_chain = 0;
+  clip->is_meta = 0;
+
+  if (!in_path)
+  {
+    clip->is_meta = 1;
+    if (clip->path)
+      g_free (clip->path);
+    clip->path = NULL;
+    return;
+  }
+
+  if (!strcmp (in_path, "black") ||
+      !strcmp (in_path, "blue") ||
+      strstr (in_path, "gegl:"))
+    clip->is_chain = 1;
+
+  if (in_path[0] == '/' || clip->is_chain)
+  {
+    path = g_strdup (in_path);
+  }
+  else
+  {
+    if (clip->edl->parent_path)
+      path = g_strdup_printf ("%s%s", clip->edl->parent_path, in_path);
+    else
+      path = g_strdup_printf ("%s", in_path);
+  }
+
+  if (clip->path && !strcmp (clip->path, path))
+  {
+    g_free (path);
+    return;
+  }
+
+  if (clip->path)
+    g_free (clip->path);
+  clip->path = path;
+
+  if (clip->is_chain)
+  {
+    GError *error = NULL;
+    if (is_connected (clip->chain_loader, clip->loader))
+      remove_in_betweens (clip->chain_loader, clip->loader);
+    else
+      gegl_node_link_many (clip->chain_loader, clip->loader, NULL);
+
+    gegl_create_chain (path, clip->chain_loader, clip->loader, 0,
+                       400, //edl->height,
+                       NULL, &error);
+    if (error)
+      {
+        /* should set error string */
+        fprintf (stderr, "chain source: %s\n", error->message);
+        g_error_free (error);
+      }
+  }
+  else
+  {
+    if (g_str_has_suffix (path, ".png") ||
+        g_str_has_suffix (path, ".jpg") ||
+        g_str_has_suffix (path, ".exr") ||
+        g_str_has_suffix (path, ".EXR") ||
+        g_str_has_suffix (path, ".PNG") ||
+        g_str_has_suffix (path, ".JPG"))
+     {
+       g_object_set (clip->full_loader, "operation", "gegl:load", NULL);
+       clip->static_source = 1;
+     }
+    else
+     {
+       g_object_set (clip->full_loader, "operation", "gegl:ff-load", NULL);
+       clip->static_source = 0;
+     }
+  }
+}
+
+int clip_get_start (Clip *clip)
+{
+  return clip->start;
+}
+
+int clip_get_end (Clip *clip)
+{
+  return clip->end;
+}
+
+int clip_get_frames (Clip *clip)
+{
+  int frames = clip_get_end (clip) - clip_get_start (clip) + 1;
+  if (frames < 0) frames = 0;
+  if (clip->is_meta)
+    return 0;
+  return frames;
+}
+
+void clip_set_start (Clip *clip, int start)
+{
+  clip->start = start;
+}
+void clip_set_end (Clip *clip, int end)
+{
+  clip->end = end;
+}
+
+void clip_set_range (Clip *clip, int start, int end)
+{
+  clip_set_start (clip, start);
+  clip_set_end (clip, end);
+}
+
+void clip_set_full (Clip *clip, const char *path, int start, int end)
+{
+  clip_set_path (clip, path);
+  clip_set_range (clip, start, end);
+}
+
+Clip *clip_new_full (GeglEDL *edl, const char *path, int start, int end)
+{
+  Clip *clip = clip_new (edl);
+  clip_set_full (clip, path, start, end);
+  return clip;
+}
+
+void clip_fade_set (Clip *clip, int do_fade_out)
+{
+  /*  should cancel any computations due to fade when cancelling it, and add them when fade is set
+   */
+}
+
+const char *clip_get_path (Clip *clip)
+{
+  return clip->path;
+}
+
+static void clip_set_proxied (Clip *clip)
+{
+  if (clip->is_chain)
+    return;
+
+  if (clip->edl->use_proxies)
+    {
+      char *path = gedl_make_proxy_path (clip->edl, clip->path);
+      gchar *old = NULL;
+      gegl_node_get (clip->proxy_loader, "path", &old, NULL);
+
+      if (!old || !strcmp (old, "") || !strcmp (path, old))
+        gegl_node_set (clip->proxy_loader, "path", path, NULL);
+      gegl_node_link_many (clip->proxy_loader, clip->loader, NULL);
+      g_free (path);
+    }
+  else
+    {
+      gchar *old = NULL;
+      gegl_node_get (clip->full_loader, "path", &old, NULL);
+      if (!old || !strcmp (old, "") || !strcmp (clip->path, old))
+        gegl_node_set (clip->full_loader, "path", clip->path, NULL);
+      gegl_node_link_many (clip->full_loader, clip->loader, NULL);
+    }
+}
+
+void clip_set_frame_no (Clip *clip, int clip_frame_no)
+{
+  if (clip_frame_no < 0)
+    clip_frame_no = 0;
+
+  clip_set_proxied (clip);
+#if 0
+  {
+    gchar *old = NULL;
+    gegl_node_get (clip->full_loader, "path", &old, NULL);
+    if (!old || !strcmp (old, "") || !strcmp (clip->path, old))
+      gegl_node_set (clip->full_loader, "path", clip->path, NULL);
+  }
+#endif
+
+  if (!clip_is_static_source (clip))
+    {
+      if (clip->edl->use_proxies)
+        gegl_node_set (clip->proxy_loader, "frame", clip_frame_no, NULL);
+      else
+        gegl_node_set (clip->full_loader, "frame", clip_frame_no, NULL);
+    }
+}
+
+int clip_is_static_source (Clip *clip)
+{
+  return clip->static_source;
+}
+
+void clip_fetch_audio (Clip *clip)
+{
+  int use_proxies = clip->edl->use_proxies;
+
+  if (clip->audio)
+    {
+      g_object_unref (clip->audio);
+      clip->audio = NULL;
+    }
+
+  if (clip_is_static_source (clip))
+    clip->audio = NULL;
+  else
+    {
+      if (use_proxies)
+        gegl_node_get (clip->proxy_loader, "audio", &clip->audio, NULL);
+      else
+        gegl_node_get (clip->full_loader, "audio", &clip->audio, NULL);
+    }
+}
+
+int is_connected (GeglNode *a, GeglNode *b)
+{
+  GeglNode *iter = a;
+  while (iter && iter != b)
+  {
+    GeglNode **nodes = NULL;
+    int count = gegl_node_get_consumers (iter, "output", &nodes, NULL);
+    if (count) iter = nodes[0];
+    else
+            iter = NULL;
+    g_free (nodes);
+  }
+  if (iter == b)
+    return 1;
+  return 0;
+}
+
+void remove_in_betweens (GeglNode *nop_scaled, GeglNode *nop_filtered)
+{
+ GeglNode *iter = nop_scaled;
+ GList *collect = NULL;
+
+ iter = nop_filtered;
+ while (iter && iter != nop_scaled)
+ {
+   GeglNode **nodes = NULL;
+   iter = gegl_node_get_producer (iter, "input", NULL);
+   g_free (nodes);
+   if (iter && iter != nop_scaled)
+     collect = g_list_append (collect, iter);
+ }
+
+ while (collect)
+ {
+    g_object_unref (collect->data);
+    collect = g_list_remove (collect, collect->data);
+ }
+ gegl_node_link_many (nop_scaled, nop_filtered, NULL);
+}
+
+void clip_rig_chain (Clip *clip, int clip_frame_no)
+{
+  GeglEDL *edl = clip->edl;
+  int use_proxies = edl->use_proxies;
+
+  g_mutex_lock (&clip->mutex);
+
+  remove_in_betweens (clip->nop_scaled, clip->nop_crop);
+
+  gegl_node_set (clip->nop_scaled, "operation", "gegl:scale-size-keepaspect",
+                               "y", 0.0,
+                               "x", 1.0 * edl->width,
+                               "sampler", use_proxies?GEDL_SAMPLER:GEGL_SAMPLER_CUBIC,
+                               NULL);
+
+  gegl_node_set (clip->nop_crop, "width", 1.0 * edl->width,
+                                 "height", 1.0 * edl->height,
+                                 NULL);
+  if (clip->is_chain)
+  {
+    if (is_connected (clip->chain_loader, clip->loader))
+      remove_in_betweens (clip->chain_loader, clip->loader);
+    else
+      gegl_node_link_many (clip->chain_loader, clip->loader, NULL);
+
+    gegl_create_chain (clip->path, clip->chain_loader, clip->loader, clip_frame_no - clip->start,
+                       edl->height,
+                       NULL, NULL);//&error);
+  }
+
+      if (clip->filter_graph)
+        {
+           GError *error = NULL;
+           gegl_create_chain (clip->filter_graph, clip->nop_scaled, clip->nop_crop, clip_frame_no - 
clip->start, edl->height, NULL, &error);
+           if (error)
+             {
+               /* should set error string */
+               fprintf (stderr, "%s\n", error->message);
+               g_error_free (error);
+             }
+         }
+      /**********************************************************************/
+
+
+      // flags,..    FULL   PREVIEW   FULL_CACHE|PREVIEW  STORE_FULL_CACHE
+      clip_set_frame_no (clip, clip_frame_no);
+  g_mutex_unlock (&clip->mutex);
+}
+
+void clip_render_frame (Clip *clip, int clip_frame_no)
+{
+      clip_rig_chain (clip, clip_frame_no);
+      g_mutex_lock (&clip->mutex);
+      gegl_node_process (clip->loader); // for the audio fetch
+      clip_fetch_audio (clip);
+
+      g_mutex_unlock (&clip->mutex);
+}
+
+
+gchar *clip_get_frame_hash (Clip *clip, int clip_frame_no)
+{
+  GeglEDL *edl = clip->edl;
+  gchar *frame_recipe;
+  GChecksum *hash;
+  int is_static_source = clip_is_static_source (clip);
+
+  frame_recipe = g_strdup_printf ("%s: %s %i %s %ix%i",
+      "gedl-pre-4",
+      clip_get_path (clip),
+      clip->filter_graph || (!is_static_source) ? clip_frame_no : 0,
+      clip->filter_graph,
+      edl->video_width,
+      edl->video_height);
+
+  hash = g_checksum_new (G_CHECKSUM_MD5);
+  g_checksum_update (hash, (void*)frame_recipe, -1);
+  char *ret = g_strdup (g_checksum_get_string(hash));
+
+  g_checksum_free (hash);
+  g_free (frame_recipe);
+
+  return ret;
+}
diff --git a/gcut/default.edl b/gcut/default.edl
new file mode 100644
index 0000000..089269b
--- /dev/null
+++ b/gcut/default.edl
@@ -0,0 +1,34 @@
+proxy-width=320
+proxy-height=240
+output-path=gedl.mp4
+video-width=800
+video-height=600
+fps=25.000000
+selection-start=291
+selection-end=291
+frame-scale=0.398424
+t0=56.702866
+frame-no=291
+
+gegl:color value=black gegl:crop width=400 height=400 31 50 -- 
+gegl:color opi=0:0 gegl:crop opi=0:0 width=400 height=400 0 50 -- [fade=26] svg:src-over opi=0:0 aux=[  
gegl:text opi=0:0 string='note: this is a gedl video\nproject for testing features\nit relies on synthetic 
GEGL\npatterns instead of\nvideo footage\nto be to minimie size\nfor inclusion in gedl sources\nbeing able to 
create this project \nfrom scratch in the gcut ui\nis a current goal.' size=0.071811296045780182rel 
color='rgb(1.0000, 1.0000, 1.0000)' width=3 height=1 gegl:translate opi=0:0 x=0.10000000000000001rel 
y=0.051874876022338867rel ]
+-- #annotations
+gegl:color opi=0:0 gegl:crop opi=0:0 width=400 height=400 reset-origin=true 0 80 -- svg:src-over opi=0:0 
aux=[  gegl:text opi=0:0 string='gcut' font='sans' size=0.20000000000000001rel color='rgb(1.0000, 1.0000, 
1.0000)' width=1 height=1 gegl:gaussian-blur opi=0:0 std-dev-x={  0.000000=0.400000rel  64.000000=0.000084rel 
 }  std-dev-y={  0.000000=0.200000rel  }  clip-extent=false gegl:translate opi=0:0 x=0.10000000000000001rel 
y=0.3304142951965332rel ]  gegl:lens-flare opi=0:0 pos-x={  0.000000=-2.000000  60.000000=0.293004  
100.000000=2.000000  }  pos-y={  0.000000=0.100000  60.000000=0.360697  100.000000=0.910000  }
+gegl:color opi=0:0 gegl:crop opi=0:0 width=400 height=400 0 50 -- [fade=64] svg:src-over opi=0:0 aux=[  
gegl:text opi=0:0 string='video editing\nfileformat+render / ui' size=0.10000000000000001rel 
color='rgb(1.0000, 1.0000, 1.0000)' width=1 height=1 gegl:translate opi=0:0 x=0.10000000000000001rel 
y=0.55924856662750244rel ]
+gegl:cell-noise opi=0:0 shape={  0.000000=1.000000  40.000000=1.850000  }  palettize=true gegl:translate 
opi=0:0 x={  500.000000=50.000000  }  y={  500.000000=500.000000  }  gegl:crop opi=0:0 width=400 height=400 0 
40 -- [fade=23] 
+gegl:simplex-noise opi=0:0 seed=605124352 gegl:crop opi=0:0 width=400 height=400 0 25 -- [fade=14] 
gegl:threshold opi=0:0 value=0.13152152299880981 gegl:gaussian-blur opi=0:0 std-dev-x=0.012467504478991032rel 
std-dev-y=0.0055317822843790054rel gegl:exposure opi=1:0 black-level=0.029268454760313034 
exposure=2.5591373443603516 gegl:levels opi=0:0 out-high=0.99985802173614502
+gegl:simplex-noise opi=0:0 scale=1.1438060998916626 seed=168548944 gegl:exposure opi=1:0 
exposure=4.4479846954345703 gegl:crop opi=0:0 width=400 height=400  0 25 -- [fade=34] svg:src-over opi=0:0 
aux=[  gegl:text opi=0:0 string='single track ui with\ncross fade' size=0.12115363776683807rel 
color='rgb(1.0000, 1.0000, 0.0000)' width=1 height=1 gegl:dropshadow opi=0:0 x=0.0018535566050559282rel 
y=0.0019916105084121227rel radius=0.028898788616061211rel opacity=1.9511399269104004 gegl:translate opi=0:0 
x=0.063941836357116699rel y=0.14936363697052002rel ]  
+gegl:simplex-noise opi=0:0 scale=2.982180118560791 iterations=3 gegl:crop opi=0:0 width=400 height=400 0 25 
-- [fade=34] svg:src-over opi=0:0 aux=[  gegl:text opi=0:0 string='multi-process rendering' 
size=0.093153767287731171rel color='rgb(1.0000, 1.0000, 0.0000)' width=1 height=1 gegl:translate opi=0:0 
x=0.059999999999999998rel y=0.16329610347747803rel ]
+gegl:simplex-noise opi=0:0 seed=1031312640 gegl:crop opi=0:0 width=400 height=400 0 25 -- [fade=34] 
+gegl:simplex-noise opi=0:0 seed=331734624 gegl:crop opi=0:0 width=400 height=400 0 25 -- [fade=34] 
svg:src-over opi=0:0 aux=[  gegl:text opi=0:0 string='single track editing' size=0.10000000000000001rel 
color='rgb(1.0000, 1.0000, 0.0000)' width=1 height=1 gegl:translate opi=0:0 x=0.059999999999999998rel 
y=0.80000000000000004rel ]
+gegl:simplex-noise gegl:crop width=400 height=400 0 36 -- [fade=34] 
+gegl:fractal-explorer zoom={ 0=120 60=1300} shiftx={ 0=-320 60=-2500} shifty=-180 gegl:crop width=480 
height=640 0 60 -- [fade=30] 
+gegl:sinus opi=0:0 complexity={  1.000000=0.500000  40.000000=4.000000  80.000000=0.200000  }  gegl:crop 
opi=0:0 width=400 height=400 0 80 -- [fade=60] 
+gegl:noise-solid opi=0:0 y-size=4.6258320808410645 seed=668093247 gegl:crop opi=0:0 width=400 height=400 0 
25 -- [fade=34] svg:src-over opi=0:0 aux=[  gegl:text opi=0:0 string='TODO' size=0.10000000000000001rel 
color='rgb(1.0000, 1.0000, 0.0000)' width=1 height=1 gegl:translate opi=0:0 x=0.050000000000000003rel 
y=0.29999999999999999rel ]
+gegl:noise-solid opi=0:0 gegl:crop opi=0:0 width=400 height=400 0 25 -- [fade=36] svg:src-over opi=0:0 aux=[ 
 gegl:text opi=0:0 string='crash recovery by default' size=0.081632360816001892rel color='rgb(1.0000, 1.0000, 
0.0000)' width=1 height=1 gegl:translate opi=0:0 x=0.10000000000000001rel y=0.42925620079040527rel ]
+gegl:noise-solid opi=0:0 gegl:crop opi=0:0 width=400 height=400 0 25 -- [fade=34] svg:src-over opi=0:0 aux=[ 
 gegl:text opi=0:0 string='overlays:audio filter' size=0.10000000000000001rel color='rgb(1.0000, 1.0000, 
0.0000)' width=1 height=1 gegl:translate opi=0:0 x=0.10000000000000001rel y=0.48208630084991455rel ]
+gegl:noise-solid gegl:crop width=400 height=400 0 25 -- [fade=34] over aux=[ text size=0.1rel 
string='network rendering' color=yellow translate x=0.1rel y=0.8rel ]
+--#end titles
+gegl:color opi=0:0 gegl:crop opi=0:0 width=400 height=400 0 474 -- svg:src-over opi=0:0 aux=[  gegl:text 
opi=0:0 string='end titles\n\nfull of newlines\n\nand some images and more\n\nmost of the 
time\n\n\nthough\n\nusing an image that one pans\nwill provide better typographic control\nperhaps based on a 
pdf/svg\n\nthis is is also where I should stick misc info' size=0.040000000000000001rel color='rgb(1.0000, 
1.0000, 1.0000)' width=1 height=8 gegl:translate opi=0:0 x=0.10000000000000001rel y={  0.000000=1.000000rel  
800.000000=-2.000000rel  }  ]
+-----
+fnord.mp4 40 140 0 -- 
diff --git a/gcut/gedl-ui.c b/gcut/gedl-ui.c
new file mode 100644
index 0000000..455f578
--- /dev/null
+++ b/gcut/gedl-ui.c
@@ -0,0 +1,2855 @@
+#define _BSD_SOURCE
+#define _DEFAULT_SOURCE
+
+#define USE_CAIRO_SCALING 1
+
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <mrg.h>
+#include <gegl.h>
+#include "gedl.h"
+#include <gegl-paramspecs.h>
+
+static int exited = 0;
+long babl_ticks (void);
+
+static unsigned char *copy_buf = NULL;
+static int copy_buf_len = 0;
+
+static int changed = 0;
+
+static int empty_selection (GeglEDL *edl)
+{
+  return edl->selection_start == edl->selection_end;
+}
+static void clip_split (Clip *oldclip, int shift);
+static void clip_remove (Clip *clip);
+
+static void mrg_gegl_blit (Mrg *mrg,
+                          float x0, float y0,
+                          float width, float height,
+                          GeglNode *node,
+                          float u, float v,
+                          float opacity,
+                          GeglEDL *edl)
+{
+  GeglRectangle bounds;
+
+  cairo_t *cr = mrg_cr (mrg);
+  cairo_surface_t *surface = NULL;
+
+  if (!node)
+    return;
+
+  bounds = *gegl_buffer_get_extent (edl->buffer_copy_temp);
+
+  if (width == -1 && height == -1)
+  {
+    width  = bounds.width;
+    height = bounds.height;
+  }
+
+  if (width == -1)
+    width = bounds.width * height / bounds.height;
+  if (height == -1)
+    height = bounds.height * width / bounds.width;
+
+#ifdef USE_CAIRO_SCALING
+  if (copy_buf_len < bounds.width * bounds.height * 4)
+  {
+    if (copy_buf)
+      free (copy_buf);
+    copy_buf_len = bounds.width * bounds.height * 4;
+    copy_buf = malloc (copy_buf_len);
+  }
+      float scale = 1.0;
+  {
+    static int foo = 0;
+    unsigned char *buf = copy_buf;
+    GeglRectangle roi = {u, v, bounds.width, bounds.height};
+    static const Babl *fmt = NULL;
+
+foo++;
+    if (!fmt) fmt = babl_format ("cairo-RGB24");
+
+    {
+      scale = width / bounds.width;
+      if (height / bounds.height < scale)
+        scale = height / bounds.height;
+
+      // XXX: the 1.001 instead of 1.00 is to work around a gegl bug
+      gegl_buffer_get (edl->buffer_copy_temp, &roi, 1.001, fmt, buf, bounds.width * 4, GEGL_ABYSS_BLACK);
+    }
+
+    surface = cairo_image_surface_create_for_data (buf, CAIRO_FORMAT_RGB24, bounds.width, bounds.height, 
bounds.width * 4);
+  }
+
+  cairo_save (cr);
+  cairo_surface_set_device_scale (surface, 1.0/scale, 1.0/scale);
+#else
+  if (copy_buf_len < width * height * 4)
+  {
+    if (copy_buf)
+      free (copy_buf);
+    copy_buf_len = width * height * 4;
+    copy_buf = malloc (copy_buf_len);
+  }
+  {
+    static int foo = 0;
+    unsigned char *buf = copy_buf;
+    GeglRectangle roi = {u, v, width, height};
+    static const Babl *fmt = NULL;
+
+foo++;
+    if (!fmt) fmt = babl_format ("cairo-RGB24");
+
+    {
+      float scale = 1.0;
+      scale = width / bounds.width;
+      if (height / bounds.height < scale)
+        scale = height / bounds.height;
+
+      gegl_buffer_get (edl->buffer_copy_temp, &roi, scale, fmt, buf, width * 4, GEGL_ABYSS_BLACK);
+    }
+
+  surface = cairo_image_surface_create_for_data (buf, CAIRO_FORMAT_RGB24, width, height, width * 4);
+  }
+
+  cairo_save (cr);
+  cairo_surface_set_device_scale (surface, 1.0, 1.0);
+#endif
+  cairo_rectangle (cr, x0, y0, width, height);
+
+
+  cairo_clip (cr);
+  cairo_translate (cr, x0, y0);
+  cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_NEAREST);
+  cairo_set_source_surface (cr, surface, 0, 0);
+  cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
+
+  if (opacity < 0.9)
+  {
+    cairo_paint_with_alpha (cr, opacity);
+  }
+  else
+  {
+    cairo_paint (cr);
+  }
+  cairo_surface_destroy (surface);
+  cairo_restore (cr);
+}
+
+typedef struct _State State;
+
+struct _State {
+  void   (*ui) (Mrg *mrg, void *state);
+  Mrg     *mrg;
+  GeglEDL *edl;
+  char    *path;
+  char    *save_path;
+};
+
+float fpx           = 2;
+
+#if 0
+ // with copy/paste we'd want to have this:
+ // available: with a path + newline being valid, and perhaps
+ // sufficient for the drag + drop case as well
+
+static void insert_string (GeglEDL *edl, const char *string)
+{
+}
+#endif
+
+static void insert_clip (GeglEDL *edl, const char *path,
+                         int in, int out)
+{
+  GList *iter;
+  Clip *clip, *cur_clip;
+  int end_frame = edl->frame_no;
+  if (in < 0)
+    in = 0;
+  if (out < 0)
+  {
+    int duration = 0;
+    if (!empty_selection (edl))
+    {
+      out = edl->selection_end - edl->selection_start;
+      if (out < 0) out = -out;
+    }
+    else
+    {
+      gedl_get_video_info (path, &duration, NULL);
+      out = duration;
+    }
+    if (out < in)
+      out = in;
+  }
+  clip = clip_new_full (edl, path, in, out);
+  clip->title = g_strdup (basename (path));
+  int clip_frame_no;
+  cur_clip = gedl_get_clip (edl, edl->frame_no, &clip_frame_no);
+
+  if (empty_selection (edl))
+  {
+    gedl_get_duration (edl);
+    if (edl->frame_no != cur_clip->abs_start)
+    {
+      gedl_get_duration (edl);
+      clip_split (cur_clip, clip_frame_no);
+      cur_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+    }
+  }
+  else
+  {
+    Clip *last_clip;
+    int sin, sout;
+    sin = edl->selection_start;
+    sout = edl->selection_end + 1;
+    if (sin > sout)
+    {
+      sout = edl->selection_start + 1;
+      sin = edl->selection_end;
+    }
+    int cur_clip_frame_no;
+    cur_clip = gedl_get_clip (edl, sin, &cur_clip_frame_no);
+    clip_split (cur_clip, cur_clip_frame_no);
+    gedl_get_duration (edl);
+    int last_clip_frame_no;
+    cur_clip = gedl_get_clip (edl, sin, &cur_clip_frame_no);
+    last_clip = gedl_get_clip (edl, sout, &last_clip_frame_no);
+    if (cur_clip == last_clip)
+    {
+      clip_split (last_clip, last_clip_frame_no);
+    }
+    last_clip = edl_get_clip_for_frame (edl, sout);
+
+    cur_clip = edl_get_clip_for_frame (edl, sin);
+    while (cur_clip != last_clip)
+    {
+      clip_remove (cur_clip);
+      cur_clip = edl_get_clip_for_frame (edl, sin);
+    }
+    edl->frame_no = sin;
+  }
+
+  cur_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+  iter = g_list_find (edl->clips, cur_clip);
+  edl->clips = g_list_insert_before (edl->clips, iter, clip);
+  end_frame += out - in + 1;
+  edl->frame_no = end_frame;
+
+  edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+
+  gedl_make_proxies (edl);
+}
+
+static void drag_dropped (MrgEvent *ev, void *data1, void *data2)
+{
+  GeglEDL *edl = data2;
+
+  char *str = g_strdup (ev->string);
+  char *s = str;
+  char *e;
+
+  e = strchr (s, '\r');
+  while (e)
+  {
+    *e = '\0';
+    if (strstr (s, "file://")) s+= strlen ("file://");
+    insert_clip (edl, s, -1, -1);
+    s = e+1;
+    if (*s == '\n') s++;
+    e = strchr (s, '\r');
+  }
+
+  g_free (str);
+}
+static void scroll_to_fit (GeglEDL *edl, Mrg *mrg);
+
+static void clicked_clip (MrgEvent *e, void *data1, void *data2)
+{
+  Clip *clip = data1;
+  GeglEDL *edl = data2;
+
+  edl->frame_no = e->x;
+  edl->selection_start = edl->frame_no;
+  edl->selection_end = edl->frame_no;
+  edl->active_clip = clip;
+  edl->playing = 0;
+  scroll_to_fit (edl, e->mrg);
+  mrg_queue_draw (e->mrg, NULL);
+  changed++;
+}
+
+#include <math.h>
+
+static void drag_clip (MrgEvent *e, void *data1, void *data2)
+{
+  GeglEDL *edl = data2;
+  edl->frame_no = e->x;
+  if (e->x >= edl->selection_start)
+  {
+    edl->selection_end = e->x;
+  }
+  else
+  {
+    edl->selection_start = e->x;
+  }
+  scroll_to_fit (edl, e->mrg);
+  mrg_queue_draw (e->mrg, NULL);
+  changed++;
+}
+
+static void drag_t0 (MrgEvent *e, void *data1, void *data2)
+{
+  GeglEDL *edl = data2;
+  edl->t0 += e->delta_x;
+  if (edl->t0 < 0.0)
+    edl->t0 = 0.0;
+  mrg_queue_draw (e->mrg, NULL);
+  mrg_event_stop_propagate (e);
+  changed++;
+}
+
+static void drag_fpx (MrgEvent *e, void *data1, void *data2)
+{
+  GeglEDL *edl = data2;
+  edl->scale = (mrg_width(e->mrg)*edl->scale + e->delta_x) / mrg_width(e->mrg);
+  mrg_queue_draw (e->mrg, NULL);
+  mrg_event_stop_propagate (e);
+  changed++;
+}
+
+static void released_clip (MrgEvent *e, void *data1, void *data2)
+{
+  Clip *clip = data1;
+  GeglEDL *edl = data2;
+  edl->frame_no = e->x;
+  edl->active_clip = clip;
+  if (edl->selection_end < edl->selection_start)
+  {
+    int temp = edl->selection_end;
+    edl->selection_end = edl->selection_start;
+    edl->selection_start = temp;
+  }
+  scroll_to_fit (edl, e->mrg);
+  mrg_queue_draw (e->mrg, NULL);
+  changed++;
+}
+
+static void stop_playing (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  edl->playing = 0;
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void select_all (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  gint end = gedl_get_duration (edl) - 1;
+  if (edl->selection_start == 0 && edl->selection_end == end)
+  {
+    gedl_set_selection (edl, 0, 0);
+  }
+  else
+  {
+    gedl_set_selection (edl, 0, end);
+  }
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+
+
+static void prev_cut (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (!edl->active_clip)
+    return;
+  {
+    GList *iter = g_list_find (edl->clips, edl->active_clip);
+
+    if (iter)
+    {
+       if (edl->frame_no == edl->active_clip->abs_start)
+       {
+         iter = iter->prev;
+         if (iter) edl->active_clip = iter->data;
+       }
+    }
+    edl->frame_no = edl->active_clip->abs_start;
+    edl->selection_start = edl->selection_end = edl->frame_no;
+  }
+  mrg_event_stop_propagate (event);
+  scroll_to_fit (edl, event->mrg);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void next_cut (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (!edl->active_clip)
+    return;
+  {
+    GList *iter = g_list_find (edl->clips, edl->active_clip);
+    if (iter) iter = iter->next;
+    if (iter)
+    {
+      edl->active_clip = iter->data;
+      edl->frame_no = edl->active_clip->abs_start;
+    }
+    else
+    {
+      edl->frame_no = edl->active_clip->abs_start + clip_get_frames (edl->active_clip);
+    }
+  }
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  edl->selection_start = edl->selection_end = edl->frame_no;
+  scroll_to_fit (edl, event->mrg);
+  changed++;
+}
+
+static void extend_selection_to_previous_cut (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  int sel_start, sel_end;
+  edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+
+  gedl_get_selection (edl, &sel_start, &sel_end);
+  prev_cut (event, data1, data2);
+  sel_start = edl->frame_no;
+  gedl_set_selection (edl, sel_start, sel_end);
+
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+
+static void extend_selection_to_next_cut (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  int sel_start, sel_end;
+
+  gedl_get_selection (edl, &sel_start, &sel_end);
+  next_cut (event, data1, data2);
+  sel_start = edl->frame_no;
+  gedl_set_selection (edl, sel_start, sel_end);
+
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void extend_selection_to_the_left (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  int sel_start, sel_end;
+
+  gedl_get_selection (edl, &sel_start, &sel_end);
+  if (edl->frame_no == sel_end)
+  {
+    sel_end --;
+    edl->frame_no --;
+  }
+  else if (edl->frame_no == sel_start)
+  {
+    sel_start --;
+    edl->frame_no --;
+  }
+  else
+  {
+    sel_start = sel_end = edl->frame_no;
+    sel_end --;
+    edl->frame_no --;
+  }
+  gedl_set_selection (edl, sel_start, sel_end);
+
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+
+static void extend_selection_to_the_right (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  int sel_start, sel_end;
+
+  gedl_get_selection (edl, &sel_start, &sel_end);
+  if (edl->frame_no == sel_end)
+  {
+    sel_end ++;
+    edl->frame_no ++;
+  }
+  else if (edl->frame_no == sel_start)
+  {
+    sel_start ++;
+    edl->frame_no ++;
+  }
+  else
+  {
+    sel_start = sel_end = edl->frame_no;
+    sel_end ++;
+    edl->frame_no ++;
+  }
+  gedl_set_selection (edl, sel_start, sel_end);
+
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static int ui_tweaks = 0;
+static int are_mergable (Clip *clip1, Clip *clip2, int delta)
+{
+  if (!clip1 || !clip2)
+    return 0;
+  if (!clip1->path)
+    return 0;
+  if (!clip2->path)
+    return 0;
+  if (strcmp (clip1->path, clip2->path))
+    return 0;
+  if (clip2->start != (clip1->end + 1 + delta))
+    return 0;
+  if (clip1->filter_graph==NULL && clip2->filter_graph != NULL)
+    return 0;
+  if (clip1->filter_graph!=NULL && clip2->filter_graph == NULL)
+    return 0;
+  if (clip1->filter_graph && strcmp (clip1->filter_graph, clip2->filter_graph))
+    return 0;
+  return 1;
+}
+
+static void clip_remove (Clip *clip)
+{
+  GeglEDL *edl = clip->edl;
+  GList *iter = g_list_find (edl->clips, clip);
+
+  if (iter->next)
+    iter = iter->next;
+  else if (iter->prev)
+    iter = iter->prev;
+  else
+    return;
+
+  edl->clips = g_list_remove (edl->clips, clip);
+  edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+}
+
+static GeglNode *selected_node = NULL;
+
+static void remove_clip (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+
+  if (!edl->active_clip)
+    return;
+
+  if (selected_node)
+  {
+    GeglNode *producer = NULL;
+    GeglNode *consumer = NULL;
+    GeglNode   **nodes = NULL;
+    const gchar **pads = NULL;
+    char      *prodpad = NULL;
+
+    int count = gegl_node_get_consumers (selected_node, "output", &nodes, &pads);
+    if (count)
+      {
+        consumer= nodes[0];
+      }
+
+    producer = gegl_node_get_producer (selected_node, "input", &prodpad);
+
+    if (producer && consumer)
+    {
+      fprintf (stderr, "%p %s %p %s\n", producer, prodpad, consumer, pads[0]);
+      gegl_node_connect_to (producer, prodpad, consumer, pads[0]);
+    }
+
+    if (prodpad)
+      g_free (prodpad);
+
+    g_object_unref (selected_node);
+    selected_node = NULL;
+    ui_tweaks++;
+  }
+  else
+  {
+    clip_remove (edl->active_clip);
+  }
+
+  gedl_cache_invalid (edl);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+static GeglNode *filter_start;
+
+static void make_rel_props (GeglNode *node)
+{
+  unsigned int n_props;
+  GParamSpec ** props = gegl_operation_list_properties (gegl_node_get_operation (node),
+                      &n_props);
+
+  for (int i = 0; i <n_props; i ++)
+  {
+    const char *unit = gegl_operation_get_property_key (gegl_node_get_operation (node), props[i]->name, 
"unit");
+
+    if (unit && !strcmp (unit, "pixel-distance"))
+    {
+      char tmpbuf[1024];
+      sprintf (tmpbuf, "%s-rel", props[i]->name);
+      GQuark rel_quark = g_quark_from_string (tmpbuf);
+      g_object_set_qdata_full (G_OBJECT(node), rel_quark,  g_strdup("foo"), g_free);
+    }
+
+  }
+}
+
+
+  void insert_node (GeglNode *selected_node, GeglNode *new)
+  {
+    GeglNode **nodes = NULL;
+    const gchar **pads = NULL;
+
+    int count = gegl_node_get_consumers (selected_node, "output", &nodes, &pads);
+    make_rel_props (new);
+
+    gegl_node_link_many (selected_node, new, NULL);
+    if (count)
+    {
+      gegl_node_connect_to (new, "output", nodes[0], pads[0]);
+    }
+  }
+
+char *filter_query = NULL;
+
+static void insert_filter (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+
+  if (!edl->active_clip)
+    return;
+
+  filter_query = g_strdup ("");
+  mrg_set_cursor_pos (event->mrg, 0);
+
+  if (!selected_node)
+    selected_node = filter_start;
+
+#if 0
+  GeglNode *new = NULL;
+  new = gegl_node_new_child (edl->gegl, "operation", "gegl:unsharp-mask", NULL);
+  insert_node (selected_node, new);
+  selected_node = new;
+#endif
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+
+}
+
+static void merge_clip (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  GList *iter = g_list_find (edl->clips, edl->active_clip);
+  Clip *clip2 = NULL;
+  if (iter) iter = iter->prev;
+  if (iter) clip2 = iter->data;
+
+  if (!are_mergable (clip2, edl->active_clip, 0))
+    return;
+
+  clip2->end = edl->active_clip->end;
+
+  remove_clip (event, data1, data2);
+  edl->active_clip = clip2;
+}
+
+static void toggle_use_proxies (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+
+  if (!edl->playing) // disallowing - to avoid some races
+  {
+    gedl_set_use_proxies (edl, edl->use_proxies?0:1);
+    gedl_cache_invalid (edl);
+
+    if (edl->use_proxies)
+      gedl_make_proxies (edl);
+  }
+
+  if (event)
+  {
+    mrg_event_stop_propagate (event);
+    mrg_queue_draw (event->mrg, NULL);
+  }
+}
+
+static void clip_split (Clip *oldclip, int shift)
+{
+  GeglEDL *edl = oldclip->edl;
+  GList *iter = g_list_find (edl->clips, oldclip);
+  Clip *clip = clip_new_full (edl, oldclip->path, oldclip->start, oldclip->end);
+  edl->clips = g_list_insert_before (edl->clips, iter, clip);
+
+  if (oldclip->filter_graph)
+    clip->filter_graph = g_strdup (oldclip->filter_graph);
+
+  clip->end      = shift - 1;
+  oldclip->start = shift;
+}
+
+static void split_clip (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  int clip_frame_no = 0;
+  Clip *clip = gedl_get_clip (edl, edl->frame_no, &clip_frame_no);
+  if (!edl->active_clip)
+    return;
+
+  if (edl->active_clip !=clip)
+  {
+    g_warning ("hmmm");
+    return;
+  }
+
+  clip_split (edl->active_clip, clip_frame_no);
+  {
+    //edl->active_clip = clip;
+  }
+  gedl_cache_invalid (edl);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void toggle_fade (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+
+  if (!edl->active_clip)
+    return;
+
+  if (edl->active_clip->fade)
+  {
+    edl->active_clip->fade = 0;
+  }
+  else
+  {
+    edl->active_clip->fade = (edl->frame - edl->active_clip->abs_start)*2;
+  }
+  gedl_cache_invalid (edl);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void duplicate_clip (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+
+  if (!edl->active_clip)
+    return;
+  {
+    GList *iter = g_list_find (edl->clips, edl->active_clip);
+    Clip *clip = clip_new_full (edl, edl->active_clip->path, edl->active_clip->start, edl->active_clip->end);
+    edl->clips = g_list_insert_before (edl->clips, iter, clip);
+    if (edl->active_clip->filter_graph)
+      clip->filter_graph = g_strdup (edl->active_clip->filter_graph);
+    edl->active_clip = clip;
+  }
+  gedl_cache_invalid (edl);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static int help = 0;
+
+static void toggle_help (MrgEvent *event, void *data1, void *data2)
+{
+  //GeglEDL *edl = data1;
+  help = help ? 0 : 1;
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+static void save_edl (GeglEDL *edl)
+{
+  if (edl->path)
+  {
+    gedl_save_path (edl, edl->path);
+  }
+}
+
+#if 1
+static void save (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  save_edl (edl);
+}
+#endif
+
+static gboolean save_idle (Mrg *mrg, gpointer edl)
+{
+  if (changed)
+  {
+    changed = 0;
+    save_edl (edl);
+  }
+  return TRUE;
+}
+
+static void set_range (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  int start, end;
+
+  gedl_get_selection (edl, &start, &end);
+  gedl_set_range (edl, start, end);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+#if 0
+static void up (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (edl->active_clip && edl->filter_edited)
+    return;
+  if (edl->clip_query_edited)
+  {
+    edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+    edl->clip_query_edited = 0;
+  }
+  else if (edl->active_source)
+  {
+    GList *l;
+    int found = 0;
+    edl->active_source->editing = 0;
+    for (l = edl->clip_db; l; l = l->next)
+    {
+      if (l->next && l->next->data == edl->active_source)
+      {
+        edl->active_source = l->data;
+        make_active_source (edl, edl->active_source);
+        found = 1;
+        break;
+      }
+    }
+    if (!found)
+    {
+      edl->active_source = NULL;
+      //edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+      edl->clip_query_edited = 1;
+      mrg_set_cursor_pos (event->mrg, strlen (edl->clip_query));
+    }
+  }
+  else if (edl->active_clip)
+  {
+    if(edl->filter_edited)
+    {
+      edl->active_source = edl->clip_db?g_list_last (edl->clip_db)->data:NULL;
+      make_active_source (edl, edl->active_source);
+      edl->filter_edited = 0;
+      edl->active_clip = NULL;
+    }
+    else
+    {
+      edl->active_source = NULL;
+      edl->filter_edited = 1;
+      mrg_set_cursor_pos (event->mrg, 0);
+      fprintf (stderr, "...\n");
+    }
+  }
+  else
+  {
+          fprintf (stderr, "uh\n");
+  }
+  mrg_queue_draw (event->mrg, NULL);
+  mrg_event_stop_propagate (event);
+  changed++;
+}
+#endif
+
+#if 0
+static void down (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (edl->active_clip && edl->filter_edited)
+    return;
+  if (edl->clip_query_edited)
+  {
+    edl->active_source = edl->clip_db->data;
+    make_active_source (edl, (void*)edl->active_source);
+    edl->clip_query_edited = 0;
+  }
+  else if (edl->active_source)
+  {
+    GList *l;
+    int found = 0;
+    edl->active_source->editing = 0;
+    for (l = edl->clip_db; l; l = l->next)
+    {
+      if (l->next && l->data == edl->active_source)
+      {
+        edl->active_source = l->next->data;
+        make_active_source (edl, l->next->data);
+        found = 1;
+        break;
+      }
+    }
+    if (!found)
+    {
+      edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+      edl->active_source = NULL;
+    }
+  }
+  else
+  {
+    edl->active_clip = NULL;
+    edl->clip_query_edited = 1;
+  }
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+#endif
+
+static void step_frame_back (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  stop_playing (event, data1, data2);
+  {
+    edl->selection_start = edl->selection_end;
+    edl->frame_no --;
+    if (edl->frame_no < 0)
+      edl->frame_no = 0;
+    edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+  }
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void step_frame (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  stop_playing (event, data1, data2);
+  {
+    edl->selection_start = edl->selection_end;
+    edl->frame_no ++;
+    edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+  }
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void clip_end_start_dec (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  Clip *clip1, *clip2;
+  if (edl->selection_start < edl->selection_end)
+  {
+    clip1 = edl_get_clip_for_frame (edl, edl->selection_start);
+    clip2 = edl_get_clip_for_frame (edl, edl->selection_end);
+  }
+  else
+  {
+    clip1 = edl_get_clip_for_frame (edl, edl->selection_end);
+    clip2 = edl_get_clip_for_frame (edl, edl->selection_start);
+  }
+  edl->selection_start--;
+  edl->selection_end--;
+  clip1->end--;
+  clip2->start--;
+  edl->frame_no--;
+  gedl_cache_invalid (edl);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+static void clip_end_start_inc (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  Clip *clip1, *clip2;
+  if (edl->selection_start < edl->selection_end)
+  {
+    clip1 = edl_get_clip_for_frame (edl, edl->selection_start);
+    clip2 = edl_get_clip_for_frame (edl, edl->selection_end);
+  }
+  else
+  {
+    clip1 = edl_get_clip_for_frame (edl, edl->selection_end);
+    clip2 = edl_get_clip_for_frame (edl, edl->selection_start);
+  }
+  edl->selection_start++;
+  edl->selection_end++;
+  clip1->end++;
+  clip2->start++;
+  edl->frame_no++;
+
+  gedl_cache_invalid (edl);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+static void clip_start_end_inc (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (edl->active_clip)
+    {
+      edl->active_clip->end++;
+      edl->active_clip->start++;
+    }
+  gedl_cache_invalid (edl);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+static void clip_start_end_dec (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (edl->active_clip)
+    {
+      edl->active_clip->end--;
+      edl->active_clip->start--;
+    }
+  gedl_cache_invalid (edl);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+static void clip_end_inc (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (edl->active_clip)
+    {
+      edl->active_clip->end++;
+      edl->frame_no++;
+    }
+  gedl_cache_invalid (edl);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+static void clip_end_dec (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (edl->active_clip)
+    {
+      edl->active_clip->end--;
+      edl->frame_no--;
+      gedl_cache_invalid (edl);
+    }
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void clip_start_inc (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (edl->active_clip)
+    {
+      edl->active_clip->start++;
+      gedl_cache_invalid (edl);
+    }
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+static void clip_start_dec (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (edl->active_clip)
+    {
+      edl->active_clip->start--;
+      gedl_cache_invalid (edl);
+    }
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+#if 0
+static void toggle_edit_source (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  edl->active_source->editing = !edl->active_source->editing;
+  if (edl->active_source->editing)
+    mrg_set_cursor_pos (event->mrg, strlen (edl->active_source->title));
+  gedl_cache_invalid (edl);
+  mrg_queue_draw (event->mrg, NULL);
+}
+#endif
+
+static void do_quit (MrgEvent *event, void *data1, void *data2)
+{
+  exited = 1;
+  killpg(0, SIGUSR2);
+  mrg_quit (event->mrg);
+}
+
+long last_frame = 0;
+
+void gedl_ui (Mrg *mrg, void *data);
+
+static void zoom_timeline (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  switch (event->scroll_direction)
+  {
+    case MRG_SCROLL_DIRECTION_UP:
+      edl->t0 +=    event->x * edl->scale;
+      edl->scale *= 1.02;
+      edl->t0 -=    event->x * edl->scale;
+      break;
+    case MRG_SCROLL_DIRECTION_DOWN:
+      edl->t0 +=    event->x * edl->scale;
+      edl->scale /= 1.02;
+      edl->t0 -=    event->x * edl->scale;
+      break;
+    case MRG_SCROLL_DIRECTION_LEFT:
+      edl->t0 += edl->scale * 2;
+      break;
+    case MRG_SCROLL_DIRECTION_RIGHT:
+      edl->t0 -= edl->scale * 2;
+      break;
+  }
+
+  scroll_to_fit (edl, event->mrg);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+#define PAD_DIM     8
+int VID_HEIGHT=96; // XXX: ugly global
+
+void render_clip (Mrg *mrg, GeglEDL *edl, const char *clip_path, int clip_start, int clip_frames, double x, 
double y, int fade, int fade2)
+{
+  char *thumb_path;
+  if (!clip_path)
+  {
+    return; // XXX: draw string!
+  }
+  thumb_path = gedl_make_thumb_path (edl, clip_path);
+
+  cairo_t *cr = mrg_cr (mrg);
+  if (fade || fade2)
+  {
+    cairo_move_to (cr, x, y + VID_HEIGHT/2);
+    cairo_line_to (cr, x + fade/2, y);
+    cairo_line_to (cr, x + clip_frames + fade2/2, y);
+    cairo_line_to (cr, x + clip_frames - fade2/2, y + VID_HEIGHT);
+    cairo_line_to (cr, x - fade/2, y + VID_HEIGHT);
+    cairo_line_to (cr, x, y + VID_HEIGHT/2);
+  }
+  else
+  {
+    cairo_rectangle (cr, x, y, clip_frames, VID_HEIGHT);
+  }
+
+  int width, height;
+  MrgImage *img = mrg_query_image (mrg, thumb_path, &width, &height);
+  g_free (thumb_path);
+  if (!edl->playing && img && width > 0)
+  {
+    cairo_surface_t *surface = mrg_image_get_surface (img);
+    cairo_matrix_t   matrix;
+    cairo_pattern_t *pattern = cairo_pattern_create_for_surface (surface);
+
+    //cairo_matrix_init_rotate (&matrix, M_PI / 2); /* compensate for .. */
+    //cairo_matrix_translate (&matrix, 0, -width);  /* vertical format   */
+
+    cairo_matrix_init_scale (&matrix, 1.0, height* 1.0/ VID_HEIGHT);
+    cairo_matrix_translate  (&matrix, -(x - clip_start), -y);
+    cairo_pattern_set_matrix (pattern, &matrix);
+    cairo_pattern_set_filter (pattern, CAIRO_FILTER_NEAREST);
+    cairo_set_source (cr, pattern);
+
+    cairo_save (cr);
+    cairo_clip_preserve (cr);
+    cairo_paint   (cr);
+    cairo_restore (cr);
+  }
+  else
+  {
+    //cairo_set_source_rgba (cr, 0.1, 0.1, 0.1, 0.5);
+    //cairo_fill_preserve (cr);
+  }
+}
+
+static void scroll_to_fit (GeglEDL *edl, Mrg *mrg)
+{
+  /* scroll to fit playhead */
+  if ( (edl->frame_no - edl->t0) / edl->scale > mrg_width (mrg) * 0.9)
+    edl->t0 = edl->frame_no - (mrg_width (mrg) * 0.8) * edl->scale;
+  else if ( (edl->frame_no - edl->t0) / edl->scale < mrg_width (mrg) * 0.1)
+    edl->t0 = edl->frame_no - (mrg_width (mrg) * 0.2) * edl->scale;
+}
+
+static void shuffle_forward (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  gedl_cache_invalid (edl);
+
+  GList *prev = NULL,
+        *next = NULL,
+        *self = g_list_find (edl->clips, edl->active_clip);
+
+  if (self)
+  {
+    next = self->next;
+    prev = self->prev;
+
+    if (self && next)
+    {
+      GList *nextnext = next->next;
+      if (prev)
+        prev->next = next;
+      next->prev = prev;
+      next->next = self;
+      self->prev = next;
+      self->next = nextnext;
+      if (self->next)
+        self->next->prev = self;
+      edl->frame_no += clip_get_frames (next->data);
+    }
+  }
+
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void shuffle_back (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  gedl_cache_invalid (edl);
+
+  GList *prev = NULL,
+        *prevprev = NULL,
+        *next = NULL,
+        *self = g_list_find (edl->clips, edl->active_clip);
+
+  if (self)
+  {
+    next = self->next;
+    prev = self->prev;
+    if (prev)
+      prevprev = prev->prev;
+
+    if (self && prev)
+    {
+      if (prevprev)
+        prevprev->next = self;
+      self->prev = prevprev;
+      self->next = prev;
+      prev->prev = self;
+      prev->next = next;
+      if (next)
+        next->prev = prev;
+
+      edl->frame_no -= clip_get_frames (prev->data);
+    }
+  }
+
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void slide_forward (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  gedl_cache_invalid (edl);
+    edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+
+  GList *prev = NULL,
+        *next = NULL,
+        *self = g_list_find (edl->clips, edl->active_clip);
+  /*
+        situations to deal with:
+          inside mergable clips
+          last of inside mergable clips
+          inside mergable clips if we padded prev
+          last of inside mergable clips if we padded prev
+          non mergable clips -> split next and shuffle into
+   */
+
+  if (self)
+  {
+    next = self->next;
+    prev = self->prev;
+
+    if (self && next && prev)
+    {
+      Clip *prev_clip = prev->data;
+      Clip *next_clip = next->data;
+      Clip *self_clip = self->data;
+
+      if (are_mergable (prev_clip, next_clip, 0))
+      {
+        if (clip_get_frames (next_clip) == 1)
+        {
+          prev_clip->end++;
+          edl->clips = g_list_remove (edl->clips, next_clip);
+          edl->frame_no ++;
+        }
+        else
+        {
+          prev_clip->end ++;
+          next_clip->start ++;
+          edl->frame_no ++;
+        }
+      } else if (are_mergable (prev_clip, next_clip, clip_get_frames (self_clip)))
+      {
+        if (clip_get_frames (next_clip) == 1)
+        {
+          prev_clip->end++;
+          edl->clips = g_list_remove (edl->clips, next_clip);
+          edl->frame_no ++;
+        }
+        else
+        {
+          prev_clip->end ++;
+          next_clip->start ++;
+          edl->frame_no ++;
+        }
+      }
+      else {
+        if (clip_get_frames (next_clip) == 1)
+        {
+          int frame_no = edl->frame_no + 1;
+          shuffle_forward (event, data1, data2);
+          edl->frame_no = frame_no;
+        } else {
+          int frame_no = edl->frame_no + 1;
+          clip_split (next_clip, next_clip->start + 1);
+          shuffle_forward (event, data1, data2);
+          edl->frame_no = frame_no;
+        }
+      }
+    }
+  }
+
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void slide_back (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  gedl_cache_invalid (edl);
+    edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+
+  GList *prev = NULL,
+        *next = NULL,
+        *self = g_list_find (edl->clips, edl->active_clip);
+  /*
+        situations to deal with:
+          inside mergable clips
+          last of inside mergable clips
+          inside mergable clips if we padded prev
+          last of inside mergable clips if we padded prev
+          non mergable clips -> split next and shuffle into
+   */
+
+  if (self)
+  {
+    next = self->next;
+    prev = self->prev;
+
+    if (self && next && prev)
+    {
+      Clip *prev_clip = prev->data;
+      Clip *next_clip = next->data;
+      Clip *self_clip = self->data;
+
+      if (are_mergable (prev_clip, next_clip, 0))
+      {
+        if (clip_get_frames (prev_clip) == 1)
+        {
+          next_clip->start --;
+          edl->clips = g_list_remove (edl->clips, prev_clip);
+          edl->frame_no --;
+        }
+        else
+        {
+          prev_clip->end --;
+          next_clip->start --;
+          edl->frame_no --;
+        }
+      } else if (are_mergable (prev_clip, next_clip, clip_get_frames (self_clip)))
+      {
+        if (clip_get_frames (prev_clip) == 1)
+        {
+          prev_clip->end--;
+          edl->clips = g_list_remove (edl->clips, prev_clip);
+          edl->frame_no --;
+        }
+        else
+        {
+          prev_clip->end --;
+          next_clip->start --;
+          edl->frame_no --;
+        }
+      }
+      else
+      {
+        if (clip_get_frames (prev_clip) == 1)
+        {
+        int frame_no = edl->frame_no - 1;
+        shuffle_back (event, data1, data2);
+        edl->frame_no = frame_no;
+        } else {
+        int frame_no = edl->frame_no - 1;
+        clip_split (prev_clip, prev_clip->end );
+        shuffle_back (event, data1, data2);
+        edl->frame_no = frame_no;
+        }
+      }
+    }
+  }
+
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+static void zoom_1 (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  gedl_cache_invalid (edl);
+  edl->scale = 1.0;
+  scroll_to_fit (edl, event->mrg);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+static void zoom_fit (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  gedl_cache_invalid (edl);
+  edl->t0 = 0.0;
+  edl->scale = gedl_get_duration (edl) * 1.0 / mrg_width (event->mrg);
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+}
+
+static void tweaked_state (Mrg *mrg)
+{
+  ui_tweaks++;
+}
+
+static void toggle_bool (MrgEvent *e, void *data1, void *data2)
+{
+  GeglNode *node = data1;
+  const char *prop = data2;
+  gboolean old_value;
+  gboolean new_value;
+  gegl_node_get (node, prop, &old_value, NULL);
+  new_value = !old_value;
+  gegl_node_set (node, prop, new_value, NULL);
+
+  changed++;
+  mrg_event_stop_propagate (e);
+  mrg_queue_draw (e->mrg, NULL);
+  tweaked_state (e->mrg);
+}
+
+GeglNode *snode = NULL;
+const char *sprop = NULL;
+
+static void edit_string (MrgEvent *e, void *data1, void *data2)
+{
+  GeglNode *node = data1;
+  const char *prop = data2;
+  snode = node;
+  sprop = prop;
+  changed++;
+  mrg_event_stop_propagate (e);
+  mrg_set_cursor_pos (e->mrg, 0); // XXX: could fech strlen and use that
+  mrg_queue_draw (e->mrg, NULL);
+  tweaked_state (e->mrg);
+}
+
+static void jump_to_pos (MrgEvent *e, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  gint pos = GPOINTER_TO_INT(data2);
+
+  fprintf (stderr, "set frame %i\n", pos);
+  edl->frame_no = pos;
+  mrg_event_stop_propagate (e);
+  mrg_queue_draw (e->mrg, NULL);
+}
+
+static void end_edit (MrgEvent *e, void *data1, void *data2)
+{
+  snode = NULL;
+  sprop = NULL;
+  mrg_event_stop_propagate (e);
+  mrg_set_cursor_pos (e->mrg, 0); // XXX: could fech strlen and use that
+  mrg_queue_draw (e->mrg, NULL);
+}
+
+static void drag_double_slider (MrgEvent *e, void *data1, void *data2)
+{
+  gpointer *data = data1;
+  GeglParamSpecDouble *gspec = (void*)data2;
+  GParamSpec          *spec  = (void*)data2;
+  GeglNode            *node  = data[1];
+  GeglEDL             *edl   = data[0];
+  char tmpbuf[1024];
+  sprintf (tmpbuf, "%s-rel", spec->name);
+  GQuark rel_quark = g_quark_from_string (tmpbuf);
+  sprintf (tmpbuf, "%s-anim", spec->name);
+  GQuark anim_quark = g_quark_from_string (tmpbuf);
+  double ui_min = gspec->ui_minimum;
+  double ui_max = gspec->ui_maximum;
+  if (g_object_get_qdata (G_OBJECT (node), rel_quark) && 1)
+    {
+      ui_min /= 1000.0;
+      ui_max /= 1000.0;
+    }
+
+  float new_val = e->x * (ui_max - ui_min) + ui_min;
+
+  if (g_object_get_qdata (G_OBJECT (node), anim_quark))
+  {
+    GeglPath *path = g_object_get_qdata (G_OBJECT (node), anim_quark);
+    int nodes = gegl_path_get_n_nodes (path);
+    int i;
+    int clip_frame_no=0;
+    GeglPathItem path_item;
+    gedl_get_clip (edl, edl->frame_no, &clip_frame_no);
+
+    for (i = 0; i < nodes; i ++)
+    {
+      gegl_path_get_node (path, i, &path_item);
+      if (fabs (path_item.point[0].x - clip_frame_no) < 0.5)
+      {
+        path_item.point[0].x = clip_frame_no;
+        path_item.point[0].y = new_val;
+        gegl_path_replace_node (path, i, &path_item);
+        goto done;
+      }
+      else if (path_item.point[0].x > clip_frame_no)
+      {
+        path_item.point[0].x = clip_frame_no;
+        path_item.point[0].y = new_val;
+        gegl_path_insert_node (path, i - 1, &path_item);
+        goto done;
+      }
+    }
+    path_item.type = 'L';
+    path_item.point[0].x = clip_frame_no;
+    path_item.point[0].y = new_val;
+    gegl_path_insert_node (path, -1, &path_item);
+done:
+    if(0);
+
+  }
+  else
+  {
+    gegl_node_set (node, spec->name, new_val, NULL);
+  }
+
+  mrg_queue_draw (e->mrg, NULL);
+  mrg_event_stop_propagate (e);
+  changed++;
+  tweaked_state (e->mrg);
+}
+
+
+static void remove_key (MrgEvent *e, void *data1, void *data2)
+{
+  gpointer *data = data1;
+  GeglEDL             *edl   = data[0];
+  GeglNode            *node  = data[1];
+  const char          *pname = data[2];
+  int  clip_frame_no  = GPOINTER_TO_INT(data[3]);
+  char tmpbuf[1024];
+  sprintf (tmpbuf, "%s-anim", pname);
+  GQuark anim_quark = g_quark_from_string (tmpbuf);
+
+  fprintf (stderr, "remove key %p %s %i\n", node, pname, clip_frame_no);
+
+  if (g_object_get_qdata (G_OBJECT (node), anim_quark))
+  {
+    GeglPath *path = g_object_get_qdata (G_OBJECT (node), anim_quark);
+    int nodes = gegl_path_get_n_nodes (path);
+    int i;
+    int clip_frame_no=0;
+    GeglPathItem path_item;
+    gedl_get_clip (edl, edl->frame_no, &clip_frame_no);
+
+    for (i = 0; i < nodes; i ++)
+    {
+      gegl_path_get_node (path, i, &path_item);
+      if (fabs (path_item.point[0].x - clip_frame_no) < 0.5)
+      {
+        gegl_path_remove_node (path, i);
+        break;
+      }
+    }
+  }
+
+  mrg_queue_draw (e->mrg, NULL);
+  mrg_event_stop_propagate (e);
+  changed++;
+  tweaked_state (e->mrg);
+}
+
+static void drag_int_slider (MrgEvent *e, void *data1, void *data2)
+{
+  GeglParamSpecInt *gspec = (void*)data2;
+  GParamSpec       *spec  = (void*)data2;
+  GeglNode         *node  = (void*)data1;
+  char tmpbuf[1024];
+  sprintf (tmpbuf, "%s-rel", spec->name);
+  GQuark rel_quark = g_quark_from_string (tmpbuf);
+  double ui_min = gspec->ui_minimum;
+  double ui_max = gspec->ui_maximum;
+  if (g_object_get_qdata (G_OBJECT (node), rel_quark) && 1)
+    {
+      ui_min /= 1000.0;
+      ui_max /= 1000.0;
+    }
+
+  gint new_val = e->x * (ui_max - ui_min) + ui_min;
+  gegl_node_set (node, spec->name, new_val, NULL);
+
+  mrg_queue_draw (e->mrg, NULL);
+  mrg_event_stop_propagate (e);
+  changed++;
+  tweaked_state (e->mrg);
+}
+
+static void update_string (const char *new_string, void *user_data)
+{
+  if (snode && sprop)
+    gegl_node_set (snode, sprop, new_string, NULL);
+  ui_tweaks++;
+}
+
+float print_props (Mrg *mrg, GeglEDL *edl, GeglNode *node, float x, float y)
+{
+  unsigned int n_props;
+  GParamSpec ** props = gegl_operation_list_properties (gegl_node_get_operation (node),
+                      &n_props);
+
+  for (int i = 0; i <n_props; i ++)
+  {
+    char *str = NULL;
+    mrg_set_xy (mrg, x, y);
+    GType type = props[i]->value_type;
+
+    char tmpbuf[1024];
+
+    sprintf (tmpbuf, "%s-rel", props[i]->name);
+    GQuark rel_quark = g_quark_from_string (tmpbuf);
+    sprintf (tmpbuf, "%s-anim", props[i]->name);
+    GQuark anim_quark = g_quark_from_string (tmpbuf);
+    mrg_set_xy (mrg, x, y);
+
+    if (g_type_is_a (type, G_TYPE_DOUBLE))
+    {
+      GeglParamSpecDouble *gspec = (void*)props[i];
+      double val;
+      gegl_node_get (node, props[i]->name, &val, NULL);
+      double width = mrg_width (mrg) - x - mrg_em(mrg) * 15;
+      double ui_min = gspec->ui_minimum;
+      double ui_max = gspec->ui_maximum;
+
+      if (g_object_get_qdata (G_OBJECT (node), rel_quark) && 1)
+      {
+        ui_min /= 1000.0;
+        ui_max /= 1000.0;
+      }
+
+      cairo_save (mrg_cr (mrg));
+      cairo_translate (mrg_cr (mrg), x + mrg_em(mrg) * 10, y - mrg_em(mrg));
+      cairo_rectangle (mrg_cr (mrg), 0, 0,
+                       width,
+                       mrg_em (mrg));
+      cairo_save (mrg_cr (mrg));
+      cairo_scale (mrg_cr (mrg), width, 1.0);
+
+      {
+      gpointer *data = g_new0 (gpointer, 3);
+      data[0]=edl;
+      data[1]=node;
+      data[2]=gspec;
+      mrg_listen_full (mrg, MRG_DRAG, drag_double_slider, data, gspec, (void*)g_free, NULL);
+      }
+
+      cairo_restore (mrg_cr (mrg));
+      cairo_set_source_rgba (mrg_cr (mrg), 1,1,1,1.0);
+      cairo_stroke (mrg_cr (mrg));
+
+      cairo_rectangle (mrg_cr (mrg), 0,
+                       0, (val - ui_min) / (ui_max - ui_min) * width,
+                       mrg_em (mrg)  );
+      cairo_set_source_rgba (mrg_cr (mrg), 1,1,1,1.0);
+      cairo_fill (mrg_cr (mrg));
+
+      cairo_restore (mrg_cr (mrg));
+
+      str = g_strdup_printf ("%s:%f", props[i]->name, val);
+      while (str[strlen(str)-1]=='0')
+      {
+        if (str[strlen(str)-2]=='.')
+          break;
+        str[strlen(str)-1]='\0';
+      }
+      mrg_printf (mrg, "%s", str);
+    }
+    else if (g_type_is_a (type, G_TYPE_INT))
+    {
+      GeglParamSpecDouble *gspec = (void*)props[i];
+      gint val;
+      gegl_node_get (node, props[i]->name, &val, NULL);
+      double width = mrg_width (mrg) - x - mrg_em(mrg) * 15;
+      double ui_min = gspec->ui_minimum;
+      double ui_max = gspec->ui_maximum;
+
+      if (g_object_get_qdata (G_OBJECT (node), rel_quark) && 1)
+      {
+        ui_min /= 1000.0;
+        ui_max /= 1000.0;
+      }
+
+      cairo_save (mrg_cr (mrg));
+      cairo_translate (mrg_cr (mrg), x + mrg_em(mrg) * 10, y - mrg_em(mrg));
+      cairo_rectangle (mrg_cr (mrg), 0, 0,
+                       width,
+                       mrg_em (mrg));
+      cairo_save (mrg_cr (mrg));
+      cairo_scale (mrg_cr (mrg), width, 1.0);
+
+      mrg_listen (mrg, MRG_DRAG, drag_int_slider, node, gspec);
+      cairo_restore (mrg_cr (mrg));
+      cairo_set_source_rgba (mrg_cr (mrg), 1,1,1,1.0);
+      cairo_stroke (mrg_cr (mrg));
+
+      cairo_rectangle (mrg_cr (mrg), 0,
+                       0,
+                       (val - ui_min) / (ui_max - ui_min) * width,
+                       mrg_em (mrg)  );
+      cairo_set_source_rgba (mrg_cr (mrg), 1,1,1,1.0);
+      cairo_fill (mrg_cr (mrg));
+
+      cairo_restore (mrg_cr (mrg));
+
+      str = g_strdup_printf ("%s:%d", props[i]->name, val);
+      mrg_printf (mrg, "%s", str);
+    }
+    else if (g_type_is_a (type, G_TYPE_BOOLEAN))
+    {
+      gboolean val;
+      gegl_node_get (node, props[i]->name, &val, NULL);
+      str = g_strdup_printf ("%s:%s", props[i]->name, val?"yes":"no");
+      mrg_text_listen (mrg, MRG_CLICK, toggle_bool, node, (void*)g_intern_string(props[i]->name));
+      mrg_printf (mrg, "%s", str);
+      mrg_text_listen_done (mrg);
+    }
+    else if (g_type_is_a (type, G_TYPE_STRING))
+    {
+      char *val = NULL;
+      gegl_node_get (node, props[i]->name, &val, NULL);
+      mrg_printf (mrg, "%s: \"", props[i]->name);
+      if (snode && !strcmp (props[i]->name, sprop))
+      {
+        mrg_edit_start (mrg, update_string, edl);
+      }
+      else
+        mrg_text_listen (mrg, MRG_CLICK, edit_string, node, (void*)g_intern_string(props[i]->name));
+      mrg_printf (mrg, "%s", val);
+
+      if (snode && !strcmp (props[i]->name, sprop))
+        mrg_edit_end (mrg);
+      else
+        mrg_text_listen_done (mrg);
+      mrg_printf (mrg, "\"");
+      g_free (val);
+      str= g_strdup ("");
+    }
+    else
+    {
+      str = g_strdup_printf ("%s: [todo: handle this property type]", props[i]->name);
+      mrg_printf (mrg, "%s", str);
+    }
+
+    if (str)
+    {
+      g_free (str);
+      y -= mrg_em (mrg) * 1.2;
+    }
+
+    if (g_object_get_qdata (G_OBJECT (node), rel_quark))
+       mrg_printf (mrg, "rel");
+    if (g_object_get_qdata (G_OBJECT (node), anim_quark))
+    {
+       GeglPath *path = g_object_get_qdata (G_OBJECT (node), anim_quark);
+       int clip_frame_no;
+       gedl_get_clip (edl, edl->frame_no, &clip_frame_no);
+       mrg_printf (mrg, "{anim}");
+       {
+         GeglPathItem path_item;
+         int nodes = gegl_path_get_n_nodes (path);
+         int j;
+         for (j = 0 ; j < nodes; j ++)
+         {
+           gegl_path_get_node (path, j, &path_item);
+           if (fabs (path_item.point[0].x - clip_frame_no) < 0.5)
+           {
+      gpointer *data = g_new0 (gpointer, 4);
+      data[0]=edl;
+      data[1]=node;
+      data[2]=(void*)g_intern_string(props[i]->name);
+      data[3]=GINT_TO_POINTER(clip_frame_no);
+             mrg_text_listen_full (mrg, MRG_CLICK, remove_key, data, node, (void*)g_free, NULL);
+             mrg_printf (mrg, "(key)");
+             mrg_text_listen_done (mrg);
+           }
+         }
+         // if this prop has keyframe here - permit deleting it from
+         // here
+       }
+
+       cairo_t *cr = mrg_cr (mrg);
+
+       cairo_save (cr);
+
+       cairo_scale (cr, 1.0/edl->scale, 1);
+       cairo_translate (cr,  (edl->active_clip?edl->active_clip->abs_start:0)-edl->t0,
+                        mrg_height (mrg) * SPLIT_VER);
+
+       {
+         int j;
+         gdouble y = 0.0;
+         gdouble miny = 100000.0;
+         gdouble maxy = -100000.0;
+
+         // todo: draw markers for zero, min and max, with labels
+         //       do all curves in one scaled space? - will break for 2 or more magnitudes diffs
+
+         for (j = -10; j < clip_get_frames (edl->active_clip) + 10; j ++)
+         {
+           gegl_path_calc_y_for_x (path, j, &y);
+           if (y < miny) miny = y;
+           if (y > maxy) maxy = y;
+         }
+
+         cairo_new_path (cr);
+         gegl_path_calc_y_for_x (path, 0, &y);
+         y = VID_HEIGHT * 0.9 - ((y - miny) / (maxy - miny)) * VID_HEIGHT * 0.8;
+         cairo_move_to (cr, 0, y);
+         for (j = -10; j < clip_get_frames (edl->active_clip) + 10; j ++)
+         {
+           gegl_path_calc_y_for_x (path, j, &y);
+           y = VID_HEIGHT * 0.9 - ((y - miny) / (maxy - miny)) * VID_HEIGHT * 0.8;
+           cairo_line_to (cr, j, y);
+         }
+
+
+       cairo_restore (cr);
+       cairo_set_line_width (cr, 2.0);
+       cairo_set_source_rgba (cr, 1.0, 0.5, 0.5, 255);
+       cairo_stroke (cr);
+       cairo_save (cr);
+
+       cairo_translate (cr,  ((edl->active_clip?edl->active_clip->abs_start:0)-edl->t0) * 1.0 / edl->scale,
+                        mrg_height (mrg) * SPLIT_VER);
+
+       cairo_set_source_rgba (cr, 1.0, 0.5, 0.5, 255);
+           int nodes = gegl_path_get_n_nodes (path);
+           GeglPathItem path_item;
+
+          for (j = 0; j < nodes; j ++)
+          {
+            gegl_path_get_node (path, j, &path_item);
+
+            cairo_arc (cr, path_item.point[0].x * 1.0/edl->scale, -0.5 * mrg_em (mrg),
+                       mrg_em (mrg) * 0.5, 0.0, 3.1415*2);
+            mrg_listen (mrg, MRG_PRESS, jump_to_pos, edl, GINT_TO_POINTER( (int)(path_item.point[0].x + 
edl->active_clip->abs_start)));
+            cairo_fill (cr);
+          }
+       cairo_restore (cr);
+       }
+    }
+    if (g_object_get_qdata (G_OBJECT (node), g_quark_from_string (props[i]->name)))
+       mrg_printf (mrg, "{???}");
+  }
+
+  return y;
+}
+
+static Clip *ui_clip = NULL;
+static GeglNode *source_start;
+static GeglNode *source_end;
+static GeglNode *filter_end;
+
+
+static void select_node (MrgEvent *e, void *data1, void *data2)
+{
+  if (selected_node == data1)
+    selected_node = NULL;
+  else
+    selected_node = data1;
+  snode = NULL;
+  sprop = NULL;
+
+  mrg_event_stop_propagate (e);
+  mrg_queue_draw (e->mrg, NULL);
+}
+
+float print_nodes (Mrg *mrg, GeglEDL *edl, GeglNode *node, float x, float y)
+{
+    while (node)
+    {
+
+      if ((node != source_start) &&
+          (node != source_end) &&
+          (node != filter_start) &&
+          (node != filter_end))
+      {
+        if (node == selected_node)
+          y = print_props (mrg, edl, node, x + mrg_em(mrg) * 0.5, y);
+
+        mrg_set_xy (mrg, x, y);
+        mrg_text_listen (mrg, MRG_CLICK, select_node, node, NULL);
+        mrg_printf (mrg, "%s", gegl_node_get_operation (node));
+        mrg_text_listen_done (mrg);
+        y -= mrg_em (mrg) * 1.5;
+      }
+
+      GeglNode **nodes = NULL;
+      const gchar **pads = NULL;
+
+      int count = gegl_node_get_consumers (node, "output", &nodes, &pads);
+      if (count)
+      {
+        node = nodes[0];
+        if (strcmp (pads[0], "input"))
+          node = NULL;
+      }
+      else
+        node = NULL;
+      g_free (nodes);
+      g_free (pads);
+      //if (node && node == clip->nop_crop)
+       // node = NULL;
+      if (node)
+      {
+        GeglNode *iter = gegl_node_get_producer (node, "aux", NULL);
+        if (iter)
+        {
+          GeglNode *next;
+          do {
+            next = gegl_node_get_producer (iter, "input", NULL);
+            if (next) iter = next;
+          } while(next);
+
+          y = print_nodes (mrg, edl, iter, x + mrg_em (mrg) * 2, y);
+        }
+      }
+    }
+    return y;
+}
+
+void update_ui_clip (Clip *clip, int clip_frame_no)
+{
+  GError *error = NULL;
+  if (ui_clip == NULL ||
+      ui_clip != clip)
+  {
+    selected_node = NULL;
+    snode = NULL;
+    if (source_start)
+     {
+       remove_in_betweens (source_start, source_end);
+       g_object_unref (source_start);
+       source_start = NULL;
+       g_object_unref (source_end);
+       source_end = NULL;
+     }
+
+    if (filter_start)
+     {
+       remove_in_betweens (filter_start, filter_end);
+       g_object_unref (filter_start);
+       filter_start = NULL;
+       g_object_unref (filter_end);
+       filter_end = NULL;
+     }
+
+    source_start = gegl_node_new ();
+    source_end   = gegl_node_new ();
+
+    gegl_node_set (source_start, "operation", "gegl:nop", NULL);
+    gegl_node_set (source_end, "operation", "gegl:nop", NULL);
+    gegl_node_link_many (source_start, source_end, NULL);
+
+    if (clip->is_chain)
+      gegl_create_chain (clip->path, source_start, source_end,
+                         clip->edl->frame_no - clip->abs_start,
+                         1.0, NULL, &error);
+
+    filter_start = gegl_node_new ();
+    filter_end = gegl_node_new ();
+
+    gegl_node_set (filter_start, "operation", "gegl:nop", NULL);
+    gegl_node_set (filter_end,   "operation", "gegl:nop", NULL);
+
+    gegl_node_link_many (filter_start, filter_end, NULL);
+    if (clip->filter_graph)
+    {
+      gegl_create_chain (clip->filter_graph, filter_start, filter_end,
+                         clip->edl->frame_no - clip->abs_start,
+                         1.0, NULL, &error);
+    }
+    ui_clip = clip;
+  }
+
+  if (selected_node)
+  {
+    unsigned int n_props;
+
+    if (ui_tweaks)
+    {
+      char *serialized_filter = NULL;
+      char *serialized_source = NULL;
+      serialized_filter = gegl_serialize (filter_start, filter_end,
+                                   NULL,GEGL_SERIALIZE_TRIM_DEFAULTS|GEGL_SERIALIZE_VERSION);
+      serialized_source = gegl_serialize (source_start, source_end,
+                                   NULL,GEGL_SERIALIZE_TRIM_DEFAULTS|GEGL_SERIALIZE_VERSION);
+
+      if (clip->filter_graph)
+      {
+        gchar *old = clip->filter_graph;
+
+        if (g_str_has_suffix (serialized_filter, "gegl:nop opi=0:0"))
+        { /* XXX: ugly hack - we remove the common bit we do not want */
+          serialized_filter[strlen(serialized_filter)-strlen("gegl:nop opi=0:0")]='\0';
+        }
+        clip->filter_graph = serialized_filter;
+        g_free (old);
+        fprintf (stderr, "{%s}\n", clip->filter_graph);
+      }
+      else
+        g_free (serialized_filter);
+
+      if (clip->is_chain)
+      {
+        if (g_str_has_suffix (serialized_source, "gegl:nop opi=0:0"))
+        { /* XXX: ugly hack - we remove the common bit we do not want */
+          serialized_source[strlen(serialized_source)-strlen("gegl:nop opi=0:0")]='\0';
+        }
+        clip_set_path (clip, serialized_source);
+      }
+      else
+        g_free (serialized_source);
+
+      ui_tweaks = 0;
+      changed ++;
+
+      gedl_cache_invalid (clip->edl);
+    }
+
+    GParamSpec ** props = gegl_operation_list_properties (gegl_node_get_operation (selected_node), &n_props);
+
+    for (int i = 0; i <n_props; i ++)
+    {
+      char tmpbuf[1024];
+      sprintf (tmpbuf, "%s-anim", props[i]->name);
+      GQuark anim_quark = g_quark_from_string (tmpbuf);
+      // this only deals with double for now
+      if (g_object_get_qdata (G_OBJECT (selected_node), anim_quark))
+      {
+        GeglPath *path = g_object_get_qdata (G_OBJECT (selected_node), anim_quark);
+        gdouble val = 0.0;
+        gegl_path_calc_y_for_x (path, clip_frame_no * 1.0, &val);
+
+        gegl_node_set (selected_node, props[i]->name, val, NULL);
+      }
+    }
+  }
+}
+
+/* XXX: add some constarint?  like having input, or output pad or both - or
+ * being a specific subclass - as well as sort, so that best (prefix_) match
+ * comes first
+ */
+char **gedl_get_completions (const char *filter_query)
+{
+  gchar **completions = NULL;
+  gchar **operations = gegl_list_operations (NULL);
+  gchar *alloc = NULL;
+  int matches = 0;
+  int memlen = sizeof (gpointer);
+
+  // score matches, sort them - and default to best
+
+  for (int i = 0; operations[i]; i++)
+  {
+    if (strstr (operations[i], filter_query))
+    {
+      matches ++;
+      memlen += strlen (operations[i]) + 1 + sizeof (gpointer);
+    }
+  }
+
+  completions = g_malloc0 (memlen);
+  alloc = ((void*)completions) + (matches + 1) * sizeof (gpointer);
+
+  int match = 0;
+  for (int i = 0; operations[i]; i++)
+  {
+    if (strstr (operations[i], filter_query))
+    {
+      completions[match] = alloc;
+      strcpy (alloc, operations[i]);
+      alloc += strlen (alloc) + 1;
+      match++;
+    }
+  }
+  g_free (operations);
+
+  if (match == 0)
+  {
+    return NULL;
+    g_free (completions);
+  }
+  return completions;
+}
+
+static void complete_filter_query_edit (MrgEvent *e, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  gchar **completions = gedl_get_completions (filter_query);
+  if (!selected_node)
+    selected_node = filter_start;
+
+  if (!completions)
+    return;
+
+  GeglNode *new = NULL;
+  new = gegl_node_new_child (edl->gegl, "operation", completions[0], NULL);
+  if (filter_query)
+    g_free (filter_query);
+  filter_query = NULL;
+  insert_node (selected_node, new);
+  selected_node = new;
+
+
+  g_free (completions);
+  ui_tweaks++;
+  gedl_cache_invalid (edl);
+  mrg_queue_draw (e->mrg, NULL);
+  mrg_event_stop_propagate (e);
+}
+
+static void end_filter_query_edit (MrgEvent *e, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  if (filter_query)
+    g_free (filter_query);
+  filter_query = NULL;
+  mrg_event_stop_propagate (e);
+  
+  ui_tweaks++;
+  gedl_cache_invalid (edl);
+  mrg_queue_draw (e->mrg, NULL);
+}
+
+static void update_filter_query (const char *new_string, void *user_data)
+{
+  if (filter_query)
+    g_free (filter_query);
+  filter_query = g_strdup (new_string);
+}
+
+
+void gedl_draw (Mrg     *mrg,
+                GeglEDL *edl,
+                double   x0,
+                double    y,
+                double  fpx,
+                double   t0)
+{
+
+  GList *l;
+  cairo_t *cr = mrg_cr (mrg);
+  double t;
+  int duration = gedl_get_duration (edl); // causes update of abs_start
+
+  VID_HEIGHT = mrg_height (mrg) * (1.0 - SPLIT_VER) * 0.8;
+  int scroll_height = mrg_height (mrg) * (1.0 - SPLIT_VER) * 0.2;
+  t = 0;
+  int clip_frame_no;
+
+  if (duration == 0)
+    return;
+
+  float y2 = y - mrg_em (mrg) * 1.5;
+
+  edl->active_clip = gedl_get_clip (edl, edl->frame_no, &clip_frame_no);
+
+  if (edl->active_clip) // && edl->active_clip->filter_graph)
+  {
+    Clip *clip = edl->active_clip;
+
+    update_ui_clip (clip, clip_frame_no);
+
+    mrg_set_style (mrg, "font-size: 3%; background-color: #0008; color: #fff");
+
+    if (clip->is_chain)
+    {
+      GeglNode *iter = source_end;
+      while (gegl_node_get_producer (iter, "input", NULL))
+      {
+        iter = gegl_node_get_producer (iter, "input", NULL);
+      }
+      y2 = print_nodes (mrg, edl, iter, mrg_em (mrg), y2);
+    }
+    else
+    {
+      mrg_set_xy (mrg, mrg_em(mrg) * 1, y2);
+      mrg_printf (mrg, "%s", clip->path);
+      y2 -= mrg_em (mrg) * 1.5;
+    }
+    y2 = print_nodes (mrg, edl, filter_start, mrg_em (mrg), y2);
+
+    if (filter_query)
+    {
+      mrg_set_xy (mrg, mrg_em(mrg) * 1, y2);
+      mrg_edit_start (mrg, update_filter_query, edl);
+      mrg_printf (mrg, "%s", filter_query);
+      mrg_edit_end (mrg);
+    
+      mrg_add_binding (mrg, "escape", NULL, "end edit", end_filter_query_edit, edl);
+      mrg_add_binding (mrg, "return", NULL, "end edit", complete_filter_query_edit, edl);
+
+      // XXX: print completions that are clickable?
+      {
+        gchar **operations = gedl_get_completions (filter_query);
+        mrg_start (mrg, NULL, NULL);
+        mrg_set_xy (mrg, mrg_em(mrg) * 1, mrg_height (mrg) * SPLIT_VER + mrg_em (mrg));
+        gint matches=0;
+        for (int i = 0; operations[i] && matches < 40; i++)
+          {
+            mrg_printf (mrg, "%s", operations[i]);
+            mrg_printf (mrg, " ");
+            matches ++;
+          }
+        mrg_end(mrg);
+        g_free (operations);
+      }
+    }
+  }
+
+  cairo_set_source_rgba (cr, 1, 1,1, 1);
+
+  if (edl->playing)
+  {
+    scroll_to_fit (edl, mrg);
+    t0 = edl->t0;
+  }
+
+  cairo_save (cr);
+  {
+    cairo_scale (cr, 1.0 / duration * mrg_width (mrg), 1.0);
+  }
+
+  y += VID_HEIGHT;
+
+  cairo_rectangle (cr, t0, y, mrg_width(mrg)*fpx, scroll_height);
+  mrg_listen (mrg, MRG_DRAG, drag_t0, edl, edl);
+  cairo_set_source_rgba (cr, 1, 1, 0.5, 0.25);
+  if (edl->playing)
+  cairo_stroke (cr);
+  else
+  cairo_fill (cr);
+
+  cairo_rectangle (cr, t0 + mrg_width(mrg)*fpx*0.9, y, mrg_width(mrg)*fpx * 0.1, scroll_height);
+  mrg_listen (mrg, MRG_DRAG, drag_fpx, edl, edl);
+  cairo_fill (cr);
+
+  /* we could cull drawing already here, we let cairo do it for now, */
+
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    int frames = clip_get_frames (clip);
+    cairo_rectangle (cr, t, y, frames, scroll_height);
+    cairo_stroke (cr);
+    t += frames;
+  }
+
+  int start = 0, end = 0;
+  gedl_get_range (edl, &start, &end);
+  cairo_rectangle (cr, start, y, end - start, scroll_height);
+  cairo_set_source_rgba (cr, 0, 0.11, 0.0, 0.5);
+  cairo_fill_preserve (cr);
+  cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
+  cairo_stroke (cr);
+
+  {
+    double frame = edl->frame_no;
+    if (fpx < 1.0)
+      cairo_rectangle (cr, frame, y-5, 1.0, 5 + scroll_height);
+    else
+      cairo_rectangle (cr, frame, y-5, fpx, 5 + scroll_height);
+    cairo_set_source_rgba (cr,1,0,0,0.85);
+    cairo_fill (cr);
+  }
+
+  cairo_restore (cr);
+  y -= VID_HEIGHT;
+  t = 0;
+
+  cairo_move_to (cr, x0 + PAD_DIM, y + VID_HEIGHT + PAD_DIM * 3);
+
+  cairo_save (cr);
+  cairo_translate (cr,  x0, 0);
+  cairo_scale (cr, 1.0/fpx, 1);
+  cairo_translate (cr, -t0, 0);
+
+  gedl_get_selection (edl, &start, &end);
+  cairo_rectangle (cr, start + 0.5, y - PAD_DIM, end - start, VID_HEIGHT + PAD_DIM * 2);
+  cairo_set_source_rgba (cr, 1, 0, 0, 0.75);
+  cairo_fill (cr);
+
+  cairo_rectangle (cr, t0, y, mrg_width(mrg)*fpx, VID_HEIGHT);
+  mrg_listen (mrg, MRG_DROP, drag_dropped, edl, edl);
+  cairo_new_path (cr);
+
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    int frames = clip_get_frames (clip);
+    if (clip->is_meta)
+    {
+      double tx = t, ty = y;
+      cairo_save (cr);
+      cairo_user_to_device (cr, &tx, &ty);
+      cairo_identity_matrix (cr);
+      mrg_set_xy (mrg, tx, y + VID_HEIGHT);
+      mrg_printf (mrg, "%s", clip->filter_graph); // only used for annotations for now - could script vars
+      cairo_restore (cr);
+    }
+    else
+    {
+      Clip *next = clip_get_next (clip);
+      render_clip (mrg, edl, clip->path, clip->start, frames, t, y, clip->fade, next?next->fade:0);
+      /* .. check if we are having anim things going on.. if so - print it here  */
+    }
+
+    if (clip == edl->active_clip)
+      cairo_set_source_rgba (cr, 1, 1, 0.5, 1.0);
+    else
+      cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
+
+    mrg_listen (mrg, MRG_PRESS, clicked_clip, clip, edl);
+    mrg_listen (mrg, MRG_DRAG, drag_clip, clip, edl);
+    mrg_listen (mrg, MRG_RELEASE, released_clip, clip, edl);
+    cairo_stroke (cr);
+
+    t += frames;
+  }
+
+  if (!edl->playing){
+     static gint bitlen = 0;
+     static guchar *bitmap;
+     static long bitticks = 0;
+     int i;
+     int state = -1;
+     int length = 0;
+
+     if (bitlen && ( babl_ticks() - bitticks > 1000 * 1000 * 2))
+     {
+       /* update cache bitmap if it is more than 2s old */
+       bitlen = 0;
+       g_free (bitmap);
+       bitmap = NULL;
+     }
+
+     if (bitlen == 0)
+     {
+       bitmap = gedl_get_cache_bitmap (edl, &bitlen);
+       bitticks = babl_ticks ();
+     }
+     cairo_set_source_rgba (cr, 0.3, 1, 0.3, 1.0);
+     for (i = 0; i < bitlen * 8; i++)
+     {
+        if (bitmap[i / 8] & (1<< (i%8)))
+        {
+          if (state == 1)
+          {
+            length++;
+          }
+          else
+          {
+            length = 0;
+            state = 1;
+          }
+        }
+        else
+        {
+          if (state == 0)
+          {
+            length++;
+          }
+          else
+          {
+            cairo_rectangle (cr, i-length, y, length + 1, VID_HEIGHT * 0.05);
+            length = 0;
+            state = 0;
+          }
+        }
+     }
+     cairo_fill (cr);
+  }
+
+  double frame = edl->frame_no;
+  if (fpx < 1.0)
+    cairo_rectangle (cr, frame, y-PAD_DIM, 1.0, VID_HEIGHT + PAD_DIM * 2);
+  else
+    cairo_rectangle (cr, frame, y-PAD_DIM, fpx, VID_HEIGHT + PAD_DIM * 2);
+  cairo_set_source_rgba (cr,1,0,0,1);
+  cairo_fill (cr);
+  cairo_restore (cr);
+
+  cairo_rectangle (cr, 0, y - PAD_DIM, mrg_width (mrg), VID_HEIGHT + PAD_DIM * 4);
+  mrg_listen (mrg, MRG_SCROLL, zoom_timeline, edl, NULL);
+  cairo_new_path (cr);
+}
+
+static const char *css =
+" document { background: black; }"
+"";
+
+#if 0
+static void edit_filter_graph (MrgEvent *event, void *data1, void *data2)
+{ //XXX
+  GeglEDL *edl = data1;
+
+  //edl->active_source = NULL;
+  edl->filter_edited = !edl->filter_edited;
+  mrg_queue_draw (event->mrg, NULL);
+}
+#endif
+
+#if 0
+static void update_filter (const char *new_string, void *user_data)
+{
+  GeglEDL *edl = user_data;
+  if (edl->active_clip->filter_graph)
+    g_free (edl->active_clip->filter_graph);
+  edl->active_clip->filter_graph = g_strdup (new_string);
+  gedl_cache_invalid (edl);
+  mrg_queue_draw (edl->mrg, NULL);
+}
+#endif
+
+static void toggle_ui_mode  (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  edl->ui_mode ++;
+  if (edl->ui_mode > GEDL_LAST_UI_MODE)
+    edl->ui_mode = 0;
+
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  changed++;
+}
+
+void help_ui (Mrg *mrg, GeglEDL *edl)
+{
+  if (help)
+  {
+    MrgBinding * bindings = mrg_get_bindings (mrg);
+    int i;
+
+    //cairo_set_source_rgba (mrg_cr (mrg), 0, 0, 0, 0.85);
+    //cairo_paint (mrg_cr(mrg));
+    mrg_set_font_size (mrg, mrg_height (mrg) / 20.0);
+  mrg_set_style (mrg, "color: white;background: transparent; text-stroke: 4.5px #000");
+    mrg_set_edge_right (mrg, mrg_width (mrg) - mrg_em (mrg) *2);
+    mrg_set_edge_left (mrg, mrg_em (mrg));
+    mrg_set_xy (mrg, mrg_em (mrg), mrg_em (mrg) * 2);
+
+
+    for (i = 0; bindings[i].cb; i++)
+    {
+      if (bindings[i].label)
+        mrg_printf_xml (mrg, "<div style='display:inline-block; padding-right: 1em;'><b>%s</b>&nbsp;%s</div> 
 ", bindings[i].nick, bindings[i].label);
+    }
+  }
+  else
+  {
+    mrg_set_xy (mrg, mrg_width(mrg) - 10 * mrg_em (mrg), mrg_height(mrg) * SPLIT_VER);
+    mrg_printf (mrg, "F1 toggle help");
+  }
+}
+
+gchar *message = NULL;
+
+extern int cache_hits;
+extern int cache_misses;
+
+long babl_ticks (void);
+
+void gedl_ui (Mrg *mrg, void *data)
+{
+  State *o = data;
+  GeglEDL *edl = o->edl;
+
+  int long start_time = babl_ticks ();
+
+  mrg_stylesheet_add (mrg, css, NULL, 0, NULL);
+  mrg_set_style (mrg, "font-size: 11px");
+#if 0
+  cairo_set_source_rgb (mrg_cr (mrg), 0,0,0);
+  cairo_paint (mrg_cr (mrg));
+#endif
+
+  if (message)
+  {
+    mrg_set_style (mrg, "font-size: 0.1yh");
+    mrg_printf (mrg, "%s", message);
+    return;
+  }
+
+  /* XXX: sync ui changes here, rather than deferred  */
+
+  g_mutex_lock (&edl->buffer_copy_mutex);
+  if (edl->buffer_copy_temp)
+    g_object_unref (edl->buffer_copy_temp);
+  edl->buffer_copy_temp = edl->buffer_copy;
+  g_object_ref (edl->buffer_copy);
+  gegl_node_set (edl->cached_result, "buffer", edl->buffer_copy_temp, NULL);
+  g_mutex_unlock (&edl->buffer_copy_mutex);
+
+  switch (edl->ui_mode)
+  {
+     case GEDL_UI_MODE_FULL:
+     case GEDL_UI_MODE_TIMELINE:
+     case GEDL_UI_MODE_NONE:
+        mrg_gegl_blit (mrg, (int)(mrg_width (mrg) * 0.0), 0,
+                      (int)(mrg_width (mrg) * 1.0),
+                      mrg_height (mrg),// * SPLIT_VER,
+                      o->edl->cached_result,
+                      0, 0,
+        /* opacity */ 1.0 //edl->frame_no == done_frame?1.0:0.5
+                      ,edl);
+        break;
+     case GEDL_UI_MODE_PART:
+        mrg_gegl_blit (mrg, (int)(mrg_width (mrg) * 0.2), 0,
+                      (int)(mrg_width (mrg) * 0.8),
+                      mrg_height (mrg) * SPLIT_VER,
+                      o->edl->cached_result,
+                      0, 0,
+        /* opacity */ 1.0 //edl->frame_no == done_frame?1.0:0.5
+                      ,edl);
+        break;
+  }
+
+
+  switch (edl->ui_mode)
+  {
+     case GEDL_UI_MODE_FULL:
+     case GEDL_UI_MODE_TIMELINE:
+     case GEDL_UI_MODE_PART:
+     gedl_draw (mrg, edl, 0, mrg_height (mrg) * SPLIT_VER, edl->scale, edl->t0);
+
+
+
+
+  break;
+     case GEDL_UI_MODE_NONE:
+        break;
+     break;
+  }
+
+  if(0)fprintf (stderr, "%f\n", 1.0 / ((babl_ticks () - start_time)/1000.0/ 1000.0));
+  if (edl->ui_mode != GEDL_UI_MODE_NONE)
+  {
+
+  mrg_set_xy (mrg, mrg_em (mrg), mrg_height(mrg) * SPLIT_VER);
+  mrg_set_style (mrg, "color: white;background: transparent; text-stroke: 1.5px #000");
+  mrg_set_edge_right (mrg, mrg_width (mrg));// * 0.25 - 8);
+#if 0
+  {
+    GeglRectangle rect;
+    rect = gegl_node_get_bounding_box (o->edl->cached_result);
+
+    mrg_printf (mrg, "%ix%i\n", rect.width, rect.height);
+  }
+#endif
+
+#if 0
+  mrg_printf (mrg, "cache hit: %2.2f%% of %i\n", 100.0 * cache_hits / (cache_hits + cache_misses), 
cache_hits + cache_misses);
+#endif
+
+#if 0
+  if (done_frame != edl->frame_no)
+    mrg_printf (mrg, "frame %i (%i shown)",edl->frame_no, done_frame);
+  else
+#endif
+  mrg_printf (mrg, " %i  ", edl->frame_no);
+
+#if 0
+  if (edl->active_source)
+  {
+    char *basename = g_path_get_basename (edl->active_source->path);
+    mrg_printf (mrg, "%i\n", edl->source_frame_no);
+    mrg_printf (mrg, "%s\n", basename);
+  }
+#endif
+
+  //mrg_printf (mrg, "%i %i %i %i %i\n", edl->frame, edl->frame_no, edl->source_frame_no, rendering_frame, 
done_frame);
+
+  if (!renderer_done (edl))
+    mrg_printf (mrg, "... ");
+
+  }
+
+  if (snode)
+  {
+    mrg_add_binding (mrg, "escape", NULL, "end edit", end_edit, edl);
+  }
+
+  if (!edl->clip_query_edited &&
+      !edl->filter_edited &&
+      !filter_query &&
+      !snode)
+  {
+    mrg_add_binding (mrg, "F1", NULL, "toggle help", toggle_help, edl);
+    mrg_add_binding (mrg, "q", NULL, "quit", (void*)do_quit, mrg);
+
+    if (edl->playing)
+    {
+      mrg_add_binding (mrg, "space", NULL, "pause", renderer_toggle_playing, edl);
+      if (edl->active_clip && edl->frame_no != edl->active_clip->abs_start)
+        mrg_add_binding (mrg, "v", NULL, "split clip", split_clip, edl);
+    }
+    else
+    {
+      mrg_add_binding (mrg, "space", NULL, "play", renderer_toggle_playing, edl);
+
+      mrg_add_binding (mrg, "tab", NULL, "cycle ui amount", toggle_ui_mode, edl);
+      mrg_add_binding (mrg, "e", NULL, "zoom timeline to fit", zoom_fit, edl);
+      mrg_add_binding (mrg, "1", NULL, "zoom timeline 1px = 1 frame", zoom_1, edl);
+      if (edl->use_proxies)
+        mrg_add_binding (mrg, "p", NULL, "don't use proxies", toggle_use_proxies, edl);
+      else
+        mrg_add_binding (mrg, "p", NULL, "use proxies", toggle_use_proxies, edl);
+
+      mrg_add_binding (mrg, "s", NULL, "save", save, edl);
+      mrg_add_binding (mrg, "a", NULL, "select all", select_all, edl);
+
+      mrg_add_binding (mrg, "left/right", NULL, "step frame", step_frame, edl);
+      mrg_add_binding (mrg, "right", NULL, NULL, step_frame, edl);
+      mrg_add_binding (mrg, "left", NULL, NULL, step_frame_back, edl);
+      mrg_add_binding (mrg, "l", NULL, NULL, step_frame, edl);
+      mrg_add_binding (mrg, "h", NULL, NULL, step_frame_back, edl);
+
+      mrg_add_binding (mrg, "up/down", NULL, "previous/next cut", prev_cut, edl);
+      mrg_add_binding (mrg, "up", NULL, NULL, prev_cut, edl);
+      mrg_add_binding (mrg, "k", NULL, NULL, prev_cut, edl);
+      mrg_add_binding (mrg, "down", NULL, NULL, next_cut, edl);
+      mrg_add_binding (mrg, "j", NULL, NULL, next_cut, edl);
+
+      mrg_add_binding (mrg, "shift-left/right", NULL, "extend selection", extend_selection_to_the_right, 
edl);
+      mrg_add_binding (mrg, "shift-right", NULL, NULL, extend_selection_to_the_right, edl);
+      mrg_add_binding (mrg, "shift-left", NULL, NULL,  extend_selection_to_the_left, edl);
+      mrg_add_binding (mrg, "shift-up", NULL, NULL,    extend_selection_to_previous_cut, edl);
+      mrg_add_binding (mrg, "shift-down", NULL, NULL,  extend_selection_to_next_cut, edl);
+      mrg_add_binding (mrg, "L", NULL, NULL, extend_selection_to_the_right, edl);
+      mrg_add_binding (mrg, "H", NULL, NULL,  extend_selection_to_the_left, edl);
+      mrg_add_binding (mrg, "K", NULL, NULL,    extend_selection_to_previous_cut, edl);
+      mrg_add_binding (mrg, "J", NULL, NULL,  extend_selection_to_next_cut, edl);
+
+      if (empty_selection (edl))
+      {
+        mrg_add_binding (mrg, "x", NULL, "remove clip", remove_clip, edl);
+        mrg_add_binding (mrg, "d", NULL, "duplicate clip", duplicate_clip, edl);
+
+        if (edl->active_clip)
+        {
+          if (edl->frame_no == edl->active_clip->abs_start)
+          {
+            GList *iter = g_list_find (edl->clips, edl->active_clip);
+            Clip *clip2 = NULL;
+            if (iter) iter = iter->prev;
+            if (iter) clip2 = iter->data;
+
+
+            if (are_mergable (clip2, edl->active_clip, 0))
+              mrg_add_binding (mrg, "v", NULL, "merge clip", merge_clip, edl);
+          }
+          else
+          {
+            mrg_add_binding (mrg, "v", NULL, "split clip", split_clip, edl);
+          }
+          mrg_add_binding (mrg, "f", NULL, "toggle fade", toggle_fade, edl);
+        }
+
+      }
+      else
+      {
+        mrg_add_binding (mrg, "x", NULL, "cut selection", remove_clip, edl);
+        mrg_add_binding (mrg, "c", NULL, "copy selection", remove_clip, edl);
+        mrg_add_binding (mrg, "r", NULL, "set playback range", set_range, edl);
+      }
+
+      if (edl->active_clip)
+      {
+        mrg_add_binding (mrg, "i", NULL, "insert filter", insert_filter, edl);
+
+        if (edl->frame_no == edl->active_clip->abs_start)
+        {
+
+          if (empty_selection (edl))
+          {
+            mrg_add_binding (mrg, "control-left/right", NULL, "adjust in", clip_start_inc, edl);
+            mrg_add_binding (mrg, "control-right", NULL, NULL, clip_start_inc, edl);
+            mrg_add_binding (mrg, "control-left", NULL, NULL, clip_start_dec, edl);
+            mrg_add_binding (mrg, "control-h", NULL, NULL, clip_start_inc, edl);
+            mrg_add_binding (mrg, "control-l", NULL, NULL, clip_start_dec, edl);
+            mrg_add_binding (mrg, "control-up/down", NULL, "shuffle clip backward/forward", shuffle_back, 
edl);
+            mrg_add_binding (mrg, "control-up", NULL, NULL, shuffle_back, edl);
+            mrg_add_binding (mrg, "control-down", NULL, NULL, shuffle_forward, edl);
+            mrg_add_binding (mrg, "control-k", NULL, NULL, shuffle_back, edl);
+            mrg_add_binding (mrg, "control-j", NULL, NULL, shuffle_forward, edl);
+          }
+        }
+        else
+        {
+          if (empty_selection (edl))
+          {
+            if (edl->frame_no == edl->active_clip->abs_start + clip_get_frames (edl->active_clip)-1)
+            {
+              mrg_add_binding (mrg, "control-left/right", NULL, "adjust out", clip_end_inc, edl);
+              mrg_add_binding (mrg, "control-right", NULL, NULL, clip_end_inc, edl);
+              mrg_add_binding (mrg, "control-left", NULL, NULL, clip_end_dec, edl);
+            }
+            else
+            {
+              mrg_add_binding (mrg, "control-left/right", NULL, "slide clip backward/forward", slide_back, 
edl);
+              mrg_add_binding (mrg, "control-left", NULL, NULL, slide_back, edl);
+              mrg_add_binding (mrg, "control-right", NULL, NULL, slide_forward, edl);
+
+
+              mrg_add_binding (mrg, "control-up/down", NULL, "slide cut window", clip_start_end_inc, edl);
+              mrg_add_binding (mrg, "control-up", NULL, NULL, clip_start_end_inc, edl);
+              mrg_add_binding (mrg, "control-down", NULL, NULL, clip_start_end_dec, edl);
+            }
+          }
+          else {
+            Clip *start_clip = gedl_get_clip (edl, edl->selection_start, NULL);
+            Clip *end_clip = gedl_get_clip (edl, edl->selection_end, NULL);
+            GList *start_iter = g_list_find (edl->clips, start_clip);
+            GList *end_iter = g_list_find (edl->clips, end_clip);
+
+            if (start_iter &&
+                (start_iter->next == end_iter ||
+                start_iter->prev == end_iter))
+            {
+              mrg_add_binding (mrg, "control-left/right", NULL, "move cut", clip_end_start_inc, edl);
+              mrg_add_binding (mrg, "control-right", NULL, NULL, clip_end_start_inc, edl);
+              mrg_add_binding (mrg, "control-left", NULL, NULL, clip_end_start_dec, edl);
+            }
+          }
+        }
+      }
+    }
+  }
+
+#if 0
+  if (edl->active_source)
+    mrg_add_binding (mrg, "return", NULL, NULL, toggle_edit_source, edl);
+  else //if (edl->filter_edited)
+    mrg_add_binding (mrg, "return", NULL, NULL, edit_filter_graph, edl);
+#endif
+
+  switch (edl->ui_mode)
+  {
+     case GEDL_UI_MODE_FULL:
+     case GEDL_UI_MODE_TIMELINE:
+     case GEDL_UI_MODE_PART:
+     default:
+        help_ui (mrg, edl);
+        break;
+     case GEDL_UI_MODE_NONE:
+        break;
+  }
+
+  if(0)fprintf (stderr, "b:%f\n", 1.0 / ((babl_ticks () - start_time)/1000.0/ 1000.0));
+}
+
+gboolean cache_renderer_iteration (Mrg *mrg, gpointer data)
+{
+  GeglEDL *edl = data;
+  if (!edl->playing)
+    {
+      int i;
+      int render_slaves = g_get_num_processors ();
+      killpg(0, SIGUSR2); // this will cause previous set of renderers to quite after current frame
+      for (i = 0; i < render_slaves; i ++)
+      {
+        char *cmd = g_strdup_printf ("%s %s cache %i %i&",
+                                     gedl_binary_path,
+                                     edl->path, i, render_slaves);
+        save_edl (edl);
+        system (cmd);
+        g_free (cmd);
+      }
+    }
+  return TRUE;
+}
+
+int gedl_ui_main (GeglEDL *edl);
+int gedl_ui_main (GeglEDL *edl)
+{
+  Mrg *mrg = mrg_new (800, 600, NULL);
+  //Mrg *mrg = mrg_new (-1, -1, NULL);
+  State o = {NULL,};
+  o.mrg = mrg;
+  o.edl = edl;
+
+  edl->mrg = mrg;
+
+  edl->cache_flags = CACHE_TRY_ALL;// | CACHE_MAKE_ALL;
+  mrg_set_ui (mrg, gedl_ui, &o);
+
+  mrg_add_timeout (mrg, 10100, save_idle, edl);
+
+  cache_renderer_iteration (mrg, edl);
+  mrg_add_timeout (mrg, 90 /* seconds */  * 1000, cache_renderer_iteration, edl);
+
+  gedl_get_duration (edl);
+  //mrg_set_target_fps (mrg, -1);
+//  gedl_set_use_proxies (edl, 1);
+  toggle_use_proxies (NULL, edl, NULL);
+  renderer_start (edl);
+  mrg_main (mrg);
+  gedl_free (edl);
+  gegl_exit ();
+
+  return 0;
+}
diff --git a/gcut/gedl.c b/gcut/gedl.c
new file mode 100644
index 0000000..1ac5a36
--- /dev/null
+++ b/gcut/gedl.c
@@ -0,0 +1,1608 @@
+#include <string.h>
+#include <signal.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <gegl.h>
+#include <gexiv2/gexiv2.h>
+#include <gegl-audio-fragment.h>
+
+/* GEGL edit decision list - a digital video cutter and splicer */
+
+/* take a string and expand {t=v i t=v t=v }  to numeric or string
+   value. Having it that way.. makes it hard to keep parts of graph,.
+   unless graph is kept when constructing... and values are filled in
+   if topology of graphs match..
+ */
+
+#include "gedl.h"
+
+#define DEFAULT_output_path      "output.mp4"
+#define DEFAULT_video_codec      "auto"
+#define DEFAULT_audio_codec      "auto"
+#define DEFAULT_video_width       0
+#define DEFAULT_video_height      0
+#define DEFAULT_proxy_width       0
+#define DEFAULT_proxy_height      0
+#define DEFAULT_video_bufsize     0
+#define DEFAULT_video_bitrate     256
+#define DEFAULT_video_tolerance   -1
+#define DEFAULT_audio_bitrate     64
+#define DEFAULT_audio_samplerate  64
+#define DEFAULT_frame_start       0
+#define DEFAULT_frame_end         0
+#define DEFAULT_selection_start   0
+#define DEFAULT_selection_end     0
+#define DEFAULT_range_start       0
+#define DEFAULT_range_end         0
+#define DEFAULT_framedrop         0
+
+char *gedl_binary_path = NULL;
+
+const char *default_edl =
+#include "default.edl.inc"
+;
+
+static char *escaped_base_path (GeglEDL *edl, const char *clip_path)
+{
+  char *path0= g_strdup (clip_path);
+  char *path = path0;
+  int i;
+  char *ret = 0;
+  if (!strncmp (path, edl->parent_path, strlen(edl->parent_path)))
+      path += strlen (edl->parent_path);
+  for (i = 0; path[i];i ++)
+  {
+    switch (path[i]){
+      case '/':
+      case ' ':
+      case '\'':
+      case '#':
+      case '%':
+      case '?':
+      case '*':
+        path[i]='_';
+        break;
+    }
+  }
+  ret = g_strdup (path);
+  g_free (path0);
+  return ret;
+}
+
+char *gedl_make_thumb_path (GeglEDL *edl, const char *clip_path)
+{
+  gchar *ret;
+  gchar *path = escaped_base_path (edl, clip_path);
+  ret = g_strdup_printf ("%s.gedl/thumb/%s.png", edl->parent_path, path); // XXX: should escape 
relative/absolute path instead of basename - or add bit of its hash
+  g_free (path);
+  return ret;
+}
+
+char *gedl_make_proxy_path (GeglEDL *edl, const char *clip_path)
+{
+  gchar *ret;
+  gchar *path = escaped_base_path (edl, clip_path);
+  ret = g_strdup_printf ("%s.gedl/proxy/%s-%ix%i.mp4", edl->parent_path, path, edl->proxy_width, 
edl->proxy_height);
+  g_free (path);
+  return ret;
+}
+
+
+#include <stdlib.h>
+
+GeglEDL *gedl_new           (void)
+{
+  GeglRectangle roi = {0,0,1024, 1024};
+  GeglEDL *edl = g_malloc0(sizeof (GeglEDL));
+
+  edl->gegl = gegl_node_new ();
+  edl->cache_flags = 0;
+  edl->cache_flags = CACHE_TRY_ALL;
+  //edl->cache_flags = CACHE_TRY_ALL | CACHE_MAKE_ALL;
+  edl->selection_start = 23;
+  edl->selection_end = 42;
+
+  edl->cache_loader     = gegl_node_new_child (edl->gegl, "operation", "gegl:" CACHE_FORMAT  "-load", NULL);
+
+  edl->output_path      = DEFAULT_output_path;
+  edl->video_codec      = DEFAULT_video_codec;
+  edl->audio_codec      = DEFAULT_audio_codec;
+  edl->video_width      = DEFAULT_video_width;
+  edl->video_height     = DEFAULT_video_height;
+  edl->proxy_width      = DEFAULT_proxy_width;
+  edl->proxy_height     = DEFAULT_proxy_height;
+  edl->video_size_default = 1;
+  edl->video_bufsize    = DEFAULT_video_bufsize;
+  edl->video_bitrate    = DEFAULT_video_bitrate;
+  edl->video_tolerance  = DEFAULT_video_tolerance;;
+  edl->audio_bitrate    = DEFAULT_audio_bitrate;
+  edl->audio_samplerate = DEFAULT_audio_samplerate;
+  edl->framedrop        = DEFAULT_framedrop;
+  edl->frame_no         = 0;  /* frame-no in ui shell */
+  edl->frame = -1;            /* frame-no in renderer thread */
+  edl->scale = 1.0;
+
+  edl->buffer = gegl_buffer_new (&roi, babl_format ("R'G'B'A u8"));
+  edl->buffer_copy = gegl_buffer_new (&roi, babl_format ("R'G'B'A u8"));
+  edl->buffer_copy_temp = gegl_buffer_new (&roi, babl_format ("R'G'B'A u8"));
+
+  edl->clip_query = strdup ("");
+  edl->use_proxies = 0;
+
+  g_mutex_init (&edl->buffer_copy_mutex);
+  return edl;
+}
+
+void gedl_set_size (GeglEDL *edl, int width, int height);
+void gedl_set_size (GeglEDL *edl, int width, int height)
+{
+  edl->width = width;
+  edl->height = height;
+}
+
+void     gedl_free          (GeglEDL *edl)
+{
+  while (edl->clips)
+  {
+    clip_free (edl->clips->data);
+    edl->clips = g_list_remove (edl->clips, edl->clips->data);
+  }
+  if (edl->path)
+    g_free (edl->path);
+  if (edl->parent_path)
+    g_free (edl->parent_path);
+
+  g_object_unref (edl->gegl);
+  if (edl->buffer)
+    g_object_unref (edl->buffer);
+  if (edl->buffer_copy)
+    g_object_unref (edl->buffer_copy);
+  if (edl->buffer_copy_temp)
+    g_object_unref (edl->buffer_copy_temp);
+  g_mutex_clear (&edl->buffer_copy_mutex);
+  g_free (edl);
+}
+
+
+Clip *gedl_get_clip (GeglEDL *edl, int frame, int *clip_frame_no)
+{
+  GList *l;
+  int clip_start = 0;
+
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    int clip_frames = clip_get_frames (clip);
+    if (clip->is_meta)
+      continue;
+
+    if (frame - clip_start < clip_frames)
+    {
+      /* found right clip */
+      if (clip_frame_no)
+       *clip_frame_no = (frame - clip_start) + clip_get_start (clip);
+      return clip;
+    }
+    clip_start += clip_frames;
+  }
+  return NULL;
+}
+
+int cache_hits = 0;
+int cache_misses = 0;
+
+void gedl_set_use_proxies (GeglEDL *edl, int use_proxies)
+{
+  int frame;
+  edl->use_proxies = use_proxies;
+
+  if (edl->use_proxies)
+    gedl_set_size (edl, edl->proxy_width, edl->proxy_height);
+  else
+    gedl_set_size (edl, edl->video_width, edl->video_height);
+
+  frame = edl->frame;
+  if (frame > 0)
+  {
+    edl->frame--;
+    gedl_set_frame (edl, frame);
+  }
+
+}
+
+/* computes the hash of a given rendered frame - without altering
+ * any state
+ */
+gchar *gedl_get_frame_hash_full (GeglEDL *edl, int frame,
+                                 Clip **clip0, int *clip0_frame,
+                                 Clip **clip1, int *clip1_frame,
+                                 double *mix)
+{
+  GList *l;
+  int clip_start = 0;
+  int prev_clip_start = 0;
+
+
+
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    int clip_frames = clip_get_frames (clip);
+
+    if (clip->is_meta)
+      continue;
+
+    if (frame - clip_start < clip_frames)
+    {
+      int clip_frame_no = (frame - clip_start) + clip_get_start (clip);
+
+      GList *lp = l->prev;
+      GList *ln = l->next;
+      Clip *prev = lp?lp->data:NULL;
+      Clip *next = ln?ln->data:NULL;
+      int prev_fade_len;
+      int next_fade_len;
+
+      while (prev && prev->is_meta)
+      {
+        lp = lp->prev;
+        prev = lp?lp->data:NULL;
+      }
+
+      while (next && next->is_meta)
+      {
+        ln = ln->next;
+        next = ln?ln->data:NULL;
+      }
+
+      /* XXX: fade in from black if there is no previous clip */
+
+      prev_fade_len = prev ? clip_get_frames (prev) : clip_frames;
+      next_fade_len = next ? clip_get_frames (next) : clip_frames;
+
+      if (prev_fade_len > clip_frames) prev_fade_len = clip_frames;
+      if (next_fade_len > clip_frames) next_fade_len = clip_frames;
+
+      prev_fade_len /= 2;
+      next_fade_len /= 2;  /* 1/4 the length of the smallest of this or other
+                              clip is our maximum fade - should be adjusted to
+                              1/2 later - when perhaps a scaling factor or a
+                              per-case duration is set which gets maxed by this
+                              heuristic   */
+      if (clip->fade/2 < prev_fade_len)
+        prev_fade_len = clip->fade/2;
+
+      if (next)
+      {
+        if (next->fade/2 < next_fade_len)
+          next_fade_len = next->fade/2;
+      }
+
+      if (prev && frame - clip_start < prev_fade_len)                   /* in */
+      {
+        char *clip0_hash = clip_get_frame_hash (clip, clip_frame_no);
+        char *clip1_hash = clip_get_frame_hash (prev, frame - prev_clip_start + clip_get_start (prev));
+        double ratio = 0.5 + ((frame-clip_start) * 1.0 / prev_fade_len)/2;
+        char *str = g_strdup_printf ("%s %s %f", clip1_hash, clip0_hash, ratio);
+        g_free (clip0_hash);
+        g_free (clip1_hash);
+        GChecksum *hash = g_checksum_new (G_CHECKSUM_MD5);
+        g_checksum_update (hash, (void*)str, -1);
+        g_free (str);
+        char *ret = g_strdup (g_checksum_get_string(hash));
+        g_checksum_free (hash);
+        if (clip0) *clip0 = prev;
+        if (clip0_frame) *clip0_frame = frame - prev_clip_start + clip_get_start (prev);
+        if (clip1) *clip1 = clip;
+        if (clip1_frame) *clip1_frame = clip_frame_no;
+        if (mix) *mix = ratio;
+
+        return ret;
+      }
+
+      if (next && frame - clip_start > clip_frames - next_fade_len)/* out*/
+      {
+        char *clip0_hash = clip_get_frame_hash (clip, clip_frame_no);
+        char *clip1_hash = clip_get_frame_hash (next, frame - (clip_start + clip_frames) + clip_get_start 
(next));
+        double ratio = (1.0-(clip_frames-(frame-clip_start)) * 1.0 / next_fade_len)/2;
+        char *str = g_strdup_printf ("%s %s %f", clip0_hash, clip1_hash, ratio);
+        g_free (clip0_hash);
+        g_free (clip1_hash);
+        GChecksum *hash = g_checksum_new (G_CHECKSUM_MD5);
+        g_checksum_update (hash, (void*)str, -1);
+        g_free (str);
+        char *ret = g_strdup (g_checksum_get_string(hash));
+        g_checksum_free (hash);
+        if (clip0) *clip0 = clip;
+        if (clip0_frame) *clip0_frame = clip_frame_no;
+        if (clip1_frame) *clip1_frame = frame - (clip_start +clip_frames) + clip_get_start (next);
+        if (clip1) *clip1 = next;
+        if (mix)   *mix = ratio;
+        return ret;
+      }
+      else
+      {
+        if (clip0) *clip0 = clip;
+        if (clip0_frame) *clip0_frame = clip_frame_no;
+        if (clip1) *clip1 = NULL;
+        if (mix)   *mix = 0.0;
+        return clip_get_frame_hash (clip, clip_frame_no);
+      }
+    }
+    prev_clip_start = clip_start;
+    clip_start += clip_frames;
+  }
+
+  if (clip0) *clip0 = NULL;
+  if (clip0_frame) *clip0_frame = 0;
+  if (clip1_frame) *clip1_frame = 0;
+  if (clip1) *clip1 = NULL;
+  if (mix)   *mix = 0.0;
+  return NULL;
+}
+
+gchar *gedl_get_frame_hash (GeglEDL *edl, int frame)
+{
+  return gedl_get_frame_hash_full (edl, frame, NULL, NULL, NULL, NULL, NULL);
+}
+
+void gedl_update_buffer (GeglEDL *edl)
+{
+  g_mutex_lock (&edl->buffer_copy_mutex);
+  {
+    GeglBuffer *t = edl->buffer_copy;
+    edl->buffer_copy = gegl_buffer_dup (edl->buffer);
+    if (t)
+      g_object_unref (t);
+  }
+  g_mutex_unlock (&edl->buffer_copy_mutex);
+}
+/*  calling this causes gedl to rig up its graphs for providing/rendering this frame
+ */
+void gedl_set_frame (GeglEDL *edl, int frame)
+{
+  if ((edl->frame) == frame && (frame != 0))
+  {
+    return;
+  }
+
+  edl->frame = frame;
+
+  Clip *clip0; int clip0_frame;
+  Clip *clip1; int clip1_frame;
+  double mix;
+
+  char *frame_hash = gedl_get_frame_hash_full (edl, frame, &clip0, &clip0_frame, &clip1, &clip1_frame, &mix);
+  char *cache_path = g_strdup_printf ("%s.gedl/cache/%s", edl->parent_path, frame_hash);
+  g_free (frame_hash);
+  if (g_file_test (cache_path, G_FILE_TEST_IS_REGULAR) &&
+      (edl->cache_flags & CACHE_TRY_ALL))
+  {
+    Clip *clip = NULL;
+    gegl_node_set (edl->cache_loader, "path", cache_path, NULL);
+    gegl_node_link_many (edl->cache_loader, edl->result, NULL);
+    clip = edl_get_clip_for_frame (edl, edl->frame);
+    if (clip)
+    {
+    if (clip->audio)
+      {
+        g_object_unref (clip->audio);
+        clip->audio = NULL;
+      }
+    clip->audio = gegl_audio_fragment_new (44100, 2, 0, 44100);
+    gegl_meta_get_audio (cache_path, clip->audio);
+    }
+    GeglRectangle ext = gegl_node_get_bounding_box (edl->result);
+    gegl_buffer_set_extent (edl->buffer, &ext);
+    gegl_node_process (edl->store_final_buf);
+
+    gedl_update_buffer (edl);
+    g_free (cache_path);
+    return;
+  }
+
+  if (clip0 == NULL)
+  {
+    g_free (cache_path);
+    return;
+  }
+
+  if (clip1 == NULL)
+  {
+    clip_render_frame (clip0, clip0_frame);
+    gegl_node_link_many (clip0->nop_crop, edl->result, NULL);
+  }
+  else
+  {
+    gegl_node_set (edl->mix, "ratio", mix, NULL);
+    clip_render_frame (clip0, clip0_frame);
+    clip_render_frame (clip1, clip1_frame);
+    gegl_node_link_many (clip0->nop_crop, edl->mix, edl->result, NULL);
+    gegl_node_connect_to (clip1->nop_crop, "output", edl->mix, "aux");
+  }
+  gegl_node_process (edl->store_final_buf);
+  gedl_update_buffer (edl);
+
+  /* write cached render of this frame */
+  if (cache_path &&
+      !strstr (clip0->path, ".gedl/cache") && (!edl->use_proxies))
+    {
+      const gchar *cache_path_final = cache_path;
+      gchar *cache_path       = g_strdup_printf ("%s~", cache_path_final);
+
+      if (!g_file_test (cache_path_final, G_FILE_TEST_IS_REGULAR) && !edl->playing)
+        {
+          GeglNode *save_graph = gegl_node_new ();
+          GeglNode *save;
+          save = gegl_node_new_child (save_graph,
+                      "operation", "gegl:" CACHE_FORMAT "-save",
+                      "path", cache_path,
+                      NULL);
+          if (!strcmp (CACHE_FORMAT, "png"))
+          {
+            gegl_node_set (save, "bitdepth", 8, NULL);
+          }
+          gegl_node_link_many (edl->result, save, NULL);
+          gegl_node_process (save);
+          if (clip1 && clip1->audio && mix > 0.5)
+            gegl_meta_set_audio (cache_path, clip1->audio);
+          else if (clip0->audio) // XXX: mix audio
+            gegl_meta_set_audio (cache_path, clip0->audio);
+          rename (cache_path, cache_path_final);
+          g_object_unref (save_graph);
+        }
+      g_free (cache_path);
+    }
+
+  g_free (cache_path);
+}
+
+void gedl_set_time (GeglEDL *edl, double seconds)
+{
+  gedl_set_frame (edl, seconds * edl->fps);
+}
+
+void gedl_set_fps (GeglEDL *edl, double fps)
+{
+  edl->fps = fps;
+}
+double gedl_get_fps (GeglEDL *edl)
+{
+  return edl->fps;
+}
+int    gedl_get_frame (GeglEDL *edl)
+{
+  return edl->frame;
+}
+double gedl_get_time (GeglEDL *edl)
+{
+  return edl->frame / edl->fps;
+}
+GeglAudioFragment *gedl_get_audio (GeglEDL *edl)
+{
+  Clip * clip = edl_get_clip_for_frame (edl, edl->frame);
+  return clip?clip->audio:NULL;
+}
+const char *gedl_get_clip_path (GeglEDL *edl)
+{
+  Clip * clip = edl_get_clip_for_frame (edl, edl->frame);
+  return clip?clip->clip_path:"";
+}
+
+void gedl_get_video_info (const char *path, int *duration, double *fps)
+{
+  GeglNode *gegl = gegl_node_new ();
+  GeglNode *probe = gegl_node_new_child (gegl, "operation",
+                          "gegl:ff-load", "path", path, NULL);
+  gegl_node_process (probe);
+
+  if (duration)
+  gegl_node_get (probe, "frames", duration, NULL);
+  if (fps)
+  gegl_node_get (probe, "frame-rate", fps, NULL);
+  g_object_unref (gegl);
+}
+
+int gedl_get_duration (GeglEDL *edl)
+{
+  int count = 0;
+  GList *l;
+  for (l = edl->clips; l; l = l->next)
+  {
+    ((Clip*)(l->data))->abs_start = count;
+    count += clip_get_frames (l->data);
+  }
+  return count;
+}
+#include <string.h>
+
+void gedl_parse_clip (GeglEDL *edl, const char *line)
+{
+  int start = 0; int end = 0; int duration = 0;
+  const char *rest = NULL;
+  char path[1024];
+  if (line[0] == '#' ||
+      line[1] == '#' ||
+      strlen (line) < 4)
+    return;
+
+  if (strstr (line, "--"))
+    rest = strstr (line, "--") + 2;
+
+  if (rest) while (*rest == ' ')rest++;
+
+  sscanf (line, "%s %i %i %i", path, &start, &end, &duration);
+  if (strlen (path) > 3)
+    {
+      SourceClip *sclip = g_new0 (SourceClip, 1);
+      edl->clip_db = g_list_append (edl->clip_db, sclip);
+      sclip->path = g_strdup (path);
+      sclip->start = start;
+      sclip->end = end;
+      sclip->duration = duration;
+      if (rest)
+        sclip->title = g_strdup (rest);
+    }
+  /* todo: parse hh:mm:ss.nn timestamps,
+   */
+}
+
+void gedl_parse_line (GeglEDL *edl, const char *line)
+{
+  int start = 0; int end = 0;
+  const char *rest = NULL;
+  char path[1024];
+  if (line[0] == '#' ||
+      line[1] == '#' ||
+      strlen (line) < 4)
+    return;
+
+  if (strchr (line, '=') && !strstr(line, "--"))
+   {
+     char *key = g_strdup (line);
+     char *value = strchr (key, '=') + 1;
+     value[-1]='\0';
+
+     while (value[strlen(value)-1]==' ' ||
+            value[strlen(value)-1]=='\n')
+            value[strlen(value)-1]='\0';
+     if (!strcmp (key, "fps"))               gedl_set_fps (edl, g_strtod (value, NULL));
+     if (!strcmp (key, "framedrop"))         edl->framedrop     = g_strtod (value, NULL);
+     if (!strcmp (key, "output-path"))       edl->output_path = g_strdup (value);
+     if (!strcmp (key, "video-codec"))       edl->video_codec = g_strdup (value);
+     if (!strcmp (key, "audio-codec"))       edl->audio_codec = g_strdup (value);
+     if (!strcmp (key, "audio-sample-rate")) edl->audio_samplerate = g_strtod (value, NULL);
+     if (!strcmp (key, "video-bufsize"))     edl->video_bufsize = g_strtod (value, NULL);
+     if (!strcmp (key, "video-bitrate"))     edl->video_bitrate = g_strtod (value, NULL);
+     if (!strcmp (key, "audio-bitrate"))     edl->audio_bitrate = g_strtod (value, NULL);
+     if (!strcmp (key, "video-width"))       edl->video_width = g_strtod (value, NULL);
+     if (!strcmp (key, "video-height"))      edl->video_height = g_strtod (value, NULL);
+     if (!strcmp (key, "proxy-width"))       edl->proxy_width = g_strtod (value, NULL);
+     if (!strcmp (key, "proxy-height"))      edl->proxy_height = g_strtod (value, NULL);
+     if (!strcmp (key, "frame-start"))       edl->range_start = g_strtod (value, NULL);
+     if (!strcmp (key, "frame-end"))         edl->range_end = g_strtod (value, NULL);
+     if (!strcmp (key, "selection-start"))   edl->selection_start = g_strtod (value, NULL);
+     if (!strcmp (key, "selection-end"))     edl->selection_end = g_strtod (value, NULL);
+     //if (!strcmp (key, "range-start"))       edl->range_start = g_strtod (value, NULL);
+     //if (!strcmp (key, "range-end"))         edl->range_end = g_strtod (value, NULL);
+     if (!strcmp (key, "frame-no"))          edl->frame_no = g_strtod (value, NULL);
+     if (!strcmp (key, "frame-scale"))       edl->scale = g_strtod (value, NULL);
+     if (!strcmp (key, "t0"))                edl->t0 = g_strtod (value, NULL);
+
+     g_free (key);
+     return;
+   }
+  if (strstr (line, "--"))
+    rest = strstr (line, "--") + 2;
+
+
+  {
+    const char *p = strstr (line, "--");
+    if (!p)
+      p = line + strlen(line)-1;
+    {
+      if (p>line) p --;
+      while (p>line && *p == ' ') p --;
+
+      while (p>line && isdigit (*p)) p --;
+      end = atoi (p+1);
+
+      if (p>line) p --;
+      while (p>line && *p == ' ') p --;
+
+      while (p>line && isdigit (*p)) p --;
+      start = atoi (p+1);
+
+      if (p>line) p --;
+      while (p>line && *p == ' ') p --;
+
+      memcpy (path, line, (p-line) + 1);
+      path[(p-line)+1]=0;
+    }
+  }
+
+  if (strlen (path) > 3)
+    {
+      Clip *clip = NULL;
+      int ff_probe = 0;
+     clip = clip_new_full (edl, path, start, end);
+
+
+     if (!clip_is_static_source (clip) &&
+         (start == 0 && end == 0))
+       ff_probe = 1;
+     edl->clips = g_list_append (edl->clips, clip);
+     if (strstr (line, "[fade="))
+       {
+         ff_probe = 1;
+         rest = strstr (line, "[fade=") + strlen ("[fade=");
+         clip->fade = atoi (rest);
+         while (*rest && *rest != ']') rest++;
+         if (*rest == ']') rest++;
+       }
+
+     if (rest) while (*rest == ' ')rest++;
+
+
+     if (clip == edl->clips->data)
+     {
+       ff_probe = 1;
+     }
+
+     if (ff_probe && !clip_is_static_source (clip))
+       {
+         gedl_get_video_info (clip->path, &clip->duration, &clip->fps);
+
+         if (edl->fps == 0.0)
+         {
+           gedl_set_fps (edl, clip->fps);
+         }
+       }
+
+     if (rest)
+       {
+         clip->filter_graph = g_strdup (rest);
+         while (clip->filter_graph[strlen(clip->filter_graph)-1]==' ' ||
+                clip->filter_graph[strlen(clip->filter_graph)-1]=='\n')
+                clip->filter_graph[strlen(clip->filter_graph)-1]='\0';
+       }
+
+     if (clip->end == 0)
+     {
+        clip->end = clip->duration;
+     }
+    }
+  else if (start == 0 && end == 0 && rest)
+  {
+    Clip *clip = clip_new_full (edl, NULL, 0, 0);
+    clip->filter_graph = g_strdup (rest);
+    edl->clips = g_list_append (edl->clips, clip);
+  }
+}
+
+#include <string.h>
+
+void gedl_update_video_size (GeglEDL *edl);
+GeglEDL *gedl_new_from_string (const char *string, const char *parent_path);
+GeglEDL *gedl_new_from_string (const char *string, const char *parent_path)
+{
+  GString *line = g_string_new ("");
+  GeglEDL *edl = gedl_new ();
+  int clips_done = 0;
+  int newlines = 0;
+  edl->parent_path = g_strdup (parent_path);
+
+  for (const char *p = string; p==string || *(p-1); p++)
+  {
+    switch (*p)
+    {
+      case 0:
+      case '\n':
+       if (clips_done)
+       {
+         if (line->len > 2)
+           gedl_parse_clip (edl, line->str);
+         g_string_assign (line, "");
+       }
+       else
+       {
+         if (line->str[0] == '-' &&
+             line->str[1] == '-' &&
+             line->str[2] == '-')
+         {
+           clips_done = 1;
+           g_string_assign (line, "");
+         }
+         else
+         {
+           if (*p == 0)
+           {
+             newlines = 2;
+           }
+           else if (*p == '\n')
+           {
+             newlines ++;
+           }
+           else
+           {
+             newlines = 0;
+           }
+           if (strchr (line->str, '='))
+             newlines = 3;
+
+           if (newlines >= 2)
+           {
+             gedl_parse_line (edl, line->str);
+             g_string_assign (line, "");
+           }
+           else
+             g_string_append_c (line, *p);
+         }
+       }
+       break;
+      default: g_string_append_c (line, *p);
+       break;
+    }
+  }
+  g_string_free (line, TRUE);
+
+  gedl_update_video_size (edl);
+  gedl_set_use_proxies (edl, edl->use_proxies);
+
+  return edl;
+}
+
+void gedl_save_path (GeglEDL *edl, const char *path)
+{
+  char *serialized;
+  serialized = gedl_serialize (edl);
+
+  if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
+  {
+     char backup_path[4096];
+     struct tm *tim;
+     char *old_contents = NULL;
+
+     g_file_get_contents (path, &old_contents, NULL, NULL);
+     if (old_contents)
+     {
+       char *oc, *s;
+
+       oc = strstr (old_contents, "\n\n");
+       s  = strstr (serialized, "\n\n");
+       if (oc && s && !strcmp (oc, s))
+       {
+         g_free (old_contents);
+         return;
+       }
+       g_free (old_contents);
+     }
+
+     sprintf (backup_path, "%s.gedl/history/%s-", edl->parent_path, basename(edl->path));
+
+     time_t now = time(NULL);
+     tim = gmtime(&now);
+
+     strftime(backup_path + strlen(backup_path), sizeof(backup_path)-strlen(backup_path), "%Y%m%d_%H%M%S", 
tim);
+     rename (path, backup_path);
+  }
+
+  FILE *file = fopen (path, "w");
+  if (!file)
+  {
+    g_free (serialized);
+    return;
+  }
+
+  if (serialized)
+  {
+    fprintf (file, "%s", serialized);
+    g_free (serialized);
+  }
+  fclose (file);
+}
+
+void gedl_update_video_size (GeglEDL *edl)
+{
+  if ((edl->video_width == 0 || edl->video_height == 0) && edl->clips)
+    {
+      Clip *clip = edl->clips->data;
+      GeglNode *gegl = gegl_node_new ();
+      GeglRectangle rect;
+      // XXX: is ff-load good for pngs and jpgs as well?
+      GeglNode *probe;
+      probe = gegl_node_new_child (gegl, "operation", "gegl:ff-load", "path", clip->path, NULL);
+      gegl_node_process (probe);
+      rect = gegl_node_get_bounding_box (probe);
+      edl->video_width = rect.width;
+      edl->video_height = rect.height;
+      g_object_unref (gegl);
+    }
+  if ((edl->proxy_width <= 0) && edl->video_width)
+  {
+    edl->proxy_width = 320;
+  }
+  if ((edl->proxy_height <= 0) && edl->video_width)
+  {
+    edl->proxy_height = edl->proxy_width * (1.0 * edl->video_height / edl->video_width);
+  }
+}
+
+static void generate_gedl_dir (GeglEDL *edl)
+{
+  char *tmp = g_strdup_printf ("cd %s; mkdir .gedl 2>/dev/null ; mkdir .gedl/cache 2>/dev/null mkdir 
.gedl/proxy 2>/dev/null mkdir .gedl/thumb 2>/dev/null ; mkdir .gedl/video 2>/dev/null; mkdir .gedl/history 
2>/dev/null", edl->parent_path);
+  system (tmp);
+  g_free (tmp);
+}
+
+static GTimer *  timer            = NULL;
+static guint     timeout_id       = 0;
+static gdouble   throttle         = 4.0;
+
+void gedl_reread (GeglEDL *edl)
+{
+  GeglEDL *new_edl = gedl_new_from_path (edl->path);
+  GList *l;
+
+  /* swap clips */
+  l = edl->clips;
+  edl->clips = new_edl->clips;
+  new_edl->clips = l;
+  edl->active_clip = NULL; // XXX: better to resolve?
+
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    clip->edl = edl;
+  }
+
+  for (l = new_edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    clip->edl = new_edl;
+  }
+
+  gedl_free (new_edl);
+}
+
+static gboolean timeout (gpointer user_data)
+{
+  GeglEDL *edl = user_data;
+  gedl_reread (edl);
+  // system (user_data);
+  g_timer_start (timer);
+  timeout_id = 0;
+  return FALSE;
+}
+
+
+static void file_changed (GFileMonitor     *monitor,
+                          GFile            *file,
+                          GFile            *other_file,
+                          GFileMonitorEvent event_type,
+                          GeglEDL          *edl)
+{
+  if (event_type == G_FILE_MONITOR_EVENT_CHANGED)
+    {
+          if (!timeout_id)
+            {
+              gdouble elapsed = g_timer_elapsed (timer, NULL);
+              gdouble wait    = throttle - elapsed;
+
+              if (wait <= 0.0)
+                wait = 0.0;
+
+              timeout_id = g_timeout_add (wait * 1000, timeout, edl);
+            }
+    }
+}
+
+void
+gedl_monitor_start (GeglEDL *edl)
+{
+  if (!edl->path)
+    return;
+  /* save to make sure file exists */
+  gedl_save_path (edl, edl->path);
+  /* start monitor */
+  timer = g_timer_new ();
+  edl->monitor = g_file_monitor_file (g_file_new_for_path (edl->path),
+                                      G_FILE_MONITOR_NONE,
+                                      NULL, NULL);
+  if(0)g_signal_connect (edl->monitor, "changed", G_CALLBACK (file_changed), edl);
+}
+
+GeglEDL *gedl_new_from_path (const char *path)
+{
+  GeglEDL *edl = NULL;
+  gchar *string = NULL;
+
+  g_file_get_contents (path, &string, NULL, NULL);
+  if (string)
+  {
+    char *rpath = realpath (path, NULL);
+    char *parent = g_strdup (rpath);
+    strrchr(parent, '/')[1]='\0';
+
+      edl = gedl_new_from_string (string, parent);
+
+    g_free (parent);
+    g_free (string);
+    if (!edl->path)
+      edl->path = g_strdup (realpath (path, NULL)); // XXX: leak
+  }
+  else
+  {
+    char *parent = NULL;
+    if (path[0] == '/')
+    {
+      parent = strdup (path);
+      strrchr(parent, '/')[1]='\0';
+    }
+    else
+    {
+      parent = g_malloc0 (PATH_MAX);
+      getcwd (parent, PATH_MAX);
+    }
+    edl = gedl_new_from_string ("", parent);
+    if (!edl->path)
+    {
+      if (path[0] == '/')
+      {
+        edl->path = g_strdup (path);
+      }
+      else
+      {
+        edl->path = g_strdup_printf ("%s/%s", parent, basename (path));
+      }
+    }
+    g_free (parent);
+  }
+  generate_gedl_dir (edl);
+
+  return edl;
+}
+
+static void setup (GeglEDL *edl)
+{
+  edl->result = gegl_node_new_child (edl->gegl, "operation", "gegl:nop", NULL);
+  edl->mix = gegl_node_new_child (edl->gegl, "operation", "gegl:mix", NULL);
+  edl->encode = gegl_node_new_child (edl->gegl, "operation", "gegl:ff-save",
+                                     "path",           edl->output_path,
+                                     "frame-rate",     gedl_get_fps (edl),
+                                     "video-bit-rate", edl->video_bitrate,
+                                     "video-bufsize",  edl->video_bufsize,
+                                     "audio-bit-rate", edl->audio_bitrate,
+                                     "audio-codec",    edl->audio_codec,
+                                     "video-codec",    edl->video_codec,
+                                     NULL);
+  edl->cached_result = gegl_node_new_child (edl->gegl, "operation", "gegl:buffer-source", "buffer", 
edl->buffer, NULL);
+  edl->store_final_buf = gegl_node_new_child (edl->gegl, "operation", "gegl:write-buffer", "buffer", 
edl->buffer, NULL);
+
+  gegl_node_link_many (edl->result, edl->store_final_buf, NULL);
+  gegl_node_link_many (edl->cached_result, edl->encode, NULL);
+}
+
+static void init (int argc, char **argv)
+{
+  gegl_init (&argc, &argv);
+  g_object_set (gegl_config (),
+                "application-license", "GPL3",
+                NULL);
+}
+
+static void encode_frames (GeglEDL *edl)
+{
+  int frame_no;
+  for (frame_no = edl->range_start; frame_no <= edl->range_end; frame_no++)
+  {
+    edl->frame_no = frame_no;
+    gedl_set_frame (edl, edl->frame_no);
+
+    fprintf (stdout, "\r%1.2f%% %04d / %04d   ",
+     100.0 * (frame_no-edl->range_start) * 1.0 / (edl->range_end - edl->range_start),
+     frame_no, edl->range_end);
+
+    gegl_node_set (edl->encode, "audio", gedl_get_audio (edl), NULL);
+    gegl_node_process (edl->encode);
+    fflush (0);
+  }
+  fprintf (stdout, "\n");
+}
+
+void nop_handler(int sig)
+{
+}
+
+static int stop_cacher = 0;
+
+void handler1(int sig)
+{
+  stop_cacher = 1;
+}
+
+static int cacheno = 0;
+static int cachecount = 2;
+
+static inline int this_cacher (int frame_no)
+{
+  if (frame_no % cachecount == cacheno) return 1;
+  return 0;
+}
+
+static void process_frames_cache (GeglEDL *edl)
+{
+  int frame_no = edl->frame_no;
+  int frame_start = edl->frame_no;
+  int duration;
+  signal(SIGUSR2, handler1);
+  duration = gedl_get_duration (edl);
+
+  GList *l;
+  int clip_start = 0;
+
+  // TODO: use bitmap from ui to speed up check
+
+  edl->frame_no = frame_start;
+  if (this_cacher (edl->frame_no))
+    gedl_set_frame (edl, edl->frame_no);
+   if (stop_cacher)
+    return;
+
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    int clip_frames = clip_get_frames (clip);
+    edl->frame_no = clip_start;
+    if (this_cacher (edl->frame_no))
+    {
+      gedl_set_frame (edl, edl->frame_no);
+    }
+
+    clip_start += clip_frames;
+    if (stop_cacher)
+      return;
+  }
+
+  for (frame_no = frame_start - 3; frame_no < duration; frame_no++)
+  {
+    edl->frame_no = frame_no;
+    if (this_cacher (edl->frame_no))
+      gedl_set_frame (edl, edl->frame_no);
+    if (stop_cacher)
+      return;
+  }
+  for (frame_no = 0; frame_no < frame_start; frame_no++)
+  {
+    edl->frame_no = frame_no;
+    if (this_cacher (edl->frame_no))
+      gedl_set_frame (edl, edl->frame_no);
+    if (stop_cacher)
+      return;
+  }
+}
+
+static inline void set_bit (guchar *bitmap, int no)
+{
+  bitmap[no/8] |= (1 << (no % 8));
+}
+
+guchar *gedl_get_cache_bitmap (GeglEDL *edl, int *length_ret)
+{
+  int duration = gedl_get_duration (edl);
+  int frame_no;
+  int length = (duration / 8) + 1;
+  guchar *ret = g_malloc0 (length);
+
+  if (length_ret)
+    *length_ret = length;
+
+  for (frame_no = 0; frame_no < duration; frame_no++)
+  {
+    const gchar *hash = gedl_get_frame_hash (edl, frame_no);
+    gchar *path = g_strdup_printf ("%s.gedl/cache/%s", edl->parent_path, hash);
+    if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
+      set_bit (ret, frame_no);
+    g_free (path);
+  }
+
+  return ret;
+}
+
+static void process_frames_cache_stat (GeglEDL *edl)
+{
+  int frame_no = edl->frame_no;
+  int duration;
+  signal(SIGUSR2, handler1);
+  duration = gedl_get_duration (edl);
+
+  /* XXX: should probably do first frame of each clip - since
+          these are used for quick keyboard navigation of the
+          project
+   */
+
+  for (frame_no = 0; frame_no < duration; frame_no++)
+  {
+    const gchar *hash = gedl_get_frame_hash (edl, frame_no);
+    gchar *path = g_strdup_printf ("%s.gedl/cache/%s", edl->parent_path, hash);
+    if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
+      fprintf (stdout, "%i ", frame_no);
+    //  fprintf (stdout, ". %i %s\n", frame_no, hash);
+   // else
+    //  fprintf (stdout, "  %i %s\n", frame_no, hash);
+    g_free (path);
+  }
+}
+
+int gegl_make_thumb_image (GeglEDL *edl, const char *path, const char *icon_path)
+{
+  GString *str = g_string_new ("");
+
+  g_string_assign (str, "");
+  g_string_append_printf (str, "%s iconographer -p -h -f 'mid-col 96 audio' %s -a %s",
+  //g_string_append_printf (str, "iconographer -p -h -f 'thumb 96' %s -a %s",
+                          gedl_binary_path, path, icon_path);
+  system (str->str);
+
+  g_string_free (str, TRUE);
+
+  return 0;
+}
+
+int gegl_make_thumb_video (GeglEDL *edl, const char *path, const char *thumb_path)
+{
+  GString *str = g_string_new ("");
+
+  g_string_assign (str, "");
+  g_string_append_printf (str, "ffmpeg -y -i %s -vf scale=%ix%i %s", path, edl->proxy_width, 
edl->proxy_height, thumb_path);
+  system (str->str);
+  g_string_free (str, TRUE);
+
+  return 0;
+#if 0  // much slower and worse for fps/audio than ffmpeg method for creating thumbs
+  int tot_frames; //
+  g_string_append_printf (str, 
"video-bitrate=100\n\noutput-path=%s\nvideo-width=256\nvideo-height=144\n\n%s\n", thumb_path, path);
+  edl = gedl_new_from_string (str->str);
+  setup (edl);
+  tot_frames = gedl_get_duration (edl);
+
+  if (edl->range_end == 0)
+    edl->range_end = tot_frames-1;
+  process_frames (edl);
+  gedl_free (edl);
+  g_string_free (str, TRUE);
+  return 0;
+#endif
+}
+
+int gedl_ui_main (GeglEDL *edl);
+
+int gegl_make_thumb_video (GeglEDL *edl, const char *path, const char *thumb_path);
+void gedl_make_proxies (GeglEDL *edl)
+{
+  GList *l;
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    if (clip->is_chain == 0 && clip->static_source == 0 && clip->is_meta == 0)
+    {
+      char *proxy_path = gedl_make_proxy_path (edl, clip->path);
+      char *thumb_path = gedl_make_thumb_path (edl, clip->path);
+      if (!g_file_test (proxy_path, G_FILE_TEST_IS_REGULAR))
+        gegl_make_thumb_video (edl, clip->path, proxy_path);
+      if (!g_file_test (thumb_path, G_FILE_TEST_IS_REGULAR))
+        gegl_make_thumb_image(edl, proxy_path, thumb_path);
+      g_free (proxy_path);
+      g_free (thumb_path);
+    }
+  }
+}
+
+void gedl_start_sanity (void)
+{
+  int fails = 0;
+  if (system("which ffmpeg > /dev/null") != 0)
+  {
+    fprintf (stderr, "gedl missing runtime dependency: ffmpeg command in PATH\n");
+    fails ++;
+  }
+  if (!gegl_has_operation ("gegl:ff-load"))
+  {
+    fprintf (stderr, "gedl missing runtime dependenct: gegl:ff-load operation\n");
+    fails ++;
+  }
+  if (!gegl_has_operation ("gegl:ff-save"))
+  {
+    fprintf (stderr, "gedl missing runtime dependenct: gegl:ff-save operation\n");
+    fails ++;
+  }
+  if (fails)
+    exit (-1);
+}
+
+gint iconographer_main (gint    argc, gchar **argv);
+
+int main (int argc, char **argv)
+{
+  GeglEDL *edl = NULL;
+  const char *edl_path = "input.edl";
+  int tot_frames;
+
+  gedl_binary_path = realpath (argv[0], NULL);
+  if (!gedl_binary_path)
+    gedl_binary_path = "gedl";
+
+  if (argv[1] && !strcmp (argv[1], "iconographer"))
+  {
+    argv[1] = argv[0];
+    return iconographer_main (argc-1, argv + 1);
+  }
+
+  setenv ("GEGL_USE_OPENCL", "no", 1);
+  setenv ("GEGL_MIPMAP_RENDERING", "1", 1);
+
+  init (argc, argv);
+  gedl_start_sanity ();
+
+  if (!argv[1])
+  {
+    static char *new_argv[3]={NULL, "gedl.edl", NULL};
+    new_argv[0] = argv[0];
+    argv = new_argv;
+    argc++;
+    g_file_set_contents (argv[1], default_edl, -1, NULL);
+  }
+
+  edl_path = argv[1]; //realpath (argv[1], NULL);
+
+  if (g_str_has_suffix (edl_path, ".mp4") ||
+      g_str_has_suffix (edl_path, ".ogv") ||
+      g_str_has_suffix (edl_path, ".mkv") ||
+      g_str_has_suffix (edl_path, ".MKV") ||
+      g_str_has_suffix (edl_path, ".avi") ||
+      g_str_has_suffix (edl_path, ".MP4") ||
+      g_str_has_suffix (edl_path, ".OGV") ||
+      g_str_has_suffix (edl_path, ".AVI"))
+  {
+    char str[1024];
+    int duration;
+    double fps;
+    GeglNode *gegl = gegl_node_new ();
+    GeglNode *probe = gegl_node_new_child (gegl, "operation",
+                                           "gegl:ff-load", "path", edl_path,
+                                           NULL);
+    gegl_node_process (probe);
+
+    gegl_node_get (probe, "frames", &duration, NULL);
+    gegl_node_get (probe, "frame-rate", &fps, NULL);
+    g_object_unref (gegl);
+
+    sprintf (str, "%s 0 %i\n", edl_path, duration);
+    {
+      char * path = realpath (edl_path, NULL); 
+      char * rpath = g_strdup_printf ("%s.edl", path);
+      char * parent = g_strdup (rpath);
+      strrchr(parent, '/')[1]='\0';
+      edl = gedl_new_from_string (str, parent);
+      g_free (parent);
+      edl->path = rpath;
+      free (path);
+    }
+    generate_gedl_dir (edl);
+  }
+  else
+  {
+    edl = gedl_new_from_path (edl_path);
+  }
+
+  chdir (edl->parent_path); /* we try as good as we can to deal with absolute
+                               paths correctly,  */
+
+  setup (edl);
+
+  {
+#define RUNMODE_UI     0
+#define RUNMODE_RENDER 1
+#define RUNMODE_CACHE  2
+#define RUNMODE_CACHE_STAT  3
+#define RUNMODE_RESERIALIZE  4
+    int runmode = RUNMODE_UI;
+    for (int i = 0; argv[i]; i++)
+    {
+      if (!strcmp (argv[i], "render")) runmode = RUNMODE_RENDER;
+      if (!strcmp (argv[i], "reserialize")) runmode = RUNMODE_RESERIALIZE;
+      if (!strcmp (argv[i], "cachestat")) runmode = RUNMODE_CACHE_STAT;
+      if (!strcmp (argv[i], "cache"))
+      {
+        runmode = RUNMODE_CACHE;
+        if (argv[i+1])
+        {
+          cacheno = atoi (argv[i+1]);
+          if (argv[i+2])
+            cachecount = atoi (argv[i+2]);
+        }
+      }
+    }
+
+    switch (runmode)
+    {
+      case RUNMODE_RESERIALIZE:
+        printf ("%s", gedl_serialize (edl));
+        exit (0);
+        break;
+      case RUNMODE_UI:
+
+        signal(SIGUSR2, nop_handler);
+
+        gedl_monitor_start (edl);
+
+        return gedl_ui_main (edl);
+      case RUNMODE_RENDER:
+        tot_frames  = gedl_get_duration (edl);
+        if (edl->range_end == 0)
+          edl->range_end = tot_frames-1;
+        encode_frames (edl);
+        gedl_free (edl);
+        return 0;
+      case RUNMODE_CACHE:
+        tot_frames  = gedl_get_duration (edl);
+        if (edl->range_end == 0)
+          edl->range_end = tot_frames-1;
+        process_frames_cache (edl);
+        gedl_free (edl);
+        return 0;
+      case RUNMODE_CACHE_STAT:
+        process_frames_cache_stat (edl);
+        gedl_free (edl);
+        return 0;
+     }
+  }
+  return -1;
+}
+
+char *gedl_serialize (GeglEDL *edl)
+{
+  GList *l;
+  char *ret;
+  GString *ser = g_string_new ("");
+
+  if (edl->proxy_width != DEFAULT_proxy_width)
+    g_string_append_printf (ser, "proxy-width=%i\n",  edl->proxy_width);
+  if (edl->proxy_height != DEFAULT_proxy_height)
+    g_string_append_printf (ser, "proxy-height=%i\n", edl->proxy_height);
+  if (edl->framedrop != DEFAULT_framedrop)
+    g_string_append_printf (ser, "framedrop=%i\n", edl->framedrop);
+
+  if (strcmp(edl->output_path, DEFAULT_output_path))
+    g_string_append_printf (ser, "output-path=%s\n", edl->output_path);
+  if (strcmp(edl->video_codec, DEFAULT_video_codec))
+    g_string_append_printf (ser, "video-codec=%s\n", edl->video_codec);
+  if (strcmp(edl->audio_codec, DEFAULT_audio_codec))
+    g_string_append_printf (ser, "audio-codec=%s\n", edl->audio_codec);
+  if (edl->video_width != DEFAULT_video_width)
+    g_string_append_printf (ser, "video-width=%i\n",  edl->video_width);
+  if (edl->video_height != DEFAULT_video_height)
+    g_string_append_printf (ser, "video-height=%i\n", edl->video_height);
+  if (edl->video_bufsize != DEFAULT_video_bufsize)
+    g_string_append_printf (ser, "video-bufsize=%i\n", edl->video_bufsize);
+  if (edl->video_bitrate != DEFAULT_video_bitrate)
+    g_string_append_printf (ser, "video-bitrate=%i\n",  edl->video_bitrate);
+  if (edl->video_tolerance != DEFAULT_video_tolerance)
+    g_string_append_printf (ser, "video-tolerance=%i\n", edl->video_tolerance);
+  if (edl->audio_bitrate != DEFAULT_audio_bitrate)
+    g_string_append_printf (ser, "audio-bitrate=%i\n",  edl->audio_bitrate);
+  if (edl->audio_samplerate != DEFAULT_audio_samplerate)
+    g_string_append_printf (ser, "audio-samplerate=%i\n",  edl->audio_samplerate);
+
+  g_string_append_printf (ser, "fps=%f\n", gedl_get_fps (edl));
+
+  if (edl->range_start != DEFAULT_range_start)
+    g_string_append_printf (ser, "range-start=%i\n",  edl->range_start);
+  if (edl->range_end != DEFAULT_range_end)
+    g_string_append_printf (ser, "range-end=%i\n", edl->range_end);
+
+  if (edl->selection_start != DEFAULT_selection_start)
+    g_string_append_printf (ser, "selection-start=%i\n",  edl->selection_start);
+  if (edl->selection_end != DEFAULT_selection_end)
+    g_string_append_printf (ser, "selection-end=%i\n",  edl->selection_end);
+  if (edl->scale != 1.0)
+    g_string_append_printf (ser, "frame-scale=%f\n", edl->scale);
+  if (edl->t0 != 1.0)
+    g_string_append_printf (ser, "t0=%f\n", edl->t0);
+
+  g_string_append_printf (ser, "frame-no=%i\n", edl->frame_no);
+  g_string_append_printf (ser, "\n");
+
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    gchar *path = clip->path;
+    if (!path)
+      path = "";
+    if (!strncmp (path, edl->parent_path, strlen(edl->parent_path)))
+      path += strlen (edl->parent_path);
+
+    if (strlen(path)== 0 &&
+        clip->start == 0 &&
+        clip->end == 0 &&
+        clip->filter_graph)
+    {
+      g_string_append_printf (ser, "--%s\n", clip->filter_graph);
+    }
+    else
+    {
+    g_string_append_printf (ser, "%s %d %d ", path, clip->start, clip->end);
+    if (clip->filter_graph||clip->fade)
+      g_string_append_printf (ser, "-- ");
+    if (clip->fade)
+      g_string_append_printf (ser, "[fade=%i] ", clip->fade);
+    if (clip->filter_graph)
+      g_string_append_printf (ser, "%s", clip->filter_graph);
+    g_string_append_printf (ser, "\n");
+    }
+  }
+  g_string_append_printf (ser, "-----\n");
+  for (l = edl->clip_db; l; l = l->next)
+  {
+    SourceClip *clip = l->data;
+    g_string_append_printf (ser, "%s %d %d %d%s%s%s\n", clip->path, clip->start, clip->end, clip->duration,
+        "", //(edl->active_source == clip)?" [active]":"",
+        clip->title?" -- ":"",clip->title?clip->title:"");
+  }
+  ret=ser->str;
+  g_string_free (ser, FALSE);
+  return ret;
+}
+
+void
+gegl_meta_set_audio (const char        *path,
+                     GeglAudioFragment *audio)
+{
+  GError *error = NULL;
+  GExiv2Metadata *e2m = gexiv2_metadata_new ();
+  gexiv2_metadata_open_path (e2m, path, &error);
+  if (error)
+  {
+    g_warning ("%s", error->message);
+  }
+  else
+  {
+    int i, c;
+    GString *str = g_string_new ("");
+    int sample_count = gegl_audio_fragment_get_sample_count (audio);
+    int channels = gegl_audio_fragment_get_channels (audio);
+    if (gexiv2_metadata_has_tag (e2m, "Xmp.xmp.GEGL"))
+      gexiv2_metadata_clear_tag (e2m, "Xmp.xmp.GEGL");
+
+    g_string_append_printf (str, "%i %i %i %i",
+                              gegl_audio_fragment_get_sample_rate (audio),
+                              gegl_audio_fragment_get_channels (audio),
+                              gegl_audio_fragment_get_channel_layout (audio),
+                              gegl_audio_fragment_get_sample_count (audio));
+
+    for (i = 0; i < sample_count; i++)
+      for (c = 0; c < channels; c++)
+        g_string_append_printf (str, " %0.5f", audio->data[c][i]);
+
+    gexiv2_metadata_set_tag_string (e2m, "Xmp.xmp.GeglAudio", str->str);
+    gexiv2_metadata_save_file (e2m, path, &error);
+    if (error)
+      g_warning ("%s", error->message);
+    g_string_free (str, TRUE);
+  }
+  g_object_unref (e2m);
+}
+
+void
+gegl_meta_get_audio (const char        *path,
+                     GeglAudioFragment *audio)
+{
+  GError *error = NULL;
+  GExiv2Metadata *e2m = gexiv2_metadata_new ();
+  gexiv2_metadata_open_path (e2m, path, &error);
+  if (!error)
+  {
+    GString *word = g_string_new ("");
+    gchar *p;
+    gchar *ret = gexiv2_metadata_get_tag_string (e2m, "Xmp.xmp.GeglAudio");
+    int element_no = 0;
+    int channels = 2;
+    int max_samples = 2000;
+
+    if (ret)
+    for (p = ret; p==ret || p[-1] != '\0'; p++)
+    {
+      switch (p[0])
+      {
+        case '\0':case ' ':
+        if (word->len > 0)
+         {
+           switch (element_no++)
+           {
+             case 0:
+               gegl_audio_fragment_set_sample_rate (audio, g_strtod (word->str, NULL));
+               break;
+             case 1:
+               channels = g_strtod (word->str, NULL);
+               gegl_audio_fragment_set_channels (audio, channels);
+               break;
+             case 2:
+               gegl_audio_fragment_set_channel_layout (audio, g_strtod (word->str, NULL));
+               break;
+             case 3:
+               gegl_audio_fragment_set_sample_count (audio, g_strtod (word->str, NULL));
+               break;
+             default:
+               {
+                  int sample_no = element_no - 4;
+                  int channel_no = sample_no % channels;
+                  sample_no/=2;
+                  if (sample_no < max_samples)
+                  audio->data[channel_no][sample_no] = g_strtod (word->str, NULL);
+               }
+               break;
+           }
+         }
+        g_string_assign (word, "");
+        break;
+        default:
+        g_string_append_c (word, p[0]);
+        break;
+      }
+    }
+    g_string_free (word, TRUE);
+    g_free (ret);
+  }
+  else
+    g_warning ("%s", error->message);
+  g_object_unref (e2m);
+}
+
+void gedl_set_selection (GeglEDL *edl, int start_frame, int end_frame)
+{
+  edl->selection_start = start_frame;
+  edl->selection_end   = end_frame;
+}
+
+void gedl_get_selection (GeglEDL *edl,
+                         int     *start_frame,
+                         int     *end_frame)
+{
+  if (start_frame)
+    *start_frame = edl->selection_start;
+  if (end_frame)
+    *end_frame = edl->selection_end;
+}
+
+void gedl_set_range (GeglEDL *edl, int start_frame, int end_frame)
+{
+  edl->range_start = start_frame;
+  edl->range_end   = end_frame;
+}
+
+void gedl_get_range (GeglEDL *edl,
+                     int     *start_frame,
+                     int     *end_frame)
+{
+  if (start_frame)
+    *start_frame = edl->range_start;
+  if (end_frame)
+    *end_frame = edl->range_end;
+}
+
+Clip * edl_get_clip_for_frame (GeglEDL *edl, int frame)
+{
+  GList *l;
+  int t = 0;
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    if (frame >= t && frame < t + clip_get_frames (clip))
+    {
+      return clip;
+    }
+    t += clip_get_frames (clip);
+  }
+  return NULL;
+}
diff --git a/gcut/gedl.h b/gcut/gedl.h
new file mode 100644
index 0000000..b47b6d6
--- /dev/null
+++ b/gcut/gedl.h
@@ -0,0 +1,259 @@
+#if TODO // this is the projects todo-list
+
+  bugs
+    huge video files cause (cairo) thumtrack overflow, vertical also has this problem - how to split?
+    if the initial frame is not a representative video frame of the comp, audio glitches
+    ogv render files are more compatible than generated mp4 files, firefox among few that renders the mp4 in 
audio sync
+
+  roadmap/missing features
+    move gedl to gegl git repo
+
+    engine
+      rescaling playback speed of clips
+      support importing clips of different fps
+
+      support for configuring the final background render to be as high
+      fidelity as GEGL processing allows - rather than sharing tuning for
+      preview rendering.
+
+      support for other timecodes, mm:ss:ff and s
+      using edl files as clip sources - hopefully without even needing caches.
+
+      global filters
+        overlaying of audio from wav / mp3 files
+        operation chains
+        subtitles
+
+    ui
+      port gedl-ui.c to lua
+      detect locked or crashed ui, kill and respawn
+      trimming by mouse / dragging clips around by mouse
+      show a modal ui-block when generating proxies/thumbtrack on media import, instead of blocking/being 
blank
+      gaps in timeline (will be implemented as blank clips - but ui could be different)
+      insert videos from the commandline
+      ui for adding/editing global filters/annotation/sound bits/beeps
+
+
+#endif
+
+#define CACHE_FORMAT "jpg"
+#define GEDL_SAMPLER   GEGL_SAMPLER_NEAREST
+
+#ifndef GEDL_H
+#define GEDL_H
+
+#include <gio/gio.h>
+
+typedef struct _GeglEDL GeglEDL;
+typedef struct _Clip    Clip;
+
+void gedl_set_use_proxies (GeglEDL *edl, int use_proxies);
+int         gegl_make_thumb_video (GeglEDL *edl, const char *path, const char *thumb_path);
+char *gedl_make_proxy_path (GeglEDL *edl, const char *clip_path);
+const char *compute_cache_path    (const char *path);
+
+#define CACHE_TRY_SIMPLE    (1<<0)
+#define CACHE_TRY_MIX       (1<<1)
+#define CACHE_TRY_FILTERED  (1<<2)
+#define CACHE_TRY_ALL       (CACHE_TRY_SIMPLE | CACHE_TRY_FILTERED | CACHE_TRY_MIX)
+#define CACHE_MAKE_FILTERED (1<<3)
+#define CACHE_MAKE_SIMPLE   (1<<4)
+#define CACHE_MAKE_MIX      (1<<5)
+#define CACHE_MAKE_ALL      (CACHE_MAKE_SIMPLE|CACHE_MAKE_MIX|CACHE_MAKE_FILTERED)
+
+enum {
+  GEDL_UI_MODE_FULL = 0,
+  GEDL_UI_MODE_NONE = 1,
+  GEDL_UI_MODE_TIMELINE = 2,
+  GEDL_UI_MODE_PART = 3,
+};
+
+#define GEDL_LAST_UI_MODE 1
+
+GeglEDL    *gedl_new                (void);
+void        gedl_free               (GeglEDL    *edl);
+void        gedl_set_fps            (GeglEDL    *edl,
+                                     double      fps);
+double      gedl_get_fps            (GeglEDL    *edl);
+int         gedl_get_duration       (GeglEDL    *edl);
+double      gedl_get_time           (GeglEDL    *edl);
+void        gedl_parse_line         (GeglEDL    *edl, const char *line);
+GeglEDL    *gedl_new_from_path      (const char *path);
+void        gedl_load_path          (GeglEDL    *edl, const char *path);
+void        gedl_save_path          (GeglEDL    *edl, const char *path);
+GeglAudioFragment  *gedl_get_audio  (GeglEDL    *edl);
+Clip       *gedl_get_clip           (GeglEDL *edl, int frame, int *clip_frame_no);
+
+void        gedl_set_frame          (GeglEDL    *edl, int frame);
+void        gedl_set_time           (GeglEDL    *edl, double seconds);
+int         gedl_get_frame          (GeglEDL    *edl);
+char       *gedl_serialize          (GeglEDL    *edl);
+
+void        gedl_set_range          (GeglEDL    *edl, int start_frame, int end_frame);
+void        gedl_get_range          (GeglEDL    *edl,
+                                     int        *start_frame,
+                                     int        *end_frame);
+
+void        gedl_set_selection      (GeglEDL    *edl, int start_frame, int end_frame);
+void        gedl_get_selection      (GeglEDL    *edl,
+                                     int        *start_frame,
+                                     int        *end_frame);
+char       *gedl_make_thumb_path    (GeglEDL    *edl, const char *clip_path);
+guchar     *gedl_get_cache_bitmap   (GeglEDL *edl, int *length_ret);
+
+
+Clip       *clip_new               (GeglEDL *edl);
+void        clip_free              (Clip *clip);
+const char *clip_get_path     (Clip *clip);
+void        clip_set_path          (Clip *clip, const char *path);
+int         clip_get_start         (Clip *clip);
+int         clip_get_end           (Clip *clip);
+int         clip_get_frames        (Clip *clip);
+void        clip_set_start         (Clip *clip, int start);
+void        clip_set_end           (Clip *clip, int end);
+void        clip_set_range         (Clip *clip, int start, int end);
+int         clip_is_static_source  (Clip *clip);
+gchar *     clip_get_frame_hash    (Clip *clip, int clip_frame_no);
+Clip  *     clip_get_next          (Clip *self);
+Clip  *     clip_get_prev          (Clip *self);
+void        clip_fetch_audio       (Clip *clip);
+void        clip_set_full          (Clip *clip, const char *path, int start, int end);
+Clip  *     clip_new_full          (GeglEDL *edl, const char *path, int start, int end);
+
+//void   clip_set_frame_no      (Clip *clip, int frame_no);
+void        clip_render_frame       (Clip *clip, int clip_frame_no);
+
+Clip *      edl_get_clip_for_frame (GeglEDL           *edl, int frame);
+void        gedl_make_proxies      (GeglEDL           *edl);
+void        gedl_get_video_info    (const char        *path, int *duration, double *fps);
+void        gegl_meta_set_audio    (const char        *path,
+                                    GeglAudioFragment *audio);
+void        gegl_meta_get_audio    (const char        *path,
+                                    GeglAudioFragment *audio);
+
+#define SPLIT_VER  0.8
+
+extern char *gedl_binary_path;
+
+/*********/
+
+typedef struct SourceClip
+{
+  char  *path;
+  int    start;
+  int    end;
+  char  *title;
+  int    duration;
+  int    editing;
+  char  *filter_graph; /* chain of gegl filters */
+} SourceClip;
+
+struct _Clip
+{
+  char  *path;  /*path to media file */
+  int    start; /*frame number starting with 0 */
+  int    end;   /*last frame, inclusive fro single frame, make equal to start */
+  char  *title;
+  int    duration;
+  int    editing;
+  char  *filter_graph; /* chain of gegl filters */
+  /* to here Clip must match start of SourceClip */
+  GeglEDL *edl;
+
+  double fps;
+  int    fade; /* the main control for fading in.. */
+  int    static_source;
+  int    is_chain;
+  int    is_meta;
+
+  int    abs_start;
+
+  const char        *clip_path;
+  GeglNode          *gegl;
+  GeglAudioFragment *audio;
+  GeglNode          *chain_loader;
+  GeglNode          *full_loader;
+  GeglNode          *proxy_loader;
+  GeglNode          *loader; /* nop that one of the prior is linked to */
+
+  GeglNode          *nop_scaled;
+  GeglNode          *nop_crop;
+  GeglNode          *nop_store_buf;
+
+  GMutex             mutex;
+};
+
+struct _GeglEDL
+{
+  GFileMonitor *monitor;
+  char         *path;
+  char         *parent_path;
+  GList        *clip_db;
+  GList        *clips;
+  int           frame; /* render thread, frame_no is ui side */
+  double        fps;
+  GeglBuffer   *buffer;
+  GeglBuffer   *buffer_copy_temp;
+  GeglBuffer   *buffer_copy;
+  GMutex        buffer_copy_mutex;
+  GeglNode     *cached_result;
+  GeglNode     *gegl;
+  int           playing;
+  int           width;
+  int           height;
+  GeglNode     *cache_loader;
+  int           cache_flags;
+  int           selection_start;
+  int           selection_end;
+  int           range_start;
+  int           range_end;
+  const char   *output_path;
+  const char   *video_codec;
+  const char   *audio_codec;
+  int           proxy_width;
+  int           proxy_height;
+  int           video_width;
+  int           video_height;
+  int           video_size_default;
+  int           video_bufsize;
+  int           video_bitrate;
+  int           video_tolerance;
+  int           audio_bitrate;
+  int           audio_samplerate;
+  int           frame_no;
+  int           source_frame_no;
+  int           use_proxies;
+  int           framedrop;
+  int           ui_mode;
+
+  GeglNode     *result;
+  GeglNode     *store_final_buf;
+  GeglNode     *mix;
+
+  GeglNode     *encode;
+  double        scale;
+  double        t0;
+  Clip         *active_clip;
+
+  void         *mrg;
+
+  char         *clip_query;
+  int           clip_query_edited;
+  int           filter_edited;
+} _GeglEDL;
+
+void update_size (GeglEDL *edl, Clip *clip);
+void remove_in_betweens (GeglNode *nop_scaled, GeglNode *nop_filtered);
+int  is_connected (GeglNode *a, GeglNode *b);
+void gedl_update_buffer (GeglEDL *edl);
+
+#ifdef MICRO_RAPTOR_GUI
+ /* renderer.h */
+void renderer_toggle_playing (MrgEvent *event, void *data1, void *data2);
+void gedl_cache_invalid (GeglEDL *edl);
+int renderer_done (GeglEDL *edl);
+void renderer_start (GeglEDL *edl);
+
+#endif
+
+#endif
diff --git a/gcut/iconographer.c b/gcut/iconographer.c
new file mode 100644
index 0000000..c0ebbd4
--- /dev/null
+++ b/gcut/iconographer.c
@@ -0,0 +1,748 @@
+/*
+ * Copyright (c) 2015 Rituwall Inc, authored by pippin gimp org
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define main iconographer_main
+
+#include <gegl.h>
+#include <gegl-audio-fragment.h>
+#include <glib/gprintf.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <math.h>
+
+#define NEGL_RGB_HEIGHT     42
+#define NEGL_RGB_THEIGHT    42
+#define NEGL_RGB_HIST_DIM    6      // if changing make dim * dim * dim divisible by 3
+#define NEGL_RGB_HIST_SLOTS (NEGL_RGB_HIST_DIM * NEGL_RGB_HIST_DIM * NEGL_RGB_HIST_DIM)
+#define NEGL_FFT_DIM        64
+
+/* each row in the video terrain is the following 8bit RGB (24bit) data: */
+typedef struct FrameInfo  
+{
+  uint8_t rgb_hist[NEGL_RGB_HIST_SLOTS];
+  uint8_t rgb_square_diff[3];
+  uint8_t audio_energy[3];
+} FrameInfo;
+
+char *format="histogram diff audio 4 thumb 64 mid-col 20";
+
+int frame_start = 0;
+int frame_end   = 0;
+int total_frames = 0;
+double frame_rate = 0; 
+
+char *video_path = NULL;
+char *thumb_path = NULL;
+char *input_analysis_path = NULL;
+char *output_analysis_path = NULL;
+int   show_progress = 0;
+int   frame_thumb = 0;
+int   horizontal = 0;
+int   time_out = 0;
+
+long  babl_ticks (void);
+
+void  usage()
+{
+      printf ("usage: iconographer [options] <video> [thumb]\n"
+" -p, --progress   - show /progress in terminal\n"
+" -a <analysis-path>, ---analysis\n"
+"                  - path to store information extraction result, if the file\n"
+"                    already exists it will be reused for best frame analysis\n"
+"                    instead of a full dump happening again.\n"
+" -h, --horizontal   store a horizontal strata instead of vertical\n"
+" -t, --timeout - stop doing frame info dump after this many seconds have passed)\n"
+/*" -s <frame>, --start-frame <frame>\n"
+"           - first frame to extract analysis from (default 0)\n"*/
+" -e <frame>, --end-frame <frame>\n"
+"           - last frame to extract analysis from (default is 0 which means auto end)\n"
+" -f, --format - format string, specify which forms of analysis to put in the analysis file,\n"
+"                the default format is: \"histogram audio thumb 40 mid-col 20\"\n"
+"\n"
+"\n"
+"Options can also follow the video (and thumb) arguments.\n"
+"\n");
+  exit (0);
+}
+
+void parse_args (int argc, char **argv)
+{
+  int i;
+  int stage = 0;
+  for (i = 1; i < argc; i++)
+  {
+    if (g_str_equal (argv[i], "-f")||
+        g_str_equal (argv[i], "--format"))
+    {
+      format = g_strdup (argv[i+1]);
+      i++;
+    } 
+    else if (g_str_equal (argv[i], "-p") ||
+        g_str_equal (argv[i], "--progress"))
+    {
+      show_progress = 1;
+    }
+    else if (g_str_equal (argv[i], "-h") ||
+        g_str_equal (argv[i], "--horizontal"))
+    {
+      horizontal = 1;
+    }
+    else if (g_str_equal (argv[i], "-v") ||
+        g_str_equal (argv[i], "--vertical"))
+    {
+      horizontal = 0;
+    }
+    else if (g_str_equal (argv[i], "-a") ||
+        g_str_equal (argv[i], "--analysis"))
+    {
+      input_analysis_path = g_strdup (argv[i+1]);
+      output_analysis_path = g_strdup (argv[i+1]);
+      i++;
+    } 
+
+    else if (g_str_equal (argv[i], "-s") ||
+             g_str_equal (argv[i], "--start-frame"))
+    {
+      frame_start = g_strtod (argv[i+1], NULL);
+      i++;
+    } 
+    else if (g_str_equal (argv[i], "-t") ||
+             g_str_equal (argv[i], "--time-out"))
+    {
+      time_out = g_strtod (argv[i+1], NULL);
+      i++;
+    } 
+    else if (g_str_equal (argv[i], "-e") ||
+             g_str_equal (argv[i], "--end-frame"))
+    {
+      frame_end = g_strtod (argv[i+1], NULL);
+      i++;
+    } 
+    else if (g_str_equal (argv[i], "--help"))
+    {
+      usage();
+    }
+    else if (stage == 0)
+    {
+      video_path = g_strdup (argv[i]);
+      stage = 1;
+    } else if (stage == 1)
+    {
+      thumb_path = g_strdup (argv[i]);
+      stage = 2;
+    }
+  }
+}
+
+#include <string.h>
+
+GeglNode   *gegl_decode  = NULL;
+
+GeglBuffer *previous_video_frame  = NULL;
+GeglBuffer *video_frame  = NULL;
+GeglBuffer *terrain      = NULL;
+
+uint8_t rgb_hist_shuffler[NEGL_RGB_HIST_SLOTS];
+uint8_t rgb_hist_unshuffler[NEGL_RGB_HIST_SLOTS];
+
+typedef struct {
+  int r;
+  int g;
+  int b;
+  int no; 
+} Entry;
+
+static int rgb_hist_inited = 0;
+
+static gint sorted_color (gconstpointer a, gconstpointer b)
+{
+  const Entry *ea = a;
+  const Entry *eb = b;
+  return (ea->g * 110011 + ea->r * 213 + ea->b) -
+         (eb->g * 110011 + eb->r * 213 + eb->b);
+}
+
+static inline void init_rgb_hist (void)
+{
+  /* sort RGB histogram slots by luminance for human readability
+   */
+  if (!rgb_hist_inited)
+    {
+      GList *list = NULL;
+      GList *l;
+      int r, g, b, i;
+      i = 0;
+      for (r = 0; r < NEGL_RGB_HIST_DIM; r++)
+        for (g = 0; g < NEGL_RGB_HIST_DIM; g++)
+          for (b = 0; b < NEGL_RGB_HIST_DIM; b++, i++)
+          {
+            Entry *e = g_malloc0 (sizeof (Entry));
+            e->r = r;
+            e->g = g;
+            e->b = b;
+            e->no = i;
+            list = g_list_prepend (list, e);
+          }
+      list = g_list_sort (list, sorted_color);
+      i = 0;
+      for (l = list; l; l = l->next, i++)
+      {
+        Entry *e = l->data;
+        int no = e->no;
+        int li = i;
+        rgb_hist_shuffler[li] = no;
+        rgb_hist_unshuffler[no] = li;
+        g_free (e);
+      }
+      g_list_free (list);
+      rgb_hist_inited = 1;
+    }
+}
+
+int rgb_hist_shuffle (int in)
+{
+  init_rgb_hist();
+  return rgb_hist_unshuffler[in];
+}
+
+int rgb_hist_unshuffle (int in)
+{
+  init_rgb_hist();
+  return rgb_hist_unshuffler[in];
+}
+
+GeglNode *store, *load;
+
+static void decode_frame_no (int frame)
+{
+  if (video_frame)
+  {
+    if (strstr (format, "histogram"))
+    {
+       if (previous_video_frame)
+         g_object_unref (previous_video_frame);
+       previous_video_frame = gegl_buffer_dup (video_frame);
+    }
+    g_object_unref (video_frame);
+  }
+  video_frame = NULL;
+  gegl_node_set (load, "frame", frame, NULL);
+  gegl_node_process (store);
+}
+
+int count_color_bins (FrameInfo *info, int threshold)
+{
+  int count = 0;
+  int i;
+  for (i = 0; i < NEGL_RGB_HIST_SLOTS; i++)
+    if (info->rgb_hist[i] > threshold)
+      count ++;
+
+  return count;
+}
+
+float score_frame (FrameInfo *info, int frame_no)
+{
+  float sum_score             = 0.0;
+  float rgb_histogram_count   = count_color_bins (info, 1) * 1.0 / NEGL_RGB_HIST_SLOTS;
+  float audio_energy          = info->audio_energy[1] / 255.0;
+  float new_scene             = (info->rgb_square_diff[0] / 255.0 +
+                                info->rgb_square_diff[1] / 255.0 +
+                                info->rgb_square_diff[2] / 255.0) * 3;
+  float after_first_40_sec    = frame_no / frame_rate > 40.0 ? 1.0 : 0.3;
+  float after_first_12_sec    = frame_no / frame_rate > 12.0 ? 1.0 : 0.1;
+  float within_first_third    = frame_no < total_frames / 3  ? 1 : 0.6;
+
+  sum_score = rgb_histogram_count;
+  sum_score *= within_first_third  * 0.33;
+  sum_score *= after_first_40_sec  * 0.33;
+  sum_score *= after_first_12_sec  * 0.33;
+  sum_score *= (audio_energy       + 0.1) * 0.7;
+  sum_score *= (new_scene          + 0.05);
+  return sum_score;
+}
+
+static void find_best_thumb (void)
+{
+  int frame = 0;
+  float best_score = 0.0;
+  frame_thumb = 0;
+  
+  for (frame = 0; frame < frame_end; frame++)
+  {
+    FrameInfo info;
+    GeglRectangle terrain_row;
+    if (horizontal)
+      terrain_row = (GeglRectangle){frame-frame_start, 0, 1, sizeof (FrameInfo)};
+    else 
+      terrain_row = (GeglRectangle){0, frame-frame_start, sizeof (FrameInfo), 1};
+    gegl_buffer_get (terrain, &terrain_row, 1.0, babl_format("RGB u8"),
+                     &info, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+    float score = score_frame (&info, frame);
+    if (score > best_score)
+      {
+        best_score = score;
+        frame_thumb = frame;
+      }
+  }
+  fprintf (stderr, "best frame: %i\n", frame_thumb);
+}
+
+GeglNode *translate = NULL;
+
+static int extract_mid_col (GeglBuffer *buffer, void *rgb_mid_col, int samples)
+{
+  GeglRectangle mid_col;
+  mid_col.x = 0;
+  mid_col.y = 
+     gegl_buffer_get_extent (buffer)-> height * 1.0 *
+      samples / gegl_buffer_get_extent (buffer)->width / 2.0;
+  mid_col.height = 1;
+  mid_col.width = samples;
+  gegl_buffer_get (video_frame, &mid_col, 
+     1.0 * samples / gegl_buffer_get_extent (buffer)->width,
+     babl_format ("R'G'B' u8"),
+     rgb_mid_col,
+     GEGL_AUTO_ROWSTRIDE,
+     GEGL_ABYSS_NONE);
+  return 3 * samples;
+}
+
+static int extract_thumb (GeglBuffer *buffer, void *rgb_thumb, int samples, int samples2)
+{
+  GeglRectangle thumb_scan;
+  static float vpos = 0.0;
+  if (horizontal)
+  {
+    thumb_scan.y = 0;
+    thumb_scan.x = 
+       gegl_buffer_get_extent (buffer)-> width * 1.0 *
+       samples / gegl_buffer_get_extent (buffer)->width * vpos;
+    vpos += (1.0/samples2);
+    if (vpos > 1.0) vpos = 0.0;
+    thumb_scan.width = 1;
+    thumb_scan.height = samples;
+  }
+  else
+  {
+    thumb_scan.x = 0;
+    thumb_scan.y = 
+       gegl_buffer_get_extent (buffer)-> height * 1.0 *
+       samples / gegl_buffer_get_extent (buffer)->width * vpos;
+    vpos += (1.0/samples2);
+    if (vpos > 1.0) vpos = 0.0;
+    thumb_scan.height = 1;
+    thumb_scan.width = samples;
+  }
+  gegl_buffer_get (buffer, &thumb_scan, 
+     horizontal?1.0 * samples/ gegl_buffer_get_extent (buffer)->height:
+                1.0 * samples/ gegl_buffer_get_extent (buffer)->width,
+     babl_format ("R'G'B' u8"),
+     (void*)rgb_thumb,
+     //(void*)&(info->rgb_thumb)[0],
+     GEGL_AUTO_ROWSTRIDE,
+     GEGL_ABYSS_NONE);
+  return 3 * samples;
+}
+
+
+int extract_audio_energy (GeglAudioFragment *audio, uint8_t *audio_energy, int dups)
+{
+  int i;
+  float left_max = 0;
+  float right_max = 0;
+  float left_sum = 0;
+  float right_sum = 0;
+  int sample_count = gegl_audio_fragment_get_sample_count (audio);
+  for (i = 0; i < sample_count; i++)
+  {
+    left_sum += fabs (audio->data[0][i]);
+    right_sum += fabs (audio->data[1][i]);
+    if (fabs (audio->data[0][i]) > left_max)
+      left_max = fabs (audio->data[0][i]);
+    if (fabs (audio->data[1][i]) > right_max)
+      right_max = fabs (audio->data[1][i]);
+  }
+  left_sum /= sample_count;
+  right_sum /= sample_count;
+  left_max = left_sum;
+  right_max = right_sum;
+  
+  left_max *= 255;
+  if (left_max > 255)
+    left_max = 255;         
+  right_max *= 255;
+  if (right_max > 255)
+    right_max = 255;         
+
+  for (i = 0; i < dups; i ++)
+  {
+    audio_energy[3*i+0] = left_max;
+    audio_energy[3*i+1] = (left_max+right_max)/2;
+    audio_energy[3*i+2] = right_max;
+  }
+  return 3 * dups;
+}
+
+int extract_mid_row (GeglBuffer *buffer, void *rgb_mid_row, int samples)
+{
+  GeglRectangle mid_row;
+  mid_row.width = 1;
+  mid_row.height = samples;
+  mid_row.x = 
+     gegl_buffer_get_extent (buffer)-> width * 1.0 *
+      samples / gegl_buffer_get_extent (buffer)->height / 2.0;
+  mid_row.y = 0;
+  gegl_buffer_get (buffer, &mid_row, 
+    1.0 * samples / gegl_buffer_get_extent (buffer)->height,
+    babl_format ("R'G'B' u8"),
+    rgb_mid_row,
+    GEGL_AUTO_ROWSTRIDE,
+    GEGL_ABYSS_NONE);
+  return 3 * samples;
+}
+
+static void record_pix_stats (GeglBuffer *buffer, GeglBuffer *previous_buffer,
+         uint8_t *info_rgb_hist,
+         uint8_t *rgb_square_diff)
+{
+  int rgb_hist[NEGL_RGB_HIST_DIM * NEGL_RGB_HIST_DIM * NEGL_RGB_HIST_DIM]={0,};
+  int sum = 0;
+  int second_max_hist = 0;
+  int max_hist = 0;
+  GeglBufferIterator *it = gegl_buffer_iterator_new (buffer, NULL, 0,
+          babl_format ("R'G'B' u8"),
+          GEGL_BUFFER_READ,
+          GEGL_ABYSS_NONE);
+  if (previous_video_frame)
+    gegl_buffer_iterator_add (it, previous_buffer, NULL, 0,
+          babl_format ("R'G'B' u8"),
+          GEGL_BUFFER_READ,
+          GEGL_ABYSS_NONE);
+
+  int slot;
+  for (slot = 0; slot < NEGL_RGB_HIST_SLOTS; slot ++)
+    rgb_hist[slot] = 0;
+  long r_square_diff = 0;
+  long g_square_diff = 0;
+  long b_square_diff = 0;
+
+  while (gegl_buffer_iterator_next (it))
+  {
+    uint8_t *data = (void*)it->data[0];
+    int i;
+    if (strstr (format, "histogram"))
+    {
+      for (i = 0; i < it->length; i++)
+      {
+        int r = data[i * 3 + 0] / 256.0 * NEGL_RGB_HIST_DIM;
+        int g = data[i * 3 + 1] / 256.0 * NEGL_RGB_HIST_DIM;
+        int b = data[i * 3 + 2] / 256.0 * NEGL_RGB_HIST_DIM;
+        int slot = r * NEGL_RGB_HIST_DIM * NEGL_RGB_HIST_DIM +
+                   g * NEGL_RGB_HIST_DIM +
+                   b;
+        if (slot < 0) slot = 0;
+        if (slot >= NEGL_RGB_HIST_SLOTS)
+            slot = NEGL_RGB_HIST_SLOTS;
+
+        rgb_hist[slot]++;
+        if (rgb_hist[slot] > max_hist)
+        {
+          second_max_hist = max_hist;
+          max_hist = rgb_hist[slot];
+        }
+        sum++;
+      }
+    }
+  
+      if (previous_buffer)
+      {
+        uint8_t *data_prev = (void*)it->data[1];
+        int i;
+        for (i = 0; i < it->length; i++)
+        {
+  #define square(a) ((a)*(a))
+          r_square_diff += square(data[i * 3 + 0] -data_prev [i * 3 + 0]);
+          g_square_diff += square(data[i * 3 + 1] -data_prev [i * 3 + 1]);
+          b_square_diff += square(data[i * 3 + 2] -data_prev [i * 3 + 2]);
+        }
+      }
+  }
+
+  if (strstr(format, "histogram"))
+  {
+   int slot;
+   for (slot = 0; slot < NEGL_RGB_HIST_SLOTS; slot ++)
+   {
+     int val = (sqrtf(rgb_hist[slot]) / (sqrtf(second_max_hist) * 0.9 + sqrtf(max_hist) * 0.1)) * 255;
+     if (val > 255)
+       val = 255; 
+     info_rgb_hist[rgb_hist_shuffle (slot)] = val;
+    }
+  }
+  if (previous_buffer)
+  {
+    rgb_square_diff[0] = sqrt(r_square_diff) * 255 / sum;
+    rgb_square_diff[1] = sqrt(g_square_diff) * 255 / sum;
+    rgb_square_diff[2] = sqrt(b_square_diff) * 255 / sum;
+  }
+}
+
+gint
+main (gint    argc,
+      gchar **argv)
+{
+  if (argc < 2)
+    usage();
+
+  gegl_init (&argc, &argv);
+  parse_args (argc, argv);
+
+  gegl_decode = gegl_node_new ();
+
+  store = gegl_node_new_child (gegl_decode,
+                               "operation", "gegl:buffer-sink",
+                               "buffer", &video_frame, NULL);
+  load = gegl_node_new_child (gegl_decode,
+                              "operation", "gegl:ff-load",
+                              "frame", 0,
+                              "path", video_path,
+                              NULL);
+  gegl_node_link_many (load, store, NULL);
+
+  decode_frame_no (0); /* we issue a processing/decoding of a frame - to get metadata */
+  {
+    gegl_node_get (load, "frame-rate", &frame_rate, NULL);
+    total_frames = 0; gegl_node_get (load, "frames", &total_frames, NULL);
+    
+    if (frame_end == 0)
+      frame_end = total_frames;
+  }
+  GeglRectangle terrain_rect;
+
+  if (horizontal)
+   terrain_rect = (GeglRectangle){0, 0,
+                                frame_end - frame_start + 1,
+                                1024};
+  else
+   terrain_rect = (GeglRectangle){0, 0,
+                                1024,
+                                frame_end - frame_start + 1};
+
+  if (input_analysis_path && g_file_test (input_analysis_path, G_FILE_TEST_IS_REGULAR))
+  {
+    GeglNode *load_graph = gegl_node_new ();
+    GeglNode *load = gegl_node_new_child (load_graph, "operation", "gegl:load", "path", input_analysis_path, 
NULL);
+    GeglNode *store = gegl_node_new_child (load_graph, "operation",
+                                           "gegl:buffer-sink",
+                                           "buffer", &terrain, NULL);
+    gegl_node_link_many (load, store, NULL);
+    gegl_node_process (store);
+    g_object_unref (load_graph);
+
+    frame_end = frame_start + gegl_buffer_get_extent (terrain)->height;
+    /* the last frame aavilavle for analysis is the last one loaded rom cache,.. perhaps with some timeout */
+  }
+  else
+  {
+    terrain = gegl_buffer_new (&terrain_rect, babl_format ("R'G'B' u8"));
+    {
+      gint frame;
+      gint max_buf_pos = 0;
+      for (frame = frame_start; frame <= frame_end; frame++)
+        {
+          FrameInfo info = {0};
+          uint8_t buffer[4096] = {0,};
+          int buffer_pos = 0;
+
+          if (show_progress)
+          {
+            double percent_full = 100.0 * (frame-frame_start) / (frame_end-frame_start);
+            double percent_time = time_out?100.0 * babl_ticks()/1000.0/1000.0 / time_out:0.0;
+            fprintf (stdout, "\r%2.1f%% %i/%i (%i)", 
+                     percent_full>percent_time?percent_full:percent_time,
+                     frame-frame_start,
+                     frame_end-frame_start,
+                     frame);
+            fflush (stdout);
+          }
+
+          GeglRectangle terrain_row;
+          if (horizontal)
+            terrain_row = (GeglRectangle){frame-frame_start, 0, 1, 1024};
+          else
+            terrain_row = (GeglRectangle){0, frame-frame_start, 1024, 1};
+
+          decode_frame_no (frame);
+
+          //for (int i=0;i<(signed)sizeof(buffer);i++)buffer[i]=0;
+
+          char *p = format;
+
+          GString *word = g_string_new ("");
+          while (*p == ' ') p++;
+          for (p= format;p==format || p[-1]!='\0';p++)
+          {
+            if (*p != '\0' && *p != ' ')
+              {
+                g_string_append_c (word, *p);
+              }
+            else
+              {
+               if (!strcmp (word->str, "histogram"))
+               {
+                 record_pix_stats (video_frame, previous_video_frame,
+                                   &(info.rgb_hist[0]),
+                                   &(info.rgb_square_diff)[0]);
+                 for (int i = 0; i < NEGL_RGB_HIST_SLOTS; i++)
+                 {
+                  buffer[buffer_pos] = info.rgb_hist[i];
+                  buffer_pos++;
+                 }
+                 for (int i = 0; i < 3; i++)
+                 {
+                   buffer[buffer_pos] = info.rgb_square_diff[i];
+                   buffer_pos++;
+                 }
+               }
+               else if (!strcmp (word->str, "mid-row"))
+               {
+                  int samples = NEGL_RGB_HEIGHT;
+                  if (p[1] >= '0' && p[1] <= '9')
+                  {
+                    samples = g_strtod (&p[1], &p);
+                  }
+                  buffer_pos += extract_mid_row (video_frame, &(buffer)[buffer_pos], samples);
+               }
+               else if (!strcmp (word->str, "mid-col"))
+               {
+                  int samples = NEGL_RGB_HEIGHT;
+                  if (p[1] >= '0' && p[1] <= '9')
+                  {
+                    samples = g_strtod (&p[1], &p);
+                  }
+                  buffer_pos += extract_mid_col (video_frame, &(buffer)[buffer_pos], samples);
+               }
+               else if (!strcmp (word->str, "thumb"))
+               {
+                  int samples  = NEGL_RGB_THEIGHT;
+                  int samples2;
+
+                  if (p[1] >= '0' && p[1] <= '9')
+                  {
+                    samples = g_strtod (&p[1], &p);
+                  }
+
+                  if (horizontal)
+                    samples2 = samples * gegl_buffer_get_width 
(video_frame)/gegl_buffer_get_height(video_frame);
+                  else
+                    samples2 = samples * gegl_buffer_get_height 
(video_frame)/gegl_buffer_get_width(video_frame);
+
+                  buffer_pos += extract_thumb (video_frame, &(buffer)[buffer_pos], samples, samples2);
+               }
+               else if (!strcmp (word->str, "audio"))
+               {
+                  int dups = 1;
+                  GeglAudioFragment *audio = NULL;
+
+                  if (p[1] >= '0' && p[1] <= '9')
+                  {
+                    dups = g_strtod (&p[1], &p);
+                  }
+                 gegl_node_get (load, "audio", &audio, NULL);
+                 if (audio)
+                  {
+                    extract_audio_energy (audio, &buffer[buffer_pos], dups);
+                    g_object_unref (audio);
+                  }
+                 buffer_pos+=3 * dups;
+               }
+               g_string_assign (word, "");
+            }
+          }
+          max_buf_pos = buffer_pos;
+          g_string_free (word, TRUE);
+
+          gegl_buffer_set (terrain, &terrain_row, 0, babl_format("RGB u8"),
+                           buffer,
+                           GEGL_AUTO_ROWSTRIDE);
+
+          if (time_out > 1.0 &&
+              babl_ticks()/1000.0/1000.0 > time_out)
+            {
+               frame_end = frame;
+               if (horizontal)
+                 terrain_rect.width = frame_end - frame_start + 1;
+               else
+                 terrain_rect.height = frame_end - frame_start + 1;
+          //     gegl_buffer_set_extent (terrain, &terrain_rect);
+            }
+
+          if (horizontal)
+            terrain_rect.height = max_buf_pos/3;
+          else
+            terrain_rect.width = max_buf_pos/3;
+          gegl_buffer_set_extent (terrain, &terrain_rect);
+        }
+        if (show_progress)
+        {
+          fprintf (stdout, "\n");
+          fflush (stdout);
+        }
+    }
+
+    if (output_analysis_path)
+    {
+      GeglNode *save_graph = gegl_node_new ();
+      GeglNode *readbuf = gegl_node_new_child (save_graph, "operation", "gegl:buffer-source", "buffer", 
terrain, NULL);
+      GeglNode *save = gegl_node_new_child (save_graph, "operation", "gegl:png-save",
+        "path", output_analysis_path, NULL);
+        gegl_node_link_many (readbuf, save, NULL);
+      gegl_node_process (save);
+      g_object_unref (save_graph);
+    }
+  }
+  
+  if (thumb_path)
+  {
+    GeglNode *save_graph = gegl_node_new ();
+    find_best_thumb ();
+    if (frame_thumb != 0)
+      decode_frame_no (frame_thumb-1);
+    decode_frame_no (frame_thumb);
+    GeglNode *readbuf = gegl_node_new_child (save_graph, "operation", "gegl:buffer-source", "buffer", 
video_frame, NULL);
+    GeglNode *save = gegl_node_new_child (save_graph, "operation", "gegl:png-save",
+      "path", thumb_path, NULL);
+      gegl_node_link_many (readbuf, save, NULL);
+    gegl_node_process (save);
+    g_object_unref (save_graph);
+  }
+
+  if (video_frame)
+    g_object_unref (video_frame);
+  video_frame = NULL;
+  if (previous_video_frame)
+    g_object_unref (previous_video_frame);
+  previous_video_frame = NULL;
+  if (terrain)
+    g_object_unref (terrain);
+  terrain = NULL;
+  g_object_unref (gegl_decode);
+
+  gegl_exit ();
+
+  return 0;
+}
diff --git a/gcut/renderer.c b/gcut/renderer.c
new file mode 100644
index 0000000..0cdd139
--- /dev/null
+++ b/gcut/renderer.c
@@ -0,0 +1,287 @@
+#define _BSD_SOURCE
+#define _DEFAULT_SOURCE
+
+#include <string.h>
+#include <signal.h>
+#include <stdio.h>
+#include <gegl.h>
+#include "gedl.h"
+#include <mrg.h>
+#include <SDL.h>
+#include <gegl-audio-fragment.h>
+
+static GThread *thread = NULL;
+long babl_ticks (void);
+static long prev_ticks = 0;
+int rendering_frame = -1;
+int done_frame     = -1;
+static int audio_started = 0;
+static int audio_len    = 0;
+static int audio_pos    = 0;
+static int audio_post   = 0;
+
+#define AUDIO_BUF_LEN 819200000
+
+int16_t audio_data[AUDIO_BUF_LEN];
+
+void gedl_cache_invalid (GeglEDL *edl)
+{
+  edl->frame = -1;
+  done_frame=-1;
+  rendering_frame=-1;
+}
+
+
+static void sdl_audio_cb(void *udata, Uint8 *stream, int len)
+{
+  int audio_remaining = audio_len - audio_pos;
+  if (audio_remaining < 0)
+    return;
+
+  if (audio_remaining < len) len = audio_remaining;
+
+  //SDL_MixAudio(stream, (uint8_t*)&audio_data[audio_pos/2], len, SDL_MIX_MAXVOLUME);
+  memcpy (stream, (uint8_t*)&audio_data[audio_pos/2], len);
+  audio_pos += len;
+  audio_post += len;
+  if (audio_pos >= AUDIO_BUF_LEN)
+  {
+    audio_pos = 0;
+  }
+}
+
+static void sdl_add_audio_sample (int sample_pos, float left, float right)
+{
+   audio_data[audio_len/2 + 0] = left * 32767.0 * 0.46;
+   audio_data[audio_len/2 + 1] = right * 32767.0 * 0.46;
+   audio_len += 4;
+
+   if (audio_len >= AUDIO_BUF_LEN)
+   {
+     audio_len = 0;
+   }
+}
+
+static void open_audio (int frequency)
+{
+  SDL_AudioSpec spec = {0};
+  SDL_Init(SDL_INIT_AUDIO);
+  spec.freq = frequency;
+  spec.format = AUDIO_S16SYS;
+  spec.channels = 2;
+  spec.samples = 1024;
+  spec.callback = sdl_audio_cb;
+  SDL_OpenAudio(&spec, 0);
+
+  if (spec.format != AUDIO_S16SYS)
+   {
+      fprintf (stderr, "not getting format we wanted\n");
+   }
+  if (spec.freq != frequency)
+   {
+      fprintf (stderr, "not getting desires samplerate(%i) we wanted got %i instead\n", frequency, 
spec.freq);
+   }
+}
+
+void end_audio (void)
+{
+}
+
+
+static inline void skipped_frames (int count)
+{
+  //fprintf (stderr, "[%i]", count);
+}
+
+static inline void wait_for_frame ()
+{
+  //fprintf (stderr, ".");
+}
+
+
+void playing_iteration (Mrg *mrg, GeglEDL *edl);
+
+extern int got_cached;
+
+static gpointer renderer_thread (gpointer data)
+{
+  GeglEDL *edl = data;
+
+  for (;;)
+  {
+    playing_iteration (edl->mrg, edl);
+    {
+      if (edl->frame_no != done_frame)
+      {
+        rendering_frame = edl->frame_no;
+
+        {
+          GeglRectangle ext = {0,0,edl->width, edl->height};
+          //GeglRectangle ext = gegl_node_get_bounding_box (edl->result);
+          gegl_buffer_set_extent (edl->buffer, &ext);
+        }
+        gedl_set_frame (edl, edl->frame_no); /* this does the frame-set, which causes render  */
+#if 0
+        {
+          GeglRectangle ext = gegl_node_get_bounding_box (edl->result);
+          gegl_buffer_set_extent (edl->buffer, &ext);
+        }
+#endif
+
+        {
+          GeglAudioFragment *audio = gedl_get_audio (edl);
+          if (audio)
+          {
+            int sample_count = gegl_audio_fragment_get_sample_count (audio);
+            if (sample_count > 0)
+            {
+              int i;
+              if (!audio_started)
+              {
+                open_audio (gegl_audio_fragment_get_sample_rate (audio));
+                SDL_PauseAudio(0);
+                audio_started = 1;
+              }
+              for (i = 0; i < sample_count; i++)
+              {
+                sdl_add_audio_sample (0, audio->data[0][i], audio->data[1][i]);
+              }
+            }
+          }
+        }
+        done_frame = rendering_frame;
+        mrg_queue_draw (edl->mrg, NULL); // could queue only rect - if we had it
+      }
+      else
+      {
+        g_usleep (50);
+      }
+    }
+  }
+  end_audio ();
+  return NULL;
+}
+
+void renderer_start (GeglEDL *edl)
+{
+  if (!thread)
+    thread = g_thread_new ("renderer", renderer_thread, edl);
+}
+
+gboolean cache_renderer_iteration (Mrg *mrg, gpointer data);
+
+void renderer_toggle_playing (MrgEvent *event, void *data1, void *data2)
+{
+  GeglEDL *edl = data1;
+  edl->playing =  !edl->playing;
+  if (!edl->playing)
+  {
+    cache_renderer_iteration (event->mrg, edl);
+  }
+  else
+  {
+    killpg(0, SIGUSR2);
+  }
+  mrg_event_stop_propagate (event);
+  mrg_queue_draw (event->mrg, NULL);
+  prev_ticks = babl_ticks ();
+}
+
+static int max_frame (GeglEDL *edl)
+{
+  GList *l;
+  int t = 0;
+  int start, end;
+
+  gedl_get_range (edl, &start, &end);
+  if (end)
+    return end;
+
+  for (l = edl->clips; l; l = l->next)
+  {
+    Clip *clip = l->data;
+    t += clip_get_frames (clip);
+  }
+
+  return t;
+}
+
+
+void playing_iteration (Mrg *mrg, GeglEDL *edl)
+{
+  long ticks = 0;
+  double delta = 1;
+  ticks = babl_ticks ();
+  if (prev_ticks == 0) prev_ticks = ticks;
+
+  if (edl->playing)
+    {
+#if 0
+      if (prev_ticks - ticks < 1000000.0 / gedl_get_fps (edl))
+        return;
+#endif
+      delta = (((ticks - prev_ticks) / 1000000.0) * ( edl->fps ));
+      //fprintf (stderr, "%f\n", delta);
+      if (delta < 1.0)
+      {
+        wait_for_frame ();
+        mrg_queue_draw (mrg, NULL);
+        return;
+      }
+        //delta = 0;
+      {
+#if 0
+        static int frameskip = -1;
+        if (frameskip < 0)
+        {
+          if (getenv ("GEDL_FRAMESKIP"))
+            frameskip = 1;
+          else
+            frameskip = 0;
+        }
+        if (!frameskip)
+          delta = 1;
+#else
+        if (edl->framedrop)
+        {
+          if (delta >= 2.0)
+            {
+              skipped_frames ( (int)(delta)-1 );
+            }
+        }
+        else
+        {
+          if (delta > 1.0)
+            delta = 1;
+          else
+            delta = 0;
+        }
+#endif
+      }
+      if (rendering_frame != done_frame)
+        return;
+      if (delta >= 1.0)
+      {
+
+      if (edl->active_clip)
+      {
+        edl->frame_no += delta;
+        int start, end;
+        gedl_get_range (edl, &start, &end);
+        if (edl->frame_no > max_frame (edl))
+        {
+           edl->frame_no = 0;
+           if (end)
+             edl->frame_no = start;
+        }
+        edl->active_clip = edl_get_clip_for_frame (edl, edl->frame_no);
+        prev_ticks = ticks;
+      }
+      }
+    }
+}
+
+int renderer_done (GeglEDL *edl)
+{
+  return done_frame == edl->frame_no; //rendering_frame;
+}



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