idFishWithCat commited on
Commit
fab980c
·
verified ·
1 Parent(s): 771dc73

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +285 -0
app.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import ffmpeg
3
+ import tempfile
4
+ import os
5
+ from PIL import Image
6
+ import numpy as np
7
+ import os
8
+ import time
9
+
10
+ # 设置umask确保新创建的文件有正确的权限
11
+ os.umask(0o022)
12
+
13
+
14
+ def subliminal_insert(video_path,
15
+ frame_img_path,
16
+ interval_frames,
17
+ duration_frames,
18
+ output_dir,
19
+ progress=None):
20
+ """
21
+ 将特定帧按间隔植入视频流中
22
+ """
23
+ if not os.path.exists(video_path):
24
+ raise FileNotFoundError("视频文件不存在")
25
+ if not os.path.exists(frame_img_path):
26
+ raise FileNotFoundError("图片文件不存在")
27
+
28
+ temp_output = None
29
+ frame_temp = None
30
+
31
+ try:
32
+ # 获取视频基本信息
33
+ probe = ffmpeg.probe(video_path)
34
+ video_info = next(s for s in probe['streams']
35
+ if s['codec_type'] == 'video')
36
+ width = int(video_info['width'])
37
+ height = int(video_info['height'])
38
+ fps = eval(video_info['r_frame_rate'])
39
+
40
+ print(f"视频信息: {width}x{height} @ {fps}fps")
41
+
42
+ if progress is not None:
43
+ progress(0.1, "正在处理图片...")
44
+
45
+ # 调整图片大小以匹配视频尺寸
46
+ img = Image.open(frame_img_path)
47
+ img = img.resize((width, height))
48
+ frame_temp = tempfile.NamedTemporaryFile(delete=False,
49
+ suffix='.png').name
50
+ img.save(frame_temp)
51
+
52
+ if progress is not None:
53
+ progress(0.2, "正在准备处理视频...")
54
+
55
+ # 使用系统临时目录
56
+ gradio_temp_dir = os.path.join(tempfile.gettempdir(), "gradio")
57
+ os.makedirs(gradio_temp_dir, exist_ok=True)
58
+ temp_output = os.path.join(gradio_temp_dir,
59
+ f"output_{int(time.time())}.mp4")
60
+ print(f"将创建输出文件: {temp_output}")
61
+
62
+ try:
63
+ if progress is not None:
64
+ progress(0.3, "正在处理视频...")
65
+
66
+ # 构建ffmpeg命令
67
+ input_video = ffmpeg.input(video_path)
68
+ input_image = ffmpeg.input(frame_temp)
69
+
70
+ # 构建filter complex
71
+ v = ffmpeg.filter(
72
+ [input_video, input_image],
73
+ 'overlay',
74
+ enable=f'if(lt(mod(n,{interval_frames}),{duration_frames}),1,0)'
75
+ )
76
+
77
+ # 检查输入视频是否有音频流
78
+ has_audio = any(s['codec_type'] == 'audio'
79
+ for s in probe['streams'])
80
+
81
+ # 构建输出
82
+ if has_audio:
83
+ stream = ffmpeg.output(v,
84
+ input_video.audio,
85
+ temp_output,
86
+ acodec='copy',
87
+ vcodec='libx264',
88
+ preset='ultrafast',
89
+ crf=23,
90
+ movflags='+faststart')
91
+ else:
92
+ stream = ffmpeg.output(v,
93
+ temp_output,
94
+ vcodec='libx264',
95
+ preset='ultrafast',
96
+ crf=23,
97
+ movflags='+faststart')
98
+
99
+ # 打印完整命令
100
+ cmd = " ".join(stream.compile())
101
+ print("FFmpeg命令:", cmd)
102
+
103
+ # 运行命令
104
+ stdout, stderr = stream.run(capture_stdout=True,
105
+ capture_stderr=True,
106
+ overwrite_output=True)
107
+
108
+ if progress is not None:
109
+ progress(0.9, "正在完成处理...")
110
+
111
+ # 检查输出文件
112
+ if not os.path.exists(temp_output):
113
+ raise Exception(f"输出文件不存在: {temp_output}")
114
+
115
+ if os.path.getsize(temp_output) == 0:
116
+ raise Exception(f"输出文件大小为0: {temp_output}")
117
+
118
+ # 设置文件权限
119
+ try:
120
+ os.chmod(temp_output, 0o644)
121
+ except Exception as e:
122
+ print(f"设置文件权限时出错: {str(e)}")
123
+
124
+ print(
125
+ f"成功创建输出文件: {temp_output}, 大小: {os.path.getsize(temp_output)} bytes"
126
+ )
127
+
128
+ if progress is not None:
129
+ progress(1.0, "处理完成")
130
+
131
+ return temp_output
132
+
133
+ except ffmpeg.Error as e:
134
+ print("FFmpeg stdout:",
135
+ e.stdout.decode('utf8') if e.stdout else "No stdout")
136
+ print("FFmpeg stderr:",
137
+ e.stderr.decode('utf8') if e.stderr else "No stderr")
138
+ raise Exception(
139
+ f"FFmpeg处理失败: {e.stderr.decode('utf8') if e.stderr else str(e)}"
140
+ )
141
+
142
+ except Exception as e:
143
+ print(f"处理视频时出错: {str(e)}")
144
+ if isinstance(e, ffmpeg.Error):
145
+ if e.stderr:
146
+ print("FFmpeg错误输出:", e.stderr.decode('utf8'))
147
+ raise
148
+
149
+ finally:
150
+ # 清理临时文件
151
+ if frame_temp and os.path.exists(frame_temp):
152
+ try:
153
+ os.unlink(frame_temp)
154
+ except:
155
+ pass
156
+
157
+
158
+ def process_video(video_input,
159
+ frame_input,
160
+ interval,
161
+ duration,
162
+ progress=gr.Progress()):
163
+ """
164
+ 处理视频和图片输入
165
+ """
166
+ # 输入验证
167
+ if interval <= 0 or duration <= 0:
168
+ raise ValueError("间隔和持续时间必须大于0")
169
+ if interval < duration:
170
+ raise ValueError("间隔必须大于持续时间")
171
+ if interval < 12: # 安全限制
172
+ raise ValueError("为了安全起见,间隔不能小于12帧")
173
+
174
+ temp_files = [] # 跟踪临时文件
175
+
176
+ # 创建一个固定的输出目录(在当前工作目录下)
177
+ output_dir = os.path.join(os.getcwd(), "outputs")
178
+ os.makedirs(output_dir, exist_ok=True)
179
+ # 确保目录有正确的权限
180
+ os.chmod(output_dir, 0o777)
181
+
182
+ try:
183
+ # 检查输入是否为空
184
+ if video_input is None or frame_input is None:
185
+ raise ValueError("视频或图片输入为空")
186
+
187
+ # 处理视频输入
188
+ video_temp = tempfile.NamedTemporaryFile(delete=False,
189
+ suffix='.mp4').name
190
+ temp_files.append(video_temp)
191
+
192
+ if isinstance(video_input, str):
193
+ video_temp = video_input
194
+ else:
195
+ try:
196
+ with open(video_temp, 'wb') as f:
197
+ f.write(video_input.read() if hasattr(video_input, 'read'
198
+ ) else video_input)
199
+ except Exception as e:
200
+ raise Exception(f"视频写入失败: {str(e)}")
201
+
202
+ # 处理图片输入
203
+ frame_temp = tempfile.NamedTemporaryFile(delete=False,
204
+ suffix='.png').name
205
+ temp_files.append(frame_temp)
206
+
207
+ try:
208
+ if hasattr(frame_input, 'save'):
209
+ frame_input.save(frame_temp)
210
+ elif isinstance(frame_input, np.ndarray):
211
+ Image.fromarray(frame_input).save(frame_temp)
212
+ else:
213
+ raise ValueError("不支持的图片格式")
214
+ except Exception as e:
215
+ raise Exception(f"图片处理失败: {str(e)}")
216
+
217
+ output_path = subliminal_insert(video_temp, frame_temp, int(interval),
218
+ int(duration), output_dir, progress)
219
+
220
+ print(f"处理完成,输出文件: {output_path}")
221
+ return output_path
222
+
223
+ except Exception as e:
224
+ print(f"处理失败: {str(e)}")
225
+ raise
226
+
227
+ finally:
228
+ # 清理临时文件
229
+ for temp_file in temp_files:
230
+ if temp_file != video_input and os.path.exists(temp_file):
231
+ try:
232
+ os.unlink(temp_file)
233
+ except Exception:
234
+ pass
235
+
236
+
237
+ # 创建Gradio界面
238
+ iface = gr.Interface(fn=process_video,
239
+ inputs=[
240
+ gr.Video(label="输入视频", format="mp4"),
241
+ gr.Image(label="要植入的帧", type="pil"),
242
+ gr.Slider(minimum=12,
243
+ maximum=60,
244
+ value=24,
245
+ step=1,
246
+ label="植入间隔(帧数)",
247
+ info="每隔多少帧插入一次图片"),
248
+ gr.Slider(minimum=1,
249
+ maximum=10,
250
+ value=1,
251
+ step=1,
252
+ label="植入持续时间(帧数)",
253
+ info="每次插入的图片持续多少帧")
254
+ ],
255
+ outputs=gr.File(label="输出视频", visible=True),
256
+ title="阈下植入",
257
+ description="将特定图片帧按指定间隔植入到视频中",
258
+ article="""
259
+ 使用说明:
260
+ 1. 上传要处理的视频文件(MP4格式)
261
+ 2. 上传要植入的图片
262
+ 3. 调整植入间隔和持续时间
263
+ 4. 点击提交开始处理
264
+
265
+ 注意事项:
266
+ - 间隔帧数必须大于持续帧数
267
+ - 建议间隔至少为24帧(1秒)
268
+ - 处理时间取决于视频长度和复杂度
269
+ """,
270
+ flagging_mode="never",
271
+ theme="default")
272
+
273
+ if __name__ == "__main__":
274
+ iface.launch(
275
+ debug=True,
276
+ show_error=True,
277
+ server_name="0.0.0.0",
278
+ server_port=7861,
279
+ share=True,
280
+ allowed_paths=[
281
+ "/tmp", "/tmp/gradio",
282
+ os.path.join(os.getcwd(), "outputs")
283
+ ], # 明确允许访问的路径
284
+ root_path="", # 确保根路径正确
285
+ )