Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added LICEcap's LCF file format support #15

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions anim_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,23 @@ def find_matching_rect(bitmap, num_used_rows, packed, src, sx, sy, w, h):
return None

def generate_animation(anim_name):
frames = []
rex = re.compile("screen_([0-9]+).png")
for f in os.listdir(anim_name):
m = re.search(rex, f)
if m:
frames.append((int(m.group(1)), anim_name + "/" + f))
frames.sort()

images = [misc.imread(f) for t, f in frames]
if anim_name.endswith('.lcf'):
from lcf import process
frames = [(f.delta, f) for f in process(anim_name)]
images = [f.get_array() for t, f in frames]
delays = [t for t, f in frames] + [END_FRAME_PAUSE]
anim_name = anim_name[:-4]
else:
frames = []
rex = re.compile("screen_([0-9]+).png")
for f in os.listdir(anim_name):
m = re.search(rex, f)
if m:
frames.append((int(m.group(1)), anim_name + "/" + f))
frames.sort()
images = [misc.imread(f) for t, f in frames]
times = [t for t, f in frames]
delays = (array(times[1:] + [times[-1] + END_FRAME_PAUSE]) - array(times)).tolist()

zero = images[0] - images[0]
pairs = zip([zero] + images[:-1], images)
Expand Down Expand Up @@ -204,8 +212,6 @@ def generate_animation(anim_name):
os.system("mv " + anim_name + "_packed_tmp.png " + anim_name + "_packed.png")

# Generate JSON to represent the data
times = [t for t, f in frames]
delays = (array(times[1:] + [times[-1] + END_FRAME_PAUSE]) - array(times)).tolist()

timeline = []
for i in xrange(len(images)):
Expand Down
114 changes: 114 additions & 0 deletions lcf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
from __future__ import division

import zlib
import struct
from collections import namedtuple

import numpy as np


LCF_VERSION = 0x11CEb001

LcfHeader = namedtuple('LcfHeader', 'version bpp w h bsize_w bsize_h nf cdata_left dsize')
LcfBlock = namedtuple('LcfBlock', 'hdr frame_deltas frames')


class LcfFrame(object):
def __init__(self, frame, block, block_idx, frame_idx):
self.frame = frame
self.block = block
self.block_idx = block_idx
self.frame_idx = frame_idx
self.delta = abs(block.frame_deltas[frame_idx])

def get_array(self):
hdr = self.block.hdr
arr = np.ndarray(shape=(0, hdr.w, 3), dtype=np.uint8)
for y, ypos in enumerate(xrange(0, hdr.h, hdr.bsize_h)):
hei = min(hdr.h - ypos, hdr.bsize_h)
line = np.ndarray(shape=(hei, 0, 3), dtype=np.uint8)
for x, xpos in enumerate(xrange(0, hdr.w, hdr.bsize_w)):
# wid = min(hdr.w - xpos, hdr.bsize_w)
line = np.concatenate((line, self.frame[x][y]), axis=1)
arr = np.concatenate((arr, line), axis=0)
return arr


class BadBlock(Exception):
pass


class CorruptedBlock(BadBlock):
pass


def read(fd, size):
data = fd.read(size)
if len(data) != size:
raise BadBlock("Cannot read %s bytes from file" % size)
return data


def read_block(fd):
hdr = LcfHeader(*struct.unpack('iiiiiiiii', read(fd, 9 * 4)))
if hdr.version != LCF_VERSION:
raise BadBlock("Wrong LCF version")
if hdr.nf < 1 or hdr.nf > 1024:
raise BadBlock("Wrong number of frames")
if hdr.bpp != 16:
raise CorruptedBlock("Wrong bits per pixel")
frame_deltas = [struct.unpack('i', read(fd, 4))[0] for f in xrange(hdr.nf)]
cdata = read(fd, hdr.cdata_left)
decompress = zlib.decompressobj()
data = decompress.decompress(cdata)
if hdr.dsize != len(data):
raise CorruptedBlock("Wrong uncompressed data size")
# Decode slices
bytespersample = (hdr.bpp + 7) // 8
# Setup frames as blocks of ns_x * ns_y slices
ns_x = (hdr.w + hdr.bsize_w - 1) // hdr.bsize_w
ns_y = (hdr.h + hdr.bsize_h - 1) // hdr.bsize_h
frames = [[[[] for y in xrange(ns_y)] for x in xrange(ns_x)] for f in xrange(hdr.nf)]
sp = 0
for y, ypos in enumerate(xrange(0, hdr.h, hdr.bsize_h)):
hei = min(hdr.h - ypos, hdr.bsize_h)
for x, xpos in enumerate(xrange(0, hdr.w, hdr.bsize_w)):
wid = min(hdr.w - xpos, hdr.bsize_w)
sz1 = hei * wid * bytespersample
cnt = 0
for i in xrange(hdr.nf):
if cnt > 0:
cnt -= 1
else:
buff = data[sp:sp + sz1]
if len(buff) != sz1:
raise CorruptedBlock("Wrong size read!")
arr = np.fromstring(buff, dtype=np.uint16)
lvalid = np.zeros(arr.size * 3, np.uint8)
lvalid.view()[0::3] = (arr << 3) & 0xf8
lvalid.view()[1::3] = (arr >> 3) & 0xfc
lvalid.view()[2::3] = (arr >> 8) & 0xf8
lvalid.shape = (hei, wid, 3)
sp += sz1
if i < hdr.nf - 1:
cnt = ord(data[sp])
sp += 1
frames[i][x][y] = lvalid
if sp != len(data):
print "Not all data consumed (%s/%s)!" % (sp, len(data))
return LcfBlock(hdr, frame_deltas, frames)


def process(filename):
blocks = []
with open(filename) as fd:
while True:
try:
blocks.append(read_block(fd))
except CorruptedBlock as e:
print(e)
break
except BadBlock:
break
return [LcfFrame(f, b, i, j) for i, b in enumerate(blocks) for j, f in enumerate(b.frames)]