Skip to content

Instantly share code, notes, and snippets.

@joserebelo
Last active December 26, 2018 22:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save joserebelo/b9be41b7b88774f712e2f864fdd39878 to your computer and use it in GitHub Desktop.
Save joserebelo/b9be41b7b88774f712e2f864fdd39878 to your computer and use it in GitHub Desktop.
Mi Band 2 - Font pack / unpack
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Simple python command line tool to pack / unpack the Mi Band 2 font firmware files
from PIL import Image
import os
import math
import binascii
import sys
# Unpack the Mi Band 2 font file
# Creates 1bpp bmp and txt file with the characters
def unpackFont(font_path):
print('Unpacking', font_path)
font_file = open(font_path, 'rb')
txt_file = open(font_path + ".txt", 'wb')
# header = 16 bytes
header = font_file.read(16)
# last 2 header bytes = number of bytes used for characters
# 2 bytes per character, utf-16le
num_bytes_characters = (header[15] << 8) + header[14]
num_characters = num_bytes_characters // 2
# read the characters
for i in range (0, num_characters):
b = font_file.read(2)
txt_file.write(b)
# spritesheet dimensions (square)
dim = int(math.ceil(math.sqrt(num_characters)))
img = Image.new('1', (dim * 16, dim * 16), 0)
pixels = img.load()
for i in range (0, num_characters):
# each character is a 16x16 image, 1 bit per pixel, meaning 32 bytes
char_bytes = font_file.read(32)
row = i // dim
col = i % dim
x = 0
y = 0
# big endian
for byte in char_bytes:
bits = [(byte >> bit) & 1 for bit in range(8 - 1, -1, -1)]
for b in bits:
pixels[16 * col + x, 16 * row + y] = b
x += 1
if x == 16:
x = 0
y += 1
img.save(font_path + '.bmp')
# Create a Mi Band 2 font file from a bmp and txt
def packFont(bmp_path, txt_path, font_path):
print('Packing', font_path)
font_file = open(font_path, 'wb')
bmp_file = open(bmp_path, 'rb')
txt_file = open(txt_path, 'rb')
characters = txt_file.read()
num_characters = len(characters) // 2
# sort the characters, keep track of each index
chars_arr = str(characters, 'utf-16le')
chars_idx = range(num_characters)
chars = list(sorted(zip(chars_arr, chars_idx)))
# header, taken from Mili_pro.ft.en
header = bytearray(binascii.unhexlify('484D5A4B01FFFFFFFF00FFFFFFFFCA00'))
# header, taken from Mili_pro.ft
# header = bytearray(binascii.unhexlify('484D5A4B01FFFFFFFFFFFFFFFFFF7438'))
l = len(characters).to_bytes(2, byteorder='big')
header[14] = l[1]
header[15] = l[0]
font_file.write(header)
img = Image.open(bmp_path)
cols = img.size[0] // 16
rows = img.size[1] // 16
img_rgb = img.convert('RGB')
pixels = img_rgb.load()
for i in range (0, num_characters):
font_file.write(chars[i][0].encode('utf-16le'))
for i in range (0, num_characters):
idx = chars[i][1]
row = idx // cols
col = idx % cols
x = 0
y = 0
while y < 16:
b = 0
for i in range(0, 8):
if pixels[col * 16 + x, row * 16 + y] != (0, 0, 0):
b = b | (1 << (7 - i))
x += 1
if x == 16:
x = 0
y += 1
font_file.write(b.to_bytes(1, 'big'))
if len(sys.argv) == 3 and sys.argv[1] == 'unpack':
unpackFont(sys.argv[2])
elif len(sys.argv) == 5 and sys.argv[1] == 'pack':
packFont(sys.argv[2], sys.argv[3], sys.argv[4])
else:
print('Usage:')
print(' python', sys.argv[0], 'unpack Mili_pro.ft.en')
print(' python', sys.argv[0], 'pack Mili_pro.ft.en.bmp Mili_pro.ft.en.txt out.ft.en')
@dpeddi
Copy link

dpeddi commented Sep 13, 2017

$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.9 (jessie)
Release: 8.9
Codename: jessie

img.width img.height properties aren't available.
[code]
--- a/mi2font.py
+++ b/mi2font.py
@@ -1,4 +1,5 @@
-#!/usr/bin/python
+#!/usr/bin/python3
+# -- coding: utf-8 --

@@ -83,8 +84,8 @@ def packFont(bmp_path, txt_path, font_path):
font_file.write(characters)

 img = Image.open(bmp_path)
  • cols = img.width // 16
  • rows = img.height // 16
  • cols = img.size[0] // 16
  • rows = img.size[1] // 16
    pixels = img.load()
    [/code]

@joserebelo
Copy link
Author

@dpeddi thanks, updated :)

@lazarosfs
Copy link

Good job.
Why do I get this error though:

python mi2font.py unpack Mili_pro.ft
('Unpacking', 'Mili_pro.ft')
Traceback (most recent call last):
File "mi2font.py", line 102, in
unpackFont(sys.argv[2])
File "mi2font.py", line 23, in unpackFont
num_bytes_characters = (header[15] << 8) + header[14]
TypeError: unsupported operand type(s) for <<: 'str' and 'int'

@joserebelo
Copy link
Author

@lazarosfs can't reproduce the problem here, are you using Python 3?

@lazarosfs
Copy link

lazarosfs commented Sep 20, 2017

I tried both 2.7 and 3. it only happens on one pc. Can't figure it out.
May I ask, in Mili_pro.ft.txt after the letters follow all binary data.
Do I have to add my characters at the end or replace some japanese for example to pack correcly?

Thankx again for the great work.

@joserebelo
Copy link
Author

joserebelo commented Sep 20, 2017

As long as the order in the txt and the bmp files is the same, there should be no problem. You can either replace characters in both files or add new characters at the end, it should work :)

I haven't tried altering the chinese font file, you might need to uncomment line 71 to use the header from Mili_pro.ft instead of Mili_pro.ft.en

@joserebelo
Copy link
Author

Fixed the problem with the inverted colors.

@tomurbanowicz
Copy link

tomi@tomi /media/sf_github/tools-master/miband $ python miband.py unpack Mili_pro.ft.en
('Unpacking', 'Mili_pro.ft.en')
Traceback (most recent call last):
File "miband.py", line 108, in
unpackFont(sys.argv[2])
File "miband.py", line 29, in unpackFont
num_bytes_characters = (header[15] << 8) + header[14]
TypeError: unsupported operand type(s) for <<: 'str' and 'int'

@joserebelo
Copy link
Author

joserebelo commented Sep 21, 2017

@tomurbanowicz you need Python 3, doesn't seem to work on Python 2.

@joserebelo
Copy link
Author

Updated the script to ensure the font file is packed with the characters in order.

Thanks to @lazarosfs :)

@lazarosfs
Copy link

Great work again @josebelo I will test again just to be sure that the packing is done correctly when characters are not in ascending format. :)

@chienkd
Copy link

chienkd commented Apr 4, 2018

Hi, @joserebelo!
I want to add vietnamese character! Can I add to txt file and draw at the end of bmp file?
How Miband 2 knows which character in the ft file is displayed on the screen when it received vietnamese unicode text?
Thanks.

@pm-cz
Copy link

pm-cz commented Jun 1, 2018

Hi, @chienkd,
if it is still important for you, I have created tool along the lines of bipfont from https://github.com/amazfitbip/tools, and successfully created a Czech font. It uses same input characters as fonts from Amazfit BIP, so if there is already a repo/font with vietnamese characters for that, it should be able to use it.

The font was uploaded successfully to Miband2 by gadgetbridge, but I was not able to test it in an application which would send characters directly and is known to work. Gadgetbridge's test call displayed utf-8 characters outside of standard 8-bit ASCII with an extra space after them, so I don't know if it is its issue or firmware issue. So I am not sure if I should publish my script to github or not. Most apps provide transliteration to 7-bit ASCII anyway or do not support text display at all.

@rigormortisi
Copy link

Thank you very much, this script finally allowed me have Georgian characters on my mi band 2, though i struggled a bit, in the end i made it, again thank you very much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment