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): class ANIParser(BaseParser):
SIGNATURE = b'RIFF' SIGNATURE = b'RIFF'
ANI_TYPE = b'ACON' ANI_TYPE = b'ACON'
HEADER_CHUNK = b'anih'
LIST_CHUNK = b'LIST'
SEQ_CHUNK = b'seq '
RATE_CHUNK = b'rate'
FRAME_TYPE = b'fram' FRAME_TYPE = b'fram'
ICON_CHUNK = b'icon'
RIFF_HEADER = struct.Struct('<4sI4s') RIFF_HEADER = struct.Struct('<4sI4s')
CHUNK_HEADER = struct.Struct('<4sI') CHUNK_HEADER = struct.Struct('<4sI')
ANIH_HEADER = struct.Struct('<IIIIIIIII') ANIH_HEADER = struct.Struct('<IIIIIIIII')
@ -52,7 +57,7 @@ class ANIParser(BaseParser):
return name, size, offset return name, size, offset
def _parse(self, offset: int) -> List[CursorFrame]: 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: if size != self.ANIH_HEADER.size:
raise ValueError('Unexpected anih header size %d, expected %d' % (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( 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]) 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: if not flags & self.ICON_FLAG:
raise NotImplementedError('Raw BMP images not supported.') raise NotImplementedError('Raw BMP images not supported.')
@ -70,8 +78,8 @@ class ANIParser(BaseParser):
delays = [display_rate for _ in range(step_count)] delays = [display_rate for _ in range(step_count)]
while offset < len(self.blob): while offset < len(self.blob):
name, size, offset = self._read_chunk(offset, expected=[b'LIST', b'seq ', b'rate']) name, size, offset = self._read_chunk(offset, expected=[self.LIST_CHUNK, self.SEQ_CHUNK, self.RATE_CHUNK])
if name == b'LIST': if name == self.LIST_CHUNK:
list_end = offset + size list_end = offset + size
if self.blob[offset:offset + 4] != self.FRAME_TYPE: if self.blob[offset:offset + 4] != self.FRAME_TYPE:
raise ValueError('Unexpected RIFF list type: %r, expected %r' % raise ValueError('Unexpected RIFF list type: %r, expected %r' %
@ -79,18 +87,18 @@ class ANIParser(BaseParser):
offset += 4 offset += 4
for i in range(frame_count): 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]) frames.append(CURParser(self.blob[offset:offset + size]).frames[0])
offset += size offset += size
if offset != list_end: if offset != list_end:
raise ValueError('Wrong RIFF list size: %r, expected %r' % (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])] order = [i for i, in self.UNSIGNED.iter_unpack(self.blob[offset:offset + size])]
if len(order) != step_count: if len(order) != step_count:
raise ValueError('Wrong animation sequence size: %r, expected %r' % (len(order), step_count)) raise ValueError('Wrong animation sequence size: %r, expected %r' % (len(order), step_count))
offset += size 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])] delays = [i for i, in self.UNSIGNED.iter_unpack(self.blob[offset:offset + size])]
if len(delays) != step_count: if len(delays) != step_count:
raise ValueError('Wrong animation rate size: %r, expected %r' % (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 itertools import chain
from typing import List from typing import List
from win2xcur.cursor import CursorFrame from win2xcur.cursor import CursorFrame
from win2xcur.parser import CURParser from win2xcur.parser import ANIParser, CURParser
def to_cur(frame: CursorFrame) -> bytes: def to_cur(frame: CursorFrame) -> bytes:
@ -24,3 +25,38 @@ def to_cur(frame: CursorFrame) -> bytes:
offset += len(blob) offset += len(blob)
return b''.join(chain([header], directory, image_data)) 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