* 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:
parent
afacdfdee2
commit
2bb2cb687e
5 changed files with 113 additions and 39 deletions
26
README
Normal file
26
README
Normal 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
33
bin/oxtimelines
Normal file → Executable 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()
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,6 +549,7 @@ 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
|
||||||
|
if self.render_small_tiles:
|
||||||
resize = (self.full_tile_w, self.small_tile_h)
|
resize = (self.full_tile_w, self.small_tile_h)
|
||||||
self.full_tile_image = self.full_tile_image.resize(resize, Image.ANTIALIAS)
|
self.full_tile_image = self.full_tile_image.resize(resize, Image.ANTIALIAS)
|
||||||
tile_file = '%stimelineantialias%dp.jpg' % (
|
tile_file = '%stimelineantialias%dp.jpg' % (
|
39
setup.py
Normal file
39
setup.py
Normal 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'
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in a new issue