Windowsアニメーションカーソル(.ani)の情報を取得するサンプルコードです。
#rifffilereader.py from __future__ import annotations from io import FileIO from os import SEEK_SET, SEEK_CUR from typing import Tuple from dataclasses import dataclass @dataclass class RIFFFileChunk: reader: RIFFFileReader signature: str data_size: int list_type: str data_position: int def read(self, size: int, offset: int = 0): if offset < 0: raise ValueError("オフセットが負です。") if size + offset > self.dataSize: raise ValueError("範囲外のデータを参照しました。") self.reader.file.seek(self.data_position + offset, SEEK_SET) return self.reader.file.read(size) def readinto(self, obj: bytes, offset: int=0) -> int | None: if offset < 0: raise ValueError("オフセットが負です。") if offset + len(bytes(obj)) > self.data_size: raise ValueError("範囲外のデータを参照しました。") f = self.reader.file f.seek(self.data_position + offset, SEEK_SET) return f.readinto(obj) class RIFFFileReader: __path: str __f: FileIO __signature: str __data_size: int __form_type: str __chunks: Tuple[RIFFFileChunk] def __init__(self, path: str): self.__path = path f = open(path, "rb") # RIFFヘッダーの確認 self.__signature = f.read(4).decode("ascii") if self.__signature != "RIFF": raise RuntimeError("ファイルはRIFFファイルではありません。") self.__data_size = int.from_bytes(f.read(4), byteorder="little", signed=False) self.__form_type = f.read(4).decode("ascii") self.__f = f chunks = [] while True: signature = f.read(4).decode("ascii") if len(signature) == 0: break data_size = int.from_bytes(f.read(4), byteorder="little", signed=False) if signature == "LIST": list_type = f.read(4).decode("ascii") data_position = f.tell() else: list_type = None data_position = f.tell() f.seek(data_size, SEEK_CUR) chunks.append(RIFFFileChunk(self, signature, data_size, list_type, data_position)) self.__chunks = tuple(chunks) def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() def close(self): self.__f.close() @property def path(self) -> str: return self.__path @property def signature(self) -> str: return self.__signature @property def dataSize(self) -> int: return self.__data_size @property def formType(self) -> str: return self.__form_type @property def chunks(self) -> Tuple[RIFFFileChunk]: return self.__chunks @property def file(self) -> FileIO: return self.__f
#aniheader.py from rifffilereader import RIFFFileReader from ctypes import Structure, c_uint32 class ANIHeader(Structure): _fields_ = ( ("size", c_uint32), # cbSizeOf ("frames", c_uint32), # cFrames ("steps", c_uint32), # cSteps ("cx", c_uint32), ("cy", c_uint32), ("bit_count", c_uint32), # cBitCount ("planes", c_uint32), # cPlanes ("jif_rate", c_uint32), # JifRate ("flags", c_uint32), ) @staticmethod def from_file(path: str): #from itertools document def first_true(iterable, default=False, pred=None): return next(filter(pred, iterable), default) with RIFFFileReader(path) as riff: anih_chunk = first_true(riff.chunks, lambda chunk: chunk.signature == "anih") aniHeader = ANIHeader() anih_chunk.readinto(aniHeader) return aniHeader
#main.py from aniheader import ANIHeader from ctypes import Structure def structure_to_dict(struct: Structure): return dict((field, getattr(struct, field)) for field, _ in struct._fields_) print(structure_to_dict(ANIHeader.from_file(r"C:\Windows\Cursors\aero_busy.ani"))) #{'size': 36, 'frames': 18, 'steps': 18, 'cx': 0, 'cy': 0, 'bit_count': 32, 'planes': 1, 'jif_rate': 3, 'flags': 1}