[pitivi] Port to the new GESAsset/GESProject/GESMetaContainer APIs



commit 267dd14179d6f8d5eaec5e4b7e39f119e6ca7ef9
Author: Thibault Saunier <thibault saunier collabora com>
Date:   Wed Dec 19 18:00:59 2012 -0300

    Port to the new GESAsset/GESProject/GESMetaContainer APIs
    
    	+ Quickly fix obselete tests
    	+ Make some refactoring where needed
        + Bump the GSTreamer dependency to 1.1.0, actually master...

 bin/pitivi-git-environment.sh    |    4 +-
 pitivi/application.py            |    7 +-
 pitivi/check.py                  |    2 +-
 pitivi/clipproperties.py         |    4 +-
 pitivi/dialogs/clipmediaprops.py |   26 +-
 pitivi/dialogs/startupwizard.py  |   17 +-
 pitivi/mainwindow.py             |  125 +++---
 pitivi/mediafilespreviewer.py    |    3 +-
 pitivi/medialibrary.py           |  264 +++---------
 pitivi/preset.py                 |    9 +-
 pitivi/project.py                |  823 +++++++++++++++++++++++++++++---------
 pitivi/render.py                 |  479 +++++++---------------
 pitivi/settings.py               |  212 ----------
 pitivi/timeline/timeline.py      |  185 ++++------
 pitivi/timeline/track.py         |    3 +-
 pitivi/transitions.py            |   60 ++--
 pitivi/utils/ui.py               |    6 +-
 pitivi/viewer.py                 |    1 -
 tests/test_settings.py           |   60 ++--
 19 files changed, 1065 insertions(+), 1225 deletions(-)
---
diff --git a/bin/pitivi-git-environment.sh b/bin/pitivi-git-environment.sh
index 616b8a7..589d6bb 100755
--- a/bin/pitivi-git-environment.sh
+++ b/bin/pitivi-git-environment.sh
@@ -16,7 +16,9 @@
 MYPITIVI=${MYPITIVI:-$HOME/pitivi-git}
 # Change this variable to 'master' if you prefer to work with the master branch
 # When using "master", this script will automatically "pull --rebase" modules.
-GST_RELEASE_TAG="1.0.2"
+# This will fail and check master in the end
+# Using master until we depend on a released version
+GST_RELEASE_TAG="master"
 # If you care about building the GStreamer/GES developer API documentation:
 BUILD_DOCS=false
 # Here are some dependencies for building GStreamer and GES. If they're missing,
diff --git a/pitivi/application.py b/pitivi/application.py
index 06a054a..23d284e 100644
--- a/pitivi/application.py
+++ b/pitivi/application.py
@@ -43,7 +43,6 @@ from pitivi.settings import GlobalSettings
 from pitivi.utils.threads import ThreadMaster
 from pitivi.mainwindow import PitiviMainWindow
 from pitivi.project import ProjectManager, ProjectLogObserver
-from pitivi.undo.medialibrary import MediaLibraryLogObserver
 from pitivi.undo.undo import UndoableActionLog, DebugActionLogObserver
 from pitivi.dialogs.startupwizard import StartUpWizard
 
@@ -146,7 +145,6 @@ class Pitivi(Loggable, Signallable):
         # TODO reimplement the observing after GES port
         #self.timelineLogObserver = TimelineLogObserver(self.action_log)
         self.projectLogObserver = ProjectLogObserver(self.action_log)
-        self.medialibrary_log_observer = MediaLibraryLogObserver(self.action_log)
 
         self.version_information = {}
         self._checkVersion()
@@ -195,12 +193,12 @@ class Pitivi(Loggable, Signallable):
     def _newProjectLoaded(self, project):
         pass
 
-    def _projectManagerNewProjectLoaded(self, projectManager, project):
+    def _projectManagerNewProjectLoaded(self, projectManager, project,
+            unused_fully_loaded):
         self.current = project
         self.action_log.clean()
         #self.timelineLogObserver.startObserving(project.timeline)
         self.projectLogObserver.startObserving(project)
-        self.medialibrary_log_observer.startObserving(project.medialibrary)
         self._newProjectLoaded(project)
         self.emit("new-project-loaded", project)
 
@@ -326,7 +324,6 @@ class GuiPitivi(InteractivePitivi):
         return False
 
 
-#FIXME the GES port screwed the import to the timeline
 class ProjectCreatorGuiPitivi(GuiPitivi):
     """
     Creates an instance of PiTiVi with the UI and loading a list
diff --git a/pitivi/check.py b/pitivi/check.py
index 94db31a..1690084 100644
--- a/pitivi/check.py
+++ b/pitivi/check.py
@@ -41,7 +41,7 @@ HARD_DEPS = {
     "GES": "1.0.0",
     "gnonlin": "0.11.89.1",
     "GooCanvas": "2.0",
-    "Gst": "1.0.2",
+    "Gst": "1.1.0",
     "Gtk": "3.4.0",
     "xdg": None,  # "pyxdg", using static python bindings
 
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index b802e96..a383b47 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -401,7 +401,7 @@ class EffectProperties(Gtk.Expander):
         obj = self.timeline_objects[0]
         for track_effect in obj.get_top_effects():
             if not track_effect.props.bin_description in HIDDEN_EFFECTS:
-                material = self.app.effects.getFactoryFromName(
+                asset = self.app.effects.getFactoryFromName(
                     track_effect.props.bin_description)
                 to_append = [track_effect.props.active]
                 track = track_effect.get_track()
@@ -411,7 +411,7 @@ class EffectProperties(Gtk.Expander):
                     to_append.append("Video")
 
                 to_append.append(track_effect.props.bin_description)
-                to_append.append(material.description)
+                to_append.append(asset.description)
                 to_append.append(track_effect)
 
                 self.storemodel.append(to_append)
diff --git a/pitivi/dialogs/clipmediaprops.py b/pitivi/dialogs/clipmediaprops.py
index 5846e49..86253bb 100644
--- a/pitivi/dialogs/clipmediaprops.py
+++ b/pitivi/dialogs/clipmediaprops.py
@@ -38,7 +38,6 @@ class clipmediapropsDialog():
 
     def __init__(self, project, audio_streams, video_streams):
         self.project = project
-        self.settings = self.project.getSettings()
         self.audio_streams = audio_streams
         self.video_streams = video_streams
         self.has_audio = self.has_video = self.is_image = False
@@ -110,36 +109,27 @@ class clipmediapropsDialog():
         self.checkbutton6 = builder.get_object("checkbutton6")
 
     def _applyButtonCb(self, unused_button):
-        _width = _height = _framerate = _par = -1
-        _channels = _rate = _depth = -1
-
+        project = self.projet
         if self.has_video:
             # This also handles the case where the video is a still image
             video = self.video_streams[0]
             if self.checkbutton1.get_active():
-                _width = video.get_width()
-                _height = video.get_height()
+                project.width = video.get_width()
+                project.height = video.get_height()
             if (self.checkbutton2.get_active() and not self.is_image):
-                _framerate = Gst.Fraction(video.get_framerate_num(),
+                project.framerate = Gst.Fraction(video.get_framerate_num(),
                                           video.get_framerate_denom())
             if (self.checkbutton3.get_active() and not self.is_image):
-                _par = Gst.Fraction(video.get_par_num(),
+                project.par = Gst.Fraction(video.get_par_num(),
                                     video.get_par_denom())
-            self.settings.setVideoProperties(_width, _height, _framerate, _par)
-
         if self.has_audio:
             audio = self.audio_streams[0]
             if self.checkbutton4.get_active():
-                _channels = audio.get_channels()
+                project.channels = audio.get_channels()
             if self.checkbutton5.get_active():
-                _rate = audio.get_sample_rate()
+                project.rate = audio.get_sample_rate()
             if self.checkbutton6.get_active():
-                _depth = audio.get_depth()
-            self.settings.setAudioProperties(_channels, _rate, _depth)
-
-        if self.has_video or self.has_audio:
-            self.project.setSettings(self.settings)
-
+                project.depth = audio.get_depth()
         self.dialog.destroy()
 
     def _cancelButtonCb(self, unused_button):
diff --git a/pitivi/dialogs/startupwizard.py b/pitivi/dialogs/startupwizard.py
index a5dcfaf..a0c2bd4 100644
--- a/pitivi/dialogs/startupwizard.py
+++ b/pitivi/dialogs/startupwizard.py
@@ -23,6 +23,7 @@ import os
 
 from gi.repository import Gtk
 from gi.repository import Gdk
+from gi.repository import GES
 
 from gettext import gettext as _
 
@@ -56,7 +57,10 @@ class StartUpWizard(object):
         # simple way to hide it.
         filter = Gtk.RecentFilter()
         filter.set_name(_("Projects"))
-        filter.add_pattern("*.xptv")
+
+        for asset in GES.list_assets(GES.Formatter):
+            filter.add_pattern('*.' + asset.get_meta(GES.META_FORMATTER_EXTENSION))
+
         self.recent_chooser.add_filter(filter)
 
         if not missing_soft_deps:
@@ -125,17 +129,16 @@ class StartUpWizard(object):
         """Handle the failure of a project open operation."""
         self.show()
 
-    def _projectLoadedCb(self, unused_project_manager, project):
+    def _projectLoadedCb(self, unused_project_manager, project, fully_loaded):
         """Handle the success of a project load operation.
 
         All the create or load project usage scenarios must generate
         a new-project-loaded signal from self.app.projectManager!
         """
-        if project.disconnect:
-            return
-        self.app.projectManager.disconnect_by_function(self._projectFailedCb)
-        self.app.projectManager.disconnect_by_function(self._projectLoadedCb)
-        self.app.projectManager.disconnect_by_function(self._projectLoadingCb)
+        if fully_loaded:
+            self.app.projectManager.disconnect_by_function(self._projectFailedCb)
+            self.app.projectManager.disconnect_by_function(self._projectLoadedCb)
+            self.app.projectManager.disconnect_by_function(self._projectLoadingCb)
 
     def _projectLoadingCb(self, unused_project_manager, unused_project):
         """Handle the start of a project load operation."""
diff --git a/pitivi/mainwindow.py b/pitivi/mainwindow.py
index 727f9b2..cb1a6b8 100644
--- a/pitivi/mainwindow.py
+++ b/pitivi/mainwindow.py
@@ -43,7 +43,7 @@ from pitivi.clipproperties import ClipProperties
 from pitivi.configure import pitivi_version, APPNAME, APPURL, get_pixmap_dir, get_ui_dir
 from pitivi.effects import EffectListWidget
 from pitivi.mediafilespreviewer import PreviewWidget
-from pitivi.medialibrary import MediaLibraryWidget, MediaLibraryError
+from pitivi.medialibrary import MediaLibraryWidget
 from pitivi.settings import GlobalSettings
 from pitivi.tabsmanager import BaseTabs
 from pitivi.timeline.timeline import Timeline
@@ -121,9 +121,6 @@ GlobalSettings.addConfigOption('lastCurrentVersion',
     default='')
 
 
-#FIXME Hacky, reimplement when avalaible in GES
-formats = [(None, _("PiTiVi native (XML)"), ('xptv',))]
-
 # FIXME PyGi to get stock_add working
 Gtk.stock_add = lambda items: None
 
@@ -608,10 +605,13 @@ class PitiviMainWindow(Gtk.Window, Loggable):
     def _mediaLibraryPlayCb(self, medialibrary, uri):
         self._viewUri(uri)
 
-    def _mediaLibrarySourceRemovedCb(self, medialibrary, uri, unused_info):
+    def _projectChangedCb(self, project):
+        self.main_actions.get_action("SaveProject").set_sensitive(True)
+
+    def _mediaLibrarySourceRemovedCb(self, project, asset):
         """When a clip is removed from the Media Library, tell the timeline
         to remove all instances of that clip."""
-        self.timeline_ui.purgeObject(uri)
+        self.timeline_ui.purgeObject(asset.get_id())
 
     def _selectedLayerChangedCb(self, widget, layer):
         self.main_actions.get_action("RemoveLayer").set_sensitive(layer is not None)
@@ -737,15 +737,16 @@ class PitiviMainWindow(Gtk.Window, Loggable):
         chooser.set_select_multiple(False)
         # TODO: Remove this set_current_folder call when GTK bug 683999 is fixed
         chooser.set_current_folder(self.settings.lastProjectFolder)
-        for format in formats:
+        formatter_assets = GES.list_assets(GES.Formatter)
+        formatter_assets.sort(key=lambda x: - x.get_meta(GES.META_FORMATTER_RANK))
+        for format in formatter_assets:
             filt = Gtk.FileFilter()
-            filt.set_name(format[1])
-            for ext in format[2]:
-                filt.add_pattern("*%s" % ext)
+            filt.set_name(format.get_meta(GES.META_DESCRIPTION))
+            filt.add_pattern("*%s" % format.get_meta(GES.META_FORMATTER_EXTENSION))
             chooser.add_filter(filt)
         default = Gtk.FileFilter()
         default.set_name(_("All supported formats"))
-        default.add_custom(Gtk.FileFilterFlags.URI, GES.Formatter.can_load_uri, None)
+        default.add_custom(Gtk.FileFilterFlags.URI, self._canLoadUri, None)
         chooser.add_filter(default)
 
         response = chooser.run()
@@ -754,6 +755,9 @@ class PitiviMainWindow(Gtk.Window, Loggable):
         chooser.destroy()
         return True
 
+    def _canLoadUri(self, filterinfo, uri):
+        GES.Formatter.can_load_uri(filterinfo.uri)
+
     def _undoCb(self, action):
         self.app.action_log.undo()
 
@@ -767,27 +771,16 @@ class PitiviMainWindow(Gtk.Window, Loggable):
             self.prefsdialog.dialog.set_transient_for(self)
         self.prefsdialog.run()
 
-    def _projectManagerNewProjectLoadedCb(self, projectManager, project):
+    def _projectManagerNewProjectLoadedCb(self, projectManager, project, unused_fully_loaded):
         """
         Once a new project has been loaded, wait for media library's
         "ready" signal to populate the timeline.
         """
         self.log("A new project is loaded, wait for clips")
-        self._connectToProjectSources(self.app.current.medialibrary)
+        self._connectToProject(self.app.current)
         self.app.current.timeline.connect("notify::duration",
                 self._timelineDurationChangedCb)
         self.app.current.pipeline.activatePositionListener()
-
-        # This should only be done when loading a project, and disconnected
-        # as soon as we receive the signal.
-        self.app.current.medialibrary.connect("ready", self._projectClipsReady)
-
-    def _projectClipsReady(self, medialibrary):
-        """
-        After the project is loaded along with its media files, update the UI.
-        """
-        self.log("Project clips are ready, update the UI")
-        self.app.current.medialibrary.disconnect_by_func(self._projectClipsReady)
         self._setProject()
 
         #FIXME GES we should re-enable this when possible
@@ -795,7 +788,6 @@ class PitiviMainWindow(Gtk.Window, Loggable):
 
         # Enable export functionality
         self.main_actions.get_action("ExportProject").set_sensitive(True)
-
         if self._missingUriOnLoading:
             self.app.current.setModificationState(True)
             self.main_actions.get_action("SaveProject").set_sensitive(True)
@@ -809,7 +801,7 @@ class PitiviMainWindow(Gtk.Window, Loggable):
             self.recent_manager.add_item(uri)
         self.log("A NEW project is loading, deactivate UI")
 
-    def _projectManagerSaveProjectFailedCb(self, projectManager, uri, exception=None):
+    def _projectManagerSaveProjectFailedCb(self, projectManager, uri, unused, exception=None):
         project_filename = unquote(uri.split("/")[-1])
         dialog = Gtk.MessageDialog(self,
             Gtk.DialogFlags.MODAL,
@@ -826,10 +818,13 @@ class PitiviMainWindow(Gtk.Window, Loggable):
         self.error("failed to save project")
 
     def _projectManagerProjectSavedCb(self, projectManager, project, uri):
-        self.app.action_log.checkpoint()
-        self._syncDoUndo(self.app.action_log)
+        # FIXME GES: Reimplement Undo/Redo
+        #self.app.action_log.checkpoint()
+        #self._syncDoUndo(self.app.action_log)
+        self.main_actions.get_action("SaveProject").set_sensitive(False)
         if uri:
             self.recent_manager.add_item(uri)
+
         if project.uri is None:
             project.uri = uri
 
@@ -954,7 +949,10 @@ class PitiviMainWindow(Gtk.Window, Loggable):
         dialog.run()
         dialog.destroy()
 
-    def _projectManagerMissingUriCb(self, instance, formatter, tfs):
+    def _projectManagerMissingUriCb(self, u_project_manager, u_project,
+            error, asset):
+        uri = asset.get_id()
+        new_uri = None
         dialog = Gtk.Dialog(_("Locate missing file..."),
             self,
             Gtk.DialogFlags.MODAL,
@@ -973,11 +971,11 @@ class PitiviMainWindow(Gtk.Window, Loggable):
         # This can happen if the file was moved or deleted by an application
         # that does not manage Freedesktop thumbnails. The user is in luck!
         # This is based on medialibrary's addDiscovererInfo method.
-        thumbnail_hash = md5(tfs.get_uri()).hexdigest()
+        thumbnail_hash = md5(uri).hexdigest()
         thumb_dir = os.path.expanduser("~/.thumbnails/normal/")
         thumb_path_normal = thumb_dir + thumbnail_hash + ".png"
         if os.path.exists(thumb_path_normal):
-            self.debug("A thumbnail file was found for %s" % tfs.get_uri())
+            self.debug("A thumbnail file was found for %s" % uri)
             thumbnail = Gtk.Image.new_from_file(thumb_path_normal)
             thumbnail.set_padding(0, SPACING)
             hbox.pack_start(thumbnail, False, False, 0)
@@ -998,7 +996,7 @@ class PitiviMainWindow(Gtk.Window, Loggable):
 
         text = _('The following file has moved: "<b>%s</b>"'
                  '\nPlease specify its new location:'
-                 % info_name(tfs))
+                 % info_name(asset))
 
         label = Gtk.Label()
         label.set_markup(text)
@@ -1016,7 +1014,7 @@ class PitiviMainWindow(Gtk.Window, Loggable):
         # Use a Gtk FileFilter to only show files with the same extension
         # Note that splitext gives us the extension with the ".", no need to
         # add it inside the filter string.
-        filename, extension = os.path.splitext(tfs.get_uri())
+        filename, extension = os.path.splitext(uri)
         filter = Gtk.FileFilter()
         # Translators: this is a format filter in a filechooser. Ex: "AVI files"
         filter.set_name(_("%s files" % extension))
@@ -1038,10 +1036,6 @@ class PitiviMainWindow(Gtk.Window, Loggable):
         if response == Gtk.ResponseType.OK:
             self.log("User chose a new URI for the missing file")
             new_uri = chooser.get_uri()
-            if new_uri:
-                self.app.current.medialibrary.addUris([new_uri])
-                formatter.update_source_uri(tfs, new_uri)
-                self._missingUriOnLoading = True
         else:
             # Even if the user clicks Cancel, the discoverer keeps trying to
             # import the rest of the clips...
@@ -1052,7 +1046,7 @@ class PitiviMainWindow(Gtk.Window, Loggable):
             attempted_uri = self.app.current.uri
             reason = _('No replacement file was provided for "<i>%s</i>".\n\n'
                     'PiTiVi does not currently support partial projects.'
-                    % info_name(tfs))
+                    % info_name(asset))
             # Put an end to the async signals spamming us with dialogs:
             self.app.projectManager.disconnect_by_func(self._projectManagerMissingUriCb)
             # Don't overlap the file chooser with our error dialog
@@ -1067,11 +1061,13 @@ class PitiviMainWindow(Gtk.Window, Loggable):
             self.app.projectManager.emit("new-project-failed", attempted_uri, reason)
 
         dialog.destroy()
+        return new_uri
 
-    def _connectToProjectSources(self, medialibrary):
+    def _connectToProject(self, project):
         #FIXME GES we should re-enable this when possible
         #medialibrary.connect("missing-plugins", self._sourceListMissingPluginsCb)
-        medialibrary.connect("source-removed", self._mediaLibrarySourceRemovedCb)
+        project.connect("asset-removed", self._mediaLibrarySourceRemovedCb)
+        project.connect("project-changed", self._projectChangedCb)
 
     def _actionLogCommit(self, action_log, stack, nested):
         if nested:
@@ -1116,31 +1112,39 @@ class PitiviMainWindow(Gtk.Window, Loggable):
             self.warning("Current project instance does not exist")
             return False
         try:
-            self.app.current.disconnect_by_func(self._settingsChangedCb)
+            self.app.current.disconnect_by_func(self._renderingSettingsChangedCb)
         except:
             # When loading the first project, the signal has never been
             # connected before.
             pass
         self.viewer.setPipeline(self.app.current.pipeline)
-        self._settingsChangedCb(self.app.current, None, self.app.current.settings)
+        self._renderingSettingsChangedCb(self.app.current, None, None)
         if self.timeline_ui:
-            self.timeline_ui.setProject(self.app.current)
+            #self.timeline_ui.setProject(self.app.current)
             self.clipconfig.project = self.app.current
             #FIXME GES port undo/redo
             #self.app.timelineLogObserver.pipeline = self.app.current.pipeline
-        self.app.current.connect("settings-changed", self._settingsChangedCb)
+
+        self.app.current.connect("rendering-settings-changed", self._renderingSettingsChangedCb)
         # When creating a blank project, medialibrary will eventually trigger
         # this _setProject method, but there's no project URI yet.
         if self.app.current.uri:
             folder_path = os.path.dirname(path_from_uri(self.app.current.uri))
             self.settings.lastProjectFolder = folder_path
 
-    def _settingsChangedCb(self, project, unused_old, new):
-        # TODO: this method's signature should be changed:
-        # project = self.app.current,
-        # old is never used, and the new is equal to self.app.current.settings
-        self.viewer.setDisplayAspectRatio(
-            float(new.videopar.num / new.videopar.denom * new.videowidth) / float(new.videoheight))
+    def _renderingSettingsChangedCb(self, project, item, value):
+        """
+            Called when any Project metadata changes, we filter out the ones
+            we are interested in.
+
+            if @item is None, it mean we called it ourself, and want the
+            aspect-ratio to be set
+        """
+        self.main_actions.get_action("SaveProject").set_sensitive(False)
+        if item in ["videopar", "videowidth", "videoheight"] or item is None:
+            ratio = float(project.videopar.num / project.videopar.denom *
+                          project.videowidth) / float(project.videoheight)
+            self.viewer.setDisplayAspectRatio(ratio)
 
     def _sourceListMissingPluginsCb(self, project, uri, factory,
             details, descriptions, missingPluginsCallback):
@@ -1199,23 +1203,26 @@ class PitiviMainWindow(Gtk.Window, Loggable):
 
     def _showSaveAsDialog(self, project):
         self.log("Save URI requested")
+
         chooser = Gtk.FileChooserDialog(_("Save As..."),
             self,
             action=Gtk.FileChooserAction.SAVE,
             buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
             Gtk.STOCK_SAVE, Gtk.ResponseType.OK))
 
+        asset = GES.Formatter.get_default()
+        filt = Gtk.FileFilter()
+        filt.set_name(asset.get_meta(GES.META_DESCRIPTION))
+        filt.add_pattern("*.%s" % asset.get_meta(GES.META_FORMATTER_EXTENSION))
+        chooser.add_filter(filt)
+
         chooser.set_icon_name("pitivi")
         chooser.set_select_multiple(False)
-        chooser.set_current_name(_("Untitled") + ".xptv")
+        chooser.set_current_name(_("Untitled") + "." +
+                asset.get_meta(GES.META_FORMATTER_EXTENSION))
         chooser.set_current_folder(self.settings.lastProjectFolder)
         chooser.props.do_overwrite_confirmation = True
-        for format in formats:
-            filt = Gtk.FileFilter()
-            filt.set_name(format[1])
-            for ext in format[2]:
-                filt.add_pattern("*.%s" % ext)
-            chooser.add_filter(filt)
+
         default = Gtk.FileFilter()
         default.set_name(_("Detect automatically"))
         default.add_pattern("*")
@@ -1227,8 +1234,8 @@ class PitiviMainWindow(Gtk.Window, Loggable):
             # need to do this to work around bug in Gst.uri_construct
             # which escapes all /'s in path!
             uri = "file://" + chooser.get_filename()
-            self.log("uri:%s , format:%s", uri, format)
             format = chooser.get_filter().get_name()
+            self.log("uri:%s , format:%s", uri, format)
             if format == _("Detect automatically"):
                 format = None
             self.settings.lastProjectFolder = chooser.get_current_folder()
@@ -1257,7 +1264,7 @@ class PitiviMainWindow(Gtk.Window, Loggable):
         preview_window.hide()  # Hack to allow setting the window position
         previewer.previewUri(uri)
         previewer.setMinimal()
-        info = self.app.current.medialibrary.getInfoFromUri(uri)
+        info = self.app.current.get_asset(uri, GES.TimelineFileSource).get_info()
         try:
             # For videos and images, automatically resize the window
             # Try to keep it 1:1 if it can fit within 85% of the parent window
diff --git a/pitivi/mediafilespreviewer.py b/pitivi/mediafilespreviewer.py
index 0c00c55..202a959 100644
--- a/pitivi/mediafilespreviewer.py
+++ b/pitivi/mediafilespreviewer.py
@@ -170,6 +170,7 @@ class PreviewWidget(Gtk.VBox, Loggable):
             self.show_error(uri)
         else:
             self.log("Call discoverer for " + uri)
+            self.fixme("Use a GESAsset here, and discover async with it")
             try:
                 info = self.discoverer.discover_uri(uri)
             except Exception, e:
@@ -322,7 +323,7 @@ class PreviewWidget(Gtk.VBox, Loggable):
             self.player.set_state(Gst.State.NULL)
             self.is_playing = False
             err, dbg = message.parse_error()
-            self.error("Error: %s " % err, dbg)
+            self.error("Error: %s %s" % (err, dbg))
 
     def _update_position(self, *args):
         if self.is_playing:
diff --git a/pitivi/medialibrary.py b/pitivi/medialibrary.py
index fd3033a..d12f394 100644
--- a/pitivi/medialibrary.py
+++ b/pitivi/medialibrary.py
@@ -36,7 +36,7 @@ import time
 from urllib import unquote
 from gettext import gettext as _
 from hashlib import md5
-from gi.repository.GstPbutils import Discoverer, DiscovererVideoInfo
+from gi.repository.GstPbutils import DiscovererVideoInfo
 
 from pitivi.configure import get_ui_dir, get_pixmap_dir
 from pitivi.settings import GlobalSettings
@@ -44,11 +44,11 @@ from pitivi.mediafilespreviewer import PreviewWidget
 from pitivi.dialogs.filelisterrordialog import FileListErrorDialog
 from pitivi.dialogs.clipmediaprops import clipmediapropsDialog
 from pitivi.utils.ui import beautify_length
-from pitivi.utils.misc import PathWalker, quote_uri
-from pitivi.utils.signal import SignalGroup, Signallable
+from pitivi.utils.misc import PathWalker
+from pitivi.utils.signal import SignalGroup
 from pitivi.utils.loggable import Loggable
 import pitivi.utils.ui as dnd
-from pitivi.utils.ui import beautify_info, info_name, SPACING, PADDING
+from pitivi.utils.ui import beautify_info, info_name, SPACING
 
 from pitivi.utils.ui import TYPE_PITIVI_FILESOURCE
 
@@ -81,7 +81,7 @@ STORE_MODEL_STRUCTURE = (
 (COL_ICON,
  COL_ICON_LARGE,
  COL_INFOTEXT,
- COL_FACTORY,
+ COL_ASSET,
  COL_URI,
  COL_LENGTH,
  COL_SEARCH_TEXT,
@@ -109,146 +109,6 @@ SUPPORTED_FILE_FORMATS = {"video": ("3gpp", "3gpp2", "dv", "mp4", "mpeg", "ogg",
 OTHER_KNOWN_FORMATS = ("video/mp2t")
 
 
-class MediaLibraryError(Exception):
-    pass
-
-
-class MediaLibrary(Signallable, Loggable):
-    """
-    Contains the sources for a project, stored as SourceFactory objects.
-
-    @ivar discoverer: The discoverer object used internally
-    @type discoverer: L{Discoverer}
-    @ivar nb_files_to_import: Total number of URIs on the last addUris call.
-    @type nb_files_to_import: int
-    @ivar nb_imported_files: Number of URIs loaded since the last addUris call.
-    @type nb_imported_files: int
-
-    Signals:
-     - C{source-added} : A source has been discovered and added to the MediaLibrary.
-     - C{source-removed} : A source was removed from the MediaLibrary.
-     - C{discovery-error} : The given uri is not a media file.
-     - C{nothing-to-import} : All the given uri were already imported
-     - C{ready} : No more files are being discovered/added.
-     - C{starting} : Some files are being discovered/added.
-    """
-
-    __signals__ = {
-        "source-added": ["info"],
-        "source-removed": ["uri"],
-        "discovery-error": ["uri", "reason"],
-        "nothing-to-import": [],
-        "ready": [],
-        "starting": [],
-    }
-
-    def __init__(self):
-        Loggable.__init__(self)
-        Signallable.__init__(self)
-        # A (URI -> SourceFactory) map.
-        self._sources = {}
-        # A list of SourceFactory objects.
-        self._ordered_sources = []
-        self._resetImportCounters()
-
-        self.discoverer = Discoverer.new(Gst.SECOND)
-        self.discoverer.connect("discovered", self.addDiscovererInfo)
-        self.discoverer.connect("finished", self.finishDiscovererCb)
-        self.discoverer.start()
-
-    def _resetImportCounters(self):
-        self.nb_files_to_import = 0
-        self.nb_imported_files = 0
-
-    def finishDiscovererCb(self, unused_discoverer):
-        self.debug("Got the discoverer's finished signal")
-        self._resetImportCounters()
-        self.emit("ready")
-
-    def addUris(self, uris):
-        """
-        Add c{uris} to the source list.
-
-        The uris will be analyzed before being added.
-        """
-        self.emit("starting")
-        self.debug("Adding %s", uris)
-        if type(uris) is str:
-            # A single URI string was provided... make it a list, otherwise
-            # we would loop over the characters of the string!
-            uris = [uris]
-        self.nb_files_to_import += len(uris)
-        for uri in uris:
-            # Ensure we have a correctly encoded URI according to RFC 2396.
-            # Otherwise, in some cases we'd get rogue characters that break
-            # searching for duplicates
-            uri = quote_uri(uri)
-            if uri not in self._sources:
-                self.discoverer.discover_uri_async(uri)
-                self.debug("Added a uri to discoverer async")
-            else:
-                self.nb_files_to_import -= 1
-                self.debug('"%s" is already in the media library' % uri)
-        if self.nb_files_to_import == 0:
-            # This is a cornercase hack for when you try to import a bunch of
-            # clips that are all present in the media library already.
-            # This will allow the progressbar to hide.
-            self.emit("nothing-to-import")
-        else:
-            self.debug("Done adding all URIs to discoverer async")
-
-    def removeUri(self, uri):
-        """
-        Remove the info for c{uri} from the source list.
-        """
-        # In theory we don't need quote_uri here, but since removeUri is public,
-        # we can never be too sure.
-        uri = quote_uri(uri)
-        try:
-            info = self._sources.pop(uri)
-        except KeyError:
-            raise MediaLibraryError("URI not in the medialibrary", uri)
-        try:
-            self._ordered_sources.remove(info)
-        except ValueError:
-            # this can only happen if discoverer hasn't finished scanning the
-            # source, so info must be None
-            assert info is None
-
-        self.debug("Removing %s", uri)
-        self.emit("source-removed", uri, info)
-
-    def getInfoFromUri(self, uri):
-        """
-        Get the source corresponding to C{uri}.
-        """
-        # Make sure the URI is properly quoted, as other modules calling this
-        # method do not necessarily provide URIs encoded in the same way.
-        uri = quote_uri(uri)
-        info = self._sources.get(uri)
-        if info is None:
-            raise MediaLibraryError("URI not in the medialibrary", uri)
-        return info
-
-    def addDiscovererInfo(self, discoverer, info, error):
-        """
-        Add the specified SourceFactory to the list of sources.
-        """
-        if error:
-            self.emit("discovery-error", info.get_uri(), error.message)
-        else:
-            uri = info.get_uri()
-            if self._sources.get(uri, None) is not None:
-                raise MediaLibraryError("We already have info for this URI", uri)
-            self._sources[uri] = info
-            self._ordered_sources.append(info)
-            self.nb_imported_files += 1
-            self.emit("source-added", info)
-
-    def getSources(self):
-        return self._ordered_sources
-
-
 def compare_simple(model, iter1, iter2, user_data):
     if model[iter1][user_data] < model[iter2][user_data]:
         return -1
@@ -463,12 +323,7 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
         self._removeSources()
 
     def _insertEndCb(self, unused_action):
-        sources = []
-        for uri in self.getSelectedItems():
-            sources.append(GES.TimelineFileSource(uri=uri))
-
-        self.app.gui.timeline_ui.insertEnd(sources)
-        self._sources_to_insert = self.getSelectedItems()
+        self.app.gui.timeline_ui.insertEnd(self.getSelectedAssets())
 
     def _disableKeyboardShortcutsCb(self, *unused_args):
         """
@@ -544,18 +399,16 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
         This first disconnects any handlers connected to an old project.
         If project is None, this just disconnects any connected handlers.
         """
-        self.project_signals.connect(project.medialibrary,
-            "source-added", None, self._sourceAddedCb)
-        self.project_signals.connect(project.medialibrary,
-            "source-removed", None, self._sourceRemovedCb)
-        self.project_signals.connect(project.medialibrary,
-            "discovery-error", None, self._discoveryErrorCb)
-        self.project_signals.connect(project.medialibrary,
-            "nothing-to-import", None, self._hideProgressBarCb)
-        self.project_signals.connect(project.medialibrary,
-            "ready", None, self._sourcesStoppedImportingCb)
-        self.project_signals.connect(project.medialibrary,
-            "starting", None, self._sourcesStartedImportingCb)
+        self.project_signals.connect(project, "asset-added", None,
+                self._assetAddedCb)
+        self.project_signals.connect(project, "asset-removed", None,
+                self._assetRemovedCb)
+        self.project_signals.connect(project, "error-loading-asset",
+                 None, self._errorCreatingAssetCb)
+        self.project_signals.connect(project, "done-importing", None,
+                self._sourcesStoppedImportingCb)
+        self.project_signals.connect(project, "start-importing", None,
+                self._sourcesStartedImportingCb)
 
     def _setClipView(self, view_type):
         """
@@ -632,12 +485,14 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
         self._importDialog.add_filter(default)
         self._importDialog.show()
 
-    def _updateProgressbar(self):
+    def _updateProgressbar(self, current_clip_iter=None, total_clips=None):
         """
         Update the _progressbar with the ratio of clips imported vs the total
         """
-        current_clip_iter = self.app.current.medialibrary.nb_imported_files
-        total_clips = self.app.current.medialibrary.nb_files_to_import
+        if current_clip_iter is None and total_clips is None:
+            current_clip_iter = self.app.current.nb_imported_files
+            total_clips = self.app.current.nb_files_to_import
+
         progressbar_text = _("Importing clip %(current_clip)d of %(total)d" %
             {"current_clip": current_clip_iter,
             "total": total_clips})
@@ -647,7 +502,9 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
         elif total_clips != 0:
             self._progressbar.set_fraction((current_clip_iter - 1) / float(total_clips))
 
-    def _addDiscovererInfo(self, info):
+    def _addAsset(self, asset):
+        info = asset.get_info()
+
         # The code below tries to read existing thumbnails from the freedesktop
         # thumbnails directory (~/.thumbnails). The filenames are simply
         # the file URI hashed with md5, so we can retrieve them easily.
@@ -690,7 +547,7 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
         self.pending_rows.append((thumbnail,
                                   thumbnail_large,
                                   beautify_info(info),
-                                  info,
+                                  asset,
                                   info.get_uri(),
                                   duration,
                                   name,
@@ -705,15 +562,18 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
 
     # medialibrary callbacks
 
-    def _sourceAddedCb(self, unused_medialibrary, factory):
+    def _assetAddedCb(self, unused_project, asset,
+            current_clip_iter=None, total_clips=None):
         """ a file was added to the medialibrary """
-        self._updateProgressbar()
-        self._addDiscovererInfo(factory)
+        if isinstance(asset, GES.AssetFileSource):
+            self._updateProgressbar()
+            self._addAsset(asset)
 
-    def _sourceRemovedCb(self, unused_medialibrary, uri, unused_info):
+    def _assetRemovedCb(self, unsued_project, asset):
         """ the given uri was removed from the medialibrary """
         # find the good line in the storemodel and remove it
         model = self.storemodel
+        uri = asset.get_id()
         for row in model:
             if uri == row[COL_URI]:
                 model.remove(row.iter)
@@ -722,17 +582,19 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
             self._welcome_infobar.show_all()
         self.debug("Removing %s", uri)
 
-    def _discoveryErrorCb(self, unused_medialibrary, uri, reason, extra=None):
+    def _errorCreatingAssetCb(self, unsued_project, error, id, type):
         """ The given uri isn't a media file """
-        error = (uri, reason, extra)
-        self._errors.append(error)
+        if GObject.type_is_a(type, GES.TimelineFileSource):
+            error = (id, str(error.domain), error)
+            self._errors.append(error)
+            self._updateProgressbar()
 
-    def _sourcesStartedImportingCb(self, unused_medialibrary):
+    def _sourcesStartedImportingCb(self, unsued_project):
         self.import_start_time = time.time()
         self._welcome_infobar.hide()
         self._progressbar.show()
 
-    def _sourcesStoppedImportingCb(self, unused_medialibrary):
+    def _sourcesStoppedImportingCb(self, unsued_project):
         self.debug("Importing took %.3f seconds" % (time.time() - self.import_start_time))
         self.flush_pending_rows()
         self._progressbar.hide()
@@ -746,14 +608,6 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
 
             self._import_warning_infobar.show_all()
 
-    def _hideProgressBarCb(self, unused_medialibrary):
-        """
-        This is only called when all the uris we tried to import were already
-        present in the media library. We then need to hide the progressbar
-        because the media library is not going to emit the "ready" signal.
-        """
-        self._progressbar.hide()
-
     ## Error Dialog Box callbacks
 
     def _errorDialogBoxCloseCb(self, unused_dialog):
@@ -774,7 +628,7 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
             self.app.settings.closeImportDialog = \
                 dialogbox.props.extra_widget.get_active()
             filenames = dialogbox.get_uris()
-            self.app.current.medialibrary.addUris(filenames)
+            self.app.current.addUris(filenames)
             if self.app.settings.closeImportDialog:
                 dialogbox.destroy()
                 self._importDialog = None
@@ -803,17 +657,16 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
 
         self.app.action_log.begin("remove clip from source list")
         for row in rows:
-            uri = model[row.get_path()][COL_URI]
-            self.app.current.medialibrary.removeUri(uri)
+            asset = model[row.get_path()][COL_ASSET]
+            self.app.current.remove_asset(asset)
         self.app.action_log.commit()
 
-    def _sourceIsUsed(self, uri):
+    def _sourceIsUsed(self, asset):
         """Check if a given URI is present in the timeline"""
         layers = self.app.current.timeline.get_layers()
         for layer in layers:
             for tlobj in layer.get_objects():
-                tlobj_uri = quote_uri(tlobj.get_uri())
-                if tlobj_uri == uri:
+                if tlobj.get_asset() == asset:
                     return True
         return False
 
@@ -821,14 +674,14 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
         """
         Select, in the media library, unused sources in the project.
         """
-        sources = self.app.current.medialibrary.getSources()
+        assets = self.app.current.list_assets(GES.TimelineFileSource)
         unused_sources_uris = []
 
         model = self.treeview.get_model()
         selection = self.treeview.get_selection()
-        for source in sources:
-            if not self._sourceIsUsed(source.get_uri()):
-                unused_sources_uris.append(source.get_uri())
+        for asset in assets:
+            if not self._sourceIsUsed(asset):
+                unused_sources_uris.append(asset.get_uri())
 
         # Hack around the fact that making selections (in a treeview/iconview)
         # deselects what was previously selected
@@ -857,10 +710,10 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
         """
         paths = self.getSelectedPaths()[0]  # Only use the first item
         model = self.treeview.get_model()
-        factory = model[paths][COL_FACTORY]
+        info = model[paths][COL_ASSET].get_info()
         d = clipmediapropsDialog(self.app.current,
-                                factory.get_audio_streams(),
-                                factory.get_video_streams())
+                                info.get_audio_streams(),
+                                info.get_video_streams())
         d.run()
 
     def _warningInfoBarDismissedCb(self, unused_button):
@@ -1058,6 +911,9 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
             self.storemodel.clear()
             self._connectToProject(project)
 
+        # Make sure that the sources added to the project are added added
+        self.flush_pending_rows()
+
     def _newProjectFailedCb(self, unused_pitivi, unused_reason, unused_uri):
         self.storemodel.clear()
         self.project_signals.disconnectAll()
@@ -1089,12 +945,12 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
         if len(directories):
             # Recursively import from folders that were dragged into the library
             self.app.threads.addThread(PathWalker, directories,
-                                    self.app.current.medialibrary.addUris)
+                                    self.app.current.addUris)
         if len(remote_files):
             #TODO waiting for remote files downloader support to be implemented
             pass
         if len(filenames):
-            self.app.current.medialibrary.addUris(filenames)
+            self.app.current.addUris(filenames)
 
     #used with TreeView and IconView
     def _dndDragBeginCb(self, view, context):
@@ -1134,3 +990,11 @@ class MediaLibraryWidget(Gtk.VBox, Loggable):
                     for path in self._draggedPaths]
         return [self.modelFilter[path][COL_URI]
             for path in self.getSelectedPaths()]
+
+    def getSelectedAssets(self):
+        """ Returns a list of selected items URIs """
+        if self._draggedPaths:
+            return [self.modelFilter[path][COL_ASSET]
+                    for path in self._draggedPaths]
+        return [self.modelFilter[path][COL_ASSET]
+            for path in self.getSelectedPaths()]
diff --git a/pitivi/preset.py b/pitivi/preset.py
index c0006d4..0cf57a5 100644
--- a/pitivi/preset.py
+++ b/pitivi/preset.py
@@ -27,7 +27,7 @@ import json
 
 from gettext import gettext as _
 
-from pitivi.render import available_muxers, available_video_encoders, available_audio_encoders
+from pitivi.render import CachedEncoderList
 from pitivi.settings import xdg_data_home
 from pitivi.utils.misc import isWritable
 from pitivi.configure import get_renderpresets_dir, get_audiopresets_dir, get_videopresets_dir
@@ -395,9 +395,10 @@ class RenderPresetManager(PresetManager):
         acodec = parser["acodec"]
         vcodec = parser["vcodec"]
 
-        if (acodec not in [fact.get_name() for fact in available_audio_encoders()]
-        or vcodec not in [fact.get_name() for fact in available_video_encoders()]
-        or container not in [fact.get_name() for fact in available_muxers()]):
+        cached_encs = CachedEncoderList()
+        if (acodec not in [fact.get_name() for fact in cached_encs.aencoders]
+        or vcodec not in [fact.get_name() for fact in cached_encs.vencoders]
+        or container not in [fact.get_name() for fact in cached_encs.muxers]):
             return
 
         try:
diff --git a/pitivi/project.py b/pitivi/project.py
index 75097cd..c833671 100644
--- a/pitivi/project.py
+++ b/pitivi/project.py
@@ -24,6 +24,7 @@ Project related classes
 """
 
 import os
+from gi.repository import GstPbutils
 from gi.repository import GES
 from gi.repository import Gst
 from gi.repository import Gtk
@@ -35,8 +36,6 @@ from datetime import datetime
 from gettext import gettext as _
 from pwd import getpwuid
 
-from pitivi.medialibrary import MediaLibrary
-from pitivi.settings import MultimediaSettings
 from pitivi.undo.undo import UndoableAction
 from pitivi.configure import get_ui_dir
 
@@ -53,15 +52,14 @@ from pitivi.utils.ui import frame_rates, audio_rates, audio_depths,\
     pixel_aspect_ratios, display_aspect_ratios, SPACING
 from pitivi.preset import AudioPresetManager, DuplicatePresetNameException,\
     VideoPresetManager
+from pitivi.render import CachedEncoderList
 
 
+DEFAULT_MUXER = "oggmux"
+DEFAULT_VIDEO_ENCODER = "theoraenc"
+DEFAULT_AUDIO_ENCODER = "vorbisenc"
+
 #------------------ Backend classes ------------------------------------------#
-class Timeline(GES.Timeline):
-    def __init__(self):
-        GES.Timeline.__init__(self)
-        self.add_track(GES.Track.video_raw_new())
-        self.add_track(GES.Track.audio_raw_new())
-        self.selection = Selection()
 
 
 class ProjectSettingsChanged(UndoableAction):
@@ -86,21 +84,25 @@ class ProjectLogObserver(UndoableAction):
         self.log = log
 
     def startObserving(self, project):
-        project.connect("settings-changed", self._settingsChangedCb)
+        project.connect("notify-meta", self._settingsChangedCb)
 
     def stopObserving(self, project):
         try:
-            project.disconnect_by_function(self._settingsChangedCb)
+            project.disconnect_by_func(self._settingsChangedCb)
         except Exception:
             # This can happen when we interrupt the loading of a project,
             # such as in mainwindow's _projectManagerMissingUriCb
             pass
 
-    def _settingsChangedCb(self, project, old, new):
+    def _settingsChangedCb(self, project, item, value):
+        """
+        FIXME Renable undo/redo
         action = ProjectSettingsChanged(project, old, new)
         self.log.begin("change project settings")
         self.log.push(action)
         self.log.commit()
+        """
+        pass
 
 
 class ProjectManager(Signallable, Loggable):
@@ -108,7 +110,7 @@ class ProjectManager(Signallable, Loggable):
         "new-project-loading": ["uri"],
         "new-project-created": ["project"],
         "new-project-failed": ["uri", "exception"],
-        "new-project-loaded": ["project"],
+        "new-project-loaded": ["project", "fully_ready"],
         "save-project-failed": ["uri", "exception"],
         "project-saved": ["project", "uri"],
         "closing-project": ["project"],
@@ -125,7 +127,6 @@ class ProjectManager(Signallable, Loggable):
         self.backup_lock = 0
         self.avalaible_effects = avalaible_effects
         self.formatter = None
-        self._medialib_awaiting_discovery = []
 
     def loadProject(self, uri):
         """
@@ -162,19 +163,19 @@ class ProjectManager(Signallable, Loggable):
             # The "old" backup file will eventually be deleted or overwritten.
             self.current = Project(uri=uri)
 
-        self.emit("new-project-created", self.current)
-
-        timeline = self.current.timeline
-        self.formatter = GES.PitiviFormatter()
-        self.formatter.connect("source-moved", self._formatterMissingURICb)
-        self.formatter.connect("loaded", self._projectLoadedCb)
-        if self.formatter.load_from_uri(timeline, uri):
+        self.current.connect("missing-uri", self._missingURICb)
+        self.current.connect("loaded", self._projectLoadedCb)
+        if self.current.createTimeline():
+            self.emit("new-project-created", self.current)
             self.current.connect("project-changed", self._projectChangedCb)
-            return True
-        self.emit("new-project-failed", uri,
-            _('This might be due to a bug or an unsupported project file format. '
-                'If you were trying to add a media file to your project, '
-                'use the "Import" button instead.'))
+            return
+        else:
+            self.emit("new-project-failed", uri,
+                      _('This might be due to a bug or an unsupported project file format. '
+                      'If you were trying to add a media file to your project, '
+                      'use the "Import" button instead.'))
+            return
+
         # Reset projectManager and disconnect all the signals:
         self.newBlankProject()
         return False
@@ -186,8 +187,8 @@ class ProjectManager(Signallable, Loggable):
         @param time_diff: the difference, in seconds, between file mtimes
         """
         dialog = Gtk.Dialog("", None, 0,
-                    (_("Ignore backup"), Gtk.ResponseType.REJECT,
-                    _("Restore from backup"), Gtk.ResponseType.YES))
+                            (_("Ignore backup"), Gtk.ResponseType.REJECT,
+                            _("Restore from backup"), Gtk.ResponseType.YES))
         dialog.set_icon_name("pitivi")
         dialog.set_resizable(False)
         dialog.set_default_response(Gtk.ResponseType.YES)
@@ -209,7 +210,7 @@ class ProjectManager(Signallable, Loggable):
 
         # make the [[image] text] hbox
         image = Gtk.Image.new_from_stock(Gtk.STOCK_DIALOG_QUESTION,
-               Gtk.IconSize.DIALOG)
+                                         Gtk.IconSize.DIALOG)
         hbox = Gtk.HBox(False, SPACING * 2)
         hbox.pack_start(image, False, True, 0)
         hbox.pack_start(vbox, True, True, 0)
@@ -228,28 +229,25 @@ class ProjectManager(Signallable, Loggable):
         else:
             return False
 
-    def saveProject(self, project, uri=None, overwrite=False, formatter=None, backup=False):
+    def saveProject(self, project, uri=None, overwrite=False, formatter_type=None,
+                    backup=False):
         """
         Save the L{Project} to the given location.
 
-        If specified, use the given formatter.
-
         @type project: L{Project}
         @param project: The L{Project} to save.
         @type uri: L{str}
         @param uri: The absolute URI of the location to store the project to.
         @param overwrite: Whether to overwrite existing location.
         @type overwrite: C{bool}
-        @type formatter: L{Formatter}
-        @param formatter: The L{Formatter} to use to store the project if specified.
-        If it is not specified, then it will be saved at its original format.
+        @type formatter_type: L{GType}
+        @param formatter: The type of the formatter to use to store the project if specified.
+        default is GES.XmlFormatter
         @param backup: Whether the requested save operation is for a backup
         @type backup: C{bool}
 
-        @see: L{Formatter.saveProject}
+        @see: L{GES.Project.save}
         """
-        if formatter is None:
-            formatter = GES.PitiviFormatter()
         if backup:
             if project.uri and self.current.uri is not None:
                 # Ignore whatever URI that is passed on to us. It's a trap.
@@ -270,7 +268,7 @@ class ProjectManager(Signallable, Loggable):
             if not isWritable(path_from_uri(uri)):
                 # TODO: this will not be needed when GTK+ bug #601451 is fixed
                 self.emit("save-project-failed", uri,
-                        _("You do not have permissions to write to this folder."))
+                          _("You do not have permissions to write to this folder."))
                 return
 
             # Update the project instance's uri for the "Save as" scenario.
@@ -278,22 +276,29 @@ class ProjectManager(Signallable, Loggable):
             if not backup:
                 project.uri = uri
 
-        if uri is None or not formatter.can_save_uri(uri):
+        if uri is None:
             self.emit("save-project-failed", uri,
-                    _("Cannot save with this file format."))
+                      _("Cannot save with this file format."))
             return
 
-        if overwrite or not os.path.exists(path_from_uri(uri)):
-            formatter.set_sources(project.medialibrary.getSources())
-            saved = formatter.save_to_uri(project.timeline, uri)
-            if saved:
-                if not backup:
-                    # Do not emit the signal when autosaving a backup file
-                    self.emit("project-saved", project, uri)
-                    self.debug('Saved project "%s"' % uri)
-                else:
-                    self.debug('Saved backup "%s"' % uri)
-            return saved
+        try:
+            saved = project.save(project.timeline, uri, formatter_type, overwrite)
+        except Exception, e:
+            self.emit("save-project-failed", uri,
+                      _("Cannot save with this file format. %s"), e)
+
+        if saved:
+            if not backup:
+                # Do not emit the signal when autosaving a backup file
+                project.setModificationState(False)
+                self.emit("project-saved", project, uri)
+                self.debug('Saved project "%s"' % uri)
+            else:
+                self.debug('Saved backup "%s"' % uri)
+        else:
+            self.emit("save-project-failed", uri,
+                      _("Cannot save with this file format"))
+        return saved
 
     def exportProject(self, project, uri):
         """
@@ -301,8 +306,9 @@ class ProjectManager(Signallable, Loggable):
         and all sources
         """
         # write project file to temporary file
-        project_name = project.name if project.name else "project"
-        tmp_name = "%s.xptv" % project_name
+        project_name = project.name if project.name else _("project")
+        asset = GES.Formatter.get_default()
+        tmp_name = "%s.%s" % (project_name, asset.get_meta(GES.META_FORMATTER_EXTENSION))
 
         try:
             directory = os.path.dirname(uri)
@@ -317,7 +323,7 @@ class ProjectManager(Signallable, Loggable):
                 tar.add(path_from_uri(tmp_uri), os.path.join(top, tmp_name))
 
                 # get common path
-                sources = project.medialibrary.getSources()
+                sources = project.listSources()
                 if self._allSourcesInHomedir(sources):
                     common = os.path.expanduser("~")
                 else:
@@ -325,7 +331,7 @@ class ProjectManager(Signallable, Loggable):
 
                 # add all sources
                 for source in sources:
-                    path = path_from_uri(source.get_uri())
+                    path = path_from_uri(source.get_id())
                     tar.add(path, os.path.join(top, os.path.relpath(path, common)))
                 tar.close()
 
@@ -386,19 +392,12 @@ class ProjectManager(Signallable, Loggable):
         # setting default values for project metadata
         project.author = getpwuid(os.getuid()).pw_gecos.split(",")[0]
 
+        project.createTimeline()
         self.emit("new-project-created", project)
         self.current = project
 
-        # Add default tracks to the timeline of the new project.
-        # The tracks of the timeline determine what tracks
-        # the rendered content will have. Pitivi currently supports
-        # projects with exactly one video track and one audio track.
         project.connect("project-changed", self._projectChangedCb)
-        if emission:
-            self.current.disconnect = False
-        else:
-            self.current.disconnect = True
-        self.emit("new-project-loaded", self.current)
+        self.emit("new-project-loaded", self.current, emission)
         self.time_loaded = time()
 
         return True
@@ -461,41 +460,22 @@ class ProjectManager(Signallable, Loggable):
         name, ext = os.path.splitext(uri)
         return name + ext + "~"
 
-    def _formatterMissingURICb(self, formatter, tfs):
-        return self.emit("missing-uri", formatter, tfs)
+    def _missingURICb(self, project, error, asset, what=None):
+        return self.emit("missing-uri", project, error, asset)
 
-    def _sourceAddedCb(self, unused_medialib, info):
-        try:
-            self._medialib_awaiting_discovery.remove(info.get_uri())
-        except ValueError:
-            self.error("%s not awaited, user is really fast", info.get_uri)
-
-        if not self._medialib_awaiting_discovery:
-            self.current.medialibrary.disconnect_by_function(self._sourceAddedCb)
-            self.emit("new-project-loaded", self.current)
-            self.time_loaded = time()
-
-    def _projectLoadedCb(self, formatter, timeline):
-        self.debug("Project loaded, starting media discovery")
-        for uri in self.formatter.get_sources():
-            self._medialib_awaiting_discovery.append(quote_uri(uri))
-        self.current.medialibrary.addUris(self._medialib_awaiting_discovery)
-        if self._medialib_awaiting_discovery:
-            self.current.medialibrary.connect("source-added", self._sourceAddedCb)
-        else:
-            self.emit("new-project-loaded", self.current)
-            self.time_loaded = time()
+    def _projectLoadedCb(self, project, timeline):
+        self.debug("Project loaded")
+        self.emit("new-project-loaded", self.current, True)
+        self.time_loaded = time()
 
 
-class Project(Signallable, Loggable):
+class Project(Loggable, GES.Project):
     """The base class for PiTiVi projects
 
     @ivar name: The name of the project
     @type name: C{str}
     @ivar description: A description of the project
     @type description: C{str}
-    @ivar medialibrary: The sources used by this project
-    @type medialibrary: L{MediaLibrary}
     @ivar timeline: The timeline
     @type timeline: L{GES.Timeline}
     @ivar pipeline: The timeline's pipeline
@@ -506,13 +486,18 @@ class Project(Signallable, Loggable):
     @type loaded: C{bool}
 
     Signals:
-     - C{settings-changed}: The project settings changed
      - C{project-changed}: Modifications were made to the project
+     - C{start-importing}: Started to import files in bash
+     - C{done-importing}: Done importing files in bash
     """
 
-    __signals__ = {
-        "settings-changed": ['old', 'new'],
-        "project-changed": [],
+    __gsignals__ = {
+        "start-importing": (GObject.SignalFlags.RUN_LAST, None, ()),
+        "done-importing": (GObject.SignalFlags.RUN_LAST, None, ()),
+        "project-changed": (GObject.SignalFlags.RUN_LAST, None, ()),
+        "rendering-settings-changed": (GObject.SignalFlags.RUN_LAST, None,
+                                       (GObject.TYPE_PYOBJECT,
+                                        GObject.TYPE_PYOBJECT,))
     }
 
     def __init__(self, name="", uri=None, **kwargs):
@@ -521,67 +506,364 @@ class Project(Signallable, Loggable):
         @param uri: the uri of the project
         """
         Loggable.__init__(self)
+        GES.Project.__init__(self, uri=uri, extractable_type=GES.Timeline)
         self.log("name:%s, uri:%s", name, uri)
-        self.name = name
-        self.author = ""
-        self.year = ""
-        self.settings = None
-        self.description = ""
+        self.pipeline = None
+        self.timeline = None
+        self.seeker = Seeker()
+
+        # FIXME Remove our URI and work more closely with GES.Project URI handling
         self.uri = uri
-        self.urichanged = False
-        self.format = None
-        self.medialibrary = MediaLibrary()
 
+        # Follow imports
         self._dirty = False
-        self.timeline = Timeline()
+        self._to_import_uris = []
+        self.nb_files_to_import = 0
+        self.nb_imported_files = 0
+
+        # Project property default values
+        self.register_meta(GES.MetaFlag.READWRITE, "name", name)
+        self.register_meta(GES.MetaFlag.READWRITE, "author",
+                           getpwuid(os.getuid()).pw_gecos.split(",")[0])
+
+        # Handle rendering setting
+        self.set_meta("render-scale", 100.0)
+
+        container_profile = \
+            GstPbutils.EncodingContainerProfile.new("pitivi-profile",
+                                                    _("Pitivi encoding profile"),
+                                                    Gst.Caps("application/ogg"),
+                                                    None)
+
+        # Create video profile (We use the same default seetings as the project settings)
+        video_profile = GstPbutils.EncodingVideoProfile.new(Gst.Caps("video/x-theora"),
+                                                            None,
+                                                            Gst.Caps("video/x-raw"),
+                                                            0)
+
+        # Create audio profile (We use the same default seetings as the project settings)
+        audio_profile = GstPbutils.EncodingAudioProfile.new(Gst.Caps("audio/x-vorbis"),
+                                                            None,
+                                                            Gst.Caps("audio/x-raw"),
+                                                            0)
+        container_profile.add_profile(video_profile)
+        container_profile.add_profile(audio_profile)
+        # Keep a reference to those profiles
+        # FIXME We should handle the case we have more than 1 audio and 1 video profiles
+        self.container_profile = container_profile
+        self.audio_profile = audio_profile
+        self.video_profile = video_profile
+
+        # Add the profile to ourself
+        self.add_encoding_profile(container_profile)
+
+        # Now set the presets/ GstElement that will be used
+        # FIXME We might want to add the default Container/video decoder/audio encoder
+        # into the application settings, for now we just make sure to pick one with
+        # eighest probably the user has installed ie ogg+vorbis+theora
+
+        self.muxer = DEFAULT_MUXER
+        self.vencoder = DEFAULT_VIDEO_ENCODER
+        self.aencoder = DEFAULT_AUDIO_ENCODER
+        self._ensureAudioRestrictions()
+        self._ensureVideoRestrictions()
+
+        # FIXME That does not really belong to here and should be savable into
+        # The serilized file. For now, just let it be here.
+        # A (muxer -> containersettings) map.
+        self._containersettings_cache = {}
+        # A (vencoder -> vcodecsettings) map.
+        self._vcodecsettings_cache = {}
+        # A (aencoder -> acodecsettings) map.
+        self._acodecsettings_cache = {}
+
+    #-----------------#
+    # Our properties  #
+    #-----------------#
+
+    # Project specific properties
+    @property
+    def name(self):
+        return self.get_meta("name")
+
+    @name.setter
+    def name(self, name):
+        self.set_meta("name", name)
+        self.setModificationState(True)
+
+    @property
+    def year(self):
+        return self.get_meta("year")
+
+    @year.setter
+    def year(self, year):
+        self.set_meta("year", year)
+        self.setModificationState(True)
+
+    @property
+    def description(self):
+        return self.get_meta("description")
+
+    @description.setter
+    def description(self, description):
+        self.set_meta("description", description)
+        self.setModificationState(True)
+
+    @property
+    def author(self):
+        return self.get_meta("author")
+
+    @author.setter
+    def author(self, author):
+        self.set_meta("author", author)
+        self.setModificationState(True)
+
+    # Encoding related properties
+    @property
+    def videowidth(self):
+        return self.video_profile.get_restriction()[0]["videowidth"]
+
+    @videowidth.setter
+    def videowidth(self, value):
+        if self.video_profile.get_restriction()[0]["videowidth"] != value and value:
+            self.video_profile.get_restriction()[0]["videowidth"] = value
+            self._emitChange("rendering-settings-changed", "videowidth", value)
+
+    @property
+    def videoheight(self):
+        return self.video_profile.get_restriction()[0]["videoheight"]
+
+    @videoheight.setter
+    def videoheight(self, value):
+        if self.video_profile.get_restriction()[0]["videoheight"] != value and value:
+            self.video_profile.get_restriction()[0]["videoheight"] = value
+            self._emitChange("rendering-settings-changed", "videoheight", value)
+
+    @property
+    def videorate(self):
+        return self.video_profile.get_restriction()[0]["videorate"]
+
+    @videorate.setter
+    def videorate(self, value):
+        if self.video_profile.get_restriction()[0]["videorate"] != value and value:
+            self.video_profile.get_restriction()[0]["videorate"] = value
+
+    @property
+    def videopar(self):
+        return self.video_profile.get_restriction()[0]["videopar"]
+
+    @videopar.setter
+    def videopar(self, value):
+        if self.video_profile.get_restriction()[0]["videopar"] != value and value:
+            self.video_profile.get_restriction()[0]["videopar"] = value
+
+    @property
+    def audiochannels(self):
+        return self.audio_profile.get_restriction()[0]["audiochannels"]
+
+    @audiochannels.setter
+    def audiochannels(self, value):
+        if self.video_profile.get_restriction()[0]["audiochannels"] != value and value:
+            self.audio_profile.get_restriction()[0]["audiochannels"] = value
+            self._emitChange("rendering-settings-changed", "audiochannels", value)
+
+    @property
+    def audiorate(self):
+        return self.audio_profile.get_restriction()[0]["audiorate"]
+
+    @audiorate.setter
+    def audiorate(self, value):
+        if self.video_profile.get_restriction()[0]["audiorate"] != value and value:
+            self.audio_profile.get_restriction()[0]["audiorate"] = value
+            self._emitChange("rendering-settings-changed", "audiorate", value)
+
+    @property
+    def audiodepth(self):
+        return self.audio_profile.get_restriction()[0]["audiodepth"]
+
+    @audiodepth.setter
+    def audiodepth(self, value):
+        if self.video_profile.get_restriction()[0]["audiodepth"] != value and value:
+            self.audio_profile.get_restriction()[0]["audiodepth"] = value
+            self._emitChange("rendering-settings-changed", "audiodepth", value)
+
+    @property
+    def aencoder(self):
+        return self.audio_profile.get_preset_name()
+
+    @aencoder.setter
+    def aencoder(self, value):
+        if self.audio_profile.get_preset_name() != value and value:
+            feature = Gst.Registry.get().lookup_feature(value)
+            if feature is None:
+                self.error("%s not in registry", value)
+            else:
+                for template in feature.get_static_pad_templates():
+                    if template.name_template == "src":
+                        audiotype = template.get_caps()[0].get_name()
+                        break
+                self.audio_profile.set_format(Gst.Caps(audiotype))
+            self.audio_profile.set_preset_name(value)
+
+            self._emitChange("rendering-settings-changed", "aencoder", value)
+
+    @property
+    def vencoder(self):
+        return self.video_profile.get_preset_name()
+
+    @vencoder.setter
+    def vencoder(self, value):
+        if self.video_profile.get_preset_name() != value and value:
+            feature = Gst.Registry.get().lookup_feature(value)
+            if feature is None:
+                self.error("%s not in registry", value)
+            else:
+                for template in feature.get_static_pad_templates():
+                    if template.name_template == "src":
+                        videotype = template.get_caps()[0].get_name()
+                        break
+                self.video_profile.set_format(Gst.Caps(videotype))
+
+            self.video_profile.set_preset_name(value)
+
+            self._emitChange("rendering-settings-changed", "vencoder", value)
+
+    @property
+    def muxer(self):
+        return self.container_profile.get_preset_name()
+
+    @muxer.setter
+    def muxer(self, value):
+        if self.container_profile.get_preset_name() != value and value:
+            feature = Gst.Registry.get().lookup_feature(value)
+            if feature is None:
+                self.error("%s not in registry", value)
+            else:
+                for template in feature.get_static_pad_templates():
+                    if template.name_template == "src":
+                        muxertype = template.get_caps()[0].get_name()
+                        break
+                self.container_profile.set_format(Gst.Caps(muxertype))
+            self.container_profile.set_preset_name(value)
+
+            self._emitChange("rendering-settings-changed", "muxer", value)
+
+    @property
+    def render_scale(self):
+        return self.get_meta("render-scale")
+
+    @render_scale.setter
+    def render_scale(self, value):
+        if value:
+            return self.set_meta("render-scale", value)
+
+    #--------------------------------------------#
+    # GES.Project virtual methods implementation #
+    #--------------------------------------------#
+    def _handle_asset_loaded(self, id):
+        try:
+            self._to_import_uris.remove(id)
+            self.nb_imported_files += 1
+            if not self._to_import_uris:
+                self.nb_imported_files = 0
+                self.nb_files_to_import = 0
+                self._emitChange("done-importing")
+        except ValueError:
+            # We care only about the assets we are tracking
+            pass
+
+    def do_asset_added(self, asset):
+        """
+        When GES.Project emit "asset-added" this vmethod
+        get calls
+        """
+        self._handle_asset_loaded(asset.get_id())
+
+    def do_loading_error(self, error, id, type):
+        """ vmethod, get called on "asset-loading-error"""
+        self._handle_asset_loaded(id)
+
+    def do_loaded(self, timeline):
+        """ vmethod, get called on "loaded" """
+        self._ensureTracks()
+        #self._ensureLayer()
+
+        encoders = CachedEncoderList()
+        # The project just loaded, we need to check the new
+        # encoding profiles and make use of it now.
+        container_profile = self.list_encoding_profiles()[0]
+        if container_profile is not self.container_profile:
+            # The encoding profile might have been reset from the
+            # Project file, we just take it as our
+            self.container_profile = container_profile
+            self.muxer = self._getElementFactoryName(encoders.muxers, container_profile)
+            if self.muxer is None:
+                self.muxer = DEFAULT_MUXER
+            for profile in container_profile.get_profiles():
+                if isinstance(profile, GstPbutils.EncodingVideoProfile):
+                    self.video_profile = profile
+                    if self.video_profile.get_restriction() is None:
+                        self.video_profile.set_restriction(Gst.Caps("video/x-raw"))
+                    self._ensureVideoRestrictions()
+
+                    self.vencoder = self._getElementFactoryName(encoders.vencoders, profile)
+                elif isinstance(profile, GstPbutils.EncodingAudioProfile):
+                    self.audio_profile = profile
+                    if self.audio_profile.get_restriction() is None:
+                        self.audio_profile.set_restriction(Gst.Caps("audio/x-raw"))
+                    self._ensureAudioRestrictions()
+                    self.aencoder = self._getElementFactoryName(encoders.aencoders, profile)
+                else:
+                    self.warning("We do not handle profile: %s" % profile)
 
+    #--------------------------------------------#
+    #               Our API                      #
+    #--------------------------------------------#
+    def createTimeline(self):
+        """
+        The pitivi.Project handle 1 timeline at a time
+        unlike GES.Project
+        """
+        self.timeline = self.extract()
+        if self.timeline is None:
+            return False
+
+        self.timeline.selection = Selection()
         self.pipeline = Pipeline()
         self.pipeline.add_timeline(self.timeline)
-        self.seeker = Seeker()
 
-        self.settings = MultimediaSettings()
+        return True
+
+    def addUris(self, uris):
+        """
+        Add c{uris} to the source list.
 
-    def getUri(self):
-        return self._uri
+        The uris will be analyzed before being added.
+        """
+        # Do not try to reload URIS that we already have loaded
+        uris = [uri for uri in uris if self.get_asset(uri, GES.TimelineFileSource) is None]
+        if not uris:
+            return
 
-    def setUri(self, uri):
-        # FIXME support not local project
-        if uri and not Gst.uri_has_protocol(uri, "file"):
-            # Note that this does *not* give the same result as quote_uri()
-            self._uri = Gst.uri_construct("file", uri)
+        self.nb_files_to_import += len(uris)
+        if self._to_import_uris:
+            self._to_import_uris = set(uris.extend(list(self._to_import_uris)))
         else:
-            self._uri = uri
+            self._emitChange("start-importing")
+            self._to_import_uris = uris
 
-    uri = property(getUri, setUri)
+        for uri in self._to_import_uris:
+            self.create_asset(uri, GES.TimelineFileSource)
+
+    def listSources(self):
+        return self.list_assets(GES.TimelineFileSource)
 
     def release(self):
-        self.pipeline.release()
+        if self.pipeline:
+            self.pipeline.release()
         self.pipeline = None
         self.timeline = None
 
-    # Project settings methods
-
-    def getSettings(self):
-        """
-        return the currently configured settings.
-        """
-        self.debug("self.settings %s", self.settings)
-        return self.settings
-
-    def setSettings(self, settings):
-        """
-        Sets the given settings as the project's settings.
-        @param settings: The new settings for the project.
-        @type settings: MultimediaSettings
-        """
-        assert settings
-        self.log("Setting %s as the project's settings", settings)
-        oldsettings = self.settings
-        self.settings = settings
-        self.emit('settings-changed', oldsettings, settings)
-
-    # Save and Load features
-
     def setModificationState(self, state):
         self._dirty = state
         if state:
@@ -590,13 +872,153 @@ class Project(Signallable, Loggable):
     def hasUnsavedModifications(self):
         return self._dirty
 
+    def getDAR(self):
+        return Gst.Fraction(self.videowidth, self.videoheight) * self.videopar
+
+    def getVideoWidthAndHeight(self, render=False):
+        """ Returns the video width and height as a tuple
+
+        @param render: Whether to apply self.render_scale to the returned values
+        @type render: bool
+        """
+        if render:
+            scale = self.render_scale
+        else:
+            scale = 100
+        return self.videowidth * scale / 100, self.videoheight * scale / 100
+
+    def getVideoCaps(self, render=False):
+        """ Returns the GstCaps corresponding to the video settings """
+        videowidth, videoheight = self.getVideoWidthAndHeight(render=render)
+        vstr = "width=%d,height=%d,pixel-aspect-ratio=%d/%d,framerate=%d/%d" % (
+            videowidth, videoheight,
+            self.videopar.num, self.videopar.denom,
+            self.videorate.num, self.videorate.denom)
+        caps_str = "video/x-raw,%s" % (vstr)
+        video_caps = Gst.caps_from_string(caps_str)
+        return video_caps
+
+    def getAudioCaps(self):
+        """ Returns the GstCaps corresponding to the audio settings """
+        # TODO: Figure out why including 'depth' causes pipeline failures:
+        astr = "rate=%d,channels=%d" % (self.audiorate, self.audiochannels)
+        caps_str = "audio/x-raw,%s" % (astr)
+        audio_caps = Gst.caps_from_string(caps_str)
+        return audio_caps
+
+    def setAudioProperties(self, nbchanns=-1, rate=-1, depth=-1):
+        """
+        Set the number of audio channels, rate and depth
+        """
+        self.info("%d x %dHz %dbits", nbchanns, rate, depth)
+        if not nbchanns == -1 and not nbchanns == self.audiochannels:
+            self.audiochannels = nbchanns
+        if not rate == -1 and not rate == self.audiorate:
+            self.audiorate = rate
+        if not depth == -1 and not depth == self.audiodepth:
+            self.audiodepth = depth
+
+    def setEncoders(self, muxer="", vencoder="", aencoder=""):
+        """ Set the video/audio encoder and muxer """
+        if not muxer == "" and not muxer == self.muxer:
+            self.muxer = muxer
+        if not vencoder == "" and not vencoder == self.vencoder:
+            self.vencoder = vencoder
+        if not aencoder == "" and not aencoder == self.aencoder:
+            self.aencoder = aencoder
+
+    @property
+    def containersettings(self):
+        return self._containersettings_cache.setdefault(self.muxer, {})
+
+    @containersettings.setter
+    def containersettings(self, value):
+        self._containersettings_cache[self.muxer] = value
+
+    @property
+    def vcodecsettings(self):
+        return self._vcodecsettings_cache.setdefault(self.vencoder, {})
+
+    @vcodecsettings.setter
+    def vcodecsettings(self, value):
+        self._vcodecsettings_cache[self.vencoder] = value
+
+    @property
+    def acodecsettings(self):
+        return self._acodecsettings_cache.setdefault(self.aencoder, {})
+
+    @acodecsettings.setter
+    def acodecsettings(self, value):
+        self._acodecsettings_cache[self.aencoder] = value
+
+    #--------------------------------------------#
+    #               Private methods              #
+    #--------------------------------------------#
+
+    def _ensureTracks(self):
+        if self.timeline is None:
+            self.warning("Can't ensure tracks if no timeline set")
+            return
+
+        track_types = [track.get_property("track-type")
+                       for track in self.timeline.get_tracks()]
+
+        if GES.TrackType.VIDEO not in track_types:
+            self.timeline.add_track(GES.Track.video_raw_new())
+        if GES.TrackType.AUDIO not in track_types:
+            self.timeline.add_track(GES.Track.audio_raw_new())
+
+    def _ensureLayer(self):
+        if self.timeline is None:
+            self.warning("Can't ensure tracks if no timeline set")
+            return
+        if not self.timeline.get_layers():
+            self.timeline.append_layer()
+
+    def _ensureVideoRestrictions(self):
+        if not self.videowidth:
+            self.videowidth = 720
+        if not self.videoheight:
+            self.videoheight = 576
+        if not self.videorate:
+            self.videorate = Gst.Fraction(25, 1)
+        if not self.videopar:
+            self.videopar = Gst.Fraction(16, 15)
+
+    def _ensureAudioRestrictions(self):
+        if not self.audiochannels:
+            self.audiochannels = 2
+        if not self.audiorate:
+            self.audiorate = 44100
+        if not self.audiodepth:
+            self.audiodepth = 16
+
+    def _emitChange(self, signal, key=None, value=None):
+        if key and value:
+            self.emit(signal, key, value)
+        else:
+            self.emit(signal)
+        self.setModificationState(True)
+
+    def _getElementFactoryName(self, elements, profile):
+        if profile.get_preset_name():
+            return profile.get_preset_name()
+
+        factories = Gst.ElementFactory.list_filter(elements,
+                                                   Gst.Caps(profile.get_format()),
+                                                   Gst.PadDirection.SRC,
+                                                   False)
+        if factories:
+            factories.sort(key=lambda x: - x.get_rank())
+            return factories[0].get_name()
+        return None
+
 
 #----------------------- UI classes ------------------------------------------#
 class ProjectSettingsDialog():
 
     def __init__(self, parent, project):
         self.project = project
-        self.settings = project.getSettings()
 
         self.builder = Gtk.Builder()
         self.builder.add_from_file(os.path.join(get_ui_dir(), "projectsettings.ui"))
@@ -606,19 +1028,25 @@ class ProjectSettingsDialog():
         # add custom display aspect ratio widget
         self.dar_fraction_widget = FractionWidget()
         self.video_properties_table.attach(self.dar_fraction_widget,
-            0, 1, 6, 7, xoptions=Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, yoptions=0)
+                                           0, 1, 6, 7,
+                                           xoptions=Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                                           yoptions=0)
         self.dar_fraction_widget.show()
 
         # add custom pixel aspect ratio widget
         self.par_fraction_widget = FractionWidget()
         self.video_properties_table.attach(self.par_fraction_widget,
-            1, 2, 6, 7, xoptions=Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, yoptions=0)
+                                           1, 2, 6, 7,
+                                           xoptions=Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                                           yoptions=0)
         self.par_fraction_widget.show()
 
         # add custom framerate widget
         self.frame_rate_fraction_widget = FractionWidget()
         self.video_properties_table.attach(self.frame_rate_fraction_widget,
-            1, 2, 2, 3, xoptions=Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL, yoptions=0)
+                                           1, 2, 2, 3,
+                                           xoptions=Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
+                                           yoptions=0)
         self.frame_rate_fraction_widget.show()
 
         # populate coboboxes with appropriate data
@@ -633,13 +1061,13 @@ class ProjectSettingsDialog():
         # behavior
         self.wg = RippleUpdateGroup()
         self.wg.addVertex(self.frame_rate_combo,
-                signal="changed",
-                update_func=self._updateCombo,
-                update_func_args=(self.frame_rate_fraction_widget,))
+                          signal="changed",
+                          update_func=self._updateCombo,
+                          update_func_args=(self.frame_rate_fraction_widget,))
         self.wg.addVertex(self.frame_rate_fraction_widget,
-                signal="value-changed",
-                update_func=self._updateFraction,
-                update_func_args=(self.frame_rate_combo,))
+                          signal="value-changed",
+                          update_func=self._updateFraction,
+                          update_func_args=(self.frame_rate_combo,))
         self.wg.addVertex(self.dar_combo, signal="changed")
         self.wg.addVertex(self.dar_fraction_widget, signal="value-changed")
         self.wg.addVertex(self.par_combo, signal="changed")
@@ -647,54 +1075,60 @@ class ProjectSettingsDialog():
         self.wg.addVertex(self.width_spinbutton, signal="value-changed")
         self.wg.addVertex(self.height_spinbutton, signal="value-changed")
         self.wg.addVertex(self.save_audio_preset_button,
-                 update_func=self._updateAudioSaveButton)
+                          update_func=self._updateAudioSaveButton)
         self.wg.addVertex(self.save_video_preset_button,
-                 update_func=self._updateVideoSaveButton)
+                          update_func=self._updateVideoSaveButton)
         self.wg.addVertex(self.channels_combo, signal="changed")
         self.wg.addVertex(self.sample_rate_combo, signal="changed")
         self.wg.addVertex(self.sample_depth_combo, signal="changed")
 
         # constrain width and height IFF constrain_sar_button is active
         self.wg.addEdge(self.width_spinbutton, self.height_spinbutton,
-            predicate=self.constrained, edge_func=self.updateHeight)
+                        predicate=self.constrained,
+                        edge_func=self.updateHeight)
         self.wg.addEdge(self.height_spinbutton, self.width_spinbutton,
-            predicate=self.constrained, edge_func=self.updateWidth)
+                        predicate=self.constrained,
+                        edge_func=self.updateWidth)
 
         # keep framereate text field and combo in sync
         self.wg.addBiEdge(self.frame_rate_combo, self.frame_rate_fraction_widget)
 
         # keep dar text field and combo in sync
         self.wg.addEdge(self.dar_combo, self.dar_fraction_widget,
-            edge_func=self.updateDarFromCombo)
+                        edge_func=self.updateDarFromCombo)
         self.wg.addEdge(self.dar_fraction_widget, self.dar_combo,
-            edge_func=self.updateDarFromFractionWidget)
+                        edge_func=self.updateDarFromFractionWidget)
 
         # keep par text field and combo in sync
         self.wg.addEdge(self.par_combo, self.par_fraction_widget,
-            edge_func=self.updateParFromCombo)
+                        edge_func=self.updateParFromCombo)
         self.wg.addEdge(self.par_fraction_widget, self.par_combo,
-            edge_func=self.updateParFromFractionWidget)
+                        edge_func=self.updateParFromFractionWidget)
 
         # constrain DAR and PAR values. because the combo boxes are already
         # linked, we only have to link the fraction widgets together.
         self.wg.addEdge(self.par_fraction_widget, self.dar_fraction_widget,
-            edge_func=self.updateDarFromPar)
+                        edge_func=self.updateDarFromPar)
         self.wg.addEdge(self.dar_fraction_widget, self.par_fraction_widget,
-            edge_func=self.updateParFromDar)
+                        edge_func=self.updateParFromDar)
 
         # update PAR when width/height change and the DAR checkbutton is
         # selected
         self.wg.addEdge(self.width_spinbutton, self.par_fraction_widget,
-            predicate=self.darSelected, edge_func=self.updateParFromDar)
+                        predicate=self.darSelected,
+                        edge_func=self.updateParFromDar)
         self.wg.addEdge(self.height_spinbutton, self.par_fraction_widget,
-            predicate=self.darSelected, edge_func=self.updateParFromDar)
+                        predicate=self.darSelected,
+                        edge_func=self.updateParFromDar)
 
         # update DAR when width/height change and the PAR checkbutton is
         # selected
         self.wg.addEdge(self.width_spinbutton, self.dar_fraction_widget,
-            predicate=self.parSelected, edge_func=self.updateDarFromPar)
+                        predicate=self.parSelected,
+                        edge_func=self.updateDarFromPar)
         self.wg.addEdge(self.height_spinbutton, self.dar_fraction_widget,
-            predicate=self.parSelected, edge_func=self.updateDarFromPar)
+                        predicate=self.parSelected,
+                        edge_func=self.updateDarFromPar)
 
         # presets
         self.audio_presets = AudioPresetManager()
@@ -702,12 +1136,12 @@ class ProjectSettingsDialog():
         self.video_presets = VideoPresetManager()
         self.video_presets.loadAll()
 
-        self._fillPresetsTreeview(
-            self.audio_preset_treeview, self.audio_presets,
-            self._updateAudioPresetButtons)
-        self._fillPresetsTreeview(
-            self.video_preset_treeview, self.video_presets,
-            self._updateVideoPresetButtons)
+        self._fillPresetsTreeview(self.audio_preset_treeview,
+                                  self.audio_presets,
+                                  self._updateAudioPresetButtons)
+        self._fillPresetsTreeview(self.video_preset_treeview,
+                                  self.video_presets,
+                                  self._updateVideoPresetButtons)
 
         # A map which tells which infobar should be used when displaying
         # an error for a preset manager.
@@ -753,22 +1187,20 @@ class ProjectSettingsDialog():
             self.select_par_radiobutton.props.active = True
             self.par_fraction_widget.setWidgetValue(value)
 
-        mgr.bindWidget("par", updatePar,
-            self.par_fraction_widget.getWidgetValue)
+        mgr.bindWidget("par", updatePar, self.par_fraction_widget.getWidgetValue)
 
     def bindFractionWidget(self, mgr, name, widget):
-        mgr.bindWidget(name, widget.setWidgetValue,
-            widget.getWidgetValue)
+        mgr.bindWidget(name, widget.setWidgetValue, widget.getWidgetValue)
 
     def bindCombo(self, mgr, name, widget):
         mgr.bindWidget(name,
-            lambda x: set_combo_value(widget, x),
-            lambda: get_combo_value(widget))
+                       lambda x: set_combo_value(widget, x),
+                       lambda: get_combo_value(widget))
 
     def bindSpinbutton(self, mgr, name, widget):
         mgr.bindWidget(name,
-            lambda x: widget.set_value(float(x)),
-            lambda: int(widget.get_value()))
+                       lambda x: widget.set_value(float(x)),
+                       lambda: int(widget.get_value()))
 
     def _fillPresetsTreeview(self, treeview, mgr, update_buttons_func):
         """Set up the specified treeview to display the specified presets.
@@ -792,7 +1224,7 @@ class ProjectSettingsDialog():
         renderer.connect("edited", self._presetNameEditedCb, mgr)
         renderer.connect("editing-started", self._presetNameEditingStartedCb, mgr)
         treeview.get_selection().connect("changed", self._presetChangedCb, mgr,
-                                        update_buttons_func)
+                                         update_buttons_func)
         treeview.connect("focus-out-event", self._treeviewDefocusedCb, mgr)
 
     def createAudioNoPreset(self, mgr):
@@ -806,7 +1238,7 @@ class ProjectSettingsDialog():
             "par": Gst.Fraction(int(get_combo_value(self.par_combo).num),
                                 int(get_combo_value(self.par_combo).denom)),
             "frame-rate": Gst.Fraction(int(get_combo_value(self.frame_rate_combo).num),
-                            int(get_combo_value(self.frame_rate_combo).denom)),
+                                       int(get_combo_value(self.frame_rate_combo).denom)),
             "height": int(self.height_spinbutton.get_value()),
             "width": int(self.width_spinbutton.get_value())})
 
@@ -1028,17 +1460,17 @@ class ProjectSettingsDialog():
 
     def updateUI(self):
 
-        self.width_spinbutton.set_value(self.settings.videowidth)
-        self.height_spinbutton.set_value(self.settings.videoheight)
+        self.width_spinbutton.set_value(self.project.videowidth)
+        self.height_spinbutton.set_value(self.project.videoheight)
 
         # video
-        self.frame_rate_fraction_widget.setWidgetValue(self.settings.videorate)
-        self.par_fraction_widget.setWidgetValue(self.settings.videopar)
+        self.frame_rate_fraction_widget.setWidgetValue(self.project.videorate)
+        self.par_fraction_widget.setWidgetValue(self.project.videopar)
 
         # audio
-        set_combo_value(self.channels_combo, self.settings.audiochannels)
-        set_combo_value(self.sample_rate_combo, self.settings.audiorate)
-        set_combo_value(self.sample_depth_combo, self.settings.audiodepth)
+        set_combo_value(self.channels_combo, self.project.audiochannels)
+        set_combo_value(self.sample_rate_combo, self.project.audiorate)
+        set_combo_value(self.sample_depth_combo, self.project.audiodepth)
 
         self._selectDarRadiobuttonToggledCb(self.select_dar_radiobutton)
 
@@ -1057,19 +1489,14 @@ class ProjectSettingsDialog():
         self.project.year = str(self.year_spinbutton.get_value_as_int())
 
     def updateSettings(self):
-        width = int(self.width_spinbutton.get_value())
-        height = int(self.height_spinbutton.get_value())
-        par = self.par_fraction_widget.getWidgetValue()
-        frame_rate = self.frame_rate_fraction_widget.getWidgetValue()
-
-        channels = get_combo_value(self.channels_combo)
-        sample_rate = get_combo_value(self.sample_rate_combo)
-        sample_depth = get_combo_value(self.sample_depth_combo)
-
-        self.settings.setVideoProperties(width, height, frame_rate, par)
-        self.settings.setAudioProperties(channels, sample_rate, sample_depth)
-
-        self.project.setSettings(self.settings)
+        self.project.videowidth = int(self.width_spinbutton.get_value())
+        self.project.videoheight = int(self.height_spinbutton.get_value())
+        self.project.videopar = self.par_fraction_widget.getWidgetValue()
+        self.project.videorate = self.frame_rate_fraction_widget.getWidgetValue()
+
+        self.project.audiochannels = get_combo_value(self.channels_combo)
+        self.project.audiorate = get_combo_value(self.sample_rate_combo)
+        self.project.audiodepth = get_combo_value(self.sample_depth_combo)
 
     def _responseCb(self, unused_widget, response):
         if response == Gtk.ResponseType.OK:
diff --git a/pitivi/render.py b/pitivi/render.py
index f5113c5..5b65d07 100644
--- a/pitivi/render.py
+++ b/pitivi/render.py
@@ -30,12 +30,7 @@ from gi.repository import Gst
 from gi.repository import GES
 import time
 
-import pitivi.utils.loggable as log
-
 from gettext import gettext as _
-from gi.repository.GstPbutils import EncodingContainerProfile, EncodingVideoProfile, \
-    EncodingAudioProfile
-
 from pitivi import configure
 from pitivi.utils.signal import Signallable
 
@@ -57,79 +52,100 @@ except ImportError:
     has_libnotify = False
 
 
-#---------------- Private utils ---------------------------------------#
-def get_compatible_sink_pad(factoryname, caps):
-    """
-    Returns the pad name of a (request) pad from factoryname which is
-    compatible with the given caps.
-    """
-    factory = Gst.Registry.get().lookup_feature(factoryname)
-    if factory is None:
-        log.warning("render", "%s is not a valid factoryname", factoryname)
-        return None
-
-    res = []
-    sinkpads = [x for x in factory.get_static_pad_templates() if x.direction == Gst.PadDirection.SINK]
-    for p in sinkpads:
-        c = p.get_caps()
-        log.log("render", "sinkcaps %s", c.to_string())
-        inter = caps.intersect(c)
-        log.log("render", "intersection %s", inter.to_string())
-        if inter:
-            res.append(p.name_template)
-    if len(res) > 0:
-        return res[0]
-    return None
-
-
-#FIME GES port make it obselete, handle it properly again
-def get_compatible_sink_caps(factoryname, caps):
-    """
-    Returns the compatible caps between 'caps' and the sink pad caps of 'factoryname'
+class CachedEncoderList(object):
     """
-    log.log("render", "factoryname : %s , caps : %s", factoryname, caps.to_string())
-    factory = Gst.Registry.get().lookup_feature(factoryname)
-    if factory is None:
-        log.warning("render", "%s is not a valid factoryname", factoryname)
-        return None
-
-    res = []
-    sinkcaps = [x.get_caps() for x in factory.get_static_pad_templates() if x.direction == Gst.PadDirection.SINK]
-    for c in sinkcaps:
-        log.log("render", "sinkcaps %s", c.to_string())
-        inter = caps.intersect(c)
-        log.log("render", "intersection %s", inter.to_string())
-        if inter:
-            res.append(inter)
-
-    if len(res) > 0:
-        return res[0]
-    return None
-
-
-def list_compat(a1, b1):
-    for x1 in a1:
-        if not x1 in b1:
-            return False
-    return True
+    Registry of avalaible Muxer/Audio encoder/Video Encoder. And
+    avalaible combinations of those.
+
+    You can acces directly the
 
+    @aencoders: List of avalaible audio encoders
+    @vencoders: List of avalaible video encoders
+    @muxers: List of avalaible muxers
+    @audio_combination: Dictionary from muxer names to compatible audio encoders ordered by Rank
+    @video_combination: Dictionary from muxer names to compatible video encoders ordered by Rank
 
-def my_can_sink_caps(muxer, ocaps, muxsinkcaps=[]):
-    """ returns True if the given caps intersect with some of the muxer's
-    sink pad templates' caps.
+
+    It is a singleton.
     """
-    # fast version
-    if muxsinkcaps != []:
-        for c in muxsinkcaps:
-            if not c.intersect(ocaps).is_empty():
-                return True
+
+    _instance = None
+
+    def __new__(cls, *args, **kwargs):
+        """
+        Override the new method to return the singleton instance if available.
+        Otherwise, create one.
+        """
+        if not cls._instance:
+            cls._instance = super(CachedEncoderList, cls).__new__(cls, *args, **kwargs)
+            Gst.Registry.get().connect("feature-added", cls._instance._registryFeatureAddedCb)
+            cls._instance._buildEncoders()
+            cls._instance._buildCombinations()
+        return cls._instance
+
+    def _buildEncoders(self):
+        self.aencoders = []
+        self.vencoders = []
+        self.muxers = Gst.ElementFactory.list_get_elements(Gst.ELEMENT_FACTORY_TYPE_MUXER,
+                                                           Gst.Rank.SECONDARY)
+
+        for fact in Gst.ElementFactory.list_get_elements(
+                Gst.ELEMENT_FACTORY_TYPE_ENCODER, Gst.Rank.SECONDARY):
+            klist = fact.get_klass().split('/')
+            if "Video" in klist or "Image" in klist:
+                self.vencoders.append(fact)
+            elif "Audio" in klist:
+                self.aencoders.append(fact)
+
+    def _buildCombinations(self):
+        self.audio_combination = {}
+        self.video_combination = {}
+        useless_muxers = set([])
+        for muxer in self.muxers:
+            mux = muxer.get_name()
+            aencs = self._findCompatibleEncoders(self.aencoders, muxer)
+            vencs = self._findCompatibleEncoders(self.vencoders, muxer)
+            # only include muxers with audio and video
+
+            if aencs and vencs:
+                self.audio_combination[mux] = sorted(aencs, key=lambda x: - x.get_rank())
+                self.video_combination[mux] = sorted(vencs, key=lambda x: - x.get_rank())
+            else:
+                useless_muxers.add(muxer)
+
+        for muxer in useless_muxers:
+            self.muxers.remove(muxer)
+
+    def _findCompatibleEncoders(self, encoders, muxer, muxsinkcaps=[]):
+        """ returns the list of encoders compatible with the given muxer """
+        res = []
+        if muxsinkcaps == []:
+            muxsinkcaps = [x.get_caps() for x in muxer.get_static_pad_templates()
+                 if x.direction == Gst.PadDirection.SINK]
+        for encoder in encoders:
+            for tpl in encoder.get_static_pad_templates():
+                if tpl.direction == Gst.PadDirection.SRC:
+                    if self._canSinkCaps(muxer, tpl.get_caps(), muxsinkcaps):
+                        res.append(encoder)
+                        break
+        return res
+
+    def _canSinkCaps(self, muxer, ocaps, muxsinkcaps=[]):
+        """ returns True if the given caps intersect with some of the muxer's
+        sink pad templates' caps.
+        """
+        # fast version
+        if muxsinkcaps != []:
+            for c in muxsinkcaps:
+                if not c.intersect(ocaps).is_empty():
+                    return True
+            return False
+        # slower default
+        for x in muxer.get_static_pad_templates():
+            if x.direction == Gst.PadDirection.SINK:
+                if not x.get_caps().intersect(ocaps).is_empty():
+                    return True
         return False
-    # slower default
-    for x in muxer.get_static_pad_templates():
-        if x.direction == Gst.PadDirection.SINK:
-            if not x.get_caps().intersect(ocaps).is_empty():
-                return True
-    return False
 
     # sinkcaps = (x.get_caps() for x in muxer.get_static_pad_templates() if x.direction == Gst.PadDirection.SINK)
     # for x in sinkcaps:
@@ -137,131 +153,9 @@ def my_can_sink_caps(muxer, ocaps, muxsinkcaps=[]):
     #         return True
     # return False
 
-
-class CachedEncoderList(object):
-    def __init__(self):
-        self._factories = None
-        self._registry = Gst.Registry.get()
-        self._registry.connect("feature-added", self._registryFeatureAddedCb)
-
-    def _ensure_factories(self):
-        if self._factories is None:
-            self._buildFactories()
-
-    def _buildFactories(self):
-        self._factories = self._registry.get_feature_list(Gst.ElementFactory)
-        self._audioEncoders = []
-        self._videoEncoders = []
-        self._muxers = []
-        for fact in self._factories:
-            klist = fact.get_klass().split('/')
-            if list_compat(("Codec", "Muxer"), klist):
-                self._muxers.append(fact)
-            elif list_compat(("Codec", "Encoder", "Video"), klist) or list_compat(("Codec", "Encoder", "Image"), klist):
-                self._videoEncoders.append(fact)
-            elif list_compat(("Codec", "Encoder", "Audio"), klist):
-                self._audioEncoders.append(fact)
-
-    def available_muxers(self):
-        if self._factories is None:
-            self._buildFactories()
-        return self._muxers
-
-    def available_audio_encoders(self):
-        if self._factories is None:
-            self._buildFactories()
-        return self._audioEncoders
-
-    def available_video_encoders(self):
-        if self._factories is None:
-            self._buildFactories()
-        return self._videoEncoders
-
     def _registryFeatureAddedCb(self, registry, feature):
-        self._factories = None
-
-_cached_encoder_list = None
-
-
-def encoderlist():
-    global _cached_encoder_list
-    if _cached_encoder_list is None:
-        _cached_encoder_list = CachedEncoderList()
-    return _cached_encoder_list
-
-
-def available_muxers():
-    """ return all available muxers """
-    enclist = encoderlist()
-    return enclist.available_muxers()
-
-
-def available_video_encoders():
-    """ returns all available video encoders """
-    enclist = encoderlist()
-    return enclist.available_video_encoders()
-
-
-def available_audio_encoders():
-    """ returns all available audio encoders """
-    enclist = encoderlist()
-    return enclist.available_audio_encoders()
-
-
-def encoders_muxer_compatible(encoders, muxer, muxsinkcaps=[]):
-    """ returns the list of encoders compatible with the given muxer """
-    res = []
-    if muxsinkcaps == []:
-        muxsinkcaps = [x.get_caps() for x in muxer.get_static_pad_templates() if x.direction == Gst.PadDirection.SINK]
-    for encoder in encoders:
-        for tpl in encoder.get_static_pad_templates():
-            if tpl.direction == Gst.PadDirection.SRC:
-                if my_can_sink_caps(muxer, tpl.get_caps(), muxsinkcaps):
-                    res.append(encoder)
-                    break
-    return res
-
-
-raw_audio_caps = Gst.caps_from_string("audio/x-raw")
-raw_video_caps = Gst.caps_from_string("video/x-raw")
-
-
-def muxer_can_sink_raw_audio(muxer):
-    """ Returns True if given muxer can accept raw audio """
-    return my_can_sink_caps(muxer, raw_audio_caps)
-
-
-def muxer_can_sink_raw_video(muxer):
-    """ Returns True if given muxer can accept raw video """
-    return my_can_sink_caps(muxer, raw_video_caps)
-
-
-def available_combinations():
-    """Return a 3-tuple of (muxers, audio, video), where:
-        - muxers is a list of muxer factories
-        - audio is a dictionary from muxer names to compatible audio encoders
-        - video is a dictionary from muxer names to compatible video encoders
-    """
-
-    aencoders = available_audio_encoders()
-    vencoders = available_video_encoders()
-    muxers = available_muxers()
-
-    audio = {}
-    video = {}
-    containers = []
-    for muxer in muxers:
-        mux = muxer.get_name()
-        aencs = encoders_muxer_compatible(aencoders, muxer)
-        vencs = encoders_muxer_compatible(vencoders, muxer)
-        # only include muxers with audio and video
-
-        if aencs and vencs:
-            audio[mux] = aencs
-            video[mux] = vencs
-            containers.append(muxer)
-
-    return containers, audio, video
+        # TODO Check what feature has been added and update our lists
+        pass
 
 
 def beautify_factoryname(factory):
@@ -404,8 +298,6 @@ class RenderDialog(Loggable):
     @type preferred_aencoder: str
     @ivar preferred_vencoder: The last video encoder selected by the user.
     @type preferred_vencoder: str
-    @ivar settings: The settings used for rendering.
-    @type settings: MultimediaSettings
     """
     INHIBIT_REASON = _("Currently rendering")
 
@@ -425,7 +317,6 @@ class RenderDialog(Loggable):
 
         self.outfile = None
         self.notification = None
-        self.settings = project.getSettings()
         self.timestarted = 0
 
         # Various gstreamer signal connection ID's
@@ -459,15 +350,15 @@ class RenderDialog(Loggable):
         # We store these so that when the user tries various container formats,
         # (AKA muxers) we select these a/v encoders, if they are compatible with
         # the current container format.
-        self.preferred_vencoder = self.settings.vencoder
-        self.preferred_aencoder = self.settings.aencoder
+        self.preferred_vencoder = self.project.vencoder
+        self.preferred_aencoder = self.project.aencoder
 
         self._initializeComboboxModels()
         self._displaySettings()
         self._displayRenderSettings()
 
         self.window.connect("delete-event", self._deleteEventCb)
-        self.settings.connect("settings-changed", self._settingsChanged)
+        self.project.connect("rendering-settings-changed", self._settingsChanged)
 
         # Monitor changes
 
@@ -522,8 +413,8 @@ class RenderDialog(Loggable):
             "frame-rate": Gst.Fraction(
                 int(get_combo_value(self.frame_rate_combo).num),
                 int(get_combo_value(self.frame_rate_combo).denom)),
-            "height": self.getDimension("height"),
-            "width": self.getDimension("width")})
+            "height": self.project.videoheight,
+            "width": self.project.videowidth})
 
     def bindCombo(self, mgr, name, widget):
         if name == "container":
@@ -563,7 +454,7 @@ class RenderDialog(Loggable):
 
     def muxer_setter(self, widget, value):
         set_combo_value(widget, Gst.ElementFactory.find(value))
-        self.settings.setEncoders(muxer=value)
+        self.project.setEncoders(muxer=value)
 
         # Update the extension of the filename.
         basename = os.path.splitext(self.fileentry.get_text())[0]
@@ -578,51 +469,40 @@ class RenderDialog(Loggable):
 
     def acodec_setter(self, widget, value):
         set_combo_value(widget, Gst.ElementFactory.find(value))
-        self.settings.setEncoders(aencoder=value)
+        self.project.aencoder = value
         if not self.muxer_combo_changing:
             # The user directly changed the audio encoder combo.
             self.preferred_aencoder = value
 
     def vcodec_setter(self, widget, value):
         set_combo_value(widget, Gst.ElementFactory.find(value))
-        self.settings.setEncoders(vencoder=value)
+        self.project.setEncoders(vencoder=value)
         if not self.muxer_combo_changing:
             # The user directly changed the video encoder combo.
             self.preferred_vencoder = value
 
     def sample_depth_setter(self, widget, value):
-        set_combo_value(widget, value)
-        self.settings.setAudioProperties(depth=value)
+        self.project.audiodepth = set_combo_value(widget, value)
 
     def sample_rate_setter(self, widget, value):
-        set_combo_value(widget, value)
-        self.settings.setAudioProperties(rate=value)
+        self.project.audiorate = set_combo_value(widget, value)
 
     def channels_setter(self, widget, value):
-        set_combo_value(widget, value)
-        self.settings.setAudioProperties(nbchanns=value)
+        self.project.audiochannels = set_combo_value(widget, value)
 
     def framerate_setter(self, widget, value):
-        set_combo_value(widget, value)
-        self.settings.setVideoProperties(framerate=value)
+        self.project.videorate = set_combo_value(widget, value)
 
     def bindHeight(self, mgr):
         mgr.bindWidget("height",
-                    lambda x: self.settings.setVideoProperties(height=x),
+                    lambda x: setattr(self.project, "videoheight", x),
                     lambda: 0)
 
     def bindWidth(self, mgr):
         mgr.bindWidget("width",
-                    lambda x: self.settings.setVideoProperties(width=x),
+                    lambda x: setattr(self.project, "videowidth", x),
                     lambda: 0)
 
-    def getDimension(self, dimension):
-        value = self.settings.getVideoWidthAndHeight()
-        if dimension == "height":
-            return value[1]
-        elif dimension == "width":
-            return value[0]
-
     def _fillPresetsTreeview(self, treeview, mgr, update_buttons_func):
         """Set up the specified treeview to display the specified presets.
 
@@ -781,38 +661,37 @@ class RenderDialog(Loggable):
         self.remove_render_preset_button = self.builder.get_object("remove_render_preset_button")
         self.render_preset_infobar = self.builder.get_object("render-preset-infobar")
 
-    def _settingsChanged(self, settings):
+    def _settingsChanged(self, project, key, value):
         self.updateResolution()
 
     def _initializeComboboxModels(self):
         # Avoid loop import
-        from pitivi.settings import MultimediaSettings
         self.frame_rate_combo.set_model(frame_rates)
         self.channels_combo.set_model(audio_channels)
         self.sample_rate_combo.set_model(audio_rates)
         self.sample_depth_combo.set_model(audio_depths)
-        self.muxercombobox.set_model(factorylist(MultimediaSettings.muxers))
+        self.muxercombobox.set_model(factorylist(CachedEncoderList().muxers))
 
     def _displaySettings(self):
         """Display the settings that also change in the ProjectSettingsDialog.
         """
         # Video settings
-        set_combo_value(self.frame_rate_combo, self.settings.videorate)
+        set_combo_value(self.frame_rate_combo, self.project.videorate)
         # Audio settings
-        set_combo_value(self.channels_combo, self.settings.audiochannels)
-        set_combo_value(self.sample_rate_combo, self.settings.audiorate)
-        set_combo_value(self.sample_depth_combo, self.settings.audiodepth)
+        set_combo_value(self.channels_combo, self.project.audiochannels)
+        set_combo_value(self.sample_rate_combo, self.project.audiorate)
+        set_combo_value(self.sample_depth_combo, self.project.audiodepth)
 
     def _displayRenderSettings(self):
         """Display the settings which can be changed only in the RenderDialog.
         """
         # Video settings
         # note: this will trigger an update of the video resolution label
-        self.scale_spinbutton.set_value(self.settings.render_scale)
+        self.scale_spinbutton.set_value(self.project.render_scale)
         # Muxer settings
         # note: this will trigger an update of the codec comboboxes
         set_combo_value(self.muxercombobox,
-            Gst.ElementFactory.find(self.settings.muxer))
+            Gst.ElementFactory.find(self.project.muxer))
 
     def _checkForExistingFile(self, *args):
         """
@@ -838,7 +717,7 @@ class RenderDialog(Loggable):
 
     def updateFilename(self, basename):
         """Updates the filename UI element to show the specified file name."""
-        extension = extension_for_muxer(self.settings.muxer)
+        extension = extension_for_muxer(self.project.muxer)
         if extension:
             name = "%s%s%s" % (basename, os.path.extsep, extension)
         else:
@@ -847,13 +726,12 @@ class RenderDialog(Loggable):
 
     def updateAvailableEncoders(self):
         """Update the encoder comboboxes to show the available encoders."""
-        video_encoders = self.settings.getVideoEncoders()
-        video_encoder_model = factorylist(video_encoders)
-        self.video_encoder_combo.set_model(video_encoder_model)
+        encoders = CachedEncoderList()
+        vencoder_model = factorylist(encoders.video_combination[self.project.muxer])
+        self.video_encoder_combo.set_model(vencoder_model)
 
-        audio_encoders = self.settings.getAudioEncoders()
-        audio_encoder_model = factorylist(audio_encoders)
-        self.audio_encoder_combo.set_model(audio_encoder_model)
+        aencoder_model = factorylist(encoders.audio_combination[self.project.muxer])
+        self.audio_encoder_combo.set_model(aencoder_model)
 
         self._updateEncoderCombo(self.video_encoder_combo, self.preferred_vencoder)
         self._updateEncoderCombo(self.audio_encoder_combo, self.preferred_aencoder)
@@ -879,7 +757,7 @@ class RenderDialog(Loggable):
         the properties.
         @type settings_attr: str
         """
-        properties = getattr(self.settings, settings_attr)
+        properties = getattr(self.project, settings_attr)
         self.dialog = GstElementSettingsDialog(factory, properties=properties,
                                             parent_window=self.window)
         self.dialog.ok_btn.connect("clicked", self._okButtonClickedCb, settings_attr)
@@ -919,40 +797,14 @@ class RenderDialog(Loggable):
         self._gstSigId = {}
         self.app.current.pipeline.disconnect_by_func(self._updatePositionCb)
 
-    def _updateProjectSettings(self):
-        """Updates the settings of the project if the render settings changed.
-        """
-        settings = self.project.getSettings()
-        if (settings.muxer == self.settings.muxer
-        and settings.aencoder == self.settings.aencoder
-        and settings.vencoder == self.settings.vencoder
-        and settings.containersettings == self.settings.containersettings
-        and settings.acodecsettings == self.settings.acodecsettings
-        and settings.vcodecsettings == self.settings.vcodecsettings
-        and settings.render_scale == self.settings.render_scale):
-            # No setting which can be changed in the Render dialog
-            # and which we want to save have been changed.
-            return
-
-        settings.setEncoders(muxer=self.settings.muxer,
-                             aencoder=self.settings.aencoder,
-                             vencoder=self.settings.vencoder)
-        settings.containersettings = self.settings.containersettings
-        settings.acodecsettings = self.settings.acodecsettings
-        settings.vcodecsettings = self.settings.vcodecsettings
-        settings.setVideoProperties(render_scale=self.settings.render_scale)
-        # Signal that the project settings have been changed.
-        self.project.setSettings(settings)
-
     def destroy(self):
-        self._updateProjectSettings()
         self.window.destroy()
 
     #------------------- Callbacks ------------------------------------------#
 
     #-- UI callbacks
     def _okButtonClickedCb(self, unused_button, settings_attr):
-        setattr(self.settings, settings_attr, self.dialog.getSettings())
+        setattr(self, settings_attr, self.dialog.getSettings())
         self.dialog.window.destroy()
 
     def _renderButtonClickedCb(self, unused_button):
@@ -965,23 +817,7 @@ class RenderDialog(Loggable):
         self.progress = RenderingProgressDialog(self.app, self)
         self.window.hide()  # Hide the rendering settings dialog while rendering
 
-        # FIXME GES: Handle presets here!
-        self.containerprofile = EncodingContainerProfile.new(None, None,
-            Gst.caps_from_string(self.muxertype), None)
-
-        if self.video_output_checkbutton.get_active():
-            self.videoprofile = EncodingVideoProfile.new(
-                Gst.caps_from_string(self.videotype), None,
-                self.settings.getVideoCaps(True), 0)
-            self.containerprofile.add_profile(self.videoprofile)
-
-        if self.audio_output_checkbutton.get_active():
-            self.audioprofile = EncodingAudioProfile.new(
-                Gst.caps_from_string(self.audiotype), None,
-                self.settings.getAudioCaps(), 0)
-            self.containerprofile.add_profile(self.audioprofile)
-
-        self._pipeline.set_render_settings(self.outfile, self.containerprofile)
+        self._pipeline.set_render_settings(self.outfile, self.project.container_profile)
         self.startAction()
         self.progress.window.show()
         self.progress.connect("cancel", self._cancelRender)
@@ -1045,42 +881,36 @@ class RenderDialog(Loggable):
             self.progress.updatePosition(fraction, text)
 
     def _elementAddedCb(self, bin, element):
-        # Setting properties on Gst.Element-s has they are added to the
-        # Gst.Encodebin
-        if element.get_factory() == get_combo_value(self.video_encoder_combo):
-            for setting in self.settings.vcodecsettings:
-                element.set_property(setting, self.settings.vcodecsettings[setting])
-        elif element.get_factory() == get_combo_value(self.audio_encoder_combo):
-            for setting in self.settings.acodecsettings:
-                element.set_property(setting, self.settings.vcodecsettings[setting])
+        """
+        Setting properties on Gst.Element-s has they are added to the
+        Gst.Encodebin
+        """
+        factory = element.get_factory()
+        settings = {}
+        if factory == get_combo_value(self.video_encoder_combo):
+            settings = self.project.vcodecsettings
+        elif factory == get_combo_value(self.audio_encoder_combo):
+            settings = self.project.acodecsettings
+
+        for propname, value in settings.iteritems():
+            element.set_property(propname, value)
+            self.debug("Setting %s to %s", propname, value)
 
     #-- Settings changed callbacks
     def _scaleSpinbuttonChangedCb(self, button):
         render_scale = self.scale_spinbutton.get_value()
-        self.settings.setVideoProperties(render_scale=render_scale)
+        self.project.render_scale = render_scale
         self.updateResolution()
 
     def updateResolution(self):
-        width, height = self.settings.getVideoWidthAndHeight(render=True)
+        width, height = self.project.getVideoWidthAndHeight(render=True)
         self.resolution_label.set_text(u"%dÃ%d" % (width, height))
 
     def _projectSettingsButtonClickedCb(self, button):
         from pitivi.project import ProjectSettingsDialog
         dialog = ProjectSettingsDialog(self.window, self.project)
-        dialog.window.connect("destroy", self._projectSettingsDestroyCb)
         dialog.window.run()
 
-    def _projectSettingsDestroyCb(self, dialog):
-        """Handle the destruction of the ProjectSettingsDialog."""
-        settings = self.project.getSettings()
-        self.settings.setVideoProperties(width=settings.videowidth,
-                                         height=settings.videoheight,
-                                         framerate=settings.videorate)
-        self.settings.setAudioProperties(nbchanns=settings.audiochannels,
-                                         rate=settings.audiorate,
-                                         depth=settings.audiodepth)
-        self._displaySettings()
-
     def _audioOutputCheckbuttonToggledCb(self, audio):
         active = self.audio_output_checkbutton.get_active()
         if active:
@@ -1117,18 +947,12 @@ class RenderDialog(Loggable):
 
     def _frameRateComboChangedCb(self, combo):
         framerate = get_combo_value(combo)
-        self.settings.setVideoProperties(framerate=framerate)
+        self.project.framerate = framerate
 
     def _videoEncoderComboChangedCb(self, combo):
         vencoder = get_combo_value(combo).get_name()
-        for template in Gst.Registry.get().lookup_feature(vencoder).get_static_pad_templates():
-            if template.name_template == "src":
-                self.videotype = template.get_caps().to_string()
-                for elem in self.videotype.split(","):
-                    if "{" in elem or "[" in elem:
-                        self.videotype = self.videotype[:self.videotype.index(elem) - 1]
-                        break
-        self.settings.setEncoders(vencoder=vencoder)
+        self.project.vencoder = vencoder
+
         if not self.muxer_combo_changing:
             # The user directly changed the video encoder combo.
             self.preferred_vencoder = vencoder
@@ -1138,24 +962,17 @@ class RenderDialog(Loggable):
         self._elementSettingsDialog(factory, 'vcodecsettings')
 
     def _channelsComboChangedCb(self, combo):
-        self.settings.setAudioProperties(nbchanns=get_combo_value(combo))
+        self.project.audiochannels = get_combo_value(combo)
 
     def _sampleDepthComboChangedCb(self, combo):
-        self.settings.setAudioProperties(depth=get_combo_value(combo))
+        self.project.audiodepth = get_combo_value(combo)
 
     def _sampleRateComboChangedCb(self, combo):
-        self.settings.setAudioProperties(rate=get_combo_value(combo))
+        self.project.audiorate = get_combo_value(combo)
 
     def _audioEncoderChangedComboCb(self, combo):
         aencoder = get_combo_value(combo).get_name()
-        self.settings.setEncoders(aencoder=aencoder)
-        for template in Gst.Registry.get().lookup_feature(aencoder).get_static_pad_templates():
-            if template.name_template == "src":
-                self.audiotype = template.get_caps().to_string()
-                for elem in self.audiotype.split(","):
-                    if "{" in elem or "[" in elem:
-                        self.audiotype = self.audiotype[:self.audiotype.index(elem) - 1]
-                        break
+        self.project.aencoder = aencoder
         if not self.muxer_combo_changing:
             # The user directly changed the audio encoder combo.
             self.preferred_aencoder = aencoder
@@ -1166,11 +983,7 @@ class RenderDialog(Loggable):
 
     def _muxerComboChangedCb(self, muxer_combo):
         """Handle the changing of the container format combobox."""
-        muxer = get_combo_value(muxer_combo).get_name()
-        for template in Gst.Registry.get().lookup_feature(muxer).get_static_pad_templates():
-            if template.name_template == "src":
-                self.muxertype = template.get_caps().to_string()
-        self.settings.setEncoders(muxer=muxer)
+        self.project.muxer = get_combo_value(muxer_combo).get_name()
 
         # Update the extension of the filename.
         basename = os.path.splitext(self.fileentry.get_text())[0]
diff --git a/pitivi/settings.py b/pitivi/settings.py
index 7737362..0381a9b 100644
--- a/pitivi/settings.py
+++ b/pitivi/settings.py
@@ -20,13 +20,10 @@
 # Boston, MA 02110-1301, USA.
 
 import os
-from gi.repository import Gst
 from ConfigParser import SafeConfigParser, ParsingError
 import xdg.BaseDirectory as xdg_dirs  # Freedesktop directories spec
 
 from pitivi.utils.signal import Signallable
-from pitivi.render import available_combinations, get_compatible_sink_caps
-from pitivi.utils.loggable import Loggable
 
 
 def get_bool_env(var):
@@ -329,212 +326,3 @@ class GlobalSettings(Signallable):
         if section in cls.options:
             raise ConfigError("Duplicate Section \"%s\"." % section)
         cls.options[section] = {}
-
-
-class MultimediaSettings(Signallable, Loggable):
-    """
-    Multimedia rendering and previewing settings
-
-    Signals:
-    'settings-changed' : the settings have changed
-    'encoders-changed' : The encoders or muxer have changed
-
-    @ivar render_scale: The scale to be applied to the video width and height
-    when rendering.
-    @type render_scale: int
-    """
-    __signals__ = {
-        "settings-changed": None,
-        "encoders-changed": None,
-    }
-
-    # Audio/Video settings for processing/export
-
-    # TODO : Add PAR/DAR for video
-    # TODO : switch to using GstFraction internally where appliable
-
-    muxers, aencoders, vencoders = available_combinations()
-
-    def __init__(self, **unused_kw):
-        Loggable.__init__(self)
-        self.videowidth = 720
-        self.videoheight = 576
-        self.render_scale = 100
-        self.videorate = Gst.Fraction(25, 1)
-        self.videopar = Gst.Fraction(16, 15)
-        self.audiochannels = 2
-        self.audiorate = 44100
-        self.audiodepth = 16
-        self.vencoder = None
-        self.aencoder = None
-        self.muxer = "oggmux"
-        # A (muxer -> containersettings) map.
-        self._containersettings_cache = {}
-        # A (vencoder -> vcodecsettings) map.
-        self._vcodecsettings_cache = {}
-        # A (aencoder -> acodecsettings) map.
-        self._acodecsettings_cache = {}
-
-    def copy(self):
-        ret = MultimediaSettings()
-        ret.videowidth = self.videowidth
-        ret.videoheight = self.videoheight
-        ret.render_scale = self.render_scale
-        ret.videorate = Gst.Fraction(self.videorate.num, self.videorate.denom)
-        ret.videopar = Gst.Fraction(self.videopar.num, self.videopar.denom)
-        ret.audiochannels = self.audiochannels
-        ret.audiorate = self.audiorate
-        ret.audiodepth = self.audiodepth
-        ret.vencoder = self.vencoder
-        ret.aencoder = self.aencoder
-        ret.muxer = self.muxer
-        ret.containersettings = dict(self.containersettings)
-        ret.acodecsettings = dict(self.acodecsettings)
-        ret.vcodecsettings = dict(self.vcodecsettings)
-        return ret
-
-    def getDAR(self):
-        return Gst.Fraction(self.videowidth, self.videoheight) * self.videopar
-
-    def __str__(self):
-        """
-        Redefine __str__ to allow printing the project audio/video settings.
-        This is used for debugging, do not make these strings translatable.
-        """
-        msg = "\n\n"
-        msg += "\tVideo: " + str(self.videowidth) + "x" + str(self.videoheight) +\
-               " " + str(self.videorate) + " fps, " + str(self.videopar) + " PAR"
-        msg += "\n\t\tEncoder: " + str(self.vencoder)
-        if self.vcodecsettings:
-            msg += "\n\t\tCodec settings: " + str(self.vcodecsettings)
-        msg += "\n\tAudio: " + str(self.audiochannels) + " channels, " +\
-            str(self.audiorate) + " Hz, " + str(self.audiodepth) + " bits"
-        msg += "\n\t\tEncoder: " + str(self.aencoder)
-        if self.acodecsettings:
-            msg += "\n\t\tCodec settings: " + str(self.acodecsettings)
-        msg += "\n\tMuxer: " + str(self.muxer)
-        if self.containersettings:
-            msg += "\n\t\t" + str(self.containersettings)
-        msg += "\n\n"
-        return msg
-
-    def getVideoWidthAndHeight(self, render=False):
-        """ Returns the video width and height as a tuple
-
-        @param render: Whether to apply self.render_scale to the returned values
-        @type render: bool
-        """
-        if render:
-            scale = self.render_scale
-        else:
-            scale = 100
-        return self.videowidth * scale / 100, self.videoheight * scale / 100
-
-    def getVideoCaps(self, render=False):
-        """ Returns the GstCaps corresponding to the video settings """
-        videowidth, videoheight = self.getVideoWidthAndHeight(render=render)
-        vstr = "width=%d,height=%d,pixel-aspect-ratio=%d/%d,framerate=%d/%d" % (
-            videowidth, videoheight,
-            self.videopar.num, self.videopar.denom,
-            self.videorate.num, self.videorate.denom)
-        caps_str = "video/x-raw,%s" % (vstr)
-        video_caps = Gst.caps_from_string(caps_str)
-        if self.vencoder:
-            return get_compatible_sink_caps(self.vencoder, video_caps)
-        return video_caps
-
-    def getAudioCaps(self):
-        """ Returns the GstCaps corresponding to the audio settings """
-        # TODO: Figure out why including 'depth' causes pipeline failures:
-        astr = "rate=%d,channels=%d" % (self.audiorate, self.audiochannels)
-        caps_str = "audio/x-raw,%s" % (astr)
-        audio_caps = Gst.caps_from_string(caps_str)
-        if self.aencoder:
-            return get_compatible_sink_caps(self.aencoder, audio_caps)
-        return audio_caps
-
-    def setVideoProperties(self, width=-1, height=-1, framerate=-1, par=-1,
-            render_scale=-1):
-        """ Set the video width, height and framerate """
-        self.info("set_video_props %d x %d @ %r fps", width, height, framerate)
-        changed = False
-        if not width == -1 and not width == self.videowidth:
-            self.videowidth = width
-            changed = True
-        if not height == -1 and not height == self.videoheight:
-            self.videoheight = height
-            changed = True
-        if not render_scale == -1 and not render_scale == self.render_scale:
-            self.render_scale = render_scale
-            changed = True
-        if not framerate == -1 and not framerate == self.videorate:
-            self.videorate = framerate
-            changed = True
-        if not par == -1 and not par == self.videopar:
-            self.videopar = par
-            changed = True
-        if changed:
-            self.emit("settings-changed")
-
-    def setAudioProperties(self, nbchanns=-1, rate=-1, depth=-1):
-        """ Set the number of audio channels, rate and depth """
-        self.info("%d x %dHz %dbits", nbchanns, rate, depth)
-        changed = False
-        if not nbchanns == -1 and not nbchanns == self.audiochannels:
-            self.audiochannels = nbchanns
-            changed = True
-        if not rate == -1 and not rate == self.audiorate:
-            self.audiorate = rate
-            changed = True
-        if not depth == -1 and not depth == self.audiodepth:
-            self.audiodepth = depth
-            changed = True
-        if changed:
-            self.emit("settings-changed")
-
-    def setEncoders(self, muxer="", vencoder="", aencoder=""):
-        """ Set the video/audio encoder and muxer """
-        changed = False
-        if not muxer == "" and not muxer == self.muxer:
-            self.muxer = muxer
-            changed = True
-        if not vencoder == "" and not vencoder == self.vencoder:
-            self.vencoder = vencoder
-            changed = True
-        if not aencoder == "" and not aencoder == self.aencoder:
-            self.aencoder = aencoder
-            changed = True
-        if changed:
-            self.emit("encoders-changed")
-
-    @property
-    def containersettings(self):
-        return self._containersettings_cache.setdefault(self.muxer, {})
-
-    @containersettings.setter
-    def containersettings(self, value):
-        self._containersettings_cache[self.muxer] = value
-
-    @property
-    def vcodecsettings(self):
-        return self._vcodecsettings_cache.setdefault(self.vencoder, {})
-
-    @vcodecsettings.setter
-    def vcodecsettings(self, value):
-        self._vcodecsettings_cache[self.vencoder] = value
-
-    @property
-    def acodecsettings(self):
-        return self._acodecsettings_cache.setdefault(self.aencoder, {})
-
-    @acodecsettings.setter
-    def acodecsettings(self, value):
-        self._acodecsettings_cache[self.aencoder] = value
-
-    def getAudioEncoders(self):
-        """ List audio encoders compatible with the current muxer """
-        return self.aencoders[self.muxer]
-
-    def getVideoEncoders(self):
-        """ List video encoders compatible with the current muxer """
-        return self.vencoders[self.muxer]
diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py
index a5ecba1..d58a899 100644
--- a/pitivi/timeline/timeline.py
+++ b/pitivi/timeline/timeline.py
@@ -545,6 +545,7 @@ class TimelineControls(Gtk.VBox, Loggable):
     timeline = property(getTimeline, setTimeline, None, "The timeline property")
 
     def _layerAddedCb(self, timeline, layer):
+        self.debug("Layer %s added", layer)
         video_control = VideoLayerControl(self.app, layer)
         audio_control = AudioLayerControl(self.app, layer)
 
@@ -945,13 +946,9 @@ class Timeline(Gtk.Table, Loggable, Zoomable):
         self._framerate = Gst.Fraction(1, 1)
         self._timeline = None
 
-        # Used to insert sources at the end of the timeline
-        self._sources_to_insert = []
-
         self.zoomed_fitted = True
 
         # Timeline edition related fields
-        self._creating_tckobjs_sigid = {}
         self._move_context = None
 
         self._project = None
@@ -1173,11 +1170,11 @@ class Timeline(Gtk.Table, Loggable, Zoomable):
         if not self._drag_started:
             self._drag_started = True
         elif context.list_targets() not in DND_EFFECT_LIST and self.app.gui.medialibrary.dragged:
-            if not self._temp_objects and not self._creating_tckobjs_sigid:
+            if not self._temp_objects:
                 self._create_temp_source(x, y)
 
             # Let some time for TrackObject-s to be created
-            if self._temp_objects and not self._creating_tckobjs_sigid:
+            if self._temp_objects:
                 focus = self._temp_objects[0]
                 if self._move_context is None:
                     self._move_context = EditingContext(focus,
@@ -1204,10 +1201,6 @@ class Timeline(Gtk.Table, Loggable, Zoomable):
         If the user drags outside the timeline,
         remove the temporary objects we had created during the drap operation.
         """
-        # If TrackObject-s still being created, wait before deleting
-        if self._creating_tckobjs_sigid:
-            return True
-
         # Clean up only if clip was not dropped already
         if self._drag_started:
             self.debug("Drag cleanup")
@@ -1361,31 +1354,19 @@ class Timeline(Gtk.Table, Loggable, Zoomable):
         drag-and-drop operation.
         """
         layer = self._ensureLayer()[0]
-        duration = 0
-
-        for uri in self.app.gui.medialibrary.getSelectedItems():
-            info = self._project.medialibrary.getInfoFromUri(uri)
-            src = GES.TimelineFileSource(uri=uri)
-            src.props.start = duration
-            # Set image duration
-            # FIXME: after GES Materials are merged, check if image instead
-            if src.props.duration == 0:
-                src.set_duration(long(self._settings.imageClipLength) * Gst.SECOND / 1000)
-            duration += info.get_duration()
-            layer.add_object(src)
-            id = src.connect("track-object-added", self._trackObjectsCreatedCb, src, x, y)
-            self._creating_tckobjs_sigid[src] = id
-
-    def _trackObjectsCreatedCb(self, unused_tl, track_object, tlobj, x, y):
-        # Make sure not to start the moving process before the TrackObject-s
-        # are created. We concider that the time between the different
-        # TrackObject-s creation is short enough so we are all good when the
-        # first TrackObject is added to the TimelineObject
-        if tlobj.is_image():
-            tlobj.set_duration(long(self._settings.imageClipLength) * Gst.SECOND / 1000)
-        self._temp_objects.insert(0, tlobj)
-        tlobj.disconnect(self._creating_tckobjs_sigid[tlobj])
-        del self._creating_tckobjs_sigid[tlobj]
+        start = 0
+
+        for asset in self.app.gui.medialibrary.getSelectedAssets():
+            if asset.is_image():
+                clip_duration = long(long(self._settings.imageClipLength) * Gst.SECOND / 1000)
+            else:
+                clip_duration = asset.get_duration()
+
+            source = layer.add_asset(asset, start, 0,
+                clip_duration, 1.0, asset.get_supported_types())
+
+            self._temp_objects.insert(0, source)
+            start += asset.get_duration()
 
     def _move_temp_source(self, x, y):
         x = self.hadj.props.value + x
@@ -1544,11 +1525,12 @@ class Timeline(Gtk.Table, Loggable, Zoomable):
         timeline_duration = duration + Gst.SECOND - 1
         timeline_duration_s = int(timeline_duration / Gst.SECOND)
 
-        self.debug("duration: %s, timeline duration: %s" % (duration,
+        self.debug("duration: %s, timeline duration: %s" % (print_ns(duration),
            print_ns(timeline_duration)))
 
         ideal_zoom_ratio = float(ruler_width) / timeline_duration_s
         nearest_zoom_level = Zoomable.computeZoomLevel(ideal_zoom_ratio)
+        self.debug("Ideal zoom: %s, nearest_zoom_level %s", ideal_zoom_ratio, nearest_zoom_level)
         Zoomable.setZoomLevel(nearest_zoom_level)
         self.timeline.props.snapping_distance = \
             Zoomable.pixelToNs(self.app.settings.edgeSnapDeadband)
@@ -1559,35 +1541,43 @@ class Timeline(Gtk.Table, Loggable, Zoomable):
 
 ## Project callbacks
 
-    def _projectChangedCb(self, app, project):
+    def _projectChangedCb(self, app, project, unused_fully_loaded):
         """
-        When a new blank project is created, immediately clear the timeline.
-
-        Otherwise, we would sit around until a clip gets imported to the
-        media library, waiting for a "ready" signal.
+        When a project is loaded, we connect to its pipeline
         """
-        self.debug("New blank project created, pre-emptively clearing the timeline")
-        self.setProject(project)
+        self.debug("Project changed")
+
+        if project:
+            self.debug("Project is not None, connecting to its pipeline")
+            self._seeker = self._project.seeker
+            self._pipeline = self._project.pipeline
+            self._pipeline.connect("position", self.positionChangedCb)
+            self.ruler.setProjectFrameRate(self._project.videorate)
+            self.ruler.zoomChanged()
+            self._renderingSettingsChangedCb(self._project, None, None)
+
+            self._setBestZoomRatio()
 
-    def setProject(self, project):
+    def _projectCreatedCb(self, app, project):
+        """
+        When a project is created, we connect to it timeline
+        """
         self.debug("Setting project %s", project)
         if self._project:
-            self._project.disconnect_by_function(self._settingsChangedCb)
-            self._pipeline.disconnect_by_func(self.positionChangedCb)
-            self.pipeline = None
+            self._project.disconnect_by_func(self._renderingSettingsChangedCb)
+            try:
+                self._pipeline.disconnect_by_func(self.positionChangedCb)
+            except TypeError:
+                pass  # We were not connected no problem
+
+            self._pipeline = None
+            self._seeker = None
 
         self._project = project
         if self._project:
+            self._project.connect("rendering-settings-changed",
+                                  self._renderingSettingsChangedCb)
             self.setTimeline(project.timeline)
-            self.ruler.setProjectFrameRate(self._project.getSettings().videorate)
-            self.ruler.zoomChanged()
-            self._settingsChangedCb(self._project, None, self._project.getSettings())
-
-            self._seeker = self._project.seeker
-            self._pipeline = self._project.pipeline
-            self._pipeline.connect("position", self.positionChangedCb)
-            self._project.connect("settings-changed", self._settingsChangedCb)
-            self._setBestZoomRatio()
 
     def setProjectManager(self, projectmanager):
         if self._projectmanager is not None:
@@ -1595,11 +1585,22 @@ class Timeline(Gtk.Table, Loggable, Zoomable):
 
         self._projectmanager = projectmanager
         if projectmanager is not None:
+            projectmanager.connect("new-project-created", self._projectCreatedCb)
             projectmanager.connect("new-project-loaded", self._projectChangedCb)
 
-    def _settingsChangedCb(self, project, old, new):
-        self._framerate = new.videorate
-        self.ruler.setProjectFrameRate(self._framerate)
+    def _renderingSettingsChangedCb(self, project, item, value):
+        """
+        Called when any Project metadata changes, we filter out the one
+        we are interested in.
+
+        if @item is None, it mean we called it ourself, and want to force
+        getting the project videorate value
+        """
+        if item == "videorate" or item is None:
+            if value is None:
+                value = project.videorate
+            self._framerate = value
+            self.ruler.setProjectFrameRate(self._framerate)
 
 ## Timeline callbacks
 
@@ -1839,66 +1840,22 @@ class Timeline(Gtk.Table, Loggable, Zoomable):
             path, mime = foo[0], foo[1]
             self._project.pipeline.save_thumbnail(-1, -1, mime, path)
 
-    def insertEnd(self, sources):
+    def insertEnd(self, assets):
         """
         Add source at the end of the timeline
         @type sources: An L{GES.TimelineSource}
         @param x2: A list of sources to add to the timeline
         """
         self.app.action_log.begin("add clip")
+        # FIXME we should find the longets layer instead of adding it to the
+        # first one
         # Handle the case of a blank project
-        self._ensureLayer()
-        self._sources_to_insert = sources
-
-        # Start adding sources in the timeline
-        self._insertNextSource()
-
-    def _insertNextSource(self):
-        """ Insert a source at the end of the timeline's first track """
-        timeline = self.app.current.timeline
-
-        if not self._sources_to_insert:
-            # We need to wait (100ms is enoug for sure) for TrackObject-s to
-            # be added to the Tracks
-            # FIXME remove this "hack" when Materials are merged
-            GObject.timeout_add(100, self._finalizeSourceAdded)
-            self.app.action_log.commit()
-
-            # Update zoom level if needed
-            return
-        source = self._sources_to_insert.pop()
-        layer = timeline.get_layers()[0]  # FIXME Get the longest layer
-        # Waiting for the TrackObject to be created because of a race
-        # condition, and to know the real length of the timeline when
-        # adding several sources at a time.
-        # connecting before adding, as it signaled before connection
-        source.connect("track-object-added", self._trackObjectAddedCb)
-        layer.add_object(source)
-
-    def _trackObjectAddedCb(self, source, trackobj):
-        """ After an object has been added to the first track, position it
-        correctly and request the next source to be processed. """
-        timeline = self.app.current.timeline
-        layer = timeline.get_layers()[0]  # FIXME Get the longest layer
-
-        # Set the duration of the clip if it is an image
-        if hasattr(source, "is_image") and source.is_image():
-            source.set_duration(long(self._settings.imageClipLength) * Gst.SECOND / 1000)
-
-        # Handle the case where we just inserted the first clip
-        if len(layer.get_objects()) == 1:
-            source.props.start = 0
-        else:
-            source.props.start = timeline.props.duration
-
-        # We only need one TrackObject to estimate the new duration.
-        # Process the next source.
-        source.disconnect_by_func(self._trackObjectAddedCb)
-        self._insertNextSource()
+        layer = self._ensureLayer()[0]
+        for asset in assets:
+            if asset.is_image():
+                clip_duration = long(long(self._settings.imageClipLength) * Gst.SECOND / 1000)
+            else:
+                clip_duration = asset.get_duration()
 
-    def _finalizeSourceAdded(self):
-        timeline = self.app.current.timeline
-        self.app.current.seeker.seek(timeline.props.duration)
-        if self.zoomed_fitted is True:
-            self._setBestZoomRatio()
-        return False
+            layer.add_asset(asset, self.timeline.props.duration,
+                0, clip_duration, 1.0, asset.get_supported_types())
diff --git a/pitivi/timeline/track.py b/pitivi/timeline/track.py
index 967413a..cd2f609 100644
--- a/pitivi/timeline/track.py
+++ b/pitivi/timeline/track.py
@@ -763,8 +763,7 @@ class TrackFileSource(TrackObject):
         Set the human-readable file name as the clip's text label
         """
         if self.element:
-            uri = self.element.props.uri
-            info = self.app.current.medialibrary.getInfoFromUri(uri)
+            info = element.get_timeline_object().get_asset().get_info()
             self.name.props.text = info_name(info)
             twidth, theight = text_size(self.name)
             self.namewidth = twidth
diff --git a/pitivi/transitions.py b/pitivi/transitions.py
index ceeec27..f8f58bc 100644
--- a/pitivi/transitions.py
+++ b/pitivi/transitions.py
@@ -35,7 +35,7 @@ from pitivi.utils.loggable import Loggable
 from pitivi.utils.signal import Signallable
 from pitivi.utils.ui import SPACING, PADDING
 
-(COL_TRANSITION_ID,
+(COL_TRANSITION_ASSET,
  COL_NAME_TEXT,
  COL_DESC_TEXT,
  COL_ICON) = range(4)
@@ -100,7 +100,7 @@ class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
         self.infobar.add(txtlabel)
         self.infobar.show_all()
 
-        self.storemodel = Gtk.ListStore(str, str, str, GdkPixbuf.Pixbuf)
+        self.storemodel = Gtk.ListStore(GES.Asset, str, str, GdkPixbuf.Pixbuf)
 
         self.iconview_scrollwin = Gtk.ScrolledWindow()
         self.iconview_scrollwin.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
@@ -150,14 +150,13 @@ class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
 # UI callbacks
 
     def _transitionSelectedCb(self, event):
-        selected_item = self.getSelectedItem()
-        if not selected_item:
-        # The user clicked between icons
+        transition_asset = self.getSelectedItem()
+        if not transition_asset:
+            # The user clicked between icons
             return False
-        transition_id = int(selected_item)
-        transition = self.available_transitions.get(transition_id)
-        self.debug("New transition type selected: %s" % transition)
-        if transition.value_nick == "crossfade":
+
+        self.debug("New transition type selected: %s" % transition_asset.get_id())
+        if transition_asset.get_id() == "crossfade":
             self.props_widgets.set_sensitive(False)
         else:
             self.props_widgets.set_sensitive(True)
@@ -166,7 +165,7 @@ class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
         position = self.app.current.pipeline.getPosition()
         self.app.current.pipeline.simple_seek(0)
 
-        self.element.set_transition_type(transition)
+        self.element.get_timeline_object().set_asset(transition_asset)
 
         # Seek back into the previous position, refreshing the preview
         self.app.current.pipeline.simple_seek(position)
@@ -219,13 +218,12 @@ class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
 # GES callbacks
 
     def _transitionTypeChangedCb(self, element, unused_prop):
-        transition = element.get_transition_type()
         try:
             self.iconview.disconnect_by_func(self._transitionSelectedCb)
         except TypeError:
             pass
         finally:
-            self.selectTransition(transition)
+            self.selectTransition(element.get_asset())
             self.iconview.connect("button-release-event", self._transitionSelectedCb)
 
     def _borderChangedCb(self, element, unused_prop):
@@ -260,27 +258,17 @@ class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
         """
         Get the list of transitions from GES and load the associated thumbnails.
         """
-        # TODO: rewrite this method when GESRegistry exists
-        self.available_transitions = {}
-        # GES currently has transitions IDs up to 512
-        # Number 0 means "no transition", so we might just as well skip it.
-        for i in range(1, 513):
-            try:
-                transition = GES.VideoStandardTransitionType(i)
-            except ValueError:
-                # We hit a gap in the enum
-                pass
-            else:
-                self.available_transitions[transition.numerator] = transition
-                self.storemodel.append([str(transition.numerator),
-                                        str(transition.value_nick),
-                                        str(transition.value_name),
-                                        self._getIcon(transition.value_nick)])
+        for trans_asset in GES.list_assets(GES.TimelineTransition):
+            trans_asset.icon = self._getIcon(trans_asset.get_id())
+            self.storemodel.append([trans_asset,
+                                    str(trans_asset.get_id()),
+                                    str(trans_asset.get_meta(GES.META_DESCRIPTION)),
+                                    trans_asset.icon])
 
         # Now that the UI is fully ready, enable searching
         self.modelFilter.set_visible_func(self._setRowVisible, data=None)
         # Alphabetical/name sorting instead of based on the ID number
-        #self.storemodel.set_sort_column_id(COL_NAME_TEXT, Gtk.SortType.ASCENDING)
+        self.storemodel.set_sort_column_id(COL_NAME_TEXT, Gtk.SortType.ASCENDING)
 
     def activate(self, element):
         """
@@ -290,8 +278,8 @@ class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
         self.element.connect("notify::border", self._borderChangedCb)
         self.element.connect("notify::invert", self._invertChangedCb)
         self.element.connect("notify::type", self._transitionTypeChangedCb)
-        transition = element.get_transition_type()
-        if transition.value_nick == "crossfade":
+        transition_asset = element.get_timeline_object().get_asset()
+        if transition_asset.get_id() == "crossfade":
             self.props_widgets.set_sensitive(False)
         else:
             self.props_widgets.set_sensitive(True)
@@ -299,16 +287,16 @@ class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
         self.props_widgets.show_all()
         self.infobar.hide()
         self.searchbar.show_all()
-        self.selectTransition(transition)
+        self.selectTransition(transition_asset)
         self.app.gui.switchContextTab("transitions")
 
-    def selectTransition(self, transition):
+    def selectTransition(self, transition_asset):
         """
         For a given transition type, select it in the iconview if available.
         """
         model = self.iconview.get_model()
         for row in model:
-            if int(transition.numerator) == int(row[COL_TRANSITION_ID]):
+            if transition_asset == row[COL_TRANSITION_ASSET]:
                 path = model.get_path(row.iter)
                 self.iconview.select_path(path)
                 self.iconview.scroll_to_path(path, False, 0, 0)
@@ -354,7 +342,7 @@ class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
 
         view.set_tooltip_item(tooltip, path)
 
-        name = model.get_value(iter_, COL_TRANSITION_ID)
+        name = model.get_value(iter_, COL_TRANSITION_ASSET).get_id()
         if self._current_transition_name != name:
             self._current_transition_name = name
             icon = model.get_value(iter_, COL_ICON)
@@ -371,7 +359,7 @@ class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
         path = self.iconview.get_selected_items()
         if path == []:
             return None
-        return self.modelFilter[path[0]][COL_TRANSITION_ID]
+        return self.modelFilter[path[0]][COL_TRANSITION_ASSET]
 
     def _setRowVisible(self, model, iter, data):
         """
diff --git a/pitivi/utils/ui.py b/pitivi/utils/ui.py
index 70f2de9..c8b2bd5 100644
--- a/pitivi/utils/ui.py
+++ b/pitivi/utils/ui.py
@@ -27,6 +27,7 @@ classes that help with UI drawing around the application
 """
 import glib
 from gi.repository import Gst
+from gi.repository import GES
 from gi.repository import Gtk
 import os
 import cairo
@@ -190,7 +191,10 @@ def beautify_info(info):
 
 def info_name(info):
     """Return a human-readable filename (without the path and quoting)."""
-    filename = unquote(os.path.basename(info.get_uri()))
+    if  isinstance(info, GES.Asset):
+        filename = unquote(os.path.basename(info.get_id()))
+    else:
+        filename = unquote(os.path.basename(info.get_uri()))
     return glib.markup_escape_text(filename)
 
 
diff --git a/pitivi/viewer.py b/pitivi/viewer.py
index 49c920a..40586ea 100644
--- a/pitivi/viewer.py
+++ b/pitivi/viewer.py
@@ -29,7 +29,6 @@ from gi.repository import GES
 GdkX11  # pyflakes
 from gi.repository import GstVideo
 GstVideo    # pyflakes
-from gi.repository import GdkPixbuf
 import cairo
 
 from gettext import gettext as _
diff --git a/tests/test_settings.py b/tests/test_settings.py
index 2a13630..fa6ef6e 100644
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -1,12 +1,12 @@
 import unittest
-from pitivi.settings import MultimediaSettings
+from pitivi.project import Project
 
 
 class TestExportSettings(unittest.TestCase):
-    """Test the settings.MultimediaSettings class."""
+    """Test the project.MultimediaSettings class."""
 
     def setUp(self):
-        self.settings = MultimediaSettings()
+        self.project = Project()
 
     def testMasterAttributes(self):
         self._testMasterAttribute('muxer', dependant_attr='containersettings')
@@ -18,30 +18,30 @@ class TestExportSettings(unittest.TestCase):
         attr_value1 = "%s_value1" % attr
         attr_value2 = "%s_value2" % attr
 
-        setattr(self.settings, attr, attr_value1)
-        setattr(self.settings, dependant_attr, {})
-        getattr(self.settings, dependant_attr)["key1"] = "v1"
-
-        setattr(self.settings, attr, attr_value2)
-        setattr(self.settings, dependant_attr, {})
-        getattr(self.settings, dependant_attr)["key2"] = "v2"
-
-        setattr(self.settings, attr, attr_value1)
-        self.assertTrue("key1" in getattr(self.settings, dependant_attr))
-        self.assertFalse("key2" in getattr(self.settings, dependant_attr))
-        self.assertEqual("v1", getattr(self.settings, dependant_attr)["key1"])
-        setattr(self.settings, dependant_attr, {})
-
-        setattr(self.settings, attr, attr_value2)
-        self.assertFalse("key1" in getattr(self.settings, dependant_attr))
-        self.assertTrue("key2" in getattr(self.settings, dependant_attr))
-        self.assertEqual("v2", getattr(self.settings, dependant_attr)["key2"])
-        setattr(self.settings, dependant_attr, {})
-
-        setattr(self.settings, attr, attr_value1)
-        self.assertFalse("key1" in getattr(self.settings, dependant_attr))
-        self.assertFalse("key2" in getattr(self.settings, dependant_attr))
-
-        setattr(self.settings, attr, attr_value2)
-        self.assertFalse("key1" in getattr(self.settings, dependant_attr))
-        self.assertFalse("key2" in getattr(self.settings, dependant_attr))
+        setattr(self.project, attr, attr_value1)
+        setattr(self.project, dependant_attr, {})
+        getattr(self.project, dependant_attr)["key1"] = "v1"
+
+        setattr(self.project, attr, attr_value2)
+        setattr(self.project, dependant_attr, {})
+        getattr(self.project, dependant_attr)["key2"] = "v2"
+
+        setattr(self.project, attr, attr_value1)
+        self.assertTrue("key1" in getattr(self.project, dependant_attr))
+        self.assertFalse("key2" in getattr(self.project, dependant_attr))
+        self.assertEqual("v1", getattr(self.project, dependant_attr)["key1"])
+        setattr(self.project, dependant_attr, {})
+
+        setattr(self.project, attr, attr_value2)
+        self.assertFalse("key1" in getattr(self.project, dependant_attr))
+        self.assertTrue("key2" in getattr(self.project, dependant_attr))
+        self.assertEqual("v2", getattr(self.project, dependant_attr)["key2"])
+        setattr(self.project, dependant_attr, {})
+
+        setattr(self.project, attr, attr_value1)
+        self.assertFalse("key1" in getattr(self.project, dependant_attr))
+        self.assertFalse("key2" in getattr(self.project, dependant_attr))
+
+        setattr(self.project, attr, attr_value2)
+        self.assertFalse("key1" in getattr(self.project, dependant_attr))
+        self.assertFalse("key2" in getattr(self.project, dependant_attr))



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