From d8ac9d8fad7b99e4ef515f526fa44788e311c3bb Mon Sep 17 00:00:00 2001 From: Yuxin Wu Date: Sat, 27 Jun 2020 19:00:30 -0700 Subject: [PATCH] compatibility with an old database file --- wechat/audio.py | 24 ++++++++------ wechat/avatar.py | 62 +++++++++++++++++++++++++------------ wechat/msgslice.py | 2 +- wechat/parser.py | 28 +++++++++++------ wechat/res.py | 1 + wechat/static/TP_SPEAK.html | 2 +- 6 files changed, 78 insertions(+), 41 deletions(-) diff --git a/wechat/audio.py b/wechat/audio.py index 48d3d51..6892a06 100644 --- a/wechat/audio.py +++ b/wechat/audio.py @@ -30,16 +30,20 @@ def do_parse_wechat_audio_file(file_name): with open(file_name, 'rb') as f: header = f.read(10) if b'AMR' in header: - raise NotImplementedError("AMR decoding not implemented because it seems deprecated since WeChat6.0+") - # The below is python2 only. It should be equivalent to using sox from command line? - import pysox - infile = pysox.CSoxStream(file_name) - outfile = pysox.CSoxStream(mp3_file, 'w', infile.get_signal()) - chain = pysox.CEffectsChain(infile, outfile) - chain.flow_effects() - outfile.close() - signal = infile.get_signal().get_signalinfo() - duration = signal['length'] * 1.0 / signal['rate'] + cmd = f"sox -e signed -c 1 {file_name} {mp3_file}" + subproc_succ(cmd) + cmd = f"soxi -D {mp3_file}" + duration = float(subproc_succ(cmd)) + + # The below is python2 only. It should be equivalent to using sox from command line + # import pysox + # infile = pysox.CSoxStream(file_name) + # outfile = pysox.CSoxStream(mp3_file, 'w', infile.get_signal()) + # chain = pysox.CEffectsChain(infile, outfile) + # chain.flow_effects() + # outfile.close() + # signal = infile.get_signal().get_signalinfo() + # duration = signal['length'] * 1.0 / signal['rate'] elif b'SILK' in header: raw_file = os.path.join('/tmp', os.path.basename(file_name)[:-4] + '.raw') diff --git a/wechat/avatar.py b/wechat/avatar.py index 87e4b95..0b04043 100644 --- a/wechat/avatar.py +++ b/wechat/avatar.py @@ -1,3 +1,4 @@ +#!/usr/bin/env python3 # -*- coding: UTF-8 -*- from PIL import Image @@ -35,32 +36,36 @@ class AvatarReader(object): """ username: `username` field in db.rcontact""" if not self._use_avt: return None + username = ensure_unicode(username) + avtid = md5(username.encode('utf-8')) + dir1, dir2 = avtid[:2], avtid[2:4] + candidates = glob.glob(os.path.join(self.avt_dir, dir1, dir2, f"*{avtid}*")) + default_candidate = os.path.join(self.avt_dir, dir1, dir2, f"user_{avtid}.png") + candidates.append(default_candidate) - username = ensure_unicode(username).encode('utf-8') - filename = md5(username) - dir1, dir2 = filename[:2], filename[2:4] - filename = os.path.join(dir1, dir2, - "user_{}.png".format(filename)) + def priority(s): + if "_hd" in s and s.endswith(".png"): + return 10 + else: + return 1 - try: + candidates = sorted(set(candidates), key=priority, reverse=True) + + for cand in candidates: try: if self.avt_use_db: - pos, size = self.query_index(filename) + pos, size = self.query_index(cand) return self.read_img(pos, size) else: - img_file = os.path.join(self.avt_dir, filename) - if os.path.exists(img_file): - return Image.open(img_file) - else: - return None - except TypeError: - logger.warning("Avatar for {} not found in avatar database.".format(username)) - return None - except Exception as e: - raise - # logger.exception("Failed to retrieve avatar!") - # return None - + if os.path.exists(cand): + if cand.endswith(".bm"): + return self.read_bm_file(cand) + else: + return Image.open(cand) + except Exception: + logger.exception("HHH") + pass + logger.warning("Avatar for {} not found in avatar database.".format(username)) def read_img(self, pos, size): file_idx = pos >> 32 @@ -78,8 +83,25 @@ class AvatarReader(object): logger.warn("Cannot read avatar from {}: {}".format(fname, str(e))) return None + def read_bm_file(self, fname): + # history at https://github.com/ppwwyyxx/wechat-dump/pull/14 + with open(fname, 'rb') as f: + # filesize is 36880=96x96x4+16 + size = (96, 96, 3) + img = np.zeros(size, dtype='uint8') + for i in range(96): + for j in range(96): + r, g, b, a = f.read(4) + img[i,j] = (r, g, b) + return Image.fromarray(img, mode="RGB") + def query_index(self, filename): conn = sqlite3.connect(self.avt_db) cursor = conn.execute("select Offset,Size from Index_avatar where FileName='{}'".format(filename)) pos, size = cursor.fetchone() return pos, size + +if __name__ == '__main__': + import sys + r = AvatarReader(sys.argv[1], sys.argv[2]) + print(r.get_avatar(sys.argv[3])) diff --git a/wechat/msgslice.py b/wechat/msgslice.py index fae947a..2d4d8c4 100644 --- a/wechat/msgslice.py +++ b/wechat/msgslice.py @@ -32,7 +32,7 @@ class MessageSlicerBySize(object): """ Separate messages into slices by max slice size, to avoid too large html. """ - def __init__(self, size=1000): + def __init__(self, size=2000): """ a slice will have <= 1.5 * cnt messages""" self.size = size assert self.size > 1 diff --git a/wechat/parser.py b/wechat/parser.py index c0c2b6b..16021d9 100644 --- a/wechat/parser.py +++ b/wechat/parser.py @@ -101,15 +101,25 @@ SELECT {} FROM message self.emoji_groups[md5] = group NEEDED_EMOJI_CATALOG = [49, 50, 17] - emojiinfo_q = self.cc.execute( -""" SELECT md5, catalog, name, cdnUrl FROM EmojiInfo""") - for row in emojiinfo_q: - md5, catalog, name, cdnUrl = row - if cdnUrl: - self.emoji_url[md5] = cdnUrl - if catalog not in NEEDED_EMOJI_CATALOG: - continue - self.internal_emojis[md5] = name + try: + emojiinfo_q = self.cc.execute( + """ SELECT md5, catalog, name, cdnUrl FROM EmojiInfo""") + except: # old database does not have cdnurl + emojiinfo_q = self.cc.execute( + """ SELECT md5, catalog, name FROM EmojiInfo""") + for row in emojiinfo_q: + md5, catalog, name = row + if catalog not in NEEDED_EMOJI_CATALOG: + continue + self.internal_emojis[md5] = name + else: + for row in emojiinfo_q: + md5, catalog, name, cdnUrl = row + if cdnUrl: + self.emoji_url[md5] = cdnUrl + if catalog not in NEEDED_EMOJI_CATALOG: + continue + self.internal_emojis[md5] = name def _parse(self): diff --git a/wechat/res.py b/wechat/res.py index 081fcb8..e16be77 100644 --- a/wechat/res.py +++ b/wechat/res.py @@ -121,6 +121,7 @@ class Resource(object): """ return base64 unicode string""" im = self.avt_reader.get_avatar(username) if im is None: + logger.warning(f"Avatar for {username} is missing.") return "" buf = io.BytesIO() try: diff --git a/wechat/static/TP_SPEAK.html b/wechat/static/TP_SPEAK.html index b47e026..4ad694d 100644 --- a/wechat/static/TP_SPEAK.html +++ b/wechat/static/TP_SPEAK.html @@ -4,7 +4,7 @@
- {voice_duration}" + {voice_duration:.1f}"