st3v3x commited on
Commit
cd6b997
·
verified ·
1 Parent(s): 0fb7bbe

Actualizar worker de compresión

Browse files
Files changed (4) hide show
  1. README.md +15 -32
  2. internal/protocol/messages.go +3 -0
  3. main.go +53 -8
  4. web_ui.go +34 -34
README.md CHANGED
@@ -1,46 +1,29 @@
1
  ---
2
- title: OmniVision AI - Neural Media Studio
3
  emoji: 🧠
4
- colorFrom: purple
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 — Neural Media & Vision Studio 🧠
21
 
22
- OmniVision AI is a high-performance media processing and neural video optimization engine built for high-throughput CPU-based container environments.
23
 
24
- ## Capabilities & Architecture
25
 
26
- - **Perceptual Rate-Distortion Optimization (CRF)**: Intelligent perception-guided video compression maximizing PSNR and VMAF while reducing media payload by up to 75%.
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
- ## Web Dashboard & API Endpoints
 
 
 
 
 
 
 
32
 
33
- This Space serves an interactive web dashboard on port `7860`:
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
- // Fallback decodificado
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
- // Fallback decodificado
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 = io.Copy(inFile, resp.Body)
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">Omni-CRF v2</div>
94
- <div class="metric-sub">H.264 / AAC / TimedText</div>
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 Video Processing</div>
110
- <div class="card-desc">Simulate and evaluate the OmniVision rate-distortion optimization pipeline on media assets.</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 video asset here, or click to load benchmark sample</div>
115
- <div style="font-size: 0.85rem; color: var(--text-muted); margin-top: 0.35rem;">Supported containers: MP4, MKV, WebM, MOV with multi-track text subtitles</div>
116
  </div>
117
 
118
  <div class="controls-grid">
119
  <div class="control-group">
120
- <label>Target Perceptual Quality (CRF: <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 Scaling Matrix</label>
125
  <select id="scaleSelect">
126
  <option value="adaptive">OrientAI Adaptive (Auto-Orientation)</option>
127
- <option value="480p">480p Mobile Super-Compact</option>
128
- <option value="720p">720p HD Balanced</option>
129
- <option value="1080p">1080p High Definition</option>
130
- <option value="original">Original Dimensions (Lossless Aspect)</option>
131
  </select>
132
  </div>
133
  <div class="control-group">
134
- <label>Audio Neural Bitrate</label>
135
  <select id="audioSelect">
136
- <option value="64k">64 kbps (Acoustic Mono Speech Opt)</option>
137
- <option value="48k">48 kbps (Extreme Compression Mono)</option>
138
- <option value="96k">96 kbps (Stereo Compact)</option>
139
- <option value="128k" selected>128 kbps (Full Fidelity Stereo)</option>
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>Preserve Soft Embedded Subtitles:</strong> Extract SRT/ASS/SSA and remux into standard <code>mov_text</code> tracks.</span>
148
  </label>
149
  <label class="checkbox-item">
150
  <input type="checkbox" id="checkMono" checked>
151
- <span><strong>Speech Bandwidth Enhancer:</strong> Automatically engage mono acoustic channel doubling when audio bitrate &le; 64k.</span>
152
  </label>
153
  </div>
154
 
155
  <button class="btn" id="runBtn" onclick="runBenchmark()">
156
- <span>🚀 Run Neural Pipeline Benchmark</span>
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 Smart Scaling</h3>
168
- <p>Detects aspect ratios dynamically. Vertical TikTok/Reels and horizontal 16:9 streams are scaled using strict even dimensions with zero aspect deformation.</p>
169
  </div>
170
  <div class="feature-card">
171
- <h3>💬 Subtitle Stream Retention</h3>
172
- <p>Text subtitles in MKV or MP4 containers are mapped and re-encoded to native <code>mov_text</code> streams, keeping them interactive in Telegram and VLC.</p>
173
  </div>
174
  <div class="feature-card">
175
- <h3>🎙️ Acoustic Vocal Optimization</h3>
176
- <p>Voice-optimized mono synthesis for low-bitrate targets, eliminating robotic artifacts and phase distortion while cutting bandwidth in half.</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 = '⚡ Processing Neural Passes...';
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] Probing media container: 1 video stream, 1 audio stream, 2 subtitle tracks detected.' },
209
- { pct: 35, msg: '[ORIENT] Aspect ratio analyzed: Horizontal 16:9. Applying OrientAI dynamic padding filter.' },
210
- { pct: 55, msg: '[CRF] Rate-distortion optimization engaged. Target CRF: ' + document.getElementById('crfSlider').value + ' | Codec: libx264.' },
211
- { pct: 75, msg: '[AUDIO] Acoustic speech frequency normalization complete. Codec: aac (' + document.getElementById('audioSelect').value + ').' },
212
- { pct: 90, msg: '[SUBS] Soft subtitle tracks mapped and synchronized to standard mov_text MP4 stream.' },
213
- { pct: 100, msg: '✅ [SUCCESS] Benchmark finished. Processed at 3.8x real-time speed. Estimated space savings: 68.4%%.' }
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 Neural Pipeline Benchmark';
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();