zhalice2011 commited on
Commit
a4529e6
Β·
verified Β·
1 Parent(s): 0b9e296

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. main.py +136 -50
main.py CHANGED
@@ -1,5 +1,4 @@
1
  import json
2
- import os
3
  import subprocess
4
  import tempfile
5
  from pathlib import Path
@@ -18,6 +17,65 @@ class ProcessRequest(BaseModel):
18
  api_key: str
19
 
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  @app.get("/health")
22
  async def health():
23
  ffmpeg_ok = subprocess.run(
@@ -26,24 +84,62 @@ async def health():
26
  return {"status": "ok", "ffmpeg": ffmpeg_ok}
27
 
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  @app.post("/process")
30
  async def process_video(req: ProcessRequest):
 
31
  tmp_dir = tempfile.mkdtemp()
32
  video_path = Path(tmp_dir) / f"{req.asset_id}.mp4"
33
  cover_path = Path(tmp_dir) / f"{req.asset_id}_cover.jpg"
 
34
 
35
  try:
36
  # 1. Download video
37
- async with httpx.AsyncClient(timeout=120) as client:
38
- async with client.stream("GET", req.video_url) as resp:
39
- if resp.status_code != 200:
40
- raise HTTPException(
41
- status_code=400,
42
- detail=f"Failed to download video: {resp.status_code}",
43
- )
44
- with open(video_path, "wb") as f:
45
- async for chunk in resp.aiter_bytes(chunk_size=65536):
46
- f.write(chunk)
47
 
48
  # 2. Extract metadata with ffprobe
49
  probe_result = subprocess.run(
@@ -73,7 +169,6 @@ async def process_video(req: ProcessRequest):
73
  width = int(video_stream.get("width", 0)) if video_stream else 0
74
  height = int(video_stream.get("height", 0)) if video_stream else 0
75
 
76
- # Parse fps from r_frame_rate (e.g. "30/1")
77
  fps = 0.0
78
  if video_stream and video_stream.get("r_frame_rate"):
79
  parts = video_stream["r_frame_rate"].split("/")
@@ -88,9 +183,9 @@ async def process_video(req: ProcessRequest):
88
  "fps": fps,
89
  }
90
 
91
- # 3. Extract cover frame
92
  seek_time = "1" if duration > 1 else "0"
93
- ffmpeg_result = subprocess.run(
94
  [
95
  "ffmpeg", "-ss", seek_time,
96
  "-i", str(video_path),
@@ -99,8 +194,7 @@ async def process_video(req: ProcessRequest):
99
  ],
100
  capture_output=True,
101
  )
102
- if ffmpeg_result.returncode != 0:
103
- # Fallback to 0s
104
  subprocess.run(
105
  [
106
  "ffmpeg", "-ss", "0",
@@ -111,50 +205,42 @@ async def process_video(req: ProcessRequest):
111
  capture_output=True,
112
  )
113
 
114
- cover_url = None
115
  if cover_path.exists():
116
- # 4. Upload cover to asset-service
117
- async with httpx.AsyncClient(timeout=60) as client:
118
- with open(cover_path, "rb") as f:
119
- upload_resp = await client.post(
120
- f"{req.api_url}/api/assets/upload",
121
- headers={"Authorization": f"Bearer {req.api_key}"},
122
- files={"file": (f"{req.asset_id}_cover.jpg", f, "image/jpeg")},
123
- data={
124
- "type": "image",
125
- "title": f"{req.asset_id}_cover",
126
- "app": "media_processor",
127
- },
128
- )
129
- if upload_resp.status_code in (200, 201):
130
- cover_asset = upload_resp.json().get("asset", {})
131
- cover_id = cover_asset.get("id")
132
- if cover_id:
133
- cover_url = f"{req.api_url}/api/assets/{cover_id}/file"
134
-
135
- # 5. Update original asset with metadata
136
  async with httpx.AsyncClient(timeout=30) as client:
137
- update_body = {
138
- "asset_details": {
139
- "cover_url": cover_url,
140
- "duration": duration,
141
- "width": width,
142
- "height": height,
143
- "media_info": json.dumps(media_info),
144
- }
145
- }
146
  await client.put(
147
  f"{req.api_url}/api/assets/{req.asset_id}",
148
  headers={
149
  "Authorization": f"Bearer {req.api_key}",
150
  "Content-Type": "application/json",
151
  },
152
- json=update_body,
 
 
 
 
 
 
 
 
 
153
  )
154
 
155
  return {
156
  "success": True,
157
  "cover_url": cover_url,
 
158
  "duration": duration,
159
  "width": width,
160
  "height": height,
@@ -166,8 +252,8 @@ async def process_video(req: ProcessRequest):
166
  except Exception as e:
167
  return {"success": False, "error": str(e)}
168
  finally:
169
- # 6. Cleanup
170
- for p in (video_path, cover_path):
171
  if p.exists():
172
  p.unlink()
173
- Path(tmp_dir).rmdir() if Path(tmp_dir).exists() else None
 
 
1
  import json
 
2
  import subprocess
3
  import tempfile
4
  from pathlib import Path
 
17
  api_key: str
18
 
19
 
20
+ class ThumbnailRequest(BaseModel):
21
+ asset_id: str
22
+ file_url: str
23
+ api_url: str
24
+ api_key: str
25
+
26
+
27
+ # ── Helpers ──────────────────────────────────────────────
28
+
29
+
30
+ async def _download_file(url: str, dest: Path):
31
+ """Stream-download a file to disk."""
32
+ async with httpx.AsyncClient(timeout=120) as client:
33
+ async with client.stream("GET", url) as resp:
34
+ if resp.status_code != 200:
35
+ raise HTTPException(
36
+ status_code=400,
37
+ detail=f"Failed to download: {resp.status_code}",
38
+ )
39
+ with open(dest, "wb") as f:
40
+ async for chunk in resp.aiter_bytes(chunk_size=65536):
41
+ f.write(chunk)
42
+
43
+
44
+ def _resize_to_thumbnail(src: Path, dest: Path):
45
+ """Resize image to width=300, height proportional."""
46
+ subprocess.run(
47
+ [
48
+ "ffmpeg", "-i", str(src),
49
+ "-vf", "scale=300:-1",
50
+ "-q:v", "3",
51
+ str(dest),
52
+ ],
53
+ capture_output=True,
54
+ )
55
+
56
+
57
+ async def _upload_image(file_path: Path, filename: str, api_url: str, api_key: str) -> str | None:
58
+ """Upload an image file to asset-service and return its file URL."""
59
+ if not file_path.exists():
60
+ return None
61
+ async with httpx.AsyncClient(timeout=60) as client:
62
+ with open(file_path, "rb") as f:
63
+ resp = await client.post(
64
+ f"{api_url}/api/assets/upload",
65
+ headers={"Authorization": f"Bearer {api_key}"},
66
+ files={"file": (filename, f, "image/jpeg")},
67
+ data={"type": "image", "title": filename, "app": "media_processor"},
68
+ )
69
+ if resp.status_code in (200, 201):
70
+ asset_id = resp.json().get("asset", {}).get("id")
71
+ if asset_id:
72
+ return f"{api_url}/api/assets/{asset_id}/file"
73
+ return None
74
+
75
+
76
+ # ── Endpoints ────────────────────────────────────────────
77
+
78
+
79
  @app.get("/health")
80
  async def health():
81
  ffmpeg_ok = subprocess.run(
 
84
  return {"status": "ok", "ffmpeg": ffmpeg_ok}
85
 
86
 
87
+ @app.post("/thumbnail")
88
+ async def generate_thumbnail(req: ThumbnailRequest):
89
+ """Generate a 300px-wide thumbnail for an image asset."""
90
+ tmp_dir = tempfile.mkdtemp()
91
+ src_path = Path(tmp_dir) / f"{req.asset_id}_src"
92
+ thumb_path = Path(tmp_dir) / f"{req.asset_id}_thumb.jpg"
93
+
94
+ try:
95
+ # 1. Download image
96
+ await _download_file(req.file_url, src_path)
97
+
98
+ # 2. Resize to 300px width
99
+ _resize_to_thumbnail(src_path, thumb_path)
100
+
101
+ # 3. Upload thumbnail
102
+ thumbnail_url = await _upload_image(
103
+ thumb_path, f"{req.asset_id}_thumb.jpg", req.api_url, req.api_key
104
+ )
105
+
106
+ # 4. Update asset with thumbnail_url
107
+ if thumbnail_url:
108
+ async with httpx.AsyncClient(timeout=30) as client:
109
+ await client.put(
110
+ f"{req.api_url}/api/assets/{req.asset_id}",
111
+ headers={
112
+ "Authorization": f"Bearer {req.api_key}",
113
+ "Content-Type": "application/json",
114
+ },
115
+ json={"asset_details": {"thumbnail_url": thumbnail_url}},
116
+ )
117
+
118
+ return {"success": True, "thumbnail_url": thumbnail_url}
119
+
120
+ except HTTPException:
121
+ raise
122
+ except Exception as e:
123
+ return {"success": False, "error": str(e)}
124
+ finally:
125
+ for p in (src_path, thumb_path):
126
+ if p.exists():
127
+ p.unlink()
128
+ if Path(tmp_dir).exists():
129
+ Path(tmp_dir).rmdir()
130
+
131
+
132
  @app.post("/process")
133
  async def process_video(req: ProcessRequest):
134
+ """Extract cover + thumbnail + metadata from a video asset."""
135
  tmp_dir = tempfile.mkdtemp()
136
  video_path = Path(tmp_dir) / f"{req.asset_id}.mp4"
137
  cover_path = Path(tmp_dir) / f"{req.asset_id}_cover.jpg"
138
+ thumb_path = Path(tmp_dir) / f"{req.asset_id}_thumb.jpg"
139
 
140
  try:
141
  # 1. Download video
142
+ await _download_file(req.video_url, video_path)
 
 
 
 
 
 
 
 
 
143
 
144
  # 2. Extract metadata with ffprobe
145
  probe_result = subprocess.run(
 
169
  width = int(video_stream.get("width", 0)) if video_stream else 0
170
  height = int(video_stream.get("height", 0)) if video_stream else 0
171
 
 
172
  fps = 0.0
173
  if video_stream and video_stream.get("r_frame_rate"):
174
  parts = video_stream["r_frame_rate"].split("/")
 
183
  "fps": fps,
184
  }
185
 
186
+ # 3. Extract cover frame (full resolution)
187
  seek_time = "1" if duration > 1 else "0"
188
+ result = subprocess.run(
189
  [
190
  "ffmpeg", "-ss", seek_time,
191
  "-i", str(video_path),
 
194
  ],
195
  capture_output=True,
196
  )
197
+ if result.returncode != 0:
 
198
  subprocess.run(
199
  [
200
  "ffmpeg", "-ss", "0",
 
205
  capture_output=True,
206
  )
207
 
208
+ # 4. Generate 300px thumbnail from cover
209
  if cover_path.exists():
210
+ _resize_to_thumbnail(cover_path, thumb_path)
211
+
212
+ # 5. Upload cover + thumbnail
213
+ cover_url = await _upload_image(
214
+ cover_path, f"{req.asset_id}_cover.jpg", req.api_url, req.api_key
215
+ )
216
+ thumbnail_url = await _upload_image(
217
+ thumb_path, f"{req.asset_id}_thumb.jpg", req.api_url, req.api_key
218
+ )
219
+
220
+ # 6. Update original asset
 
 
 
 
 
 
 
 
 
221
  async with httpx.AsyncClient(timeout=30) as client:
 
 
 
 
 
 
 
 
 
222
  await client.put(
223
  f"{req.api_url}/api/assets/{req.asset_id}",
224
  headers={
225
  "Authorization": f"Bearer {req.api_key}",
226
  "Content-Type": "application/json",
227
  },
228
+ json={
229
+ "asset_details": {
230
+ "cover_url": cover_url,
231
+ "thumbnail_url": thumbnail_url,
232
+ "duration": duration,
233
+ "width": width,
234
+ "height": height,
235
+ "media_info": json.dumps(media_info),
236
+ }
237
+ },
238
  )
239
 
240
  return {
241
  "success": True,
242
  "cover_url": cover_url,
243
+ "thumbnail_url": thumbnail_url,
244
  "duration": duration,
245
  "width": width,
246
  "height": height,
 
252
  except Exception as e:
253
  return {"success": False, "error": str(e)}
254
  finally:
255
+ for p in (video_path, cover_path, thumb_path):
 
256
  if p.exists():
257
  p.unlink()
258
+ if Path(tmp_dir).exists():
259
+ Path(tmp_dir).rmdir()