Latest News

Follow the latest news on OpenShot Video Editor!


Merry Christmas everyone! Here is the first set of screenshots ever released for OpenShot Video Editor. They showcase a few of the features we are working on, including the awesome timeline! Please help us out by sharing these screenshots and linking to this blog. Also, as i said in my preview post, we are still looking for contributors that have any Gnonlin experience, so please contact us if you are interested in helping us. (view larger images)

[Update: These screenshots are constantly updated, and should reflect the current state of OpenShot]


We are looking for a few good contributors to help us with OpenShot Video Editor! If you have any experience with Gnonlin and are interested in joining our team, please contact me through LaunchPad or post a comment to this blog entry.

Gnonlin has great potential to help empower the next wave of video editors for Linux, but unfortunately it's barely documented! In fact, there are only a few (3 to be exact) tutorials for Gnonlin that I have found.

As a community, I think we will see a surge in the number of video editors being developed for Linux when Gnonlin has better documentation and more tutorials (and fewer bugs). I can definitely understand why many people gravitate towards MLT and other "more documented" media frameworks.


I am revisiting the topic of exporting a frame of video to a PNG image file using Python and Gstreamer. In my previous post, I demonstrated how to use the gst-launch command to accomplish this, but I never could get it to work with just Python. Also, my previous pipeline only worked for MPEG and DV video files.

Before we get into the Python part of this post, let me share an updated pipeline that can be used from the command line. This should work with any valid Gstreamer video file, and export the 1st frame to a PNG:

gst-launch gstfilesrc location=/home/MyUser/Videos/MyMovieFile.MPG ! decodebin ! ffmpegcolorspace ! pngenc ! filesink location=/home/MyUser/Videos/MyExportedFrame.png
Now on to the fun part... the Python version of this same pipeline. There are really two approaches to building a Gstreamer pipeline in Python. The 1st approach is to create each element as a separate object, link the elements together, and then change the state of the pipeline to "PLAYING". This can be very difficult to do correctly (based on the complexity of your pipeline), and due to the fact not all elements can be linked together before the pipeline has been prerolled.

The 2nd approach to build a pipeline is much simpler and has many limitations, but it works great for exporting video frames. Please see the 5 lines of Python code below which does the same thing as my pipeline above.

# This example Python script takes any gstreamer movie format, and exports a PNG image of the
# 1st frame. In other words, it takes a screen grab or screen capture of your video file.
# This example uses an MPEG video, but it should work the same with any video format.

# Import the gstreamer library
import gst

# Define your pipeline, just as you would at the command prompt.
# This is much easier than trying to create and link each gstreamer element in Python.
# This is great for pipelines that end with a filesink (i.e. there is no audible or visual output)
pipe = gst.parse_launch("filesrc location=/home/MyUser/Videos/MyMovieFile.MPG ! decodebin ! ffmpegcolorspace ! pngenc ! filesink location=/home/MyUser/Videos/MyExportedFrame.png")

# Get a reference to the pipeline's bus
bus = pipe.get_bus()

# Set the pipeline's state to PLAYING

# Listen to the pipeline's bus indefinitely until we receive a EOS (end of stream) message.
# This is a super important step, or the pipeline might not work as expected. For example,
# in my example pipeline above, the pngenc will not export an actual image unless you have
# this line of code. It just exports a 0 byte png file. So... don't forget this step.
bus.poll(gst.MESSAGE_EOS, -1)

I hope these examples are useful to you. It's hard to find good Python and Gstreamer examples, and this might be one of the few good examples of this technique on the Internet (as of today). If there are other examples of this, please let me know, because I haven't found a single one. Good luck!


What do pickles and pythons have in common? Well, I'll get to that in a second. First, let's talk about application data. A key part of every program design is "how and where do I store my data". I have considered many different options for the OpenShot Video Editor, including XML, Embedded MySQL, and SqlLite.

The problem with using any of these methods, is that I will be responsible for writing all the data access code. That would include iterating through my complex object model, trying to persist all the various variables, objects, collections, and lists when the user clicks "save". Then when the user returns and "opens" their project, I would have to reverse the process. This can be a very tedious and buggy process. There has to be an easier way, right?

Here is where the pickles come into this story. There is a "pickle" module (as well as a faster cPickle module) that comes built in to Python. It is responsible for serializing and deserializing any Python object, including complex custom objects.

Here are some examples of how the cPickle module works:

Saving a custom object to the file system:
import cPickle as pickle
myFile = file(file_path, "wb")
pickle.dump(your_custom_object, myFile, True)

Loading a custom object from the file system:
import cPickle as pickle
myFile = file(file_path, "rb")
your_custom_object = pickle.load(myFile)


We've all heard the promise of GTK and how it will allow your application to be easily ported to other operating systems, such as Windows, Mac, Linux, and others. Something that you might not know is that if your GTK application uses the Gnome library, it will only work on Linux! This seems obvious to me now, but this really stumped me for a while.

Check it: I was using the Glade3 Interface designer to create some GTK forms, and I choose the Gnome App (instead of the GTK window). It looked the same, and was mostly assembled out of GTK widgets, but it has a subtle difference... it requires the Gnome library to be referenced. Long story short, I spend hours and hours trying to get my simple Gnome App to work on Windows with no luck. As soon as I replaced it with a standard GTK window, it worked great on Windows.

import gtk, # This is fine, and your program will work on all OSes
import gnome.ui #This will destroy your hopes of a cross-platform app

So, to wrap this up... be sure to use only 100% pure GTK if you are interested in a cross-platform application.


Once again I find myself struggling with Drag 'n Drop using GTK and Python. Why is it so difficult to get something working? Why do all the examples on the Internet suck? These are all good questions my friends, and today I bring some answers.

First of all, there are many different levels of drag 'n drop in GTK. There are some higher level methods and some lower level methods. I won't pretend to be an expert, but instead I will share a SUPER USEFUL example I've found and modified. If you are struggling to implement drag 'n drop in your application, please stop now, and run this example. Hopefully, this will work with your own widgets and save you some time.

import pygtk
import gtk

# function to track the motion of the cursor while dragging
def motion_cb(wid, context, x, y, time):
print "motion"
context.drag_status(gtk.gdk.ACTION_COPY, time)
return True

# function to print out the mime type of the drop item
def drop_cb(wid, context, x, y, time):
l.set_text('\n'.join([str(t) for t in context.targets]))
context.finish(True, False, time)
return True

# Create a GTK window and Label, and hook up
# drag n drop signal handlers to the window
w = gtk.Window()
w.set_size_request(200, 150)
w.drag_dest_set(0, [], 0)
w.connect('drag_motion', motion_cb)
w.connect('drag_drop', drop_cb)
w.connect('destroy', lambda w: gtk.main_quit())
l = gtk.Label()

# Start the program

My previous posts about drag n' drop used some of the higher level methods and signals, such as drag_data_received. This worked great for dropping text/uri-list type items on a gtkTreeView, but it didn't work for tree nodes (which are type GTK_TREE_MODEL_ROW). In other words, it didn't work with a tree node as a drop item... instead it seemed to ignore that type of item.

So, to wrap this up, use the above example in your program, cross your fingers, and I hope this might save you some time. Good luck!


I have just learned a few more tricks on how to best work with GTK and Python. First, let me start at the beginning. After you create a Glade file (If you don't know what Glade is, read this), the next step is to create the Python code to load the Glade file (i.e. turn the XML into actual widgets on the screen), and hook up all the signals to actual Python methods. Well, if you're like me, you don't know how many parameters are needed for each signal handler, and soon you find yourself looking for examples (or just guessing) on how to hook up various signals (such as a button click, treeview drag_begin, window destroy, etc...). This can become a very time consuming and frustrating process, especially when you are just learning Python & GTK.

Trick #1: Using Synaptic Package Manager (or equivalent), go get the tepache package. This package gives you a command line way of generating the Python code needed to load a Glade file into a class, auto-connect all the signals, and it even stubs out all the signal handler methods. If that wasn't enough, it even helps setup language translations for your Glade file.

To use the tepache command, use the following syntax in the terminal window: tepache YourFile.Glade

After you run this command, it will generate a .py file, named Here is the output from the tepache command using one of my more complex Glade files:

import os
import gtk
import gnome

from SimpleGladeApp import SimpleGladeApp
from SimpleGladeApp import bindtextdomain

app_name = "main"
app_version = "0.0.1"

glade_dir = ""
locale_dir = ""

bindtextdomain(app_name, locale_dir)

class App1(SimpleGladeApp):

def __init__(self, path="",
domain=app_name, **kwargs):
path = os.path.join(glade_dir, path)
SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

def new(self):
print "A new %s has been created" % self.__class__.__name__

def on_app1_destroy(self, widget, *args):
print "on_app1_destroy called with self.%s" % widget.get_name()

def on_mnuQuit_activate(self, widget, *args):
print "on_mnuQuit_activate called with self.%s" % widget.get_name()

def on_mnuAbout_activate(self, widget, *args):
print "on_mnuAbout_activate called with self.%s" % widget.get_name()

def on_treeFiles_drag_drop(self, widget, *args):
print "on_treeFiles_drag_drop called with self.%s" % widget.get_name()

def on_tlbAddTrack_clicked(self, widget, *args):
print "on_tlbAddTrack_clicked called with self.%s" % widget.get_name()

def on_tlbRemoveTrack_clicked(self, widget, *args):
print "on_tlbRemoveTrack_clicked called with self.%s" % widget.get_name()

def on_hscrollbar1_value_changed(self, widget, *args):
print "on_hscrollbar1_value_changed called with self.%s" % widget.get_name()

def on_vscrollbar1_value_changed(self, widget, *args):
print "on_vscrollbar1_value_changed called with self.%s" % widget.get_name()

def on_scrolledwindow_Right_drag_drop(self, widget, *args):
print "on_scrolledwindow_Right_drag_drop called with self.%s" % widget.get_name()

def main():
gnome.program_init("main", "0.0.1")
app1 = App1()

if __name__ == "__main__":

Trick #2: There is a library that comes with pyGtk called SimpleGladeApp, which is going to save you a ton of time. It is used quite heavily in the tepache output (as you can see from my example). What does this library do, you ask?

Let me break it down for you:
  1. It loads a Glade XML file and returns an object for your form. It reminds me very much of a form object from the Windows world (i.e. Visual Basic.NET, C# Windows Application, etc...).
  2. On this new form object, it has a reference to every single widget on your form. For example: self.myTree, self.btnToolBar1, self.Canvas1.
  3. It adds a show() and hide() method for every widget.
  4. It adds a grab_focus() method for every widget.
  5. It adds a run() and quit() for your form (which simply calls the main_loop() gtk methods.
  6. It auto-connects all the signal handlers you have defined (of that tepache defined) to the signals defined in the Glade file.
  7. It translates all the text in the Glade file (if you have the appropriate translation files in your project).
So, now that you know this, take a look at my example above again. Anywhere in this class you can access all the widgets, hide and show them, set focus to them, all without calling the GladeFile.get_widget("") method.

I hope this blog entry is helpful to some of you. It's going to save me a ton of time trying to setup my GUI and hook up all the signals. Good luck!


Have you ever wanted to change the drag icon on a GTK TreeView control using Python? This sounds really easy, but in fact there is a trick to getting it to work correctly. Hopefully you have found this blog prior to spending 7 days searching for a solution (like I did).

I have posted a fully working example program below, that will demonstrate how to successfully change the icon used during a drag operation. Pay close attention to way the drag_begin signal is connected (hint: that's the trick). Also, for this example to work, you must change the image path to point at a valid JPG or PNG image on your computer.

Hopefully the world is a better place now that I've shared this example. Good luck!

# import the gtk library
import gtk

# signal handler for the tree drag begin signal
# change the drag icon to a JPG as soon as the drag starts
def on_drag_begin(widget, context):
print "Drag Begin"

# Get a pixbuf of an image (i.e. JPG). This will be your icon.
play_image = gtk.image_new_from_file("glade/icons/jpg/arrow.jpg")
mypixbuf = play_image.get_pixbuf()

# Change the icon to the pixbuf (i.e. the JPG)
context.set_icon_pixbuf(mypixbuf, 10, 10)

# add tree to window
myTree = gtk.TreeView()

# Enable drag and drop on the treeview. If you don't call this method,
# the tree won't allow you to drag anything
myTree.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, [('text/plain', 0, 0)], gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)

# connect the drag begin signal handler. Be sure
# to use the "connect_after" syntax and not the regular "connect".
myTree.connect_after('drag_begin', on_drag_begin)

# create a TreeStore with one string column to use as the model
myTreeStore = gtk.TreeStore(str, str)

# we'll add some data now
myTreeStore.append(None, ["Choose a Video or Audio File to Begin", ""])

# Set the treeview's data model

# create 2 TreeViewColumns to display the data
tvcolumn = gtk.TreeViewColumn('File Name')
tvcolumn1 = gtk.TreeViewColumn('Length')

# add columns to treeview

# create a CellRendererText to render the data
cell = gtk.CellRendererText()
cell1 = gtk.CellRendererText()

# add the cell to the tvcolumn and allow it to expand
tvcolumn.pack_start(cell, True)
tvcolumn1.pack_start(cell1, True)

# set the cell "text" attribute to column 0 - retrieve text
# from that column in treestore
tvcolumn.add_attribute(cell, 'text', 0)
tvcolumn1.add_attribute(cell1, 'text', 1)

# create new window and add the treeview
window = gtk.Window()

# Start the Main loop


If you have ever attempted to enable drag & drop with Python and GTK, then you've probably run into the same problem I did. When I would drop an item on a widget, it just would not fire off the signal handler. Very frustrating!!!

However, after some digging around, I found the solution. It's a GTK method that you must call to enable a widget as a drop target. The method is widgetname.drag_dest_set(). See my example below to see how it's used.

Example Code:

This isn't a fully working example, but the code snippets are valid syntax and should be useful in your own program. Good luck!

# Set the type of file that can be dropped
dnd_list = [ ( 'text/uri-list', 0, TARGET_TYPE_URI_LIST ) ]

# Connect a function to handle the drop signal
myTree.connect('drag_data_received', on_drag_data_received)

# IMPORTANT: Set an object as a drop destination, or none of this code will work
myTree.drag_dest_set( gtk.DEST_DEFAULT_MOTION |
dnd_list, gtk.gdk.ACTION_COPY)

# Connect a function to handle the drag motion signal from a goocanvas widget
MyCanvas.connect('drag_motion', on_drag_motion)

# Set the canvas to allow drops from any type of file
MyCanvas.drag_dest_set(0, [], 0)

# Function to handle the drag motion
def on_drag_motion(wid, context, x, y, time):
print " *** motion detected ***"

# Function to handle the drop
def on_drag_data_received(widget, context, x, y, selection, target_type, timestamp):
print " ((( drop detected ))) "


I haven't made a ton of progress over the last 30 days, due to my "paying" job and my family obligations, however, I'm hopeful that September will be more productive. Here are my goals for September:

Goal 1) Finish integrating my smaller test projects into the final OpenShot code base.
Goal 2) Complete the user interface, including branding and custom icons.

Here are the icons I've developed thus far:


OpenShot Video Editor now has a home at I will be managing the entire project using LaunchPad, including source control, bug tracking, idea tracking, language translations, and a custom repository for deployments.

Even though I love Perforce, and would have liked to use it for this project, LaunchPad just offers so much integration... so many features... I just couldn't resist. By the way, the source code in LaunchPad is still very unstable, and I don't recommend waisting your time looking at it until I make an announcement that it's ready.


After months of brainstorming, I have finally decided on an official name for this project: OpenShot Video Editor. Open stands for open-source, and Shot stands for a single cinematic take.

Here is my first draft of the logo. Expect the final product to include a theme that borrows heavily from this logo's colors and design elements.


Have you ever run into a problem where your favorite Python IDE can't determine the type of a variable, and thus you get no auto-completion? There is a built-in function in Python called isInstance(your_variable, type_of_variable) that is designed to compare a variable to a class type, and return true or false. However, just the presence of this function will give many IDEs all the information they need to determine the type of your variable, and thus give you better auto-completion.

For example, imagine you had a function that returned a gtk.Window.
MyWindow = GetMyWindow()

Most IDEs would not correctly determine the type of MyWindow. To solve this, try the following code:

MyWindow = GetMyWindow()
isInstance(MyWindow, gtk.Window)

There are many articles that point out the dangers of using isInstance, however if you are simply using it like my example, it shouldn't cause any problems.


There comes a time in every man's life where a single goocanvas.Canvas widget just won't cut it. Imagine you need two separate canvases for your interface, but you want them to scroll together with a single scroll bar. How would you solve this problem?

First, to enable a goocanvas.Canvas object to scroll is very easy (or so it would seem). Simply drop it into a gtk.ScrolledWindow widget, and you're done. As long as the boundary of your canvas exceeds the size, the scroll bars will work.

The Problem...

However, don't get too excited. A scrolledWindow widget that has a goocanvas.Canvas object as a child will not bubble up any scroll signals / events. So, if you have another canvas on the window, you will have no luck trying to synchronize up the scrollbars. Or if you want only part of the canvas to scroll, and part to remain fixed, this will not work.

The Solution...
Place two canvas objects next to each other. Place a vertical and horizontal scroll bar where you want them to appear. Create a signal handler for the scroll events, and use the Canvas.scroll_to(x, y) method to manually scroll both canvases.

Example Code:
Here are the signal handler functions for the 2 scroll bars to synchronize the scrolling of 2 goocanvas.Canvas objects:

def on_vscrollbar1_value_changed(self, widget):
isinstance(widget, gtk.VScrollbar)
vertical_value = widget.get_value()

# Get horizontal value
horizontal_scrollbar = self.frmMain.get_widget("hscrollbar1")
horizontal_value = horizontal_scrollbar.get_value()

# scroll the canvases
self.MyCanvas.scroll_to(horizontal_value, vertical_value)
self.MyCanvas_Left.scroll_to(horizontal_value, vertical_value)

def on_hscrollbar1_value_changed(self, widget):
isinstance(widget, gtk.HScrollbar)
horizontal_value = widget.get_value()

# Get vertical value
vertical_scrollbar = self.frmMain.get_widget("vscrollbar1")
vertical_value = vertical_scrollbar.get_value()

# scroll the canvases
self.MyCanvas.scroll_to(horizontal_value, vertical_value)

A picture is worth a thousand words:
Here are 2 similar screens using a goocanvas.Canvas object. Notice the horizontal scroll bar placement on them (at the bottom of the screen). The 2nd screen has an offset scroll bar, which will only scroll that half of the Canvas. Pretty cool!

Screen 1 - Using a single goocanvas.Canvas and a gtk.ScrolledWindow widget:

Screen 2 - Using two goocanvas.Canvas objects and individual scroll bar widgets:


As you know, I am creating a complex timeline for my non-linear video application. I had the choice between the GnomeCanvas (which is obsolete) and a lesser known goocanvas (which is cairo based). I originally choose to use the GnomeCanvas, because it is much more well known, and there is more documentation for it.

After working with the GnomeCanvas in Python for many hours, something became very apparent, the Python bindings for the GnomeCanvas are incomplete and buggy. For example, CanvasGroups are not supported, they throw an exception when used. CanvasPixbuf objects don't expose all of their properties, such as the pixbuf property. So, once you create a CanvasPixbuf, there is no way to change the image. Arghhh!

I have now switched my focus to the goocanvas library, and things are going much better. I hope to have a release of my timeline demo in the next 2 weeks (still lots left to complete).


I have a new favorite Python IDE, and it's the Wingware IDE. That's right folks, the Eclipse IDE (with PyDev) does an admirable job, as well as The Eric Python IDE, but neither are as impressive as Wingware. From this point forward, I dumping the other IDEs and going 100% with Wingware.

Why Wingware?
There are many reasons to like Wingware over other Python IDEs. Here are a few of my favorite features:

  • Source Assistant - This not only describes the current method, module, or variable but also shows you all the expected parameters, shows the __doc__ string, and in many cases links to the official website for the library method (for example, the GTK methods).

  • Great Code Completion - The code completion feels much more complete (and faster loading) than other IDEs. For example, working with the GTK library was a breeze, where as I have struggled using Eclipse... In most other Python IDEs they support limited code completion, but without showing the list of expected parameters. That sucks when you are working with an unknown library. Not a problem anymore!

  • Easy Debugger - The debugger is very similar to Visual Studio (which I have used for years). I found the debugger much easier to step into code, analize the locals and stack data, and they even have a cool tool called a "Debug Probe", which is an immediate window that lets you interact with the current stack data (and includes code completion).

  • Perforce Support - I am a huge Perforce (source control software) fan. Any product that integrates with Perforce sounds like my kind of product. Seriously, I will be using Perforce to manage the source code in my video editor project.

  • Wingware Supports Open-Source - Most supprising, the Wingware company fully supports non-commercial open-source projects, and if you meet their requirements (which I did), they will issue a FREE license. Thanks guys!


Here is a quick update for everyone following this project. I am working on an prototype timeline, using the Gnome canvas, GTK, and Python. So far my "prototype first" methodology seems to be playing out nicely. It is allowing me to quickly test out various libraries, GUI techniques, and design patterns, while allowing me to focus on very specific parts of the application.

The timeline prototype contains the following features:

  • Multiple Tracks
  • Ability to add variable-length video clips (i.e. 30 secs, 1 min, 2 min)
  • Drag 'n Drop video clips
  • Ability to split video clips and move their individual pieces around
  • A Zoom Slider (to adjust the zoom of the timeline)
I will continue to share updates as I develop this prototype, and I hope to have some screenshots available soon.


I never thought licensing free software would be so interesting. Before I start releasing more source code, I figured it was time to understand what the Free Software Foundation and GPL (General Public License) is all about. So here are my thoughts about licensing free software:

Why Even Bother?
To write software and not license it opens the door for corporations or businesses with money (more money than you) to take your source code, redistribute it with their name on it, removing credit from you (the developer), and profiting off of your work. Most people would agree that even free software needs to be licensed and copyrighted.

What License Should I Use?
Once you've decided to license your source code, their are a ton of licensing choices. Many types of licenses make sense for various types of software. For a free, open-source software project, the GNU GPL (version 3) makes a lot of sense. However, their are 2 main variations of the GPL that you should consider:

The main difference between these two licenses is the LGPL license can be freely modified by anyone, and doesn't require the modifications (i.e. modified source code) to be re-distributed. It's perfectly legal to only distribute the binaries from your changes, and never share your source code. Why is this dangerous? Because it allows proprietary versions of your software to be released, and thus you no longer have a "free" software product.

The main selling point of GPL (over other licenses) is your code can be freely modified by anyone, and even redistributed by anyone, but any public release of any version of your software (by anyone) must make the source code available. It's also perfectly fine for someone to sell a version of a GPL software product, however they must make the source code available, and they must not charge extra for the source code.

What about GStreamer?
The GStreamer library is unique, in that it uses many non-free, proprietary codecs. If your software wants to use this library, it will be in violation with the GPL (version 3). What does the GStreamer team recommend? They recommend adding an exception clause to your GPL similar to this example:
"The EXAMPLE project hereby grants permission for non-GPL compatible GStreamer plugins to be used and distributed together with GStreamer and EXAMPLE. This permission is above and beyond the permissions granted by the GPL license by which EXAMPLE is covered. If you modify this code, you may extend this exception to your version of the code, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version."
Are There Any Other Licenses?
Of course, there are many open-source licenses to consider, not just the GNU GPL. The Open Source Initiative (OSI) has a comprehensive list of OSI approved licenses for you to consider.


Just to be clear, I am no lawyer, and I strongly recommend you speak to a lawyer if you are interested in licensing an open-source project, especially if it uses libraries that contain non-free software. Also, there are many great resources on the Internet related to this topic, so head to Google and start learning.


Since starting this journey, I have had the pleasure to meet many people inside the Linux video editing community. The PiTiVi project (lead by Edward Hervey) has contacted me about joining their project. PiTiVi is a Python, Gstreamer, and Gnonlin based non-linear video editing project.

I have also been in contact with many members of the Cinelerra project (which is arguably the largest and most complete FOSS Linux-based video editor). The Cinelerra community is designing version 3 (named Lumiera), and they are actively collecting ideas. I have been keeping track of the best ideas. Don't worry, I'll give credit to them for any ideas I use. =) Thanks guys.

Recently, I have been contacted by the creator of a brand new non-linear video editing project, called the Saya Video Editor. I'm not sure what the term Saya means (Wikipedia claims that it is a Japanese pop singer). Let's hope that's not what it stands for. =) They are using a different multimedia backend than all the previous editors I mentioned, so good luck to them.

I'm not sure where my project will end up in this community. There are many smart and creative people, and a huge community of users that really want a good video editor.

One thing is for sure, Linux will have a good video editor soon:

Subscribe to: Posts (Atom)