使用win32file.CopyFile加速文件复制速度

使用win32file.CopyFile加速文件复制速度

测试发现,使用win32file来复制文件比使用shutil要快速1/3左右

import win32file
#路径需要为文件路径,而不是目录路径
win32file.CopyFile(start_path,dest_path,1)
import os import tempfile import pythoncom import win32com.client import threading import shutil import tkinter as tk from tkinter import filedialog, ttk, messagebox, scrolledtext from docx import Document from PyPDF2 import PdfMerger, PdfReader, PdfWriter from reportlab.pdfgen import canvas from reportlab.lib.pagesizes import letter from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.lib.colors import red, black, white from reportlab.platypus import Table, TableStyle from io import BytesIO from datetime import datetime class PDFConverterApp: def __init__(self, root): self.root = root self.root.title("audio_data") self.root.geometry("800x650") # 增加窗口高度以容纳新控件 self.folders = [] self.log_messages = [] self.output_path = "" # 存储自定义输出路径 self.backup_mode = tk.BooleanVar(value=True) # 添加备份模式开关 self.output_filename = tk.StringVar(value="听筒磁干扰_Simulation_Result") # 默认文件名 self.create_widgets() def create_widgets(self): # 创建顶部框架 top_frame = ttk.Frame(self.root, padding=10) top_frame.pack(fill=tk.X) output_frame = ttk.LabelFrame(self.root, text="输出设置", padding=10) output_frame.pack(fill=tk.X, padx=10, pady=(0, 5)) # 文件名输入框 ttk.Label(output_frame, text="文件名:").grid(row=0, column=0, sticky=tk.W, padx=(0, 5)) filename_entry = ttk.Entry(output_frame, textvariable=self.output_filename, width=30) filename_entry.grid(row=0, column=1, sticky=tk.W, padx=5) # 输出路径选择 ttk.Label(output_frame, text="输出路径:").grid(row=0, column=2, sticky=tk.W, padx=(20, 5)) self.path_entry = ttk.Entry(output_frame, width=40, state='readonly') self.path_entry.grid(row=0, column=3, sticky=tk.EW, padx=5) browse_btn = ttk.Button(output_frame, text="浏览...", command=self.choose_output_path) browse_btn.grid(row=0, column=4, padx=(5, 0)) # 设置网格列权重 output_frame.columnconfigure(3, weight=1) # 添加文件夹按钮 add_btn = ttk.Button(top_frame, text="添加文件夹", command=self.add_folder) add_btn.pack(side=tk.LEFT, padx=5) # 移除文件夹按钮 remove_btn = ttk.Button(top_frame, text="移除选中", command=self.remove_selected) remove_btn.pack(side=tk.LEFT, padx=5) # 清空列表按钮 clear_btn = ttk.Button(top_frame, text="清空列表", command=self.clear_list) clear_btn.pack(side=tk.LEFT, padx=5) # 处理按钮 process_btn = ttk.Button(top_frame, text="开始处理", command=self.start_processing) process_btn.pack(side=tk.RIGHT, padx=5) # 创建文件夹列表 list_frame = ttk.LabelFrame(self.root, text="待处理文件夹", padding=10) list_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 滚动条 scrollbar = ttk.Scrollbar(list_frame) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) backup_frame = ttk.Frame(output_frame) backup_frame.grid(row=0, column=5, sticky=tk.W, padx=(20, 0)) self.backup_check = ttk.Checkbutton( backup_frame, text="备份原始Word文件", variable=self.backup_mode ) self.backup_check.pack(side=tk.LEFT) # 文件夹列表 self.folder_list = tk.Listbox( list_frame, selectmode=tk.EXTENDED, yscrollcommand=scrollbar.set, height=10 ) self.folder_list.pack(fill=tk.BOTH, expand=True) scrollbar.config(command=self.folder_list.yview) # 创建日志区域 log_frame = ttk.LabelFrame(self.root, text="处理日志", padding=10) log_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 日志文本框 self.log_text = scrolledtext.ScrolledText( log_frame, wrap=tk.WORD, state=tk.DISABLED ) self.log_text.pack(fill=tk.BOTH, expand=True) # 进度条 self.progress = ttk.Progressbar( self.root, orient=tk.HORIZONTAL, mode='determinate' ) self.progress.pack(fill=tk.X, padx=10, pady=5) def choose_output_path(self): """选择输出文件夹""" path = filedialog.askdirectory(title="选择输出文件夹") if path: self.output_path = path self.path_entry.config(state='normal') self.path_entry.delete(0, tk.END) self.path_entry.insert(0, path) self.path_entry.config(state='readonly') self.log(f"已设置输出路径: {path}") def add_folder(self): """添加要处理的文件夹""" folders = filedialog.askdirectory( title="选择要处理的文件夹", mustexist=True ) if folders: self.folders.append(folders) self.folder_list.insert(tk.END, folders) self.log(f"已添加文件夹: {folders}") def remove_selected(self): """移除选中的文件夹""" selected = self.folder_list.curselection() for index in selected[::-1]: folder = self.folder_list.get(index) self.folder_list.delete(index) self.folders.remove(folder) self.log(f"已移除文件夹: {folder}") def clear_list(self): """清空文件夹列表""" self.folder_list.delete(0, tk.END) self.folders = [] self.log("已清空文件夹列表") def log(self, message): """向日志区域添加消息""" timestamp = datetime.now().strftime("%H:%M:%S") log_entry = f"[{timestamp}] {message}" self.log_messages.append(log_entry) self.log_text.config(state=tk.NORMAL) self.log_text.insert(tk.END, log_entry + "\n") self.log_text.config(state=tk.DISABLED) self.log_text.yview(tk.END) # 自动滚动到底部 self.root.update_idletasks() def start_processing(self): """启动处理过程""" if not self.folders: messagebox.showwarning("警告", "请先添加要处理的文件夹") return # 禁用处理按钮 self.root.title("Word 转 PDF 合并工具 - 处理中...") self.progress["value"] = 0 # 在新线程中处理,避免界面冻结 thread = threading.Thread(target=self.process_folders) thread.daemon = True thread.start() def process_folders(self): """处理多个文件夹中的Word文件""" try: # 提前初始化 output_folder(修复点1) if self.output_path: output_folder = self.output_path else: output_folder = next((p for p in self.folders if os.path.isdir(p)), os.getcwd()) self.log(f"开始处理 {len(self.folders)} 个文件...") # 获取所有文件夹中的Word文件 word_files = self.get_all_word_files(self.folders) if not word_files: self.log("没有找到任何Word文档") return self.log(f"共找到 {len(word_files)} 个Word文档") self.progress["maximum"] = len(word_files) + 5 # 文件数 + 合并步骤 backup_dir = os.path.join(output_folder, "原始Word备份") if self.backup_mode.get(): os.makedirs(backup_dir, exist_ok=True) # 创建临时目录存储转换后的PDF with tempfile.TemporaryDirectory() as temp_dir: pdf_files_with_header = [] toc_entries = [] all_tables = {} current_page = 1 # 处理每个Word文件 for i, word_file in enumerate(word_files): self.progress["value"] = i + 1 # 修改点:只获取文件名,不包含路径 file_name = os.path.splitext(os.path.basename(word_file))[0] display_name = file_name # 这里只使用文件名 original_pdf = os.path.join(temp_dir, f"{file_name}_original.pdf") pdf_with_header = os.path.join(temp_dir, f"{file_name}_with_header.pdf") if self.backup_mode.get(): try: # 优化路径处理逻辑 dest_dir = os.path.join(backup_dir, os.path.basename(os.path.dirname(word_file))) os.makedirs(dest_dir, exist_ok=True) # 确保目标目录存在 # 直接复制文件 dest_path = os.path.join(dest_dir, os.path.basename(word_file)) shutil.copy2(word_file, dest_path) except OSError as e: print(f"文件备份失败: {e}") # 可添加日志记录或错误处理回调 except Exception as e: print(f"未知错误: {e}") # 提取表格数据 tables = self.extract_spec_table(word_file) if tables: all_tables[display_name] = tables self.log(f"已从 {display_name} 中提取 {len(tables)} 个数据表格") # 转换为PDF if self.word_to_pdf(word_file, original_pdf): # 添加内联标题 - 传入只包含文件名的display_name if self.add_inline_header(original_pdf, display_name, pdf_with_header): pdf_files_with_header.append(pdf_with_header) toc_entries.append((display_name, current_page)) current_page += self.get_pdf_page_count(pdf_with_header) else: pdf_files_with_header.append(original_pdf) toc_entries.append((display_name, current_page)) current_page += self.get_pdf_page_count(original_pdf) else: self.log(f"跳过 {display_name},转换失败") # 更新进度条 self.progress["value"] = len(word_files) + 1 if not pdf_files_with_header: self.log("没有成功转换的PDF文件,无法进行合并") return # 获取输出路径 if self.output_path: output_folder = self.output_path else: output_folder = next((p for p in self.folders if os.path.isdir(p)), os.getcwd()) # 获取文件名 report_name = self.output_filename.get().strip() if not report_name: report_name = self.get_folder_name_parts(self.folders[0]) # 使用默认规则 output_pdf = os.path.join(output_folder, f"{report_name}.pdf") # 合并PDF self.progress["value"] = len(word_files) + 2 success = self.merge_pdfs_with_summary( pdf_files_with_header, toc_entries, all_tables, output_pdf ) self.progress["value"] = len(word_files) + 3 if success: self.log(f"处理完成!输出文件: {output_pdf}") messagebox.showinfo("完成", f"处理完成!输出文件: {output_pdf}") else: self.log("处理失败") messagebox.showerror("错误", "处理过程中出现错误") self.root.title("Word 转 PDF 合并工具") except Exception as e: self.log(f"处理过程中出现错误: {str(e)}") messagebox.showerror("错误", f"处理过程中出现错误: {str(e)}") self.root.title("Word 转 PDF 合并工具") # 以下是原有的处理函数,保持不变但添加为类方法 def extract_spec_table(self, word_path): """从Word文档中提取SPEC(dB)、Simulation和Pass/Fail数据表格""" try: doc = Document(word_path) tables = [] for table in doc.tables: headers = [cell.text.strip() for cell in table.rows[0].cells] if "SPEC(dB)" in headers and "Simulation" in headers and "Pass/Fail" in headers: table_data = [] table_data.append(headers) for row in table.rows[1:]: row_data = [cell.text.strip() for cell in row.cells] table_data.append(row_data) tables.append(table_data) return tables except Exception as e: self.log(f"提取 {os.path.basename(word_path)} 中的表格时出错: {str(e)}") return [] def add_inline_header(self, pdf_path, title, output_path): """在PDF的第一页顶部添加一行红色加粗的标题""" try: reader = PdfReader(pdf_path) writer = PdfWriter() if len(reader.pages) > 0: first_page = reader.pages[0] packet = BytesIO() can = canvas.Canvas(packet, pagesize=letter) width, height = letter font_name = "Helvetica-Bold" try: pdfmetrics.registerFont(TTFont('SimSun', 'simsun.ttc')) pdfmetrics.registerFont(TTFont('SimSun-Bold', 'simsun.ttc')) font_name = "SimSun-Bold" except: pass can.setFont(font_name, 14) can.setFillColor(red) can.drawString(50, height - 50, title) can.save() packet.seek(0) title_reader = PdfReader(packet) title_page = title_reader.pages[0] first_page.merge_page(title_page) writer.add_page(first_page) for page in reader.pages[1:]: writer.add_page(page) with open(output_path, "wb") as f: writer.write(f) return True return False except Exception as e: self.log(f"PDF添加标题失败: {str(e)}") return False def create_summary_page(self, toc_entries, all_tables, output_path): """创建Summary页""" try: c = canvas.Canvas(output_path, pagesize=letter) width, height = letter font_name = "Helvetica" try: pdfmetrics.registerFont(TTFont('SimSun', 'simsun.ttc')) font_name = "SimSun" except: pass # Summary标题 c.setFont(font_name, 24) c.setFillColor(red) c.drawCentredString(width / 2.0, height - 50, "Summary") c.setFillColor(black) y_position = height - 100 # 添加数据汇总表格 if all_tables: c.setFont(font_name, 16) c.drawString(50, y_position, "Data Summary:") y_position -= 30 c.setFont(font_name, 10) table_width = width - 100 for doc_name, tables in all_tables.items(): c.setFont(font_name, 12) c.setFillColor(red) c.drawString(60, y_position, f"Document: {doc_name}") y_position -= 20 c.setFillColor(black) c.setFont(font_name, 10) for table_data in tables: col_widths = [table_width / len(table_data[0])] * len(table_data[0]) table = Table(table_data, colWidths=col_widths) style = TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), white), ('TEXTCOLOR', (0, 0), (-1, 0), black), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('FONTNAME', (0, 0), (-1, 0), font_name), ('FONTNAME', (0, 1), (-1, -1), font_name), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('BACKGROUND', (0, 1), (-1, -1), white), ('GRID', (0, 0), (-1, -1), 1, black) ]) table.setStyle(style) table_height = table.wrap(0, 0)[1] if y_position - table_height < 50: c.showPage() y_position = height - 50 c.setFont(font_name, 24) c.setFillColor(red) c.drawCentredString(width / 2.0, y_position, "Summary") y_position -= 50 c.setFillColor(black) table.drawOn(c, 50, y_position - table_height) y_position -= (table_height + 20) c.save() return output_path except Exception as e: self.log(f"创建Summary页失败: {str(e)}") return None def word_to_pdf(self, word_path, pdf_path): """将Word文档转换为PDF""" pythoncom.CoInitialize() try: word = win32com.client.Dispatch("Word.Application") word.Visible = False doc = word.Documents.Open(os.path.abspath(word_path)) doc.SaveAs(os.path.abspath(pdf_path), FileFormat=17) doc.Close() word.Quit() self.log(f"已将 {os.path.basename(word_path)} 转换为PDF") return True except Exception as e: self.log(f"转换 {os.path.basename(word_path)} 时出错: {str(e)}") return False finally: pythoncom.CoUninitialize() def get_pdf_page_count(self, pdf_path): """获取PDF文件的页数""" try: reader = PdfReader(pdf_path) return len(reader.pages) except: return 0 def merge_pdfs_with_summary(self, pdf_files, toc_entries, all_tables, output_path): """合并PDF文件并添加Summary页""" try: with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as summary_file: summary_path = summary_file.name self.create_summary_page(toc_entries, all_tables, summary_path) summary_page_count = self.get_pdf_page_count(summary_path) updated_toc_entries = [(title, page_num + summary_page_count) for title, page_num in toc_entries] self.create_summary_page(updated_toc_entries, all_tables, summary_path) merger = PdfMerger() merger.append(summary_path) current_page = summary_page_count for pdf, (title, _) in zip(pdf_files, updated_toc_entries): merger.append(pdf) merger.add_outline_item(title, current_page) current_page += self.get_pdf_page_count(pdf) merger.write(output_path) merger.close() os.remove(summary_path) self.log(f"已成功合并 {len(pdf_files)} 个PDF文件") return True except Exception as e: self.log(f"合并PDF时出错: {str(e)}") return False def get_all_word_files(self, folder_paths): """获取所有Word文件""" word_extensions = ['.docx', '.doc'] word_files = [] for folder_path in folder_paths: if not os.path.isdir(folder_path): continue for file in os.listdir(folder_path): file_ext = os.path.splitext(file)[1].lower() if file_ext in word_extensions: word_path = os.path.join(folder_path, file) word_files.append(word_path) return word_files def get_folder_name_parts(self, folder_paths): """生成报告文件名""" if not folder_paths: return "听筒磁干扰仿真报告" folder_path = folder_paths[0] norm_path = os.path.normpath(folder_path) parts = [p for p in norm_path.split(os.sep) if p] if len(parts) >= 3: return f"{parts[-3]}_{parts[-2]}_{parts[-1]}" elif len(parts) == 2: return f"{parts[-2]}_{parts[-1]}" elif len(parts) == 1: return parts[0] return "听筒磁干扰仿真报告" if __name__ == "__main__": root = tk.Tk() app = PDFConverterApp(root) root.mainloop() # 添加这行启动事件循环 添加在待处理的文件夹中包括所有子文件寻找Sound_Pres_Cal.xlsx将其一同复制到新创建的原始Word备份文件夹中
09-14
import os import sys import tempfile import shutil import random import cv2 import numpy as np from PIL import Image, ImageDraw import subprocess import json from datetime import datetime from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog, QMessageBox, QGroupBox, QProgressBar, QFrame) from PyQt5.QtCore import Qt, QThread, pyqtSignal from PyQt5.QtGui import QPixmap, QIcon # 定义需要替换的帧列表 REPLACE_FRAMES = [ 10, 20, 30, 40, 50, 60, 250, 260, 270, 280, 290, 300, 490, 500, 510, 520, 530, 540, 730, 740, 750, 760, 770, 780, 970, 980, 990, 1000, 1010, 1020, 1210, 1220, 1230, 1240, 1250, 1260, 1450, 1460, 1470, 1480, 1490, 1500, 1690, 1700, 1710, 1720, 1730, 1740, 1930, 1940, 1950, 1960, 1970, 1980, 2170, 2180, 2190, 2200, 2210, 2220, 2410, 2420, 2430, 2440, 2450, 2460, 2650, 2660, 2670, 2680, 2690, 2700, 2890, 2900, 2910, 2920, 2930, 2940, 3130, 3140, 3150, 3160, 3170, 3180, 3370, 3380, 3390, 3400, 3410, 3420, 3610, 3620, 3630, 3640, 3650, 3660, 3850, 3860, 3870, 3880, 3890, 3900, 4090, 4100, 4110, 4120, 4130, 4140, 4330, 4340, 4350, 4360, 4370, 4380, 4570, 4580, 4590, 4600, 4610, 4620, 4810, 4820, 4830, 4840, 4850, 4860, 5050, 5060, 5070, 5080, 5090, 5100, 5290, 5300, 5310, 5320, 5330, 5340, 5530, 5540, 5550, 5560, 5570, 5580, 5770, 5780, 5790, 5800, 5810, 5820, 6010, 6020, 6030, 6040, 6050, 6060, 6250, 6260, 6270, 6280, 6290, 6300, 6490, 6500, 6510, 6520, 6530, 6540, 6730, 6740, 6750, 6760, 6770, 6780, 6970, 6980, 6990, 7000, 7010, 7020, 7210, 7220, 7230, 7240, 7250, 7260, 7450, 7460, 7470, 7480, 7490, 7500, 7690, 7700, 7710, 7720, 7730, 7740, 7930, 7940, 7950, 7960, 7970, 7980 ] class VideoProcessor(QThread): progress_updated = pyqtSignal(int) status_updated = pyqtSignal(str) finished = pyqtSignal(bool, str) error_occurred = pyqtSignal(str) def __init__(self, mode, video_a_path, video_b_path=None): super().__init__() self.mode = mode self.video_a_path = video_a_path self.video_b_path = video_b_path self.temp_dir = tempfile.mkdtemp() self.output_path = "" self.total_frames = 0 self.fps = 0 self.duration = 0 self.cancel_requested = False def run(self): try: # 获取视频基本信息 self.get_video_info(self.video_a_path) if self.mode == "doodle": self.process_doodle_mode() elif self.mode == "shoot": self.process_shoot_mode() self.cleanup() if not self.cancel_requested: self.finished.emit(True, self.output_path) except Exception as e: self.status_updated.emit(f"错误: {str(e)}") self.error_occurred.emit(str(e)) self.cleanup() if not self.cancel_requested: self.finished.emit(False, str(e)) def cancel_processing(self): """取消处理过程""" self.cancel_requested = True self.status_updated.emit("处理已取消") self.cleanup() def get_video_info(self, video_path): """获取视频的基本信息""" cap = cv2.VideoCapture(video_path) if not cap.isOpened(): raise Exception("无法打开视频文件") self.fps = cap.get(cv2.CAP_PROP_FPS) self.total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) self.duration = self.total_frames / self.fps if self.fps > 0 else 0 cap.release() if self.total_frames == 0: raise Exception("视频文件中没有帧") def process_doodle_mode(self): # 步骤1: 提取音频 self.status_updated.emit("提取音频...") audio_path = os.path.join(self.temp_dir, "audio.aac") self.extract_audio(self.video_a_path, audio_path) if self.cancel_requested: return self.progress_updated.emit(5) # 步骤2: 处理视频帧 self.status_updated.emit("处理视频帧...") frames_dir = os.path.join(self.temp_dir, "frames_a") os.makedirs(frames_dir, exist_ok=True) self.process_video_frames_optimized(self.video_a_path, frames_dir) if self.cancel_requested: return self.progress_updated.emit(30) # 步骤3: 替换指定帧为涂鸦图片 self.status_updated.emit("替换指定帧为涂鸦图片...") self.replace_frames_with_doodles(frames_dir) if self.cancel_requested: return self.progress_updated.emit(60) # 步骤4: 合成视频B self.status_updated.emit("合成视频B...") video_b_path = os.path.join(self.temp_dir, "video_b.mp4") self.create_video_from_frames(frames_dir, video_b_path, self.fps) if self.cancel_requested: return self.progress_updated.emit(70) # 步骤5: 生成背景视频并叠加 self.status_updated.emit("生成背景视频并叠加...") bg_video_path = os.path.join(self.temp_dir, "background.mp4") self.create_background_video(bg_video_path, self.duration) if self.cancel_requested: return self.progress_updated.emit(80) # 步骤6: 最终处理 self.status_updated.emit("最终处理...") self.output_path = self.get_output_path("OK.mp4") self.overlay_videos(bg_video_path, video_b_path, self.output_path) if self.cancel_requested: return self.progress_updated.emit(90) # 步骤7: 添加处理后的音频 self.status_updated.emit("添加音频...") self.add_processed_audio(self.output_path, audio_path, self.output_path) if self.cancel_requested: return self.progress_updated.emit(95) # 步骤8: 污染元数据 self.status_updated.emit("处理元数据...") self.pollute_metadata(self.output_path) if self.cancel_requested: return self.progress_updated.emit(100) self.status_updated.emit("处理完成!") def process_shoot_mode(self): # 步骤1: 提取音频 self.status_updated.emit("提取音频...") audio_path = os.path.join(self.temp_dir, "audio.aac") self.extract_audio(self.video_a_path, audio_path) if self.cancel_requested: return self.progress_updated.emit(5) # 步骤2: 处理视频A帧 self.status_updated.emit("处理视频A帧...") frames_dir_a = os.path.join(self.temp_dir, "frames_a") os.makedirs(frames_dir_a, exist_ok=True) self.process_video_frames_optimized(self.video_a_path, frames_dir_a) if self.cancel_requested: return self.progress_updated.emit(20) # 步骤3: 处理视频B self.status_updated.emit("处理视频B...") frames_dir_b = os.path.join(self.temp_dir, "frames_b") os.makedirs(frames_dir_b, exist_ok=True) # 调整视频B时长与视频A一致 adjusted_b_path = os.path.join(self.temp_dir, "adjusted_b.mp4") self.adjust_video_duration(self.video_b_path, adjusted_b_path, self.duration) if self.cancel_requested: return # 处理视频B帧 self.process_video_frames_optimized(adjusted_b_path, frames_dir_b) if self.cancel_requested: return self.progress_updated.emit(40) # 步骤4: 替换指定帧 self.status_updated.emit("替换指定帧...") self.replace_frames_from_b(frames_dir_a, frames_dir_b) if self.cancel_requested: return self.progress_updated.emit(70) # 步骤5: 合成视频C self.status_updated.emit("合成视频C...") video_c_path = os.path.join(self.temp_dir, "video_c.mp4") self.create_video_from_frames(frames_dir_a, video_c_path, self.fps) if self.cancel_requested: return self.progress_updated.emit(80) # 步骤6: 生成背景视频并叠加 self.status_updated.emit("生成背景视频并叠加...") bg_video_path = os.path.join(self.temp_dir, "background.mp4") self.create_background_video(bg_video_path, self.duration) if self.cancel_requested: return self.progress_updated.emit(85) # 步骤7: 最终处理 self.status_updated.emit("最终处理...") self.output_path = self.get_output_path("OK.mp4") self.overlay_videos(bg_video_path, video_c_path, self.output_path) if self.cancel_requested: return self.progress_updated.emit(90) # 步骤8: 添加处理后的音频 self.status_updated.emit("添加音频...") self.add_processed_audio(self.output_path, audio_path, self.output_path) if self.cancel_requested: return self.progress_updated.emit(95) # 步骤9: 污染元数据 self.status_updated.emit("处理元数据...") self.pollute_metadata(self.output_path) if self.cancel_requested: return self.progress_updated.emit(100) self.status_updated.emit("处理完成!") def extract_audio(self, video_path, audio_path): """提取音频""" if self.cancel_requested: return try: command = [ 'ffmpeg', '-i', video_path, '-vn', '-acodec', 'copy', audio_path, '-y', '-loglevel', 'error' ] subprocess.run(command, check=True, capture_output=True) except subprocess.CalledProcessError as e: raise Exception(f"音频提取失败: {str(e)}") except FileNotFoundError: raise Exception("未找到FFmpeg,请确保已安装FFmpeg并添加到系统路径") def process_video_frames_optimized(self, video_path, output_dir): """优化版的视频帧处理""" if self.cancel_requested: return cap = cv2.VideoCapture(video_path) if not cap.isOpened(): raise Exception("无法打开视频文件") frame_count = 0 success = True while success and not self.cancel_requested: success, frame = cap.read() if not success: break # 调整分辨率为1080x1920 frame = cv2.resize(frame, (1080, 1920)) # 保存帧 frame_path = os.path.join(output_dir, f"{frame_count+1:04d}.jpg") cv2.imwrite(frame_path, frame, [cv2.IMWRITE_JPEG_QUALITY, 95]) frame_count += 1 # 每处理50帧更新一次进度 if frame_count % 50 == 0: progress = int(frame_count * 100 / self.total_frames) self.progress_updated.emit(progress) cap.release() if frame_count == 0 and not self.cancel_requested: raise Exception("视频文件中没有帧") def replace_frames_with_doodles(self, frames_dir): """用涂鸦图片替换指定帧""" if self.cancel_requested: return frame_files = sorted([f for f in os.listdir(frames_dir) if f.endswith('.jpg')], key=lambda x: int(x.split('.')[0])) # 只处理存在的帧 valid_frames = [f for f in REPLACE_FRAMES if f <= len(frame_files)] if not valid_frames: return for i, frame_num in enumerate(valid_frames): if self.cancel_requested: return doodle_img = self.generate_doodle_image() frame_path = os.path.join(frames_dir, f"{frame_num:04d}.jpg") doodle_img.save(frame_path, quality=95) # 更新进度 if i % 10 == 0: # 每10帧更新一次进度 progress = 30 + int((i + 1) * 30 / len(valid_frames)) self.progress_updated.emit(progress) def replace_frames_from_b(self, frames_dir_a, frames_dir_b): """从视频B替换帧到视频A""" if self.cancel_requested: return frame_files_a = sorted([f for f in os.listdir(frames_dir_a) if f.endswith('.jpg')], key=lambda x: int(x.split('.')[0])) # 只处理存在的帧 valid_frames = [f for f in REPLACE_FRAMES if f <= len(frame_files_a)] if not valid_frames: return for i, frame_num in enumerate(valid_frames): if self.cancel_requested: return # 从视频B获取对应帧 frame_b_path = os.path.join(frames_dir_b, f"{frame_num:04d}.jpg") if os.path.exists(frame_b_path): # 替换视频A的帧 frame_a_path = os.path.join(frames_dir_a, f"{frame_num:04d}.jpg") shutil.copyfile(frame_b_path, frame_a_path) # 更新进度 if i % 10 == 0: # 每10帧更新一次进度 progress = 40 + int((i + 1) * 30 / len(valid_frames)) self.progress_updated.emit(progress) def generate_doodle_image(self): """生成随机涂鸦图片""" width, height = 1080, 1920 img = Image.new('RGB', (width, height), color=(255, 255, 255)) draw = ImageDraw.Draw(img) # 随机绘制一些线条和形状 for _ in range(random.randint(5, 15)): x1 = random.randint(0, width) y1 = random.randint(0, height) x2 = random.randint(0, width) y2 = random.randint(0, height) color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) draw.line([(x1, y1), (x2, y2)], fill=color, width=random.randint(1, 5)) for _ in range(random.randint(3, 8)): x = random.randint(0, width) y = random.randint(0, height) size = random.randint(20, 200) color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) draw.rectangle([x, y, x+size, y+size], fill=color, outline=None) for _ in range(random.randint(2, 5)): x = random.randint(0, width) y = random.randint(0, height) radius = random.randint(10, 100) color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)) draw.ellipse([x, y, x+radius, y+radius], fill=color, outline=None) return img def create_video_from_frames(self, frames_dir, output_path, fps): """从帧创建视频""" if self.cancel_requested: return try: frame_pattern = os.path.join(frames_dir, '%04d.jpg') command = [ 'ffmpeg', '-r', str(fps), '-i', frame_pattern, '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '18', '-preset', 'fast', output_path, '-y', '-loglevel', 'error' ] subprocess.run(command, check=True, capture_output=True) except subprocess.CalledProcessError as e: raise Exception(f"视频合成失败: {str(e)}") def create_background_video(self, output_path, duration): """创建背景视频 - 修复版""" if self.cancel_requested: return try: # 使用更简单可靠的背景生成方法 # 创建一个纯黑背景视频,然后添加轻微的随机噪声 command = [ 'ffmpeg', '-f', 'lavfi', '-i', f'color=black:size=1080x2336:rate={self.fps}:duration={duration}', '-vf', 'noise=alls=20:allf=t+u', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '--crf', '23', '-preset', 'fast', output_path, '-y', '-loglevel', 'error' ] subprocess.run(command, check=True, capture_output=True) except subprocess.CalledProcessError as e: # 如果上面的方法失败,尝试使用更简单的方法 try: # 使用纯黑背景,不添加噪声 command = [ 'ffmpeg', '-f', 'lavfi', '-i', f'color=black:size=1080x2336:rate={self.fps}:duration={duration}', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '23', '-preset', 'fast', output_path, '-y', '-loglevel', 'error' ] subprocess.run(command, check=True, capture_output=True) except subprocess.CalledProcessError as e2: raise Exception(f"背景视频创建失败: {str(e2)}") def overlay_videos(self, bg_video_path, fg_video_path, output_path): """叠加视频""" if self.cancel_requested: return try: command = [ 'ffmpeg', '-i', bg_video_path, '-i', fg_video_path, '-filter_complex', '[1]scale=1080:1920:force_original_aspect_ratio=decrease[fg];' + '[0][fg]overlay=(W-w)/2:(H-h)/2:format=auto:alpha=0.98', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '18', '-preset', 'fast', output_path, '-y', '-loglevel', 'error' ] subprocess.run(command, check=True, capture_output=True) except subprocess.CalledProcessError as e: raise Exception(f"视频叠加失败: {str(e)}") def add_processed_audio(self, video_path, audio_path, output_path): """添加处理后的音频""" if self.cancel_requested: return try: temp_output = os.path.join(self.temp_dir, "temp_output.mp4") command = [ 'ffmpeg', '-i', video_path, '-i', audio_path, '-c:v', 'copy', '-c:a', 'aac', '-b:a', '192k', '-map', '0:v:0', '-map', '1:a:0', '-shortest', temp_output, '-y', '-loglevel', 'error' ] subprocess.run(command, check=True, capture_output=True) shutil.move(temp_output, output_path) except subprocess.CalledProcessError as e: raise Exception(f"音频添加失败: {str(e)}") def adjust_video_duration(self, video_path, output_path, target_duration): """调整视频时长""" if self.cancel_requested: return try: # 获取当前视频时长 cap = cv2.VideoCapture(video_path) fps = cap.get(cv2.CAP_PROP_FPS) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) current_duration = frame_count / fps if fps > 0 else 0 cap.release() if current_duration <= 0: raise Exception("无法获取视频时长") # 计算速度因子 speed_factor = current_duration / target_duration command = [ 'ffmpeg', '-i', video_path, '-filter:v', f'setpts={speed_factor}*PTS', '-an', output_path, '-y', '-loglevel', 'error' ] subprocess.run(command, check=True, capture_output=True) except subprocess.CalledProcessError as e: raise Exception(f"视频时长调整失败: {str(e)}") def pollute_metadata(self, video_path): """污染元数据""" if self.cancel_requested: return try: # 使用FFmpeg直接添加元数据 temp_output = os.path.join(self.temp_dir, "temp_metadata.mp4") # 生成随机元数据 creation_time = self.random_date() location = self.random_location() command = [ 'ffmpeg', '-i', video_path, '-metadata', f'creation_time={creation_time}', '-metadata', f'location={location}', '-metadata', 'make=RandomCamera', '-metadata', 'model=RandomModel', '-metadata', 'software=Video Processor v1.0', '-c', 'copy', temp_output, '-y', '-loglevel', 'error' ] subprocess.run(command, check=True, capture_output=True) shutil.move(temp_output, video_path) except (subprocess.CalledProcessError, FileNotFoundError): # 如果失败,跳过元数据污染 pass def random_date(self): """生成随机日期""" year = random.randint(2010, 2023) month = random.randint(1, 12) day = random.randint(1, 28) hour = random.randint(0, 23) minute = random.randint(0, 59) second = random.randint(0, 59) return f"{year:04d}-{month:02d}-{day:02d}T{hour:02d}:{minute:02d}:{second:02d}Z" def random_location(self): """生成随机位置""" locations = [ "40.7128° N, 74.0060° W", # 纽约 "34.0522° N, 118.2437° W", # 洛杉矶 "51.5074° N, 0.1278° W", # 伦敦 "48.8566° N, 2.3522° E", # 巴黎 "35.6762° N, 139.6503° E", # 东京 "39.9042° N, 116.4074° E", # 北京 "-33.8688° N, 151.2093° E", # 悉尼 "55.7558° N, 37.6173° E" # 莫斯科 ] return random.choice(locations) def get_output_path(self, base_name): """获取输出路径,避免重名""" counter = 0 name, ext = os.path.splitext(base_name) output_path = base_name while os.path.exists(output_path): counter += 1 output_path = f"{name}_{counter}{ext}" return output_path def cleanup(self): """清理临时文件""" if os.path.exists(self.temp_dir): try: shutil.rmtree(self.temp_dir) except: pass # 忽略清理错误 class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("视频处理工具") self.setGeometry(100, 100, 800, 700) # 设置图标 if hasattr(sys, '_MEIPASS'): # 打包后的路径 icon_path = os.path.join(sys._MEIPASS, 'app.ico') else: # 开发时的路径 icon_path = 'app.ico' if os.path.exists(icon_path): self.setWindowIcon(QIcon(icon_path)) self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.layout = QVBoxLayout() self.central_widget.setLayout(self.layout) self.setup_ui() self.video_a_path = "" self.video_b_path = "" self.processor = None def setup_ui(self): # 标题 title_label = QLabel("视频处理工具") title_label.setAlignment(Qt.AlignCenter) title_label.setStyleSheet("font-size: 20px; font-weight: bold; margin: 15px;") self.layout.addWidget(title_label) # 二维码区域 qr_frame = QFrame() qr_frame.setFrameShape(QFrame.StyledPanel) qr_layout = QVBoxLayout() qr_label = QLabel("扫描二维码获取更多信息") qr_label.setAlignment(Qt.AlignCenter) qr_layout.addWidget(qr_label) # 加载二维码图片 if hasattr(sys, '_MEIPASS'): qr_path = os.path.join(sys._MEIPASS, 'qrcode.png') else: qr_path = 'qrcode.png' if os.path.exists(qr_path): qr_pixmap = QPixmap(qr_path) qr_pixmap = qr_pixmap.scaled(150, 150, Qt.KeepAspectRatio, Qt.SmoothTransformation) qr_image_label = QLabel() qr_image_label.setPixmap(qr_pixmap) qr_image_label.setAlignment(Qt.AlignCenter) qr_layout.addWidget(qr_image_label) qr_frame.setLayout(qr_layout) qr_frame.setMaximumHeight(200) self.layout.addWidget(qr_frame) # 涂鸦模式组 doodle_group = QGroupBox("涂鸦模式") doodle_group.setStyleSheet("QGroupBox { font-weight: bold; }") doodle_layout = QVBoxLayout() doodle_desc = QLabel("将视频A的特定帧替换为AI生成的涂鸦图片") doodle_desc.setWordWrap(True) doodle_layout.addWidget(doodle_desc) doodle_btn_layout = QHBoxLayout() self.doodle_video_a_btn = QPushButton("选择视频A") self.doodle_video_a_btn.clicked.connect(lambda: self.select_video("doodle_a")) self.doodle_video_a_label = QLabel("未选择视频") self.doodle_video_a_label.setWordWrap(True) doodle_btn_layout.addWidget(self.doodle_video_a_btn) doodle_btn_layout.addWidget(self.doodle_video_a_label) doodle_layout.addLayout(doodle_btn_layout) self.doodle_process_btn = QPushButton("开始处理") self.doodle_process_btn.clicked.connect(lambda: self.start_processing("doodle")) doodle_layout.addWidget(self.doodle_process_btn) doodle_group.setLayout(doodle_layout) self.layout.addWidget(doodle_group) # 实拍模式组 shoot_group = QGroupBox("实拍模式") shoot_group.setStyleSheet("QGroupBox { font-weight: bold; }") shoot_layout = QVBoxLayout() shoot_desc = QLabel("将视频A的特定帧替换为视频B的对应帧") shoot_desc.setWordWrap(True) shoot_layout.addWidget(shoot_desc) shoot_btn_layout_a = QHBoxLayout() self.shoot_video_a_btn = QPushButton("选择视频A") self.shoot_video_a_btn.clicked.connect(lambda: self.select_video("shoot_a")) self.shoot_video_a_label = QLabel("未选择视频") self.shoot_video_a_label.setWordWrap(True) shoot_btn_layout_a.addWidget(self.shoot_video_a_btn) shoot_btn_layout_a.addWidget(self.shoot_video_a_label) shoot_layout.addLayout(shoot_btn_layout_a) shoot_btn_layout_b = QHBoxLayout() self.shoot_video_b_btn = QPushButton("选择视频B") self.shoot_video_b_btn.clicked.connect(lambda: self.select_video("shoot_b")) self.shoot_video_b_label = QLabel("未选择视频") self.shoot_video_b_label.setWordWrap(True) shoot_btn_layout_b.addWidget(self.shoot_video_b_btn) shoot_btn_layout_b.addWidget(self.shoot_video_b_label) shoot_layout.addLayout(shoot_btn_layout_b) self.shoot_process_btn = QPushButton("开始处理") self.shoot_process_btn.clicked.connect(lambda: self.start_processing("shoot")) shoot_layout.addWidget(self.shoot_process_btn) shoot_group.setLayout(shoot_layout) self.layout.addWidget(shoot_group) # 进度条 self.progress_bar = QProgressBar() self.progress_bar.setVisible(False) self.layout.addWidget(self.progress_bar) # 状态标签 self.status_label = QLabel("就绪") self.status_label.setAlignment(Qt.AlignCenter) self.layout.addWidget(self.status_label) # 取消按钮 self.cancel_btn = QPushButton("取消处理") self.cancel_btn.setVisible(False) self.cancel_btn.clicked.connect(self.cancel_processing) self.layout.addWidget(self.cancel_btn) # 底部信息 info_label = QLabel("© 2023 视频处理工具 | 支持格式: MP4, AVI, MOV, MKV") info_label.setAlignment(Qt.AlignCenter) info_label.setStyleSheet("color: gray; font-size: 10px; margin-top: 10px;") self.layout.addWidget(info_label) def select_video(self, mode): file_path, _ = QFileDialog.getOpenFileName( self, "选择视频文件", "", "视频文件 (*.mp4 *.avi *.mov *.mkv);;所有文件 (*.*)" ) if file_path: if mode == "doodle_a": self.video_a_path = file_path self.doodle_video_a_label.setText(os.path.basename(file_path)) elif mode == "shoot_a": self.video_a_path = file_path self.shoot_video_a_label.setText(os.path.basename(file_path)) elif mode == "shoot_b": self.video_b_path = file_path self.shoot_video_b_label.setText(os.path.basename(file_path)) def start_processing(self, mode): if mode == "doodle": if not self.video_a_path: QMessageBox.warning(self, "警告", "请先选择视频A!") return self.processor = VideoProcessor("doodle", self.video_a_path) elif mode == "shoot": if not self.video_a_path or not self.video_b_path: QMessageBox.warning(self, "警告", "请先选择视频A和视频B!") return self.processor = VideoProcessor("shoot", self.video_a_path, self.video_b_path) # 连接信号和槽 self.processor.progress_updated.connect(self.update_progress) self.processor.status_updated.connect(self.update_status) self.processor.finished.connect(self.processing_finished) self.processor.error_occurred.connect(self.handle_error) # 禁用按钮 self.set_buttons_enabled(False) # 显示进度条和取消按钮 self.progress_bar.setVisible(True) self.cancel_btn.setVisible(True) self.progress_bar.setValue(0) # 开始处理 self.processor.start() def update_progress(self, value): self.progress_bar.setValue(value) def update_status(self, message): self.status_label.setText(message) def processing_finished(self, success, message): self.set_buttons_enabled(True) self.cancel_btn.setVisible(False) if success: self.status_label.setText("处理完成!") QMessageBox.information(self, "成功", f"视频处理完成! 输出文件: {message}") else: self.status_label.setText("处理失败!") def handle_error(self, error_message): """处理错误信号""" QMessageBox.critical(self, "错误", f"处理过程中发生错误: {error_message}") def cancel_processing(self): """取消处理""" if self.processor and self.processor.isRunning(): self.processor.cancel_processing() self.status_label.setText("正在取消处理...") self.cancel_btn.setEnabled(False) def set_buttons_enabled(self, enabled): self.doodle_process_btn.setEnabled(enabled) self.shoot_process_btn.setEnabled(enabled) self.doodle_video_a_btn.setEnabled(enabled) self.shoot_video_a_btn.setEnabled(enabled) self.shoot_video_b_btn.setEnabled(enabled) if enabled: self.progress_bar.setVisible(False) if __name__ == "__main__": # 确保程序单实例运行 try: from PyQt5.QtCore import QSharedMemory import sys # 创建共享内存段,确保程序单实例运行 app_shared_memory = QSharedMemory("VideoProcessorTool") if not app_shared_memory.create(512, QSharedMemory.ReadWrite): QMessageBox.critical(None, "错误", "程序已经在运行中!") sys.exit(1) app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) except Exception as e: QMessageBox.critical(None, "错误", f"程序启动失败: {str(e)}") 修改这个主程序代码,使用多线程加速处理 ,线程数量可以自己选择, 软件处理任务时候总是黑框跳出来,怎么让它不跳出来,软件主界面上写着 将视频A的特定帧替换为AI生成的涂鸦图片,将视频A的特定帧替换为视频B的对应帧,这两行字不显示,处理到每一步在软件的主界面不显示出来 只显示进度条,
08-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值