use crate::{FileAnalyze, StreamKind};
pub struct FileLevelInfo<'a> {
pub file_size: u64,
pub extension: Option<&'a str>,
pub modified_unix_secs: Option<i64>,
pub local_offset_secs: i64,
}
pub fn fill_file_level_fields(fa: &mut FileAnalyze, info: &FileLevelInfo<'_>) {
let file_size = info.file_size;
fa.set_field(StreamKind::General, 0, "FileSize", file_size.to_string());
if let Some(ext) = info.extension {
fa.set_field(StreamKind::General, 0, "FileExtension", ext.to_owned());
}
let audio_stream_size: Option<u64> =
fa.retrieve(StreamKind::Audio, 0, "StreamSize").and_then(|z| z.as_str().parse().ok());
let duration_ms: Option<u64> = fa
.retrieve(StreamKind::General, 0, "Duration")
.and_then(|z| z.as_str().parse().ok())
.or_else(|| {
fa.retrieve(StreamKind::Audio, 0, "Duration").and_then(|z| z.as_str().parse().ok())
});
if let Some(ms) = duration_ms {
fa.set_field(StreamKind::General, 0, "Duration", ms.to_string());
}
if let Some(ms) = duration_ms
&& ms > 0
{
let overall = ((file_size as f64) * 8.0 * 1000.0 / (ms as f64)).round() as u64;
let has_video = fa.stream_count(StreamKind::Video) > 0;
let overall_mode = if !has_video {
fa.retrieve(StreamKind::Audio, 0, "BitRate_Mode").map(|z| z.as_str().to_owned())
} else {
let video_fr_mode =
fa.retrieve(StreamKind::Video, 0, "FrameRate_Mode").map(|z| z.as_str().to_owned());
if video_fr_mode.as_deref() == Some("VFR") { Some("VBR".to_owned()) } else { None }
};
if let Some(mode) = overall_mode {
fa.set_field(StreamKind::General, 0, "OverallBitRate_Mode", mode);
}
fa.set_field(StreamKind::General, 0, "OverallBitRate", overall.to_string());
}
if fa.stream_count(StreamKind::Video) > 0 {
if fa.retrieve(StreamKind::General, 0, "FrameRate").is_none()
&& let Some(fr) =
fa.retrieve(StreamKind::Video, 0, "FrameRate").map(|z| z.as_str().to_owned())
{
fa.set_field(StreamKind::General, 0, "FrameRate", fr);
}
if fa.retrieve(StreamKind::General, 0, "FrameCount").is_none()
&& let Some(fc) =
fa.retrieve(StreamKind::Video, 0, "FrameCount").map(|z| z.as_str().to_owned())
{
fa.set_field(StreamKind::General, 0, "FrameCount", fc);
}
}
let video_stream_size: u64 = fa
.retrieve(StreamKind::Video, 0, "StreamSize")
.and_then(|z| z.as_str().parse().ok())
.unwrap_or(0);
if let Some(audio_size) = audio_stream_size {
let elementary = audio_size + video_stream_size;
if elementary < file_size {
fa.set_field(
StreamKind::General,
0,
"StreamSize",
(file_size - elementary).to_string(),
);
}
} else if video_stream_size > 0 && video_stream_size < file_size {
fa.set_field(
StreamKind::General,
0,
"StreamSize",
(file_size - video_stream_size).to_string(),
);
}
if let Some(unix_secs) = info.modified_unix_secs {
fa.set_field(StreamKind::General, 0, "File_Modified_Date", format_utc(unix_secs));
fa.set_field(
StreamKind::General,
0,
"File_Modified_Date_Local",
format_local(unix_secs, info.local_offset_secs),
);
}
}
fn format_utc(unix_secs: i64) -> String {
let (y, m, d, hh, mm, ss) = civil_from_unix(unix_secs);
format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02} UTC")
}
fn format_local(unix_secs: i64, local_offset_secs: i64) -> String {
let (y, m, d, hh, mm, ss) = civil_from_unix(unix_secs + local_offset_secs);
format!("{y:04}-{m:02}-{d:02} {hh:02}:{mm:02}:{ss:02}")
}
fn civil_from_unix(unix_secs: i64) -> (i32, u8, u8, u8, u8, u8) {
let days = unix_secs.div_euclid(86400);
let rem = unix_secs.rem_euclid(86400);
let hh = (rem / 3600) as u8;
let mm = ((rem % 3600) / 60) as u8;
let ss = (rem % 60) as u8;
let z = days + 719_468;
let era = if z >= 0 { z / 146_097 } else { (z - 146_096) / 146_097 };
let doe = (z - era * 146_097) as u32;
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
let y = yoe as i64 + era * 400;
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
let mp = (5 * doy + 2) / 153;
let d = (doy - (153 * mp + 2) / 5 + 1) as u8;
let m = (if mp < 10 { mp + 3 } else { mp - 9 }) as u8;
let year = if m <= 2 { y + 1 } else { y } as i32;
(year, m, d, hh, mm, ss)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fills_filesize_and_extension() {
let mut fa = FileAnalyze::new(b"");
fa.stream_prepare(StreamKind::General);
let info = FileLevelInfo {
file_size: 12548,
extension: Some("jpg"),
modified_unix_secs: None,
local_offset_secs: 0,
};
fill_file_level_fields(&mut fa, &info);
assert_eq!(
fa.retrieve(StreamKind::General, 0, "FileSize")
.map(|z| z.as_str().to_owned())
.as_deref(),
Some("12548")
);
assert_eq!(
fa.retrieve(StreamKind::General, 0, "FileExtension")
.map(|z| z.as_str().to_owned())
.as_deref(),
Some("jpg")
);
}
#[test]
fn propagates_video_framerate_and_framecount_to_general() {
let mut fa = FileAnalyze::new(b"");
fa.stream_prepare(StreamKind::General);
fa.set_field(StreamKind::Video, 0, "FrameRate", "24.000");
fa.set_field(StreamKind::Video, 0, "FrameCount", "1440");
let info = FileLevelInfo {
file_size: 1,
extension: None,
modified_unix_secs: None,
local_offset_secs: 0,
};
fill_file_level_fields(&mut fa, &info);
assert_eq!(
fa.retrieve(StreamKind::General, 0, "FrameRate")
.map(|z| z.as_str().to_owned())
.as_deref(),
Some("24.000")
);
assert_eq!(
fa.retrieve(StreamKind::General, 0, "FrameCount")
.map(|z| z.as_str().to_owned())
.as_deref(),
Some("1440")
);
}
#[test]
fn does_not_propagate_framerate_without_video() {
let mut fa = FileAnalyze::new(b"");
fa.stream_prepare(StreamKind::General);
fa.set_field(StreamKind::Audio, 0, "Format", "AAC");
let info = FileLevelInfo {
file_size: 1,
extension: None,
modified_unix_secs: None,
local_offset_secs: 0,
};
fill_file_level_fields(&mut fa, &info);
assert!(fa.retrieve(StreamKind::General, 0, "FrameRate").is_none());
}
#[test]
fn computes_overall_bitrate_from_duration() {
let mut fa = FileAnalyze::new(b"");
fa.stream_prepare(StreamKind::General);
fa.set_field(StreamKind::General, 0, "Duration", "1000");
let info = FileLevelInfo {
file_size: 125_000, extension: None,
modified_unix_secs: None,
local_offset_secs: 0,
};
fill_file_level_fields(&mut fa, &info);
assert_eq!(
fa.retrieve(StreamKind::General, 0, "OverallBitRate")
.map(|z| z.as_str().to_owned())
.as_deref(),
Some("1000000")
);
}
#[test]
fn formats_modified_date_utc_and_local() {
let mut fa = FileAnalyze::new(b"");
fa.stream_prepare(StreamKind::General);
let info = FileLevelInfo {
file_size: 1,
extension: None,
modified_unix_secs: Some(1_609_459_200),
local_offset_secs: 3600, };
fill_file_level_fields(&mut fa, &info);
assert_eq!(
fa.retrieve(StreamKind::General, 0, "File_Modified_Date")
.map(|z| z.as_str().to_owned())
.as_deref(),
Some("2021-01-01 00:00:00 UTC")
);
assert_eq!(
fa.retrieve(StreamKind::General, 0, "File_Modified_Date_Local")
.map(|z| z.as_str().to_owned())
.as_deref(),
Some("2021-01-01 01:00:00")
);
}
}