123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- import io
- import logging
- import time
- import uuid
- from PIL import Image
- logging.basicConfig(format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s', level=logging.INFO)
- def scaleImage(img, scale):
- if img is None:
- return
- return img.resize((img.width * scale, img.height * scale))
- def readCUR(f, width=-1.0, height=-1.0):
- frameImage = Image.open(f, formats=['cur', 'ico']).convert('RGBA')
- if (width, height) == (-1.0, -1.0):
- return frameImage, (float(frameImage.width), float(frameImage.height))
- if -1 in (width, height):
- width = height / frameImage.height * frameImage.width if width == -1 else width
- height = width / frameImage.width * frameImage.height if height == -1 else height
- return frameImage.resize((int(width), int(height))), (width, height)
- def analyzeANI(f):
- if f.read(4) != b'RIFF':
- return {'code': -1, 'msg': 'File is not a ANI File!'}
- logging.debug('文件头检查完成!')
- fileSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
- # if os.path.getsize(filePath) != fileSize:
- # return {'code':-2,'msg':'File is damaged!'}
- logging.debug('文件长度检查完成!')
- if f.read(4) != b'ACON':
- return {'code': -1, 'msg': 'File is not a ANI File!'}
- logging.debug('魔数检查完成!')
- frameRate = (1/60)*1000
- while (True):
- chunkName = f.read(4)
- if chunkName == b'LIST':
- break
- chunkSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
- if chunkName.lower() == b'rate':
- logging.debug('发现自定义速率!')
- frameRate = frameRate * int.from_bytes(f.read(4), byteorder='little', signed=False)
- logging.warning('发现自定义速率!由于GIF限制,将取第一帧与第二帧的速率作为整体速率!')
- f.read(chunkSize - 4)
- else:
- logging.debug('发现自定义Chunk!')
- f.read(chunkSize)
- listChunkSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
- if f.read(4) != b'fram':
- return {'code': -3, 'msg': 'File not a ANI File!(No Frames)'}
- logging.debug('frame头检查完成!')
- frameList = []
- nowSize = 4
- while (nowSize < listChunkSize):
- if f.read(4) != b'icon':
- return {'code': -4, 'msg': 'File not a ANI File!(Other Kind Frames)'}
- nowSize += 4
- subChunkSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
- nowSize += 4
- frameList.append(f.read(subChunkSize))
- nowSize += subChunkSize
- return {'code': 0, 'msg': frameList, 'frameRate': frameRate}
- def main():
- from config import capeConfig
- uniqueId = (f'local.{capeConfig['Author'] or 'unknown'}'
- f'.{capeConfig['CapeName'] or 'untitled'}'
- f'.{time.time()}.{str(uuid.uuid4()).upper()}')
- capeData = {
- 'Author': capeConfig['Author'],
- 'CapeName': capeConfig['CapeName'],
- 'CapeVersion': capeConfig['CapeVersion'],
- 'Cloud': False,
- 'Cursors': {},
- 'HiDPI': capeConfig['HiDPI'],
- 'Identifier': capeConfig['Identifier'] or uniqueId,
- 'MinimumVersion': 2.0,
- 'Version': 2.0
- }
- for cursorType, cursorConfig in capeConfig['Cursors'].items():
- cursorSetting = {
- 'FrameCount': 1,
- 'FrameDuration': cursorConfig['FrameDuration'],
- 'HotSpotX': cursorConfig['HotSpot'][0] + 2.0,
- 'HotSpotY': cursorConfig['HotSpot'][1] + 2.0,
- 'Representations': []
- }
- hidpiRatio = 2 if capeConfig['HiDPI'] else 1
- width, height = cursorConfig.get('Size', (-1.0, -1.0))
- with open(cursorConfig['Path'], 'rb') as f:
- spriteSheet = None
- if (res := analyzeANI(f))['code'] == 0:
- logging.info('ANI文件分析完成,帧提取完成!')
- cursorSetting['FrameCount'] = len(res['msg'])
- for frameIndex in range(len(res['msg'])):
- b = io.BytesIO(res['msg'][frameIndex])
- frame, (width, height) = readCUR(b, width, height)
- position = (2, 2 + int((height + 4) * frameIndex))
- if frameIndex == 0:
- spriteSheet = Image.new('RGBA', (int(width + 4), int(height + 4) * len(res['msg'])))
- spriteSheet.paste(frame, position)
- else:
- logging.info('尝试作为CUR读入')
- frame, (width, height) = readCUR(f, width, height)
- spriteSheet = Image.new('RGBA', (int(width + 4), int(height + 4)))
- spriteSheet.paste(frame, (2, 2))
- logging.info(f'目标尺寸:{width}x{height}@{hidpiRatio}x')
- cursorSetting['PointsHigh'], cursorSetting['PointsWide'] = width + 4, height + 4
- for scale in (1, 2) if capeConfig['HiDPI'] else (1,):
- byteBuffer = io.BytesIO()
- scaleImage(spriteSheet, scale).save(byteBuffer, format='tiff', compression='tiff_lzw')
- cursorSetting['Representations'].append(byteBuffer.getvalue())
- capeData['Cursors'][cursorType] = cursorSetting
- from plistlib import dump, FMT_XML
- with open(f'{capeData['Identifier']}.cape', 'wb') as f:
- dump(capeData, f, fmt=FMT_XML, sort_keys=True, skipkeys=False)
- if __name__ == '__main__':
- main()
|