mirror of
https://github.com/quantum5/win2xcur.git
synced 2025-04-24 10:11:57 -04:00
Add .ani writer
This commit is contained in:
parent
fbe77f9422
commit
05bc9d0e24
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue