MakiAi commited on
Commit
cf1639b
·
verified ·
1 Parent(s): 9dc25c6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -249
app.py CHANGED
@@ -1,265 +1,77 @@
1
- import os
2
- import shutil
3
- import tempfile
4
- from datetime import datetime
5
- from typing import List, Tuple
6
-
7
- import gradio as gr
8
  import cv2
 
9
  from PIL import Image
10
  import numpy as np
 
 
11
 
12
- SUPPORTED_EXTS = {".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v", ".mpeg", ".mpg", ".wmv", ".flv"}
13
- MAX_FILE_SIZE_MB = 500 # 最大ファイルサイズ(MB)
14
-
15
-
16
- def _safe_filename(path: str) -> str:
17
- base = os.path.basename(path)
18
- stem, _ = os.path.splitext(base)
19
- stem = "".join(c for c in stem if c.isalnum() or c in ("-", "_"))[:60]
20
- return stem or "video"
21
-
22
-
23
- def check_file_size(file_path: str) -> bool:
24
- """ファイルサイズをチェックする"""
25
- if not os.path.exists(file_path):
26
- return False
27
 
28
- file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
29
- return file_size_mb <= MAX_FILE_SIZE_MB
30
-
31
-
32
- def extract_frames(files: List[str]) -> Tuple[List[str], List[str], List[Tuple[str, Image.Image]], str]:
33
- if not files:
34
- return [], [], [], "⚠️ ファイルがアップロードされていません。"
35
-
36
- workdir = tempfile.mkdtemp(prefix="frames_")
37
- out_images, out_zips, gallery_items = [], [], []
38
- status_messages = []
39
-
40
- for path in files:
41
- if not path or not os.path.exists(path):
42
- status_messages.append(f"⚠️ ファイルが見つかりません: {os.path.basename(path)}")
43
- continue
44
-
45
- # ファイルサイズチェック
46
- if not check_file_size(path):
47
- file_size_mb = os.path.getsize(path) / (1024 * 1024)
48
- status_messages.append(f"❌ ファイルサイズが大きすぎます ({file_size_mb:.1f}MB > {MAX_FILE_SIZE_MB}MB): {os.path.basename(path)}")
49
- continue
50
-
51
- _, ext = os.path.splitext(path)
52
- if ext.lower() not in SUPPORTED_EXTS:
53
- status_messages.append(f"⚠️ サポートされていない形式: {os.path.basename(path)}")
54
- continue
55
-
56
- safe = _safe_filename(path)
57
- vid_dir = os.path.join(workdir, safe)
58
- os.makedirs(vid_dir, exist_ok=True)
59
-
60
- try:
61
- # OpenCVでビデオを開く
62
- cap = cv2.VideoCapture(path)
63
-
64
- if not cap.isOpened():
65
- status_messages.append(f"❌ ビデオファイルを開けませんでした: {safe}")
66
- continue
67
-
68
- # ビデオ情報を取得
69
- fps = cap.get(cv2.CAP_PROP_FPS)
70
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
71
-
72
- print(f"Processing {safe}: {total_frames} frames at {fps:.2f} fps")
73
-
74
- # 最初のフレームを取得
75
- ret, first_frame = cap.read()
76
- if not ret:
77
- status_messages.append(f"❌ 最初のフレームを読み込めませんでした: {safe}")
78
- cap.release()
79
- continue
80
-
81
- # BGRからRGBに変換
82
- first_frame_rgb = cv2.cvtColor(first_frame, cv2.COLOR_BGR2RGB)
83
- img_first = Image.fromarray(first_frame_rgb)
84
-
85
- # 最後のフレームを取得
86
- img_last = img_first # デフォルト値
87
-
88
- if total_frames > 1:
89
- # 複数の試行で最後のフレームを取得
90
- for offset in range(min(10, total_frames)):
91
- frame_pos = max(0, total_frames - 1 - offset)
92
- cap.set(cv2.CAP_PROP_POS_FRAMES, frame_pos)
93
- ret, last_frame = cap.read()
94
- if ret:
95
- last_frame_rgb = cv2.cvtColor(last_frame, cv2.COLOR_BGR2RGB)
96
- img_last = Image.fromarray(last_frame_rgb)
97
- print(f"Got last frame at position {frame_pos}")
98
- break
99
- else:
100
- print(f"Warning: Could not read last frame, using first frame instead")
101
-
102
  cap.release()
103
-
104
- # 画像を保存
105
- ts = datetime.now().strftime("%Y%m%d_%H%M%S")
106
- first_path = os.path.join(vid_dir, f"{safe}_first_{ts}.png")
107
- last_path = os.path.join(vid_dir, f"{safe}_last_{ts}.png")
108
-
109
- # PNG品質を調整してファイルサイズを抑制
110
- img_first.save(first_path, "PNG", optimize=True)
111
- img_last.save(last_path, "PNG", optimize=True)
112
-
113
- out_images.extend([first_path, last_path])
114
- gallery_items.extend([
115
- (first_path, f"{safe} FIRST FRAME"),
116
- (last_path, f"{safe} LAST FRAME")
117
- ])
118
-
119
- # ZIPファイルを作成
120
- zip_path = shutil.make_archive(
121
- os.path.join(workdir, f"{safe}_frames_{ts}"),
122
- 'zip',
123
- vid_dir
124
- )
125
- out_zips.append(zip_path)
126
-
127
- status_messages.append(f"✅ 処理成功: {safe}")
128
-
129
- except Exception as e:
130
- status_messages.append(f"❌ エラーが発生しました ({safe}): {str(e)}")
131
- print(f"Error processing {path}: {str(e)}")
132
- import traceback
133
- traceback.print_exc()
134
- continue
135
-
136
- # ステータスメッセージをまとめる
137
- if gallery_items:
138
- final_status = f"✅ {len(gallery_items)//2}個のビデオから正常にフレームを抽出しました。\n"
139
- else:
140
- final_status = "⚠️ フレームを抽出できませんでした。\n"
141
-
142
- if status_messages:
143
- final_status += "\n".join(status_messages)
144
-
145
- return out_images, out_zips, gallery_items, final_status
146
-
147
 
148
  # Gradioインターフェース
149
- with gr.Blocks(
150
- title="First & Last Frame Extractor",
151
- theme=gr.themes.Soft(),
152
- analytics_enabled=False, # アナリティクスを無効化
153
- css="""
154
- .gradio-container {
155
- max-width: 1200px !important;
156
- margin: auto !important;
157
- }
158
- """
159
- ) as demo:
160
- gr.Markdown(f"""
161
- # 🎞️ First & Last Frame Extractor
162
- Upload one or more videos to extract the **first** and **last** frame.
163
 
164
- **Supported formats:** MP4, MOV, AVI, MKV, WebM, M4V, MPEG, MPG, WMV, FLV
165
- **Maximum file size:** {MAX_FILE_SIZE_MB}MB per file
166
- """)
167
-
168
- with gr.Row():
169
- with gr.Column():
170
- video_files = gr.Files(
171
- label="Upload videos",
172
- file_count="multiple",
173
- file_types=["video"],
174
- type="filepath"
175
- )
176
- run_btn = gr.Button("🎬 Extract Frames", variant="primary", size="lg")
177
-
178
  with gr.Row():
179
- with gr.Tab("📸 Preview"):
180
- gallery = gr.Gallery(
181
- label="Extracted Frames",
182
- columns=2,
183
- rows=2,
184
- height=500,
185
- object_fit="contain",
186
- show_label=True
187
- )
188
-
189
- with gr.Tab("💾 Downloads"):
190
- with gr.Column():
191
- gr.Markdown("### Individual Images")
192
- images_out = gr.Files(label="PNG files", file_count="multiple")
193
- gr.Markdown("### ZIP Archives")
194
- zips_out = gr.Files(label="ZIP files (contains both frames per video)", file_count="multiple")
195
-
196
- # ステータスメッセージ
197
- status_msg = gr.Markdown("")
198
-
199
- def process_videos(files):
200
- if not files:
201
- return [], [], [], "⚠️ 少なくとも1つのビデオファイルをアップロードしてください。"
202
 
203
- try:
204
- imgs, zips, gallery_items, status = extract_frames(files)
205
- return gallery_items, imgs, zips, status
206
- except Exception as e:
207
- return [], [], [], f"❌ エラー: {str(e)}"
208
-
209
- run_btn.click(
210
- fn=process_videos,
211
- inputs=[video_files],
212
- outputs=[gallery, images_out, zips_out, status_msg]
213
- )
214
 
215
- # サンプル説明
216
- gr.Markdown(f"""
217
- ---
218
- ### 📝 使用方法:
219
- 1. ファイルアップローダーを使用して1つ以上のビデオファイルをアップロード
220
- 2. "Extract Frames"ボタンをクリック
221
- 3. Previewタブで抽出されたフレームを確認
222
- 4. Downloadsタブから個別画像またはZIPアーカイブをダウンロード
223
 
224
- ### ⚡ 機能:
225
- - 各ビデオの最初と最後のフレームを抽出
226
- - 複数のビデオ形式をサポート
227
- - 複数ビデオの一括処理
228
- - 個別PNGまたはZIPアーカイブとしてダウンロード
229
- - ファイルサイズ制限: {MAX_FILE_SIZE_MB}MB
230
 
231
- ### ⚠️ 注意事項:
232
- - 大きなファイルはアップロード前にサイズを確認してください
233
- - ネットワーク環境によってはアップロードに時間がかかる場合があります
234
- """)
 
235
 
236
  if __name__ == "__main__":
237
- # Pydanticバージョン問題の回避策
238
- try:
239
- import pydantic
240
- print(f"Pydantic version: {pydantic.__version__}")
241
-
242
- # Pydantic 2.11以降の場合の警告と対処
243
- from packaging import version
244
- if version.parse(pydantic.__version__) >= version.parse("2.11.0"):
245
- print("⚠️ Warning: Pydantic 2.11+ detected. Downgrading to compatible version...")
246
- print("Please run: pip install 'pydantic>=2.5.0,<2.11.0'")
247
-
248
- print(f"Gradio version: {gr.__version__}")
249
-
250
- except ImportError as e:
251
- print(f"Import error: {e}")
252
-
253
- # より安定した設定でlaunch
254
- demo.queue(
255
- max_size=20, # キューサイズを制限
256
- default_concurrency_limit=3 # 並行処理数をより保守的に設定
257
- ).launch(
258
- server_name="0.0.0.0",
259
- server_port=7860,
260
- share=False,
261
- max_file_size="500mb", # Gradioレベルでのファイルサイズ制限
262
- max_threads=8, # スレッド数を調整
263
- show_api=False, # API情報の表示を無効化(schema問題回避)
264
- debug=False # デバッグモードを無効化
265
- )
 
 
 
 
 
 
 
 
1
  import cv2
2
+ import gradio as gr
3
  from PIL import Image
4
  import numpy as np
5
+ import tempfile
6
+ import os
7
 
8
+ def extract_first_last_frames(video_file):
9
+ if video_file is None:
10
+ return None, None, "ビデオファイルを選択してください"
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ try:
13
+ # OpenCVでビデオを開く
14
+ cap = cv2.VideoCapture(video_file)
15
+
16
+ if not cap.isOpened():
17
+ return None, None, "ビデオファイルを開けませんでした"
18
+
19
+ # 最初のフレームを取得
20
+ ret, first_frame = cap.read()
21
+ if not ret:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  cap.release()
23
+ return None, None, "最初のフレームを読み取れませんでした"
24
+
25
+ # BGRからRGBに変換
26
+ first_frame_rgb = cv2.cvtColor(first_frame, cv2.COLOR_BGR2RGB)
27
+ first_image = Image.fromarray(first_frame_rgb)
28
+
29
+ # 総フレーム数を取得
30
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
31
+
32
+ # 最後のフレームを取得
33
+ if total_frames > 1:
34
+ # 最後のフレームに移動
35
+ cap.set(cv2.CAP_PROP_POS_FRAMES, total_frames - 1)
36
+ ret, last_frame = cap.read()
37
+ if ret:
38
+ last_frame_rgb = cv2.cvtColor(last_frame, cv2.COLOR_BGR2RGB)
39
+ last_image = Image.fromarray(last_frame_rgb)
40
+ else:
41
+ last_image = first_image # 最後のフレームが取得できない場合は最初のフレームを使用
42
+ else:
43
+ last_image = first_image # フレームが1つしかない場合
44
+
45
+ cap.release()
46
+
47
+ return first_image, last_image, f"✅ 成功!総フレーム数: {total_frames}"
48
+
49
+ except Exception as e:
50
+ return None, None, f"❌ エラー: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  # Gradioインターフェース
53
+ with gr.Blocks(title="ビデオフレーム抽出") as demo:
54
+ gr.Markdown("# 🎞️ ビデオの最初と最後のフレーム抽出")
 
 
 
 
 
 
 
 
 
 
 
 
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  with gr.Row():
57
+ video_input = gr.File(
58
+ label="ビデオファイルをアップロード",
59
+ file_types=["video"]
60
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ extract_btn = gr.Button("フレームを抽出", variant="primary")
 
 
 
 
 
 
 
 
 
 
63
 
64
+ status = gr.Textbox(label="ステータス", interactive=False)
 
 
 
 
 
 
 
65
 
66
+ with gr.Row():
67
+ first_frame = gr.Image(label="最初のフレーム", type="pil")
68
+ last_frame = gr.Image(label="最後のフレーム", type="pil")
 
 
 
69
 
70
+ extract_btn.click(
71
+ fn=extract_first_last_frames,
72
+ inputs=video_input,
73
+ outputs=[first_frame, last_frame, status]
74
+ )
75
 
76
  if __name__ == "__main__":
77
+ demo.launch()