Fix lazy audio path resolution for GPU streaming
Audio playback path was being resolved during parsing when database may not be ready, causing fallback to non-existent path. Now resolves lazily when stream starts, matching how audio analyzer works. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,8 @@ Supports:
|
||||
|
||||
import numpy as np
|
||||
import subprocess
|
||||
import threading
|
||||
import queue
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Tuple, Optional, List, Union
|
||||
from pathlib import Path
|
||||
@@ -665,12 +667,18 @@ class IPFSHLSOutput(Output):
|
||||
self.segment_cids: dict = {} # segment_number -> cid
|
||||
self._last_segment_checked = -1
|
||||
self._playlist_cid: Optional[str] = None
|
||||
self._upload_lock = threading.Lock()
|
||||
|
||||
# Import IPFS client
|
||||
from ipfs_client import add_file, add_bytes
|
||||
self._ipfs_add_file = add_file
|
||||
self._ipfs_add_bytes = add_bytes
|
||||
|
||||
# Background upload thread for async IPFS uploads
|
||||
self._upload_queue = queue.Queue()
|
||||
self._upload_thread = threading.Thread(target=self._upload_worker, daemon=True)
|
||||
self._upload_thread.start()
|
||||
|
||||
# Local HLS paths
|
||||
self.local_playlist_path = self.output_dir / "stream.m3u8"
|
||||
|
||||
@@ -727,9 +735,38 @@ class IPFSHLSOutput(Output):
|
||||
stderr=None,
|
||||
)
|
||||
|
||||
def _upload_new_segments(self):
|
||||
"""Check for new segments and upload them to IPFS."""
|
||||
def _upload_worker(self):
|
||||
"""Background worker thread for async IPFS uploads."""
|
||||
import sys
|
||||
while True:
|
||||
try:
|
||||
item = self._upload_queue.get(timeout=1.0)
|
||||
if item is None: # Shutdown signal
|
||||
break
|
||||
seg_path, seg_num = item
|
||||
self._do_upload(seg_path, seg_num)
|
||||
except queue.Empty:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Upload worker error: {e}", file=sys.stderr)
|
||||
|
||||
def _do_upload(self, seg_path: Path, seg_num: int):
|
||||
"""Actually perform the upload (runs in background thread)."""
|
||||
import sys
|
||||
try:
|
||||
cid = self._ipfs_add_file(seg_path, pin=True)
|
||||
if cid:
|
||||
with self._upload_lock:
|
||||
self.segment_cids[seg_num] = cid
|
||||
print(f"IPFS: segment_{seg_num:05d}.ts -> {cid}", file=sys.stderr)
|
||||
self._update_ipfs_playlist()
|
||||
except Exception as e:
|
||||
print(f"Failed to upload segment {seg_num}: {e}", file=sys.stderr)
|
||||
|
||||
def _upload_new_segments(self):
|
||||
"""Check for new segments and queue them for async IPFS upload."""
|
||||
import sys
|
||||
import time
|
||||
|
||||
# Find all segments
|
||||
segments = sorted(self.output_dir.glob("segment_*.ts"))
|
||||
@@ -739,53 +776,48 @@ class IPFSHLSOutput(Output):
|
||||
seg_name = seg_path.stem # segment_00000
|
||||
seg_num = int(seg_name.split("_")[1])
|
||||
|
||||
# Skip if already uploaded
|
||||
if seg_num in self.segment_cids:
|
||||
continue
|
||||
# Skip if already uploaded or queued
|
||||
with self._upload_lock:
|
||||
if seg_num in self.segment_cids:
|
||||
continue
|
||||
|
||||
# Skip if segment is still being written (check if file size is stable)
|
||||
# Skip if segment is still being written (quick non-blocking check)
|
||||
try:
|
||||
size1 = seg_path.stat().st_size
|
||||
if size1 == 0:
|
||||
continue # Empty file, still being created
|
||||
|
||||
import time
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.01) # Very short check
|
||||
size2 = seg_path.stat().st_size
|
||||
if size1 != size2:
|
||||
continue # File still being written
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
|
||||
# Upload to IPFS
|
||||
cid = self._ipfs_add_file(seg_path, pin=True)
|
||||
if cid:
|
||||
self.segment_cids[seg_num] = cid
|
||||
print(f"IPFS: segment_{seg_num:05d}.ts -> {cid}", file=sys.stderr)
|
||||
|
||||
# Update playlist after each segment upload
|
||||
self._update_ipfs_playlist()
|
||||
# Queue for async upload (non-blocking!)
|
||||
self._upload_queue.put((seg_path, seg_num))
|
||||
|
||||
def _update_ipfs_playlist(self):
|
||||
"""Generate and upload IPFS-aware m3u8 playlist."""
|
||||
if not self.segment_cids:
|
||||
return
|
||||
|
||||
import sys
|
||||
|
||||
# Build m3u8 content with IPFS URLs
|
||||
lines = [
|
||||
"#EXTM3U",
|
||||
"#EXT-X-VERSION:3",
|
||||
f"#EXT-X-TARGETDURATION:{int(self.segment_duration) + 1}",
|
||||
"#EXT-X-MEDIA-SEQUENCE:0",
|
||||
]
|
||||
with self._upload_lock:
|
||||
if not self.segment_cids:
|
||||
return
|
||||
|
||||
# Add segments in order
|
||||
for seg_num in sorted(self.segment_cids.keys()):
|
||||
cid = self.segment_cids[seg_num]
|
||||
lines.append(f"#EXTINF:{self.segment_duration:.3f},")
|
||||
lines.append(f"{self.ipfs_gateway}/{cid}")
|
||||
# Build m3u8 content with IPFS URLs
|
||||
lines = [
|
||||
"#EXTM3U",
|
||||
"#EXT-X-VERSION:3",
|
||||
f"#EXT-X-TARGETDURATION:{int(self.segment_duration) + 1}",
|
||||
"#EXT-X-MEDIA-SEQUENCE:0",
|
||||
]
|
||||
|
||||
# Add segments in order
|
||||
for seg_num in sorted(self.segment_cids.keys()):
|
||||
cid = self.segment_cids[seg_num]
|
||||
lines.append(f"#EXTINF:{self.segment_duration:.3f},")
|
||||
lines.append(f"{self.ipfs_gateway}/{cid}")
|
||||
|
||||
playlist_content = "\n".join(lines) + "\n"
|
||||
|
||||
@@ -842,9 +874,13 @@ class IPFSHLSOutput(Output):
|
||||
self._process.wait()
|
||||
self._is_open = False
|
||||
|
||||
# Upload any remaining segments
|
||||
# Queue any remaining segments
|
||||
self._upload_new_segments()
|
||||
|
||||
# Wait for pending uploads to complete
|
||||
self._upload_queue.put(None) # Signal shutdown
|
||||
self._upload_thread.join(timeout=30)
|
||||
|
||||
# Generate final playlist with #EXT-X-ENDLIST
|
||||
if self.segment_cids:
|
||||
lines = [
|
||||
|
||||
Reference in New Issue
Block a user