def __init__(self, targetContext): """ The filename may contain template variables: %(frame)d => frame number (starting at 1) %(keyframe)d => key-frame number (starting at 1) %(index)d => index of the thumbnail (starting at 1) %(timestamp)d => timestamp of the thumbnail %(time)s => composed time of the thumbnail, like %(hours)02d:%(minutes)02d:%(seconds)02d %(hours)d => hours from start %(minutes)d => minutes from start %(seconds)d => seconds from start Transcoding config should contains the attributes: periodValue periodUnit (seconds, frames or percent) maxCount thumbsWidth thumbsHeight outputFormat Enum(jpg, png) """ TranscodingTarget.__init__(self, targetContext) self._pipeline = None self._bus = None self._thumbSink = None self._thumbSrc = None self._fileSink = None self._working = False self._finalizing = False self._pending = [] # [(gst.Buffer, Variables)] self._thumbnails = {} # {filename: None} self._waiters = PassiveWaiters("Thumbnailer Finalization") self._prerollTimeout = None self._playErrorTimeout = None self._startLock = threading.Lock() self._checkConfAttr("periodValue") self._checkConfAttr("periodUnit") self._checkConfAttr("maxCount") self._checkConfAttr("thumbsWidth") self._checkConfAttr("thumbsHeight") self._checkConfAttr("outputFormat")
class ThumbnailTarget(TranscodingTarget): implements(IThumbnailer) class EncoderConfig(object): def __init__(self, config): self.videoWidth = config.thumbsWidth self.videoHeight = config.thumbsHeight self.videoMaxWidth = None self.videoMaxHeight = None self.videoWidthMultiple = None self.videoHeightMultiple = None self.videoScaleMethod = VideoScaleMethodEnum.upscale self.videoFramerate = None self.videoPAR = (1, 1) format = config.outputFormat if format == ThumbOutputTypeEnum.png: self.videoEncoder = "ffmpegcolorspace ! pngenc snapshot=false" elif format == ThumbOutputTypeEnum.jpg: self.videoEncoder = "ffmpegcolorspace ! jpegenc" else: raise TranscoderConfigError("Unknown thumbnails output " "format '%s'" % format) def __init__(self, targetContext): """ The filename may contain template variables: %(frame)d => frame number (starting at 1) %(keyframe)d => key-frame number (starting at 1) %(index)d => index of the thumbnail (starting at 1) %(timestamp)d => timestamp of the thumbnail %(time)s => composed time of the thumbnail, like %(hours)02d:%(minutes)02d:%(seconds)02d %(hours)d => hours from start %(minutes)d => minutes from start %(seconds)d => seconds from start Transcoding config should contains the attributes: periodValue periodUnit (seconds, frames or percent) maxCount thumbsWidth thumbsHeight outputFormat Enum(jpg, png) """ TranscodingTarget.__init__(self, targetContext) self._pipeline = None self._bus = None self._thumbSink = None self._thumbSrc = None self._fileSink = None self._working = False self._finalizing = False self._pending = [] # [(gst.Buffer, Variables)] self._thumbnails = {} # {filename: None} self._waiters = PassiveWaiters("Thumbnailer Finalization") self._prerollTimeout = None self._playErrorTimeout = None self._startLock = threading.Lock() self._checkConfAttr("periodValue") self._checkConfAttr("periodUnit") self._checkConfAttr("maxCount") self._checkConfAttr("thumbsWidth") self._checkConfAttr("thumbsHeight") self._checkConfAttr("outputFormat") ## Public Overriden Methods ## def getOutputFiles(self): return self._thumbnails.keys() ## ITranscoderProducer Overriden Methods ## def checkSourceMedia(self, sourcePath, sourceAnalysis): if not sourceAnalysis.hasVideo: self.raiseError("Source media doesn't have video stream") return True def updatePipeline(self, pipeline, analysis, tees, timeout=None): tag = self._getTranscodingTag() self.log("Updating transcoding pipeline for thumbnail target '%s'", tag) # First connect the custom thumbnail sink config = self._getTranscodingConfig() unit = config.periodUnit value = config.periodValue maxCount = config.maxCount ensureOne = config.ensureOne sampler = self.__createSampler(maxCount, unit, value, ensureOne, analysis) thumbSink = ThumbSink(sampler, "ThumbSink-" + tag) queue = gst.element_factory_make("queue", "thumbqueue-%s" % tag) pipeline.add(queue, thumbSink) gst.element_link_many(tees["videosink"], queue, thumbSink) # Then create the thumbnailing pipeline with a custom thumnail source config = self._getTranscodingConfig() thumbPipeName = "thumbnailing-" + tag thumbPipeline = gst.Pipeline(thumbPipeName) thumbSrc = ThumbSrc("ThumbSrc-" + tag) encoderConf = self.EncoderConfig(config) videoEncBin = makeVideoEncodeBin(encoderConf, analysis, tag, withRateControl=False, logger=self) fileSink = gst.element_factory_make("filesink", "filesink-%s" % tag) thumbPipeline.add(thumbSrc, videoEncBin, fileSink) gst.element_link_many(thumbSrc, videoEncBin, fileSink) self._thumbSink = thumbSink self._thumbSrc = thumbSrc self._pipeline = thumbPipeline self._fileSink = fileSink self._bus = self._pipeline.get_bus() self._bus.add_signal_watch() self._bus.connect("message", self._bus_message_callback) def finalize(self, timeout=None): self._finalizing = True if self._working or self._pending: return self._waiters.wait(timeout) return defer.succeed(self) def abort(self, timeout=None): self.log("Aborting thumbnail target '%s'", self._getTranscodingTag()) self.__shutdownPipeline() return defer.succeed(self) ## IThumbnailer Methods ## def push(self, buffer, vars): """ Warning, this is not called from the main thread. """ self._pending.append((buffer, vars)) self.__startThumbnailer() ## Protected GObject Callback Methods ## def _bus_message_callback(self, bus, message): try: if message.type == gst.MESSAGE_STATE_CHANGED: if message.src == self._pipeline: new = message.parse_state_changed()[1] if new == gst.STATE_PAUSED: self.__onPipelinePrerolled() return if message.type == gst.MESSAGE_EOS: self.__onPipelineEOS() return if message.type == gst.MESSAGE_ERROR: gstgerror, debug = message.parse_error() self.__onPipelineError(gstgerror.message, debug) return self.log( "Unhandled GStreamer message in thumbnailing pipeline " "'%s': %s", self._getTranscodingTag(), message ) except Exception, e: msg = "Error during thumbnailing pipeline " "message handling: " + str(e) debug = log.getExceptionMessage(e) self.__postError(msg, debug)