ani2cape.py 4.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. from config import capeConfig
  2. from plistlib import dumps,loads,dump,FMT_XML,load
  3. from PIL import Image
  4. import io,os,sys,base64
  5. import logging
  6. import time
  7. import uuid
  8. logging.basicConfig(format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s',level=logging.INFO)
  9. def analyzeANIFile(filePath):
  10. with open(filePath,'rb') as f:
  11. if f.read(4) != b'RIFF':
  12. return {"code":-1,"msg":"File is not a ANI File!"}
  13. logging.debug('文件头检查完成!')
  14. fileSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
  15. # if os.path.getsize(filePath) != fileSize:
  16. # return {"code":-2,"msg":"File is damaged!"}
  17. logging.debug('文件长度检查完成!')
  18. if f.read(4) != b'ACON':
  19. return {"code":-1,"msg":"File is not a ANI File!"}
  20. logging.debug('魔数检查完成!')
  21. frameRate = (1/60)*1000
  22. while(True):
  23. chunkName = f.read(4)
  24. if chunkName == b'LIST':
  25. break
  26. chunkSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
  27. if chunkName.lower() == b'rate':
  28. logging.debug('发现自定义速率!')
  29. frameRate = frameRate * int.from_bytes(f.read(4), byteorder='little', signed=False)
  30. logging.warning('发现自定义速率!由于GIF限制,将取第一帧与第二帧的速率作为整体速率!')
  31. f.read(chunkSize - 4)
  32. else:
  33. logging.debug('发现自定义Chunk!')
  34. f.read(chunkSize)
  35. listChunkSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
  36. if f.read(4) != b'fram':
  37. return {"code":-3,"msg":"File not a ANI File!(No Frames)"}
  38. logging.debug('frame头检查完成!')
  39. frameList = []
  40. nowSize = 4
  41. while(nowSize < listChunkSize):
  42. if f.read(4) != b'icon':
  43. return {"code":-4,"msg":"File not a ANI File!(Other Kind Frames)"}
  44. nowSize += 4
  45. subChunkSize = int.from_bytes(f.read(4), byteorder='little', signed=False)
  46. nowSize += 4
  47. frameList.append(f.read(subChunkSize))
  48. nowSize += subChunkSize
  49. return {"code":0,"msg":frameList,"frameRate":frameRate}
  50. if __name__ == '__main__':
  51. capeData = {
  52. 'Author': capeConfig['Author'],
  53. 'CapeName': capeConfig['CapeName'],
  54. 'CapeVersion': capeConfig['CapeVersion'],
  55. 'Cloud': False,
  56. 'Cursors': {},
  57. 'HiDPI': capeConfig['HiDPI'],
  58. 'Identifier': f"local.{capeConfig['Author']}.{capeConfig['CapeName']}.{time.time()}.{str(uuid.uuid4()).upper()}.{time.time()}",
  59. 'MinimumVersion': 2.0,
  60. 'Version': 2.0
  61. }
  62. for cursorType in capeConfig['Cursors'].keys():
  63. cursorSetting = {
  64. 'FrameCount': 1,
  65. 'FrameDuration': capeConfig['Cursors'][cursorType]['FrameDuration'],
  66. 'HotSpotX': capeConfig['Cursors'][cursorType]['HotSpot'][0],
  67. 'HotSpotY': capeConfig['Cursors'][cursorType]['HotSpot'][1],
  68. 'PointsHigh': capeConfig['Cursors'][cursorType]['Size'][0],
  69. 'PointsWide': capeConfig['Cursors'][cursorType]['Size'][1],
  70. 'Representations': []
  71. }
  72. res = analyzeANIFile(capeConfig['Cursors'][cursorType]['ANIPath'])
  73. if res["code"] == 0:
  74. logging.info('ANI文件分析完成,帧提取完成!')
  75. cursorSetting['FrameCount'] = len(res["msg"])
  76. spriteSheet = Image.new("RGBA", (int(cursorSetting['PointsHigh']), int(cursorSetting['PointsWide'] * len(res["msg"]))))
  77. for frameIndex in range(len(res["msg"])):
  78. frameImage = Image.open(io.BytesIO(res["msg"][frameIndex]),formats=['cur']).convert('RGBA')
  79. extracted_frame = frameImage.resize((int(cursorSetting['PointsHigh']), int(cursorSetting['PointsWide'])))
  80. position = (0, int(cursorSetting['PointsHigh'] * frameIndex))
  81. spriteSheet.paste(extracted_frame, position)
  82. byteBuffer = io.BytesIO()
  83. spriteSheet.save(byteBuffer,format="TIFF")
  84. cursorSetting['Representations'].append(byteBuffer.getvalue())
  85. capeData['Cursors'][cursorType] = cursorSetting
  86. with open(f"{capeData['Identifier']}.cape",'wb') as f:
  87. dump(capeData, f, fmt=FMT_XML, sort_keys=True, skipkeys=False)