<div dir="ltr"><div>To mix together an audio file and a picture, one has to use imagefreeze to make the image into an infinite video. The issue is to stop the pipeline when the audio file was finished reading. This can be achieved through the 'drained' signal from decodebin2 (catch it and then send EOS to mp4mux).</div><div><br></div><div>I've written the code for the above, however I have an issue about filesink not getting any data. I can see in the debug logs that mp4mux does get data, but filesink does not flush anything. Do you know why?</div><div><br></div><div>Enclosing the code and a graphviz of the pipeline</div><div><br></div><div><div>class AudioCoverVideo:</div><div> def __init__(self,</div><div> cover_path,</div><div> audio_path,</div><div> output_path):</div><div> """</div><div> Outputs a video which is a composite of a cover image and an audio medium.</div><div> Output format is set to mp4 H.264 AAC following the youtube's guidelines for videos.</div><div><br></div><div> Args:</div><div> cover_path: path to the cover file (jpeg and png only)</div><div> audio_path: path to the audio file (any format)</div><div> output_path: path to the output video (it has to be mp4)</div><div> """</div><div> import pygst</div><div> pygst.require('0.10')</div><div> import gst</div><div> import gobject</div><div> gobject.threads_init()</div><div><br></div><div> cover_decoder = None</div><div><br></div><div> if cover_path.lower().endswith('jpg') or cover_path.lower().endswith('jpeg'):</div><div> cover_decoder = 'jpegdec'</div><div> elif cover_path.lower().endswith('png'):</div><div> cover_decoder = 'pngdec'</div><div> else:</div><div> raise ValueError()</div><div><br></div><div> self.pipeline = gst.Pipeline()</div><div> </div><div> # Create bus and connect handlers for EOS and error</div><div> self.bus = self.pipeline.get_bus()</div><div> self.bus.add_signal_watch()</div><div> self.bus.connect('message::eos', self.on_eos)</div><div> self.bus.connect('message::error', self.on_error)</div><div><br></div><div> self.mux = gst.element_factory_make("mp4mux")</div><div> self.mux.set_property('faststart', True)</div><div> self.filesink = gst.element_factory_make("filesink")</div><div> self.filesink.set_property('location', output_path)</div><div> </div><div> self.pipeline.add(self.mux, self.filesink)</div><div> gst.element_link_many(self.mux, self.filesink)</div><div><br></div><div> self.ch_audio_pad = self.mux.get_request_pad('audio_%d')</div><div> self.ch_video_pad = self.mux.get_request_pad('video_%d')</div><div> </div><div> self.audio_pipeline(audio_path)</div><div> self.video_pipeline(cover_path)</div><div><br></div><div> def start(self):</div><div> import gst</div><div> import gobject</div><div> # The MainLoop</div><div> self.mainloop = gobject.MainLoop()</div><div> # And off we go!</div><div> self.pipeline.set_state(gst.STATE_PLAYING)</div><div> self.mainloop.run()</div><div><br></div><div> def audio_pipeline(self, audio_path):</div><div> import gst</div><div> src = gst.element_factory_make('filesrc', 'audiofilesrc')</div><div> self.decaudio = gst.element_factory_make('decodebin2', 'decodebinaudio')</div><div> self.audioconvert = gst.element_factory_make('audioconvert')</div><div> enc = gst.element_factory_make('voaacenc')</div><div> queue = gst.element_factory_make('queue')</div><div><br></div><div> self.audioconvertpad = self.audioconvert.get_pad('sink')</div><div><br></div><div> # Add to pipeline</div><div> self.pipeline.add(src, self.decaudio, self.audioconvert, queue, enc)</div><div><br></div><div> # Link</div><div> gst.element_link_many(src, self.decaudio)</div><div> gst.element_link_many(self.audioconvert, queue, enc)</div><div><br></div><div> # Change parameters</div><div> src.set_property('location', audio_path)</div><div> enc.set_property('bitrate', 384000)</div><div><br></div><div> # Add pads</div><div> self.decaudio.connect('new-decoded-pad', self.on_new_decoded_pad)</div><div> enc.get_pad("src").link(self.ch_audio_pad)</div><div> </div><div> # Create bus and connect handlers for EOS and error</div><div> self.decaudio.connect('drained', self.on_drained)</div><div><br></div><div> def video_pipeline(self, cover_path):</div><div> import gst</div><div> src = gst.element_factory_make('filesrc', 'videofilesrc')</div><div> self.decvideo = gst.element_factory_make('decodebin2', 'decodebinvideo')</div><div> ffmpegcs1 = gst.element_factory_make('ffmpegcolorspace', 'ffmpegcsp0')</div><div> videoscale = gst.element_factory_make('videoscale', 'videoscale0')</div><div> ffmpegcs2 = gst.element_factory_make('ffmpegcolorspace', 'ffmpegcsp1')</div><div> imagefreeze = gst.element_factory_make('imagefreeze')</div><div> capsfilter = gst.element_factory_make('capsfilter')</div><div> ffmpegcs3 = gst.element_factory_make('ffmpegcolorspace', 'ffmpegcsp2')</div><div> x264enc = gst.element_factory_make('x264enc')</div><div> queue = gst.element_factory_make('queue')</div><div><br></div><div> self.ffmpegpad = ffmpegcs1.get_pad('sink')</div><div><br></div><div> caps = gst.Caps('video/x-raw-yuv,width=1080,height=1080')</div><div> capsfilter.set_property("caps", caps)</div><div><br></div><div> # Add to pipeline</div><div> self.pipeline.add(src, self.decvideo, ffmpegcs1, imagefreeze, ffmpegcs2,</div><div> videoscale, capsfilter, ffmpegcs3, queue, x264enc)</div><div><br></div><div> # Link</div><div> gst.element_link_many(src, self.decvideo)</div><div> gst.element_link_many(ffmpegcs1, imagefreeze, ffmpegcs2,</div><div> videoscale, capsfilter, ffmpegcs3, queue, x264enc)</div><div><br></div><div> # Change parameters</div><div> src.set_property('location', cover_path)</div><div> x264enc.set_property('bframes', 2)</div><div> x264enc.set_property('profile', 'high')</div><div> x264enc.set_property('pass', 'pass1')</div><div><br></div><div> # Add pads</div><div> self.decvideo.connect('new-decoded-pad', self.on_new_decoded_pad)</div><div> x264enc.get_pad("src").link(self.ch_video_pad)</div><div><br></div><div> def on_eos(self, bus, msg):</div><div> import gst</div><div> print "EOS"</div><div> self.pipeline.set_state(gst.STATE_NULL)</div><div> self.mainloop.quit()</div><div><br></div><div> def on_drained(self, decodebin):</div><div> import gst</div><div> print "DRAINED"</div><div> self.mux.send_event(gst.event_new_eos())</div><div><br></div><div> def on_error(self, bus, msg):</div><div> error = msg.parse_error()</div><div> self.mainloop.quit()</div><div> raise IOError(u"GStreamer error: {}".format(error[1]))</div><div><br></div><div> def on_new_decoded_pad(self, element, pad, last):</div><div> print 'Decoded'</div><div> if element == self.decaudio:</div><div> apad = self.audioconvertpad</div><div> elif element == self.decvideo:</div><div> apad = self.ffmpegpad</div><div> else:</div><div> raise ValueError('Cannot connect this element: {0}'.format(element))</div><div> if not apad.is_linked(): # Only link once</div><div> pad.link(apad)</div></div><br clear="all"><div><div class="gmail_signature"><div dir="ltr"><div><div dir="ltr">F</div><div dir="ltr"><br></div></div></div></div></div>
</div>