Actualizar worker de compresión
Browse files- README.md +15 -32
- internal/protocol/messages.go +3 -0
- main.go +53 -8
- web_ui.go +34 -34
README.md
CHANGED
|
@@ -1,46 +1,29 @@
|
|
| 1 |
---
|
| 2 |
-
title: OmniVision AI
|
| 3 |
emoji: 🧠
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
license: mit
|
| 10 |
-
short_description: High-performance neural video optimization engine
|
| 11 |
-
tags:
|
| 12 |
-
- video
|
| 13 |
-
- deep-learning
|
| 14 |
-
- media-processing
|
| 15 |
-
- audio
|
| 16 |
-
- ffmpeg
|
| 17 |
-
- pytorch
|
| 18 |
---
|
| 19 |
|
| 20 |
-
# OmniVision AI —
|
| 21 |
|
| 22 |
-
OmniVision
|
| 23 |
|
| 24 |
-
##
|
| 25 |
|
| 26 |
-
|
| 27 |
-
- **OrientAI Spatial Aspect Scaler**: Real-time orientation detection ensuring both vertical (9:16) and landscape (16:9) streams are scaled with strict even dimensions and zero aspect distortion.
|
| 28 |
-
- **Embedded Subtitle Stream Synchronizer**: Automatic extraction and remuxing of multi-track text subtitles (`srt`, `ass`, `ssa`, `webvtt`) into standard MP4 `mov_text` streams.
|
| 29 |
-
- **Acoustic Speech Intelligibility Enhancer**: Low-bitrate vocal optimization with dynamic mono channel doubling for crystal-clear dialog clarity below 64 kbps.
|
| 30 |
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
|
| 34 |
-
- **Web UI**: Access the root URL `/` to test the interactive neural pipeline benchmark.
|
| 35 |
-
- **`GET /api/v1/health`**: Cluster health status, active worker threads, and memory metrics.
|
| 36 |
-
- **`GET /api/v1/models`**: Active neural model pipelines and media codecs.
|
| 37 |
-
|
| 38 |
-
## Configuration & Environment Variables
|
| 39 |
-
|
| 40 |
-
You can optionally configure cluster synchronization in Space **Settings** -> **Variables and secrets**:
|
| 41 |
-
|
| 42 |
-
| Variable | Description |
|
| 43 |
-
|---|---|
|
| 44 |
-
| `MANAGER_URL` | Optional upstream coordinator endpoint |
|
| 45 |
-
| `WORKER_SECRET` | Authentication token for cluster nodes |
|
| 46 |
-
| `WORK_DIR` | Scratch directory for media tensors |
|
|
|
|
| 1 |
---
|
| 2 |
+
title: OmniVision AI Node
|
| 3 |
emoji: 🧠
|
| 4 |
+
colorFrom: blue
|
| 5 |
colorTo: indigo
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
pinned: false
|
| 9 |
license: mit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# OmniVision AI — Runtime Node
|
| 13 |
|
| 14 |
+
Distributed media runtime node for the OmniVision processing platform. It maintains a secure connection to the configured coordinator, accepts assigned media tasks, and reports live status and resource utilization.
|
| 15 |
|
| 16 |
+
## Space configuration
|
| 17 |
|
| 18 |
+
Set these values under **Settings → Variables and secrets**:
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
+
| Name | Type | Required | Description |
|
| 21 |
+
|---|---|---:|---|
|
| 22 |
+
| `MANAGER_URL` | Variable | Yes | Public URL of the service coordinator |
|
| 23 |
+
| `WORKER_SECRET` | Secret | Yes | Shared authentication secret for this node group |
|
| 24 |
+
| `WORKER_ID` | Variable | Recommended | Unique identifier for this node |
|
| 25 |
+
| `MAX_JOBS` | Variable | No | Maximum simultaneous tasks; defaults to `1` |
|
| 26 |
+
| `SPACE_URL` | Variable | No | Public Space URL used for node identification |
|
| 27 |
+
| `WORK_DIR` | Variable | No | Temporary working directory; defaults to `/tmp/omnivision_media` |
|
| 28 |
|
| 29 |
+
The node requires `MANAGER_URL` and `WORKER_SECRET` and will stop if either is missing. The operations console displays connection status, active tasks, queue depth, and node capacity.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
internal/protocol/messages.go
CHANGED
|
@@ -80,10 +80,13 @@ type TaskAssign struct {
|
|
| 80 |
// TaskProgress es el reporte en vivo que el Worker envía mientras FFmpeg procesa
|
| 81 |
type TaskProgress struct {
|
| 82 |
TaskID string `json:"task_id"`
|
|
|
|
| 83 |
Percent float64 `json:"percent"`
|
| 84 |
Speed string `json:"speed"`
|
| 85 |
Time string `json:"time"`
|
| 86 |
ETA string `json:"eta"`
|
|
|
|
|
|
|
| 87 |
}
|
| 88 |
|
| 89 |
// TaskComplete notifica que el Worker terminó y ya subió el archivo comprimido
|
|
|
|
| 80 |
// TaskProgress es el reporte en vivo que el Worker envía mientras FFmpeg procesa
|
| 81 |
type TaskProgress struct {
|
| 82 |
TaskID string `json:"task_id"`
|
| 83 |
+
Stage string `json:"stage,omitempty"`
|
| 84 |
Percent float64 `json:"percent"`
|
| 85 |
Speed string `json:"speed"`
|
| 86 |
Time string `json:"time"`
|
| 87 |
ETA string `json:"eta"`
|
| 88 |
+
Current int64 `json:"current,omitempty"`
|
| 89 |
+
Total int64 `json:"total,omitempty"`
|
| 90 |
}
|
| 91 |
|
| 92 |
// TaskComplete notifica que el Worker terminó y ya subió el archivo comprimido
|
main.go
CHANGED
|
@@ -2,7 +2,6 @@ package main
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"context"
|
| 5 |
-
"encoding/base64"
|
| 6 |
"encoding/json"
|
| 7 |
"fmt"
|
| 8 |
"io"
|
|
@@ -87,18 +86,14 @@ func main() {
|
|
| 87 |
|
| 88 |
managerURL := os.Getenv("MANAGER_URL")
|
| 89 |
if managerURL == "" {
|
| 90 |
-
|
| 91 |
-
raw, _ := base64.StdEncoding.DecodeString("aHR0cHM6Ly93b3JrZXJzLnVkeWF0LnNpdGU=")
|
| 92 |
-
managerURL = string(raw)
|
| 93 |
} else if !strings.HasPrefix(managerURL, "http://") && !strings.HasPrefix(managerURL, "https://") {
|
| 94 |
managerURL = "https://" + managerURL
|
| 95 |
}
|
| 96 |
|
| 97 |
secret := os.Getenv("WORKER_SECRET")
|
| 98 |
if secret == "" {
|
| 99 |
-
|
| 100 |
-
raw, _ := base64.StdEncoding.DecodeString("YjlkZDFiZDQzMGI0NGIzYjYzYWNiNTFhMjk4MzA0Nzg2MmJkMTdhOWNlYTQ2NmY2YWJkZWViMTM0OTZkZTk5Mg==")
|
| 101 |
-
secret = string(raw)
|
| 102 |
}
|
| 103 |
|
| 104 |
workerID := os.Getenv("WORKER_ID")
|
|
@@ -478,7 +473,7 @@ func (w *Worker) executeTask(taskCtx context.Context, job *CurrentJob, task prot
|
|
| 478 |
return
|
| 479 |
}
|
| 480 |
|
| 481 |
-
_, err =
|
| 482 |
inFile.Close()
|
| 483 |
if err != nil {
|
| 484 |
w.reportFailed(task.TaskID, fmt.Sprintf("error guardando stream de video: %v", err))
|
|
@@ -524,6 +519,7 @@ func (w *Worker) executeTask(taskCtx context.Context, job *CurrentJob, task prot
|
|
| 524 |
}
|
| 525 |
}, func(p protocol.TaskProgress) {
|
| 526 |
p.TaskID = task.TaskID
|
|
|
|
| 527 |
_ = w.sendMessage(protocol.TypeTaskProgress, p)
|
| 528 |
})
|
| 529 |
|
|
@@ -555,6 +551,7 @@ func (w *Worker) executeTask(taskCtx context.Context, job *CurrentJob, task prot
|
|
| 555 |
|
| 556 |
// 4. Subir archivo comprimido al Manager (con reintentos si el Manager está reiniciando)
|
| 557 |
uploadURL := w.resolveURL(task.UploadURL)
|
|
|
|
| 558 |
|
| 559 |
var uploadSuccess bool
|
| 560 |
for attempt := 1; attempt <= 15; attempt++ {
|
|
@@ -620,6 +617,54 @@ func (w *Worker) executeTask(taskCtx context.Context, job *CurrentJob, task prot
|
|
| 620 |
log.Printf("[%s] ✅ Tarea finalizada y confirmada.", task.TaskID)
|
| 621 |
}
|
| 622 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 623 |
// downloadSimpleFile descarga un archivo pequeño del Manager (logo de marca de agua).
|
| 624 |
func (w *Worker) downloadSimpleFile(ctx context.Context, rawURL, destPath string) error {
|
| 625 |
req, err := http.NewRequestWithContext(ctx, "GET", rawURL, nil)
|
|
|
|
| 2 |
|
| 3 |
import (
|
| 4 |
"context"
|
|
|
|
| 5 |
"encoding/json"
|
| 6 |
"fmt"
|
| 7 |
"io"
|
|
|
|
| 86 |
|
| 87 |
managerURL := os.Getenv("MANAGER_URL")
|
| 88 |
if managerURL == "" {
|
| 89 |
+
log.Fatal("MANAGER_URL es obligatorio para conectar este worker al clúster")
|
|
|
|
|
|
|
| 90 |
} else if !strings.HasPrefix(managerURL, "http://") && !strings.HasPrefix(managerURL, "https://") {
|
| 91 |
managerURL = "https://" + managerURL
|
| 92 |
}
|
| 93 |
|
| 94 |
secret := os.Getenv("WORKER_SECRET")
|
| 95 |
if secret == "" {
|
| 96 |
+
log.Fatal("WORKER_SECRET es obligatorio; configura el secreto del clúster en los Secrets del Space")
|
|
|
|
|
|
|
| 97 |
}
|
| 98 |
|
| 99 |
workerID := os.Getenv("WORKER_ID")
|
|
|
|
| 473 |
return
|
| 474 |
}
|
| 475 |
|
| 476 |
+
_, err = w.copyDownloadWithProgress(taskCtx, task.TaskID, inFile, resp.Body, resp.ContentLength)
|
| 477 |
inFile.Close()
|
| 478 |
if err != nil {
|
| 479 |
w.reportFailed(task.TaskID, fmt.Sprintf("error guardando stream de video: %v", err))
|
|
|
|
| 519 |
}
|
| 520 |
}, func(p protocol.TaskProgress) {
|
| 521 |
p.TaskID = task.TaskID
|
| 522 |
+
p.Stage = "compressing"
|
| 523 |
_ = w.sendMessage(protocol.TypeTaskProgress, p)
|
| 524 |
})
|
| 525 |
|
|
|
|
| 551 |
|
| 552 |
// 4. Subir archivo comprimido al Manager (con reintentos si el Manager está reiniciando)
|
| 553 |
uploadURL := w.resolveURL(task.UploadURL)
|
| 554 |
+
_ = w.sendMessage(protocol.TypeTaskProgress, protocol.TaskProgress{TaskID: task.TaskID, Stage: "uploading", Percent: 0})
|
| 555 |
|
| 556 |
var uploadSuccess bool
|
| 557 |
for attempt := 1; attempt <= 15; attempt++ {
|
|
|
|
| 617 |
log.Printf("[%s] ✅ Tarea finalizada y confirmada.", task.TaskID)
|
| 618 |
}
|
| 619 |
|
| 620 |
+
func (w *Worker) copyDownloadWithProgress(ctx context.Context, taskID string, dst io.Writer, src io.Reader, total int64) (int64, error) {
|
| 621 |
+
buf := make([]byte, 512*1024)
|
| 622 |
+
started, lastReport := time.Now(), time.Now()
|
| 623 |
+
var copied int64
|
| 624 |
+
for {
|
| 625 |
+
if err := ctx.Err(); err != nil {
|
| 626 |
+
return copied, err
|
| 627 |
+
}
|
| 628 |
+
n, readErr := src.Read(buf)
|
| 629 |
+
if n > 0 {
|
| 630 |
+
written, writeErr := dst.Write(buf[:n])
|
| 631 |
+
copied += int64(written)
|
| 632 |
+
if writeErr != nil {
|
| 633 |
+
return copied, writeErr
|
| 634 |
+
}
|
| 635 |
+
if written != n {
|
| 636 |
+
return copied, io.ErrShortWrite
|
| 637 |
+
}
|
| 638 |
+
if time.Since(lastReport) >= time.Second || (total > 0 && copied >= total) {
|
| 639 |
+
elapsed := time.Since(started).Seconds()
|
| 640 |
+
pct := float64(0)
|
| 641 |
+
if total > 0 {
|
| 642 |
+
pct = float64(copied) * 100 / float64(total)
|
| 643 |
+
}
|
| 644 |
+
p := protocol.TaskProgress{TaskID: taskID, Stage: "downloading", Percent: pct, Current: copied, Total: total}
|
| 645 |
+
if elapsed > 0 {
|
| 646 |
+
p.Speed = fmt.Sprintf("%.1f MB/s", float64(copied)/elapsed/(1024*1024))
|
| 647 |
+
if total > copied {
|
| 648 |
+
p.ETA = fmt.Sprintf("%ds", int(float64(total-copied)/(float64(copied)/elapsed)))
|
| 649 |
+
}
|
| 650 |
+
}
|
| 651 |
+
_ = w.sendMessage(protocol.TypeTaskProgress, p)
|
| 652 |
+
lastReport = time.Now()
|
| 653 |
+
}
|
| 654 |
+
}
|
| 655 |
+
if readErr == io.EOF {
|
| 656 |
+
break
|
| 657 |
+
}
|
| 658 |
+
if readErr != nil {
|
| 659 |
+
return copied, readErr
|
| 660 |
+
}
|
| 661 |
+
}
|
| 662 |
+
if total > 0 && copied != total {
|
| 663 |
+
return copied, io.ErrUnexpectedEOF
|
| 664 |
+
}
|
| 665 |
+
return copied, nil
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
// downloadSimpleFile descarga un archivo pequeño del Manager (logo de marca de agua).
|
| 669 |
func (w *Worker) downloadSimpleFile(ctx context.Context, rawURL, destPath string) error {
|
| 670 |
req, err := http.NewRequestWithContext(ctx, "GET", rawURL, nil)
|
web_ui.go
CHANGED
|
@@ -90,8 +90,8 @@ func renderAIStudioHTML(cores int) string {
|
|
| 90 |
</div>
|
| 91 |
<div class="metric-card">
|
| 92 |
<div class="metric-label">Perceptual Engine</div>
|
| 93 |
-
<div class="metric-value">
|
| 94 |
-
<div class="metric-sub">
|
| 95 |
</div>
|
| 96 |
<div class="metric-card">
|
| 97 |
<div class="metric-label">Spatial Adapter</div>
|
|
@@ -106,37 +106,37 @@ func renderAIStudioHTML(cores int) string {
|
|
| 106 |
</div>
|
| 107 |
|
| 108 |
<div class="card">
|
| 109 |
-
<div class="card-title">🎬 Interactive Neural
|
| 110 |
-
<div class="card-desc">
|
| 111 |
|
| 112 |
<div class="dropzone" id="dropzone" onclick="selectSample()">
|
| 113 |
<svg viewBox="0 0 24 24"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"/></svg>
|
| 114 |
-
<div style="font-weight: 600; font-size: 1.05rem;" id="fileNameDisplay">Drag and drop
|
| 115 |
-
<div style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0.35rem;">
|
| 116 |
</div>
|
| 117 |
|
| 118 |
<div class="controls-grid">
|
| 119 |
<div class="control-group">
|
| 120 |
-
<label>
|
| 121 |
<input type="range" id="crfSlider" min="18" max="36" value="28" oninput="updateCRF(this.value)">
|
| 122 |
</div>
|
| 123 |
<div class="control-group">
|
| 124 |
-
<label>OrientAI
|
| 125 |
<select id="scaleSelect">
|
| 126 |
<option value="adaptive">OrientAI Adaptive (Auto-Orientation)</option>
|
| 127 |
-
<option value="480p">
|
| 128 |
-
<option value="720p">
|
| 129 |
-
<option value="1080p">
|
| 130 |
-
<option value="original">
|
| 131 |
</select>
|
| 132 |
</div>
|
| 133 |
<div class="control-group">
|
| 134 |
-
<label>Audio
|
| 135 |
<select id="audioSelect">
|
| 136 |
-
<option value="64k">
|
| 137 |
-
<option value="48k">
|
| 138 |
-
<option value="96k">
|
| 139 |
-
<option value="128k" selected>
|
| 140 |
</select>
|
| 141 |
</div>
|
| 142 |
</div>
|
|
@@ -144,16 +144,16 @@ func renderAIStudioHTML(cores int) string {
|
|
| 144 |
<div class="checkbox-group">
|
| 145 |
<label class="checkbox-item">
|
| 146 |
<input type="checkbox" id="checkSubs" checked>
|
| 147 |
-
<span><strong>
|
| 148 |
</label>
|
| 149 |
<label class="checkbox-item">
|
| 150 |
<input type="checkbox" id="checkMono" checked>
|
| 151 |
-
<span><strong>
|
| 152 |
</label>
|
| 153 |
</div>
|
| 154 |
|
| 155 |
<button class="btn" id="runBtn" onclick="runBenchmark()">
|
| 156 |
-
<span>🚀 Run
|
| 157 |
</button>
|
| 158 |
|
| 159 |
<div class="progress-bar-wrap" id="progWrap">
|
|
@@ -164,16 +164,16 @@ func renderAIStudioHTML(cores int) string {
|
|
| 164 |
|
| 165 |
<div class="features-grid">
|
| 166 |
<div class="feature-card">
|
| 167 |
-
<h3>📐 OrientAI
|
| 168 |
-
<p>Detects
|
| 169 |
</div>
|
| 170 |
<div class="feature-card">
|
| 171 |
-
<h3>💬
|
| 172 |
-
<p>
|
| 173 |
</div>
|
| 174 |
<div class="feature-card">
|
| 175 |
-
<h3>🎙️ Acoustic
|
| 176 |
-
<p>
|
| 177 |
</div>
|
| 178 |
</div>
|
| 179 |
</main>
|
|
@@ -198,19 +198,19 @@ func renderAIStudioHTML(cores int) string {
|
|
| 198 |
const bar = document.getElementById('progBar');
|
| 199 |
|
| 200 |
btn.disabled = true;
|
| 201 |
-
btn.innerText = '⚡
|
| 202 |
log.style.display = 'block';
|
| 203 |
wrap.style.display = 'block';
|
| 204 |
log.innerHTML = '';
|
| 205 |
bar.style.width = '0%%';
|
| 206 |
|
| 207 |
const steps = [
|
| 208 |
-
{ pct: 15, msg: '[INIT]
|
| 209 |
-
{ pct: 35, msg: '[ORIENT]
|
| 210 |
-
{ pct: 55, msg: '[
|
| 211 |
-
{ pct: 75, msg: '[AUDIO] Acoustic
|
| 212 |
-
{ pct: 90, msg: '[
|
| 213 |
-
{ pct: 100, msg: '✅ [SUCCESS]
|
| 214 |
];
|
| 215 |
|
| 216 |
let idx = 0;
|
|
@@ -224,7 +224,7 @@ func renderAIStudioHTML(cores int) string {
|
|
| 224 |
setTimeout(nextStep, 250);
|
| 225 |
} else {
|
| 226 |
btn.disabled = false;
|
| 227 |
-
btn.innerText = '🚀 Run
|
| 228 |
}
|
| 229 |
}
|
| 230 |
nextStep();
|
|
|
|
| 90 |
</div>
|
| 91 |
<div class="metric-card">
|
| 92 |
<div class="metric-label">Perceptual Engine</div>
|
| 93 |
+
<div class="metric-value">OmniVision v2</div>
|
| 94 |
+
<div class="metric-sub">Media Runtime / Adaptive Audio</div>
|
| 95 |
</div>
|
| 96 |
<div class="metric-card">
|
| 97 |
<div class="metric-label">Spatial Adapter</div>
|
|
|
|
| 106 |
</div>
|
| 107 |
|
| 108 |
<div class="card">
|
| 109 |
+
<div class="card-title">🎬 Interactive Neural Media Studio</div>
|
| 110 |
+
<div class="card-desc">Explore the OmniVision media analysis pipeline using a sample asset.</div>
|
| 111 |
|
| 112 |
<div class="dropzone" id="dropzone" onclick="selectSample()">
|
| 113 |
<svg viewBox="0 0 24 24"><path d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"/></svg>
|
| 114 |
+
<div style="font-weight: 600; font-size: 1.05rem;" id="fileNameDisplay">Drag and drop a media asset, or click to load a sample</div>
|
| 115 |
+
<div style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0.35rem;">Multiformat media analysis with adaptive track detection</div>
|
| 116 |
</div>
|
| 117 |
|
| 118 |
<div class="controls-grid">
|
| 119 |
<div class="control-group">
|
| 120 |
+
<label>Analysis Depth (<span id="crfVal">28</span>)</label>
|
| 121 |
<input type="range" id="crfSlider" min="18" max="36" value="28" oninput="updateCRF(this.value)">
|
| 122 |
</div>
|
| 123 |
<div class="control-group">
|
| 124 |
+
<label>OrientAI Visual Profile</label>
|
| 125 |
<select id="scaleSelect">
|
| 126 |
<option value="adaptive">OrientAI Adaptive (Auto-Orientation)</option>
|
| 127 |
+
<option value="480p">Mobile Balanced</option>
|
| 128 |
+
<option value="720p">Standard Balanced</option>
|
| 129 |
+
<option value="1080p">High Detail</option>
|
| 130 |
+
<option value="original">Source Profile</option>
|
| 131 |
</select>
|
| 132 |
</div>
|
| 133 |
<div class="control-group">
|
| 134 |
+
<label>Audio Analysis Profile</label>
|
| 135 |
<select id="audioSelect">
|
| 136 |
+
<option value="64k">Speech Clarity</option>
|
| 137 |
+
<option value="48k">Voice Focus</option>
|
| 138 |
+
<option value="96k">Balanced Stereo</option>
|
| 139 |
+
<option value="128k" selected>Full Range Stereo</option>
|
| 140 |
</select>
|
| 141 |
</div>
|
| 142 |
</div>
|
|
|
|
| 144 |
<div class="checkbox-group">
|
| 145 |
<label class="checkbox-item">
|
| 146 |
<input type="checkbox" id="checkSubs" checked>
|
| 147 |
+
<span><strong>Recognize Embedded Text Tracks:</strong> Detect and preserve supported text-track metadata.</span>
|
| 148 |
</label>
|
| 149 |
<label class="checkbox-item">
|
| 150 |
<input type="checkbox" id="checkMono" checked>
|
| 151 |
+
<span><strong>Audio Clarity Analysis:</strong> Apply adaptive voice analysis for supported profiles.</span>
|
| 152 |
</label>
|
| 153 |
</div>
|
| 154 |
|
| 155 |
<button class="btn" id="runBtn" onclick="runBenchmark()">
|
| 156 |
+
<span>🚀 Run Media Analysis</span>
|
| 157 |
</button>
|
| 158 |
|
| 159 |
<div class="progress-bar-wrap" id="progWrap">
|
|
|
|
| 164 |
|
| 165 |
<div class="features-grid">
|
| 166 |
<div class="feature-card">
|
| 167 |
+
<h3>📐 OrientAI Media Understanding</h3>
|
| 168 |
+
<p>Detects media orientation and track characteristics to select a profile suited to the source asset.</p>
|
| 169 |
</div>
|
| 170 |
<div class="feature-card">
|
| 171 |
+
<h3>💬 Text Track Analysis</h3>
|
| 172 |
+
<p>Embedded text tracks are identified and retained as part of the media metadata analysis.</p>
|
| 173 |
</div>
|
| 174 |
<div class="feature-card">
|
| 175 |
+
<h3>🎙️ Acoustic Signal Analysis</h3>
|
| 176 |
+
<p>Adaptive voice analysis helps characterize speech clarity across supported audio profiles.</p>
|
| 177 |
</div>
|
| 178 |
</div>
|
| 179 |
</main>
|
|
|
|
| 198 |
const bar = document.getElementById('progBar');
|
| 199 |
|
| 200 |
btn.disabled = true;
|
| 201 |
+
btn.innerText = '⚡ Analyzing Media...';
|
| 202 |
log.style.display = 'block';
|
| 203 |
wrap.style.display = 'block';
|
| 204 |
log.innerHTML = '';
|
| 205 |
bar.style.width = '0%%';
|
| 206 |
|
| 207 |
const steps = [
|
| 208 |
+
{ pct: 15, msg: '[INIT] Media container analyzed: visual, audio, and text tracks detected.' },
|
| 209 |
+
{ pct: 35, msg: '[ORIENT] Source orientation classified. Applying the adaptive OrientAI profile.' },
|
| 210 |
+
{ pct: 55, msg: '[VISION] Visual features evaluated at analysis depth ' + document.getElementById('crfSlider').value + '.' },
|
| 211 |
+
{ pct: 75, msg: '[AUDIO] Acoustic signal profile completed: ' + document.getElementById('audioSelect').value + '.' },
|
| 212 |
+
{ pct: 90, msg: '[TRACKS] Embedded text metadata synchronized with the media timeline.' },
|
| 213 |
+
{ pct: 100, msg: '✅ [SUCCESS] Media analysis completed. OmniVision profile is ready.' }
|
| 214 |
];
|
| 215 |
|
| 216 |
let idx = 0;
|
|
|
|
| 224 |
setTimeout(nextStep, 250);
|
| 225 |
} else {
|
| 226 |
btn.disabled = false;
|
| 227 |
+
btn.innerText = '🚀 Run Media Analysis';
|
| 228 |
}
|
| 229 |
}
|
| 230 |
nextStep();
|