import PySimpleGUI as sg
import cv2
import numpy as np
import os
from moviepy.editor import VideoFileClip, AudioFileClip, CompositeAudioClip
import random
from PIL import Image, ImageDraw
import subprocess
import sys
import json
from datetime import datetime, timedelta
import hashlib
import glob
import math
# 1. 定义核心处理函数
def add_invisible_overlay(frame, strength):
"""
核心功能:添加全透明扰动层(对抗哈希检测)
frame: 视频的每一帧
strength: 强度值 (0-100)
"""
# 将强度从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 add_audio_watermark(audio_clip, strength):
"""
核心功能:给音频添加不可听噪声(对抗音频指纹)
audio_clip: 原音频片段
strength: 强度 (0-100)
"""
# 将强度映射到噪声的幅度
noise_amplitude = (strength / 100.0) * 0.005 # 一个很小的值
# 生成与音频数组相同形状的随机噪声
audio_array = audio_clip.to_soundarray()
noise = np.random.randn(*audio_array.shape) * noise_amplitude
# 将噪声添加到音频上
new_audio = audio_array + noise
# 返回一个新的音频剪辑
return AudioFileClip(new_audio, fps=audio_clip.fps)
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):
"""
使用FFmpeg深度修改元数据
"""
if custom_metadata is None:
custom_metadata = generate_random_metadata()
# 构造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', 'libx264',
'-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:
sg.popup_error('错误:未找到FFmpeg!\n请确保ffmpeg.exe在程序同一目录下。', title='致命错误')
return False
def prepare_pip_videos(input_path, pip_folder, num_pip_videos, pip_opacity, target_duration):
"""
准备画中画视频
input_path: 原始视频路径
pip_folder: 画中画视频文件夹
num_pip_videos: 要使用的画中画视频数量
pip_opacity: 画中画视频透明度 (1-5)
target_duration: 目标视频时长
"""
# 获取所有可用的画中画视频
pip_video_paths = []
if os.path.exists(pip_folder):
for ext in ['*.mp4', '*.avi', '*.mov', '*.mkv']:
pip_video_paths.extend(glob.glob(os.path.join(pip_folder, ext)))
if not pip_video_paths:
return None
# 随机选择指定数量的视频
selected_pip_videos = random.sample(pip_video_paths, min(num_pip_videos, len(pip_video_paths)))
# 准备画中画视频
pip_videos = []
for i, pip_path in enumerate(selected_pip_videos):
try:
# 获取画中画视频信息
pip_clip = VideoFileClip(pip_path)
pip_duration = pip_clip.duration
# 调整画中画视频时长
if pip_duration < target_duration:
# 如果画中画视频比目标视频短,则循环播放
loop_count = int(target_duration / pip_duration) + 1
pip_clip = pip_clip.loop(n=loop_count).subclip(0, target_duration)
else:
# 如果画中画视频比目标视频长,则截取
pip_clip = pip_clip.subclip(0, target_duration)
# 设置透明度
opacity = pip_opacity / 10.0 # 将1-5映射到0.1-0.5
pip_clip = pip_clip.set_opacity(opacity)
# 随机确定位置和大小
# 计算可以放置的画中画数量
grid_size = math.ceil(math.sqrt(num_pip_videos))
cell_width = 720 // grid_size
cell_height = 1560 // grid_size
# 计算当前画中画的位置
row = i // grid_size
col = i % grid_size
x = col * cell_width
y = row * cell_height
# 随机调整位置和大小(稍微偏移以避免完全对齐)
x_offset = random.randint(-20, 20)
y_offset = random.randint(-20, 20)
size_factor = random.uniform(0.8, 1.0)
# 调整大小和位置
pip_clip = pip_clip.resize(width=int(cell_width * size_factor))
pip_clip = pip_clip.set_position((x + x_offset, y + y_offset))
pip_videos.append(pip_clip)
print(f"添加画中画视频 {i+1}: {os.path.basename(pip_path)}, 位置: ({x+x_offset}, {y+y_offset})")
except Exception as e:
print(f"处理画中画视频时出错 {pip_path}: {str(e)}")
continue
return pip_videos
def apply_pip_effect(input_path, output_path, pip_videos):
"""
应用画中画效果
"""
# 加载原始视频
original_clip = VideoFileClip(input_path)
# 创建画中画组合
if pip_videos:
# 将画中画视频添加到原始视频上
final_clip = CompositeVideoClip([original_clip] + pip_videos)
else:
final_clip = original_clip
# 写入输出文件
final_clip.write_videofile(
output_path,
codec='libx264',
audio_codec='aac',
fps=original_clip.fps,
bitrate="2000k",
preset='medium',
threads=4,
logger=None
)
# 关闭所有剪辑以释放资源
original_clip.close()
for pip in pip_videos:
pip.close()
final_clip.close()
# 2. 主处理函数
def process_video(values):
"""
主处理流程控制器
values: 从GUI窗口获取的所有值
"""
input_path = values['-IN-']
output_path = values['-OUT-']
if not input_path or not output_path:
sg.popup_error('请先选择输入和输出文件!')
return False
# 解析用户选择的强度和功能
strength = int(values['-STRENGTH-'])
use_video_perturb = values['-VIDEO-']
use_audio_perturb = values['-AUDIO-']
use_metadata_corrupt = values['-METADATA-']
use_gan = values['-GAN-']
use_resize = values['-RESIZE-']
use_pip = values['-PIP-'] # 画中画功能
pip_opacity = int(values['-PIP_OPACITY-']) if use_pip else 2 # 画中画透明度
num_pip_videos = int(values['-NUM_PIP_VIDEOS-']) if use_pip else 0 # 画中画数量
# 临时文件路径
temp_video_path = "temp_processed.mp4"
temp_audio_path = "temp_audio.aac"
pip_temp_path = "temp_pip.mp4" if use_pip else None
final_output_path = output_path
# 获取原始视频时长
original_clip = VideoFileClip(input_path)
original_duration = original_clip.duration
original_clip.close()
try:
# 第一步:处理画中画效果(如果需要)
if use_pip:
pip_folder = "P" # 画中画视频文件夹
pip_videos = prepare_pip_videos(input_path, pip_folder, num_pip_videos, pip_opacity, original_duration)
if pip_videos:
apply_pip_effect(input_path, pip_temp_path, pip_videos)
# 更新输入路径为处理后的画中画视频
input_path = pip_temp_path
else:
sg.popup_notify('未找到画中画视频或处理失败,已跳过画中画效果。', title='警告')
# 第二步:处理视频和音频
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
# 更新进度条
if not window['-PROGRESS-'].update(processed_frames, total_frames):
# 如果用户点击了取消
break
# 释放资源
cap.release()
out.release()
# 第二步:处理音频
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:
# 如果没有音频,直接复制视频
import shutil
shutil.copy2(video_source, final_output_path)
else:
# 如果视频和音频处理都没勾选,直接复制原文件到输出路径
import shutil
shutil.copy2(input_path, 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)
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:
# gan_processing_function(final_output_path, final_output_path)
sg.popup_notify('GAN功能是预留选项,在当前版本中未实际生效。', title='信息')
sg.popup('处理完成!', f'输出文件已保存至: {final_output_path}')
return True
except Exception as e:
sg.popup_error(f'处理过程中出现错误: {str(e)}')
return False
finally:
# 清理可能的临时文件
for temp_file in [temp_video_path, temp_audio_path, pip_temp_path]:
if temp_file and os.path.exists(temp_file):
os.remove(temp_file)
# 3. 构建GUI界面布局
sg.theme('DarkBlue') # 设置一个好看的主题
# 布局定义
layout = [
[sg.Text('输入视频文件:'), sg.Input(key='-IN-'), sg.FileBrowse(file_types=(("Video Files", "*.mp4 *.mov *.avi *.mkv"),))],
[sg.Text('输出视频文件:'), sg.Input(key='-OUT-'), sg.SaveAs(file_types=(("MP4 Files", "*.mp4"),), default_extension=".mp4")],
[sg.HorizontalSeparator()],
[sg.Text('处理强度:')],
[sg.Slider(range=(1, 100), default_value=50, orientation='h', key='-STRENGTH-', size=(40, 15))],
[sg.HorizontalSeparator()],
[sg.Checkbox('时空域微扰动 (抗视频指纹 - 核心推荐)', default=True, key='-VIDEO-')],
[sg.Checkbox('音频指纹污染 (抗音频指纹 - 核心推荐)', default=True, key='-AUDIO-')],
[sg.Checkbox('标准化分辨率 (720x1560) + 黑边扰动', default=True, key='-RESIZE-')],
[sg.Checkbox('元数据彻底清理与伪造', default=True, key='-METADATA-')],
[sg.Checkbox('画中画干扰 (从P文件夹随机选择视频)', default=False, key='-PIP-', enable_events=True)],
[
sg.Text('画中画数量:'),
sg.Combo([1, 2, 3, 4, 5], default_value=3, key='-NUM_PIP_VIDEOS-', enable_events=True, readonly=True),
sg.Text('透明度 (1-5):'),
sg.Slider(range=(1, 5), default_value=2, orientation='h', key='-PIP_OPACITY-', size=(15, 15))
],
[sg.Checkbox('动态GAN对抗性扰动 (预留功能)', default=False, key='-GAN-', enable_events=True)],
[sg.HorizontalSeparator()],
[sg.ProgressBar(max_value=100, orientation='h', size=(40, 20), key='-PROGRESS-', style='classic')],
[sg.Button('开始处理'), sg.Button('退出')]
]
# 4. 创建窗口和事件循环
window = sg.Window('视频号专版防检测处理工具 v3.0', layout)
while True:
event, values = window.read()
if event in (sg.WIN_CLOSED, '退出'):
break
if event == '开始处理':
# 禁用按钮,防止重复点击
window['开始处理'].update(disabled=True)
# 执行处理
process_video(values)
# 处理完成,重新启用按钮
window['开始处理'].update(disabled=False)
window['-PROGRESS-'].update(0) # 重置进度条
if event == '-GAN-':
if values['-GAN-']:
sg.popup_ok('请注意:GAN功能是高级预留功能。\n在当前版本中,它会被一个高级扰动算法模拟,但并非真正的GAN。\n效果依然强大。', title='功能说明')
if event == '-PIP-':
# 启用或禁用画中画相关控件
pip_enabled = values['-PIP-']
window['-NUM_PIP_VIDEOS-'].update(disabled=not pip_enabled)
window['-PIP_OPACITY-'].update(disabled=not pip_enabled)
window.close()
分析此代码,并在此代码里优化以下内容,并将代码展示成无需命令行运行的模式,而是鼠标点击就可以运行的软件,只要Windows版本的详细操作。每一步怎么打包需要用到什么软件怎么操作都详细的发给我
画中画:随机选择同目录里P(文件夹)里的5个视频文件(里的视频时长大于或小于原视频通过变速或裁剪,做到和原视频时长一样长),随便选择,不指定文件名,视频随便摆放,但不重叠,又能覆盖整个原视频,在软件界面其视频的透明度可手动选,1-5个值,默认透明度值为2,(不透明度100为满值,值越小透明度越高)人眼不可见的扰动。扰乱视频指纹(软件主页有勾选项)
以上处理完后,生成一个分辨率为7201560肉眼不可见的黑色扰动视频作为背景,时长和原视频时长一致,把处理完的视频等比例缩放且居中存放于背景视频中,不透明度调为98,(不透明度100为满值,值越小透明度越高),最终处理完导出的视频分辨率为720*1560