From f136829a2b3bdf94a92e250ef923a87ef72babbe Mon Sep 17 00:00:00 2001 From: ppwwyyxx Date: Sun, 23 Nov 2014 23:09:51 +0800 Subject: [PATCH] image thumbnail --- README.md | 17 ++++++++------- lib/msg.py | 8 +++---- lib/parser.py | 10 +++++++-- lib/render.py | 33 ++++++++++++++++++++++------ lib/res.py | 46 +++++++++++++++++++++++++++++++++++++--- lib/static/TP_IMG.html | 16 ++++++++++++++ lib/static/template.html | 6 +++--- lib/static/wx.css | 2 +- 8 files changed, 109 insertions(+), 29 deletions(-) create mode 100644 lib/static/TP_IMG.html diff --git a/README.md b/README.md index 3ff1b21..3bf3a41 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,15 @@ ### How to use: #### Install Dependencies: -+ numpy -+ PIL -+ audioread ++ python-numpy ++ python-PIL ++ [eyed3](http://eyed3.nicfit.net/) + sox ++ python-csscompressor(optional) #### Get Necessary Data: -+ Get /data/data/com.tencent.mm/MicroMsg/long-long-name/EnMicroMsg.db from rooted phone: -+ Get Wechat resource directory, usually at storage:/tencent/MicroMsg/long-long-name ++ Get /data/data/com.tencent.mm/MicroMsg/long-long-name/EnMicroMsg.db from root filesystem . ++ Get Wechat resource directory from user filesystem, usually at storage:/tencent/MicroMsg/long-long-name. + Get Wechat uin: + login to [web-based wechat](https://wx.qq.com); get wxuin=1234567 from `document.cookie` + Or get ``default_uin`` from /data/data/com.tencent.mm/shared_prefs/system_config_prefs.xml. @@ -28,11 +29,11 @@ ``` ./dump_msg.py decrypted_db.db output_dir ``` -+ Dump messages of one contact to html (for now, raw message only): ++ Dump messages of one contact to an html including voice messages and image thumbnail: ``` ./dump_html.py decrypted_db.db output.html ``` ### TODO -+ parse audio messages, links, emoji and images -+ output to rich-content html ++ parse links, full images, and emoji ++ Fix UI diff --git a/lib/msg.py b/lib/msg.py index 5ff164c..75a0835 100644 --- a/lib/msg.py +++ b/lib/msg.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: msg.py -# Date: Sat Nov 22 23:23:52 2014 +0800 +# Date: Sun Nov 23 20:44:33 2014 +0800 # Author: Yuxin Wu from datetime import datetime @@ -19,13 +19,11 @@ TYPE_VOIP = 50 TYPE_SYSTEM = 10000 class WeChatMsg(object): - """ fields in concern""" FIELDS = ["msgSvrId","type","isSend","createTime","talker","content","imgPath"] - FILTER_TYPES = [TYPE_SYSTEM] @staticmethod - def filter_types(tp): - if tp in WeChatMsg.FILTER_TYPES or tp > 10000 or tp < 0: + def filter_type(tp): + if tp in [TYPE_SYSTEM] or tp > 10000 or tp < 0: return True return False diff --git a/lib/parser.py b/lib/parser.py index d04a26a..39b8f9d 100644 --- a/lib/parser.py +++ b/lib/parser.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: parser.py -# Date: Sun Nov 23 16:33:20 2014 +0800 +# Date: Sun Nov 23 20:44:02 2014 +0800 # Author: Yuxin Wu import sqlite3 @@ -51,7 +51,7 @@ SELECT {} FROM message """.format(','.join(WeChatMsg.FIELDS))) for row in db_msgs: msg = WeChatMsg(row) - if msg.type not in WeChatMsg.FILTER_TYPES: + if not WeChatMsg.filter_type(msg.type): self.msgs_by_talker[msg.talker].append(msg) self.msgs_by_talker = dict([ (self.contacts[k], sorted(v, key=lambda x: x.createTime)) @@ -68,6 +68,11 @@ SELECT {} FROM message self.username = userinfo[2] print "Your username is: {}".format(self.username) + def _parse_imginfo(self): + imginfo_q = self.cc.execute("""SELECT msgSvrId, bigImgPath FROM ImgInfo2""") + self.imginfo = dict([(k, v) for (k, v) in imginfo_q if not v.startswith('SERVERID://')]) + print "Got {} big images.".format(len(self.imginfo)) + def _find_msg_by_type(self, msgs=None): ret = [] if msgs is None: @@ -81,3 +86,4 @@ SELECT {} FROM message self._parse_userinfo() self._parse_contact() self._parse_msg() + self._parse_imginfo() diff --git a/lib/render.py b/lib/render.py index 8daf26a..3a2aa78 100755 --- a/lib/render.py +++ b/lib/render.py @@ -1,12 +1,12 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: render.py -# Date: Sun Nov 23 18:01:55 2014 +0800 +# Date: Sun Nov 23 22:57:13 2014 +0800 # Author: Yuxin Wu import os import base64 -import audioread +import eyed3 LIB_PATH = os.path.dirname(os.path.abspath(__file__)) CSS_FILE = os.path.join(LIB_PATH, 'static/wx.css') HTML_FILE = os.path.join(LIB_PATH, 'static/template.html') @@ -19,7 +19,9 @@ except: from .msg import * from .utils import ensure_unicode -TEMPLATES_FILES = {TYPE_MSG: "TP_MSG", TYPE_SPEAK: "TP_SPEAK"} +TEMPLATES_FILES = {TYPE_MSG: "TP_MSG", + TYPE_SPEAK: "TP_SPEAK", + TYPE_IMG: "TP_IMG"} TEMPLATES = dict([(k, open(os.path.join( LIB_PATH, 'static/{}.html'.format(v))).read()) for k, v in TEMPLATES_FILES.iteritems()]) @@ -40,19 +42,23 @@ class HTMLRender(object): return (avt1, avt2) def get_voice_mp3(self, imgpath): - """ return base64 string""" + """ return base64 string, and voice duration""" if self.res is None: return "" amr_fpath = self.res.speak_data[imgpath] mp3_file = os.path.join('/tmp', os.path.basename(amr_fpath)[:-4] + '.mp3') - os.system('sox {} {}'.format(amr_fpath, mp3_file)) + # TODO is there a library to use? + ret = os.system('sox {} {}'.format(amr_fpath, mp3_file)) + if ret != 0: + print "Sox Failed!" + return "" mp3_string = open(mp3_file, 'rb').read() - duration = audioread.audio_open(mp3_file).duration + duration = eyed3.load(mp3_file).info.time_secs os.unlink(mp3_file) return base64.b64encode(mp3_string), duration def render_msg(self, msg): - """ render a message, return the block""" + """ render a message, return the html block""" # TODO try: template = ensure_unicode(TEMPLATES[msg.type]) @@ -61,6 +67,19 @@ class HTMLRender(object): return template.format(sender_label='you' if not msg.isSend else 'me', voice_duration=duration, voice_str=audio_str) + elif msg.type == TYPE_IMG: + img = "" + svrid = msg.msgSvrId + imgpath = msg.imgPath + #bigimg = self.parser.imginfo.get(svrid) + #if bigimg: + #img = self.res.get_img(bigimg, smart=False) + if len(img) == 0: + #print "Big Image Failed: {}".format(bigimg) + img = imgpath.split('_')[-1] + img = self.res.get_img(img) + return template.format(sender_label='you' if not msg.isSend else 'me', + img_data=img) else: raise except: diff --git a/lib/res.py b/lib/res.py index c3f722c..b973cdd 100644 --- a/lib/res.py +++ b/lib/res.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2 # -*- coding: UTF-8 -*- # File: res.py -# Date: Sun Nov 23 15:59:48 2014 +0800 +# Date: Sun Nov 23 21:08:44 2014 +0800 # Author: Yuxin Wu import glob @@ -13,12 +13,14 @@ import base64 from lib.avatar import AvatarReader VOICE_DIRNAME = 'voice2' +JPEG_QUALITY = 50 class Resource(object): """ multimedia resources in chat""" def __init__(self, res_dir): assert os.path.isdir(res_dir), "No such directory: {}".format(res_dir) self.res_dir = res_dir + self.img_dir = os.path.join(res_dir, 'image2') self.avt_reader = AvatarReader(self.res_dir) self.init() @@ -38,6 +40,11 @@ class Resource(object): "Error interpreting the protocol, this is a bug!" self.speak_data[key] = full_path + @staticmethod + def get_file_b64(fname): + data = open(fname, 'rb').read() + return base64.b64encode(data) + def get_avatar(self, username): """ return base64 string""" ret = self.avt_reader.get_avatar(username) @@ -45,9 +52,42 @@ class Resource(object): return "" im = Image.fromarray(ret) buf = cStringIO.StringIO() - im.save(buf, 'JPEG', quality=50) + im.save(buf, 'JPEG', quality=JPEG_QUALITY) jpeg_str = buf.getvalue() return base64.b64encode(jpeg_str) + def get_img_file(self, fname, smart): + """ return base64 string""" + dir1, dir2 = fname[:2], fname[2:4] + if not smart: + filename = os.path.join(self.img_dir, dir1, dir2, fname) + if os.path.isfile(filename): + return filename + else: + dirname = os.path.join(self.img_dir, dir1, dir2) + if not os.path.isdir(dirname): + print "Directory not found: {}".format(dirname) + return "" + maxf, maxsize = "", 0 + for f in os.listdir(dirname): + if fname in f: + full_name = os.path.join(dirname, f) + size = os.path.getsize(full_name) + if size > maxsize: + maxsize = size + maxf = full_name + if maxsize == 0: + return "" + else: + return maxf + return "" - + def get_img(self, fname, smart=True): + img_file = self.get_img_file(fname, smart) + if not img_file.endswith('jpg') or not img_file.startswith('th_'): + im = Image.open(open(img_file, 'rb')) + buf = cStringIO.StringIO() + im.save(buf, 'JPEG', quality=JPEG_QUALITY) + return base64.b64encode(buf.getvalue()) + else: + return Resource.get_file_b64(img_file) diff --git a/lib/static/TP_IMG.html b/lib/static/TP_IMG.html new file mode 100644 index 0000000..d8ffb6c --- /dev/null +++ b/lib/static/TP_IMG.html @@ -0,0 +1,16 @@ +
+
+ +
+
+
+
+ + + +
+
+
+
+
+
diff --git a/lib/static/template.html b/lib/static/template.html index fa18ffa..70c55ba 100644 --- a/lib/static/template.html +++ b/lib/static/template.html @@ -10,14 +10,14 @@
-
-
+
+

{talker}

-
+
{messages}
diff --git a/lib/static/wx.css b/lib/static/wx.css index cdc0472..1539dd8 100644 --- a/lib/static/wx.css +++ b/lib/static/wx.css @@ -1392,7 +1392,7 @@ a.btnSecondary:active { } /*会话内容窗体*/ .chatPanel .chatScorll { - height: 390px; + height: 100%; width: 100%; min-height: 200px; overflow: hidden;