Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
13 |
-
|
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 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
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 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
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 |
-
|
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 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
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 |
-
|
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 |
-
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|