mirror of
https://github.com/Dispatcharr/Dispatcharr.git
synced 2026-01-22 18:28:00 +00:00
Enhancement: DVR recording remux fallback strategy: Implemented two-stage TS→MP4→MKV fallback when direct TS→MKV conversion fails due to timestamp issues. On remux failure, system now attempts TS→MP4 conversion (MP4 container handles broken timestamps better) followed by MP4→MKV conversion, automatically recovering from provider timestamp corruption. Failed conversions now properly clean up partial files and preserve source TS for manual recovery.
Some checks are pending
CI Pipeline / prepare (push) Waiting to run
CI Pipeline / docker (amd64, ubuntu-24.04) (push) Blocked by required conditions
CI Pipeline / docker (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
CI Pipeline / create-manifest (push) Blocked by required conditions
Build and Push Multi-Arch Docker Image / build-and-push (push) Waiting to run
Some checks are pending
CI Pipeline / prepare (push) Waiting to run
CI Pipeline / docker (amd64, ubuntu-24.04) (push) Blocked by required conditions
CI Pipeline / docker (arm64, ubuntu-24.04-arm) (push) Blocked by required conditions
CI Pipeline / create-manifest (push) Blocked by required conditions
Build and Push Multi-Arch Docker Image / build-and-push (push) Waiting to run
This commit is contained in:
parent
cf323f15a5
commit
0b83137ac6
2 changed files with 86 additions and 6 deletions
|
|
@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Unassociated streams filter: Added "Only Unassociated" filter option to streams table for quickly finding streams not assigned to any channels - Thanks [@JeffreyBytes](https://github.com/JeffreyBytes) (Closes #667)
|
||||
- Client-side logo caching: Added `Cache-Control` and `Last-Modified` headers to logo responses, enabling browsers to cache logos locally for 4 hours (local files) and respecting upstream cache headers (remote logos). This reduces network traffic and nginx load while providing faster page loads through browser-level caching that complements the existing nginx server-side cache - Thanks [@DawtCom](https://github.com/DawtCom)
|
||||
- DVR recording remux fallback strategy: Implemented two-stage TS→MP4→MKV fallback when direct TS→MKV conversion fails due to timestamp issues. On remux failure, system now attempts TS→MP4 conversion (MP4 container handles broken timestamps better) followed by MP4→MKV conversion, automatically recovering from provider timestamp corruption. Failed conversions now properly clean up partial files and preserve source TS for manual recovery.
|
||||
- Mature content filtering support:
|
||||
- Added `is_adult` boolean field to both Stream and Channel models with database indexing for efficient filtering and sorting
|
||||
- Automatically populated during M3U/XC refresh operations by extracting `is_adult` value from provider data
|
||||
|
|
|
|||
|
|
@ -1874,18 +1874,97 @@ def run_recording(recording_id, channel_id, start_time_str, end_time_str):
|
|||
remux_success = False
|
||||
try:
|
||||
if temp_ts_path and os.path.exists(temp_ts_path):
|
||||
subprocess.run([
|
||||
"ffmpeg", "-y", "-i", temp_ts_path, "-c", "copy", final_path
|
||||
], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
remux_success = os.path.exists(final_path)
|
||||
# Clean up temp file on success
|
||||
# First attempt: Direct TS to MKV remux
|
||||
result = subprocess.run([
|
||||
"ffmpeg", "-y",
|
||||
"-fflags", "+genpts+igndts+discardcorrupt", # Regenerate timestamps, ignore DTS
|
||||
"-err_detect", "ignore_err", # Ignore minor stream errors
|
||||
"-i", temp_ts_path,
|
||||
"-map", "0", # Map all streams
|
||||
"-c", "copy",
|
||||
final_path
|
||||
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
# Check if FFmpeg succeeded (return code 0) and output file is valid
|
||||
if result.returncode == 0 and os.path.exists(final_path) and os.path.getsize(final_path) > 0:
|
||||
remux_success = True
|
||||
logger.info(f"Direct TS→MKV remux succeeded for {os.path.basename(final_path)}")
|
||||
else:
|
||||
# Direct remux failed - try fallback: TS → MP4 → MKV to fix timestamps
|
||||
logger.warning(f"Direct TS→MKV remux failed (return code: {result.returncode}), trying fallback TS→MP4→MKV")
|
||||
|
||||
# Clean up partial/failed MKV
|
||||
try:
|
||||
if os.path.exists(final_path):
|
||||
os.remove(final_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Step 1: TS → MP4 (MP4 container handles broken timestamps better)
|
||||
temp_mp4_path = os.path.splitext(temp_ts_path)[0] + ".mp4"
|
||||
result_mp4 = subprocess.run([
|
||||
"ffmpeg", "-y",
|
||||
"-fflags", "+genpts+igndts+discardcorrupt",
|
||||
"-err_detect", "ignore_err",
|
||||
"-i", temp_ts_path,
|
||||
"-map", "0",
|
||||
"-c", "copy",
|
||||
temp_mp4_path
|
||||
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
if result_mp4.returncode == 0 and os.path.exists(temp_mp4_path) and os.path.getsize(temp_mp4_path) > 0:
|
||||
logger.info(f"TS→MP4 conversion succeeded, now converting MP4→MKV")
|
||||
|
||||
# Step 2: MP4 → MKV (clean timestamps from MP4)
|
||||
result_mkv = subprocess.run([
|
||||
"ffmpeg", "-y",
|
||||
"-i", temp_mp4_path,
|
||||
"-map", "0",
|
||||
"-c", "copy",
|
||||
final_path
|
||||
], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
if result_mkv.returncode == 0 and os.path.exists(final_path) and os.path.getsize(final_path) > 0:
|
||||
remux_success = True
|
||||
logger.info(f"Fallback TS→MP4→MKV remux succeeded for {os.path.basename(final_path)}")
|
||||
else:
|
||||
logger.error(f"MP4→MKV conversion failed (return code: {result_mkv.returncode})")
|
||||
|
||||
# Clean up temp MP4
|
||||
try:
|
||||
if os.path.exists(temp_mp4_path):
|
||||
os.remove(temp_mp4_path)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
logger.error(f"TS→MP4 conversion failed (return code: {result_mp4.returncode})")
|
||||
|
||||
# Clean up temp TS file only on successful remux
|
||||
if remux_success:
|
||||
try:
|
||||
os.remove(temp_ts_path)
|
||||
logger.debug(f"Cleaned up temp TS file: {temp_ts_path}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to remove temp TS file: {e}")
|
||||
else:
|
||||
# Keep TS file for debugging/manual recovery if remux failed
|
||||
logger.warning(f"Remux failed - keeping temp TS file for recovery: {temp_ts_path}")
|
||||
# Clean up any partial MKV
|
||||
try:
|
||||
if os.path.exists(final_path):
|
||||
os.remove(final_path)
|
||||
logger.debug(f"Cleaned up partial MKV file: {final_path}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"MKV remux failed: {e}")
|
||||
logger.warning(f"MKV remux failed with exception: {e}")
|
||||
# Clean up any partial files on exception
|
||||
try:
|
||||
if os.path.exists(final_path):
|
||||
os.remove(final_path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Persist final metadata to Recording (status, ended_at, and stream stats if available)
|
||||
try:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue