Commit 7e7a86ad authored by Edward Hervey's avatar Edward Hervey

Merging Brandon Lewis SOC branch into trunk

git-svn-id: svn+ssh://svn.gnome.org/svn/pitivi/trunk@1243 d3729300-e425-0410-8a4c-d956edccc248
parent f860f219
This diff is collapsed.
......@@ -8,28 +8,28 @@ pitividir = $(libdir)/pitivi/python/pitivi
pitivi_PYTHON = \
__init__.py \
configure.py \
instance.py \
bin.py \
check.py \
configure.py \
discoverer.py \
dnd.py \
effects.py \
instance.py \
objectfactory.py \
pitivi.py \
pitivigstutils.py \
playground.py \
plugincore.py \
pluginmanager.py \
project.py \
projectsaver.py \
serializable.py \
settings.py \
sourcelist.py \
utils.py \
pitivigstutils.py \
signalgroup.py \
sourcelist.py \
threads.py \
thumbnailer.py \
serializable.py \
projectsaver.py \
plugincore.py \
pluginmanager.py
utils.py
BUILT_SOURCES=configure.py
......
......@@ -396,7 +396,7 @@ class SmartFileBin(SmartBin):
has_video = factory.is_video,
has_audio = factory.is_audio,
width = width, height = height,
length = factory.length)
length = factory.getDuration())
def _addSource(self):
self.add(self.source)
......
......@@ -80,7 +80,6 @@ class Discoverer(gobject.GObject):
self.current = None
self.currentTags = []
self.pipeline = None
self.thumbnailing = False
self.thisdone = False
self.prerolled = False
self.nomorepads = False
......@@ -89,6 +88,7 @@ class Discoverer(gobject.GObject):
self.error = None # reason for error
self.extrainfo = None # extra information about the error
self.fakesink = None
self.isimage = False # Used to know if the file is an image
def addFile(self, filename):
""" queue a filename to be discovered """
......@@ -153,7 +153,9 @@ class Discoverer(gobject.GObject):
self.emit('not_media_file', self.current, self.error, self.extrainfo)
elif self.currentfactory:
self.currentfactory.addMediaTags(self.currentTags)
if not self.currentfactory.length:
if self.isimage:
self.currentfactory.setThumbnail(gst.uri_get_location(self.current))
if not self.currentfactory.getDuration() and not self.isimage:
self.emit('not_media_file', self.current,
_("Could not establish the duration of the file."),
_("This clip seems to be in a format which cannot be accessed in a random fashion."))
......@@ -169,6 +171,7 @@ class Discoverer(gobject.GObject):
self.nomorepads = False
self.error = None
self.extrainfo = None
self.isimage = False
# restart an analysis if there's more...
if self.queue:
......@@ -211,6 +214,8 @@ class Discoverer(gobject.GObject):
self.signalsid.append((dbin, dbin.connect("new-decoded-pad", self._newDecodedPadCb)))
self.signalsid.append((dbin, dbin.connect("unknown-type", self._unknownTypeCb)))
self.signalsid.append((dbin, dbin.connect("no-more-pads", self._noMorePadsCb)))
tfind = dbin.get_by_name("typefind")
self.signalsid.append((tfind, tfind.connect("have-type", self._typefindHaveTypeCb)))
self.pipeline.add(source, dbin)
source.link(dbin)
gst.info("analysis pipeline created")
......@@ -237,6 +242,10 @@ class Discoverer(gobject.GObject):
# return False so we don't get called again
return False
def _typefindHaveTypeCb(self, typefind, perc, caps):
if caps.to_string().startswith("image/"):
self.isimage = True
def _busMessageCb(self, unused_bus, message):
if self.thisdone:
return
......@@ -250,7 +259,7 @@ class Discoverer(gobject.GObject):
# Let's get the information from all the pads
self._getPadsInfo()
# Only go to PLAYING if we have an video stream to thumbnail
if self.currentfactory and self.currentfactory.is_video:
if self.currentfactory and self.currentfactory.is_video and not self.isimage:
gst.log("pipeline has gone to PAUSED, now pushing to PLAYING")
if self.pipeline.set_state(gst.STATE_PLAYING) == gst.STATE_CHANGE_FAILURE:
if not self.error:
......@@ -290,7 +299,7 @@ class Discoverer(gobject.GObject):
def _handleError(self, gerror, detail, unused_source):
gst.warning("got an ERROR")
if not self.error:
self.error = _("An internal error occured while analyzing this file : %s") % gerror.message
self.extrainfo = detail
......@@ -319,7 +328,7 @@ class Discoverer(gobject.GObject):
self.currentfactory.setAudioInfo(caps)
elif caps.to_string().startswith("video/x-raw") and not self.currentfactory.video_info:
self.currentfactory.setVideoInfo(caps)
if not self.currentfactory.length:
if not self.currentfactory.getDuration():
try:
length, format = pad.query_duration(gst.FORMAT_TIME)
except:
......
......@@ -2,6 +2,7 @@ elementsdir = $(libdir)/pitivi/python/pitivi/elements
elements_PYTHON = \
__init__.py \
imagefreeze.py \
singledecodebin.py \
smartscale.py \
thumbnailsink.py \
......
# PiTiVi , Non-linear video editor
#
# pitivi/elements/singledecodebin.py
#
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this program; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
"""
Image-to-video element
"""
# Goal:
#
# We want to take a raw image source and output a continuous
# video feed (by default [0,GST_CLOCK_TIME_NONE]) according to
# the srcpad negotiated caps (i.e. proper timestamps)
#
# In the event of seeks, this is the element that will handle the seeks
# and output the proper segments.
#
# for a given negotiated framerate R (in frames/second):
# The outputted buffer timestamps will be X * 1/R
# where X is an integer.
# EXCEPT for accurate segment seeks where the first/last buffers will be
# adjusted to the requested start/stop values
import gobject
import gst
class ImageFreeze(gst.Element):
__gstdetails__ = (
"ImageFreeze plugin",
"imagefreeze.py",
"Outputs a video feed out an incoming frame",
"Edward Hervey <bilboed@bilboed.com>")
_srctemplate = gst.PadTemplate("src",
gst.PAD_SRC,
gst.PAD_ALWAYS,
gst.caps_new_any())
_sinktemplate = gst.PadTemplate("sink",
gst.PAD_SINK,
gst.PAD_ALWAYS,
gst.caps_new_any())
__gsttemplates__ = (_srctemplate, _sinktemplate)
def __init__(self, *args, **kwargs):
gst.Element.__init__(self, *args, **kwargs)
self.srcpad = gst.Pad(self._srctemplate)
self.srcpad.set_event_function(self._src_event)
self.sinkpad = gst.Pad(self._sinktemplate)
self.sinkpad.set_chain_function(self._sink_chain)
self.sinkpad.set_event_function(self._sink_event)
self.sinkpad.set_setcaps_function(self._sink_setcaps)
self.add_pad(self.srcpad)
self.add_pad(self.sinkpad)
self._reset()
def _reset(self):
gst.debug("resetting ourselves")
self._outputrate = None
self._srccaps = None
# number of outputted buffers
self._offset = 0
self._segment = gst.Segment()
self._segment.init(gst.FORMAT_TIME)
self._needsegment = True
self._bufferduration = 0
# this is the buffer we store and repeatedly output
self._buffer = None
# this will be set by our task
self.last_return = gst.FLOW_OK
def _sink_setcaps(self, pad, caps):
gst.debug("caps %s" % caps.to_string())
downcaps = self.srcpad.peer_get_caps().copy()
gst.debug("downcaps %s" % downcaps.to_string())
# methodology
# 1. We override any incoming framerate
ccaps = caps.make_writable()
for struct in ccaps:
if struct.has_key("framerate"):
try:
del struct["framerate"]
except:
gst.warning("Couldn't remove 'framerate' from %s" % struct.to_string())
# 2. we do the intersection of our incoming stripped caps
# and the downstream caps
intersect = ccaps.intersect(downcaps)
if intersect.is_empty():
gst.warning("no negotiation possible !")
return False
# 3. for each candidate in the intersection, we try to set that
# candidate downstream
for candidate in intersect:
gst.debug("Trying %s" % candidate.to_string())
if self.srcpad.peer_accept_caps(candidate):
gst.debug("accepted !")
# 4. When we have an accepted caps downstream, we store the negotiated
# framerate and return
self._outputrate = candidate["framerate"]
self._bufferduration = gst.SECOND * self._outputrate.denom / self._outputrate.num
self._srccaps = candidate
return self.srcpad.set_caps(candidate)
# 5. If we can't find an accepted candidate, we return False
return False
def _src_event(self, pad, event):
# for the moment we just push it upstream
gst.debug("event %r" % event)
if event.type == gst.EVENT_SEEK:
rate,fmt,flags,startt,start,stopt,stop = event.parse_seek()
gst.debug("Handling seek event %r" % flags)
if flags & gst.SEEK_FLAG_FLUSH:
gst.debug("sending flush_start event")
self.srcpad.push_event(gst.event_new_flush_start())
self._segment.set_seek(*event.parse_seek())
gst.debug("_segment start:%s stop:%s" % (gst.TIME_ARGS(self._segment.start),
gst.TIME_ARGS(self._segment.stop)))
# create a new initial seek
gst.debug("pausing task")
self.srcpad.pause_task()
gst.debug("task paused")
seek = gst.event_new_seek(1.0, gst.FORMAT_TIME, flags,
gst.SEEK_TYPE_NONE, 0,
gst.SEEK_TYPE_NONE, 0)
#return self.sinkpad.push_event(seek)
self._needsegment = True
if flags & gst.SEEK_FLAG_FLUSH:
self.srcpad.push_event(gst.event_new_flush_stop())
self.srcpad.start_task(self.our_task)
return True
return self.sinkpad.push_event(event)
def _sink_event(self, pad, event):
gst.debug("event %r" % event)
if event.type == gst.EVENT_NEWSEGMENT:
gst.debug("dropping new segment !")
return True
elif event.type == gst.EVENT_FLUSH_START:
self._reset()
return self.srcpad.push_event(event)
def _sink_chain(self, pad, buffer):
gst.debug("buffer %s %s" % (gst.TIME_ARGS(buffer.timestamp),
gst.TIME_ARGS(buffer.duration)))
if self._buffer != None:
gst.debug("already have a buffer ! Returning GST_FLOW_WRONG_STATE")
return gst.FLOW_WRONG_STATE
self._buffer = buffer
self.srcpad.start_task(self.our_task)
return gst.FLOW_WRONG_STATE
def our_task(self, something):
#this is where we repeatedly output our buffer
gst.debug("self:%r, something:%r" % (self, something))
gst.debug("needsegment: %r" % self._needsegment)
if self._needsegment:
gst.debug("Need to output a new segment")
segment = gst.event_new_new_segment(False,
self._segment.rate,
self._segment.format,
self._segment.start,
self._segment.stop,
self._segment.start)
self.srcpad.push_event(segment)
# calculate offset
# offset is int(segment.start / outputrate)
self._offset = int(self._segment.start * self._outputrate.num / self._outputrate.denom / gst.SECOND)
self._needsegment = False
gst.debug("Newsegment event pushed")
# new position
position = self._offset * gst.SECOND * self._outputrate.denom / self._outputrate.num
if self._segment.stop != -1 and position > self._segment.stop:
gst.debug("end of configured segment (position:%s / segment_stop:%s)" % (gst.TIME_ARGS(position),
gst.TIME_ARGS(self._segment.stop)))
# end of stream/segment handling
if self._segment.flags & gst.SEEK_FLAG_SEGMENT:
# emit a gst.MESSAGE_SEGMENT_DONE
self.post_message(gst.message_new_segment_done(self, gst.FORMAT_TIME, self._segment.stop))
else:
self.srcpad.push_event(gst.event_new_eos())
self.last_return = gst.FLOW_WRONG_STATE
self.srcpad.pause_task()
# we need to update the caps here !
obuf = self._buffer.make_metadata_writable()
ok, nstart, nstop = self._segment.clip(gst.FORMAT_TIME,
position, position + self._bufferduration)
if ok:
obuf.timestamp = nstart
obuf.duration = nstop - nstart
obuf.caps = self._srccaps
gst.debug("Pushing out buffer %s" % gst.TIME_ARGS(obuf.timestamp))
self.last_return = self.srcpad.push(obuf)
self._offset += 1
if self.last_return != gst.FLOW_OK:
gst.debug("Pausing ourself, last_return : %s" % gst.flow_get_name(self.last_return))
self.srcpad.pause_task()
def do_change_state(self, transition):
if transition in [gst.STATE_CHANGE_READY_TO_PAUSED, gst.STATE_CHANGE_PAUSED_TO_READY]:
self._reset()
return gst.Element.do_change_state(self, transition)
gobject.type_register(ImageFreeze)
def dataprobe(pad, data):
if isinstance(data, gst.Buffer):
print "Buffer", gst.TIME_ARGS(data.timestamp), gst.TIME_ARGS(data.duration), data.caps.to_string()
else:
print "Event", data.type
if data.type == gst.EVENT_NEWSEGMENT:
print data.parse_new_segment()
return True
def make_image_video_bin(location):
b = gst.Bin("image-video-bin-"+location)
src = gst.element_factory_make("filesrc")
src.props.location = location
src.props.blocksize = 1024 * 1024
dec = gst.element_factory_make("jpegdec")
vscale = gst.element_factory_make("videoscale")
freeze = ImageFreeze()
cfil = gst.element_factory_make("capsfilter")
cfil.props.caps = gst.Caps("video/x-raw-yuv,framerate=25/1")
p.add(src, dec, vscale, freeze, cfil)
gst.element_link_many(src, dec, vscale)
vscale.link(freeze, gst.Caps("video/x-raw-yuv,width=640,height=480"))
gst.element_link_many(freeze, cfil)
b.add_pad(gst.GhostPad("src", cfil.get_pad("src")))
return b
def post_link(gnls, pad, q):
gnls.link(q)
# filesrc ! jpegdec ! imagefreeze ! xvimagesink
if __name__ == "__main__":
import sys
p = gst.Pipeline()
b = make_image_video_bin(sys.argv[1])
gnls = gst.element_factory_make("gnlsource")
gnls.add(b)
gnls.props.media_start = 5 * gst.SECOND
gnls.props.media_duration = 5 * gst.SECOND
gnls.props.duration = 5 * gst.SECOND
toverl = gst.element_factory_make("timeoverlay")
sink = gst.element_factory_make("xvimagesink")
sink.get_pad("sink").add_data_probe(dataprobe)
q = gst.element_factory_make("queue")
p.add(gnls, toverl, q, sink)
gst.element_link_many(q, toverl, sink)
#q.link(sink)
gnls.connect("pad-added", post_link, q)
ml = gobject.MainLoop()
p.set_state(gst.STATE_PLAYING)
ml.run()
......@@ -270,7 +270,35 @@ class ObjectFactory(gobject.GObject, Serializable):
gobject.type_register(ObjectFactory)
class FileSourceFactory(ObjectFactory):
class SourceFactory(ObjectFactory):
"""
Provides sources usable in a timeline
"""
__data_type__ = "source-factory"
def getDuration(self):
"""
Returns the maximum duration of the source in nanoseconds
If the source doesn't have a maximum duration (like an image), subclasses
should implement this by returning 2**63 - 1 (MAX_LONG).
"""
pass
def getDefaultDuration(self):
"""
Returns the default duration of a file in nanoseconds,
this should be used when using sources initially.
Most sources will return the same as getDuration(), but can be overriden
for sources that have an infinite duration.
"""
return self.getDuration()
gobject.type_register(SourceFactory)
class FileSourceFactory(SourceFactory):
"""
Provides File sources useable in a timeline
"""
......@@ -294,26 +322,33 @@ class FileSourceFactory(ObjectFactory):
def __init__(self, filename="", project=None, **kwargs):
gst.info("filename:%s , project:%s" % (filename, project))
ObjectFactory.__init__(self, **kwargs)
SourceFactory.__init__(self, **kwargs)
self.project = project
self.name = filename
self.displayname = os.path.basename(unquote(self.name))
self.lastbinid = 0
self.length = 0
self.thumbnail = ""
self.thumbnails = []
self._length = 0
self._thumbnail = ""
self._thumbnails = []
self.settings = None
## SourceFactory implementation
def getDuration(self):
return self._length
def do_set_property(self, property, value):
if property.name == "length":
if self.length and self.length != value:
if self._length and self._length != value:
gst.warning("%s : Trying to set a new length (%s) different from previous one (%s)" % (self.name,
gst.TIME_ARGS(self.length),
gst.TIME_ARGS(self._length),
gst.TIME_ARGS(value)))
self.length = value
self._length = value
elif property.name == "thumbnail":
gst.debug("thumbnail : %s" % value)
if os.path.isfile(value):
self.thumbnail = value
self._thumbnail = value
else:
gst.warning("Thumbnail path is invalid !")
else:
ObjectFactory.do_set_property(self, property, value)
......@@ -380,6 +415,9 @@ class FileSourceFactory(ObjectFactory):
""" Sets the thumbnail filename of the element """
self.set_property("thumbnail", thumbnail)
def getThumbnail(self):
return self._thumbnail
def getExportSettings(self):
""" Returns the ExportSettings corresponding to this source """
if self.settings:
......@@ -408,13 +446,13 @@ class FileSourceFactory(ObjectFactory):
def toDataFormat(self):
ret = ObjectFactory.toDataFormat(self)
ret["filename"] = self.name
ret["length"] = self.length
ret["length"] = self._length
return ret
def fromDataFormat(self, obj):
ObjectFactory.fromDataFormat(self, obj)
self.name = obj["filename"]
self.length = obj["length"]
self._length = obj["length"]
class OperationFactory(ObjectFactory):
......
......@@ -22,6 +22,7 @@
"""
Main application
"""
import os
import gobject
import gtk
import gst
......@@ -89,7 +90,7 @@ class Pitivi(gobject.GObject):
( ))
}
def __init__(self, use_ui=True, *args, **kwargs):
def __init__(self, args=[], use_ui=True):
"""
initialize pitivi with the command line arguments
"""
......@@ -108,7 +109,11 @@ class Pitivi(gobject.GObject):
% APPNAME)
instance.PiTiVi = self
# TODO parse cmd line arguments
# FIXME: use gnu getopt or somethign of the sort
project_file = None
if len(args) > 1:
if os.path.exists(args[1]):
project_file = args[1]
# get settings
self.settings = GlobalSettings()
......@@ -123,9 +128,12 @@ class Pitivi(gobject.GObject):
self.effects = Magician()
if self._use_ui:
self.uimanager = gtk.UIManager()
# we're starting a GUI for the time being
self.gui = mainwindow.PitiviMainWindow()
self.gui.show()
if project_file:
self.loadProject(filepath=project_file)
def do_closing_project(self, project):
return True
......
......@@ -141,7 +141,7 @@ class PlayGround(gobject.GObject):
bus = pipeline.get_bus()
bus.remove_signal_watch()
bus.set_sync_handler(None)
#bus.set_sync_handler(None)
if pipeline.set_state(gst.STATE_READY) == gst.STATE_CHANGE_FAILURE:
return False
......@@ -206,13 +206,18 @@ class PlayGround(gobject.GObject):
will be taken.
"""
if isinstance(self.current, SmartTimelineBin):
# fast path
return True
p = self.getTimeline()
if p:
self.switchToPipeline(p)
return True
return False
def getTimeline(self):
for pipeline in self.pipelines:
if isinstance(pipeline, SmartTimelineBin):
self.switchToPipeline(pipeline)
return True
return False
return pipeline
return None
def setVideoSinkThread(self, vsinkthread):
""" sets the video sink thread """
......
......@@ -116,7 +116,6 @@ class Project(gobject.GObject, Serializable):
# FIXME : This should be discovered !
saveformat = "pickle"
if self.uri and file_is_project(self.uri):
self.timeline = Timeline(self)
loader = ProjectSaver.newProjectSaver(saveformat)
path = gst.uri_get_location(self.uri)
fileobj = open(path, "r")
......@@ -126,7 +125,8 @@ class Project(gobject.GObject, Serializable):
except ProjectLoadError:
gst.error("Error while loading the project !!!")
return False
fileobj.close()
finally:
fileobj.close()
self.format = saveformat
self.urichanged = False
return True
......@@ -282,9 +282,12 @@ class Project(gobject.GObject, Serializable):
def fromDataFormat(self, obj):
Serializable.fromDataFormat(self, obj)
self.name = obj["name"]
self.settings = to_object_from_data_type(obj["settings"])
self.sources = to_object_from_data_type(obj["sources"])
self.timeline = to_object_from_data_type(obj["timeline"])
# calling this makes sure settigns-changed signal is emitted
self.setSettings(to_object_from_data_type(obj["settings"]))
# these objects already exist, so we initialize them from file
# to make sure UI catches signals
self.sources.fromDataFormat(obj["sources"])
self.timeline.fromDataFormat(obj["timeline"])
def uri_is_valid(uri):
return gst.uri_get_protocol(uri) == "file"
......
......@@ -81,7 +81,8 @@ class GlobalSettings:
# reads some settings from environment variable
#self.advancedModeEnabled =
#get_bool_env("PITIVI_ADVANCED_MODE")
self.fileSupportEnabled = get_bool_env("PITIVI_FILE_SUPPORT")
#self.fileSupportEnabled = get_bool_env("PITIVI_FILE_SUPPORT")
self.fileSupportEnabled = True
pass
def get_local_plugin_path(self, autocreate=True):
......
......@@ -27,7 +27,7 @@ import gobject
import gst
from source import TimelineSource
from objects import BrotherObjects, MEDIA_TYPE_AUDIO
from objects import BrotherObjects
from pitivi.serializable import to_object_from_data_type
class Layer(BrotherObjects):
......@@ -427,10 +427,10 @@ class TimelineComposition(TimelineSource):
my_add_sorted(self.sources[position-1], source)
# add it to self.gnlobject
self.gnlobject.info("adding %s to our composition" % source.gnlobject)
self.gnlobject.info("adding %s to our composition" % source.gnlobject.props.name)
self.gnlobject.add(source.gnlobject)
self.gnlobject.info("added source %s" % source.gnlobject)
self.gnlobject.info("added source %s" % source.gnlobject.props.name)
gst.info("%s" % str(self.sources))
self.emit('source-added', source)
......@@ -447,7 +447,7 @@ class TimelineComposition(TimelineSource):
auto_linked : if True will add the brother (if any) of the given source
to the linked composition with the same parameters
"""
self.gnlobject.info("source %s , position:%d, self.sources:%s" %(source, position, self.sources))
self.gnlobject.info("source %s , position:%d, self.sources:%s" %(source.name, position, self.sources))
# make sure object to add has valid start/duration
if source.start == -1 or source.duration <= 0:
......@@ -485,15 +485,15 @@ class TimelineComposition(TimelineSource):
gst.info("start=%s, position=%d, existorder=%d, sourcelength=%s" % (gst.TIME_ARGS(start),
position,
existorder,
gst.TIME_ARGS(source.factory.length)))
gst.TIME_ARGS(source.factory.getDuration())))
# set the correct start/duration time
duration = source.factory.length
duration = source.factory.getDuration()
source.setStartDurationTime(start, duration)
# pushing following
if push_following and not position in [-1, 0]:
#print self.gnlobject, "pushing following", existorder, len(self.sources[position - 1][2])
self.shiftSources(source.factory.length, existorder, len(self.sources[position - 1][2]))
self.shiftSources(source.factory.getDuration(), existorder, len(self.sources[position - 1][2]))