Simplify code that renders gstreamer pipelines.

Change-Id: Ibbd3d50fb9b7d3337155d7c3eaa12f5a50bdef18
diff --git a/edgetpuvision/gst.py b/edgetpuvision/gst.py
index e5875f1..a1fa177 100644
--- a/edgetpuvision/gst.py
+++ b/edgetpuvision/gst.py
@@ -2,7 +2,7 @@
 import itertools
 import re
 
-__all__ = ('Filter', 'Queue', 'Caps', 'Tee',
+__all__ = ('Filter', 'Source', 'Sink', 'Queue', 'Tee', 'Caps', 'Pad',
            'Size', 'Fraction', 'Format',
            'describe', 'max_inner_size', 'min_outer_size', 'center_inside', 'parse_format')
 
@@ -50,80 +50,58 @@
 def join(name, sep, params, param_sep=' '):
     return name if not params else name + sep + join_params(params, param_sep)
 
-def params_with_name(params, base, name_gens):
-    if 'name' in params:
-        return params
-    else:
-        return {**params, 'name': base + next(name_gens[base])}
-
-def suffix_gen():
-    yield ''
-    for i in itertools.count(1):
-        yield str(i)
-
-class Element:
-    def __init__(self, params):
-        self.params = params
-
-    def __getattr__(self, name):
-        return self.params[name]
-
-class Filter(Element):
-    def __init__(self, filtername, pads=None, **params):
-        super().__init__(params)
-        self.filtername = filtername
-        self.pads = pads
+class Pad:
+    def __init__(self, name, pad=''):
+        self.name = name
+        self.pad = pad
 
     def __str__(self):
-        return join(self.filtername, ' ', self.params)
+        return '%s.%s' % (self.name, self.pad)
 
-class Queue(Element):
-    def __init__(self, **params):
-        super().__init__(params)
-
-    def __str__(self):
-        return join('queue', ' ', self.params)
-
-class Caps(Element):
+class Caps:
     def __init__(self, mediatype, **params):
-        super().__init__(params)
+        self.params = params
         self.mediatype = mediatype
 
     def __str__(self):
         return join(self.mediatype, ',', self.params, ',')
 
-class Tee(Element):
-    def __init__(self, pads=None, **params):
-        super().__init__(params)
-        self.pads = pads
+class Element:
+    def __init__(self, elementname, params):
+        self.elementname = elementname
         self.params = params
 
+    def __getattr__(self, name):
+        return self.params[name]
+
     def __str__(self):
-        return join('tee', ' ', self.params)
+        return join(self.elementname, ' ', self.params)
 
-def describe0(arg, name_gens, depth):
-    recur = lambda x: describe0(x, name_gens, depth + 1)
-    indent = '  ' * (depth + 1)
+class Filter(Element):
+    def __init__(self, filtername, **params):
+        super().__init__(filtername, params)
 
+class Source(Element):
+    def __init__(self, sourcename, **params):
+        super().__init__(sourcename + 'src', params)
+
+class Sink(Element):
+    def __init__(self, sinkname, **params):
+        super().__init__(sinkname + 'sink', params)
+
+class Queue(Element):
+    def __init__(self, **params):
+        super().__init__('queue', params)
+
+class Tee(Element):
+    def __init__(self, **params):
+        super().__init__('tee', params)
+
+def describe0(arg):
     if isinstance(arg, collections.Sequence):
-        return ' ! '.join(recur(x) for x in arg)
-    elif isinstance(arg, Tee):
-        params = params_with_name(arg.params, 't', name_gens)
-        return join('tee', ' ', params) + '\n' + \
-             '\n'.join('%s%s. ! %s' % (indent, params['name'], recur(x)) for x in arg.pads)
-    elif isinstance(arg, Filter):
-        body = join(arg.filtername, ' ', arg.params)
-        if arg.pads:
-            params = params_with_name(arg.params, 'f', name_gens)
-            return body + '\n' + \
-              '\n'.join('%s%s.%s ! %s' % (indent, params['name'], pad_name, recur(x)) for pad_name, x in arg.pads.items())
-        return body
-    elif isinstance(arg, Queue):
-        return join('queue', ' ', arg.params)
-    elif isinstance(arg, Caps):
-        return join(arg.mediatype, ',', arg.params, ',')
+        return ' ! '.join(describe0(x) for x in arg)
     else:
-        raise ValueError('Invalid element: %s' % arg)
+        return str(arg)
 
 def describe(pipeline):
-    return describe0(pipeline, collections.defaultdict(suffix_gen), 0)
+    return '\n'.join(describe0(x) for x in pipeline)
diff --git a/edgetpuvision/pipelines.py b/edgetpuvision/pipelines.py
index 2c049e2..70d1d88 100644
--- a/edgetpuvision/pipelines.py
+++ b/edgetpuvision/pipelines.py
@@ -1,136 +1,136 @@
 from .gst import *
 
 def decoded_file_src(filename):
-    return (
-        Filter('filesrc', location=filename),
+    return [
+        Source('file', location=filename),
         Filter('decodebin'),
-    )
+    ]
 
 def v4l2_src(fmt):
-    return (
-        Filter('v4l2src', device=fmt.device),
+    return [
+        Source('v4l2', device=fmt.device),
         Caps('video/x-raw', format=fmt.pixel, width=fmt.size.width, height=fmt.size.height,
              framerate='%d/%d' % fmt.framerate),
-    )
+    ]
 
 def display_sink(fullscreen, sync=False):
-    return Filter('kmssink' if fullscreen else 'waylandsink', sync=sync),
+    return Sink('kms' if fullscreen else 'wayland', sync=sync),
 
 def h264_sink():
-    return Filter('appsink', name='h264sink', emit_signals=True, max_buffers=1, drop=False, sync=False)
+    return Sink('app', name='h264sink', emit_signals=True, max_buffers=1, drop=False, sync=False)
 
 def inference_pipeline(render_size, inference_size):
     size = max_inner_size(render_size, inference_size)
-    return (
+    return [
         Filter('glfilterbin', filter='glcolorscale'),
         Caps('video/x-raw', format='RGBA', width=size.width, height=size.height),
         Filter('videoconvert'),
         Caps('video/x-raw', format='RGB', width=size.width, height=size.height),
         Filter('videobox', autocrop=True),
         Caps('video/x-raw', width=inference_size.width, height=inference_size.height),
-        Filter('appsink', name='appsink', emit_signals=True, max_buffers=1, drop=True, sync=False)
-    )
+        Sink('app', name='appsink', emit_signals=True, max_buffers=1, drop=True, sync=False),
+    ]
 
 # Display
 def image_display_pipeline(filename, render_size, inference_size, fullscreen):
     size = max_inner_size(render_size, inference_size)
     return (
-        decoded_file_src(filename),
-        Tee(pads=((
-            Queue(),
-            Filter('imagefreeze'),
-            Filter('videoconvert'),
-            Filter('videoscale'),
-            Caps('video/x-raw', width=render_size.width, height=render_size.height),
-            Filter('rsvgoverlay', name='overlay'),
-            display_sink(fullscreen),
-        ),(
-            Queue(),
-            Filter('imagefreeze'),
-            Filter('glupload'),
-            inference_pipeline(render_size, inference_size),
-        )))
+        [decoded_file_src(filename),
+         Tee(name='t')],
+        [Pad('t'),
+         Queue(),
+         Filter('imagefreeze'),
+         Filter('videoconvert'),
+         Filter('videoscale'),
+         Caps('video/x-raw', width=render_size.width, height=render_size.height),
+         Filter('rsvgoverlay', name='overlay'),
+         display_sink(fullscreen)],
+        [Pad('t'),
+         Queue(),
+         Filter('imagefreeze'),
+         Filter('glupload'),
+         inference_pipeline(render_size, inference_size)],
     )
 
 def video_display_pipeline(filename, render_size, inference_size, fullscreen):
     return (
-        decoded_file_src(filename),
-        Filter('glupload'),
-        Tee(pads=((
-            Queue(max_size_buffers=1),
-            Filter('glfilterbin', filter='glcolorscale'),
-            Filter('rsvgoverlay', name='overlay'),
-            Caps('video/x-raw', width=render_size.width, height=render_size.height),
-            display_sink(fullscreen),
-        ),(
-            Queue(max_size_buffers=1, leaky='downstream'),
-            inference_pipeline(render_size, inference_size),
-        )))
+        [decoded_file_src(filename),
+         Filter('glupload'),
+         Tee(name='t')],
+        [Pad('t'),
+         Queue(max_size_buffers=1),
+         Filter('glfilterbin', filter='glcolorscale'),
+         Filter('rsvgoverlay', name='overlay'),
+         Caps('video/x-raw', width=render_size.width, height=render_size.height),
+         display_sink(fullscreen)],
+        [Pad('t'),
+         Queue(max_size_buffers=1, leaky='downstream'),
+         inference_pipeline(render_size, inference_size)],
     )
 
 def camera_display_pipeline(fmt, render_size, inference_size, fullscreen):
     return (
-        v4l2_src(fmt),
-        Filter('glupload'),
-        Tee(pads=((
-            Queue(max_size_buffers=1, leaky='downstream'),
-            Filter('glfilterbin', filter='glcolorscale'),
-            Filter('rsvgoverlay', name='overlay'),
-            display_sink(fullscreen),
-        ),(
-            Queue(max_size_buffers=1, leaky='downstream'),
-            inference_pipeline(render_size, inference_size),
-        )))
+        [v4l2_src(fmt),
+         Filter('glupload'),
+         Tee(name='t')],
+        [Pad(name='t'),
+         Queue(max_size_buffers=1, leaky='downstream'),
+         Filter('glfilterbin', filter='glcolorscale'),
+         Filter('rsvgoverlay', name='overlay'),
+         display_sink(fullscreen)],
+        [Pad(name='t'),
+         Queue(max_size_buffers=1, leaky='downstream'),
+         inference_pipeline(render_size, inference_size)],
     )
 
 # Headless
 def image_headless_pipeline(filename, render_size, inference_size):
     return (
-      decoded_file_src(filename),
-      Filter('imagefreeze'),
-      Filter('glupload'),
-      inference_pipeline(render_size, inference_size),
+      [decoded_file_src(filename),
+       Filter('imagefreeze'),
+       Filter('glupload'),
+       inference_pipeline(render_size, inference_size)],
     )
 
 def video_headless_pipeline(filename, render_size, inference_size):
     return (
-        decoded_file_src(filename),
-        Filter('glupload'),
-        inference_pipeline(render_size, inference_size),
+        [decoded_file_src(filename),
+         Filter('glupload'),
+         inference_pipeline(render_size, inference_size)],
     )
 
 def camera_headless_pipeline(fmt, render_size, inference_size):
     return (
-        v4l2_src(fmt),
-        Filter('glupload'),
-        inference_pipeline(render_size, inference_size),
+        [v4l2_src(fmt),
+         Filter('glupload'),
+         inference_pipeline(render_size, inference_size)],
     )
 
 # Streaming
 def video_streaming_pipeline(filename, render_size, inference_size):
     return (
-        Filter('filesrc', location=filename),
-        Filter('qtdemux'),
-        Tee(pads=((
-          Queue(max_size_buffers=1),
-          Filter('h264parse'),
-          Caps('video/x-h264', stream_format='byte-stream', alignment='nal'),
-          h264_sink()
-        ), (
-          Queue(max_size_buffers=1),
-          Filter('decodebin'),
-          inference_pipeline(render_size, inference_size),
-        )))
+        [Source('file', location=filename),
+         Filter('qtdemux'),
+         Tee(name='t')],
+        [Pad('t'),
+         Queue(max_size_buffers=1),
+         Filter('h264parse'),
+         Caps('video/x-h264', stream_format='byte-stream', alignment='nal'),
+         h264_sink()],
+        [Pad('t'),
+         Queue(max_size_buffers=1),
+         Filter('decodebin'),
+         inference_pipeline(render_size, inference_size)],
     )
 
 def camera_streaming_pipeline(fmt, profile, bitrate, render_size, inference_size):
     size = max_inner_size(render_size, inference_size)
     return (
-        v4l2_src(fmt),
-        Tee(pads=((
-          Queue(max_size_buffers=1, leaky='downstream'),
-          Filter('videoconvert'),
-          Filter('x264enc',
+        [v4l2_src(fmt), Tee(name='t')],
+        [Pad('t'),
+         Queue(max_size_buffers=1, leaky='downstream'),
+         Filter('videoconvert'),
+         Filter('x264enc',
                  speed_preset='ultrafast',
                  tune='zerolatency',
                  threads=4,
@@ -140,9 +140,8 @@
           Caps('video/x-h264', profile=profile),
           Filter('h264parse'),
           Caps('video/x-h264', stream_format='byte-stream', alignment='nal'),
-          h264_sink()
-        ), (
-          Queue(),
-          inference_pipeline(render_size, inference_size)
-        )))
+          h264_sink()],
+        [Pad('t'),
+         Queue(),
+         inference_pipeline(render_size, inference_size)],
     )
\ No newline at end of file