Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions crates/ersatztv-channel/src/channel_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use time::OffsetDateTime;
use tokio::io::AsyncBufReadExt;
use tokio::sync::Mutex;

use crate::dossier::Dossier;
use crate::dossier::DossierBuilder;
use crate::local_proxy::{LocalProxyServer, ScriptCommand};
use crate::playlist_manager::{PlaylistManager, PlaylistManagerOutputFiles, SubtitleSource};
use crate::playout_loader::PlayoutLoader;
Expand Down Expand Up @@ -548,7 +548,7 @@ impl ChannelSession {
.and_then(|s| s.stream_index);

let subtitle_input = match (
subtitle_probe_result,
subtitle_probe_result.clone(),
subtitle_input_source,
subtitle_timing,
) {
Expand All @@ -568,7 +568,7 @@ impl ChannelSession {
input_source: audio_input_source,
in_point: audio_timing.in_point,
out_point: audio_timing.out_point,
probe_result: audio_probe_result,
probe_result: audio_probe_result.clone(),
stream_index: audio_index,
},
video_input: ProbedInput {
Expand All @@ -579,7 +579,7 @@ impl ChannelSession {
video_timing.in_point
},
out_point: video_timing.out_point,
probe_result: video_probe_result,
probe_result: video_probe_result.clone(),
stream_index: video_index,
},
subtitle_input,
Expand Down Expand Up @@ -661,14 +661,32 @@ impl ChannelSession {
let status = status.map_err(|e| ChannelError::StreamFailure(e.to_string()))?;
let _ = reader_handle.await;
if !status.success() {
let stderr_tail = ring
let stderr_tail: Vec<_> = ring
.lock()
.map(|r| r.iter().cloned().collect())
.unwrap_or_default();
let report_source_file = self.channel_config.ffmpeg.reports_folder.as_ref().map(|folder| {

let mut builder = DossierBuilder::new(&self.channel_config, &self.ffmpeg_info)
.item(current_item)
.stderr(stderr_tail)
.video(&video_probe_result)
.audio(&audio_probe_result);

if let Some(accel) = &self.hw_accel {
builder = builder.accel(accel);
}

if let Some(subtitle_probe_result) = &subtitle_probe_result {
builder = builder.subtitle(subtitle_probe_result);
}

if let Some(report_source_file) = self.channel_config.ffmpeg.reports_folder.as_ref().map(|folder| {
PathBuf::from(folder).join(format!(".in-flight-{}.log", self.channel_config.number()))
});
let dossier = Dossier::new(&self.channel_config, current_item, stderr_tail, report_source_file);
}) {
builder = builder.report_source(report_source_file);
}

let dossier = builder.build();
if let Err(err) = dossier.write().await {
log::error!("failed to save dossier: {err}");
}
Expand Down
170 changes: 140 additions & 30 deletions crates/ersatztv-channel/src/dossier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,57 @@ use std::path::PathBuf;

use ersatztv_channel::config::ChannelConfig;
use ersatztv_channel::error::ChannelError;
use ersatztv_playout::playout::PlayoutItem;
use ersatztv_playout::playout::{DATE_FORMAT, PlayoutItem};
use ffpipeline::ffmpeg_info::FfmpegInfo;
use ffpipeline::hw_accel::HardwareAccel;
use ffpipeline::probe::ProbeResult;
use serde::Serialize;
use time::OffsetDateTime;

#[derive(Default, Serialize)]
struct MediaInfo {
video: serde_json::Value,
audio: serde_json::Value,
subtitle: serde_json::Value,
}

#[derive(Serialize)]
struct Pipeline {
ffmpeg_info: FfmpegInfo,
hw_accel: Option<HardwareAccel>,
}

pub struct Dossier {
channel_config: ChannelConfig,
item_id: String,
item_json: String,
stderr_tail: Vec<String>,
pipeline: Pipeline,
item_id: Option<String>,
item_json: Option<String>,
media_info: Option<MediaInfo>,
stderr_tail: Option<Vec<String>>,
report_source_file: Option<PathBuf>,
}

impl Dossier {
pub fn new(
channel_config: &ChannelConfig,
item: &PlayoutItem,
stderr_tail: Vec<String>,
report_source_file: Option<PathBuf>,
) -> Self {
Self {
channel_config: channel_config.clone(),
item_id: item.id.clone(),
item_json: serde_json::to_string_pretty(item).unwrap_or_else(|_| String::from("{}")),
stderr_tail,
report_source_file,
}
}

pub async fn write(&self) -> Result<(), ChannelError> {
if let Some(reports_folder) = self.channel_config.ffmpeg.reports_folder.as_ref() {
let timestamp = OffsetDateTime::now_utc().unix_timestamp_nanos();
let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc());
let formatted_now = now.format(&DATE_FORMAT)?;

let reports_folder = PathBuf::from(reports_folder);
let dossier_folder = reports_folder.join(format!(
"{}_{}_{}",
self.channel_config.number(),
timestamp,
self.item_id
));
let dossier_folder = if let Some(item_id) = &self.item_id {
reports_folder.join(format!(
"{}_{}_{}",
self.channel_config.number(),
formatted_now,
item_id
))
} else {
reports_folder.join(format!(
"{}_{}",
self.channel_config.number(),
formatted_now
))
};

tokio::fs::create_dir_all(&dossier_folder).await?;

Expand All @@ -56,13 +69,29 @@ impl Dossier {
}
}

if !report_saved {
if !report_saved
&& let Some(stderr_tail) = self.stderr_tail.as_ref().filter(|t| !t.is_empty())
{
let ffmpeg_stderr_file = dossier_folder.join("ffmpeg_stderr.log");
tokio::fs::write(&ffmpeg_stderr_file, self.stderr_tail.join("\n")).await?;
tokio::fs::write(&ffmpeg_stderr_file, stderr_tail.join("\n")).await?;
}

let pipeline_json =
serde_json::to_string_pretty(&self.pipeline).unwrap_or_else(|_| String::from("{}"));
let pipeline_file = dossier_folder.join("pipeline.json");
tokio::fs::write(&pipeline_file, pipeline_json).await?;

if let Some(item_json) = &self.item_json {
let playout_item_file = dossier_folder.join("playout_item.json");
tokio::fs::write(&playout_item_file, item_json).await?;
}

let playout_item_file = dossier_folder.join("playout_item.json");
tokio::fs::write(&playout_item_file, &self.item_json).await?;
if let Some(media_info) = &self.media_info {
let media_info_json =
serde_json::to_string_pretty(media_info).unwrap_or_else(|_| String::from("{}"));
let media_info_file = dossier_folder.join("media_info.json");
tokio::fs::write(&media_info_file, media_info_json).await?;
}

let channel_config_json = serde_json::to_string_pretty(&self.channel_config)
.unwrap_or_else(|_| String::from("{}"));
Expand All @@ -73,3 +102,84 @@ impl Dossier {
Ok(())
}
}

pub struct DossierBuilder {
channel_config: ChannelConfig,
pipeline: Pipeline,
item_id: Option<String>,
item_json: Option<String>,
media_info: Option<MediaInfo>,
stderr_tail: Option<Vec<String>>,
report_source_file: Option<PathBuf>,
}

impl DossierBuilder {
pub fn new(channel_config: &ChannelConfig, ffmpeg_info: &FfmpegInfo) -> DossierBuilder {
DossierBuilder {
channel_config: channel_config.clone(),
pipeline: Pipeline {
ffmpeg_info: ffmpeg_info.clone(),
hw_accel: None,
},
item_id: None,
item_json: None,
media_info: None,
stderr_tail: None,
report_source_file: None,
}
}

pub fn item(mut self, item: &PlayoutItem) -> DossierBuilder {
self.item_id = Some(item.id.clone());
self.item_json =
Some(serde_json::to_string_pretty(item).unwrap_or_else(|_| String::from("{}")));
self
}

pub fn stderr(mut self, stderr_tail: Vec<String>) -> DossierBuilder {
self.stderr_tail = Some(stderr_tail);
self
}

pub fn report_source(mut self, report_source_file: PathBuf) -> DossierBuilder {
self.report_source_file = Some(report_source_file);
self
}

pub fn video(mut self, video_probe_result: &ProbeResult) -> DossierBuilder {
let value = serde_json::to_value(video_probe_result).unwrap_or(serde_json::Value::Null);
self.media_info.get_or_insert_with(MediaInfo::default).video = value;
self
}

pub fn audio(mut self, audio_probe_result: &ProbeResult) -> DossierBuilder {
let value = serde_json::to_value(audio_probe_result).unwrap_or(serde_json::Value::Null);
self.media_info.get_or_insert_with(MediaInfo::default).audio = value;
self
}

pub fn subtitle(mut self, subtitle_probe_result: &ProbeResult) -> DossierBuilder {
let value = serde_json::to_value(subtitle_probe_result).unwrap_or(serde_json::Value::Null);
self.media_info
.get_or_insert_with(MediaInfo::default)
.subtitle = value;
self
}

pub fn accel(mut self, accel: &HardwareAccel) -> DossierBuilder {
self.pipeline.hw_accel = Some(accel.clone());
self
}

pub fn build(self) -> Dossier {
Dossier {
channel_config: self.channel_config,
pipeline: self.pipeline,
item_id: self.item_id,
item_json: self.item_json,
media_info: self.media_info,
stderr_tail: self.stderr_tail,
report_source_file: self.report_source_file,
}
}
}
4 changes: 3 additions & 1 deletion crates/ffpipeline/src/accel/amf.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use serde::Serialize;

use crate::ArgVec;
use crate::ffmpeg_info::{FfmpegInfo, KnownHardwareAccel};
use crate::frame_size::FrameSize;
Expand All @@ -6,7 +8,7 @@ use crate::pipeline::{FrameSurface, PixelFormat, SurfaceSet, VideoFormat};
use crate::probe::ProbeResultVideoStream;
use crate::video_codec::VideoCodec;

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct Amf;

impl HwAccel for Amf {
Expand Down
4 changes: 3 additions & 1 deletion crates/ffpipeline/src/accel/cuda.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use serde::Serialize;

use crate::ArgVec;
use crate::capabilities::nvidia::NvidiaCapabilities;
use crate::ffmpeg_info::{FfmpegInfo, KnownHardwareAccel, KnownVideoFilter};
Expand All @@ -14,7 +16,7 @@ use crate::video_filter::{
VideoFilter, VideoFilterOp,
};

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct Cuda {
pub capabilities: NvidiaCapabilities,
}
Expand Down
4 changes: 3 additions & 1 deletion crates/ffpipeline/src/accel/qsv.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use serde::Serialize;

use crate::ArgVec;
use crate::capabilities::qsv::QsvCapabilities;
use crate::ffmpeg_info::{FfmpegInfo, KnownHardwareAccel, KnownVideoFilter};
Expand All @@ -9,7 +11,7 @@ use crate::probe::ProbeResultVideoStream;
use crate::video_codec::VideoCodec;
use crate::video_filter::{DeinterlaceFilter, ScaleFilter, VideoFilter, VideoFilterOp};

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct Qsv {
pub capabilities: QsvCapabilities,
}
Expand Down
4 changes: 3 additions & 1 deletion crates/ffpipeline/src/accel/rkmpp.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use serde::Serialize;

use crate::ArgVec;
use crate::capabilities::rkmpp::RkmppCapabilities;
use crate::ffmpeg_info::{FfmpegInfo, KnownHardwareAccel};
Expand All @@ -7,7 +9,7 @@ use crate::pipeline::{FrameSurface, PixelFormat, SurfaceSet, VideoFormat};
use crate::probe::ProbeResultVideoStream;
use crate::video_codec::VideoCodec;

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct Rkmpp {
pub capabilities: RkmppCapabilities,
}
Expand Down
6 changes: 4 additions & 2 deletions crates/ffpipeline/src/accel/vaapi.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use serde::Serialize;

use crate::ArgVec;
use crate::accel::opencl::{PadOpencl, TonemapOpencl};
use crate::capabilities::opencl::OpenCLCapabilities;
Expand All @@ -18,7 +20,7 @@ use crate::video_filter::{
ToneMapFilter, VideoFilter, VideoFilterOp,
};

#[derive(Debug, Clone, PartialEq, strum::Display)]
#[derive(Debug, Clone, PartialEq, strum::Display, Serialize)]
pub enum VaapiDriver {
#[strum(serialize = "iHD")]
Ihd,
Expand All @@ -28,7 +30,7 @@ pub enum VaapiDriver {
RadeonSI,
}

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct Vaapi {
pub device: String,
pub driver: VaapiDriver,
Expand Down
4 changes: 3 additions & 1 deletion crates/ffpipeline/src/accel/video_toolbox.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use serde::Serialize;

use crate::ArgVec;
use crate::capabilities::videotoolbox::VideoToolboxCapabilities;
use crate::ffmpeg_info::{FfmpegInfo, KnownHardwareAccel, KnownVideoFilter};
Expand All @@ -9,7 +11,7 @@ use crate::probe::ProbeResultVideoStream;
use crate::video_codec::VideoCodec;
use crate::video_filter::{ScaleFilter, VideoFilter, VideoFilterOp};

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct VideoToolbox {
pub capabilities: VideoToolboxCapabilities,
}
Expand Down
4 changes: 3 additions & 1 deletion crates/ffpipeline/src/accel/vulkan.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use serde::Serialize;

use crate::ArgVec;
use crate::capabilities::vulkan::VulkanCapabilities;
use crate::ffmpeg_info::{FfmpegInfo, KnownHardwareAccel, KnownVideoFilter};
Expand All @@ -9,7 +11,7 @@ use crate::probe::ProbeResultVideoStream;
use crate::video_codec::VideoCodec;
use crate::video_filter::{ScaleFilter, ToneMapFilter, VideoFilter, VideoFilterOp};

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct Vulkan {
pub capabilities: VulkanCapabilities,
}
Expand Down
Loading
Loading