309 lines
10 KiB
HTML
309 lines
10 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>08 Media - Momentry API Docs</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; padding: 40px; }
|
||
.container { max-width: 960px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); padding: 40px; }
|
||
h1 { font-size: 24px; margin: 24px 0 12px; }
|
||
h2 { font-size: 20px; margin: 20px 0 10px; color: #222; }
|
||
h3 { font-size: 16px; margin: 16px 0 8px; color: #444; }
|
||
p { line-height: 1.6; margin: 8px 0; }
|
||
table { border-collapse: collapse; width: 100%; margin: 12px 0; font-size: 14px; }
|
||
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
|
||
th { background: #f0f0f0; font-weight: 600; }
|
||
code { background: #f0f0f0; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
|
||
pre { background: #f8f8f8; border: 1px solid #ddd; border-radius: 6px; padding: 12px; overflow-x: auto; margin: 12px 0; }
|
||
pre code { background: none; padding: 0; }
|
||
a { color: #0066cc; }
|
||
.back { display: inline-block; margin-bottom: 20px; color: #666; }
|
||
.back:hover { color: #333; }
|
||
.topbar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
|
||
.logout-btn { font-size: 13px; color: #999; text-decoration: none; }
|
||
.logout-btn:hover { color: #cc0000; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="topbar">
|
||
<a class="back" href="index.html">← Back to index</a>
|
||
<a class="logout-btn" href="#" onclick="fetch('/api/v1/auth/logout',{method:'POST'}).then(()=>window.location.reload());return false">Logout</a>
|
||
</div>
|
||
<!-- module: media -->
|
||
<!-- description: Video streaming & frame extraction -->
|
||
<!-- depends: 01_auth -->
|
||
|
||
<h2>Video Streaming & Frame Extraction</h2>
|
||
<p>All video streaming endpoints support the following common query parameters:</p>
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th>Field</th>
|
||
<th>Type</th>
|
||
<th>Required</th>
|
||
<th>Default</th>
|
||
<th>Description</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>mode</code></td>
|
||
<td>string</td>
|
||
<td>No</td>
|
||
<td><code>normal</code></td>
|
||
<td><code>normal</code> or <code>debug</code> (draws detection overlays)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>audio</code></td>
|
||
<td>string</td>
|
||
<td>No</td>
|
||
<td><code>on</code></td>
|
||
<td><code>on</code> or <code>off</code></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<hr />
|
||
<h3><code>GET /api/v1/file/:file_uuid/video</code></h3>
|
||
<p>Stream the full video file with range support for seeking.</p>
|
||
<p><strong>Auth</strong>: Required
|
||
<strong>Scope</strong>: file-level</p>
|
||
<h4>Response</h4>
|
||
<ul>
|
||
<li><strong>200</strong>: Video stream (<code>Content-Type</code> based on file extension)</li>
|
||
<li><strong>206</strong>: Partial content (range request)</li>
|
||
<li>Supports <code>Range</code> header for seeking</li>
|
||
</ul>
|
||
<hr />
|
||
<h3><code>GET /api/v1/file/:file_uuid/trace/:trace_id/video</code></h3>
|
||
<p>Stream video with highlights for a specific face trace (follows a single person across frames with bounding box overlay).</p>
|
||
<p><strong>Auth</strong>: Required
|
||
<strong>Scope</strong>: file-level</p>
|
||
<hr />
|
||
<h3><code>GET /api/v1/file/:file_uuid/video/bbox</code></h3>
|
||
<p>Stream video with bounding box overlay for all detected objects/faces.</p>
|
||
<p><strong>Auth</strong>: Required
|
||
<strong>Scope</strong>: file-level</p>
|
||
<p>Uses a built-in 5×7 bitmap font renderer to draw labels directly on video frames via FFmpeg <code>drawtext</code> filter.</p>
|
||
<hr />
|
||
<h3><code>GET /api/v1/file/:file_uuid/thumbnail</code></h3>
|
||
<p>Extract a single frame from a video as JPEG image. Uses FFmpeg <code>select</code> filter.</p>
|
||
<p><strong>Auth</strong>: Required
|
||
<strong>Scope</strong>: file-level</p>
|
||
<h4>Query Parameters</h4>
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th>Field</th>
|
||
<th>Type</th>
|
||
<th>Required</th>
|
||
<th>Default</th>
|
||
<th>Description</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>frame</code></td>
|
||
<td>integer</td>
|
||
<td>Yes</td>
|
||
<td>—</td>
|
||
<td>Zero-based frame number to extract</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>x</code></td>
|
||
<td>integer</td>
|
||
<td>No</td>
|
||
<td>—</td>
|
||
<td>Crop start X (left edge). Requires <code>y</code>, <code>w</code>, <code>h</code>.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>y</code></td>
|
||
<td>integer</td>
|
||
<td>No</td>
|
||
<td>—</td>
|
||
<td>Crop start Y (top edge). Requires <code>x</code>, <code>w</code>, <code>h</code>.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>w</code></td>
|
||
<td>integer</td>
|
||
<td>No</td>
|
||
<td>—</td>
|
||
<td>Crop width in pixels. Requires <code>x</code>, <code>y</code>, <code>h</code>.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>h</code></td>
|
||
<td>integer</td>
|
||
<td>No</td>
|
||
<td>—</td>
|
||
<td>Crop height in pixels. Requires <code>x</code>, <code>y</code>, <code>w</code>.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>All four crop params (<code>x</code>, <code>y</code>, <code>w</code>, <code>h</code>) must be provided together or omitted.</p>
|
||
<h4>Example</h4>
|
||
<div class="codehilite"><pre><span></span><code><span class="c1"># Extract frame 1000 (full frame)</span>
|
||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/thumbnail?frame=1000"</span><span class="w"> </span><span class="se">\</span>
|
||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>frame_1000.jpg
|
||
|
||
<span class="c1"># Extract and crop face region (x=320, y=240, w=160, h=160)</span>
|
||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/thumbnail?frame=1000&x=320&y=240&w=160&h=160"</span><span class="w"> </span><span class="se">\</span>
|
||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>face_crop.jpg
|
||
</code></pre></div>
|
||
|
||
<h4>Response</h4>
|
||
<ul>
|
||
<li><strong>200</strong>: <code>image/jpeg</code> binary data</li>
|
||
<li><strong>404</strong>: File not found</li>
|
||
<li><strong>500</strong>: FFmpeg error (e.g., frame number exceeds video duration)</li>
|
||
</ul>
|
||
<h3><code>GET /api/v1/file/:file_uuid/clip</code></h3>
|
||
<p>Extract a video clip (time range) as MPEG-TS stream. Uses FFmpeg <code>-ss</code> fast seek.</p>
|
||
<p><strong>Auth</strong>: Required
|
||
<strong>Scope</strong>: file-level</p>
|
||
<h4>Query Parameters</h4>
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th>Field</th>
|
||
<th>Type</th>
|
||
<th>Required</th>
|
||
<th>Default</th>
|
||
<th>Description</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>start_frame</code></td>
|
||
<td>integer</td>
|
||
<td>No*</td>
|
||
<td>—</td>
|
||
<td>Start frame (zero-based). <strong>Frame-accurate</strong> — use this for precision.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>end_frame</code></td>
|
||
<td>integer</td>
|
||
<td>No*</td>
|
||
<td>—</td>
|
||
<td>End frame (zero-based, inclusive). Requires <code>start_frame</code>.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>start_time</code></td>
|
||
<td>float</td>
|
||
<td>No*</td>
|
||
<td>—</td>
|
||
<td>Start time in seconds. Approximate (FPS-dependent). Fallback if frames not given.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>end_time</code></td>
|
||
<td>float</td>
|
||
<td>No*</td>
|
||
<td>—</td>
|
||
<td>End time in seconds. Approximate (FPS-dependent). Fallback if frames not given.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>fps</code></td>
|
||
<td>float</td>
|
||
<td>No</td>
|
||
<td>video FPS</td>
|
||
<td>Override frames-per-second for frame↔time calculation. Defaults to video's detected FPS.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>mode</code></td>
|
||
<td>string</td>
|
||
<td>No</td>
|
||
<td><code>normal</code></td>
|
||
<td><code>normal</code> or <code>debug</code> (draws "CLIP" overlay)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>audio</code></td>
|
||
<td>string</td>
|
||
<td>No</td>
|
||
<td><code>on</code></td>
|
||
<td><code>on</code> or <code>off</code></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>Either (<code>start_frame</code>+<code>end_frame</code>) OR (<code>start_time</code>+<code>end_time</code>) must be provided.</p>
|
||
<h4>Example</h4>
|
||
<div class="codehilite"><pre><span></span><code><span class="c1"># Clip by frame range (primary)</span>
|
||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/clip?start_frame=0&end_frame=47"</span><span class="w"> </span><span class="se">\</span>
|
||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>clip.ts
|
||
|
||
<span class="c1"># Clip by time range (fallback)</span>
|
||
curl<span class="w"> </span>-s<span class="w"> </span><span class="s2">"</span><span class="nv">$API</span><span class="s2">/api/v1/file/bd80fec92b0b6963d177a2c55bf713e2/clip?start_time=30&end_time=45"</span><span class="w"> </span><span class="se">\</span>
|
||
<span class="w"> </span>-H<span class="w"> </span><span class="s2">"Authorization: Bearer </span><span class="nv">$JWT</span><span class="s2">"</span><span class="w"> </span>-o<span class="w"> </span>clip.ts
|
||
</code></pre></div>
|
||
|
||
<h4>Response</h4>
|
||
<ul>
|
||
<li><strong>200</strong>: <code>video/mp2t</code> MPEG-TS stream</li>
|
||
<li><strong>400</strong>: Missing/invalid range parameters</li>
|
||
<li><strong>404</strong>: File not found</li>
|
||
<li><strong>500</strong>: FFmpeg error</li>
|
||
</ul>
|
||
<h4>Technical Notes</h4>
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th>Detail</th>
|
||
<th>Value</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><strong>Backend</strong></td>
|
||
<td>FFmpeg (<code>ffmpeg-full</code>)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Seek</strong></td>
|
||
<td><code>-ss</code> before <code>-i</code> (fast keyframe seek)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Format</strong></td>
|
||
<td>MPEG-TS (<code>mpegts</code> muxer, pipe-safe)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Codec</strong></td>
|
||
<td>H.264 + AAC</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Cache</strong></td>
|
||
<td><code>Cache-Control: public, max-age=86400</code> (24h)</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<hr />
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th>Detail</th>
|
||
<th>Value</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><strong>Backend</strong></td>
|
||
<td>FFmpeg (<code>ffmpeg-full</code>)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Filter</strong></td>
|
||
<td><code>select=eq(n\,FRAME)</code> to select frame, optional <code>crop=W:H:X:Y</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Output</strong></td>
|
||
<td>Single JPEG via pipe (<code>image2pipe</code>, <code>mjpeg</code> codec)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Cache</strong></td>
|
||
<td><code>Cache-Control: public, max-age=86400</code> (24h)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Frame number</strong></td>
|
||
<td>Zero-based (<code>frame=0</code> = first frame of video)</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</body>
|
||
</html> |