前言
最近收获了不少APNG格式的兔子表情包,用APNG2GIF工具批量转成gif后,发现在微信上,只能播放一次!于是便去研究如何批量修改gif的循环次数。
搜索引擎输入:批量修改gif循环次数,并没有找到合适的教程。搜到的结果要么是用PS改次数的教程,要么是针对单个gif的工具,无法批量。虽然我得到了300多张可爱的兔兔,但是要我一张一张手动去改,怕不也要累死……
研究
于是便开始从GIF格式本身开始研究,在维基百科 界面,找到了很多有用的信息。
从维基百科中得以知道:gif的循环次数实现在GIF89A标准的Application Extension的block中,截取百科中的相关片段如下:
byte# hexadecimal text or
(hex) value Meaning
0: 47 49 46
38 39 61 GIF89a Header
Logical Screen Descriptor
6: 90 01 400 - width in pixels
8: 90 01 400 - height in pixels
A: F7 - GCT follows for 256 colors with resolution 3 x 8bits/primary
B: 00 0 - background color #0
C: 00 - default pixel aspect ratio
D: Global Color Table
:
30D: 21 FF Application Extension block
30F: 0B 11 - eleven bytes of data follow
310: 4E 45 54
53 43 41
50 45 NETSCAPE - 8-character application name
32 2E 30 2.0 - application "authentication code"
31B: 03 3 - three more bytes of data
31C: 01 1 - index of the current data sub-block (always 1 for the NETSCAPE block)
31D: FF FF 65535 - unsigned number of repetitions
31F: 00 - end of App Extension block
320: 21 F9 Graphic Control Extension for frame #1
322: 04 4 - four bytes in the current block
323: 04 000..... - reserved; 5 lower bits are bit field
...001.. - disposal method 1: do not dispose
......0. - no user input
.......0 - transparent color is not given
324: 09 00 - 0.09 sec delay before painting next frame
326: FF - transparent color index (unused in this frame)
327: 00 - end of GCE block
328: 2C Image Descriptor of frame #1
329: 00 00 00 00 (0,0) - NW corner of frame at 0, 0
32D: 90 01 90 01 (400,400) - Frame width and height: 400 × 400
331: 00 - no local color table; no interlace
332: 08 8 LZW min code size; Image Data of frame #1 beginning
333: FF 255 - 255 bytes of LZW encoded image data follow
334: data
433: FF 255 - 255 bytes of LZW encoded image data follow
data
:
92C0: 00 - end of LZW data for this frame
92C1: 21 F9 Graphic Control Extension for frame #2
: :
EDABD: 21 F9 Graphic Control Extension for frame #44
:
F48F5: 3B File terminator
我们可以看到,其中的30D行,定义Application Extension头。31D行,定义循环次数。
再去相关网站查看对Application Extension的定义,截取如下:
i) Extension Introducer - Defines this block as an extension. This
field contains the fixed value 0x21.
ii) Application Extension Label - Identifies the block as an
Application Extension. This field contains the fixed value 0xFF.
iii) Block Size - Number of bytes in this extension block,
following the Block Size field, up to but not including the
beginning of the Application Data. This field contains the fixed
value 11.
iv) Application Identifier - Sequence of eight printable ASCII
characters used to identify the application owning the Application
Extension.
从这我们得知,我们可以通过0x21 0xFF 0x0B
这三个byte来定位到Application Extension的位置。(或者通过Identifier,一般为字符NETSCAPE)
修改与验证
由上面的信息,我们可以得到以下修改步骤:
- 通过
0x21 0xFF 0x0B
byte定位Application Extension的位置 - 通过Application Extension的位置找到gif的循环次数,偏移量为+16
- 修改循环次数为0xFF 0xFF,实现无限循环
简单的验证代码:
data, _ := ioutil.ReadFile("兔兔.gif")
index := bytes.Index(data, []byte{0x21, 0xFF, 0x0B})
fmt.Println(data[index+16 : index+18])
得到的结果为[3,0],即0x03 0x00,表明这个gif只循环了4次
修改代码
func UnlimitedGifRepetitions(path string) ([]byte, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
index := bytes.Index(data, []byte{0x21, 0xFF, 0x0B})
if index < 0 {
return nil, fmt.Errorf("cannot Found Gif Application Extension in %s", path)
}
data[index+16] = 255
data[index+17] = 255
return data, nil
}
修改完后,再次发微信验证下,完美无限循环!
October 6th, 2023 at 01:33 pm
感谢