sys_get_temp_dir();

本文介绍了解决 PHP 中使用 cURL 出现的错误:无法创建临时文件。通过打印系统临时文件夹位置并调整文件夹的安全权限来解决问题。

php出错:curl_setopt_array(): Unable to create temporary file


打印出系统临时文件夹位置(sys_get_temp_dir();),然后在文件夹属性,安全赋予权限

########################## TCL Event Handlers ############################ fanuc_system_A.tcl - lathe## This is a 2-Axis Horizontal Lathe Machine.## Created by Postino @ Fri Mar 9 12:19:40 2018 -0800# with Post Builder version 12.0.2.##########################################################################=============================================================proc PB_CMD___log_revisions { } {#=============================================================# Dummy command to log changes in this post --## 15-Jul-2014 gsl - Initial version# 28-Oct-2015 shuai - Add PB_CMD_uplevel_MOM_generic_cycle to check non-cutting motions.# Output contour data or tracking data based on if UDE (Cutter Compensation)# has been set. Make the return motion output after lathe rough cycle G71/G71.# 05-Nov-2015 shuai - Output finish operation G70 using previous rough contour data# if the subroutine name is same between them.# 25-Dec-2015 shuai - Add command PB_CMD_check_additional_profiling, PB_CMD_check_first_buffer_for_return_motion,# PB_CMD_finish_turn_cycle_contour_start, PB_CMD_finish_turn_cycle_contour_end,# PB_CMD_output_turning_cycle_command, PB_CMD_override_rough_contour_data_with_finish,# PB_CMD_set_contour_motion, PB_CMD_calculate_contour_line_number,# PB_CMD_set_turning_cycle_type and PB_CMD_calculate_parameters_for_turning_cycle_block.# 17-May-2016 Jintao - Add mom_kin_lathe_cycle_workplane_transform to fix PR6763943# 25-Nov-2016 shuai - Improve the functionality of Lathe Rough Cycle (G70/ G71/G72). The following issues have been enhanced.# - 1. In MOM_generic_cycle event:# - 1.1 Remove the checking condition for G70\G71\G72 that whether a from point has been set or not.# - 1.2 If a finish operation uses the previous corresponding contour of a rough turning operation, it will also need to skip to the next generic event.# - 2. Lathe rough cycle has been initialized in MOM_generic_cycle, so remove the command "PB_CMD_init_rough_turn_cycle_output" from proc "PB_approach_move".# - 3. In PB_CMD_init_rough_turn_cycle_output, additional profiling should be checked to fulfill some conditions when initializing contour data.# - 4. Add a new proc "PB_CMD_check_settings_for_additional_profiling" to check the settings for additional profile.# - 5. Add a new proc "PB_CMD_check_variables_for_turning_rough_cycle" to check some variables for turning rough cycle.# - 6. Adjust the line number according to turning operation type in PB_CMD_calculate_contour_line_number so as to get the correct values for parameters P and Q.# - 7. Exclude the own name of rough turning operation from the operation name list in command PB_CMD_turn_cycle_contour_end# when contour data of rough operation is overridden by finish operation.# 13-Dec-2016 shuai - Bug fix PR7290132. Output the correct line number for P and Q no matter the output status of sequence number is ON or OFF.# Delete the original command "PB_CMD_output_spindle". Rename the command "PB_CMD_output_spindle_speed" to "PB_CMD_output_spindle".# Update the command "PB_CMD_calculate_contour_line_number".# 21-Dec-2016 shuai - Bug fix PR7290203.# Refactor the output logic for lathe rough cycle function# to make the events content between the second MOM_generic_cycle (EXCLUDE) and MOM_contour_end (INCLUDE)# output at the end position of MOM_contour_end event.# 1. Update the handlers.# "MOM_linear_move", "MOM_circular_move", "MOM_contour_start" and "MOM_generic_cycle".# 2. Update the commands.# "PB_CMD_calculate_parameters_for_turning_cycle_block", "PB_CMD_finish_turn_cycle_contour_start", "PB_CMD_finish_turn_cycle_contour_end",# "PB_CMD_init_rough_turn_cycle_output", "PB_CMD_output_turning_cycle_command", "PB_CMD_override_rough_contour_data_with_finish",# "PB_CMD_turn_cycle_contour_start", "PB_CMD_turn_cycle_contour_end" and "PB_CMD_uplevel_MOM_generic_cycle".# 3. Delete the commands.# "PB_CMD__check_block_linear_for_rough_turn_cycle", "PB_CMD__check_block_circular_move_for_rough_turn_cycle", "PB_CMD__check_block_skip_for_rough_turn_cycle",# "PB_CMD__check_block_rapid_move_for_return_motion", "PB_CMD_check_first_buffer_for_return_motion" and "PB_CMD_calculate_contour_line_number".# 5. Delete the blocks.# "circular_move_rough_turn_cycle" and "linear_move_rough_turn_cycle".# 6. Enhance the MOM_abort command with "catch" to make it not output error message.# 7. Enhance the temp file name with clock time to make it unique in current output folder.# 25-April-2017 lili - Update implementation of lathe rough cycle function. All related commands please see PB_CMD_init_turn_cycle_setting, PB_CMD_uplevel_MOM_generic_cycle,# PB_CMD_turn_cycle_contour_start and PB_CMD_turn_cycle_contour_end.# Add PB_CMD_init_turn_cycle_setting at "start of program".} set cam_post_dir [MOM_ask_env_var UGII_CAM_POST_DIR] set mom_sys_this_post_dir "[file dirname [info script]]" set mom_sys_this_post_name "[file rootname [file tail [info script]]]" if { ![info exists mom_sys_post_initialized] } { if { ![info exists mom_sys_ugpost_base_initialized] } { source ${cam_post_dir}ugpost_base.tcl set mom_sys_ugpost_base_initialized 1 } set mom_sys_debug_mode OFF if { ![info exists env(PB_SUPPRESS_UGPOST_DEBUG)] } { set env(PB_SUPPRESS_UGPOST_DEBUG) 0 } if { $env(PB_SUPPRESS_UGPOST_DEBUG) } { set mom_sys_debug_mode OFF } if { ![string compare $mom_sys_debug_mode "OFF"] } { proc MOM_before_each_add_var {} {} proc MOM_before_each_event {} {} proc MOM_before_load_address {} {} proc MOM_end_debug {} {} } else { set cam_debug_dir [MOM_ask_env_var UGII_CAM_DEBUG_DIR] source ${cam_debug_dir}mom_review.tcl } #### Listing File variables set mom_sys_list_output "OFF" set mom_sys_header_output "OFF" set mom_sys_list_file_rows "40" set mom_sys_list_file_columns "30" set mom_sys_warning_output "OFF" set mom_sys_warning_output_option "FILE" set mom_sys_group_output "OFF" set mom_sys_list_file_suffix "lpt" set mom_sys_output_file_suffix "ptp" set mom_sys_commentary_output "ON" set mom_sys_commentary_list "x z feed speed" set mom_sys_pb_link_var_mode "OFF" if { [string match "OFF" $mom_sys_warning_output] } { catch { rename MOM__util_print ugpost_MOM__util_print } proc MOM__util_print { args } {} } MOM_set_debug_mode $mom_sys_debug_mode if { [string match "OFF" $mom_sys_warning_output] } { catch { rename MOM__util_print "" } catch { rename ugpost_MOM__util_print MOM__util_print } } #============================================================= proc MOM_before_output { } { #============================================================= # This command is executed just before every NC block is # to be output to a file. # # - Never overload this command! # - Any customization should be done in PB_CMD_before_output! # if { [llength [info commands PB_CMD_kin_before_output]] &&\ [llength [info commands PB_CMD_before_output]] } { PB_CMD_kin_before_output } # Write output buffer to the listing file with warnings global mom_sys_list_output if { [string match "ON" $mom_sys_list_output] } { LIST_FILE } else { global tape_bytes mom_o_buffer if { ![info exists tape_bytes] } { set tape_bytes [string length $mom_o_buffer] } else { incr tape_bytes [string length $mom_o_buffer] } } } if { [string match "OFF" [MOM_ask_env_var UGII_CAM_POST_LINK_VAR_MODE]] } { set mom_sys_link_var_mode "OFF" } else { set mom_sys_link_var_mode "$mom_sys_pb_link_var_mode" } set mom_sys_control_out "(" set mom_sys_control_in ")" set mom_sys_post_initialized 1 } set mom_sys_use_default_unit_fragment "ON" set mom_sys_alt_unit_post_name "fanuc_system_A__MM.pui"########## SYSTEM VARIABLE DECLARATIONS ############## set mom_sys_rapid_code "0" set mom_sys_linear_code "1" set mom_sys_circle_code(CLW) "2" set mom_sys_circle_code(CCLW) "3" set mom_sys_lathe_thread_advance_type(1) "33" set mom_sys_lathe_thread_advance_type(2) "34" set mom_sys_lathe_thread_advance_type(3) "35" set mom_sys_delay_code(SECONDS) "4" set mom_sys_delay_code(REVOLUTIONS) "4" set mom_sys_cutcom_code(OFF) "40" set mom_sys_cutcom_code(LEFT) "41" set mom_sys_cutcom_code(RIGHT) "42" set mom_sys_adjust_code "43" set mom_sys_adjust_code_minus "44" set mom_sys_adjust_cancel_code "49" set mom_sys_unit_code(IN) "20" set mom_sys_unit_code(MM) "21" set mom_sys_cycle_drill_break_chip_code "73" set mom_sys_cycle_off "80" set mom_sys_cycle_drill_code "81" set mom_sys_cycle_drill_deep_code "83" set mom_sys_cycle_drill_dwell_code "82" set mom_sys_cycle_tap_code "84" set mom_sys_cycle_bore_code "85" set mom_sys_output_code(ABSOLUTE) "90" set mom_sys_output_code(INCREMENTAL) "91" set mom_sys_reset_code "50" set mom_sys_feed_rate_mode_code(IPM) "98" set mom_sys_feed_rate_mode_code(IPR) "99" set mom_sys_feed_rate_mode_code(FRN) "93" set mom_sys_spindle_mode_code(SFM) "96" set mom_sys_spindle_mode_code(RPM) "97" set mom_sys_return_code "28" set mom_sys_cycle_ret_code(AUTO) "98" set mom_sys_cycle_ret_code(MANUAL) "99" set mom_sys_program_stop_code "0" set mom_sys_optional_stop_code "1" set mom_sys_end_of_program_code "2" set mom_sys_spindle_direction_code(CLW) "3" set mom_sys_spindle_direction_code(CCLW) "4" set mom_sys_spindle_direction_code(OFF) "5" set mom_sys_tool_change_code "6" set mom_sys_coolant_code(MIST) "7" set mom_sys_coolant_code(ON) "8" set mom_sys_coolant_code(FLOOD) "8" set mom_sys_coolant_code(OFF) "9" set mom_sys_head_code(INDEPENDENT) "21" set mom_sys_head_code(DEPENDENT) "22" set mom_sys_rewind_code "30" set mom_sys_sim_cycle_drill "0" set mom_sys_sim_cycle_drill_dwell "0" set mom_sys_sim_cycle_drill_deep "0" set mom_sys_sim_cycle_drill_break_chip "0" set mom_sys_sim_cycle_tap "0" set mom_sys_sim_cycle_bore "0" set mom_sys_cir_vector "Vector - Arc Start to Center" set mom_sys_spindle_max_rpm_code "50" set mom_sys_spindle_cancel_sfm_code "93" set mom_sys_spindle_ranges "0" set mom_sys_rewind_stop_code "\#" set mom_sys_home_pos(0) "0" set mom_sys_home_pos(1) "0" set mom_sys_home_pos(2) "0" set mom_sys_zero "0" set mom_sys_opskip_block_leader "/" set mom_sys_seqnum_start "10" set mom_sys_seqnum_incr "10" set mom_sys_seqnum_freq "1" set mom_sys_seqnum_max "9999" set mom_sys_lathe_x_double "1" set mom_sys_lathe_i_double "1" set mom_sys_lathe_x_factor "1" set mom_sys_lathe_z_factor "1" set mom_sys_lathe_i_factor "1" set mom_sys_lathe_k_factor "1" set mom_sys_leader(N) "N" set mom_sys_leader(X) "X" set mom_sys_leader(Y) "Y" set mom_sys_leader(Z) "Z" set mom_sys_turret_index(INDEPENDENT) "1" set mom_sys_turret_index(DEPENDENT) "2" set mom_sys_delay_param(SECONDS,format) "Dwell_SECONDS" set mom_sys_delay_param(REVOLUTIONS,format) "Dwell_REVOLUTIONS" set mom_sys_contour_feed_mode(LINEAR) "IPM" set mom_sys_rapid_feed_mode(LINEAR) "IPM" set mom_sys_cycle_feed_mode "IPM" set mom_sys_feed_param(IPM,format) "Feed_IPM" set mom_sys_feed_param(IPR,format) "Feed_IPR" set mom_sys_feed_param(FRN,format) "Feed_INV" set mom_sys_vnc_rapid_dogleg "1" set mom_sys_prev_mach_head "" set mom_sys_curr_mach_head "" set mom_sys_output_cycle95 "1" set mom_sys_lathe_y_factor "1" set mom_sys_head_code(INDEPENDENT) "21" set mom_sys_head_code(DEPENDENT) "22" set mom_sys_advanced_turbo_output "0" set mom_sys_linearization_method "angle" set mom_sys_tool_number_max "32" set mom_sys_tool_number_min "1" set mom_sys_post_description "This is a 2-Axis Horizontal Lathe Machine." set mom_sys_word_separator " " set mom_sys_end_of_block "" set mom_sys_ugpadvkins_used "0" set mom_sys_post_builder_version "12.0.2"####### KINEMATIC VARIABLE DECLARATIONS ############## set mom_kin_4th_axis_center_offset(0) "0.0" set mom_kin_4th_axis_center_offset(1) "0.0" set mom_kin_4th_axis_center_offset(2) "0.0" set mom_kin_4th_axis_max_limit "0.0" set mom_kin_4th_axis_min_incr "0.0" set mom_kin_4th_axis_min_limit "0.0" set mom_kin_4th_axis_point(0) "0.0" set mom_kin_4th_axis_point(1) "0.0" set mom_kin_4th_axis_point(2) "0.0" set mom_kin_4th_axis_zero "0.0" set mom_kin_5th_axis_center_offset(0) "0.0" set mom_kin_5th_axis_center_offset(1) "0.0" set mom_kin_5th_axis_center_offset(2) "0.0" set mom_kin_5th_axis_max_limit "0.0" set mom_kin_5th_axis_min_incr "0.0" set mom_kin_5th_axis_min_limit "0.0" set mom_kin_5th_axis_point(0) "0.0" set mom_kin_5th_axis_point(1) "0.0" set mom_kin_5th_axis_point(2) "0.0" set mom_kin_5th_axis_zero "0.0" set mom_kin_arc_output_mode "FULL_CIRCLE" set mom_kin_arc_valid_plane "XY" set mom_kin_clamp_time "2.0" set mom_kin_dependent_head "NONE" set mom_kin_flush_time "2.0" set mom_kin_ind_to_dependent_head_x "0" set mom_kin_ind_to_dependent_head_z "0" set mom_kin_independent_head "NONE" set mom_kin_linearization_flag "1" set mom_kin_linearization_tol "0.001" set mom_kin_machine_resolution ".0001" set mom_kin_machine_type "lathe" set mom_kin_machine_zero_offset(0) "0.0" set mom_kin_machine_zero_offset(1) "0.0" set mom_kin_machine_zero_offset(2) "0.0" set mom_kin_max_arc_radius "9999.9999" set mom_kin_max_dpm "0.0" set mom_kin_max_fpm "400" set mom_kin_max_fpr "1000" set mom_kin_max_frn "99999.999" set mom_kin_min_arc_length "0.01" set mom_kin_min_arc_radius "0.0001" set mom_kin_min_dpm "0.0" set mom_kin_min_fpm "0.1" set mom_kin_min_fpr "0.001" set mom_kin_min_frn "0.001" set mom_kin_output_unit "IN" set mom_kin_pivot_gauge_offset "0.0" set mom_kin_post_data_unit "IN" set mom_kin_rapid_feed_rate "600" set mom_kin_tool_change_time "0.0" set mom_kin_x_axis_limit "40" set mom_kin_y_axis_limit "40" set mom_kin_z_axis_limit "35"if [llength [info commands MOM_SYS_do_template] ] { if [llength [info commands MOM_do_template] ] { rename MOM_do_template "" } rename MOM_SYS_do_template MOM_do_template}#=============================================================proc MOM_start_of_program { } {#============================================================= global mom_logname mom_date is_from global mom_coolant_status mom_cutcom_status global mom_clamp_status mom_cycle_status global mom_spindle_status mom_cutcom_plane pb_start_of_program_flag global mom_cutcom_adjust_register mom_tool_adjust_register global mom_tool_length_adjust_register mom_length_comp_register global mom_flush_register mom_wire_cutcom_adjust_register global mom_wire_cutcom_status set pb_start_of_program_flag 0 set mom_coolant_status UNDEFINED set mom_cutcom_status UNDEFINED set mom_clamp_status UNDEFINED set mom_cycle_status UNDEFINED set mom_spindle_status UNDEFINED set mom_cutcom_plane UNDEFINED set mom_wire_cutcom_status UNDEFINED catch {unset mom_cutcom_adjust_register} catch {unset mom_tool_adjust_register} catch {unset mom_tool_length_adjust_register} catch {unset mom_length_comp_register} catch {unset mom_flush_register} catch {unset mom_wire_cutcom_adjust_register} set is_from "" catch { OPEN_files } ;# Open warning and listing files LIST_FILE_HEADER ;# List header in commentary listing global mom_sys_post_initialized if { $mom_sys_post_initialized > 1 } { return } set ::mom_sys_start_program_clock_seconds [clock seconds] # Load parameters for alternate output units PB_load_alternate_unit_settings rename PB_load_alternate_unit_settings ""#************uplevel #0 {#=============================================================proc MOM_sync { } {#============================================================= if [llength [info commands PB_CMD_kin_handle_sync_event] ] { PB_CMD_kin_handle_sync_event }}#=============================================================proc MOM_set_csys { } {#============================================================= if [llength [info commands PB_CMD_kin_set_csys] ] { PB_CMD_kin_set_csys }}#=============================================================proc MOM_msys { } {#=============================================================}#=============================================================proc MOM_end_of_program { } {#===================
最新发布
09-07
import os import sys import tempfile import shutil import random import cv2 import numpy as np from PIL import Image, ImageDraw import subprocess from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog, QMessageBox, QGroupBox, QProgressBar, QFrame, QComboBox) from PyQt5.QtCore import Qt, QThread, pyqtSignal, QThreadPool, QRunnable 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 FrameProcessingTask(QRunnable): """处理单个帧的任务""" def __init__(self, frame, frame_number, output_dir): super().__init__() self.frame = frame self.frame_number = frame_number self.output_dir = output_dir def run(self): # 调整分辨率为1080x1920 frame_resized = cv2.resize(self.frame, (1080, 1920)) frame_path = os.path.join(self.output_dir, f"{self.frame_number:04d}.jpg") cv2.imwrite(frame_path, frame_resized, [cv2.IMWRITE_JPEG_QUALITY, 95]) 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, thread_count=4): 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 self.thread_count = thread_count self.thread_pool = QThreadPool() self.thread_pool.setMaxThreadCount(thread_count) 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("合成视频...") 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("合成视频...") 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' ] # 避免弹出黑框 startupinfo = None if sys.platform == "win32": startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = 0 # 0 = SW_HIDE subprocess.run(command, check=True, capture_output=True, startupinfo=startupinfo) 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 # 确保输出目录存在 os.makedirs(output_dir, exist_ok=True) while success and not self.cancel_requested: success, frame = cap.read() if not success: break frame_count += 1 # 创建任务 task = FrameProcessingTask(frame.copy(), frame_count, output_dir) self.thread_pool.start(task) # 每处理50帧更新一次进度 if frame_count % 50 == 0: progress = int(frame_count * 100 / self.total_frames) self.progress_updated.emit(progress) # 等待所有任务完成 self.thread_pool.waitForDone() cap.release() if frame_count == 0 and not self.cancel_requested: raise Exception("视频文件中没有帧") else: # 最后更新一次进度 self.progress_updated.emit(int(frame_count * 100 / self.total_frames)) 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' ] # 避免弹出黑框 startupinfo = None if sys.platform == "win32": startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = 0 # 0 = SW_HIDE subprocess.run(command, check=True, capture_output=True, startupinfo=startupinfo) 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}', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-crf', '23', '-preset', 'fast', output_path, '-y' ] # 避免弹出黑框 startupinfo = None if sys.platform == "win32": startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = 0 # 0 = SW_HIDE subprocess.run(command, check=True, capture_output=True, startupinfo=startupinfo) except subprocess.CalledProcessError as e: raise Exception(f"背景视频创建失败: {str(e)}") 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' ] # 避免弹出黑框 startupinfo = None if sys.platform == "win32": startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = 0 # 0 = SW_HIDE subprocess.run(command, check=True, capture_output=True, startupinfo=startupinfo) 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' ] # 避免弹出黑框 startupinfo = None if sys.platform == "win32": startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = 0 # 0 = SW_HIDE subprocess.run(command, check=True, capture_output=True, startupinfo=startupinfo) 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' ] # 避免弹出黑框 startupinfo = None if sys.platform == "win32": startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = 0 # 0 = SW_HIDE subprocess.run(command, check=True, capture_output=True, startupinfo=startupinfo) 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' ] # 避免弹出黑框 startupinfo = None if sys.platform == "win32": startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW startupinfo.wShowWindow = 0 # 0 = SW_HIDE subprocess.run(command, check=True, capture_output=True, startupinfo=startupinfo) 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 self.thread_count = 4 # 默认线程数 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) # 线程选择框 thread_layout = QHBoxLayout() thread_label = QLabel("线程数量:") thread_layout.addWidget(thread_label) self.thread_combo = QComboBox() self.thread_combo.addItems(["1", "2", "4", "8"]) self.thread_combo.setCurrentText("4") self.thread_combo.currentIndexChanged.connect(self.update_thread_count) thread_layout.addWidget(self.thread_combo) self.layout.addLayout(thread_layout) # 二维码区域 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_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_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 update_thread_count(self): """更新线程数量设置""" self.thread_count = int(self.thread_combo.currentText()) 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, thread_count=self.thread_count) 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, thread_count=self.thread_count) # 连接信号和槽 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) self.thread_combo.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)}")
08-23
import os import sys import random import shutil import subprocess import numpy as np from scipy.fftpack import dct, idct from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QLineEdit, QFileDialog, QMessageBox, QProgressBar, QTextEdit) from PyQt5.QtCore import Qt, QThread, pyqtSignal import cv2 from moviepy.editor import VideoFileClip, ColorClip class VideoProcessor(QThread): progress_signal = pyqtSignal(int) log_signal = pyqtSignal(str) finished_signal = pyqtSignal(bool) def __init__(self, a_video_path, output_dir, num_videos): super().__init__() self.a_video_path = a_video_path self.output_dir = output_dir self.num_videos = num_videos self.video_dir = "video" self.daifa_dir = "daifa" def run(self): try: # 创建输出目录 if not os.path.exists(self.daifa_dir): os.makedirs(self.daifa_dir) # 获取B视频列表 b_videos = [f for f in os.listdir(self.video_dir) if f.lower().endswith(('.mp4', '.avi', '.mov', '.mkv'))] if len(b_videos) < self.num_videos: self.log_signal.emit(f"错误: video文件夹中只有{len(b_videos)}个视频,但需要{self.num_videos}个") self.finished_signal.emit(False) return # 随机选择B视频 selected_b_videos = random.sample(b_videos, self.num_videos) # 处理A视频 a_video_info = self.get_video_info(self.a_video_path) self.log_signal.emit(f"A视频信息: 时长={a_video_info['duration']}秒, 分辨率={a_video_info['width']}x{a_video_info['height']}") # 提取A视频音频 audio_path = self.extract_audio(self.a_video_path) self.log_signal.emit("已提取A视频音频") # 处理音频隐写 stego_audio_path = self.audio_steganography(audio_path) self.log_signal.emit("已完成音频隐写处理") for i, b_video_name in enumerate(selected_b_videos): self.log_signal.emit(f"处理第{i+1}个视频: {b_video_name}") # 处理A视频帧 a_frames_dir = self.process_a_video(self.a_video_path, a_video_info) self.progress_signal.emit(25) # 处理B视频 b_video_path = os.path.join(self.video_dir, b_video_name) b_frames_dir = self.process_b_video(b_video_path, a_video_info) self.progress_signal.emit(50) # 嵌入隐写 stego_frames_dir = self.embed_frames(a_frames_dir, b_frames_dir, a_video_info) self.progress_signal.emit(75) # 合成最终视频 output_path = os.path.join(self.daifa_dir, f"daifa{i+1}.mp4") self.create_final_video(stego_frames_dir, stego_audio_path, output_path, a_video_info) # 添加元数据 self.add_metadata(output_path) # 清理临时文件 self.cleanup([a_frames_dir, b_frames_dir, stego_frames_dir]) self.progress_signal.emit(100) self.log_signal.emit(f"已完成第{i+1}个视频处理: {output_path}") # 删除已使用的B视频 os.remove(b_video_path) # 清理音频文件 self.cleanup([audio_path, stego_audio_path]) self.finished_signal.emit(True) except Exception as e: self.log_signal.emit(f"处理过程中出错: {str(e)}") self.finished_signal.emit(False) def get_video_info(self, video_path): clip = VideoFileClip(video_path) info = { 'duration': clip.duration, 'fps': clip.fps, 'width': clip.w, 'height': clip.h } clip.close() return info def extract_audio(self, video_path): clip = VideoFileClip(video_path) audio_path = "temp_audio.wav" clip.audio.write_audiofile(audio_path, verbose=False, logger=None) clip.close() return audio_path def audio_steganography(self, audio_path): # 这里实现音频隐写算法 # 简化实现:只是复制文件 stego_audio_path = "temp_audio_stego.wav" shutil.copyfile(audio_path, stego_audio_path) return stego_audio_path def process_a_video(self, video_path, video_info): frames_dir = "temp_a_frames" if os.path.exists(frames_dir): shutil.rmtree(frames_dir) os.makedirs(frames_dir) # 创建扰动背景视频 bg_clip = ColorClip((720, 1560), color=[0, 0, 0], duration=video_info['duration']) bg_clip = bg_clip.set_fps(30) # 加载A视频并调整大小 a_clip = VideoFileClip(video_path) # 计算缩放比例 scale = min(720 / a_clip.w, 1560 / a_clip.h) new_w = int(a_clip.w * scale) new_h = int(a_clip.h * scale) # 调整大小并设置位置 a_clip = a_clip.resize((new_w, new_h)) a_clip = a_clip.set_opacity(0.98) # 合成视频 x_pos = (720 - new_w) // 2 y_pos = (1560 - new_h) // 2 final_clip = bg_clip.set_duration(a_clip.duration) final_clip = final_clip.set_position((x_pos, y_pos)) final_clip = final_clip.set_opacity(1) # 写入临时文件 temp_video = "temp_a_processed.mp4" final_clip.write_videofile( temp_video, codec='libx264', audio=False, fps=30, verbose=False, logger=None ) # 拆帧 cap = cv2.VideoCapture(temp_video) frame_count = 0 while True: ret, frame = cap.read() if not ret: break cv2.imwrite(os.path.join(frames_dir, f"frame_{frame_count:06d}.png"), frame) frame_count += 1 cap.release() # 清理临时文件 os.remove(temp_video) a_clip.close() bg_clip.close() final_clip.close() return frames_dir def process_b_video(self, video_path, a_video_info): frames_dir = "temp_b_frames" if os.path.exists(frames_dir): shutil.rmtree(frames_dir) os.makedirs(frames_dir) # 创建扰动背景视频 bg_clip = ColorClip((720, 1560), color=[0, 0, 0], duration=a_video_info['duration']) bg_clip = bg_clip.set_fps(30) # 加载B视频并调整大小 b_clip = VideoFileClip(video_path) # 裁剪到与A视频相同时长 if b_clip.duration > a_video_info['duration']: b_clip = b_clip.subclip(0, a_video_info['duration']) # 计算缩放比例 scale = min(720 / b_clip.w, 1560 / b_clip.h) new_w = int(b_clip.w * scale) new_h = int(b_clip.h * scale) # 调整大小 b_clip = b_clip.resize((new_w, new_h)) # 合成视频 x_pos = (720 - new_w) // 2 y_pos = (1560 - new_h) // 2 final_clip = bg_clip.set_duration(b_clip.duration) final_clip = final_clip.set_position((x_pos, y_pos)) # 写入临时文件 temp_video = "temp_b_processed.mp4" final_clip.write_videofile( temp_video, codec='libx264', audio=False, fps=30, verbose=False, logger=None ) # 拆帧 cap = cv2.VideoCapture(temp_video) frame_count = 0 while True: ret, frame = cap.read() if not ret: break cv2.imwrite(os.path.join(frames_dir, f"frame_{frame_count:06d}.png"), frame) frame_count += 1 cap.release() # 清理临时文件 os.remove(temp_video) b_clip.close() bg_clip.close() final_clip.close() return frames_dir def dct_embed(self, cover_frame, secret_frame, alpha=0.01): # 将图像转换为YUV颜色空间 cover_yuv = cv2.cvtColor(cover_frame, cv2.COLOR_BGR2YUV) secret_yuv = cv2.cvtColor(secret_frame, cv2.COLOR_BGR2YUV) # 只使用Y通道进行隐写 cover_y = cover_yuv[:,:,0].astype(np.float32) secret_y = secret_yuv[:,:,0].astype(np.float32) # 将秘密图像缩放到合适的大小 secret_y = cv2.resize(secret_y, (cover_y.shape[1]//8, cover_y.shape[0]//8)) # 对封面图像进行8x8分块DCT stego_y = cover_y.copy() for i in range(0, cover_y.shape[0], 8): for j in range(0, cover_y.shape[1], 8): if i//8 < secret_y.shape[0] and j//8 < secret_y.shape[1]: block = cover_y[i:i+8, j:j+8] dct_block = dct(dct(block.T, norm='ortho').T, norm='ortho') # 在中频系数中嵌入信息 dct_block[4, 4] += alpha * secret_y[i//8, j//8] idct_block = idct(idct(dct_block.T, norm='ortho').T, norm='ortho') stego_y[i:i+8, j:j+8] = idct_block # 合并回YUV图像 stego_yuv = cover_yuv.copy() stego_yuv[:,:,0] = stego_y # 转换回BGR颜色空间 stego_frame = cv2.cvtColor(stego_yuv, cv2.COLOR_YUV2BGR) return stego_frame def embed_frames(self, a_frames_dir, b_frames_dir, video_info): stego_frames_dir = "temp_stego_frames" if os.path.exists(stego_frames_dir): shutil.rmtree(stego_frames_dir) os.makedirs(stego_frames_dir) a_frames = sorted([f for f in os.listdir(a_frames_dir) if f.endswith('.png')]) b_frames = sorted([f for f in os.listdir(b_frames_dir) if f.endswith('.png')]) total_frames = min(len(a_frames), len(b_frames)) for i in range(total_frames): a_frame_path = os.path.join(a_frames_dir, a_frames[i]) b_frame_path = os.path.join(b_frames_dir, b_frames[i]) a_frame = cv2.imread(a_frame_path) b_frame = cv2.imread(b_frame_path) # 使用DCT隐写 stego_frame = self.dct_embed(a_frame, b_frame) # 保存隐写后的帧 cv2.imwrite(os.path.join(stego_frames_dir, f"frame_{i:06d}.png"), stego_frame) if i % 30 == 0: # 每秒钟更新一次进度 self.progress_signal.emit(50 + int(25 * i / total_frames)) return stego_frames_dir def create_final_video(self, frames_dir, audio_path, output_path, video_info): # 获取帧列表 frames = sorted([f for f in os.listdir(frames_dir) if f.endswith('.png')]) # 创建视频写入器 first_frame = cv2.imread(os.path.join(frames_dir, frames[0])) height, width = first_frame.shape[:2] fourcc = cv2.VideoWriter_fourcc(*'avc1') out = cv2.VideoWriter(output_path, fourcc, 30, (width, height)) # 写入所有帧 for frame_name in frames: frame = cv2.imread(os.path.join(frames_dir, frame_name)) out.write(frame) out.release() # 添加音频 cmd = [ 'ffmpeg', '-y', '-i', output_path, '-i', audio_path, '-c:v', 'copy', '-c:a', 'aac', '-b:a', '96k', '-strict', 'experimental', output_path + '_with_audio.mp4' ] subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # 替换原文件 os.remove(output_path) os.rename(output_path + '_with_audio.mp4', output_path) def add_metadata(self, video_path): # 随机生成元数据 dates = ['2023:06:15 12:30:45', '2023:07:20 14:25:30', '2023:08:10 09:15:22'] locations = ['Beijing', 'Shanghai', 'Guangzhou', 'Shenzhen'] devices = ['iPhone 14 Pro', 'HUAWEI P60', 'Xiaomi 13', 'Canon EOS R5'] date = random.choice(dates) location = random.choice(locations) device = random.choice(devices) # 使用FFmpeg添加元数据 cmd = [ 'ffmpeg', '-y', '-i', video_path, '-metadata', f'creation_time={date}', '-metadata', f'location={location}', '-metadata', f'model={device}', '-c', 'copy', video_path + '_with_metadata.mp4' ] subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) # 替换原文件 os.remove(video_path) os.rename(video_path + '_with_metadata.mp4', video_path) def cleanup(self, paths): for path in paths: if os.path.exists(path): if os.path.isfile(path): os.remove(path) else: shutil.rmtree(path) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.initUI() def initUI(self): self.setWindowTitle('视频隐写处理软件') self.setGeometry(100, 100, 600, 500) central_widget = QWidget() self.setCentralWidget(central_widget) layout = QVBoxLayout() # A视频选择 a_video_layout = QHBoxLayout() self.a_video_label = QLabel('A视频路径:') self.a_video_path = QLineEdit() self.a_video_browse = QPushButton('浏览') self.a_video_browse.clicked.connect(self.browse_a_video) a_video_layout.addWidget(self.a_video_label) a_video_layout.addWidget(self.a_video_path) a_video_layout.addWidget(self.a_video_browse) layout.addLayout(a_video_layout) # 处理数量输入 num_layout = QHBoxLayout() self.num_label = QLabel('处理数量:') self.num_input = QLineEdit('1') num_layout.addWidget(self.num_label) num_layout.addWidget(self.num_input) layout.addLayout(num_layout) # 进度条 self.progress_bar = QProgressBar() layout.addWidget(self.progress_bar) # 日志显示 self.log_display = QTextEdit() self.log_display.setReadOnly(True) layout.addWidget(self.log_display) # 开始按钮 self.start_button = QPushButton('开始处理') self.start_button.clicked.connect(self.start_processing) layout.addWidget(self.start_button) central_widget.setLayout(layout) def browse_a_video(self): file_path, _ = QFileDialog.getOpenFileName( self, '选择A视频', '', '视频文件 (*.mp4 *.avi *.mov *.mkv)') if file_path: self.a_video_path.setText(file_path) def log_message(self, message): self.log_display.append(message) def start_processing(self): a_video_path = self.a_video_path.text() if not os.path.exists(a_video_path): QMessageBox.warning(self, '错误', '请选择有效的A视频文件') return try: num_videos = int(self.num_input.text()) video_dir = "video" if not os.path.exists(video_dir): os.makedirs(video_dir) QMessageBox.warning(self, '错误', 'video文件夹不存在,已创建空文件夹') return b_videos = [f for f in os.listdir(video_dir) if f.lower().endswith(('.mp4', '.avi', '.mov', '.mkv'))] if len(b_videos) < num_videos: QMessageBox.warning( self, '错误', f'video文件夹中只有{len(b_videos)}个视频,但需要{num_videos}个' ) return except ValueError: QMessageBox.warning(self, '错误', '请输入有效的数字') return self.start_button.setEnabled(False) self.progress_bar.setValue(0) self.log_display.clear() self.processor = VideoProcessor(a_video_path, "daifa", num_videos) self.processor.progress_signal.connect(self.progress_bar.setValue) self.processor.log_signal.connect(self.log_message) self.processor.finished_signal.connect(self.processing_finished) self.processor.start() def processing_finished(self, success): self.start_button.setEnabled(True) if success: QMessageBox.information(self, '完成', '视频处理完成') else: QMessageBox.warning(self, '错误', '视频处理过程中出现错误') if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_()) 给这个代码一个详细打包步骤
08-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值