Ryanus commited on
Commit
a96293b
·
verified ·
1 Parent(s): 887c004

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +124 -232
app.py CHANGED
@@ -1,15 +1,10 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- import gradio as gr
5
  import os
 
6
  import tempfile
7
- import json
8
  import random
9
  from datetime import datetime
10
- from pathlib import Path
11
 
12
- # AI 和视频处理导入
13
  try:
14
  from openai import OpenAI
15
  client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) if os.getenv("OPENAI_API_KEY") else None
@@ -18,15 +13,14 @@ except ImportError:
18
  OPENAI_AVAILABLE = False
19
  client = None
20
 
21
- # 视频处理导入
22
  try:
23
  from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, vfx
24
  MOVIEPY_AVAILABLE = True
25
  except ImportError:
26
  MOVIEPY_AVAILABLE = False
27
- print("⚠️ MoviePy 不可用,将使用模拟模式")
28
 
29
- # YouTube 上传导入
30
  try:
31
  from googleapiclient.discovery import build
32
  from googleapiclient.http import MediaFileUpload
@@ -37,248 +31,146 @@ try:
37
  except ImportError:
38
  YOUTUBE_AVAILABLE = False
39
 
40
- # 全局配置
41
  MAX_VIDEO_SIZE = 100 * 1024 * 1024 # 100MB
42
  MAX_AUDIO_SIZE = 50 * 1024 * 1024 # 50MB
43
  MAX_TOTAL_SIZE = 500 * 1024 * 1024 # 500MB
44
 
45
- class YouTubeUploader:
46
- """YouTube 上传工具类"""
47
-
48
- def __init__(self):
49
- self.SCOPES = ['https://www.googleapis.com/auth/youtube.upload']
50
- self.youtube = None
51
- self.authenticated = False
52
-
53
- def setup_credentials(self, client_secrets_content):
54
- """设置 Google Cloud 凭据"""
55
- try:
56
- # 使用你已有的凭据
57
- credentials_data = {
58
- "installed": {
59
- "client_id": "288005932157-k7dhq8c7rgmt8km0nm50ntuif9lse7j1.apps.googleusercontent.com",
60
- "project_id": "corded-palisade-458105-p7",
61
- "auth_uri": "https://accounts.google.com/o/oauth2/auth",
62
- "token_uri": "https://oauth2.googleapis.com/token",
63
- "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
64
- "client_secret": "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",
65
- "redirect_uris": ["http://localhost"]
66
- }
67
- }
68
-
69
- # 保存到临时文件
70
- temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False)
71
- json.dump(credentials_data, temp_file)
72
- temp_file.close()
73
-
74
- return temp_file.name
75
- except Exception as e:
76
- return None
77
-
78
- def generate_upload_script(self, video_titles):
79
- """生成 YouTube 上传脚本"""
80
-
81
- script_template = f'''#!/usr/bin/env python3
82
- """
83
- pyTovideo2 YouTube 自动上传脚本
84
- 使用说明:
85
- 1. pip install google-api-python-client google-auth-oauthlib
86
- 2. 将此脚本与你的视频文件放在同一目录
87
- 3. 运行:python youtube_upload_script.py
88
- """
89
-
90
- import os
91
- import pickle
92
- import json
93
- from googleapiclient.discovery import build
94
- from googleapiclient.http import MediaFileUpload
95
- from google_auth_oauthlib.flow import InstalledAppFlow
96
- from google.auth.transport.requests import Request
97
-
98
- # Google Cloud 凭据(已配置好)
99
- CLIENT_SECRETS = {{
100
- "installed": {{
101
- "client_id": "288005932157-k7dhq8c7rgmt8km0nm50ntuif9lse7j1.apps.googleusercontent.com",
102
- "project_id": "corded-palisade-458105-p7",
103
- "auth_uri": "https://accounts.google.com/o/oauth2/auth",
104
- "token_uri": "https://oauth2.googleapis.com/token",
105
- "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
106
- "client_secret": "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",
107
- "redirect_uris": ["http://localhost"]
108
- }}
109
- }}
110
-
111
  class YouTubeUploader:
112
  def __init__(self):
113
  self.SCOPES = ['https://www.googleapis.com/auth/youtube.upload']
114
  self.youtube = None
115
-
116
- # 保存凭据文件
117
- with open('client_secrets.json', 'w') as f:
118
- json.dump(CLIENT_SECRETS, f)
119
 
120
  def authenticate(self):
121
- """认证 YouTube API"""
122
- credentials = None
123
-
124
- if os.path.exists('youtube_token.pickle'):
125
- with open('youtube_token.pickle', 'rb') as token:
126
- credentials = pickle.load(token)
127
-
128
- if not credentials or not credentials.valid:
129
- if credentials and credentials.expired and credentials.refresh_token:
130
- credentials.refresh(Request())
131
  else:
132
- flow = InstalledAppFlow.from_client_secrets_file(
133
- 'client_secrets.json', self.SCOPES)
134
- credentials = flow.run_local_server(port=0)
135
-
136
- with open('youtube_token.pickle', 'wb') as token:
137
- pickle.dump(credentials, token)
138
-
139
- self.youtube = build('youtube', 'v3', credentials=credentials)
140
  return True
141
 
142
  def upload_video(self, video_path, title, description="", tags="", privacy="private"):
143
- """上传视频到 YouTube"""
144
- body = {{
145
- 'snippet': {{
 
 
146
  'title': title,
147
  'description': description,
148
  'tags': tags.split(',') if tags else [],
149
- 'categoryId': '22'
150
- }},
151
- 'status': {{
152
- 'privacyStatus': privacy
153
- }}
154
- }}
155
-
156
- media = MediaFileUpload(video_path, resumable=True)
157
- request = self.youtube.videos().insert(
158
- part=','.join(body.keys()),
159
- body=body,
160
- media_body=media
161
- )
162
-
163
  response = request.execute()
164
- return f"https://www.youtube.com/watch?v={{response['id']}}"
165
-
166
- def main():
167
- """主程序"""
168
- print("🎬 pyTovideo2 YouTube 上传工具")
169
- print("=" * 40)
170
-
171
- uploader = YouTubeUploader()
172
-
173
- if not uploader.authenticate():
174
- print("❌ 认证失败")
175
- return
176
-
177
- print("✅ YouTube 认证成功!")
178
-
179
- # 要上传的视频列表
180
- videos_info = ['''
181
-
182
- for i, title in enumerate(video_titles, 1):
183
- script_template += f'''
184
- {{
185
- "file": "混剪视频_{i:02d}.mp4",
186
- "title": "{title}",
187
- "description": """🎬 使用 pyTovideo2 制作的精彩混剪视频
188
-
189
- ✨ 制作特色:
190
- • 智能视频切片处理
191
- • 音乐节拍完美同步
192
- • 创意片段随机组合
193
- • 镜像翻转防重复检测
194
-
195
- 💡 工具优势:
196
- • 批量处理高效率
197
- • 支持多种视频格式
198
- • 一键生成多个版本
199
- • 完美适配各大平台
200
-
201
- 👍 如果喜欢请:
202
- • 点赞支持这个项目
203
- • 订阅获取更多内容
204
- • 分享给感兴趣的朋友
205
- • 评论交流制作心得
206
-
207
- 🔗 项目开源地址: https://github.com/BassZou/pyToVideo2
208
-
209
- #短视频 #混剪 #Python #自动化 #pyTovideo2 #视频制作""",
210
- "tags": "短视频,混剪,Python,自动化,pyTovideo2,视频制作,内容创作",
211
- "privacy": "private" # private, unlisted, public
212
- }},'''
213
-
214
- script_template += f'''
215
- ]
216
-
217
- # 批量上传
218
- print(f"🚀 开始批量上传 {{len(videos_info)}} 个视频...")
219
-
220
- for i, video_info in enumerate(videos_info, 1):
221
- if os.path.exists(video_info['file']):
222
- print(f"\\n--- 上传第 {{i}}/{{len(videos_info)}} 个视频 ---")
223
- print(f"📹 文件: {{video_info['file']}}")
224
-
225
- try:
226
- url = uploader.upload_video(
227
- video_path=video_info['file'],
228
- title=video_info['title'],
229
- description=video_info['description'],
230
- tags=video_info['tags'],
231
- privacy=video_info['privacy']
232
- )
233
- print(f"✅ 上传成功: {{url}}")
234
- except Exception as e:
235
- print(f"❌ 上传失败: {{e}}")
236
- else:
237
- print(f"❌ 文件不存在: {{video_info['file']}}")
238
-
239
- print("\\n🎉 批量上传完成!")
240
-
241
- if __name__ == "__main__":
242
- main()
243
- '''
244
-
245
- return script_template
246
 
 
247
  def generate_ai_analysis(video_count, file_names):
248
- """使用 AI 生成视频分析"""
249
  if not OPENAI_AVAILABLE:
250
- return f"""
251
- 📊 **视频分析报告**(基于文件信息)
252
-
253
- 📹 **内容预测**:
254
- 根据上传的 {video_count} 个视频文件,预测内容类型:
255
- • 生活记录类:适合温馨背景音乐
256
- • 技能展示类:适合节奏感强的音乐
257
- • 风景旅行类��适合轻松舒缓音乐
258
- • 娱乐搞笑类:适合活泼欢快音乐
259
-
260
- 🎵 **音乐建议**:
261
- • 节拍:120-140 BPM 最适合短视频
262
- • 风格:流行、电子、轻音乐
263
- • 时长:与最终视频长度匹配
264
-
265
- ✂️ **剪辑策略**:
266
- • 快切:适合动感内容,2-3秒一切
267
- • 慢节奏:适合情感内容,4-5秒一切
268
- • 混合节奏:根据音乐变化调整
269
-
270
- 🎯 **平台优化**:
271
- • 抖音:竖屏 9:16,15-60秒
272
- • 小红书:方形 1:1,15-90秒
273
- • YouTube:横屏 16:9,3-10分钟
274
- """
275
-
276
  try:
277
- file_info = ", ".join([f"'{name}'" for name in file_names[:3]]) # 只取前3个文件名
278
-
279
- prompt = f"""作为专业视频分析师,基于用户上传的 {video_count} 个视频文件(文件名包括:{file_info}等),请提供详细分析:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
 
281
- 1. 📹 内容类型推测和特点分析
282
- 2. 🎵 最佳背景音乐风格和节拍建议
283
- 3. ✂️ 推荐的剪辑策略和转场效果
284
- 4. 🎯 目标受
 
 
 
 
 
1
  import os
2
+ import gradio as gr
3
  import tempfile
 
4
  import random
5
  from datetime import datetime
 
6
 
7
+ # AI 导入
8
  try:
9
  from openai import OpenAI
10
  client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) if os.getenv("OPENAI_API_KEY") else None
 
13
  OPENAI_AVAILABLE = False
14
  client = None
15
 
16
+ # 视频处理导入(MoviePy)
17
  try:
18
  from moviepy.editor import VideoFileClip, concatenate_videoclips, AudioFileClip, vfx
19
  MOVIEPY_AVAILABLE = True
20
  except ImportError:
21
  MOVIEPY_AVAILABLE = False
 
22
 
23
+ # YouTube API 导入
24
  try:
25
  from googleapiclient.discovery import build
26
  from googleapiclient.http import MediaFileUpload
 
31
  except ImportError:
32
  YOUTUBE_AVAILABLE = False
33
 
 
34
  MAX_VIDEO_SIZE = 100 * 1024 * 1024 # 100MB
35
  MAX_AUDIO_SIZE = 50 * 1024 * 1024 # 50MB
36
  MAX_TOTAL_SIZE = 500 * 1024 * 1024 # 500MB
37
 
38
+ # YouTube 上传类
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  class YouTubeUploader:
40
  def __init__(self):
41
  self.SCOPES = ['https://www.googleapis.com/auth/youtube.upload']
42
  self.youtube = None
43
+ self.credentials_file = 'youtube_token.pickle'
44
+ self.client_secrets_file = 'client_secrets.json'
 
 
45
 
46
  def authenticate(self):
47
+ creds = None
48
+ if os.path.exists(self.credentials_file):
49
+ with open(self.credentials_file, 'rb') as token:
50
+ creds = pickle.load(token)
51
+ if not creds or not creds.valid:
52
+ if creds and creds.expired and creds.refresh_token:
53
+ creds.refresh(Request())
 
 
 
54
  else:
55
+ flow = InstalledAppFlow.from_client_secrets_file(self.client_secrets_file, self.SCOPES)
56
+ creds = flow.run_local_server(port=0)
57
+ with open(self.credentials_file, 'wb') as token:
58
+ pickle.dump(creds, token)
59
+ self.youtube = build('youtube', 'v3', credentials=creds)
 
 
 
60
  return True
61
 
62
  def upload_video(self, video_path, title, description="", tags="", privacy="private"):
63
+ if not os.path.exists(video_path):
64
+ return {"success": False, "error": "视频文件不存在"}
65
+
66
+ body = {
67
+ 'snippet': {
68
  'title': title,
69
  'description': description,
70
  'tags': tags.split(',') if tags else [],
71
+ 'categoryId': '22' # People & Blogs
72
+ },
73
+ 'status': {
74
+ 'privacyStatus': privacy,
75
+ 'selfDeclaredMadeForKids': False
76
+ }
77
+ }
78
+ media = MediaFileUpload(video_path, resumable=True, mimetype='video/*')
79
+ request = self.youtube.videos().insert(part=','.join(body.keys()), body=body, media_body=media)
 
 
 
 
 
80
  response = request.execute()
81
+ video_id = response.get('id')
82
+ video_url = f"https://www.youtube.com/watch?v={video_id}"
83
+ return {"success": True, "video_id": video_id, "url": video_url}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
85
+ # AI 视频分析
86
  def generate_ai_analysis(video_count, file_names):
 
87
  if not OPENAI_AVAILABLE:
88
+ return "⚠️ 未配置 OpenAI,无法生成 AI 分析。"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  try:
90
+ file_info = ", ".join([f"'{name}'" for name in file_names[:3]])
91
+ prompt = f"""作为专业视频分析师,基于用户上传的 {video_count} 个视频文件(文件名包括:{file_info} 等),请提供详细分析:
92
+ 1. 内容类型推测和特点
93
+ 2. 最佳背景音乐风格和节拍
94
+ 3. 推荐剪辑策略和转场效果
95
+ 4. 目标受众分析
96
+
97
+ 请用中文回答,格式清晰。"""
98
+ response = client.chat.completions.create(
99
+ model="gpt-4o-mini",
100
+ messages=[
101
+ {"role": "system", "content": "你是专业的视频内容分析专家。"},
102
+ {"role": "user", "content": prompt}
103
+ ],
104
+ max_tokens=600,
105
+ temperature=0.7
106
+ )
107
+ return response.choices[0].message.content
108
+ except Exception as e:
109
+ return f"❌ AI 分析失败:{str(e)}"
110
+
111
+ # 视频文件大小检查
112
+ def check_filesize(video_files, audio_file):
113
+ total_size = 0
114
+ for v in video_files:
115
+ size = os.path.getsize(v.name)
116
+ if size > MAX_VIDEO_SIZE:
117
+ return False, f"单个视频文件 {os.path.basename(v.name)} 过大 {size/(1024*1024):.1f}MB,最大限制为 100MB"
118
+ total_size += size
119
+ if audio_file:
120
+ audio_size = os.path.getsize(audio_file.name)
121
+ if audio_size > MAX_AUDIO_SIZE:
122
+ return False, f"音频文件 {os.path.basename(audio_file.name)} 过大 {audio_size/(1024*1024):.1f}MB,最大限制为 50MB"
123
+ total_size += audio_size
124
+ if total_size > MAX_TOTAL_SIZE:
125
+ return False, f"上传文件总大小过大 {total_size/(1024*1024):.1f}MB,最大限制为 500MB"
126
+ return True, ""
127
+
128
+ # 视频处理示范(简化,未完整实现)
129
+ def process_videos(video_files, audio_file, clip_duration=2, num_output=3):
130
+ ok, msg = check_filesize(video_files, audio_file)
131
+ if not ok:
132
+ return msg, [], ""
133
+ # 这里可以使用 MoviePy 处理视频,当前仅模拟
134
+ out_files = [] # 真实项目中应为生成的视频路径列表
135
+ return "✅ 视频上传成功,处理已完成(演示模式,不生成文件)", out_files, ""
136
+
137
+ # Gradio 主界面
138
+ def main_app(video_files, audio_file, clip_duration, num_output, upload_yt, yt_privacy):
139
+ status, outfiles, _ = process_videos(video_files, audio_file, clip_duration, num_output)
140
+ file_names = [os.path.basename(f.name) for f in (video_files or [])]
141
+ ai_analysis = generate_ai_analysis(len(video_files or []), file_names) if OPENAI_AVAILABLE else "未启用 AI 分析功能"
142
+
143
+ yt_upload_res = ""
144
+ if upload_yt:
145
+ if not YOUTUBE_AVAILABLE:
146
+ yt_upload_res = "⚠️ 未安装 YouTube API 库,上传功能不可用"
147
+ elif not os.path.exists("client_secrets.json"):
148
+ yt_upload_res = "⚠️ client_secrets.json 文件缺失,无法上传"
149
+ else:
150
+ uploader = YouTubeUploader()
151
+ auth_ok = uploader.authenticate()
152
+ if not auth_ok:
153
+ yt_upload_res = "❌ YouTube 认证失败"
154
+ else:
155
+ # 这里示范上传已生成文件,示例为空,需替换为真实文件路径
156
+ yt_upload_res = "上传功能暂未实现,请在本地运行上传脚本"
157
+
158
+ return status, ai_analysis, yt_upload_res
159
+
160
+ with gr.Blocks() as demo:
161
+ gr.Markdown("## pyTovideo2 视频处理 + YouTube 上传示范")
162
+ video_files = gr.File(file_types=[".mp4", ".mov", ".avi"], label="上传视频文件(s)", file_count="multiple")
163
+ audio_file = gr.File(file_types=[".mp3", ".wav", ".m4a"], label="上传音频文件", file_count="single")
164
+ clip_duration = gr.Number(value=2, label="切片时长 (秒)", minimum=1, maximum=10)
165
+ num_output = gr.Number(value=3, label="输出视频数量", minimum=1, maximum=10)
166
+ upload_yt = gr.Checkbox(label="处理后自动上传至 YouTube")
167
+ yt_privacy = gr.Dropdown(choices=["public", "private", "unlisted"], value="private", label="YouTube 视频隐私状态")
168
+ btn = gr.Button("开始处理")
169
+ status_out = gr.Textbox(label="处理状态", lines=4)
170
+ ai_out = gr.Textbox(label="AI 内容分析", lines=12)
171
+ yt_out = gr.Textbox(label="YouTube 上传结果", lines=8)
172
+
173
+ btn.click(main_app, inputs=[video_files, audio_file, clip_duration, num_output, upload_yt, yt_privacy],
174
+ outputs=[status_out, ai_out, yt_out])
175
 
176
+ demo.launch()