import sys
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import cv2
import numpy as np
import random
from datetime import datetime, timedelta
import threading
import subprocess
import shutil
# 设置DPI感知,确保在高分辨率屏幕上显示正常
try:
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
except:
pass
# 尝试导入可选依赖
MOVIEPY_AVAILABLE = False
PSUTIL_AVAILABLE = False
# 处理打包后的导入问题
if getattr(sys, 'frozen', False):
# 运行在打包环境中
base_path = sys._MEIPASS
# 添加可能的包路径
for package in ['moviepy', 'imageio', 'imageio_ffmpeg', 'psutil']:
package_path = os.path.join(base_path, package)
if os.path.exists(package_path):
sys.path.insert(0, package_path)
else:
# 正常运行时
base_path = os.path.dirname(__file__)
try:
from moviepy.editor import VideoFileClip, AudioFileClip
MOVIEPY_AVAILABLE = True
except ImportError as e:
print(f"MoviePy import error: {e}")
try:
import psutil
PSUTIL_AVAILABLE = True
except ImportError:
pass
# 全局变量
stop_processing = False
# 定义核心处理函数
def add_invisible_overlay(frame, strength):
"""核心功能:添加全透明扰动层(对抗哈希检测)"""
# 将强度从0-100映射到更合理的扰动范围 (1-5)
overlay_strength = strength / 100.0 * 4 + 1 # 1 to 5
# 1. 创建一个和帧大小一样的随机噪声图像
noise = np.random.randn(*frame.shape).astype(np.float32) * overlay_strength
# 2. 将噪声加到原帧上
new_frame = frame.astype(np.float32) + noise
# 3. 确保像素值在0-255之间
new_frame = np.clip(new_frame, 0, 255).astype(np.uint8)
return new_frame
def resize_with_padding(frame, target_width=720, target_height=1560):
"""将帧调整为目标分辨率,保持宽高比,不足部分用黑色填充"""
# 获取原始尺寸
h, w = frame.shape[:2]
# 计算缩放比例
scale = target_width / w
new_h = int(h * scale)
# 如果缩放后的高度超过目标高度,则按高度缩放
if new_h > target_height:
scale = target_height / h
new_w = int(w * scale)
resized = cv2.resize(frame, (new_w, target_height))
else:
resized = cv2.resize(frame, (target_width, new_h))
# 创建目标画布(黑色)
canvas = np.zeros((target_height, target_width, 3), dtype=np.uint8)
# 计算放置位置(居中)
y_offset = (target_height - resized.shape[0]) // 2
x_offset = (target_width - resized.shape[1]) // 2
# 将缩放后的图像放到画布上
canvas[y_offset:y_offset+resized.shape[0], x_offset:x_offset+resized.shape[1]] = resized
# 在黑色区域添加不可见的随机噪声(亮度值0-5)
black_areas = np.where(canvas == 0)
if len(black_areas[0]) > 0:
# 只对黑色区域添加噪声
noise = np.random.randint(0, 6, size=black_areas[0].shape, dtype=np.uint8)
for i in range(3): # 对RGB三个通道
canvas[black_areas[0], black_areas[1], i] = noise
return canvas
def generate_random_metadata():
"""生成随机的元数据"""
# 随机设备型号列表
devices = [
"iPhone15,3", "iPhone15,2", "iPhone14,2", "iPhone14,1",
"SM-G998B", "SM-G996B", "SM-G781B",
"Mi 11 Ultra", "Mi 10", "Redmi Note 10 Pro"
]
# 随机应用程序列表
apps = [
"Wxmm_9020230808", "Wxmm_9020230701", "Wxmm_9020230605",
"LemonCamera_5.2.1", "CapCut_9.5.0", "VivaVideo_9.15.5"
]
# 随机生成创建时间(最近30天内)
now = datetime.now()
random_days = random.randint(0, 30)
random_hours = random.randint(0, 23)
random_minutes = random.randint(0, 59)
random_seconds = random.randint(0, 59)
creation_time = now - timedelta(days=random_days, hours=random_hours,
minutes=random_minutes, seconds=random_seconds)
return {
"device_model": random.choice(devices),
"writing_application": random.choice(apps),
"creation_time": creation_time.strftime("%Y-%m-%dT%H:%M:%S"),
"title": f"Video_{random.randint(10000, 99999)}",
"artist": "Mobile User",
"compatible_brands": "isom,iso2,avc1,mp41",
"major_brand": "isom"
}
def corrupt_metadata(input_path, output_path, custom_metadata=None, gpu_type="cpu"):
"""使用FFmpeg深度修改元数据"""
if custom_metadata is None:
custom_metadata = generate_random_metadata()
# 根据GPU类型设置编码器
if gpu_type == "nvidia":
video_encoder = "h264_nvenc"
elif gpu_type == "amd":
video_encoder = "h264_amf"
elif gpu_type == "intel":
video_encoder = "h264_qsv"
else:
video_encoder = "libx264"
# 构造FFmpeg命令
command = [
'ffmpeg', '-i', input_path,
'-map_metadata', '-1', # 丢弃所有元数据
'-metadata', f'title={custom_metadata["title"]}',
'-metadata', f'artist={custom_metadata["artist"]}',
'-metadata', f'creation_time={custom_metadata["creation_time"]}',
'-metadata', f'compatible_brands={custom_metadata["compatible_brands"]}',
'-metadata', f'major_brand={custom_metadata["major_brand"]}',
'-metadata', f'handler_name={custom_metadata["writing_application"]}',
'-movflags', 'use_metadata_tags',
'-c:v', video_encoder,
'-preset', 'medium',
'-crf', str(random.randint(18, 23)), # 随机CRF值
'-profile:v', 'high',
'-level', '4.0',
'-pix_fmt', 'yuv420p',
'-c:a', 'aac',
'-b:a', '96k',
'-ar', '44100',
'-y', output_path
]
# 添加设备特定元数据
if 'iPhone' in custom_metadata["device_model"]:
command.extend([
'-metadata', f'com.apple.quicktime.model={custom_metadata["device_model"]}',
'-metadata', f'com.apple.quicktime.software=16.0'
])
try:
subprocess.run(command, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return True
except subprocess.CalledProcessError as e:
print(f"FFmpeg error: {e}")
return False
except FileNotFoundError:
messagebox.showerror('致命错误', '错误:未找到FFmpeg!\n请确保ffmpeg.exe在程序同一目录下。')
return False
def create_background_video(output_path, duration, width=720, height=1560, fps=30):
"""创建带有扰动的黑色背景视频"""
# 使用FFmpeg创建带有随机噪声的黑色背景视频
cmd = [
'ffmpeg',
'-f', 'lavfi',
'-i', f'nullsrc=s={width}x{height}:d={duration}:r={fps}',
'-vf', 'noise=alls=20:allf=t',
'-c:v', 'libx264',
'-pix_fmt', 'yuv420p',
'-y', output_path
]
try:
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
return True
except subprocess.CalledProcessError as e:
print(f"创建背景视频失败: {e}")
return False
def detect_gpu():
"""检测可用的GPU类型"""
try:
# 尝试使用nvidia-smi检测NVIDIA GPU
try:
result = subprocess.run(['nvidia-smi'], capture_output=True, text=True, check=True)
if result.returncode == 0:
return "nvidia"
except (subprocess.CalledProcessError, FileNotFoundError):
pass
# 尝试使用Windows Management Instrumentation检测AMD和Intel GPU
try:
import wmi
w = wmi.WMI()
for gpu in w.Win32_VideoController():
name = gpu.Name.lower()
if "amd" in name or "radeon" in name:
return "amd"
elif "intel" in name:
return "intel"
except ImportError:
pass
# 尝试使用dxdiag检测GPU
try:
result = subprocess.run(['dxdiag', '/t', 'dxdiag.txt'], capture_output=True, text=True, check=True)
if result.returncode == 0:
with open('dxdiag.txt', 'r', encoding='utf-16') as f:
content = f.read().lower()
if "nvidia" in content:
return "nvidia"
elif "amd" in content or "radeon" in content:
return "amd"
elif "intel" in content:
return "intel"
except (subprocess.CalledProcessError, FileNotFoundError):
pass
except Exception as e:
print(f"GPU检测失败: {e}")
return "cpu"
def add_audio_watermark(audio_clip, strength):
"""给音频添加水印(简化版本)"""
# 在实际应用中,这里应该实现音频扰动算法
# 这里只是一个示例,返回原始音频
return audio_clip
def process_video():
"""主处理流程控制器"""
global stop_processing
if not MOVIEPY_AVAILABLE:
messagebox.showerror("错误", "MoviePy库未安装!请运行: pip install moviepy")
return False
input_path = input_entry.get()
output_path = output_entry.get()
if not input_path or not output_path:
messagebox.showerror('错误', '请先选择输入和输出文件!')
return False
# 解析用户选择的强度和功能
strength = strength_scale.get()
use_video_perturb = video_var.get()
use_audio_perturb = audio_var.get()
use_metadata_corrupt = metadata_var.get()
use_gan = gan_var.get()
use_resize = resize_var.get()
use_pip = pip_var.get()
pip_opacity = pip_opacity_scale.get() if use_pip else 2
num_pip_videos = int(pip_num_combo.get()) if use_pip else 0
gpu_type = gpu_combo.get()
# 临时文件路径
temp_video_path = "temp_processed.mp4"
temp_audio_path = "temp_audio.aac"
pip_temp_path = "temp_pip.mp4" if use_pip else None
background_path = "temp_background.mp4"
final_output_path = output_path
# 获取原始视频时长和帧率
try:
original_clip = VideoFileClip(input_path)
original_duration = original_clip.duration
original_fps = original_clip.fps
original_clip.close()
except Exception as e:
messagebox.showerror('错误', f'无法打开视频文件: {str(e)}')
return False
try:
# 第一步:创建背景视频
if use_resize:
if not create_background_video(background_path, original_duration, 720, 1560, original_fps):
messagebox.showerror('错误', '创建背景视频失败!')
return False
# 第二步:处理视频和音频
if use_video_perturb or use_resize:
# 使用OpenCV打开视频
cap = cv2.VideoCapture(input_path)
# 获取视频属性
fps = int(cap.get(cv2.CAP_PROP_FPS))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
# 设置目标分辨率
target_width, target_height = 720, 1560
# 创建VideoWriter来写入处理后的视频
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(temp_video_path, fourcc, fps, (target_width, target_height))
processed_frames = 0
# 主循环:逐帧处理
while True:
ret, frame = cap.read()
if not ret:
break # 读到结尾就退出
# 如果勾选了"调整分辨率",先调整分辨率
if use_resize:
frame = resize_with_padding(frame, target_width, target_height)
# 如果勾选了"视频扰动",就对当前帧进行处理
if use_video_perturb:
frame = add_invisible_overlay(frame, strength)
# 写入处理后的帧
out.write(frame)
processed_frames += 1
# 更新进度条
progress_var.set(processed_frames / total_frames * 100)
root.update_idletasks()
# 检查是否取消
if stop_processing:
break
# 释放资源
cap.release()
out.release()
if stop_processing:
messagebox.showinfo('信息', '处理已取消!')
return False
# 第三步:处理音频
if use_audio_perturb:
# 从原视频提取音频
original_video = VideoFileClip(input_path)
original_audio = original_video.audio
if original_audio is not None:
# 给音频添加水印
processed_audio = add_audio_watermark(original_audio, strength)
# 保存处理后的音频到临时文件
processed_audio.write_audiofile(temp_audio_path, logger=None)
processed_audio.close()
original_video.close()
else:
# 如果没有勾选音频处理,直接提取原音频
original_video = VideoFileClip(input_path)
original_audio = original_video.audio
if original_audio is not None:
original_audio.write_audiofile(temp_audio_path, logger=None)
original_video.close()
# 第四步:合并视频和音频
# 如果处理了视频或调整了分辨率,使用处理后的视频,否则使用原视频
video_source = temp_video_path if (use_video_perturb or use_resize) else input_path
# 如果有音频文件,合并音频
if os.path.exists(temp_audio_path):
# 使用FFmpeg合并音视频
merge_cmd = [
'ffmpeg', '-i', video_source,
'-i', temp_audio_path,
'-c:v', 'copy',
'-c:a', 'aac',
'-map', '0:v:0',
'-map', '1:a:0',
'-shortest',
'-y', final_output_path
]
subprocess.run(merge_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
else:
# 如果没有音频,直接复制视频
shutil.copy2(video_source, final_output_path)
# 第五步:处理元数据(无论是否处理视频音频,只要勾选了就执行)
if use_metadata_corrupt:
custom_meta = generate_random_metadata()
temp_final_path = final_output_path + "_temp.mp4"
success = corrupt_metadata(final_output_path, temp_final_path, custom_meta, gpu_type)
if success:
# 用处理完元数据的文件替换最终文件
if os.path.exists(final_output_path):
os.remove(final_output_path)
os.rename(temp_final_path, final_output_path)
else:
return False
# 第六步:GAN处理(预留功能)
if use_gan:
messagebox.showinfo('信息', 'GAN功能是预留选项,在当前版本中未实际生效。')
messagebox.showinfo('完成', f'处理完成!\n输出文件已保存至: {final_output_path}')
return True
except Exception as e:
messagebox.showerror('错误', f'处理过程中出现错误: {str(e)}')
return False
finally:
# 清理可能的临时文件
for temp_file in [temp_video_path, temp_audio_path, pip_temp_path, background_path]:
if temp_file and os.path.exists(temp_file):
try:
os.remove(temp_file)
except:
pass
# 重置进度条
progress_var.set(0)
# 启用开始按钮
start_button.config(state=tk.NORMAL)
stop_processing = False
def start_processing():
"""开始处理视频"""
global stop_processing
stop_processing = False
start_button.config(state=tk.DISABLED)
# 在新线程中处理视频
thread = threading.Thread(target=process_video, daemon=True)
thread.start()
def stop_processing_func():
"""停止处理"""
global stop_processing
stop_processing = True
def browse_input():
"""浏览输入文件"""
filename = filedialog.askopenfilename(
filetypes=[("Video Files", "*.mp4 *.mov *.avi *.mkv"), ("All Files", "*.*")]
)
if filename:
input_entry.delete(0, tk.END)
input_entry.insert(0, filename)
def browse_output():
"""浏览输出文件"""
filename = filedialog.asksaveasfilename(
defaultextension=".mp4",
filetypes=[("MP4 Files", "*.mp4"), ("All Files", "*.*")]
)
if filename:
output_entry.delete(0, tk.END)
output_entry.insert(0, filename)
def toggle_pip_widgets():
"""切换画中画相关控件的状态"""
state = tk.NORMAL if pip_var.get() else tk.DISABLED
pip_num_combo.config(state=state)
pip_opacity_scale.config(state=state)
def show_gan_info():
"""显示GAN功能信息"""
if gan_var.get():
messagebox.showinfo('功能说明', '请注意:GAN功能是高级预留功能。\n在当前版本中,它会被一个高级扰动算法模拟,但并非真正的GAN。\n效果依然强大。')
# 检测可用GPU
detected_gpu = detect_gpu()
gpu_options = ["自动检测", "cpu", "nvidia", "amd", "intel"]
default_gpu = detected_gpu if detected_gpu != "cpu" else "自动检测"
# 创建主窗口
root = tk.Tk()
root.title("视频号专版防检测处理工具 v3.0")
root.geometry("800x600")
# 创建变量
video_var = tk.BooleanVar(value=True)
audio_var = tk.BooleanVar(value=True)
resize_var = tk.BooleanVar(value=True)
metadata_var = tk.BooleanVar(value=True)
pip_var = tk.BooleanVar(value=False)
gan_var = tk.BooleanVar(value=False)
progress_var = tk.DoubleVar(value=0)
# 创建界面组件
main_frame = ttk.Frame(root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# 输入文件选择
ttk.Label(main_frame, text="输入视频文件:").grid(row=0, column=0, sticky=tk.W, pady=5)
input_entry = ttk.Entry(main_frame, width=50)
input_entry.grid(row=0, column=1, padx=5, pady=5)
ttk.Button(main_frame, text="浏览", command=browse_input).grid(row=0, column=2, padx=5, pady=5)
# 输出文件选择
ttk.Label(main_frame, text="输出视频文件:").grid(row=1, column=0, sticky=tk.W, pady=5)
output_entry = ttk.Entry(main_frame, width=50)
output_entry.grid(row=1, column=1, padx=5, pady=5)
ttk.Button(main_frame, text="浏览", command=browse_output).grid(row=1, column=2, padx=5, pady=5)
# 分隔线
ttk.Separator(main_frame, orient=tk.HORIZONTAL).grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
# 处理强度
ttk.Label(main_frame, text="处理强度:").grid(row=3, column=0, sticky=tk.W, pady=5)
strength_scale = tk.Scale(main_frame, from_=1, to=100, orient=tk.HORIZONTAL, length=400)
strength_scale.set(50)
strength_scale.grid(row=3, column=1, columnspan=2, sticky=(tk.W, tk.E), padx=5, pady=5)
# 分隔线
ttk.Separator(main_frame, orient=tk.HORIZONTAL).grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
# 处理选项
ttk.Checkbutton(main_frame, text="时空域微扰动 (抗视频指纹 - 核心推荐)", variable=video_var).grid(row=5, column=0, columnspan=3, sticky=tk.W, pady=2)
ttk.Checkbutton(main_frame, text="音频指纹污染 (抗音频指纹 - 核心推荐)", variable=audio_var).grid(row=6, column=0, columnspan=3, sticky=tk.W, pady=2)
ttk.Checkbutton(main_frame, text="标准化分辨率 (720x1560) + 黑边扰动", variable=resize_var).grid(row=7, column=0, columnspan=3, sticky=tk.W, pady=2)
ttk.Checkbutton(main_frame, text="元数据彻底清理与伪造", variable=metadata_var).grid(row=8, column=0, columnspan=3, sticky=tk.W, pady=2)
# 画中画选项
pip_frame = ttk.Frame(main_frame)
pip_frame.grid(row=9, column=0, columnspan=3, sticky=tk.W, pady=2)
ttk.Checkbutton(pip_frame, text="画中画干扰 (从P文件夹随机选择视频)", variable=pip_var, command=toggle_pip_widgets).grid(row=0, column=0, sticky=tk.W)
pip_options_frame = ttk.Frame(main_frame)
pip_options_frame.grid(row=10, column=0, columnspan=3, sticky=tk.W, pady=2)
ttk.Label(pip_options_frame, text="画中画数量:").grid(row=0, column=0, sticky=tk.W, padx=5)
pip_num_combo = ttk.Combobox(pip_options_frame, values=[1, 2, 3, 4, 5], state="readonly", width=5)
pip_num_combo.set(3)
pip_num_combo.grid(row=0, column=1, padx=5)
ttk.Label(pip_options_frame, text="透明度 (1-100):").grid(row=0, column=2, sticky=tk.W, padx=5)
pip_opacity_scale = tk.Scale(pip_options_frame, from_=1, to=100, orient=tk.HORIZONTAL, length=150)
pip_opacity_scale.set(2)
pip_opacity_scale.grid(row=0, column=3, padx=5)
# 禁用画中画选项
pip_num_combo.config(state=tk.DISABLED)
pip_opacity_scale.config(state=tk.DISABLED)
# GAN选项
ttk.Checkbutton(main_frame, text="动态GAN对抗性扰动 (预留功能)", variable=gan_var, command=show_gan_info).grid(row=11, column=0, columnspan=3, sticky=tk.W, pady=2)
# GPU加速选项
gpu_frame = ttk.Frame(main_frame)
gpu_frame.grid(row=12, column=0, columnspan=3, sticky=tk.W, pady=5)
ttk.Label(gpu_frame, text="GPU加速:").grid(row=0, column=0, sticky=tk.W)
gpu_combo = ttk.Combobox(gpu_frame, values=gpu_options, state="readonly", width=10)
gpu_combo.set(default_gpu)
gpu_combo.grid(row=0, column=1, padx=5)
# 分隔线
ttk.Separator(main_frame, orient=tk.HORIZONTAL).grid(row=13, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
# 进度条
ttk.Label(main_frame, text="进度:").grid(row=14, column=0, sticky=tk.W, pady=5)
progress_bar = ttk.Progressbar(main_frame, variable=progress_var, maximum=100, length=400)
progress_bar.grid(row=14, column=1, columnspan=2, sticky=(tk.W, tk.E), padx=5, pady=5)
# 按钮
button_frame = ttk.Frame(main_frame)
button_frame.grid(row=15, column=0, columnspan=3, pady=10)
start_button = ttk.Button(button_frame, text="开始处理", command=start_processing)
start_button.pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="停止", command=stop_processing_func).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="退出", command=root.quit).pack(side=tk.LEFT, padx=5)
# 配置网格权重
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
# 运行主循环
root.mainloop()
以上代码在打包时出现问题,请使用Tkinter替代PySimpleGUI代码,通过指定路径解决 MoviePy 导入问题,C:\Users\Administrator\AppData\Local\Programs\Python\Python312\Lib\site-packages\moviepy_init_.py
这是moviepy路径,给我一个完整的打包操作方案,详细列出所需的所有操作
最新发布