use std::{
collections::BTreeSet,
path::{Path, PathBuf},
sync::{
Arc, Mutex,
atomic::{AtomicBool, AtomicU64, Ordering},
},
time::Duration,
};
use crate::{
metadata::SourceId,
types::{CodecName, EngineName},
video::VideoFrame,
};
#[derive(Debug, Default, Clone)]
pub struct Stats {
pub elapsed_duration: Duration,
pub error: SharedAtomicFlag,
pub processors: Vec<ProcessorStats>,
pub worker_threads: Vec<WorkerThreadStats>,
}
impl Stats {
pub fn save(&self, output_file_path: &Path) {
let json = nojson::json(|f| {
f.set_indent_size(2);
f.set_spacing(true);
f.value(self)
})
.to_string();
if let Err(e) = std::fs::write(output_file_path, json) {
log::warn!(
"failed to write stats JSON: path={}, reason={e}",
output_file_path.display()
);
}
}
}
impl nojson::DisplayJson for Stats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("elapsed_seconds", self.elapsed_duration.as_secs_f32())?;
f.member("error", self.error.get())?;
f.member("processors", &self.processors)?;
f.member("worker_threads", &self.worker_threads)?;
Ok(())
})
}
}
#[derive(Debug, Clone)]
pub enum ProcessorStats {
Mp4AudioReader(Mp4AudioReaderStats),
Mp4VideoReader(Mp4VideoReaderStats),
WebmAudioReader(WebmAudioReaderStats),
WebmVideoReader(WebmVideoReaderStats),
AudioDecoder(AudioDecoderStats),
VideoDecoder(VideoDecoderStats),
AudioMixer(AudioMixerStats),
VideoMixer(VideoMixerStats),
AudioEncoder(AudioEncoderStats),
VideoEncoder(VideoEncoderStats),
Mp4Writer(Mp4WriterStats),
Other {
processor_type: String,
total_processing_duration: SharedAtomicDuration,
error: SharedAtomicFlag,
},
}
impl ProcessorStats {
pub fn other(processor_type: &str) -> Self {
Self::Other {
processor_type: processor_type.to_owned(),
total_processing_duration: Default::default(),
error: Default::default(),
}
}
pub fn total_processing_duration(&self) -> SharedAtomicDuration {
match self {
ProcessorStats::Mp4AudioReader(stats) => stats.total_processing_duration.clone(),
ProcessorStats::Mp4VideoReader(stats) => stats.total_processing_duration.clone(),
ProcessorStats::WebmAudioReader(stats) => stats.total_processing_duration.clone(),
ProcessorStats::WebmVideoReader(stats) => stats.total_processing_duration.clone(),
ProcessorStats::AudioDecoder(stats) => stats.total_processing_duration.clone(),
ProcessorStats::VideoDecoder(stats) => stats.total_processing_duration.clone(),
ProcessorStats::AudioMixer(stats) => stats.total_processing_duration.clone(),
ProcessorStats::VideoMixer(stats) => stats.total_processing_duration.clone(),
ProcessorStats::AudioEncoder(stats) => stats.total_processing_duration.clone(),
ProcessorStats::VideoEncoder(stats) => stats.total_processing_duration.clone(),
ProcessorStats::Mp4Writer(stats) => stats.total_processing_duration.clone(),
ProcessorStats::Other {
total_processing_duration,
..
} => total_processing_duration.clone(),
}
}
pub fn set_error(&self) {
match self {
ProcessorStats::Mp4AudioReader(stats) => stats.error.set(true),
ProcessorStats::Mp4VideoReader(stats) => stats.error.set(true),
ProcessorStats::WebmAudioReader(stats) => stats.error.set(true),
ProcessorStats::WebmVideoReader(stats) => stats.error.set(true),
ProcessorStats::AudioDecoder(stats) => stats.error.set(true),
ProcessorStats::VideoDecoder(stats) => stats.error.set(true),
ProcessorStats::AudioMixer(stats) => stats.error.set(true),
ProcessorStats::VideoMixer(stats) => stats.error.set(true),
ProcessorStats::AudioEncoder(stats) => stats.error.set(true),
ProcessorStats::VideoEncoder(stats) => stats.error.set(true),
ProcessorStats::Mp4Writer(stats) => stats.error.set(true),
ProcessorStats::Other { error, .. } => error.set(true),
}
}
}
impl nojson::DisplayJson for ProcessorStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
match self {
ProcessorStats::Mp4AudioReader(stats) => stats.fmt(f),
ProcessorStats::Mp4VideoReader(stats) => stats.fmt(f),
ProcessorStats::WebmAudioReader(stats) => stats.fmt(f),
ProcessorStats::WebmVideoReader(stats) => stats.fmt(f),
ProcessorStats::AudioDecoder(stats) => stats.fmt(f),
ProcessorStats::VideoDecoder(stats) => stats.fmt(f),
ProcessorStats::AudioMixer(stats) => stats.fmt(f),
ProcessorStats::VideoMixer(stats) => stats.fmt(f),
ProcessorStats::AudioEncoder(stats) => stats.fmt(f),
ProcessorStats::VideoEncoder(stats) => stats.fmt(f),
ProcessorStats::Mp4Writer(stats) => stats.fmt(f),
ProcessorStats::Other {
processor_type,
total_processing_duration,
error,
} => f.object(|f| {
f.member("type", processor_type)?;
f.member(
"total_processing_seconds",
total_processing_duration.get().as_secs_f32(),
)?;
f.member("error", error.get())
}),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct AudioMixerStats {
pub total_input_audio_data_count: SharedAtomicCounter,
pub total_output_audio_data_count: SharedAtomicCounter,
pub total_output_audio_data_duration: SharedAtomicDuration,
pub total_output_sample_count: SharedAtomicCounter,
pub total_output_filled_sample_count: SharedAtomicCounter,
pub total_trimmed_sample_count: SharedAtomicCounter,
pub total_processing_duration: SharedAtomicDuration,
pub error: SharedAtomicFlag,
}
impl nojson::DisplayJson for AudioMixerStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "audio_mixer")?;
f.member(
"total_input_audio_data_count",
self.total_input_audio_data_count.get(),
)?;
f.member(
"total_output_audio_data_count",
self.total_output_audio_data_count.get(),
)?;
f.member(
"total_output_audio_data_seconds",
self.total_output_audio_data_duration.get().as_secs_f32(),
)?;
f.member(
"total_output_sample_count",
self.total_output_sample_count.get(),
)?;
f.member(
"total_output_filled_sample_count",
self.total_output_filled_sample_count.get(),
)?;
f.member(
"total_trimmed_sample_count",
self.total_trimmed_sample_count.get(),
)?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Default, Clone)]
pub struct VideoMixerStats {
pub output_video_resolution: VideoResolution,
pub total_input_video_frame_count: SharedAtomicCounter,
pub total_output_video_frame_count: SharedAtomicCounter,
pub total_output_video_frame_duration: SharedAtomicDuration,
pub total_trimmed_video_frame_count: SharedAtomicCounter,
pub total_extended_video_frame_count: SharedAtomicCounter,
pub total_processing_duration: SharedAtomicDuration,
pub error: SharedAtomicFlag,
}
impl nojson::DisplayJson for VideoMixerStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "video_mixer")?;
f.member("output_video_resolution", self.output_video_resolution)?;
f.member(
"total_input_video_frame_count",
self.total_input_video_frame_count.get(),
)?;
f.member(
"total_output_video_frame_count",
self.total_output_video_frame_count.get(),
)?;
f.member(
"total_output_video_frame_seconds",
self.total_output_video_frame_duration.get().as_secs_f32(),
)?;
f.member(
"total_trimmed_video_frame_count",
self.total_trimmed_video_frame_count.get(),
)?;
f.member(
"total_extended_video_frame_count",
self.total_extended_video_frame_count.get(),
)?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Clone)]
pub struct AudioEncoderStats {
pub engine: EngineName,
pub codec: CodecName,
pub total_audio_data_count: SharedAtomicCounter,
pub total_processing_duration: SharedAtomicDuration,
pub error: SharedAtomicFlag,
}
impl AudioEncoderStats {
pub fn new(engine: EngineName, codec: CodecName) -> Self {
Self {
engine,
codec,
total_audio_data_count: Default::default(),
total_processing_duration: Default::default(),
error: Default::default(),
}
}
}
impl nojson::DisplayJson for AudioEncoderStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "audio_encoder")?;
f.member("engine", self.engine)?;
f.member("codec", self.codec)?;
f.member("total_audio_data_count", self.total_audio_data_count.get())?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Clone)]
pub struct VideoEncoderStats {
pub engine: EngineName,
pub codec: CodecName,
pub total_input_video_frame_count: SharedAtomicCounter,
pub total_output_video_frame_count: SharedAtomicCounter,
pub total_processing_duration: SharedAtomicDuration,
pub error: SharedAtomicFlag,
}
impl VideoEncoderStats {
pub fn new(engine: EngineName, codec: CodecName) -> Self {
Self {
engine,
codec,
total_input_video_frame_count: Default::default(),
total_output_video_frame_count: Default::default(),
total_processing_duration: Default::default(),
error: Default::default(),
}
}
}
impl nojson::DisplayJson for VideoEncoderStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "video_encoder")?;
f.member("engine", self.engine)?;
f.member("codec", self.codec)?;
f.member(
"total_input_video_frame_count",
self.total_input_video_frame_count.get(),
)?;
f.member(
"total_output_video_frame_count",
self.total_output_video_frame_count.get(),
)?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Default, Clone)]
pub struct AudioDecoderStats {
pub source_id: SharedOption<SourceId>,
pub engine: Option<EngineName>,
pub codec: Option<CodecName>,
pub total_audio_data_count: SharedAtomicCounter,
pub total_processing_duration: SharedAtomicDuration,
pub error: SharedAtomicFlag,
}
impl nojson::DisplayJson for AudioDecoderStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "audio_decoder")?;
f.member("source_id", self.source_id.get())?;
f.member("engine", self.engine)?;
f.member("codec", self.codec)?;
f.member("total_audio_data_count", self.total_audio_data_count.get())?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Default, Clone)]
pub struct VideoDecoderStats {
pub source_id: SharedOption<SourceId>,
pub engine: SharedOption<EngineName>,
pub codec: SharedOption<CodecName>,
pub total_input_video_frame_count: SharedAtomicCounter,
pub total_output_video_frame_count: SharedAtomicCounter,
pub total_processing_duration: SharedAtomicDuration,
pub resolutions: SharedSet<VideoResolution>,
pub error: SharedAtomicFlag,
}
impl nojson::DisplayJson for VideoDecoderStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "video_decoder")?;
f.member("source_id", self.source_id.get())?;
f.member("engine", self.engine.get())?;
f.member("codec", self.codec.get())?;
f.member(
"total_input_video_frame_count",
self.total_input_video_frame_count.get(),
)?;
f.member(
"total_output_video_frame_count",
self.total_output_video_frame_count.get(),
)?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("resolutions", self.resolutions.get())?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Default, Clone)]
pub struct SharedAtomicFlag(Arc<AtomicBool>);
impl SharedAtomicFlag {
pub fn set(&self, v: bool) {
self.0.store(v, Ordering::Relaxed)
}
pub fn get(&self) -> bool {
self.0.load(Ordering::Relaxed)
}
}
#[derive(Debug, Default, Clone)]
pub struct SharedAtomicCounter(Arc<AtomicU64>);
impl SharedAtomicCounter {
pub fn add(&self, n: u64) {
self.0.fetch_add(n, Ordering::Relaxed);
}
pub fn set(&self, n: u64) {
self.0.store(n, Ordering::Relaxed);
}
pub fn get(&self) -> u64 {
self.0.load(Ordering::Relaxed)
}
}
#[derive(Debug, Default, Clone)]
pub struct SharedAtomicDuration(SharedAtomicCounter);
impl SharedAtomicDuration {
pub fn new(n: Duration) -> Self {
let v = Self::default();
v.set(n);
v
}
pub fn add(&self, n: Duration) {
self.0.add(n.as_nanos() as u64);
}
pub fn set(&self, n: Duration) {
self.0.set(n.as_nanos() as u64);
}
pub fn get(&self) -> Duration {
Duration::from_nanos(self.0.get())
}
}
#[derive(Debug, Clone)]
pub struct SharedOption<T>(Arc<Mutex<Option<T>>>);
impl<T> SharedOption<T> {
pub fn new(value: Option<T>) -> Self {
Self(Arc::new(Mutex::new(value)))
}
pub fn set(&self, value: T) {
if let Ok(mut v) = self.0.lock() {
*v = Some(value);
}
}
pub fn set_once<F>(&self, f: F)
where
F: FnOnce() -> T,
{
if let Ok(mut v) = self.0.lock()
&& v.is_none()
{
*v = Some(f());
}
}
pub fn clear(&self) {
if let Ok(mut v) = self.0.lock() {
*v = None;
}
}
}
impl<T: Clone> SharedOption<T> {
pub fn get(&self) -> Option<T> {
if let Ok(v) = self.0.lock() {
v.clone()
} else {
None
}
}
}
impl<T> Default for SharedOption<T> {
fn default() -> Self {
Self::new(None)
}
}
#[derive(Debug, Clone)]
pub struct SharedSet<T>(Arc<Mutex<BTreeSet<T>>>);
impl<T: Ord + Eq> SharedSet<T> {
pub fn insert(&self, value: T) {
if let Ok(mut v) = self.0.lock() {
v.insert(value);
}
}
}
impl<T: Clone> SharedSet<T> {
pub fn get(&self) -> BTreeSet<T> {
if let Ok(v) = self.0.lock() {
v.clone()
} else {
BTreeSet::default()
}
}
}
impl<T> Default for SharedSet<T> {
fn default() -> Self {
Self(Arc::new(Mutex::new(BTreeSet::new())))
}
}
#[derive(Debug, Default, Clone)]
pub struct Mp4AudioReaderStats {
pub input_files: Vec<PathBuf>,
pub current_input_file: SharedOption<PathBuf>,
pub codec: Option<CodecName>,
pub total_sample_count: SharedAtomicCounter,
pub total_track_duration: SharedAtomicDuration,
pub track_duration_offset: SharedAtomicDuration,
pub total_processing_duration: SharedAtomicDuration,
pub start_time: Duration,
pub error: SharedAtomicFlag,
}
impl nojson::DisplayJson for Mp4AudioReaderStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "mp4_audio_reader")?;
f.member("input_files", &self.input_files)?;
if let Some(path) = self.current_input_file.get() {
f.member("current_input_file", path)?;
}
f.member("codec", self.codec)?;
f.member("total_sample_count", self.total_sample_count.get())?;
f.member(
"total_track_seconds",
(self.track_duration_offset.get() + self.total_track_duration.get()).as_secs_f32(),
)?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("start_time_seconds", self.start_time.as_secs_f32())?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Default, Clone)]
pub struct Mp4VideoReaderStats {
pub input_files: Vec<PathBuf>,
pub current_input_file: SharedOption<PathBuf>,
pub codec: SharedOption<CodecName>,
pub resolutions: SharedSet<VideoResolution>,
pub total_sample_count: SharedAtomicCounter,
pub total_track_duration: SharedAtomicDuration,
pub track_duration_offset: SharedAtomicDuration,
pub total_processing_duration: SharedAtomicDuration,
pub start_time: Duration,
pub error: SharedAtomicFlag,
}
impl nojson::DisplayJson for Mp4VideoReaderStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "mp4_video_reader")?;
f.member("input_files", &self.input_files)?;
if let Some(path) = self.current_input_file.get() {
f.member("current_input_file", path)?;
}
f.member("codec", self.codec.get())?;
f.member(
"resolutions",
nojson::json(|f| {
f.array(|f| {
f.elements(
self.resolutions
.get()
.iter()
.map(|res| format!("{}x{}", res.width, res.height)),
)
})
}),
)?;
f.member("total_sample_count", self.total_sample_count.get())?;
f.member(
"total_track_seconds",
(self.track_duration_offset.get() + self.total_track_duration.get()).as_secs_f32(),
)?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("start_time_seconds", self.start_time.as_secs_f32())?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Default, Clone)]
pub struct WebmAudioReaderStats {
pub input_files: Vec<PathBuf>,
pub current_input_file: SharedOption<PathBuf>,
pub codec: Option<CodecName>,
pub total_cluster_count: SharedAtomicCounter,
pub total_simple_block_count: SharedAtomicCounter,
pub total_track_duration: SharedAtomicDuration,
pub track_duration_offset: SharedAtomicDuration,
pub total_processing_duration: SharedAtomicDuration,
pub start_time: Duration,
pub error: SharedAtomicFlag,
}
impl nojson::DisplayJson for WebmAudioReaderStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "webm_audio_reader")?;
f.member("input_files", &self.input_files)?;
if let Some(path) = self.current_input_file.get() {
f.member("current_input_file", path)?;
}
f.member("codec", self.codec)?;
f.member("total_cluster_count", self.total_cluster_count.get())?;
f.member(
"total_simple_block_count",
self.total_simple_block_count.get(),
)?;
f.member(
"total_track_seconds",
(self.track_duration_offset.get() + self.total_track_duration.get()).as_secs_f32(),
)?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("start_time_seconds", self.start_time.as_secs_f32())?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Default, Clone)]
pub struct WebmVideoReaderStats {
pub input_files: Vec<PathBuf>,
pub current_input_file: SharedOption<PathBuf>,
pub codec: SharedOption<CodecName>,
pub total_cluster_count: SharedAtomicCounter,
pub total_simple_block_count: SharedAtomicCounter,
pub total_track_duration: SharedAtomicDuration,
pub track_duration_offset: SharedAtomicDuration,
pub total_processing_duration: SharedAtomicDuration,
pub start_time: Duration,
pub error: SharedAtomicFlag,
}
impl nojson::DisplayJson for WebmVideoReaderStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "webm_video_reader")?;
f.member("input_files", &self.input_files)?;
if let Some(path) = self.current_input_file.get() {
f.member("current_input_file", path)?;
}
f.member("codec", self.codec.get())?;
f.member("total_cluster_count", self.total_cluster_count.get())?;
f.member(
"total_simple_block_count",
self.total_simple_block_count.get(),
)?;
f.member(
"total_track_seconds",
(self.track_duration_offset.get() + self.total_track_duration.get()).as_secs_f32(),
)?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("start_time_seconds", self.start_time.as_secs_f32())?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Default, Clone)]
pub struct Mp4WriterStats {
pub audio_codec: SharedOption<CodecName>,
pub video_codec: SharedOption<CodecName>,
pub reserved_moov_box_size: SharedAtomicCounter,
pub actual_moov_box_size: SharedAtomicCounter,
pub total_audio_chunk_count: SharedAtomicCounter,
pub total_video_chunk_count: SharedAtomicCounter,
pub total_audio_sample_count: SharedAtomicCounter,
pub total_video_sample_count: SharedAtomicCounter,
pub total_audio_sample_data_byte_size: SharedAtomicCounter,
pub total_video_sample_data_byte_size: SharedAtomicCounter,
pub total_audio_track_duration: SharedAtomicDuration,
pub total_video_track_duration: SharedAtomicDuration,
pub total_processing_duration: SharedAtomicDuration,
pub error: SharedAtomicFlag,
}
impl nojson::DisplayJson for Mp4WriterStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member("type", "mp4_writer")?;
f.member("audio_codec", self.audio_codec.get())?;
f.member("video_codec", self.video_codec.get())?;
f.member("reserved_moov_box_size", self.reserved_moov_box_size.get())?;
f.member("actual_moov_box_size", self.actual_moov_box_size.get())?;
f.member(
"total_audio_chunk_count",
self.total_audio_chunk_count.get(),
)?;
f.member(
"total_video_chunk_count",
self.total_video_chunk_count.get(),
)?;
f.member(
"total_audio_sample_count",
self.total_audio_sample_count.get(),
)?;
f.member(
"total_video_sample_count",
self.total_video_sample_count.get(),
)?;
f.member(
"total_audio_sample_data_byte_size",
self.total_audio_sample_data_byte_size.get(),
)?;
f.member(
"total_video_sample_data_byte_size",
self.total_video_sample_data_byte_size.get(),
)?;
f.member(
"total_audio_track_seconds",
self.total_audio_track_duration.get().as_secs_f32(),
)?;
f.member(
"total_video_track_seconds",
self.total_video_track_duration.get().as_secs_f32(),
)?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member("error", self.error.get())?;
Ok(())
})
}
}
#[derive(Debug, Default, Clone)]
pub struct WorkerThreadStats {
pub processors: Vec<usize>,
pub total_processing_duration: SharedAtomicDuration,
pub total_waiting_duration: SharedAtomicDuration,
}
impl nojson::DisplayJson for WorkerThreadStats {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.object(|f| {
f.member(
"processors",
nojson::json(|f| {
let indent = f.get_indent_size();
f.set_indent_size(0);
f.value(&self.processors)?;
f.set_indent_size(indent);
Ok(())
}),
)?;
f.member(
"total_processing_seconds",
self.total_processing_duration.get().as_secs_f32(),
)?;
f.member(
"total_waiting_seconds",
self.total_waiting_duration.get().as_secs_f32(),
)?;
Ok(())
})
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct VideoResolution {
pub width: usize,
pub height: usize,
}
impl VideoResolution {
pub fn new(frame: &VideoFrame) -> Self {
Self {
width: frame.width,
height: frame.height,
}
}
}
impl nojson::DisplayJson for VideoResolution {
fn fmt(&self, f: &mut nojson::JsonFormatter<'_, '_>) -> std::fmt::Result {
f.value(format!("{}x{}", self.width, self.height))
}
}