* 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 ox
import oxtimelines import oxtimelines
from oxtimelines import video
''' # fixme: -w option should be 'keyframeswide' mode
This program takes one or more video files as input and outputs timeline images.
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 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. 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 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' will be rendered. They can be used at a later point to render small 'keyframes'
tiles without having to decode the video again. tiles without having to decode the video again.
'''
# fixme: -w option should be 'keyframeswide' mode usage: %prog [options] video1 video2'''
parser = OptionParser(usage=usage)
if __name__ == '__main__': parser.add_option('-o', '--output', dest='tiles', help='path for combined timeline tiles')
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')
parser.add_option('-c', '--cuts', dest='cuts', help='path for combined cuts json file') 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('-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('-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('-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') parser.add_option('-l', '--log', dest='log', default=False, action='store_true', help='log performance')
(opts, args) = parser.parse_args() (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() parser.print_help()
sys.exit() sys.exit()
opts.videos = map(lambda x: os.path.abspath(x), opts.videos.split(',')) opts.videos = map(os.path.abspath, args)
if opts.points: if opts.points:
opts.points = map(float, opts.points.split(',')) 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(',')) opts.sizes = map(int, opts.sizes.split(','))
''' oxtimelines.Timelines(opts.videos, opts.tiles, opts.cuts, opts.points, opts.modes, opts.sizes, opts.wide, opts.log).render()
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()

View file

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

View file

@ -4,9 +4,9 @@
from __future__ import division from __future__ import division
from glob import glob
import Image import Image
import math import math
import numpy
import os import os
from time import time, strftime from time import time, strftime
@ -14,7 +14,7 @@ import gobject
import gst import gst
from imagesink import ImageSink from imagesink import ImageSink
from ox import avinfo import ox
FPS = 25 FPS = 25
@ -44,7 +44,7 @@ class Video(gst.Pipeline):
self.add(self.src, self.sbin) self.add(self.src, self.sbin)
if self.video: if self.video:
info = avinfo(uri) info = ox.avinfo(uri)
ratio = info['video'][0]['width'] / info['video'][0]['height'] ratio = info['video'][0]['width'] / info['video'][0]['height']
self.width = int(round(self.height * ratio)) self.width = int(round(self.height * ratio))
if self.width % 4: if self.width % 4:
@ -234,6 +234,7 @@ class Timelines():
self.large_tile_w = 1500 self.large_tile_w = 1500
self.large_tile_h = sizes[0] self.large_tile_h = sizes[0]
self.large_tile_image = {} self.large_tile_image = {}
self.render_small_tiles = False
if len(sizes) == 2: if len(sizes) == 2:
self.small_tile_w = 3600 self.small_tile_w = 3600
self.small_tile_h = sizes[1] self.small_tile_h = sizes[1]
@ -250,6 +251,8 @@ class Timelines():
self.profiler = Profiler() self.profiler = Profiler()
self.profiler.set_task('gst') self.profiler.set_task('gst')
ox.makedirs(self.tile_path)
def render(self): def render(self):
if self.points: if self.points:
@ -315,6 +318,17 @@ class Timelines():
self.frame_offset = 0 self.frame_offset = 0
self.videos[0].decode(self.file_points[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): def _video_callback(self, frame_image, timestamp):
self.log and self.profiler.set_task('_video_callback()') self.log and self.profiler.set_task('_video_callback()')
@ -426,7 +440,7 @@ class Timelines():
large_keyframes_tile_i = self.large_keyframes_tile_i large_keyframes_tile_i = self.large_keyframes_tile_i
for image_w in image_widths: for image_w in image_widths:
frame_image = self.cut_frames[image_i - self.cuts[-2]] frame_image = self.cut_frames[image_i - self.cuts[-2]]
frame_image.save('deleteme.jpg') #frame_image.save('deleteme.jpg')
if mode == 'keyframeswide': if mode == 'keyframeswide':
resize = (self.wide_frame_w, self.large_tile_h) resize = (self.wide_frame_w, self.large_tile_h)
self.log and self.profiler.set_task('i,resize((w, h)) # keyframeswide timelines') self.log and self.profiler.set_task('i,resize((w, h)) # keyframeswide timelines')
@ -535,14 +549,15 @@ class Timelines():
self.full_tile_image.save(tile_file) self.full_tile_image.save(tile_file)
if self.log: if self.log:
print tile_file print tile_file
resize = (self.full_tile_w, self.small_tile_h) if self.render_small_tiles:
self.full_tile_image = self.full_tile_image.resize(resize, Image.ANTIALIAS) resize = (self.full_tile_w, self.small_tile_h)
tile_file = '%stimelineantialias%dp.jpg' % ( self.full_tile_image = self.full_tile_image.resize(resize, Image.ANTIALIAS)
self.tile_path, self.small_tile_h tile_file = '%stimelineantialias%dp.jpg' % (
) self.tile_path, self.small_tile_h
self.full_tile_image.save(tile_file) )
if self.log: self.full_tile_image.save(tile_file)
print tile_file if self.log:
print tile_file
self.log and self.profiler.unset_task() self.log and self.profiler.unset_task()
def _save_tile(self, mode, index): 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'
],
)