image thumbnail

This commit is contained in:
ppwwyyxx
2014-11-23 23:12:07 +08:00
parent 618e12ec00
commit f136829a2b
8 changed files with 109 additions and 29 deletions
+9 -8
View File
@@ -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 <resource directory> <contact name> output.html
```
### TODO
+ parse audio messages, links, emoji and images
+ output to rich-content html
+ parse links, full images, and emoji
+ Fix UI
+3 -5
View File
@@ -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 <[email protected]>
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
+8 -2
View File
@@ -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 <[email protected]>
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()
+26 -7
View File
@@ -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 <[email protected]>
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:
+43 -3
View File
@@ -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 <[email protected]>
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)
+16
View File
@@ -0,0 +1,16 @@
<div class="chatItem {sender_label}">
<div class="chatItemContent">
<img class="avatar" src="">
<div class="cloud cloudImg">
<div class="cloudPannel">
<div class="cloudBody">
<div class="cloudContent">
<span class="img_wrap">
<img class="zoomIn imageBorder" src="data:image/jpeg;base64,{img_data}" onclick="popImg(event)">
</span>
</div>
</div>
</div>
</div>
</div>
</div>
+3 -3
View File
@@ -10,14 +10,14 @@
<body>
<div id="container">
<div id="chat" class="chatPanel normalPanel">
<div class="chatContainer" style="height: 600px;">
<div class="chatMainPanel" id="chatMainPanel">
<div class="chatContainer" style="min-height:100%;">
<div class="chatMainPanel" id="chatMainPanel" style="height:100%;">
<div class="chatTitle">
<div class="chatNameWrap">
<p class="chatName" id="messagePanelTitle">{talker}</p>
</div>
</div>
<div class="chatScorll" style="height: 550px; overflow-y: scroll; position: relative; font-family:initial;">
<div class="chatScorll" style="height: 95%; overflow-y: scroll; position: relative; font-family:initial;">
<div id="chat_chatmsglist" class="chatContent" style="position: absolute;">
{messages}
</div>
+1 -1
View File
@@ -1392,7 +1392,7 @@ a.btnSecondary:active {
}
/*会话内容窗体*/
.chatPanel .chatScorll {
height: 390px;
height: 100%;
width: 100%;
min-height: 200px;
overflow: hidden;