WechatMoments/exporter/html_exporter.py
2024-03-28 23:04:06 +08:00

200 lines
8.4 KiB
Python

import datetime
import json
import shutil
import threading
import time
import xmltodict
from entity.contact import Contact
from exporter.avatar_exporter import AvatarExporter
from exporter.emoji_exporter import EmojiExporter
from exporter.image_exporter import ImageExporter
from exporter.video_exporter import VideoExporter
from log import LOG
from entity.moment_msg import MomentMsg
from pathlib import Path
def get_img_div_css(size: int) -> str:
if size == 1:
return 'width:10rem; overflow:hidden'
else:
return 'width:19rem; overflow:hidden'
def get_img_css(size: int) -> str:
"""object-fit: cover; 预览图居中裁剪
cursor:pointer; 手形鼠标
"""
img_style = "object-fit:cover;cursor:pointer;"
if size == 1:
return f'width:10rem;height:10rem;{img_style}'
elif size == 2:
return f'width:8rem;height:8rem;float:left;margin-bottom:0.2rem;margin-right:0.2rem;{img_style}'
elif size == 4:
return f'width:8rem;height:8rem;float:left;margin-bottom:0.2rem;margin-right:0.2rem;{img_style}'
else:
return f'width:5rem;height:5rem;float:left;margin-bottom:0.2rem;margin-right:0.2rem;{img_style}'
class HtmlExporter(threading.Thread):
def __init__(self, gui: 'Gui', dir_name: str, contacts_map: dict[str, Contact], begin_date: datetime.date,
end_date: datetime.date, download_pic: int, convert_video: int):
self.dir_name = dir_name
if Path(f"output/{self.dir_name}").exists():
shutil.rmtree(f"output/{self.dir_name}")
shutil.copytree("resource/template/", f"output/{self.dir_name}")
self.gui = gui
self.avatar_exporter = AvatarExporter(dir_name)
self.image_exporter = ImageExporter(dir_name)
self.video_exporter = VideoExporter(dir_name)
self.html_head = None
self.html_end = None
self.file = None
self.contacts_map = contacts_map
self.begin_date = begin_date
self.end_date = end_date
self.download_pic = download_pic
self.convert_video = convert_video
self.stop_flag = False
super().__init__()
def run(self) -> None:
with open(f"resource/template.html", encoding='utf-8') as f:
content = f.read()
self.html_head, self.html_end = content.split('/*内容分割线*/')
self.file = open(f"output/{self.dir_name}/index.html", 'w', encoding='utf-8')
if self.gui.account_info and self.gui.account_info.get('wxid'):
self.avatar_exporter.get_avatar_path(self.gui.account_info.get('wxid'))
self.html_head = self.html_head.replace("{my_wxid}", f"{self.gui.account_info.get('wxid')}")
from app.DataBase import micro_msg_db
my_info = micro_msg_db.get_contact_by_username(self.gui.account_info.get('wxid'))
self.html_head = self.html_head.replace("{my_name}", f"{my_info[4]}")
from app.DataBase import sns_db
cover_url = sns_db.get_cover_url()
if cover_url:
cover_path = self.image_exporter.save_image(cover_url, 'image')
self.html_head = self.html_head.replace("{cover_path}", cover_path)
self.file.write(self.html_head)
# 加一天
end_date = self.end_date + datetime.timedelta(days=1)
begin_time = time.mktime(datetime.datetime(self.begin_date.year, self.begin_date.month, self.begin_date.day).timetuple())
end_time = time.mktime(datetime.datetime(end_date.year, end_date.month, end_date.day).timetuple())
self.gui.video_decrypter.decrypt_videos(self, self.begin_date, end_date, self.dir_name, self.convert_video)
message_datas = sns_db.get_messages_in_time(begin_time, end_time)
for index, message_data in enumerate(message_datas):
if not self.stop_flag:
if message_data[0] in self.contacts_map:
self.export_msg(message_data[1], self.contacts_map, self.download_pic)
# 更新进度条 前30%视频处理 后70%其他处理
progress = round(index / len(message_datas) * 70)
self.gui.update_export_progressbar(30 + progress)
self.gui.update_export_progressbar(100)
self.finish_file()
self.gui.export_succeed()
def stop(self) -> None:
self.stop_flag = True
def export_msg(self, message: str, contacts_map: dict[str, Contact], download_pic: int) -> None:
LOG.info(message)
# force_list: 强制要求转media为list
msg_dict = xmltodict.parse(message, force_list={'media'})
msg_json = json.dumps(msg_dict)
msg = MomentMsg.from_json(msg_json)
# 微信ID
username = msg.timelineObject.username
# 头像路径
avatar_path = self.avatar_exporter.get_avatar_path(username)
contact = contacts_map.get(username)
# 备注, 或用户名
remark = contact.remark if contact.remark else contact.nickName
# 朋友圈图片
images = self.image_exporter.get_images(msg, download_pic)
# 朋友圈视频
videos = self.video_exporter.get_videos(msg)
# 样式 3:链接样式
content_style = msg.timelineObject.ContentObject.contentStyle
html = ' <div class="row item">\n'
html += ' <div class="col-xs-2">\n'
html += ' <div class="logo01_box">\n'
html += f' <img src="{avatar_path}" />\n'
html += ' </div>\n'
html += ' </div>\n'
html += ' <div class="col-xs-10 xs8">\n'
html += ' <div class="towp">\n'
html += f' <p class="p1">{remark}</p>\n'
if msg.timelineObject.contentDesc:
content_desc = msg.timelineObject.contentDesc.replace("\n", "<br>")
content_desc = EmojiExporter.replace_emoji(content_desc)
html += f' <p class="p2">{content_desc}</p>\n'
html += ' </div>\n'
# 超链接
if content_style == 3:
html += f' <a href="{msg.timelineObject.ContentObject.contentUrl}" target="_blank">\n'
html += ' <div class ="out_link" >\n'
if images:
thumb_path, image_path = images[0]
html += f' <img src = "{thumb_path}"/>\n'
html += f' <div class ="text" >{msg.timelineObject.ContentObject.title}</div>\n'
html += ' </div >\n'
html += ' </a>\n'
# 视频号
elif msg.timelineObject.ContentObject.finderFeed:
html += f' <div style="width:10rem; overflow:hidden">\n'
# 视频号图片
thumb_path = self.image_exporter.get_finder_images(msg)
html += f' <img src="{thumb_path}" onclick="openWarningOverlay(event)" style="width:10rem;height:10rem;object-fit:cover;cursor:pointer;"/>\n'
html += ' </div>\n'
# 视频号说明
html += ' <div class="texts_box">\n'
nickname = msg.timelineObject.ContentObject.finderFeed.nickname
desc = msg.timelineObject.ContentObject.finderFeed.desc
html += f' <p class="location">视频号 · {nickname} · {desc}</p>\n'
html += ' </div>\n'
# 普通朋友圈
else:
html += f' <div style="{get_img_div_css(len(images))}">\n'
for thumb_path, image_path in images:
html += f' <img src="{thumb_path}" full_img="{image_path}" onclick="openFullSize(event)" style="{get_img_css(len(images))}"/>\n'
html += ' </div>\n'
html += ' <div>\n'
for video_path in videos:
html += f' <video controls height="500">\n'
html += f' <source src="{video_path}" type="video/mp4">\n'
html += f' <video>\n'
html += ' </div>\n'
html += ' <div class="texts_box">\n'
if msg.timelineObject.location and msg.timelineObject.location.poiName:
html += f' <p class="location">{msg.timelineObject.location.poiName}</p>\n'
html += f' <p class="time">{msg.timelineObject.create_time}</p>\n'
html += ' </div>\n'
html += ' </div>\n'
html += '</div>\n'
self.file.write(html)
def finish_file(self):
self.file.write(self.html_end)
self.file.close()