initial commit
This commit is contained in:
commit
2aad0b7cac
2 changed files with 368 additions and 0 deletions
110
hash.py
Normal file
110
hash.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
from __future__ import division
|
||||||
|
import Image
|
||||||
|
import os
|
||||||
|
|
||||||
|
'''
|
||||||
|
This shows how to use the data timelines to implement a "Find Similar Clips"
|
||||||
|
feature, i.e. how to compute sequences and their hashes. There are two modes:
|
||||||
|
similar shapes and similar colors.
|
||||||
|
'''
|
||||||
|
|
||||||
|
ZONE_INDEX = []
|
||||||
|
for pixel_index in range(64):
|
||||||
|
x, y = pixel_index % 8, int(pixel_index / 8)
|
||||||
|
ZONE_INDEX.append(int(x / 2) + int(y / 4) * 4)
|
||||||
|
|
||||||
|
def get_hash(image, mode, debug=False):
|
||||||
|
if mode == 'color':
|
||||||
|
# divide the image into 8 zones:
|
||||||
|
# 0 0 1 1 2 2 3 3
|
||||||
|
# 0 0 1 1 2 2 3 3
|
||||||
|
# 0 0 1 1 2 2 3 3
|
||||||
|
# 0 0 1 1 2 2 3 3
|
||||||
|
# 4 4 5 5 6 6 7 7
|
||||||
|
# 4 4 5 5 6 6 7 7
|
||||||
|
# 4 4 5 5 6 6 7 7
|
||||||
|
# 4 4 5 5 6 6 7 7
|
||||||
|
image_data = image.getdata()
|
||||||
|
image_hash = 0
|
||||||
|
zone_values = []
|
||||||
|
for zone_index in range(8):
|
||||||
|
zone_values.append([])
|
||||||
|
for pixel_index, pixel_value in enumerate(image_data):
|
||||||
|
zone_values[ZONE_INDEX[pixel_index]].append(pixel_value)
|
||||||
|
for zone_index, pixel_values in enumerate(zone_values):
|
||||||
|
# get the mean for each color channel
|
||||||
|
mean = map(lambda x: int(round(sum(x) / 8)), zip(*pixel_values))
|
||||||
|
# store the mean color of each zone as an 8-bit value:
|
||||||
|
# RRRGGGBB
|
||||||
|
color_index = sum((
|
||||||
|
int(mean[0] / 32) << 5,
|
||||||
|
int(mean[1] / 32) << 2,
|
||||||
|
int(mean[2] / 64)
|
||||||
|
))
|
||||||
|
image_hash += color_index * pow(2, zone_index * 8)
|
||||||
|
elif mode == 'shape':
|
||||||
|
image_data = image.convert('L').getdata()
|
||||||
|
image_mean = sum(image_data) / 64
|
||||||
|
image_hash = 0
|
||||||
|
for pixel_index, pixel_value in enumerate(image_data):
|
||||||
|
if pixel_value > image_mean:
|
||||||
|
image_hash += pow(2, pixel_index)
|
||||||
|
return image_hash
|
||||||
|
|
||||||
|
def get_sequences(path):
|
||||||
|
modes = ['color', 'shape']
|
||||||
|
sequences = {}
|
||||||
|
for mode in modes:
|
||||||
|
sequences[mode] = []
|
||||||
|
fps = 25
|
||||||
|
position = 0
|
||||||
|
file_names = filter(lambda x: 'timelinedata8p' in x, os.listdir(path))
|
||||||
|
file_names = sorted(file_names, key=lambda x: int(x[14:-4]))
|
||||||
|
file_names = map(lambda x: path + x, file_names)
|
||||||
|
for file_name in file_names:
|
||||||
|
timeline_image = Image.open(file_name)
|
||||||
|
timeline_width = timeline_image.size[0]
|
||||||
|
for x in range(0, timeline_width, 8):
|
||||||
|
frame_image = timeline_image.crop((x, 0, x + 8, 8))
|
||||||
|
for mode in modes:
|
||||||
|
frame_hash = get_hash(frame_image, mode)
|
||||||
|
if position == 0 or frame_hash != sequences[mode][-1]['hash']:
|
||||||
|
if position > 0:
|
||||||
|
sequences[mode][-1]['out'] = position
|
||||||
|
sequences[mode].append({'in': position, 'hash': frame_hash})
|
||||||
|
position += 1 / fps
|
||||||
|
for mode in modes:
|
||||||
|
sequences[mode][-1]['out'] = position
|
||||||
|
return sequences
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from time import time
|
||||||
|
start = time()
|
||||||
|
sequences = get_sequences('../tiles/0084628/')
|
||||||
|
#sequences = get_sequences('../tiles/0097514/')
|
||||||
|
print 'get_sequences() took', time() - start , 'seconds'
|
||||||
|
for mode in ['color', 'shape']:
|
||||||
|
hashes = []
|
||||||
|
index = {}
|
||||||
|
for sequence in sequences[mode]:
|
||||||
|
if sequence['hash'] > 0:
|
||||||
|
if not sequence['hash'] in index:
|
||||||
|
index[sequence['hash']] = len(hashes)
|
||||||
|
hashes.append({
|
||||||
|
'hash': sequence['hash'],
|
||||||
|
'sequences': []
|
||||||
|
})
|
||||||
|
hashes[index[sequence['hash']]]['sequences'].append({
|
||||||
|
'in': sequence['in'],
|
||||||
|
'out': sequence['out']
|
||||||
|
})
|
||||||
|
hashes = filter(lambda x: len(x['sequences']) > 1, hashes)
|
||||||
|
print '-' * 64
|
||||||
|
print mode
|
||||||
|
print '-' * 64
|
||||||
|
#for h in sorted(hashes, key=lambda x: -len(x['sequences'])):
|
||||||
|
for h in sorted(hashes, key=lambda x: x['sequences'][-1]['out'] - x['sequences'][0]['in']):
|
||||||
|
print h['hash'], len(h['sequences']), ', '.join(
|
||||||
|
map(lambda x: '%.2f-%.2f' % (x['in'], x['out']), h['sequences'])
|
||||||
|
)
|
||||||
|
|
258
oxtimelinesutils.py
Normal file
258
oxtimelinesutils.py
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
from __future__ import division
|
||||||
|
import Image
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
'''
|
||||||
|
This is an implementation of a join_tiles function for new-style timelines.
|
||||||
|
Timelines of files will be read from source_paths, the timeline of the item will
|
||||||
|
be written to target_path.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def join_tiles(source_paths, target_path):
|
||||||
|
|
||||||
|
def divide(num, by):
|
||||||
|
# divide(100, 3) -> [33, 33, 34]
|
||||||
|
arr = []
|
||||||
|
div = int(num / by)
|
||||||
|
mod = num % by
|
||||||
|
for i in range(int(by)):
|
||||||
|
arr.append(div + (i > by - 1 - mod))
|
||||||
|
return arr
|
||||||
|
|
||||||
|
def get_file_info(file_name):
|
||||||
|
for mode in modes:
|
||||||
|
if re.match('^timeline' + mode + '64p\d+\.jpg', file_name):
|
||||||
|
return {
|
||||||
|
'file': file_name,
|
||||||
|
'mode': mode,
|
||||||
|
'index': int(file_name[11 + len(mode):-4])
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
def save_and_open(data):
|
||||||
|
# whenever a large tile is done or needed,
|
||||||
|
# this function saves the previous large tile
|
||||||
|
# (if any) and opens the next one (if any).
|
||||||
|
# in between, whenever required, small tiles
|
||||||
|
# are opened, rendered and saved, and the
|
||||||
|
# large full tile is being generated.
|
||||||
|
# 'keyframes' are only rendered in large size,
|
||||||
|
# 'keyframeswide' only resized to small size.
|
||||||
|
image_mode = 'L' if mode == 'audio' else 'RGB'
|
||||||
|
small_mode = 'keyframes' if mode == 'keyframeswide' else mode
|
||||||
|
large_tile_i = int(target_w / large_tile_w)
|
||||||
|
# save previous large tile
|
||||||
|
if large_tile_i > 0:
|
||||||
|
large_tile_i -= 1
|
||||||
|
if mode != 'keyframeswide':
|
||||||
|
image_file = '%stimeline%s%dp%d.jpg' % (
|
||||||
|
target_path, mode, large_tile_h, large_tile_i
|
||||||
|
)
|
||||||
|
data['target_images']['large'].save(image_file)
|
||||||
|
print image_file
|
||||||
|
if mode != 'keyframes':
|
||||||
|
# open small tile
|
||||||
|
small_tile_i = int(large_tile_i / 60)
|
||||||
|
small_tile_x = (large_tile_i % 60) * 60
|
||||||
|
if small_tile_x == 0:
|
||||||
|
if small_tile_i < small_tile_n - 1:
|
||||||
|
w = small_tile_w
|
||||||
|
else:
|
||||||
|
w = small_tile_last_w
|
||||||
|
data['target_images']['small'] = Image.new(image_mode, (w, small_tile_h))
|
||||||
|
# paste large tile into small tile
|
||||||
|
w = 60 if large_tile_i < large_tile_n - 1 else small_tile_last_w % 60
|
||||||
|
data['target_images']['large'] = data['target_images']['large'].resize(
|
||||||
|
(w, small_tile_h), Image.ANTIALIAS
|
||||||
|
)
|
||||||
|
data['target_images']['small'].paste(
|
||||||
|
data['target_images']['large'], (small_tile_x, 0)
|
||||||
|
)
|
||||||
|
# save small tile
|
||||||
|
if small_tile_x == small_tile_w - 60 or large_tile_i == large_tile_n - 1:
|
||||||
|
image_file = '%stimeline%s%dp%d.jpg' % (
|
||||||
|
target_path, small_mode, small_tile_h, small_tile_i
|
||||||
|
)
|
||||||
|
data['target_images']['small'].save(image_file)
|
||||||
|
print image_file
|
||||||
|
if mode == 'antialias':
|
||||||
|
# render full tile
|
||||||
|
resized = data['target_images']['large'].resize((
|
||||||
|
data['full_tile_widths'][0], large_tile_h
|
||||||
|
), Image.ANTIALIAS)
|
||||||
|
data['target_images']['full'].paste(resized, (data['full_tile_offset'], 0))
|
||||||
|
data['full_tile_offset'] += data['full_tile_widths'][0]
|
||||||
|
data['full_tile_widths'] = data['full_tile_widths'][1:]
|
||||||
|
large_tile_i += 1
|
||||||
|
# open next large tile
|
||||||
|
if large_tile_i < large_tile_n:
|
||||||
|
w = large_tile_w if large_tile_i < large_tile_n - 1 else large_tile_last_w
|
||||||
|
data['target_images']['large'] = Image.new(image_mode, (w, large_tile_h))
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
fps = 25
|
||||||
|
large_tile_w, large_tile_h = 1500, 64
|
||||||
|
small_tile_w, small_tile_h = 3600, 16
|
||||||
|
full_tile_w = 1920
|
||||||
|
modes = ['antialias', 'slitscan', 'keyframes', 'keyframeswide', 'audio']
|
||||||
|
source_files = {}
|
||||||
|
for mode in modes:
|
||||||
|
source_files[mode] = []
|
||||||
|
|
||||||
|
# read files
|
||||||
|
durations = [0] * len(source_paths)
|
||||||
|
frame_n = 0
|
||||||
|
for i, path in enumerate(source_paths):
|
||||||
|
file_info = map(get_file_info, os.listdir(path))
|
||||||
|
file_info = filter(lambda x: x != None, file_info)
|
||||||
|
for info in sorted(file_info, key=lambda x: x['index']):
|
||||||
|
mode = info['mode']
|
||||||
|
source_files[mode].append(path + info['file'])
|
||||||
|
if mode == modes[0]:
|
||||||
|
width = Image.open(source_files[mode][-1]).size[0]
|
||||||
|
durations[i] += width / fps
|
||||||
|
frame_n += width
|
||||||
|
large_tile_n = int(math.ceil(frame_n / large_tile_w))
|
||||||
|
large_tile_last_w = frame_n % large_tile_w
|
||||||
|
small_tile_n = int(math.ceil(frame_n / fps / small_tile_w))
|
||||||
|
small_tile_last_w = int(math.ceil(frame_n / fps)) % small_tile_w
|
||||||
|
|
||||||
|
# open full timeline
|
||||||
|
if large_tile_n == 1:
|
||||||
|
data['full_tile_widths'] = [large_tile_last_w]
|
||||||
|
else:
|
||||||
|
w = full_tile_w
|
||||||
|
n = large_tile_n
|
||||||
|
if large_tile_last_w < large_tile_w:
|
||||||
|
factor = full_tile_w / frame_n
|
||||||
|
last_w = int(round(large_tile_last_w * factor))
|
||||||
|
w -= last_w
|
||||||
|
n -= 1
|
||||||
|
data['full_tile_widths'] = divide(w, n)
|
||||||
|
if large_tile_last_w < large_tile_w:
|
||||||
|
data['full_tile_widths'].append(last_w)
|
||||||
|
data['full_tile_offset'] = 0
|
||||||
|
full_tile_image = Image.new('RGB', (full_tile_w, large_tile_h))
|
||||||
|
|
||||||
|
# main loop
|
||||||
|
data['target_images'] = {'large': None, 'small': None, 'full': full_tile_image}
|
||||||
|
for mode in modes:
|
||||||
|
target_w = 0
|
||||||
|
for source_file in source_files[mode]:
|
||||||
|
source_image = Image.open(source_file)
|
||||||
|
source_w = source_image.size[0]
|
||||||
|
target_x = target_w % large_tile_w
|
||||||
|
if target_x == 0:
|
||||||
|
save_and_open(data)
|
||||||
|
data['target_images']['large'].paste(source_image, (target_x, 0))
|
||||||
|
target_w += source_w
|
||||||
|
if target_x + source_w > large_tile_w:
|
||||||
|
# target tile overflows into next source tile
|
||||||
|
save_and_open(data)
|
||||||
|
target_x -= large_tile_w
|
||||||
|
data['target_images']['large'].paste(source_image, (target_x, 0))
|
||||||
|
target_w += source_w
|
||||||
|
save_and_open(data)
|
||||||
|
|
||||||
|
# save full timelines
|
||||||
|
image_file = '%stimelineantialias%dp.jpg' % (target_path, large_tile_h)
|
||||||
|
data['target_images']['full'].save(image_file)
|
||||||
|
print image_file
|
||||||
|
image_file = '%stimelineantialias%dp.jpg' % (target_path, small_tile_h)
|
||||||
|
data['target_images']['full'].resize(
|
||||||
|
(full_tile_w, small_tile_h), Image.ANTIALIAS
|
||||||
|
).save(image_file)
|
||||||
|
print image_file
|
||||||
|
|
||||||
|
# join cuts
|
||||||
|
cuts = []
|
||||||
|
offset = 0
|
||||||
|
for i, path in enumerate(source_paths):
|
||||||
|
f = open(path + 'cuts.json', 'r')
|
||||||
|
path_cuts = json.load(f)
|
||||||
|
f.close()
|
||||||
|
if i > 0:
|
||||||
|
cuts.append(offset)
|
||||||
|
for cut in path_cuts:
|
||||||
|
cuts.append(offset + cut)
|
||||||
|
offset += durations[i]
|
||||||
|
f = open(target_path + 'cuts.json', 'w')
|
||||||
|
# avoid float rounding artefacts
|
||||||
|
f.write('[' + ', '.join(map(lambda x: '%.2f' % x, cuts)) + ']')
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
|
||||||
|
def split_tiles(path, paths, durations):
|
||||||
|
|
||||||
|
def is_timeline_file(file_name):
|
||||||
|
return file_name.startswith('timeline') and file_name.endswith('.png')
|
||||||
|
|
||||||
|
file_names = filter(is_timeline_file, os.listdir(path))
|
||||||
|
tiles = {}
|
||||||
|
for file_name in file_names:
|
||||||
|
mode = re.split('\d+', file_name[8:])[0]
|
||||||
|
print file_name, mode
|
||||||
|
split = re.split('[a-z]+', file_name[8 + len(mode):-4])
|
||||||
|
height, index = map(lambda x: int(x) if len(x) else -1, split)
|
||||||
|
if not mode in tiles:
|
||||||
|
tiles[mode] = {}
|
||||||
|
if not height in tiles[mode]:
|
||||||
|
tiles[mode][height] = 0
|
||||||
|
if index + 1 > tiles[mode][height]:
|
||||||
|
tiles[mode][height] = index + 1
|
||||||
|
print tiles
|
||||||
|
|
||||||
|
# for each mode
|
||||||
|
for mode in tiles:
|
||||||
|
image_mode = 'L' if mode == 'audio' else 'RGB'
|
||||||
|
# and for each size of that mode
|
||||||
|
for i, height in enumerate(tiles[mode]):
|
||||||
|
tile_width = 1500 if i == 0 else 3600
|
||||||
|
px_per_sec = 25 if i == 0 else 1
|
||||||
|
target_images = []
|
||||||
|
target_data = []
|
||||||
|
# and for each split item
|
||||||
|
for item_index, duration in enumerate(durations):
|
||||||
|
tile_index = 0
|
||||||
|
px = int(math.ceil(duration * px_per_sec))
|
||||||
|
# create a flat list of all target images
|
||||||
|
# (and store the split item and tile index)
|
||||||
|
while px:
|
||||||
|
width = tile_width if px > tile_width else px
|
||||||
|
target_images.append(
|
||||||
|
Image.new(image_mode, (width, height))
|
||||||
|
)
|
||||||
|
target_data.append(
|
||||||
|
{'item': item_index, 'tile': tile_index}
|
||||||
|
)
|
||||||
|
tile_index += 1
|
||||||
|
px -= width
|
||||||
|
target_index = 0
|
||||||
|
offset = 0
|
||||||
|
# for each source tile
|
||||||
|
for source_index in range(tiles[mode][height]):
|
||||||
|
source_image = Image.open('%stimeline%s%dp%d.png' % (
|
||||||
|
path, mode, height, source_index
|
||||||
|
))
|
||||||
|
source_width = source_image.size[0]
|
||||||
|
target_width = target_images[target_index].size[0]
|
||||||
|
target_images[target_index].paste(source_image, (offset, 0))
|
||||||
|
# paste it into as many target tiles as needed
|
||||||
|
while source_width + offset > target_width:
|
||||||
|
offset -= target_width
|
||||||
|
target_index += 1
|
||||||
|
target_width = target_images[target_index].size[0]
|
||||||
|
target_images[target_index].paste(source_image, (offset, 0))
|
||||||
|
for i, target_image in enumerate(target_images):
|
||||||
|
file_name = '%stimeline%s%dp%d' % (
|
||||||
|
paths[target_data[i]['item']], mode, height, target_data[i]['tile']
|
||||||
|
)
|
||||||
|
# target_image.save(file_name)
|
||||||
|
print file_name, target_image.size
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
join_tiles(['../tiles/0196499/1/', '../tiles/0196499/2/', '../tiles/0196499/3/'], '../tiles/0196499/')
|
||||||
|
# split_tiles('../tiles/0097514/', ['a/', 'b/', 'c/', 'd/', '/e'], [1000, 666.64, 666.68, 666.68, 29.96])
|
Loading…
Reference in a new issue