This commit is contained in:
27942
2026-01-26 17:40:45 +08:00
parent 03cc912bd5
commit 58a750cdb8
2 changed files with 116 additions and 165 deletions

View File

@@ -104,84 +104,44 @@ class WorkerThread(QThread):
self.log_message.emit(f"使用预查找的文件列表,共 {len(self.prepared_files)} 个文件")
self.progress.emit(30)
# 判断是否为视频文件
video_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm']
video_files = [f for f in self.prepared_files if
f['path'].is_file() and any(f['path'].suffix.lower() == ext for ext in video_extensions)]
# 如果勾选了批量上传且是视频调用action1方法
if self.is_batch_mode and video_files:
# 批量上传视频调用action1方法
self.log_message.emit(f"批量上传模式:找到 {len(video_files)} 个视频文件调用action1方法")
try:
result = pdd.action1(folder_path=video_files)
ok = bool(result.get("ok")) if isinstance(result, dict) else True
if not ok:
fails = [r for r in (result.get("results") or []) if not r.get("ok")]
self.log_message.emit(f"发布校验失败条数: {len(fails)}")
for r in fails[:20]:
self.log_message.emit(f"{r.get('name')} | {r.get('reason')}")
# 逐条回传结果给GUI更新表格状态
if isinstance(result, dict):
for r in (result.get("results") or []):
try:
payload = dict(r)
payload["user_id"] = config.get("多多id", "")
self.item_result.emit(payload)
except Exception:
pass
self.progress.emit(100)
self.finished.emit(ok, f"批量上传完成,共处理 {len(video_files)} 个视频ok={ok}")
except Exception as e:
error_msg = f"执行action1方法失败: {str(e)}"
self.log_message.emit(error_msg)
logger.error(error_msg)
import traceback
traceback_str = traceback.format_exc()
logger.error(traceback_str)
self.log_message.emit(f"错误详情: {traceback_str}")
self.finished.emit(False, error_msg)
return
# 单个上传模式使用action方法
# 从预查找的文件中提取文件夹路径(取第一个文件的父目录的父目录,回到最外层文件夹)
first_file = self.prepared_files[0]
if first_file['path'].is_file():
# 文件路径:最外层文件夹/多多ID文件夹/文件名
# 需要回到最外层文件夹
folder_path = str(first_file['path'].parent.parent)
else:
# 单个上传模式使用action方法
# 从预查找的文件中提取文件夹路径(取第一个文件的父目录的父目录,回到最外层文件夹)
if self.prepared_files:
first_file = self.prepared_files[0]
if first_file['path'].is_file():
# 文件路径:最外层文件夹/多多ID文件夹/文件名
# 需要回到最外层文件夹
folder_path = str(first_file['path'].parent.parent)
else:
# 文件夹路径:最外层文件夹/多多ID文件夹/文件夹名
folder_path = str(first_file['path'].parent.parent)
# 文件夹路径:最外层文件夹/多多ID文件夹/文件夹名
folder_path = str(first_file['path'].parent.parent)
self.log_message.emit(f"使用文件夹路径: {folder_path}")
try:
result = pdd.action(folder_path=folder_path, collect_all_videos=False)
ok = bool(result.get("ok")) if isinstance(result, dict) else True
if not ok:
self.log_message.emit(f"发布校验失败: {result.get('reason') if isinstance(result, dict) else ''}")
# 单条回传结果
if isinstance(result, dict):
self.item_result.emit({
"user_id": config.get("多多id", ""),
"index": config.get("序号", ""),
"name": config.get("标题", "") or "",
"ok": ok,
"reason": result.get("reason", ""),
})
self.progress.emit(100)
self.finished.emit(ok, f"单个任务执行完成ok={ok}")
except Exception as e:
error_msg = f"执行action方法失败: {str(e)}"
self.log_message.emit(error_msg)
logger.error(error_msg)
import traceback
traceback_str = traceback.format_exc()
logger.error(traceback_str)
self.log_message.emit(f"错误详情: {traceback_str}")
self.finished.emit(False, error_msg)
return
self.log_message.emit(f"使用文件夹路径: {folder_path}")
try:
result = pdd.action(folder_path=folder_path, collect_all_videos=False)
ok = bool(result.get("ok")) if isinstance(result, dict) else True
if not ok:
self.log_message.emit(f"发布校验失败: {result.get('reason') if isinstance(result, dict) else ''}")
# 单条回传结果
if isinstance(result, dict):
self.item_result.emit({
"user_id": config.get("多多id", ""),
"index": config.get("序号", ""),
"name": config.get("标题", "") or "",
"ok": ok,
"reason": result.get("reason", ""),
})
self.progress.emit(100)
self.finished.emit(ok, f"单个任务执行完成ok={ok}")
except Exception as e:
error_msg = f"执行action方法失败: {str(e)}"
self.log_message.emit(error_msg)
logger.error(error_msg)
import traceback
traceback_str = traceback.format_exc()
logger.error(traceback_str)
self.log_message.emit(f"错误详情: {traceback_str}")
self.finished.emit(False, error_msg)
return
else:
# 未预查找文件,使用原来的逻辑
folder_path = config.get('文件夹路径', '').strip()
@@ -1578,8 +1538,8 @@ class MainWindow(QMainWindow):
def _set_status_item(self, row, text):
"""设置状态列图标与文本"""
try:
# 临时断开 itemChanged 信号,防止递归
self.config_table.itemChanged.disconnect(self.on_table_item_changed)
# 使用 blockSignals 临时阻止信号,防止递归
self.config_table.blockSignals(True)
try:
item = QTableWidgetItem(text)
item.setTextAlignment(Qt.AlignCenter) # 居中对齐
@@ -1596,15 +1556,12 @@ class MainWindow(QMainWindow):
item.setIcon(self.style().standardIcon(QStyle.SP_FileDialogInfoView))
self.config_table.setItem(row, 8, item)
finally:
# 重新连接信号
self.config_table.itemChanged.connect(self.on_table_item_changed)
# 恢复信号
self.config_table.blockSignals(False)
except Exception as e:
logger.warning(f"设置状态项失败: {e}")
# 确保信号重新连接
try:
self.config_table.itemChanged.connect(self.on_table_item_changed)
except:
pass
# 确保信号恢复
self.config_table.blockSignals(False)
def _set_progress_item(self, row, status_text):
"""设置进度列"""
@@ -2397,8 +2354,8 @@ class MainWindow(QMainWindow):
# 3. 如果行可见,更新 UI
if row_idx >= 0 and row_idx < self.config_table.rowCount():
# 临时断开 itemChanged 信号,防止递归
self.config_table.itemChanged.disconnect(self.on_table_item_changed)
# 使用 blockSignals 临时阻止信号,防止递归
self.config_table.blockSignals(True)
try:
item = QTableWidgetItem(str(value))
item.setTextAlignment(Qt.AlignCenter)
@@ -2407,12 +2364,10 @@ class MainWindow(QMainWindow):
item.setBackground(QColor("#E6F4FF"))
self.config_table.setItem(row_idx, col, item)
finally:
self.config_table.itemChanged.connect(self.on_table_item_changed)
self.config_table.blockSignals(False)
except Exception as e:
logger.warning(f"更新单元格失败: {e}")
try:
self.config_table.itemChanged.connect(self.on_table_item_changed)
except: pass
self.config_table.blockSignals(False)
def _update_table_status(self, index, status, is_config_index=False):
"""更新状态列及其对应的表格单元格
@@ -2434,7 +2389,8 @@ class MainWindow(QMainWindow):
# 3. 如果可见,更新 UI
if row_idx >= 0 and row_idx < self.config_table.rowCount():
self.config_table.itemChanged.disconnect(self.on_table_item_changed)
# 使用 blockSignals 临时阻止信号,防止递归
self.config_table.blockSignals(True)
try:
# 第8列是"情况"列,颜色与图标与 _set_status_item 一致
status_item = QTableWidgetItem(status)
@@ -2456,12 +2412,10 @@ class MainWindow(QMainWindow):
status_item.setIcon(self.style().standardIcon(QStyle.SP_FileDialogInfoView))
self.config_table.setItem(row_idx, 8, status_item)
finally:
self.config_table.itemChanged.connect(self.on_table_item_changed)
self.config_table.blockSignals(False)
except Exception as e:
logger.error(f"更新状态失败: {e}")
try:
self.config_table.itemChanged.connect(self.on_table_item_changed)
except: pass
self.config_table.blockSignals(False)
def _highlight_color(self):
@@ -2492,26 +2446,6 @@ class MainWindow(QMainWindow):
return -1
return self._filter_model_columns[k]
def _filter_column_index(self):
"""筛选项下拉对应的表格列索引;-1 表示全部列。排除情况列以多多ID等为筛选项。"""
i = self.table_column_filter.currentIndex()
if i <= 0 or not getattr(self, "_filter_table_columns", None):
return -1
k = i - 1
if k >= len(self._filter_table_columns):
return -1
return self._filter_table_columns[k]
def _filter_model_column_index(self):
"""Model/View 模式下筛选项对应的列索引;-1 表示全部列。"""
i = self.table_column_filter.currentIndex()
if i <= 0 or not getattr(self, "_filter_model_columns", None):
return -1
k = i - 1
if k >= len(self._filter_model_columns):
return -1
return self._filter_model_columns[k]
def _show_all_rows(self):
"""显示全部行"""
for row in range(self.config_table.rowCount()):
@@ -3895,6 +3829,44 @@ class MainWindow(QMainWindow):
self.execute_btn.setEnabled(True)
self.progress_bar.setVisible(False)
def _cleanup_worker_thread(self):
"""安全清理工作线程"""
if not self.worker_thread:
return
try:
# 如果线程正在运行,请求停止
if self.worker_thread.isRunning():
# 请求停止(优雅方式)
self.worker_thread.stop()
self.worker_thread.requestInterruption()
# 等待线程结束,设置合理超时
if not self.worker_thread.wait(2000):
logger.warning("工作线程未能在2秒内停止")
# 不使用 terminate(),继续处理
# 安全断开所有信号连接
try:
self.worker_thread.finished.disconnect()
except (TypeError, RuntimeError):
pass # 信号未连接或已断开
try:
self.worker_thread.log_message.disconnect()
except (TypeError, RuntimeError):
pass
try:
self.worker_thread.progress.disconnect()
except (TypeError, RuntimeError):
pass
try:
self.worker_thread.item_result.disconnect()
except (TypeError, RuntimeError):
pass
except Exception as e:
logger.warning(f"清理工作线程时出错: {e}")
finally:
self.worker_thread = None
def _process_next_batch_task(self):
"""处理任务队列中的下一个任务异步不阻塞GUI"""
# 检查是否还有任务
@@ -3923,33 +3895,7 @@ class MainWindow(QMainWindow):
# 根据任务类型处理
try:
# 清理旧线程(如果存在)
try:
if self.worker_thread:
try:
if self.worker_thread.isRunning():
# 尽量优雅停止,避免 terminate 导致 Qt 对象被强杀后崩溃
try:
self.worker_thread.stop()
except Exception:
pass
try:
self.worker_thread.requestInterruption()
except Exception:
pass
self.worker_thread.wait(1500)
self.worker_thread.finished.disconnect()
self.worker_thread.log_message.disconnect()
self.worker_thread.progress.disconnect()
try:
self.worker_thread.item_result.disconnect()
except Exception:
pass
except Exception:
pass
self.worker_thread = None
except Exception as e:
logger.warning(f"清理旧线程时出错: {e}")
self.worker_thread = None
self._cleanup_worker_thread()
# 验证 files 参数
if not files or not isinstance(files, list):
@@ -4304,27 +4250,8 @@ class MainWindow(QMainWindow):
)
if reply == QMessageBox.Yes:
self._is_closing = True
try:
if self.worker_thread:
# 停止线程
self.worker_thread.stop()
# 等待线程结束,设置超时避免无限等待
if not self.worker_thread.wait(3000): # 等待3秒
logger.warning("线程未能及时停止,强制终止")
self.worker_thread.terminate()
self.worker_thread.wait(1000) # 再等待1秒
# 断开信号连接
try:
self.worker_thread.finished.disconnect()
self.worker_thread.log_message.disconnect()
self.worker_thread.progress.disconnect()
except Exception:
pass
self.worker_thread = None
except Exception as e:
logger.error(f"关闭线程时出错: {e}")
# 使用统一的线程清理方法
self._cleanup_worker_thread()
event.accept()
else:
event.ignore()

26
main.py
View File

@@ -2,6 +2,7 @@ import os
import re
import json
import time
import threading
from pathlib import Path
from loguru import logger
@@ -10,6 +11,29 @@ from curl_cffi import requests
from DrissionPage import ChromiumPage, ChromiumOptions, SessionPage
class ThreadSafeDict:
"""线程安全的字典包装类"""
def __init__(self):
self._dict = {}
self._lock = threading.Lock()
def get(self, key, default=None):
with self._lock:
return self._dict.get(key, default)
def __setitem__(self, key, value):
with self._lock:
self._dict[key] = value
def __getitem__(self, key):
with self._lock:
return self._dict[key]
def __contains__(self, key):
with self._lock:
return key in self._dict
class Pdd:
def __init__(self, url, user_id, time_start, ht, index, title=None):
self.url = url
@@ -1534,7 +1558,7 @@ class Pdd:
return False
datas = {}
datas = ThreadSafeDict()
if __name__ == '__main__':
url = "18 【运动男孩都爱这么穿吗?🏃 - Liu_烫烫 | 小红书 - 你的生活兴趣社区】 😆 D13BaPl6xyUAuQO 😆 https://www.xiaohongshu.com/discovery/item/678ceeef000000001602fb54?source=webshare&xhsshare=pc_web&xsec_token=ABe9oWR9CYCsHBkWUPuoS1Fz3_Uz4WGFMdfCGwSbl0Dfs=&xsec_source=pc_share"