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:
parent
05eee632f8
commit
b06ea4ab46
@ -53,283 +53,6 @@ def pixbuf_to_pilbuf(buf):
|
|||||||
return data
|
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):
|
class VideoThumbnailerMarkII(object):
|
||||||
'''
|
'''
|
||||||
Creates a thumbnail from a video file. Rewrite of VideoThumbnailer.
|
Creates a thumbnail from a video file. Rewrite of VideoThumbnailer.
|
||||||
@ -398,8 +121,8 @@ class VideoThumbnailerMarkII(object):
|
|||||||
self.run()
|
self.run()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_log.critical(
|
_log.critical(
|
||||||
'Exception "{0}" caught, disconnecting and re-raising'\
|
'Exception "{0}" caught, shutting down mainloop and re-raising'\
|
||||||
.format(exc))
|
.format(exc))
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@ -410,7 +133,8 @@ class VideoThumbnailerMarkII(object):
|
|||||||
self.mainloop.run()
|
self.mainloop.run()
|
||||||
|
|
||||||
def on_playbin_message(self, message_bus, message):
|
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:
|
if message.type == gst.MESSAGE_ERROR:
|
||||||
_log.error('playbin error: {0}'.format(message))
|
_log.error('playbin error: {0}'.format(message))
|
||||||
@ -433,17 +157,20 @@ pending: {2}'.format(
|
|||||||
|
|
||||||
def on_playbin_paused(self):
|
def on_playbin_paused(self):
|
||||||
if self.has_reached_playbin_pause:
|
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.')
|
without doing anything this time.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.has_reached_playbin_pause = True
|
self.has_reached_playbin_pause = True
|
||||||
|
|
||||||
|
# XXX: Why is this even needed at this point?
|
||||||
current_video = self.playbin.get_property('current-video')
|
current_video = self.playbin.get_property('current-video')
|
||||||
|
|
||||||
if not 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')
|
from playbin')
|
||||||
|
else:
|
||||||
|
_log.info('Got video data from playbin')
|
||||||
|
|
||||||
self.duration = self.get_duration(self.playbin)
|
self.duration = self.get_duration(self.playbin)
|
||||||
self.permission_to_take_picture = True
|
self.permission_to_take_picture = True
|
||||||
@ -474,7 +201,8 @@ from playbin')
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def on_thumbnail_message(self, message_bus, message):
|
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:
|
if message.type == gst.MESSAGE_ERROR:
|
||||||
_log.error('thumbnail error: {0}'.format(message.parse_error()))
|
_log.error('thumbnail error: {0}'.format(message.parse_error()))
|
||||||
@ -490,29 +218,10 @@ pending: {2}'.format(
|
|||||||
cur_state,
|
cur_state,
|
||||||
pending_state))
|
pending_state))
|
||||||
|
|
||||||
if cur_state == gst.STATE_PAUSED and\
|
if cur_state == gst.STATE_PAUSED and \
|
||||||
not self.state == self.STATE_PROCESSING_THUMBNAIL:
|
not self.state == self.STATE_PROCESSING_THUMBNAIL:
|
||||||
self.state = self.STATE_PROCESSING_THUMBNAIL
|
|
||||||
|
|
||||||
# Find the fakesink sink pad and attach the on_buffer_probe
|
# Find the fakesink sink pad and attach the on_buffer_probe
|
||||||
# handler to it.
|
# 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_amount = self.position_callback(self.duration, gst)
|
||||||
|
|
||||||
seek_result = self.thumbnail_pipeline.seek(
|
seek_result = self.thumbnail_pipeline.seek(
|
||||||
@ -525,10 +234,30 @@ pending: {2}'.format(
|
|||||||
0)
|
0)
|
||||||
|
|
||||||
if not seek_result:
|
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:
|
elif self.state == self.STATE_PROCESSING_THUMBNAIL:
|
||||||
_log.debug('Already processing thumbnail')
|
_log.info('Already processing thumbnail')
|
||||||
|
|
||||||
def on_pad_buffer_probe(self, *args):
|
def on_pad_buffer_probe(self, *args):
|
||||||
_log.debug('buffer probe handler: {0}'.format(args))
|
_log.debug('buffer probe handler: {0}'.format(args))
|
||||||
@ -649,7 +378,7 @@ pending: {2}'.format(
|
|||||||
return self.get_duration(pipeline, attempt + 1)
|
return self.get_duration(pipeline, attempt + 1)
|
||||||
|
|
||||||
|
|
||||||
class VideoTranscoder:
|
class VideoTranscoder(object):
|
||||||
'''
|
'''
|
||||||
Video transcoder
|
Video transcoder
|
||||||
|
|
||||||
@ -1011,6 +740,10 @@ if __name__ == '__main__':
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help='Dear program, please be quiet unless *error*')
|
help='Dear program, please be quiet unless *error*')
|
||||||
|
|
||||||
|
parser.add_option('-w', '--width',
|
||||||
|
type=int,
|
||||||
|
default=180)
|
||||||
|
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
|
|
||||||
if options.verbose:
|
if options.verbose:
|
||||||
@ -1030,6 +763,7 @@ if __name__ == '__main__':
|
|||||||
transcoder = VideoTranscoder()
|
transcoder = VideoTranscoder()
|
||||||
|
|
||||||
if options.action == 'thumbnail':
|
if options.action == 'thumbnail':
|
||||||
|
args.append(options.width)
|
||||||
VideoThumbnailerMarkII(*args)
|
VideoThumbnailerMarkII(*args)
|
||||||
elif options.action == 'video':
|
elif options.action == 'video':
|
||||||
def cb(data):
|
def cb(data):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user