Updated VideoThumbnailerMarkII, removed old

Removed the old VideoThumbnailer since it's not used anymore.

VideoThumbnailerMarkII:
Changed the state switching in on_thumbnail_message to only set the
state to "processing thumbnail" if the seek was succesful.

I'm not sure what I'm doing here, but I know at least some of it is
good, and as a whole, it seems to work, so far :)
This commit is contained in:
Joar Wandborg 2013-03-24 18:49:05 +01:00
parent 05eee632f8
commit b06ea4ab46

View File

@ -53,283 +53,6 @@ def pixbuf_to_pilbuf(buf):
return data
class VideoThumbnailer:
# Declaration of thumbnailer states
STATE_NULL = 0
STATE_HALTING = 1
STATE_PROCESSING = 2
# The current thumbnailer state
def __init__(self, source_path, dest_path):
'''
Set up playbin pipeline in order to get video properties.
Initializes and runs the gobject.MainLoop()
Abstract
- Set up a playbin with a fake audio sink and video sink. Load the video
into the playbin
- Initialize
'''
# This will contain the thumbnailing pipeline
self.state = self.STATE_NULL
self.thumbnail_pipeline = None
self.buffer_probes = {}
self.errors = []
self.source_path = source_path
self.dest_path = dest_path
self.loop = gobject.MainLoop()
# Set up the playbin. It will be used to discover certain
# properties of the input file
self.playbin = gst.element_factory_make('playbin')
self.videosink = gst.element_factory_make('fakesink', 'videosink')
self.playbin.set_property('video-sink', self.videosink)
self.audiosink = gst.element_factory_make('fakesink', 'audiosink')
self.playbin.set_property('audio-sink', self.audiosink)
self.bus = self.playbin.get_bus()
self.bus.add_signal_watch()
self.watch_id = self.bus.connect('message', self._on_bus_message)
self.playbin.set_property('uri', 'file:{0}'.format(
urllib.pathname2url(self.source_path)))
self.playbin.set_state(gst.STATE_PAUSED)
self.run()
def run(self):
self.loop.run()
def _on_bus_message(self, bus, message):
_log.debug(' thumbnail playbin: {0}'.format(message))
if message.type == gst.MESSAGE_ERROR:
_log.error('thumbnail playbin: {0}'.format(message))
gobject.idle_add(self._on_bus_error)
elif message.type == gst.MESSAGE_STATE_CHANGED:
# The pipeline state has changed
# Parse state changing data
_prev, state, _pending = message.parse_state_changed()
_log.debug('State changed: {0}'.format(state))
if state == gst.STATE_PAUSED:
if message.src == self.playbin:
gobject.idle_add(self._on_bus_paused)
def _on_bus_paused(self):
'''
Set up thumbnailing pipeline
'''
current_video = self.playbin.get_property('current-video')
if current_video == 0:
_log.debug('Found current video from playbin')
else:
_log.error('Could not get any current video from playbin!')
self.duration = self._get_duration(self.playbin)
_log.info('Video length: {0}'.format(self.duration / gst.SECOND))
_log.info('Setting up thumbnailing pipeline')
self.thumbnail_pipeline = gst.parse_launch(
'filesrc location="{0}" ! decodebin ! '
'ffmpegcolorspace ! videoscale ! '
'video/x-raw-rgb,depth=24,bpp=24,pixel-aspect-ratio=1/1,width=180 ! '
'fakesink signal-handoffs=True'.format(self.source_path))
self.thumbnail_bus = self.thumbnail_pipeline.get_bus()
self.thumbnail_bus.add_signal_watch()
self.thumbnail_watch_id = self.thumbnail_bus.connect(
'message', self._on_thumbnail_bus_message)
self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
#gobject.timeout_add(3000, self._on_timeout)
return False
def _on_thumbnail_bus_message(self, bus, message):
_log.debug('thumbnail: {0}'.format(message))
if message.type == gst.MESSAGE_ERROR:
_log.error(message)
gobject.idle_add(self._on_bus_error)
if message.type == gst.MESSAGE_STATE_CHANGED:
_log.debug('State changed')
_prev, state, _pending = message.parse_state_changed()
if (state == gst.STATE_PAUSED and
not self.state == self.STATE_PROCESSING and
message.src == self.thumbnail_pipeline):
_log.info('Pipeline paused, processing')
self.state = self.STATE_PROCESSING
for sink in self.thumbnail_pipeline.sinks():
name = sink.get_name()
factoryname = sink.get_factory().get_name()
if factoryname == 'fakesink':
sinkpad = sink.get_pad('sink')
self.buffer_probes[name] = sinkpad.add_buffer_probe(
self.buffer_probe_handler, name)
_log.info('Added buffer probe')
break
# Apply the wadsworth constant, fallback to 1 second
# TODO: Will break if video is shorter than 1 sec
seek_amount = max(self.duration / 100 * 30, 1 * gst.SECOND)
_log.debug('seek amount: {0}'.format(seek_amount))
seek_result = self.thumbnail_pipeline.seek(
1.0,
gst.FORMAT_TIME,
gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
gst.SEEK_TYPE_SET,
seek_amount,
gst.SEEK_TYPE_NONE,
0)
if not seek_result:
self.errors.append('COULD_NOT_SEEK')
_log.error('Couldn\'t seek! result: {0}'.format(
seek_result))
_log.info(message)
self.shutdown()
else:
_log.debug('Seek successful')
self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
else:
_log.debug('Won\'t seek: \t{0}\n\t{1}'.format(
self.state,
message.src))
def buffer_probe_handler_real(self, pad, buff, name):
'''
Capture buffers as gdk_pixbufs when told to.
'''
_log.info('Capturing frame')
try:
caps = buff.caps
if caps is None:
_log.error('No caps passed to buffer probe handler!')
self.shutdown()
return False
_log.debug('caps: {0}'.format(caps))
filters = caps[0]
width = filters["width"]
height = filters["height"]
im = Image.new('RGB', (width, height))
data = pixbuf_to_pilbuf(buff.data)
im.putdata(data)
im.save(self.dest_path)
_log.info('Saved thumbnail')
self.shutdown()
except gst.QueryError as e:
_log.error('QueryError: {0}'.format(e))
return False
def buffer_probe_handler(self, pad, buff, name):
'''
Proxy function for buffer_probe_handler_real
'''
_log.debug('Attaching real buffer handler to gobject idle event')
gobject.idle_add(
lambda: self.buffer_probe_handler_real(pad, buff, name))
return True
def _get_duration(self, pipeline, retries=0):
'''
Get the duration of a pipeline.
Retries 5 times.
'''
if retries == 5:
return 0
try:
return pipeline.query_duration(gst.FORMAT_TIME)[0]
except gst.QueryError:
return self._get_duration(pipeline, retries + 1)
def _on_timeout(self):
_log.error('Timeout in thumbnailer!')
self.shutdown()
def _on_bus_error(self, *args):
_log.error('AHAHAHA! Error! args: {0}'.format(args))
def shutdown(self):
'''
Tell gobject to call __halt when the mainloop is idle.
'''
_log.info('Shutting down')
self.__halt()
def __halt(self):
'''
Halt all pipelines and shut down the main loop
'''
_log.info('Halting...')
self.state = self.STATE_HALTING
self.__disconnect()
gobject.idle_add(self.__halt_final)
def __disconnect(self):
_log.debug('Disconnecting...')
if not self.playbin is None:
self.playbin.set_state(gst.STATE_NULL)
for sink in self.playbin.sinks():
name = sink.get_name()
factoryname = sink.get_factory().get_name()
_log.debug('Disconnecting {0}'.format(name))
if factoryname == "fakesink":
pad = sink.get_pad("sink")
pad.remove_buffer_probe(self.buffer_probes[name])
del self.buffer_probes[name]
self.playbin = None
if self.bus is not None:
self.bus.disconnect(self.watch_id)
self.bus = None
def __halt_final(self):
_log.info('Done')
if self.errors:
_log.error(','.join(self.errors))
self.loop.quit()
class VideoThumbnailerMarkII(object):
'''
Creates a thumbnail from a video file. Rewrite of VideoThumbnailer.
@ -398,8 +121,8 @@ class VideoThumbnailerMarkII(object):
self.run()
except Exception as exc:
_log.critical(
'Exception "{0}" caught, disconnecting and re-raising'\
.format(exc))
'Exception "{0}" caught, shutting down mainloop and re-raising'\
.format(exc))
self.disconnect()
raise
@ -410,7 +133,8 @@ class VideoThumbnailerMarkII(object):
self.mainloop.run()
def on_playbin_message(self, message_bus, message):
_log.debug('playbin message: {0}'.format(message))
# Silenced to prevent clobbering of output
#_log.debug('playbin message: {0}'.format(message))
if message.type == gst.MESSAGE_ERROR:
_log.error('playbin error: {0}'.format(message))
@ -433,17 +157,20 @@ pending: {2}'.format(
def on_playbin_paused(self):
if self.has_reached_playbin_pause:
_log.warn('Has already reached logic for playbin pause. Aborting \
_log.warn('Has already reached on_playbin_paused. Aborting \
without doing anything this time.')
return False
self.has_reached_playbin_pause = True
# XXX: Why is this even needed at this point?
current_video = self.playbin.get_property('current-video')
if not current_video:
_log.critical('thumbnail could not get any video data \
_log.critical('Could not get any video data \
from playbin')
else:
_log.info('Got video data from playbin')
self.duration = self.get_duration(self.playbin)
self.permission_to_take_picture = True
@ -474,7 +201,8 @@ from playbin')
return False
def on_thumbnail_message(self, message_bus, message):
_log.debug('thumbnail message: {0}'.format(message))
# This is silenced to prevent clobbering of the terminal window
#_log.debug('thumbnail message: {0}'.format(message))
if message.type == gst.MESSAGE_ERROR:
_log.error('thumbnail error: {0}'.format(message.parse_error()))
@ -490,29 +218,10 @@ pending: {2}'.format(
cur_state,
pending_state))
if cur_state == gst.STATE_PAUSED and\
not self.state == self.STATE_PROCESSING_THUMBNAIL:
self.state = self.STATE_PROCESSING_THUMBNAIL
if cur_state == gst.STATE_PAUSED and \
not self.state == self.STATE_PROCESSING_THUMBNAIL:
# Find the fakesink sink pad and attach the on_buffer_probe
# handler to it.
for sink in self.thumbnail_pipeline.sinks():
sink_name = sink.get_name()
sink_factory_name = sink.get_factory().get_name()
if sink_factory_name == 'fakesink':
sink_pad = sink.get_pad('sink')
self.buffer_probes[sink_name] = sink_pad\
.add_buffer_probe(
self.on_pad_buffer_probe,
sink_name)
_log.info('Attached buffer probes: {0}'.format(
self.buffer_probes))
break
seek_amount = self.position_callback(self.duration, gst)
seek_result = self.thumbnail_pipeline.seek(
@ -525,10 +234,30 @@ pending: {2}'.format(
0)
if not seek_result:
_log.critical('Could not seek.')
_log.info('Could not seek.')
else:
_log.info('Seek successful, attaching buffer probe')
self.state = self.STATE_PROCESSING_THUMBNAIL
for sink in self.thumbnail_pipeline.sinks():
sink_name = sink.get_name()
sink_factory_name = sink.get_factory().get_name()
if sink_factory_name == 'fakesink':
sink_pad = sink.get_pad('sink')
self.buffer_probes[sink_name] = sink_pad\
.add_buffer_probe(
self.on_pad_buffer_probe,
sink_name)
_log.info('Attached buffer probes: {0}'.format(
self.buffer_probes))
break
elif self.state == self.STATE_PROCESSING_THUMBNAIL:
_log.debug('Already processing thumbnail')
_log.info('Already processing thumbnail')
def on_pad_buffer_probe(self, *args):
_log.debug('buffer probe handler: {0}'.format(args))
@ -649,7 +378,7 @@ pending: {2}'.format(
return self.get_duration(pipeline, attempt + 1)
class VideoTranscoder:
class VideoTranscoder(object):
'''
Video transcoder
@ -1011,6 +740,10 @@ if __name__ == '__main__':
action='store_true',
help='Dear program, please be quiet unless *error*')
parser.add_option('-w', '--width',
type=int,
default=180)
(options, args) = parser.parse_args()
if options.verbose:
@ -1030,6 +763,7 @@ if __name__ == '__main__':
transcoder = VideoTranscoder()
if options.action == 'thumbnail':
args.append(options.width)
VideoThumbnailerMarkII(*args)
elif options.action == 'video':
def cb(data):