def _get_duration(self): """Get duration of the video. Returns ------- duration : float Duration in seconds. ``None`` if duration is not available. duration_text : str Duration as a human readable string, e.g., ``'00:02:53.33'``. ``None`` if duration is not available. """ self.__dp("entered StoryBoard._get_duration") if 'duration' in self._ffprobe['format']: duration = float(self._ffprobe['format']['duration']) duration_text = util.humantime(duration) self.__dp("left StoryBoard._get_duration") return (duration, duration_text) else: self.__dp("left StoryBoard._get_duration") return (None, None)
def test_video(self): vid = Video(self.videofile, params={ 'ffprobe_bin': self.ffprobe_bin, 'print_progress': False, }) self.assertEqual(vid.path, self.videofile) self.assertEqual(vid.filename, os.path.basename(self.videofile)) self.assertIsNone(vid.title) self.assertIsInstance(vid.size, int) self.assertEqual(humansize(vid.size), vid.size_text) self.assertEqual(vid.format, 'Matroska') self.assertLess(abs(vid.duration - 10.0), 1.0) self.assertEqual(humantime(vid.duration), vid.duration_text) self.assertEqual(vid.dimension, (320, 180)) self.assertEqual(vid.dimension_text, '320x180') self.assertAlmostEqual(vid.dar, 16 / 9) self.assertEqual(vid.dar_text, '16:9') self.assertEqual(vid.scan_type, 'Progressive scan') self.assertAlmostEqual(vid.frame_rate, 25.0) self.assertEqual(vid.frame_rate_text, '25 fps') self.assertIsInstance(vid.streams, list) self.assertEqual(len(vid.streams), 3) # sha1sum sha1sum = vid.compute_sha1sum() self.assertEqual(vid.sha1sum, sha1sum) self.assertEqual(len(sha1sum), 40) # video stream vstream = vid.streams[0] self.assertIsInstance(vstream, Stream) self.assertIsNotNone(vstream.codec) self.assertAlmostEqual(vstream.dar, 16 / 9) self.assertEqual(vstream.dar_text, '16:9') self.assertEqual(vstream.dimension, (320, 180)) self.assertEqual(vstream.dimension_text, '320x180') self.assertAlmostEqual(vstream.frame_rate, 25.0) self.assertEqual(vstream.frame_rate_text, '25 fps') self.assertEqual(vstream.height, 180) self.assertEqual(vstream.index, 0) self.assertIsNotNone(vstream.info_string) self.assertIsNone(vstream.language_code) self.assertEqual(vstream.type, 'video') self.assertEqual(vstream.width, 320) # audio stream astream = vid.streams[1] self.assertIsNotNone(astream.codec) self.assertEqual(astream.index, 1) self.assertIsNone(astream.language_code) self.assertEqual(astream.type, 'audio') # subtitle stream sstream = vid.streams[2] self.assertIsNotNone(sstream.codec) self.assertEqual(sstream.index, 2) self.assertIsNone(sstream.language_code) self.assertEqual(sstream.type, 'subtitle') print('') print(vid.format_metadata(params={'include_sha1sum': True})) # specifically test the video_duration option vid = Video(self.videofile, params={ 'ffprobe_bin': self.ffprobe_bin, 'video_duration': 10.0, 'print_progress': False, }) self.assertAlmostEqual(vid.duration, 10.0) self.assertEqual(humantime(vid.duration), vid.duration_text)
def __init__(self, video, params=None): """Initialize the Video class. See class docstring for parameters of the constructor. """ if params is None: params = {} if 'debug' in params and params['debug']: self.__debug = True self.__dp("entered StoryBoard.__init__") if 'ffprobe_bin' in params: ffprobe_bin = params['ffprobe_bin'] else: _, ffprobe_bin = fflocate.guess_bins() video_duration = _read_param(params, 'video_duration', None) print_progress = _read_param(params, 'print_progress', False) self.path = os.path.abspath(video) if not os.path.exists(self.path): raise OSError("'" + video + "' does not exist") self.filename = os.path.basename(self.path) if hasattr(self.filename, 'decode'): # python2 str, need to be decoded to unicode for proper # printing self.filename = self.filename.decode('utf-8') if print_progress: sys.stderr.write("Processing %s\n" % self.filename) sys.stderr.write("Crunching metadata...\n") self._call_ffprobe(ffprobe_bin) self.title = self._get_title() self.format = self._get_format() self.size, self.size_text = self._get_size() if video_duration is None: self.duration, self.duration_text = self._get_duration() else: self.duration = video_duration self.duration_text = util.humantime(video_duration) self.sha1sum = None # SHA-1 digest is generated upon request # the remaining attributes will be dynamically set when parsing # streams self.dimension = None self.dimension_text = None self.frame_rate = None self.frame_rate_text = None self.dar = None self.dar_text = None self._process_streams() # detect if the file contains any video streams at all and try # to extract scan type only if it does for stream in self.streams: if stream.type == 'video': break else: # no video stream self.scan_type = None self.__dp("left StoryBoard.__init__") return self.scan_type = self._get_scan_type(ffprobe_bin, print_progress) self.__dp("left StoryBoard.__init__")
def create_thumbnail(frame, width, params=None): """Create thumbnail of a video frame. The timestamp of the frame can be overlayed to the thumbnail. See "Other Parameters" to how to enable this feature and relevant options. Parameters ---------- frame : storyboard.frame.Frame The video frame to be thumbnailed. width : int Width of the thumbnail. params : dict, optional Optional parameters enclosed in a dict. Default is ``None``. See the "Other Parameters" section for understood key/value pairs. Returns ------- thumbnail : PIL.Image.Image Other Parameters ---------------- aspect_ratio : float, optional Aspect ratio of the thumbnail. If ``None``, use the aspect ratio (only considering the pixel dimensions) of the frame. Default is ``None``. draw_timestamp : bool, optional Whether to draw frame timestamp over the timestamp. Default is ``False``. timestamp_font : Font, optional Font for the timestamp, if `draw_timestamp` is ``True``. Default is the font constructed by ``Font()`` without arguments. timestamp_align : {'right', 'center', 'left'}, optional Horizontal alignment of the timestamp over the thumbnail, if `draw_timestamp` is ``True``. Default is ``'right'``. Note that the timestamp is always vertically aligned towards the bottom of the thumbnail. """ if params is None: params = {} if 'aspect_ratio' in params: aspect_ratio = params['aspect_ratio'] else: image_width, image_height = frame.image.size aspect_ratio = image_width / image_height height = int(round(width / aspect_ratio)) size = (width, height) draw_timestamp = _read_param(params, 'draw_timestamp', False) if draw_timestamp: timestamp_font = _read_param(params, 'timestamp_font', Font()) timestamp_align = _read_param(params, 'timestamp_align', 'right') thumbnail = frame.image.resize(size, Image.LANCZOS) if draw_timestamp: draw = ImageDraw.Draw(thumbnail) timestamp_text = util.humantime(frame.timestamp, ndigits=0) timestamp_width, timestamp_height = \ draw.textsize(timestamp_text, timestamp_font.obj) # calculate upperleft corner of the timestamp overlay # we hard code a margin of 5 pixels timestamp_y = height - 5 - timestamp_height if timestamp_align == 'right': timestamp_x = width - 5 - timestamp_width elif timestamp_align == 'left': timestamp_x = 5 elif timestamp_align == 'center': timestamp_x = int((width - timestamp_width) / 2) else: raise ValueError("timestamp alignment option '%s' not recognized" % timestamp_align) # draw white timestamp with 1px thick black border for x_offset in range(-1, 2): for y_offset in range(-1, 2): draw.text((timestamp_x + x_offset, timestamp_y + y_offset), timestamp_text, fill='black', font=timestamp_font.obj) draw.text((timestamp_x, timestamp_y), timestamp_text, fill='white', font=timestamp_font.obj) return thumbnail