hahaa
719
gui_app.py
@@ -11,7 +11,7 @@ from PyQt5.QtWidgets import (
|
||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QPushButton, QLabel, QLineEdit, QTextEdit, QFileDialog,
|
||||
QTableWidget, QTableWidgetItem, QMessageBox, QDateTimeEdit,
|
||||
QGroupBox, QCheckBox, QProgressBar
|
||||
QGroupBox, QCheckBox, QProgressBar, QGridLayout
|
||||
)
|
||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QDateTime
|
||||
from PyQt5.QtGui import QFont
|
||||
@@ -456,11 +456,14 @@ class MainWindow(QMainWindow):
|
||||
self.worker_thread = None
|
||||
self.configs = [] # 存储从Excel导入的配置
|
||||
self.prepared_files = None # 存储通过"更新数据"找到的文件列表
|
||||
self.running_total = 0
|
||||
self.running_done = 0
|
||||
self.init_ui()
|
||||
|
||||
def init_ui(self):
|
||||
self.setWindowTitle("拼多多自动化发布工具")
|
||||
self.setGeometry(100, 100, 1000, 800)
|
||||
self.apply_styles()
|
||||
|
||||
# 创建中央部件
|
||||
central_widget = QWidget()
|
||||
@@ -468,7 +471,37 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 主布局
|
||||
main_layout = QVBoxLayout()
|
||||
main_layout.setContentsMargins(20, 20, 20, 20)
|
||||
main_layout.setSpacing(14)
|
||||
central_widget.setLayout(main_layout)
|
||||
|
||||
# 顶部标题区
|
||||
header_layout = QHBoxLayout()
|
||||
title_box = QVBoxLayout()
|
||||
title_label = QLabel("拼多多自动化发布工具")
|
||||
title_label.setObjectName("titleLabel")
|
||||
subtitle_label = QLabel("配置导入 • 文件查找 • 批量上传")
|
||||
subtitle_label.setObjectName("subtitleLabel")
|
||||
title_box.addWidget(title_label)
|
||||
title_box.addWidget(subtitle_label)
|
||||
header_layout.addLayout(title_box)
|
||||
header_layout.addStretch()
|
||||
main_layout.addLayout(header_layout)
|
||||
|
||||
# 状态卡片区
|
||||
status_layout = QHBoxLayout()
|
||||
self.status_update_value = QLabel("未更新")
|
||||
self.status_pending_value = QLabel("0")
|
||||
self.status_running_value = QLabel("0")
|
||||
|
||||
update_card = self._build_status_card("更新状态", self.status_update_value)
|
||||
pending_card = self._build_status_card("待执行", self.status_pending_value)
|
||||
running_card = self._build_status_card("执行中", self.status_running_value)
|
||||
|
||||
status_layout.addWidget(update_card)
|
||||
status_layout.addWidget(pending_card)
|
||||
status_layout.addWidget(running_card)
|
||||
main_layout.addLayout(status_layout)
|
||||
|
||||
# Excel导入区域
|
||||
import_group = QGroupBox("Excel配置导入(可选)")
|
||||
@@ -492,98 +525,70 @@ class MainWindow(QMainWindow):
|
||||
# 配置输入区域
|
||||
config_group = QGroupBox("配置信息")
|
||||
config_layout = QVBoxLayout()
|
||||
|
||||
# 多多ID
|
||||
h2 = QHBoxLayout()
|
||||
h2.addWidget(QLabel("多多ID:"))
|
||||
|
||||
grid = QGridLayout()
|
||||
grid.setHorizontalSpacing(12)
|
||||
grid.setVerticalSpacing(10)
|
||||
|
||||
self.user_id_input = QLineEdit()
|
||||
h2.addWidget(self.user_id_input)
|
||||
config_layout.addLayout(h2)
|
||||
|
||||
# 序号
|
||||
h3 = QHBoxLayout()
|
||||
h3.addWidget(QLabel("序号:"))
|
||||
self.index_input = QLineEdit()
|
||||
h3.addWidget(self.index_input)
|
||||
config_layout.addLayout(h3)
|
||||
|
||||
# 话题
|
||||
h4 = QHBoxLayout()
|
||||
h4.addWidget(QLabel("话题:"))
|
||||
self.topic_input = QLineEdit()
|
||||
h4.addWidget(self.topic_input)
|
||||
config_layout.addLayout(h4)
|
||||
|
||||
# 定时发布
|
||||
h5 = QHBoxLayout()
|
||||
h5.addWidget(QLabel("定时发布:"))
|
||||
self.schedule_datetime = QDateTimeEdit()
|
||||
self.schedule_datetime.setCalendarPopup(True)
|
||||
self.schedule_datetime.setDateTime(QDateTime.currentDateTime())
|
||||
h5.addWidget(self.schedule_datetime)
|
||||
config_layout.addLayout(h5)
|
||||
|
||||
# 达人链接
|
||||
h6 = QHBoxLayout()
|
||||
h6.addWidget(QLabel("达人链接:"))
|
||||
self.url_input = QLineEdit()
|
||||
h6.addWidget(self.url_input)
|
||||
config_layout.addLayout(h6)
|
||||
|
||||
# 执行人
|
||||
h7 = QHBoxLayout()
|
||||
h7.addWidget(QLabel("执行人:"))
|
||||
self.executor_input = QLineEdit()
|
||||
h7.addWidget(self.executor_input)
|
||||
config_layout.addLayout(h7)
|
||||
|
||||
|
||||
grid.addWidget(QLabel("多多ID:"), 0, 0)
|
||||
grid.addWidget(self.user_id_input, 0, 1)
|
||||
grid.addWidget(QLabel("序号:"), 0, 2)
|
||||
grid.addWidget(self.index_input, 0, 3)
|
||||
|
||||
grid.addWidget(QLabel("话题:"), 1, 0)
|
||||
grid.addWidget(self.topic_input, 1, 1)
|
||||
grid.addWidget(QLabel("定时发布:"), 1, 2)
|
||||
grid.addWidget(self.schedule_datetime, 1, 3)
|
||||
|
||||
grid.addWidget(QLabel("达人链接:"), 2, 0)
|
||||
grid.addWidget(self.url_input, 2, 1)
|
||||
grid.addWidget(QLabel("执行人:"), 2, 2)
|
||||
grid.addWidget(self.executor_input, 2, 3)
|
||||
|
||||
# 文件夹路径(最外层文件夹)
|
||||
h8 = QHBoxLayout()
|
||||
h8.addWidget(QLabel("资料文件夹路径:"))
|
||||
grid.addWidget(QLabel("资料文件夹路径:"), 3, 0)
|
||||
self.folder_path_input = QLineEdit()
|
||||
# 设置默认值为桌面的"多多自动化发文"
|
||||
default_path = get_default_folder_path()
|
||||
self.folder_path_input.setPlaceholderText(f"留空则使用默认路径: {default_path}")
|
||||
h8.addWidget(self.folder_path_input)
|
||||
grid.addWidget(self.folder_path_input, 3, 1, 1, 2)
|
||||
self.folder_browse_btn = QPushButton("浏览")
|
||||
self.folder_browse_btn.setProperty("variant", "secondary")
|
||||
self.folder_browse_btn.clicked.connect(self.browse_folder)
|
||||
h8.addWidget(self.folder_browse_btn)
|
||||
config_layout.addLayout(h8)
|
||||
|
||||
# 添加提示标签
|
||||
grid.addWidget(self.folder_browse_btn, 3, 3)
|
||||
|
||||
tip_label = QLabel("提示:只需填写最外层文件夹路径,程序会自动查找子文件夹中的文件")
|
||||
tip_label.setStyleSheet("color: #666; font-size: 10px;")
|
||||
config_layout.addWidget(tip_label)
|
||||
|
||||
# 文件路径字段(用于单个文件或文件列表)
|
||||
h8_1 = QHBoxLayout()
|
||||
h8_1.addWidget(QLabel("文件路径:"))
|
||||
self.file_path_input = QLineEdit()
|
||||
self.file_path_input.setPlaceholderText("可选:填写单个文件路径或留空使用文件夹路径查找")
|
||||
h8_1.addWidget(self.file_path_input)
|
||||
self.file_browse_btn = QPushButton("浏览文件")
|
||||
self.file_browse_btn.clicked.connect(self.browse_file)
|
||||
h8_1.addWidget(self.file_browse_btn)
|
||||
config_layout.addLayout(h8_1)
|
||||
|
||||
grid.addWidget(tip_label, 4, 0, 1, 4)
|
||||
|
||||
# 更新数据按钮
|
||||
h8_2 = QHBoxLayout()
|
||||
update_row = QHBoxLayout()
|
||||
self.update_data_btn = QPushButton("更新数据")
|
||||
self.update_data_btn.setStyleSheet("background-color: #2196F3; color: white; font-size: 12px; padding: 8px;")
|
||||
self.update_data_btn.setProperty("variant", "primary")
|
||||
self.update_data_btn.clicked.connect(self.update_data)
|
||||
h8_2.addWidget(self.update_data_btn)
|
||||
update_row.addWidget(self.update_data_btn)
|
||||
self.update_status_label = QLabel("未更新")
|
||||
self.update_status_label.setStyleSheet("color: #666; font-size: 10px;")
|
||||
h8_2.addWidget(self.update_status_label)
|
||||
h8_2.addStretch()
|
||||
config_layout.addLayout(h8_2)
|
||||
|
||||
update_row.addWidget(self.update_status_label)
|
||||
update_row.addStretch()
|
||||
update_row_widget = QWidget()
|
||||
update_row_widget.setLayout(update_row)
|
||||
grid.addWidget(update_row_widget, 6, 0, 1, 4)
|
||||
|
||||
# 批量上传勾选框
|
||||
h9 = QHBoxLayout()
|
||||
self.batch_upload_checkbox = QCheckBox("批量上传(如果文件夹中有多个视频,将使用批量上传模式)")
|
||||
self.batch_upload_checkbox.setChecked(False)
|
||||
h9.addWidget(self.batch_upload_checkbox)
|
||||
config_layout.addLayout(h9)
|
||||
grid.addWidget(self.batch_upload_checkbox, 7, 0, 1, 4)
|
||||
|
||||
config_layout.addLayout(grid)
|
||||
|
||||
config_group.setLayout(config_layout)
|
||||
main_layout.addWidget(config_group)
|
||||
@@ -592,9 +597,9 @@ class MainWindow(QMainWindow):
|
||||
self.table_group = QGroupBox("配置列表(从Excel导入后显示,可直接在表格中编辑)")
|
||||
table_layout = QVBoxLayout()
|
||||
self.config_table = QTableWidget()
|
||||
self.config_table.setColumnCount(8)
|
||||
self.config_table.setColumnCount(9)
|
||||
self.config_table.setHorizontalHeaderLabels([
|
||||
'多多ID', '序号', '话题', '定时发布', '间隔时间(秒)', '达人链接', '执行人', '情况'
|
||||
'多多ID', '序号', '话题', '定时发布', '间隔时间(秒)', '达人链接', '执行人', '情况', '文件路径'
|
||||
])
|
||||
self.config_table.horizontalHeader().setStretchLastSection(True)
|
||||
# 设置表格可编辑
|
||||
@@ -606,7 +611,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 执行按钮
|
||||
self.execute_btn = QPushButton("开始上传")
|
||||
self.execute_btn.setStyleSheet("background-color: #4CAF50; color: white; font-size: 14px; padding: 10px;")
|
||||
self.execute_btn.setProperty("variant", "accent")
|
||||
self.execute_btn.clicked.connect(self.execute_task)
|
||||
main_layout.addWidget(self.execute_btn)
|
||||
|
||||
@@ -615,7 +620,7 @@ class MainWindow(QMainWindow):
|
||||
log_layout = QVBoxLayout()
|
||||
self.log_text = QTextEdit()
|
||||
self.log_text.setReadOnly(True)
|
||||
self.log_text.setFont(QFont("Consolas", 9))
|
||||
self.log_text.setFont(QFont("Consolas", 10))
|
||||
log_layout.addWidget(self.log_text)
|
||||
log_group.setLayout(log_layout)
|
||||
main_layout.addWidget(log_group)
|
||||
@@ -629,6 +634,150 @@ class MainWindow(QMainWindow):
|
||||
logger.remove()
|
||||
logger.add(lambda msg: None) # 禁用默认输出,通过信号在GUI中显示
|
||||
|
||||
def _build_status_card(self, title, value_label):
|
||||
"""创建状态卡片"""
|
||||
card = QWidget()
|
||||
card.setObjectName("statusCard")
|
||||
layout = QVBoxLayout(card)
|
||||
layout.setContentsMargins(12, 10, 12, 10)
|
||||
title_label = QLabel(title)
|
||||
title_label.setObjectName("statusTitle")
|
||||
value_label.setObjectName("statusValue")
|
||||
layout.addWidget(title_label)
|
||||
layout.addWidget(value_label)
|
||||
return card
|
||||
|
||||
def set_status_cards(self, update_text=None, pending=None, running=None):
|
||||
"""更新状态卡片显示"""
|
||||
if update_text is not None:
|
||||
self.status_update_value.setText(update_text)
|
||||
if pending is not None:
|
||||
self.status_pending_value.setText(str(pending))
|
||||
if running is not None:
|
||||
self.status_running_value.setText(str(running))
|
||||
|
||||
def set_running_progress(self, done, total):
|
||||
"""更新执行中统计"""
|
||||
self.running_done = done
|
||||
self.running_total = total
|
||||
if total > 0:
|
||||
self.set_status_cards(running=f"{done}/{total}")
|
||||
else:
|
||||
self.set_status_cards(running="0")
|
||||
|
||||
def apply_styles(self):
|
||||
"""应用统一的界面样式"""
|
||||
self.setStyleSheet("""
|
||||
QWidget {
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 12px;
|
||||
color: #1f1f1f;
|
||||
background-color: #f6f7fb;
|
||||
}
|
||||
QGroupBox {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
QGroupBox::title {
|
||||
subcontrol-origin: margin;
|
||||
left: 12px;
|
||||
padding: 0 6px;
|
||||
color: #4b5563;
|
||||
font-weight: 600;
|
||||
}
|
||||
QLabel {
|
||||
color: #374151;
|
||||
}
|
||||
QLabel#titleLabel {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
background: transparent;
|
||||
}
|
||||
QLabel#subtitleLabel {
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
background: transparent;
|
||||
}
|
||||
QWidget#statusCard {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
}
|
||||
QLabel#statusTitle {
|
||||
font-size: 11px;
|
||||
color: #6b7280;
|
||||
}
|
||||
QLabel#statusValue {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
QLineEdit, QDateTimeEdit, QTextEdit {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
QLineEdit:focus, QDateTimeEdit:focus, QTextEdit:focus {
|
||||
border: 1px solid #3b82f6;
|
||||
}
|
||||
QPushButton {
|
||||
border-radius: 6px;
|
||||
padding: 8px 12px;
|
||||
background-color: #e5e7eb;
|
||||
color: #111827;
|
||||
}
|
||||
QPushButton[variant="secondary"] {
|
||||
background-color: #f3f4f6;
|
||||
color: #111827;
|
||||
}
|
||||
QPushButton[variant="primary"] {
|
||||
background-color: #3b82f6;
|
||||
color: #ffffff;
|
||||
}
|
||||
QPushButton[variant="accent"] {
|
||||
background-color: #10b981;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #d1d5db;
|
||||
}
|
||||
QPushButton[variant="primary"]:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
QPushButton[variant="accent"]:hover {
|
||||
background-color: #059669;
|
||||
}
|
||||
QTableWidget {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
gridline-color: #eef2f7;
|
||||
}
|
||||
QHeaderView::section {
|
||||
background-color: #f3f4f6;
|
||||
padding: 6px;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
QProgressBar {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
background-color: #ffffff;
|
||||
text-align: center;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #3b82f6;
|
||||
border-radius: 6px;
|
||||
}
|
||||
""")
|
||||
|
||||
def browse_excel(self):
|
||||
"""浏览Excel文件"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
@@ -643,14 +792,6 @@ class MainWindow(QMainWindow):
|
||||
if folder_path:
|
||||
self.folder_path_input.setText(folder_path)
|
||||
|
||||
def browse_file(self):
|
||||
"""浏览文件"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "选择文件", "", "所有文件 (*.*)"
|
||||
)
|
||||
if file_path:
|
||||
self.file_path_input.setText(file_path)
|
||||
|
||||
def import_excel(self):
|
||||
"""导入Excel配置文件"""
|
||||
excel_path = self.excel_path_input.text()
|
||||
@@ -685,7 +826,8 @@ class MainWindow(QMainWindow):
|
||||
'间隔时间': int(row.get('间隔时间', 0)) if pd.notna(row.get('间隔时间')) else 0,
|
||||
'达人链接': str(row.get('达人链接', '')),
|
||||
'执行人': str(row.get('执行人', '')),
|
||||
'情况': str(row.get('情况', '待执行'))
|
||||
'情况': str(row.get('情况', '待执行')),
|
||||
'文件路径': '' # 文件路径字段初始为空,通过更新数据按钮填充
|
||||
}
|
||||
self.configs.append(config)
|
||||
|
||||
@@ -694,6 +836,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 显示表格
|
||||
self.table_group.setVisible(True)
|
||||
self.set_status_cards(update_text="未更新", pending=len(self.configs))
|
||||
|
||||
QMessageBox.information(self, "成功", f"成功导入 {len(self.configs)} 条配置")
|
||||
|
||||
@@ -714,8 +857,13 @@ class MainWindow(QMainWindow):
|
||||
self.config_table.setItem(row_idx, 5, QTableWidgetItem(str(config.get('达人链接', ''))))
|
||||
self.config_table.setItem(row_idx, 6, QTableWidgetItem(str(config.get('执行人', ''))))
|
||||
self.config_table.setItem(row_idx, 7, QTableWidgetItem(str(config.get('情况', '待执行'))))
|
||||
# 文件路径列(第8列,索引为8),如果配置中没有则显示空
|
||||
file_path = config.get('文件路径', '')
|
||||
self.config_table.setItem(row_idx, 8, QTableWidgetItem(str(file_path)))
|
||||
|
||||
self.config_table.resizeColumnsToContents()
|
||||
# 未更新前,用配置行数作为待执行提示
|
||||
self.set_status_cards(pending=self.config_table.rowCount())
|
||||
|
||||
def get_config_from_table(self, row_index=0):
|
||||
"""从表格中获取指定行的配置数据(使用表格中修改后的值)"""
|
||||
@@ -745,7 +893,8 @@ class MainWindow(QMainWindow):
|
||||
'间隔时间': get_cell_int(row_index, 4, 0),
|
||||
'达人链接': get_cell_text(row_index, 5),
|
||||
'执行人': get_cell_text(row_index, 6),
|
||||
'情况': get_cell_text(row_index, 7) or '待执行'
|
||||
'情况': get_cell_text(row_index, 7) or '待执行',
|
||||
'文件路径': get_cell_text(row_index, 8) # 第8列是文件路径
|
||||
}
|
||||
|
||||
return config
|
||||
@@ -767,36 +916,84 @@ class MainWindow(QMainWindow):
|
||||
'达人链接': self.url_input.text(),
|
||||
'执行人': self.executor_input.text(),
|
||||
'文件夹路径': folder_path,
|
||||
'文件路径': self.file_path_input.text().strip(),
|
||||
'情况': '待执行'
|
||||
}
|
||||
|
||||
def update_data(self):
|
||||
"""更新数据:找出文件并保存"""
|
||||
"""更新数据:找出文件并保存到表格的文件路径列(更新所有行)"""
|
||||
try:
|
||||
# 优先使用Excel导入的配置(如果存在)
|
||||
# 检查是否有Excel导入的配置表格
|
||||
if self.configs and self.config_table.rowCount() > 0:
|
||||
config = self.get_config_from_table(0)
|
||||
if not config:
|
||||
QMessageBox.warning(self, "警告", "无法获取配置数据")
|
||||
return
|
||||
# 添加文件夹路径
|
||||
# 获取文件夹路径
|
||||
folder_path = self.folder_path_input.text().strip()
|
||||
if not folder_path:
|
||||
folder_path = get_default_folder_path()
|
||||
config['文件夹路径'] = folder_path
|
||||
config['文件路径'] = self.file_path_input.text().strip()
|
||||
|
||||
if not os.path.exists(folder_path):
|
||||
QMessageBox.warning(self, "警告", f"文件夹路径不存在: {folder_path}")
|
||||
return
|
||||
|
||||
self.log_text.append("=" * 50)
|
||||
self.log_text.append("开始批量更新所有行的文件路径...")
|
||||
self.log_text.append(f"共有 {self.config_table.rowCount()} 行需要更新")
|
||||
|
||||
# 遍历所有行,更新每行的文件路径
|
||||
total_found = 0
|
||||
for row_idx in range(self.config_table.rowCount()):
|
||||
config = self.get_config_from_table(row_idx)
|
||||
if not config:
|
||||
self.log_text.append(f"第 {row_idx + 1} 行:无法获取配置数据,跳过")
|
||||
continue
|
||||
|
||||
# 验证必填字段
|
||||
if not config.get('多多id') or not config.get('序号'):
|
||||
self.log_text.append(f"第 {row_idx + 1} 行:多多ID或序号为空,跳过")
|
||||
continue
|
||||
|
||||
self.log_text.append(f"正在更新第 {row_idx + 1} 行的文件路径...")
|
||||
self.log_text.append(f" 多多ID: {config.get('多多id')}, 序号: {config.get('序号')}")
|
||||
|
||||
# 查找该行对应的文件
|
||||
found_files = self._find_files_for_config(config, folder_path)
|
||||
|
||||
if found_files:
|
||||
# 将找到的文件路径拼接成字符串(多个文件用分号分隔)
|
||||
file_paths_str = "; ".join([str(f['path']) for f in found_files])
|
||||
self.config_table.setItem(row_idx, 8, QTableWidgetItem(file_paths_str))
|
||||
|
||||
# 同时更新self.configs中对应的配置
|
||||
if row_idx < len(self.configs):
|
||||
self.configs[row_idx]['文件路径'] = file_paths_str
|
||||
|
||||
video_count = sum(1 for f in found_files if f['path'].is_file() and any(f['path'].suffix.lower() in ['.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm'] for ext in ['.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm']))
|
||||
self.log_text.append(f" ✓ 找到 {len(found_files)} 个文件({video_count} 个视频)")
|
||||
total_found += len(found_files)
|
||||
else:
|
||||
# 清空文件路径列
|
||||
self.config_table.setItem(row_idx, 8, QTableWidgetItem(""))
|
||||
if row_idx < len(self.configs):
|
||||
self.configs[row_idx]['文件路径'] = ""
|
||||
self.log_text.append(f" ✗ 未找到匹配的文件")
|
||||
|
||||
self.log_text.append("=" * 50)
|
||||
self.log_text.append(f"批量更新完成!共更新 {self.config_table.rowCount()} 行,找到 {total_found} 个文件")
|
||||
self.update_status_label.setText(f"已更新: {self.config_table.rowCount()}行,{total_found}个文件")
|
||||
self.update_status_label.setStyleSheet("color: #4CAF50; font-size: 10px;")
|
||||
self.set_status_cards(update_text=f"已更新: {self.config_table.rowCount()}行", pending=total_found)
|
||||
QMessageBox.information(self, "成功", f"已更新所有行的文件路径!\n共 {self.config_table.rowCount()} 行,找到 {total_found} 个文件")
|
||||
return
|
||||
else:
|
||||
# 没有表格,使用原来的逻辑(单个配置)
|
||||
config = self.get_config()
|
||||
current_row = -1 # 表示没有表格,使用prepared_files
|
||||
|
||||
# 验证必填字段
|
||||
if not config.get('多多id') or not config.get('序号'):
|
||||
QMessageBox.warning(self, "警告", "请先填写多多ID和序号")
|
||||
return
|
||||
|
||||
# 获取文件夹路径或文件路径
|
||||
# 获取文件夹路径
|
||||
folder_path = config.get('文件夹路径', '')
|
||||
file_path = config.get('文件路径', '').strip()
|
||||
|
||||
if not folder_path:
|
||||
folder_path = get_default_folder_path()
|
||||
@@ -808,60 +1005,58 @@ class MainWindow(QMainWindow):
|
||||
self.log_text.append("=" * 50)
|
||||
self.log_text.append("开始更新数据,查找文件...")
|
||||
|
||||
# 如果指定了文件路径,直接使用该文件
|
||||
if file_path and os.path.exists(file_path):
|
||||
self.log_text.append(f"使用指定的文件路径: {file_path}")
|
||||
path_obj = Path(file_path)
|
||||
if path_obj.is_file():
|
||||
# 判断是否为视频文件
|
||||
video_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm']
|
||||
is_video = any(path_obj.suffix.lower() == ext for ext in video_extensions)
|
||||
|
||||
self.prepared_files = [{
|
||||
"url": config.get('达人链接', ''),
|
||||
"user_id": config.get('多多id', ''),
|
||||
"time_start": config.get('定时发布', '') if config.get('定时发布') else None,
|
||||
"ht": config.get('话题', ''),
|
||||
"index": config.get('序号', ''),
|
||||
"path": path_obj
|
||||
}]
|
||||
|
||||
file_type = "视频" if is_video else "文件"
|
||||
self.log_text.append(f"找到 1 个{file_type}文件: {path_obj.name}")
|
||||
self.update_status_label.setText(f"已更新: 1个{file_type}文件")
|
||||
self.update_status_label.setStyleSheet("color: #4CAF50; font-size: 10px;")
|
||||
QMessageBox.information(self, "成功", f"已找到 1 个{file_type}文件")
|
||||
return
|
||||
else:
|
||||
QMessageBox.warning(self, "警告", "指定的路径不是文件")
|
||||
return
|
||||
# 在文件夹中查找文件
|
||||
found_files = self._find_files_for_config(config, folder_path)
|
||||
|
||||
# 否则在文件夹中查找文件
|
||||
index = config.get('序号', '')
|
||||
video_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm']
|
||||
|
||||
found_files = []
|
||||
if found_files:
|
||||
self.prepared_files = found_files
|
||||
video_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm']
|
||||
video_count = sum(1 for f in found_files if f['path'].is_file() and any(f['path'].suffix.lower() == ext for ext in video_extensions))
|
||||
folder_count = len(found_files) - video_count
|
||||
|
||||
self.log_text.append(f"更新完成!找到 {len(found_files)} 个文件/文件夹({video_count} 个视频,{folder_count} 个文件夹)")
|
||||
self.update_status_label.setText(f"已更新: {len(found_files)}个文件")
|
||||
self.update_status_label.setStyleSheet("color: #4CAF50; font-size: 10px;")
|
||||
self.set_status_cards(update_text=f"已更新: {len(found_files)}个文件", pending=len(found_files))
|
||||
QMessageBox.information(self, "成功", f"已找到 {len(found_files)} 个文件/文件夹")
|
||||
else:
|
||||
self.prepared_files = None
|
||||
self.log_text.append("未找到匹配的文件")
|
||||
self.update_status_label.setText("未找到文件")
|
||||
self.update_status_label.setStyleSheet("color: #f44336; font-size: 10px;")
|
||||
self.set_status_cards(update_text="未找到文件", pending=0)
|
||||
QMessageBox.warning(self, "警告", "未找到匹配的文件")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"更新数据失败: {str(e)}"
|
||||
self.log_text.append(error_msg)
|
||||
QMessageBox.critical(self, "错误", error_msg)
|
||||
logger.error(f"更新数据失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _find_files_for_config(self, config, folder_path):
|
||||
"""根据配置查找文件(辅助方法)"""
|
||||
found_files = []
|
||||
index = config.get('序号', '')
|
||||
video_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm']
|
||||
|
||||
try:
|
||||
# 遍历最外层文件夹下的所有子文件夹
|
||||
subdirs = [f for f in os.listdir(folder_path) if os.path.isdir(os.path.join(folder_path, f))]
|
||||
self.log_text.append(f"在最外层文件夹下找到 {len(subdirs)} 个子文件夹")
|
||||
|
||||
# 找到匹配当前多多ID的文件夹
|
||||
target_subdir = None
|
||||
for subdir_name in subdirs:
|
||||
if subdir_name == str(config.get('多多id')):
|
||||
target_subdir = os.path.join(folder_path, subdir_name)
|
||||
self.log_text.append(f"找到匹配的多多ID文件夹: {subdir_name}")
|
||||
break
|
||||
|
||||
if not target_subdir:
|
||||
QMessageBox.warning(self, "警告", f"未找到多多ID为 {config.get('多多id')} 的文件夹")
|
||||
self.log_text.append(f"未找到匹配的多多ID文件夹")
|
||||
return
|
||||
return found_files
|
||||
|
||||
# 扫描该文件夹下的文件
|
||||
items = os.listdir(target_subdir)
|
||||
self.log_text.append(f"扫描文件夹: {os.path.basename(target_subdir)},共 {len(items)} 个项目")
|
||||
|
||||
for item_name in items:
|
||||
item_path = os.path.join(target_subdir, item_name)
|
||||
@@ -882,7 +1077,6 @@ class MainWindow(QMainWindow):
|
||||
"index": str(index),
|
||||
"path": path_obj
|
||||
})
|
||||
self.log_text.append(f"找到视频文件: {item_name}")
|
||||
else:
|
||||
# 如果是文件夹,可能是图片文件夹
|
||||
found_files.append({
|
||||
@@ -893,30 +1087,10 @@ class MainWindow(QMainWindow):
|
||||
"index": str(index),
|
||||
"path": path_obj
|
||||
})
|
||||
self.log_text.append(f"找到文件夹: {item_name}")
|
||||
|
||||
if found_files:
|
||||
self.prepared_files = found_files
|
||||
video_count = sum(1 for f in found_files if f['path'].is_file() and any(f['path'].suffix.lower() == ext for ext in video_extensions))
|
||||
folder_count = len(found_files) - video_count
|
||||
self.log_text.append(f"更新完成!找到 {len(found_files)} 个文件/文件夹({video_count} 个视频,{folder_count} 个文件夹)")
|
||||
self.update_status_label.setText(f"已更新: {len(found_files)}个文件")
|
||||
self.update_status_label.setStyleSheet("color: #4CAF50; font-size: 10px;")
|
||||
QMessageBox.information(self, "成功", f"已找到 {len(found_files)} 个文件/文件夹")
|
||||
else:
|
||||
self.prepared_files = None
|
||||
self.log_text.append("未找到匹配的文件")
|
||||
self.update_status_label.setText("未找到文件")
|
||||
self.update_status_label.setStyleSheet("color: #f44336; font-size: 10px;")
|
||||
QMessageBox.warning(self, "警告", "未找到匹配的文件")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"更新数据失败: {str(e)}"
|
||||
self.log_text.append(error_msg)
|
||||
QMessageBox.critical(self, "错误", error_msg)
|
||||
logger.error(f"更新数据失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
logger.error(f"查找文件失败: {e}")
|
||||
|
||||
return found_files
|
||||
|
||||
def execute_task(self):
|
||||
"""执行任务"""
|
||||
@@ -967,10 +1141,13 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 创建工作线程,传递预查找的文件列表
|
||||
self.worker_thread = WorkerThread(config, is_batch_mode, self.prepared_files, self)
|
||||
# 连接finished信号,用于单个任务完成后显示弹窗
|
||||
self.worker_thread.finished.connect(self.on_task_finished)
|
||||
self.worker_thread.log_message.connect(self.log_text.append)
|
||||
self.worker_thread.progress.connect(self.progress_bar.setValue)
|
||||
self.worker_thread.start()
|
||||
self.set_status_cards(pending=1)
|
||||
self.set_running_progress(0, 1)
|
||||
|
||||
self.log_text.append("=" * 50)
|
||||
mode_text = "批量上传" if is_batch_mode else "逐个上传"
|
||||
@@ -980,7 +1157,7 @@ class MainWindow(QMainWindow):
|
||||
self.log_text.append(f"开始执行任务({mode_text}模式)...")
|
||||
|
||||
def execute_batch_from_excel(self):
|
||||
"""从Excel配置批量执行(使用表格中修改后的配置)"""
|
||||
"""从Excel配置批量执行(自动判断相同多多ID的mp4文件,批量上传)"""
|
||||
# 获取文件夹路径,如果为空则使用默认路径
|
||||
folder_path = self.folder_path_input.text().strip()
|
||||
if not folder_path:
|
||||
@@ -990,54 +1167,209 @@ class MainWindow(QMainWindow):
|
||||
QMessageBox.warning(self, "警告", f"文件夹路径不存在: {folder_path}")
|
||||
return
|
||||
|
||||
is_batch_mode = self.batch_upload_checkbox.isChecked()
|
||||
|
||||
# 从表格中获取配置(使用用户修改后的值)
|
||||
# 从表格中获取所有配置(使用用户修改后的值)
|
||||
if self.config_table.rowCount() == 0:
|
||||
QMessageBox.warning(self, "警告", "配置列表为空,请先导入Excel配置")
|
||||
return
|
||||
|
||||
# 获取第一行的配置(从表格中读取,而不是从self.configs)
|
||||
config = self.get_config_from_table(0)
|
||||
if not config:
|
||||
QMessageBox.warning(self, "警告", "无法获取配置数据")
|
||||
self.log_text.append("=" * 50)
|
||||
self.log_text.append("开始分析配置,准备批量上传...")
|
||||
|
||||
# 收集所有配置及其对应的文件
|
||||
all_configs_with_files = []
|
||||
video_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm']
|
||||
|
||||
for row_idx in range(self.config_table.rowCount()):
|
||||
config = self.get_config_from_table(row_idx)
|
||||
if not config:
|
||||
continue
|
||||
|
||||
# 验证必填字段
|
||||
if not config.get('多多id') or not config.get('序号'):
|
||||
self.log_text.append(f"第 {row_idx + 1} 行:多多ID或序号为空,跳过")
|
||||
continue
|
||||
|
||||
# 添加文件夹路径
|
||||
config['文件夹路径'] = folder_path
|
||||
|
||||
# 从文件路径列读取文件路径
|
||||
file_path_str = config.get('文件路径', '').strip()
|
||||
if file_path_str:
|
||||
# 文件路径可能用分号分隔(多个文件)
|
||||
file_paths = [p.strip() for p in file_path_str.split(';') if p.strip()]
|
||||
files = []
|
||||
for fp in file_paths:
|
||||
if os.path.exists(fp):
|
||||
path_obj = Path(fp)
|
||||
files.append({
|
||||
"url": config.get('达人链接', ''),
|
||||
"user_id": config.get('多多id', ''),
|
||||
"time_start": config.get('定时发布', '') if config.get('定时发布') else None,
|
||||
"ht": config.get('话题', ''),
|
||||
"index": config.get('序号', ''),
|
||||
"path": path_obj
|
||||
})
|
||||
|
||||
if files:
|
||||
all_configs_with_files.append({
|
||||
'config': config,
|
||||
'files': files,
|
||||
'row_idx': row_idx
|
||||
})
|
||||
self.log_text.append(f"第 {row_idx + 1} 行:从文件路径列读取到 {len(files)} 个文件")
|
||||
else:
|
||||
# 如果文件路径列为空,尝试查找文件
|
||||
self.log_text.append(f"第 {row_idx + 1} 行:文件路径列为空,尝试查找文件...")
|
||||
found_files = self._find_files_for_config(config, folder_path)
|
||||
if found_files:
|
||||
all_configs_with_files.append({
|
||||
'config': config,
|
||||
'files': found_files,
|
||||
'row_idx': row_idx
|
||||
})
|
||||
self.log_text.append(f"第 {row_idx + 1} 行:找到 {len(found_files)} 个文件")
|
||||
|
||||
if not all_configs_with_files:
|
||||
QMessageBox.warning(self, "警告", "未找到任何文件,请先点击'更新数据'按钮")
|
||||
return
|
||||
|
||||
total_tasks = sum(len(item['files']) for item in all_configs_with_files)
|
||||
pending_tasks = total_tasks
|
||||
self.set_status_cards(pending=pending_tasks)
|
||||
self.set_running_progress(0, total_tasks)
|
||||
|
||||
# 添加文件夹路径
|
||||
config['文件夹路径'] = folder_path
|
||||
# 按多多ID分组
|
||||
from collections import defaultdict
|
||||
grouped_by_user_id = defaultdict(list)
|
||||
for item in all_configs_with_files:
|
||||
user_id = item['config'].get('多多id', '')
|
||||
grouped_by_user_id[user_id].append(item)
|
||||
|
||||
# 检查是否勾选了批量上传
|
||||
# 如果已经更新了数据,根据预查找的文件判断是否为批量上传
|
||||
if self.prepared_files:
|
||||
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)]
|
||||
if is_batch_mode and len(video_files) > 1:
|
||||
self.log_text.append(f"检测到 {len(video_files)} 个视频文件,使用批量上传模式...")
|
||||
elif is_batch_mode and len(video_files) <= 1:
|
||||
self.log_text.append(f"只找到 {len(video_files)} 个视频文件,将使用单个上传模式...")
|
||||
is_batch_mode = False
|
||||
elif not is_batch_mode:
|
||||
self.log_text.append(f"使用单个上传模式...")
|
||||
self.log_text.append("=" * 50)
|
||||
self.log_text.append(f"按多多ID分组:共 {len(grouped_by_user_id)} 个不同的多多ID")
|
||||
|
||||
# 禁用按钮
|
||||
self.execute_btn.setEnabled(False)
|
||||
self.progress_bar.setVisible(True)
|
||||
self.progress_bar.setValue(0)
|
||||
self.set_status_cards(running=1)
|
||||
|
||||
# 创建工作线程,传递预查找的文件列表
|
||||
self.worker_thread = WorkerThread(config, is_batch_mode, self.prepared_files, self)
|
||||
self.worker_thread.finished.connect(self.on_task_finished)
|
||||
self.worker_thread.log_message.connect(self.log_text.append)
|
||||
self.worker_thread.progress.connect(self.progress_bar.setValue)
|
||||
self.worker_thread.start()
|
||||
|
||||
# 处理每个多多ID组
|
||||
total_processed = 0
|
||||
for user_id, items in grouped_by_user_id.items():
|
||||
self.log_text.append(f"\n处理多多ID: {user_id},共 {len(items)} 个配置")
|
||||
|
||||
# 收集该多多ID下的所有文件
|
||||
all_files = []
|
||||
for item in items:
|
||||
all_files.extend(item['files'])
|
||||
|
||||
# 分离视频文件和图片文件夹
|
||||
video_files = [f for f in all_files if f['path'].is_file() and any(f['path'].suffix.lower() == ext for ext in video_extensions)]
|
||||
image_folders = [f for f in all_files if f['path'].is_dir()]
|
||||
|
||||
self.log_text.append(f" 视频文件: {len(video_files)} 个")
|
||||
self.log_text.append(f" 图片文件夹: {len(image_folders)} 个")
|
||||
|
||||
# 使用第一个配置创建Pdd实例(因为同一个多多ID,配置应该相同)
|
||||
first_config = items[0]['config']
|
||||
pdd = Pdd(
|
||||
url=first_config.get('达人链接', ''),
|
||||
user_id=user_id,
|
||||
time_start=first_config.get('定时发布', '') if first_config.get('定时发布') else None,
|
||||
ht=first_config.get('话题', ''),
|
||||
index=first_config.get('序号', ''),
|
||||
title=first_config.get('标题', None)
|
||||
)
|
||||
|
||||
# 第一步:如果有多个视频文件(>1),批量上传所有视频
|
||||
if len(video_files) > 1:
|
||||
# 检查是否都是mp4文件
|
||||
all_mp4 = all(f['path'].suffix.lower() == '.mp4' for f in video_files)
|
||||
if all_mp4:
|
||||
self.log_text.append(f" ✓ 检测到 {len(video_files)} 个mp4文件,批量上传所有视频(action1方法)")
|
||||
else:
|
||||
self.log_text.append(f" ✓ 检测到 {len(video_files)} 个视频文件,批量上传所有视频(action1方法)")
|
||||
|
||||
# 批量上传所有视频
|
||||
self.worker_thread = WorkerThread(first_config, True, video_files, self)
|
||||
# 不连接finished信号,避免每个任务完成就弹窗
|
||||
self.worker_thread.log_message.connect(self.log_text.append)
|
||||
self.worker_thread.progress.connect(self.progress_bar.setValue)
|
||||
self.worker_thread.start()
|
||||
|
||||
# 等待完成
|
||||
self.worker_thread.wait()
|
||||
total_processed += len(video_files)
|
||||
pending_tasks = max(pending_tasks - len(video_files), 0)
|
||||
self.set_status_cards(pending=pending_tasks)
|
||||
self.set_running_progress(total_processed, total_tasks)
|
||||
self.log_text.append(f" ✓ 批量上传 {len(video_files)} 个视频完成")
|
||||
elif len(video_files) == 1:
|
||||
# 只有1个视频,单个上传
|
||||
self.log_text.append(f" → 只有1个视频文件,单个上传")
|
||||
self.worker_thread = WorkerThread(first_config, False, video_files, self)
|
||||
# 不连接finished信号,避免每个任务完成就弹窗
|
||||
self.worker_thread.log_message.connect(self.log_text.append)
|
||||
self.worker_thread.progress.connect(self.progress_bar.setValue)
|
||||
self.worker_thread.start()
|
||||
self.worker_thread.wait()
|
||||
total_processed += len(video_files)
|
||||
pending_tasks = max(pending_tasks - len(video_files), 0)
|
||||
self.set_status_cards(pending=pending_tasks)
|
||||
self.set_running_progress(total_processed, total_tasks)
|
||||
self.log_text.append(f" ✓ 单个视频上传完成")
|
||||
|
||||
# 第二步:如果有图片文件夹,逐个上传图片
|
||||
if image_folders:
|
||||
self.log_text.append(f" → 开始逐个上传 {len(image_folders)} 个图片文件夹")
|
||||
for idx, img_folder in enumerate(image_folders, 1):
|
||||
# 找到该图片文件夹对应的配置(通过序号匹配)
|
||||
folder_index = img_folder.get('index', '')
|
||||
matching_config = None
|
||||
for item in items:
|
||||
if item['config'].get('序号', '') == folder_index:
|
||||
matching_config = item['config']
|
||||
break
|
||||
|
||||
# 如果找不到匹配的配置,使用第一个配置
|
||||
if not matching_config:
|
||||
matching_config = first_config
|
||||
|
||||
self.log_text.append(f" 上传第 {idx}/{len(image_folders)} 个图片文件夹(序号: {folder_index})")
|
||||
|
||||
pdd_img = Pdd(
|
||||
url=matching_config.get('达人链接', ''),
|
||||
user_id=user_id,
|
||||
time_start=matching_config.get('定时发布', '') if matching_config.get('定时发布') else None,
|
||||
ht=matching_config.get('话题', ''),
|
||||
index=folder_index,
|
||||
title=matching_config.get('标题', None)
|
||||
)
|
||||
|
||||
self.worker_thread = WorkerThread(matching_config, False, [img_folder], self)
|
||||
# 不连接finished信号,避免每个任务完成就弹窗
|
||||
self.worker_thread.log_message.connect(self.log_text.append)
|
||||
self.worker_thread.progress.connect(self.progress_bar.setValue)
|
||||
self.worker_thread.start()
|
||||
self.worker_thread.wait()
|
||||
total_processed += 1
|
||||
pending_tasks = max(pending_tasks - 1, 0)
|
||||
self.set_status_cards(pending=pending_tasks)
|
||||
self.set_running_progress(total_processed, total_tasks)
|
||||
self.log_text.append(f" ✓ 图片文件夹 {idx} 上传完成")
|
||||
|
||||
self.log_text.append(f" ✓ 所有图片文件夹上传完成")
|
||||
|
||||
self.progress_bar.setValue(100)
|
||||
self.log_text.append("=" * 50)
|
||||
mode_text = "批量上传" if is_batch_mode else "逐个上传"
|
||||
if self.prepared_files:
|
||||
self.log_text.append(f"开始执行任务({mode_text}模式,使用预查找的文件列表)...")
|
||||
else:
|
||||
self.log_text.append(f"开始执行任务({mode_text}模式)...")
|
||||
self.log_text.append(f"使用配置 - 多多ID: {config.get('多多id')}, 序号: {config.get('序号')}, 话题: {config.get('话题')}")
|
||||
self.log_text.append(f"所有任务执行完成!共处理 {total_processed} 个文件/文件夹")
|
||||
self.execute_btn.setEnabled(True)
|
||||
self.set_running_progress(0, 0)
|
||||
self.set_status_cards(pending=0)
|
||||
|
||||
# 所有任务完成后,显示完成弹窗
|
||||
QMessageBox.information(self, "任务完成", f"所有任务执行完成!\n共处理 {total_processed} 个文件/文件夹")
|
||||
|
||||
def count_videos_in_folder(self, folder_path, index):
|
||||
"""统计文件夹中匹配序号的视频文件数量(与main.py逻辑一致)"""
|
||||
@@ -1076,6 +1408,8 @@ class MainWindow(QMainWindow):
|
||||
def on_task_finished(self, success, message):
|
||||
"""任务完成回调"""
|
||||
self.progress_bar.setValue(100)
|
||||
self.set_running_progress(0, 0)
|
||||
self.set_status_cards(pending=0)
|
||||
|
||||
if success:
|
||||
QMessageBox.information(self, "成功", message)
|
||||
@@ -1115,3 +1449,4 @@ def main():
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
# docker run honeygain/honeygain -tou-accept -email ddrwode1@gmail.com -pass 040828cjj -device DEVICE_NAME
|
||||
38
main.py
@@ -24,8 +24,36 @@ class Pdd:
|
||||
self.user_profile_url_template = None # 用户信息URL模板
|
||||
|
||||
self.title = title
|
||||
self.ht = ht
|
||||
self.ht = self._format_topic(ht) # 格式化话题
|
||||
self.index = index
|
||||
|
||||
def _format_topic(self, topic_str):
|
||||
"""
|
||||
格式化话题:将"拍出氛围感-好看照片分享"转换为"#拍出氛围感 #好看照片分享"
|
||||
|
||||
Args:
|
||||
topic_str: 原始话题字符串,可能包含"-"分隔符
|
||||
|
||||
Returns:
|
||||
格式化后的话题字符串,每个部分前加"#"
|
||||
"""
|
||||
if not topic_str:
|
||||
return ""
|
||||
|
||||
# 按"-"分割话题
|
||||
parts = [part.strip() for part in topic_str.split("-") if part.strip()]
|
||||
|
||||
# 为每个部分添加"#"
|
||||
formatted_parts = []
|
||||
for part in parts:
|
||||
# 如果部分已经以"#"开头,不再添加
|
||||
if part.startswith("#"):
|
||||
formatted_parts.append(part)
|
||||
else:
|
||||
formatted_parts.append(f"#{part}")
|
||||
|
||||
# 用空格连接
|
||||
return " ".join(formatted_parts)
|
||||
|
||||
def create_page(self):
|
||||
co = ChromiumOptions()
|
||||
@@ -835,7 +863,9 @@ class Pdd:
|
||||
try:
|
||||
video_path = video_info["path"]
|
||||
video_name = video_path.name
|
||||
video_ht = video_info.get("ht", self.ht)
|
||||
# 格式化话题(如果还没有格式化)
|
||||
raw_ht = video_info.get("ht", self.ht)
|
||||
video_ht = self._format_topic(raw_ht) if raw_ht else self.ht
|
||||
video_time_start = video_info.get("time_start", self.time_start)
|
||||
video_url = video_info.get("url", self.url)
|
||||
|
||||
@@ -924,7 +954,9 @@ class Pdd:
|
||||
# 1. 输入视频描述
|
||||
logger.info(f" 步骤1: 输入视频描述...")
|
||||
try:
|
||||
desc_text = video_name.split(".")[0].split("-")[-1] + video_ht
|
||||
# 格式化话题(如果还没有格式化)
|
||||
formatted_ht = self._format_topic(video_ht) if video_ht else ""
|
||||
desc_text = video_name.split(".")[0].split("-")[-1] + formatted_ht
|
||||
logger.info(f" 描述文本: {desc_text[:50]}...")
|
||||
desc_inputs = video_container.eles('x://*[starts-with(@id, "magicdomid")]')
|
||||
if desc_inputs:
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"top_topics_and_observing_domains": [ ]
|
||||
} ],
|
||||
"hex_encoded_hmac_key": "434BF7DBD7DA573B45E0A11AD9045A61B6221D14AE2F9A341E2FEF659AF071F6",
|
||||
"next_scheduled_calculation_time": "13413450070590344"
|
||||
"next_scheduled_calculation_time": "13413450070590402"
|
||||
}
|
||||
|
||||
BIN
user/user_data/Default/Cache/Cache_Data/f_000024
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_000025
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_000026
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_000027
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_000028
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_00002b
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_00002c
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_00002e
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_00002f
Normal file
BIN
user/user_data/Default/Cache/Cache_Data/f_000030
Normal file
BIN
user/user_data/Default/Cache/Cache_Data/f_000033
Normal file
BIN
user/user_data/Default/Cache/Cache_Data/f_000034
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_000035
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_000036
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_000037
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_000038
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_000039
Normal file
|
After Width: | Height: | Size: 50 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_00003a
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
user/user_data/Default/Cache/Cache_Data/f_00003b
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
user/user_data/Default/Code Cache/js/2ebd13f5a34d55e4_0
Normal file
BIN
user/user_data/Default/Code Cache/js/399a530489cd8286_0
Normal file
BIN
user/user_data/Default/Code Cache/js/78a15497f6f30679_0
Normal file
BIN
user/user_data/Default/Code Cache/js/8defdcdb9c411105_0
Normal file
BIN
user/user_data/Default/Code Cache/js/a8d27ebf8c7979db_0
Normal file
BIN
user/user_data/Default/Code Cache/js/a9bb6b31b26d2f60_0
Normal file
BIN
user/user_data/Default/Code Cache/js/ad0326de15d5d36a_0
Normal file
BIN
user/user_data/Default/Code Cache/js/b59b18f8baba8ef8_0
Normal file
BIN
user/user_data/Default/Code Cache/js/bedb1dc7d9c03b82_0
Normal file
BIN
user/user_data/Default/Code Cache/js/c79e370c2463809e_0
Normal file
BIN
user/user_data/Default/Code Cache/js/da515118a7da4bb9_0
Normal file
BIN
user/user_data/Default/Code Cache/js/da60b385ab5013f9_0
Normal file
BIN
user/user_data/Default/Code Cache/js/e78ccae5f4030b2f_0
Normal file
BIN
user/user_data/Default/Code Cache/js/ec5259168dff3052_0
Normal file
BIN
user/user_data/Default/Code Cache/js/f8f575eb043de8fa_0
Normal file
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:44:57.801 9844 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Extension State/MANIFEST-000001
|
||||
2026/01/20-11:44:57.803 9844 Recovering log #3
|
||||
2026/01/20-11:44:57.803 9844 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Extension State/000003.log
|
||||
2026/01/20-15:00:02.034 2974 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Extension State/MANIFEST-000001
|
||||
2026/01/20-15:00:02.035 2974 Recovering log #3
|
||||
2026/01/20-15:00:02.035 2974 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Extension State/000003.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:38:03.001 1990 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Extension State/MANIFEST-000001
|
||||
2026/01/20-11:38:03.002 1990 Recovering log #3
|
||||
2026/01/20-11:38:03.004 1990 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Extension State/000003.log
|
||||
2026/01/20-13:46:23.475 4ac8 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Extension State/MANIFEST-000001
|
||||
2026/01/20-13:46:23.476 4ac8 Recovering log #3
|
||||
2026/01/20-13:46:23.477 4ac8 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Extension State/000003.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:44:58.548 9a04 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\File System\Origins/MANIFEST-000001
|
||||
2026/01/20-11:44:58.549 9a04 Recovering log #6
|
||||
2026/01/20-11:44:58.549 9a04 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\File System\Origins/000006.log
|
||||
2026/01/20-15:00:02.654 d04 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\File System\Origins/MANIFEST-000001
|
||||
2026/01/20-15:00:02.654 d04 Recovering log #6
|
||||
2026/01/20-15:00:02.655 d04 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\File System\Origins/000006.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:38:03.788 3e60 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\File System\Origins/MANIFEST-000001
|
||||
2026/01/20-11:38:03.789 3e60 Recovering log #6
|
||||
2026/01/20-11:38:03.790 3e60 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\File System\Origins/000006.log
|
||||
2026/01/20-13:46:24.016 4ac8 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\File System\Origins/MANIFEST-000001
|
||||
2026/01/20-13:46:24.016 4ac8 Recovering log #6
|
||||
2026/01/20-13:46:24.017 4ac8 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\File System\Origins/000006.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:45:04.150 5350 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store\Encryption/MANIFEST-000001
|
||||
2026/01/20-11:45:04.150 5350 Recovering log #3
|
||||
2026/01/20-11:45:04.151 5350 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store\Encryption/000003.log
|
||||
2026/01/20-15:00:08.336 880c Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store\Encryption/MANIFEST-000001
|
||||
2026/01/20-15:00:08.337 880c Recovering log #3
|
||||
2026/01/20-15:00:08.337 880c Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store\Encryption/000003.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:38:09.362 349c Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store\Encryption/MANIFEST-000001
|
||||
2026/01/20-11:38:09.364 349c Recovering log #3
|
||||
2026/01/20-11:38:09.365 349c Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store\Encryption/000003.log
|
||||
2026/01/20-13:46:29.475 2adc Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store\Encryption/MANIFEST-000001
|
||||
2026/01/20-13:46:29.476 2adc Recovering log #3
|
||||
2026/01/20-13:46:29.476 2adc Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store\Encryption/000003.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:45:04.066 5350 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store/MANIFEST-000001
|
||||
2026/01/20-11:45:04.066 5350 Recovering log #3
|
||||
2026/01/20-11:45:04.067 5350 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store/000003.log
|
||||
2026/01/20-15:00:08.249 880c Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store/MANIFEST-000001
|
||||
2026/01/20-15:00:08.250 880c Recovering log #3
|
||||
2026/01/20-15:00:08.250 880c Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store/000003.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:38:09.287 349c Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store/MANIFEST-000001
|
||||
2026/01/20-11:38:09.287 349c Recovering log #3
|
||||
2026/01/20-11:38:09.288 349c Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store/000003.log
|
||||
2026/01/20-13:46:29.403 2adc Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store/MANIFEST-000001
|
||||
2026/01/20-13:46:29.404 2adc Recovering log #3
|
||||
2026/01/20-13:46:29.404 2adc Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\GCM Store/000003.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:45:10.721 9a04 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\IndexedDB\https_mcn.pinduoduo.com_0.indexeddb.leveldb/MANIFEST-000001
|
||||
2026/01/20-11:45:10.722 9a04 Recovering log #11
|
||||
2026/01/20-11:45:10.734 9a04 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\IndexedDB\https_mcn.pinduoduo.com_0.indexeddb.leveldb/000011.log
|
||||
2026/01/20-15:00:14.224 1b20 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\IndexedDB\https_mcn.pinduoduo.com_0.indexeddb.leveldb/MANIFEST-000001
|
||||
2026/01/20-15:00:14.225 1b20 Recovering log #15
|
||||
2026/01/20-15:00:14.228 1b20 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\IndexedDB\https_mcn.pinduoduo.com_0.indexeddb.leveldb/000015.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:45:07.804 9a04 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\IndexedDB\https_mcn.pinduoduo.com_0.indexeddb.leveldb/MANIFEST-000001
|
||||
2026/01/20-11:45:07.804 9a04 Recovering log #11
|
||||
2026/01/20-11:45:07.818 9a04 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\IndexedDB\https_mcn.pinduoduo.com_0.indexeddb.leveldb/000011.log
|
||||
2026/01/20-15:00:11.696 2974 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\IndexedDB\https_mcn.pinduoduo.com_0.indexeddb.leveldb/MANIFEST-000001
|
||||
2026/01/20-15:00:11.697 2974 Recovering log #15
|
||||
2026/01/20-15:00:11.698 2974 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\IndexedDB\https_mcn.pinduoduo.com_0.indexeddb.leveldb/000015.log
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:44:57.684 99c8 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Local Storage\leveldb/MANIFEST-000001
|
||||
2026/01/20-11:44:57.688 99c8 Recovering log #7
|
||||
2026/01/20-11:44:57.697 99c8 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Local Storage\leveldb/000007.log
|
||||
2026/01/20-15:00:01.875 288c Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Local Storage\leveldb/MANIFEST-000001
|
||||
2026/01/20-15:00:01.879 288c Recovering log #7
|
||||
2026/01/20-15:00:01.884 288c Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Local Storage\leveldb/000007.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:38:02.888 8da8 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Local Storage\leveldb/MANIFEST-000001
|
||||
2026/01/20-11:38:02.894 8da8 Recovering log #7
|
||||
2026/01/20-11:38:02.901 8da8 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Local Storage\leveldb/000007.log
|
||||
2026/01/20-13:46:23.318 674 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Local Storage\leveldb/MANIFEST-000001
|
||||
2026/01/20-13:46:23.322 674 Recovering log #7
|
||||
2026/01/20-13:46:23.330 674 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Local Storage\leveldb/000007.log
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"sts":[{"expiry":1779765891.342828,"host":"dERK8Ko+SPll3fI4ktOXyGETlPtRvoHIttvQhh3OR68=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1768879491.342831},{"expiry":1779259199.044976,"host":"myxca24Fg7L/IgePD/QeLaUxbyYNmJdOyLPYvlVtjPE=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1768372799.044977},{"expiry":1800416698.125972,"host":"5EdUoB7YUY9zZV+2DkgVXgho8WUvp+D+6KpeUOhNQIM=","mode":"force-https","sts_include_subdomains":false,"sts_observed":1768880698.125974},{"expiry":1800416698.182832,"host":"8/RrMmQlCD2Gsp14wUCE1P8r7B2C5+yE0+g79IPyRsc=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1768880698.182834}],"version":2}
|
||||
{"sts":[{"expiry":1779774321.330876,"host":"dERK8Ko+SPll3fI4ktOXyGETlPtRvoHIttvQhh3OR68=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1768887921.33088},{"expiry":1779259199.044976,"host":"myxca24Fg7L/IgePD/QeLaUxbyYNmJdOyLPYvlVtjPE=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1768372799.044977},{"expiry":1800428402.512637,"host":"5EdUoB7YUY9zZV+2DkgVXgho8WUvp+D+6KpeUOhNQIM=","mode":"force-https","sts_include_subdomains":false,"sts_observed":1768892402.512638},{"expiry":1800428402.39833,"host":"8/RrMmQlCD2Gsp14wUCE1P8r7B2C5+yE0+g79IPyRsc=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1768892402.398333}],"version":2}
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:44:57.531 301c Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Service Worker\Database/MANIFEST-000001
|
||||
2026/01/20-11:44:57.532 301c Recovering log #3
|
||||
2026/01/20-11:44:57.533 301c Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Service Worker\Database/000003.log
|
||||
2026/01/20-15:00:01.795 6014 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Service Worker\Database/MANIFEST-000001
|
||||
2026/01/20-15:00:01.796 6014 Recovering log #3
|
||||
2026/01/20-15:00:01.797 6014 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Service Worker\Database/000003.log
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
2026/01/20-11:38:02.715 2428 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Service Worker\Database/MANIFEST-000001
|
||||
2026/01/20-11:38:02.717 2428 Recovering log #3
|
||||
2026/01/20-11:38:02.719 2428 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Service Worker\Database/000003.log
|
||||
2026/01/20-13:46:47.829 8278 Reusing MANIFEST C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Service Worker\Database/MANIFEST-000001
|
||||
2026/01/20-13:46:47.837 8278 Recovering log #3
|
||||
2026/01/20-13:46:47.839 8278 Reusing old log C:\Users\27942\Desktop\codesk\haha\user\user_data\Default\Service Worker\Database/000003.log
|
||||
|
||||