potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

Python 3 Windowsアニメーションカーソル(.ani)の情報を取得する

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}

参考