* add install script

* tune commandline interface
* remove tiles that might be left over from previous run
* general cleanup and fixes
This commit is contained in:
j 2012-05-16 12:08:35 +02:00
parent afacdfdee2
commit 2bb2cb687e
5 changed files with 113 additions and 39 deletions

26
README Normal file
View File

@ -0,0 +1,26 @@
oxtimelines - create timeline from video
This program takes one or more video files as input and outputs timeline images.
If a cuts path is given, it also outputs a json file containing cuts. If in and
out points are given, only that part of the video(s) will be rendered.
The timeline modes can be any combination of 'antialias' (average color),
'slitscan' (center pixel), 'keyframes' (one or more frames per cut), 'audio'
(waveform), 'cuts' (antialias with cut detection overlay, for debugging) and
'data' (each frame resized to 8x8 px).
One or two timeline heights can be specified, larger height first. The timeline
widths will be 1 px per frame for the first one, and 1 px per second for the
second (smaller) one. If the wide option is set, large 'keyframeswide' tiles
will be rendered. They can be used at a later point to render small 'keyframes'
tiles without having to decode the video again.
depends on
gstreamer 0.10.30 or newer
python-imaging
gst-python
python-ox
on ubuntu 10.04 you need
sudo add-apt-repository ppa:gstreamer-developers/ppa

33
bin/oxtimelines Normal file → Executable file
View File

@ -16,10 +16,12 @@ if os.path.exists(os.path.join(root, 'oxtimelines')):
import ox
import oxtimelines
from oxtimelines import video
'''
This program takes one or more video files as input and outputs timeline images.
# fixme: -w option should be 'keyframeswide' mode
if __name__ == '__main__':
usage = '''
%prog takes one or more video files as input and outputs timeline images.
If a cuts path is given, it also outputs a json file containing cuts. If in and
out points are given, only that part of the video(s) will be rendered.
@ -33,35 +35,26 @@ widths will be 1 px per frame for the first one, and 1 px per second for the
second (smaller) one. If the wide option is set, large 'keyframeswide' tiles
will be rendered. They can be used at a later point to render small 'keyframes'
tiles without having to decode the video again.
'''
# fixme: -w option should be 'keyframeswide' mode
if __name__ == '__main__':
parser = OptionParser()
parser.add_option('-v', '--videos', dest='videos', help='video file(s)')
parser.add_option('-t', '--tiles', dest='tiles', help='path for combined timeline tiles')
usage: %prog [options] video1 video2'''
parser = OptionParser(usage=usage)
parser.add_option('-o', '--output', dest='tiles', help='path for combined timeline tiles')
parser.add_option('-c', '--cuts', dest='cuts', help='path for combined cuts json file')
parser.add_option('-p', '--points', dest='points', help='inpoint,outpoint (optional)')
parser.add_option('-m', '--modes', dest='modes', help='timeline mode(s) (antialias, slitscan, keyframes, audio, cuts, data)')
parser.add_option('-s', '--sizes', dest='sizes', help='timeline size(s) (large or large,small)')
parser.add_option('-s', '--sizes', dest='sizes', help='timeline size(s) (64 or 64,16)')
parser.add_option('-w', '--wide', dest='wide', default=False, action='store_true', help='keep wide frames tiles')
parser.add_option('-l', '--log', dest='log', default=False, action='store_true', help='log performance')
(opts, args) = parser.parse_args()
if None in (opts.videos, opts.modes, opts.sizes):
if None in (opts.modes, opts.sizes, opts.tiles) or not args:
parser.print_help()
sys.exit()
opts.videos = map(lambda x: os.path.abspath(x), opts.videos.split(','))
opts.videos = map(os.path.abspath, args)
if opts.points:
opts.points = map(float, opts.points.split(','))
opts.modes = opts.modes.split(',')
opts.modes = [m.strip() for m in opts.modes.split(',')]
opts.sizes = map(int, opts.sizes.split(','))
'''
for f in glob('%s*.png' % opts.tiles):
os.unlink(f)
'''
video.Timelines(opts.videos, opts.tiles, opts.cuts, opts.points, opts.modes, opts.sizes, opts.wide, opts.log).render()
oxtimelines.Timelines(opts.videos, opts.tiles, opts.cuts, opts.points, opts.modes, opts.sizes, opts.wide, opts.log).render()

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
# GPL 2008-2010
__version__ = 'bzr'
import gobject
gobject.threads_init()
@ -15,5 +16,5 @@ pygst.require("0.10")
import gst
import Image
import video
#import audio
import timeline
from timeline import Timelines

View File

@ -4,9 +4,9 @@
from __future__ import division
from glob import glob
import Image
import math
import numpy
import os
from time import time, strftime
@ -14,7 +14,7 @@ import gobject
import gst
from imagesink import ImageSink
from ox import avinfo
import ox
FPS = 25
@ -44,11 +44,11 @@ class Video(gst.Pipeline):
self.add(self.src, self.sbin)
if self.video:
info = avinfo(uri)
info = ox.avinfo(uri)
ratio = info['video'][0]['width'] / info['video'][0]['height']
self.width = int(round(self.height * ratio))
if self.width % 4:
self.width += 4 - self.width % 4
self.width += 4 - self.width % 4
self.vqueue = gst.element_factory_make('queue')
self.scale = gst.element_factory_make('videoscale')
self.rate = gst.element_factory_make('videorate')
@ -234,11 +234,12 @@ class Timelines():
self.large_tile_w = 1500
self.large_tile_h = sizes[0]
self.large_tile_image = {}
self.render_small_tiles = False
if len(sizes) == 2:
self.small_tile_w = 3600
self.small_tile_h = sizes[1]
self.small_tile_image = {}
self.render_small_tiles = True
self.render_small_tiles = True
self.render_wide_tiles = render_wide_tiles
@ -250,6 +251,8 @@ class Timelines():
self.profiler = Profiler()
self.profiler.set_task('gst')
ox.makedirs(self.tile_path)
def render(self):
if self.points:
@ -297,7 +300,7 @@ class Timelines():
self.frame_ratio = frame_size[0] / frame_size[1]
self.frame_center = int(frame_size[0] / 2)
self.large_tile_n = int(math.ceil(self.frame_n / self.large_tile_w))
self.large_tile_n = int(math.ceil(self.frame_n / self.large_tile_w))
self.large_tile_last_w = self.frame_n % self.large_tile_w
if self.render_small_tiles:
self.small_tile_n = int(math.ceil(self.duration / self.small_tile_w))
@ -315,6 +318,17 @@ class Timelines():
self.frame_offset = 0
self.videos[0].decode(self.file_points[0])
#remove tiles that might exist from previous run
if not self.points:
for mode in self.modes:
tiles = glob('%s/timeline%s*%d*.jpg' % (self.tile_path, mode, self.large_tile_h))
for f in ox.sorted_strings(tiles)[self.large_tile_i+2:]:
os.unlink(f)
if self.render_small_tiles:
tiles = glob('%s/timeline%s*%d*.jpg' % (self.tile_path, mode, self.small_tile_h))
for f in ox.sorted_strings(tiles)[self.small_tile_i+2:]:
os.unlink(f)
def _video_callback(self, frame_image, timestamp):
self.log and self.profiler.set_task('_video_callback()')
@ -351,7 +365,7 @@ class Timelines():
if self.render_slitscan:
crop = (self.frame_center, 0, self.frame_center + 1, self.large_tile_h)
self.large_tile_image['slitscan'].paste(frame_image.crop(crop), paste)
self.log and self.profiler.unset_task()
self.log and self.profiler.unset_task()
# render data tile
if self.render_data or self.detect_cuts:
@ -426,7 +440,7 @@ class Timelines():
large_keyframes_tile_i = self.large_keyframes_tile_i
for image_w in image_widths:
frame_image = self.cut_frames[image_i - self.cuts[-2]]
frame_image.save('deleteme.jpg')
#frame_image.save('deleteme.jpg')
if mode == 'keyframeswide':
resize = (self.wide_frame_w, self.large_tile_h)
self.log and self.profiler.set_task('i,resize((w, h)) # keyframeswide timelines')
@ -468,7 +482,7 @@ class Timelines():
if large_tile_x == 0:
large_tile_i = int(self.frame_i / self.large_tile_w)
if large_tile_i < self.large_tile_n - 1:
w = self.large_tile_w
w = self.large_tile_w
else:
w = self.large_tile_last_w
self.large_tile_image['audio'] = Image.new('L', (w, self.large_tile_h))
@ -535,14 +549,15 @@ class Timelines():
self.full_tile_image.save(tile_file)
if self.log:
print tile_file
resize = (self.full_tile_w, self.small_tile_h)
self.full_tile_image = self.full_tile_image.resize(resize, Image.ANTIALIAS)
tile_file = '%stimelineantialias%dp.jpg' % (
self.tile_path, self.small_tile_h
)
self.full_tile_image.save(tile_file)
if self.log:
print tile_file
if self.render_small_tiles:
resize = (self.full_tile_w, self.small_tile_h)
self.full_tile_image = self.full_tile_image.resize(resize, Image.ANTIALIAS)
tile_file = '%stimelineantialias%dp.jpg' % (
self.tile_path, self.small_tile_h
)
self.full_tile_image.save(tile_file)
if self.log:
print tile_file
self.log and self.profiler.unset_task()
def _save_tile(self, mode, index):

39
setup.py Normal file
View File

@ -0,0 +1,39 @@
# setup.py
# -*- coding: UTF-8 -*-
# vi:si:et:sw=4:sts=4:ts=4
try:
from setuptools import setup
except:
from distutils.core import setup
def get_bzr_version():
import os
info = os.path.join(os.path.dirname(__file__), '.bzr/branch/last-revision')
if os.path.exists(info):
f = open(info)
rev = int(f.read().split()[0])
f.close()
if rev:
return u'%s' % rev
return u'unknown'
setup(name='oxtimelines',
version='0.%s' % get_bzr_version() ,
scripts=[
'bin/oxtimelines',
],
packages=[
'oxtimelines',
],
author='0x2620',
author_email='0x2620@0x2620.org',
description='extract timelines from videos',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Utilities'
],
)