From b1a6f52148c027e9716fd1b1a8acfdf892cccae0 Mon Sep 17 00:00:00 2001 From: shuaikangzhou <863909694@qq.com> Date: Fri, 22 Dec 2023 22:42:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A9=E7=94=A8=E5=A4=9A=E7=BA=BF=E7=A8=8B?= =?UTF-8?q?=E5=A4=84=E7=90=86=E8=AF=AD=E9=9F=B3=E5=92=8C=E8=A1=A8=E6=83=85?= =?UTF-8?q?=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/DataBase/media_msg.py | 23 ++++++--- app/DataBase/msg.py | 5 +- app/DataBase/output_pc.py | 91 ++++++++++++++++++++++++++++----- app/ui/contact/export_dialog.py | 35 +++++++++++-- app/util/emoji.py | 20 ++++++-- 5 files changed, 145 insertions(+), 29 deletions(-) diff --git a/app/DataBase/media_msg.py b/app/DataBase/media_msg.py index 1cee12d..77b47a7 100644 --- a/app/DataBase/media_msg.py +++ b/app/DataBase/media_msg.py @@ -55,12 +55,16 @@ class MediaMsg: try: lock.acquire(True) self.cursor.execute(sql, [reserved0]) - return self.cursor.fetchone()[0] + result = self.cursor.fetchone() + finally: lock.release() + return result[0] if result else None def get_audio(self, reserved0, output_path): buf = self.get_media_buffer(reserved0) + if not buf: + return '' silk_path = f"{output_path}\\{reserved0}.silk" pcm_path = f"{output_path}\\{reserved0}.pcm" mp3_path = f"{output_path}\\{reserved0}.mp3" @@ -76,10 +80,10 @@ class MediaMsg: try: # 调用系统上的 ffmpeg 可执行文件 # 获取 FFmpeg 可执行文件的路径 - ffmpeg_path = get_ffmpeg_path() - # 调用 FFmpeg - cmd = f'''{ffmpeg_path} -loglevel quiet -y -f s16le -i {pcm_path} -ar 44100 -ac 1 {mp3_path}''' - system(cmd) + # ffmpeg_path = get_ffmpeg_path() + # # 调用 FFmpeg + # cmd = f'''{ffmpeg_path} -loglevel quiet -y -f s16le -i {pcm_path} -ar 44100 -ac 1 {mp3_path}''' + # system(cmd) # 源码运行的时候下面的有效 # 这里不知道怎么捕捉异常 cmd = f'''{os.path.join(os.getcwd(), 'app', 'resources', 'ffmpeg.exe')} -loglevel quiet -y -f s16le -i {pcm_path} -ar 44100 -ac 1 {mp3_path}''' @@ -88,11 +92,14 @@ class MediaMsg: print(f"Error: {e}") cmd = f'''{os.path.join(os.getcwd(),'app','resources','ffmpeg.exe')} -loglevel quiet -y -f s16le -i {pcm_path} -ar 44100 -ac 1 {mp3_path}''' system(cmd) - system(f'del {silk_path}') - system(f'del {pcm_path}') + # system(f'del {silk_path}') + # system(f'del {pcm_path}') print(mp3_path) return mp3_path - + def get_audio_path(self, reserved0, output_path): + mp3_path = f"{output_path}\\{reserved0}.mp3" + mp3_path = mp3_path.replace("/", "\\") + return mp3_path def get_audio_text(self, content): try: root = ET.fromstring(content) diff --git a/app/DataBase/msg.py b/app/DataBase/msg.py index db3f09b..267e751 100644 --- a/app/DataBase/msg.py +++ b/app/DataBase/msg.py @@ -154,15 +154,15 @@ class Msg: sql = ''' select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID,BytesExtra,CompressContent from MSG - where StrTalker=? and Type=? + where StrTalker=? and Type=? order by CreateTime ''' try: lock.acquire(True) self.cursor.execute(sql, [username_, type_]) + result = self.cursor.fetchall() finally: lock.release() - result = self.cursor.fetchall() else: sql = ''' select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID,BytesExtra,CompressContent @@ -610,3 +610,4 @@ if __name__ == '__main__': msg.init_database() print(msg.get_latest_time_of_message('wxid_0o18ef858vnu22', year_='2023')) print(msg.get_messages_number('wxid_0o18ef858vnu22', year_='2023')) + print(msg.get_messages_by_type('wxid_0o18ef858vnu22',34)) diff --git a/app/DataBase/output_pc.py b/app/DataBase/output_pc.py index d4700e6..f8afa96 100644 --- a/app/DataBase/output_pc.py +++ b/app/DataBase/output_pc.py @@ -15,7 +15,7 @@ from ..util import path import shutil from ..util.compress_content import parser_reply -from ..util.emoji import get_emoji +from ..util.emoji import get_emoji, get_emoji_path os.makedirs('./data/聊天记录', exist_ok=True) @@ -131,17 +131,33 @@ class Output(QThread): self.Child = ChildThread(self.contact, type_=self.output_type, message_types=self.message_types) self.Child.progressSignal.connect(self.progress) self.Child.rangeSignal.connect(self.rangeSignal) - self.Child.okSignal.connect(self.okSignal) + self.Child.okSignal.connect(self.count_finish_num) self.Child.start() + # 语音消息单独的线程 + self.output_media = OutputMedia(self.contact) + self.output_media.okSingal.connect(self.count_finish_num) + self.output_media.progressSignal.connect(self.progress) + self.output_media.start() + # emoji消息单独的线程 + self.output_emoji = OutputEmoji(self.contact) + self.output_emoji.okSingal.connect(self.count_finish_num) + self.output_emoji.progressSignal.connect(self.progress) + self.output_emoji.start() + self.total_num = 3 + + def count_finish_num(self, num): + self.num += 1 + if self.num == self.total_num: + self.okSignal.emit(1) def cancel(self): self.requestInterruption() -def modify_audio_metadata(audiofile, new_artist): # 修改音频元数据中的“创作者”标签 +def modify_audio_metadata(audiofile, new_artist): # 修改音频元数据中的“创作者”标签 return audiofile = load(audiofile) - + # 检查文件是否有标签 if audiofile.tag is None: audiofile.initTag() @@ -246,7 +262,7 @@ class ChildThread(QThread): try: os.utime(origin_docx_path + image_path[1:], (timestamp, timestamp)) except: - print("网络图片",image_path) + print("网络图片", image_path) pass image_path = image_path.replace('\\', '/') doc.write( @@ -280,7 +296,7 @@ class ChildThread(QThread): displayname = escape_js_and_html(displayname) if self.output_type == Output.HTML: try: - audio_path = media_msg_db.get_audio(msgSvrId, output_path=origin_docx_path + "/voice") + audio_path = media_msg_db.get_audio_path(msgSvrId, output_path=origin_docx_path + "/voice") audio_path = audio_path.replace('/', '\\') modify_audio_metadata(audio_path, displayname) os.utime(audio_path, (timestamp, timestamp)) @@ -318,7 +334,7 @@ class ChildThread(QThread): displayname = MePC().name if is_send else self.contact.remark displayname = escape_js_and_html(displayname) if self.output_type == Output.HTML: - emoji_path = get_emoji(str_content, thumb=True, output_path=origin_docx_path + '/emoji') + emoji_path = get_emoji_path(str_content, thumb=True, output_path=origin_docx_path + '/emoji') emoji_path = './emoji/' + os.path.basename(emoji_path) doc.write( f'''{{ type:{3}, text: '{emoji_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{displayname}'}},''' @@ -388,7 +404,8 @@ class ChildThread(QThread): str_time = message[8] timestamp = message[5] is_chatroom = 1 if self.contact.is_chatroom else 0 - str_content = str_content.replace('重新编辑]]>', "") + str_content = str_content.replace('重新编辑]]>', "") res = findall('()', str_content) for xmlstr, b in res: str_content = str_content.replace(xmlstr, "") @@ -495,13 +512,13 @@ class ChildThread(QThread): stream.setCodec('utf-8') content = stream.readAll() file.close() - html_head,html_end = content.split('/*注意看这是分割线*/') + html_head, html_end = content.split('/*注意看这是分割线*/') f = open(filename, 'w', encoding='utf-8') f.write(html_head.replace("Chat Records", f"{self.contact.remark}")) MePC().avatar.save(os.path.join(f"{origin_docx_path}/avatar/{MePC().wxid}.png")) if self.contact.is_chatroom: for message in messages: - if message[4]: # is_send + if message[4]: # is_send continue try: chatroom_avatar_path = f"{origin_docx_path}/avatar/{message[12].wxid}.png" @@ -513,12 +530,12 @@ class ChildThread(QThread): else: self.contact.avatar.save(os.path.join(f"{origin_docx_path}/avatar/{self.contact.wxid}.png")) self.rangeSignal.emit(len(messages)) - total_steps = len(messages) for index, message in enumerate(messages): type_ = message[2] sub_type = message[3] timestamp = message[5] - self.progressSignal.emit(int((index + 1) / total_steps * 100)) + if type_ != 34 and type_ != 47: + self.progressSignal.emit(1) if self.is_5_min(timestamp): str_time = message[8] f.write( @@ -582,4 +599,52 @@ class ChildThread(QThread): self.to_txt() def cancel(self): - self.requestInterruption() \ No newline at end of file + self.requestInterruption() + + +class OutputMedia(QThread): + okSingal = pyqtSignal(int) + progressSignal = pyqtSignal(int) + + def __init__(self, contact): + super().__init__() + self.contact = contact + + def run(self): + origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}" + messages = msg_db.get_messages_by_type(self.contact.wxid, 34) + for message in messages: + is_send = message[4] + msgSvrId = message[9] + audio_path = media_msg_db.get_audio(msgSvrId, output_path=origin_docx_path + "/voice") + audio_path = audio_path.replace('/', '\\') + if self.contact.is_chatroom: + if is_send: + displayname = MePC().name + else: + displayname = message[12].remark + else: + displayname = MePC().name if is_send else self.contact.remark + displayname = escape_js_and_html(displayname) + modify_audio_metadata(audio_path, displayname) + # os.utime(audio_path, (timestamp, timestamp)) + self.progressSignal.emit(1) + self.okSingal.emit(34) + + +class OutputEmoji(QThread): + okSingal = pyqtSignal(int) + progressSignal = pyqtSignal(int) + + def __init__(self, contact): + super().__init__() + self.contact = contact + + def run(self): + origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}" + messages = msg_db.get_messages_by_type(self.contact.wxid, 47) + for message in messages: + str_content = message[7] + emoji_path = get_emoji(str_content, thumb=True, output_path=origin_docx_path + '/emoji') + self.progressSignal.emit(1) + self.okSingal.emit(34) diff --git a/app/ui/contact/export_dialog.py b/app/ui/contact/export_dialog.py index d83d874..1776103 100644 --- a/app/ui/contact/export_dialog.py +++ b/app/ui/contact/export_dialog.py @@ -1,3 +1,6 @@ +import time + +from PyQt5.QtCore import QTimer from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QDialog, QVBoxLayout, QCheckBox, QHBoxLayout, \ QProgressBar, QLabel, QMessageBox @@ -20,6 +23,7 @@ QPushButton:hover { } """ + class ExportDialog(QDialog): def __init__(self, contact=None, title="选择导出的类型", file_type="csv", parent=None): super(ExportDialog, self).__init__(parent) @@ -27,14 +31,15 @@ class ExportDialog(QDialog): self.contact = contact if file_type == 'html': self.export_type = Output.HTML - self.export_choices = {"文本": True, "图片": True, "语音": True, "视频": True, "表情包": True, + self.export_choices = {"文本": True, "图片": True, "语音": False, "视频": False, "表情包": False, '拍一拍等系统消息': True} # 定义导出的数据类型,默认全部选择 elif file_type == 'csv': self.export_type = Output.CSV self.export_choices = {"文本": True, "图片": True, "视频": True, "表情包": True} # 定义导出的数据类型,默认全部选择 elif file_type == 'txt': self.export_type = Output.TXT - self.export_choices = {"文本": True, "图片": True, "语音": True, "视频": True, "表情包": True} # 定义导出的数据类型,默认全部选择 + self.export_choices = {"文本": True, "图片": True, "语音": True, "视频": True, + "表情包": True} # 定义导出的数据类型,默认全部选择 else: self.export_choices = {"文本": True, "图片": True, "视频": True, "表情包": True} # 定义导出的数据类型,默认全部选择 self.setWindowTitle(title) @@ -43,14 +48,17 @@ class ExportDialog(QDialog): self.worker = None # 导出线程 self.progress_bar = QProgressBar(self) self.progress_label = QLabel(self) + self.time_label = QLabel(self) for export_type, default_state in self.export_choices.items(): checkbox = QCheckBox(export_type) checkbox.setChecked(default_state) layout.addWidget(checkbox) layout.addWidget(self.progress_bar) layout.addWidget(self.progress_label) + layout.addWidget(self.time_label) self.notice_label = QLabel(self) - self.notice_label.setText("注意:导出HTML时选择图片、视频、语音、表情包(特别是表情包)\n会导致大大影响导出速度,请合理选择导出的类型") + self.notice_label.setText( + "注意:导出HTML时选择图片、视频、语音、表情包(特别是表情包)\n会导致大大影响导出速度,请合理选择导出的类型") layout.addWidget(self.notice_label) hlayout = QHBoxLayout(self) self.export_button = QPushButton("导出") @@ -62,6 +70,11 @@ class ExportDialog(QDialog): hlayout.addWidget(self.cancel_button) layout.addLayout(hlayout) self.setLayout(layout) + self.timer = QTimer(self) + self.time = 0 + self.total_msg_num = 100 # 总的消息个数 + self.num = 0 # 当前完成的消息个数 + self.timer.timeout.connect(self.update_elapsed_time) def export_data(self): self.export_button.setEnabled(False) @@ -75,12 +88,22 @@ class ExportDialog(QDialog): self.worker = Output(self.contact, type_=self.export_type, message_types=selected_types) self.worker.progressSignal.connect(self.update_progress) self.worker.okSignal.connect(self.export_finished) + self.worker.rangeSignal.connect(self.set_total_msg_num) self.worker.start() + # 启动定时器,每1000毫秒更新一次任务进度 + self.timer.start(1000) + self.start_time = time.time() # self.accept() # 使用accept关闭对话框 + def set_total_msg_num(self, num): + self.total_msg_num = num + def export_finished(self): self.export_button.setEnabled(True) self.cancel_button.setEnabled(True) + self.time = 0 + end_time = time.time() + print(f'总耗时:{end_time - self.start_time}s') reply = QMessageBox(self) reply.setIcon(QMessageBox.Information) reply.setWindowTitle('OK') @@ -90,7 +113,13 @@ class ExportDialog(QDialog): api = reply.exec_() self.accept() + def update_elapsed_time(self): + self.time += 1 + self.time_label.setText(f"耗时: {self.time}s") + def update_progress(self, progress_percentage): + self.num += 1 + progress_percentage = int((self.num) / self.total_msg_num * 100) self.progress_bar.setValue(progress_percentage) self.progress_label.setText(f"导出进度: {progress_percentage}%") diff --git a/app/util/emoji.py b/app/util/emoji.py index ddafc5f..e5886ae 100644 --- a/app/util/emoji.py +++ b/app/util/emoji.py @@ -174,9 +174,9 @@ def get_most_emoji(messages): except: dic[md5] = [1, emoji_info] md5_nums = [(num[0], key, num[1]) for key, num in dic.items()] - md5_nums.sort(key=lambda x: x[0],reverse=True) + md5_nums.sort(key=lambda x: x[0], reverse=True) if not md5_nums: - return '',0 + return '', 0 md5 = md5_nums[0][1] num = md5_nums[0][0] emoji_info = md5_nums[0][2] @@ -195,7 +195,6 @@ def get_emoji(xml_string, thumb=True, output_path=root_path) -> str: prefix = 'th_' if thumb else '' file_path = os.path.join(output_path, prefix + md5 + f) if os.path.exists(file_path): - print('表情包已存在') return file_path url = emoji_info['thumburl'] if thumb else emoji_info['cdnurl'] if not url or url == "": @@ -231,6 +230,21 @@ def get_emoji(xml_string, thumb=True, output_path=root_path) -> str: return output_path +def get_emoji_path(xml_string, thumb=True, output_path=root_path) -> str: + try: + emoji_info = parser_xml(xml_string) + md5 = emoji_info['md5'] + image_format = ['.png', '.gif', '.jpeg'] + for f in image_format: + prefix = 'th_' if thumb else '' + file_path = os.path.join(output_path, prefix + md5 + f) + return file_path + except: + logger.error(traceback.format_exc()) + output_path = os.path.join(output_path, "404.png") + return output_path + + if __name__ == '__main__': # xml_string = ' ' # res1 = parser_xml(xml_string)