Ryanus commited on
Commit
ba492f5
·
verified ·
1 Parent(s): 758e10b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +159 -203
app.py CHANGED
@@ -4,120 +4,9 @@ import tempfile
4
  import random
5
  import subprocess
6
  import shutil
7
- import json
8
- import pickle
9
  from datetime import datetime
10
 
11
- # Google API 导入
12
- try:
13
- from googleapiclient.discovery import build
14
- from googleapiclient.http import MediaFileUpload
15
- from google_auth_oauthlib.flow import InstalledAppFlow
16
- from google.auth.transport.requests import Request
17
- YOUTUBE_AVAILABLE = True
18
- except ImportError:
19
- YOUTUBE_AVAILABLE = False
20
-
21
- # 内置Google Cloud凭据(用户无需设置)
22
- CLIENT_SECRETS = {
23
- "installed": {
24
- "client_id": "288005932157-k7dhq8c7rgmt8km0nm50ntuif9lse7j1.apps.googleusercontent.com",
25
- "project_id": "corded-palisade-458105-p7",
26
- "auth_uri": "https://accounts.google.com/o/oauth2/auth",
27
- "token_uri": "https://oauth2.googleapis.com/token",
28
- "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
29
- "client_secret": "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",
30
- "redirect_uris": ["http://localhost"]
31
- }
32
- }
33
-
34
- SCOPES = ['https://www.googleapis.com/auth/youtube.upload']
35
- CREDENTIALS_FILE = 'youtube_token.pickle'
36
-
37
- class SimpleYouTubeUploader:
38
- def __init__(self):
39
- self.youtube = None
40
- self.authenticated = False
41
-
42
- def authenticate(self):
43
- if not YOUTUBE_AVAILABLE:
44
- return False, "❌ YouTube API库未安装"
45
-
46
- try:
47
- creds = None
48
- if os.path.exists(CREDENTIALS_FILE):
49
- with open(CREDENTIALS_FILE, 'rb') as token:
50
- creds = pickle.load(token)
51
-
52
- if not creds or not creds.valid:
53
- if creds and creds.expired and creds.refresh_token:
54
- creds.refresh(Request())
55
- else:
56
- # 创建临时凭据文件
57
- temp_secrets = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
58
- json.dump(CLIENT_SECRETS, temp_secrets)
59
- temp_secrets.close()
60
-
61
- flow = InstalledAppFlow.from_client_secrets_file(temp_secrets.name, SCOPES)
62
- creds = flow.run_local_server(port=0, open_browser=False)
63
-
64
- # 清理临时文件
65
- os.unlink(temp_secrets.name)
66
-
67
- with open(CREDENTIALS_FILE, 'wb') as token:
68
- pickle.dump(creds, token)
69
-
70
- self.youtube = build('youtube', 'v3', credentials=creds)
71
- self.authenticated = True
72
- return True, "✅ YouTube认证成功"
73
-
74
- except Exception as e:
75
- return False, f"❌ 认证失败: {str(e)}"
76
-
77
- def upload_video(self, video_path, title, description="", privacy="private"):
78
- if not self.authenticated:
79
- return {"success": False, "error": "请先完成认证"}
80
-
81
- if not os.path.exists(video_path):
82
- return {"success": False, "error": f"文件不存在: {video_path}"}
83
-
84
- body = {
85
- 'snippet': {
86
- 'title': title,
87
- 'description': description,
88
- 'tags': ["混剪", "自动生成", "短视频", "创意"],
89
- 'categoryId': '22'
90
- },
91
- 'status': {
92
- 'privacyStatus': privacy,
93
- 'selfDeclaredMadeForKids': False
94
- }
95
- }
96
-
97
- try:
98
- media = MediaFileUpload(video_path, resumable=True, mimetype='video/*')
99
- request = self.youtube.videos().insert(
100
- part=','.join(body.keys()),
101
- body=body,
102
- media_body=media
103
- )
104
-
105
- response = request.execute()
106
- video_id = response['id']
107
- url = f"https://www.youtube.com/watch?v={video_id}"
108
-
109
- return {
110
- "success": True,
111
- "video_id": video_id,
112
- "url": url,
113
- "title": title
114
- }
115
- except Exception as e:
116
- return {"success": False, "error": str(e)}
117
-
118
- # 创建全局上传器实例
119
- youtube_uploader = SimpleYouTubeUploader()
120
-
121
  def ffmpeg_cut_video(input_path, start_time, duration, output_path):
122
  command = [
123
  'ffmpeg', '-ss', str(start_time), '-i', input_path,
@@ -182,7 +71,11 @@ def generate_mixed_videos(clips, num_output_videos):
182
  start_idx = i * clips_per_video
183
  end_idx = len(clips) if i == num_output_videos - 1 else (start_idx + clips_per_video)
184
  selected_clips = clips[start_idx:end_idx]
185
- output_path = os.path.join(temp_dir, f"mixed_video_{i+1}.mp4")
 
 
 
 
186
  ok = concat_videos_ffmpeg(selected_clips, output_path)
187
  if not ok:
188
  raise RuntimeError("视频合并失败")
@@ -192,87 +85,151 @@ def generate_mixed_videos(clips, num_output_videos):
192
  shutil.rmtree(temp_dir, ignore_errors=True)
193
  raise
194
 
195
- def process_and_upload(video_files, clip_duration, num_output_videos, upload_to_youtube, privacy_setting):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  if not video_files or len(video_files) == 0:
197
- return "❌ 请上传至少一个视频文件", [], ""
198
 
199
  try:
 
 
 
200
  # 1. 视频处理
201
  clips, clips_temp_dir = extract_clips_from_videos(video_files, clip_duration)
202
  output_files, output_temp_dir = generate_mixed_videos(clips, num_output_videos)
 
 
203
  shutil.rmtree(clips_temp_dir, ignore_errors=True)
204
 
205
- status_msg = f"✅ 成功生成 {len(output_files)} 个混剪视频"
206
- upload_result = ""
207
 
208
- # 2. YouTube上传(如果勾选)
209
- if upload_to_youtube and YOUTUBE_AVAILABLE:
210
- auth_success, auth_msg = youtube_uploader.authenticate()
211
- upload_result += auth_msg + "\n\n"
212
-
213
- if auth_success:
214
- upload_result += "🚀 开始上传到YouTube...\n\n"
215
-
216
- for i, video_path in enumerate(output_files, 1):
217
- title = f"自动混剪视频 #{i} - {datetime.now().strftime('%Y%m%d_%H%M')}"
218
- description = f"""🎬 自动生成的混剪视频
219
 
220
- 📹 **制作信息:**
221
- 切片时长:{clip_duration}
222
- 生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
223
- 自动随机混剪
224
 
225
- 🔧 **制作工具:**
226
- FFmpeg 自动剪辑
227
- Python 批量处理
228
- 智能片段组合
229
 
230
- 👍 **如果喜欢请点赞支持!**
 
 
 
 
 
231
 
232
- #混剪 #自动生成 #短视频 #创意 #FFmpeg"""
233
-
234
- result = youtube_uploader.upload_video(
235
- video_path=video_path,
236
- title=title,
237
- description=description,
238
- privacy=privacy_setting
239
- )
240
-
241
- if result["success"]:
242
- upload_result += f"✅ 视频 {i} 上传成功\n🔗 {result['url']}\n\n"
243
- else:
244
- upload_result += f"❌ 视频 {i} 上传失败: {result['error']}\n\n"
245
- else:
246
- upload_result += "❌ YouTube认证失败,无法上传"
247
-
248
- elif upload_to_youtube and not YOUTUBE_AVAILABLE:
249
- upload_result = "⚠️ YouTube API库未安装,无法上传"
 
 
 
 
 
250
 
251
- return status_msg, output_files, upload_result
 
 
 
252
 
253
  except Exception as e:
254
- return f"❌ 处理失败: {str(e)}", [], ""
255
 
256
  def main():
257
  with gr.Blocks(
258
- title="FFmpeg自动剪辑+YouTube一键上传",
259
  theme=gr.themes.Soft()
260
  ) as demo:
261
 
262
  gr.HTML("""
263
  <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); border-radius: 10px; color: white; margin-bottom: 20px;">
264
- <h1>🎬 FFmpeg 自动剪辑 + YouTube 一键上传</h1>
265
- <p style="margin: 10px 0 0 0;">长视频自动切片 → 智能混剪 → 一键上传YouTube</p>
266
  </div>
267
  """)
268
 
269
  with gr.Row():
270
  with gr.Column(scale=2):
271
  video_input = gr.File(
272
- label="📤 上传视频文件",
273
- file_types=[".mp4", ".mov", ".avi"],
274
  file_count="multiple",
275
- height=100
276
  )
277
 
278
  with gr.Row():
@@ -284,24 +241,13 @@ def main():
284
  )
285
  num_output = gr.Number(
286
  value=3,
287
- label="生成视频数",
288
  minimum=1,
289
- maximum=6
290
- )
291
-
292
- with gr.Row():
293
- upload_youtube = gr.Checkbox(
294
- label="🚀 一键上传到YouTube",
295
- value=False
296
- )
297
- privacy_setting = gr.Dropdown(
298
- choices=["private", "unlisted", "public"],
299
- value="private",
300
- label="YouTube隐私设置"
301
  )
302
 
303
  process_btn = gr.Button(
304
- "🎬 开始处理并上传",
305
  variant="primary",
306
  size="lg"
307
  )
@@ -309,50 +255,60 @@ def main():
309
  with gr.Column(scale=1):
310
  status_output = gr.Textbox(
311
  label="📊 处理状态",
312
- lines=6,
313
- interactive=False
 
314
  )
315
 
316
  with gr.Row():
317
- with gr.Column():
318
- output_files = gr.File(
319
- label="📁 下载生成的视频",
320
- file_count="multiple"
321
  )
322
 
323
- with gr.Column():
324
- upload_result = gr.Textbox(
325
- label="🚀 YouTube上传结果",
326
- lines=10,
327
  interactive=False,
328
  show_copy_button=True
329
  )
330
 
331
  process_btn.click(
332
- fn=process_and_upload,
333
- inputs=[video_input, clip_duration, num_output, upload_youtube, privacy_setting],
334
- outputs=[status_output, output_files, upload_result]
335
  )
336
 
337
  gr.Markdown("""
338
  ---
339
  ### 📖 使用说明
340
 
341
- **🎬 视频处理:**
342
- - 上传一个或多个视频文件
343
- - 自动按指定时长切片
344
- - 随机重组生成新的混剪视频
 
 
 
 
 
 
 
 
345
 
346
- **🚀 YouTube上传:**
347
- - 勾选"一键上传到YouTube"
348
- - **首次使用**:会弹出授权链接,需要在浏览器中登录Google账号授权
349
- - **后续使用**:直接上传,无需重复授权
350
- - 支持设置视频隐私级别
 
351
 
352
- **⚠️ 注意事项:**
353
- - 需要环境安装FFmpeg
354
- - YouTube上传需要Google账号授权(仅需授权一次)
355
- - 建议先设为私密视频测试
356
  """)
357
 
358
  demo.launch()
 
4
  import random
5
  import subprocess
6
  import shutil
7
+ import zipfile
 
8
  from datetime import datetime
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  def ffmpeg_cut_video(input_path, start_time, duration, output_path):
11
  command = [
12
  'ffmpeg', '-ss', str(start_time), '-i', input_path,
 
71
  start_idx = i * clips_per_video
72
  end_idx = len(clips) if i == num_output_videos - 1 else (start_idx + clips_per_video)
73
  selected_clips = clips[start_idx:end_idx]
74
+
75
+ # 为每个视频生成更有意义的文件名
76
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
77
+ output_path = os.path.join(temp_dir, f"混剪视频_{i+1}_{timestamp}.mp4")
78
+
79
  ok = concat_videos_ffmpeg(selected_clips, output_path)
80
  if not ok:
81
  raise RuntimeError("视频合并失败")
 
85
  shutil.rmtree(temp_dir, ignore_errors=True)
86
  raise
87
 
88
+ def create_video_package(output_files, original_file_names):
89
+ """将生成的视频文件打包成zip,并创建说明文件"""
90
+
91
+ # 创建临时目录用于打包
92
+ package_dir = tempfile.mkdtemp()
93
+ zip_path = os.path.join(package_dir, f"混剪视频包_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip")
94
+
95
+ try:
96
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
97
+ # 添加所有视频文件
98
+ for video_file in output_files:
99
+ arcname = os.path.basename(video_file)
100
+ zipf.write(video_file, arcname)
101
+
102
+ # 创建说明文件
103
+ readme_content = f"""# 混剪视频包说明
104
+
105
+ ## 📊 生成信息
106
+ - 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
107
+ - 视频数量: {len(output_files)} 个
108
+ - 源文件: {', '.join(original_file_names)}
109
+
110
+ ## 📁 文件列表
111
+ """
112
+
113
+ for i, video_file in enumerate(output_files, 1):
114
+ file_size = os.path.getsize(video_file) / (1024 * 1024) # MB
115
+ readme_content += f"- 混剪视频_{i}.mp4 ({file_size:.1f}MB)\n"
116
+
117
+ readme_content += f"""
118
+ ## 🎬 使用建议
119
+ - 适合发布到抖音、快手、小红书等短视频平台
120
+ - 建议添加背景音乐以提升观看体验
121
+ - 可以进一步编辑添加字幕、特效等
122
+
123
+ ## ⚡ 技术信息
124
+ - 制作工具: FFmpeg 自动剪辑
125
+ - 处理方式: 智能切片 + 随机重组
126
+ - 视频编码: H.264/AAC
127
+
128
+ ---
129
+ 感谢使用 FFmpeg 自动混剪工具!
130
+ """
131
+
132
+ # 将说明文件添加到zip
133
+ zipf.writestr("README.txt", readme_content.encode('utf-8'))
134
+
135
+ return zip_path
136
+
137
+ except Exception as e:
138
+ shutil.rmtree(package_dir, ignore_errors=True)
139
+ raise e
140
+
141
+ def process_and_package(video_files, clip_duration, num_output_videos):
142
  if not video_files or len(video_files) == 0:
143
+ return "❌ 请上传至少一个视频文件", None, ""
144
 
145
  try:
146
+ # 获取原始文件名
147
+ original_names = [os.path.basename(f.name) for f in video_files]
148
+
149
  # 1. 视频处理
150
  clips, clips_temp_dir = extract_clips_from_videos(video_files, clip_duration)
151
  output_files, output_temp_dir = generate_mixed_videos(clips, num_output_videos)
152
+
153
+ # 清理切片临时目录
154
  shutil.rmtree(clips_temp_dir, ignore_errors=True)
155
 
156
+ # 2. 打包所有视频
157
+ zip_file_path = create_video_package(output_files, original_names)
158
 
159
+ # 计算总大小
160
+ total_size = os.path.getsize(zip_file_path) / (1024 * 1024) # MB
161
+
162
+ status_msg = f"""✅ 处理完成!
 
 
 
 
 
 
 
163
 
164
+ 📊 **生成统计:**
165
+ 混剪视频数量: {len(output_files)}
166
+ 打包文件大小: {total_size:.1f}MB
167
+ 处理时间: {datetime.now().strftime('%H:%M:%S')}
168
 
169
+ 📦 **打包内容:**
170
+ {len(output_files)} 个混剪视频文件
171
+ 1 个使用说明文档
172
+ 所有文件已压缩打包
173
 
174
+ 💾 **下载说明:**
175
+ 点击下方下载按钮保存到本地硬盘
176
+ """
177
+
178
+ # 生成详细信息
179
+ details = f"""🎬 **视频详情:**
180
 
181
+ """
182
+ for i, video_file in enumerate(output_files, 1):
183
+ file_size = os.path.getsize(video_file) / (1024 * 1024)
184
+ details += f"• 混剪视频_{i}: {file_size:.1f}MB\n"
185
+
186
+ details += f"""
187
+ 🔧 **技术参数:**
188
+ • 切片时长: {clip_duration}秒
189
+ • 随机混剪算法: 智能重组
190
+ 视频编码: H.264
191
+ 音频编码: AAC
192
+
193
+ 📱 **发布建议:**
194
+ • 适合短视频平台发布
195
+ 建议添加背景音乐
196
+ • 可进一步编辑添加特效
197
+ 注意平台尺寸要求
198
+
199
+ 💡 **后续处理:**
200
+ • 解压后即可使用
201
+ • 支持主流播放器播放
202
+ • 可用视频编辑软件进一步处理
203
+ """
204
 
205
+ # 清理视频临时目录(但保留zip文件)
206
+ shutil.rmtree(output_temp_dir, ignore_errors=True)
207
+
208
+ return status_msg, zip_file_path, details
209
 
210
  except Exception as e:
211
+ return f"❌ 处理失败: {str(e)}", None, ""
212
 
213
  def main():
214
  with gr.Blocks(
215
+ title="FFmpeg自动剪辑+打包下载",
216
  theme=gr.themes.Soft()
217
  ) as demo:
218
 
219
  gr.HTML("""
220
  <div style="text-align: center; padding: 20px; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); border-radius: 10px; color: white; margin-bottom: 20px;">
221
+ <h1>🎬 FFmpeg 自动剪辑 + 打包下载</h1>
222
+ <p style="margin: 10px 0 0 0;">长视频自动切片 → 智能混剪 → 一键打包下载</p>
223
  </div>
224
  """)
225
 
226
  with gr.Row():
227
  with gr.Column(scale=2):
228
  video_input = gr.File(
229
+ label="📤 上传视频文件 (支持多个)",
230
+ file_types=[".mp4", ".mov", ".avi", ".mkv"],
231
  file_count="multiple",
232
+ height=120
233
  )
234
 
235
  with gr.Row():
 
241
  )
242
  num_output = gr.Number(
243
  value=3,
244
+ label="生成视频数量",
245
  minimum=1,
246
+ maximum=8
 
 
 
 
 
 
 
 
 
 
 
247
  )
248
 
249
  process_btn = gr.Button(
250
+ "🎬 开始自动剪辑并打包",
251
  variant="primary",
252
  size="lg"
253
  )
 
255
  with gr.Column(scale=1):
256
  status_output = gr.Textbox(
257
  label="📊 处理状态",
258
+ lines=10,
259
+ interactive=False,
260
+ show_copy_button=True
261
  )
262
 
263
  with gr.Row():
264
+ with gr.Column(scale=1):
265
+ download_file = gr.File(
266
+ label="📦 下载混剪视频包",
267
+ interactive=False
268
  )
269
 
270
+ with gr.Column(scale=1):
271
+ details_output = gr.Textbox(
272
+ label="📝 详细信息",
273
+ lines=15,
274
  interactive=False,
275
  show_copy_button=True
276
  )
277
 
278
  process_btn.click(
279
+ fn=process_and_package,
280
+ inputs=[video_input, clip_duration, num_output],
281
+ outputs=[status_output, download_file, details_output]
282
  )
283
 
284
  gr.Markdown("""
285
  ---
286
  ### 📖 使用说明
287
 
288
+ **🎬 视频处理流程:**
289
+ 1. **上传视频** → 支持 MP4、MOV、AVI、MKV 格式
290
+ 2. **自动切片** → 按指定时长将视频切成小段
291
+ 3. **智能混剪** → 随机重组片段生成新视频
292
+ 4. **自动打包** → 所有视频 + 说明文档打包成ZIP
293
+ 5. **一键下载** → 保存到本地硬盘
294
+
295
+ **💾 打包内容:**
296
+ - ✅ 所有生成的混剪视频文件
297
+ - ✅ 详细的使用说明文档
298
+ - ✅ 技术参数和建议信息
299
+ - ✅ 压缩打包,节省存储空间
300
 
301
+ **🚀 优势特点:**
302
+ - 🔥 **完全本地化** → 无需任何在线服务
303
+ - ⚡ **批量处理** → 支持多视频同时处理
304
+ - 🎯 **智能算法** → 随机重组避免重复
305
+ - 📱 **适配平台** → 生成适合短视频平台的内容
306
+ - 💾 **打包下载** → 一次下载全部文件
307
 
308
+ **⚠️ 系统要求:**
309
+ - 需要安装 FFmpeg 和 FFprobe
310
+ - 建议使用稳定的网络连接
311
+ - 推荐 Chrome 或 Firefox 浏览器
312
  """)
313
 
314
  demo.launch()