Add .ani writer

This commit is contained in:
Quantum 2020-10-03 01:24:47 -04:00
parent fbe77f9422
commit 05bc9d0e24
2 changed files with 51 additions and 7 deletions

View file

@ -10,7 +10,12 @@ from win2xcur.parser.cur import CURParser
class ANIParser(BaseParser):
SIGNATURE = b'RIFF'
ANI_TYPE = b'ACON'
HEADER_CHUNK = b'anih'
LIST_CHUNK = b'LIST'
SEQ_CHUNK = b'seq '
RATE_CHUNK = b'rate'
FRAME_TYPE = b'fram'
ICON_CHUNK = b'icon'
RIFF_HEADER = struct.Struct('<4sI4s')
CHUNK_HEADER = struct.Struct('<4sI')
ANIH_HEADER = struct.Struct('<IIIIIIIII')
@ -52,7 +57,7 @@ class ANIParser(BaseParser):
return name, size, offset
def _parse(self, offset: int) -> List[CursorFrame]:
_, size, offset = self._read_chunk(offset, expected=[b'anih'])
_, size, offset = self._read_chunk(offset, expected=[self.HEADER_CHUNK])
if size != self.ANIH_HEADER.size:
raise ValueError('Unexpected anih header size %d, expected %d' % (size, self.ANIH_HEADER.size))
@ -60,6 +65,9 @@ class ANIParser(BaseParser):
size, frame_count, step_count, width, height, bit_count, planes, display_rate, flags = self.ANIH_HEADER.unpack(
self.blob[offset:offset + self.ANIH_HEADER.size])
if size != self.ANIH_HEADER.size:
raise ValueError('Unexpected size in anih header %d, expected %d' % (size, self.ANIH_HEADER.size))
if not flags & self.ICON_FLAG:
raise NotImplementedError('Raw BMP images not supported.')
@ -70,8 +78,8 @@ class ANIParser(BaseParser):
delays = [display_rate for _ in range(step_count)]
while offset < len(self.blob):
name, size, offset = self._read_chunk(offset, expected=[b'LIST', b'seq ', b'rate'])
if name == b'LIST':
name, size, offset = self._read_chunk(offset, expected=[self.LIST_CHUNK, self.SEQ_CHUNK, self.RATE_CHUNK])
if name == self.LIST_CHUNK:
list_end = offset + size
if self.blob[offset:offset + 4] != self.FRAME_TYPE:
raise ValueError('Unexpected RIFF list type: %r, expected %r' %
@ -79,18 +87,18 @@ class ANIParser(BaseParser):
offset += 4
for i in range(frame_count):
_, size, offset = self._read_chunk(offset, expected=[b'icon'])
_, size, offset = self._read_chunk(offset, expected=[self.ICON_CHUNK])
frames.append(CURParser(self.blob[offset:offset + size]).frames[0])
offset += size
if offset != list_end:
raise ValueError('Wrong RIFF list size: %r, expected %r' % (offset, list_end))
elif name == b'seq ':
elif name == self.SEQ_CHUNK:
order = [i for i, in self.UNSIGNED.iter_unpack(self.blob[offset:offset + size])]
if len(order) != step_count:
raise ValueError('Wrong animation sequence size: %r, expected %r' % (len(order), step_count))
offset += size
elif name == b'rate':
elif name == self.RATE_CHUNK:
delays = [i for i, in self.UNSIGNED.iter_unpack(self.blob[offset:offset + size])]
if len(delays) != step_count:
raise ValueError('Wrong animation rate size: %r, expected %r' % (len(delays), step_count))

View file

@ -1,8 +1,9 @@
from io import BytesIO
from itertools import chain
from typing import List
from win2xcur.cursor import CursorFrame
from win2xcur.parser import CURParser
from win2xcur.parser import ANIParser, CURParser
def to_cur(frame: CursorFrame) -> bytes:
@ -24,3 +25,38 @@ def to_cur(frame: CursorFrame) -> bytes:
offset += len(blob)
return b''.join(chain([header], directory, image_data))
def get_ani_cur_list(frames: List[CursorFrame]) -> bytes:
io = BytesIO()
for frame in frames:
cur_file = to_cur(frame)
io.write(ANIParser.CHUNK_HEADER.pack(ANIParser.ICON_CHUNK, len(cur_file)))
io.write(cur_file)
if len(cur_file) & 1:
io.write(b'\0')
return io.getvalue()
def get_ani_rate_chunk(frames: List[CursorFrame]) -> bytes:
io = BytesIO()
io.write(ANIParser.CHUNK_HEADER.pack(ANIParser.RATE_CHUNK, ANIParser.UNSIGNED.size * len(frames)))
for frame in frames:
io.write(ANIParser.UNSIGNED.pack(int(round(frame.delay * 60))))
return io.getvalue()
def to_ani(frames: List[CursorFrame]) -> bytes:
ani_header = ANIParser.ANIH_HEADER.pack(
ANIParser.ANIH_HEADER.size, len(frames), len(frames), 0, 0, 32, 1, 1, ANIParser.ICON_FLAG
)
cur_list = get_ani_cur_list(frames)
chunks = [
ANIParser.CHUNK_HEADER.pack(ANIParser.HEADER_CHUNK, len(ani_header)),
ani_header,
ANIParser.RIFF_HEADER.pack(ANIParser.LIST_CHUNK, len(cur_list) + 4, ANIParser.FRAME_TYPE),
cur_list,
]
body = b''.join(chunks)
return ANIParser.RIFF_HEADER.pack(ANIParser.SIGNATURE, len(body) + 4, ANIParser.ANI_TYPE) + body