| from __future__ import annotations |
|
|
| import os |
| from dataclasses import dataclass |
|
|
| from . import media_io as media |
| from . import video_buffers as video |
|
|
|
|
| @dataclass |
| class MuxSession: |
| mux_process: object | None = None |
| output_path_for_write: str = "" |
| video_only_output_path: str = "" |
| reserved_metadata_path: str | None = None |
| stopped: bool = False |
| mux_finished: bool = False |
| owns_output_path_for_write: bool = False |
| owns_video_only_output: bool = False |
|
|
| def set_output_path(self, output_path: str) -> None: |
| self.output_path_for_write = output_path |
| self.video_only_output_path = "" |
|
|
| def ensure_started( |
| self, |
| *, |
| server_config: dict, |
| ffmpeg_path: str, |
| process_is_hdr: bool, |
| use_live_av_mux: bool, |
| output_container: str, |
| source_path: str, |
| exact_start_seconds: float, |
| selected_audio_track: int | None, |
| resolved_width: int, |
| resolved_height: int, |
| fps_float: float, |
| source_audio_duration_seconds: float | None = None, |
| ) -> None: |
| if self.mux_process is not None: |
| return |
| self.reserved_metadata_path = media.create_reserved_metadata_file(self.output_path_for_write) |
| self.owns_output_path_for_write = use_live_av_mux |
| self.owns_video_only_output = not use_live_av_mux |
| if self.owns_video_only_output: |
| self.video_only_output_path = media.reserve_video_only_output_path(self.output_path_for_write) |
| if process_is_hdr: |
| hdr_video_crf = server_config.get("hdr_video_crf", 8) |
| self.mux_process = media.start_hdr_av_mux_process(ffmpeg_path, self.output_path_for_write, resolved_width, resolved_height, fps_float, hdr_video_crf, output_container, source_path, exact_start_seconds, selected_audio_track, self.reserved_metadata_path, source_audio_duration_seconds) if use_live_av_mux else media.start_hdr_video_mux_process(ffmpeg_path, self.video_only_output_path, resolved_width, resolved_height, fps_float, hdr_video_crf, output_container, self.reserved_metadata_path) |
| return |
| video_codec = server_config.get("video_output_codec", "libx264_8") |
| self.mux_process = media.start_av_mux_process(ffmpeg_path, self.output_path_for_write, resolved_width, resolved_height, fps_float, video_codec, output_container, source_path, exact_start_seconds, selected_audio_track, self.reserved_metadata_path, source_audio_duration_seconds) if use_live_av_mux else media.start_video_mux_process(ffmpeg_path, self.video_only_output_path, resolved_width, resolved_height, fps_float, video_codec, output_container, self.reserved_metadata_path) |
|
|
| def write_chunk(self, *, process_is_hdr: bool, video_tensor_hdr, video_tensor_uint8, start_frame: int, frame_count: int): |
| if process_is_hdr and video_tensor_hdr is not None: |
| return video.write_hdr_video_chunk(self.mux_process, video_tensor_hdr, start_frame=start_frame, frame_count=frame_count) |
| return video.write_video_chunk(self.mux_process, video_tensor_uint8, start_frame=start_frame, frame_count=frame_count) |
|
|
| def finalize(self) -> tuple[int, str, bool]: |
| return_code, stderr, forced_termination = media.finalize_mux_process(self.mux_process) |
| self.mux_finished = True |
| return return_code, stderr, forced_termination |
|
|
| def cleanup_partial_outputs(self) -> None: |
| if self.mux_process is not None and not self.mux_finished and self.mux_process.poll() is None: |
| try: |
| media.finalize_mux_process(self.mux_process) |
| except Exception: |
| pass |
| if self.owns_output_path_for_write and self.mux_process is not None and not self.stopped and self.mux_process.returncode not in (0, None) and os.path.isfile(self.output_path_for_write): |
| media.delete_file_if_exists(self.output_path_for_write, label="continuation output") |
| if self.owns_video_only_output: |
| media.delete_file_if_exists(self.video_only_output_path, label="video-only output") |
| media.delete_file_if_exists(self.reserved_metadata_path, label="reserved metadata file") |
|
|