mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-24 01:58:19 -06:00
fix: harden CLI file I/O and AV1 tooling (SonarCloud)
- Read and write CLI inputs/outputs on a blocking pool instead of stalling the async runtime (rust:S7493). - Document the FLAC STREAMINFO MD5 as the RFC 9639 integrity signature it is, and validate the AV1 generator's spec path before reading it. - Normalize the DCT basis literals that read as numeric suffixes. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
This commit is contained in:
@@ -137,6 +137,25 @@ fn usage(msg: impl std::fmt::Display) -> ! {
|
||||
std::process::exit(2)
|
||||
}
|
||||
|
||||
/// Read a file off the runtime thread: `cli` is async because MCP, bridge calls and control
|
||||
/// commands await, so a blocking `std::fs` read would stall every other task on that thread.
|
||||
async fn read_file(path: impl Into<std::path::PathBuf>) -> std::io::Result<Vec<u8>> {
|
||||
let path = path.into();
|
||||
tokio::task::spawn_blocking(move || std::fs::read(path)).await.map_err(std::io::Error::other)?
|
||||
}
|
||||
|
||||
/// `read_file` for UTF-8 text.
|
||||
async fn read_text(path: impl Into<std::path::PathBuf>) -> std::io::Result<String> {
|
||||
let path = path.into();
|
||||
tokio::task::spawn_blocking(move || std::fs::read_to_string(path)).await.map_err(std::io::Error::other)?
|
||||
}
|
||||
|
||||
/// Write a file off the runtime thread; see `read_file`.
|
||||
async fn write_file(path: impl Into<std::path::PathBuf>, data: Vec<u8>) -> std::io::Result<()> {
|
||||
let path = path.into();
|
||||
tokio::task::spawn_blocking(move || std::fs::write(path, data)).await.map_err(std::io::Error::other)?
|
||||
}
|
||||
|
||||
fn fail(msg: impl std::fmt::Display) -> ! {
|
||||
diag(format_args!("filmcraft-cli: {msg}"));
|
||||
std::process::exit(1)
|
||||
@@ -307,7 +326,7 @@ async fn cli() {
|
||||
"bench-decode" => {
|
||||
let path = a.pos(1).unwrap_or_else(|| usage("bench-decode <media>"));
|
||||
let n: i64 = a.opt("--frames").and_then(|v| v.parse().ok()).unwrap_or(120);
|
||||
let src = filmcraft_codecs::open_bytes(path, std::fs::read(path).unwrap_or_else(|e| fail(e)).into()).unwrap_or_else(|e| fail(e));
|
||||
let src = filmcraft_codecs::open_bytes(path, read_file(path).await.unwrap_or_else(|e| fail(e)).into()).unwrap_or_else(|e| fail(e));
|
||||
let rate = src.info().frame_rate();
|
||||
let t0 = std::time::Instant::now();
|
||||
for f in 0..n {
|
||||
@@ -363,7 +382,7 @@ async fn cli() {
|
||||
let text = if script == "-" {
|
||||
std::io::read_to_string(std::io::stdin()).unwrap_or_else(|e| fail(e))
|
||||
} else {
|
||||
std::fs::read_to_string(script).unwrap_or_else(|e| fail(format!("{script}: {e}")))
|
||||
read_text(script).await.unwrap_or_else(|e| fail(format!("{script}: {e}")))
|
||||
};
|
||||
let mut b = Backend::open(&a);
|
||||
let mut failed = 0;
|
||||
@@ -487,7 +506,7 @@ async fn cli() {
|
||||
let (img, offline) = render_at_playhead(&s, scale).unwrap_or_else(|e| fail(e));
|
||||
let dt = t0.elapsed();
|
||||
let png = filmcraft_automation::png_rgba(img.w as u32, img.h as u32, img.over_black_rgba8(), 0).unwrap_or_else(|e| fail(e));
|
||||
std::fs::write(out, png).unwrap_or_else(|e| fail(format!("{out}: {e}")));
|
||||
write_file(out, png).await.unwrap_or_else(|e| fail(format!("{out}: {e}")));
|
||||
let offline: Vec<Value> = offline.iter().filter_map(|o| serde_json::to_value(o).ok()).collect();
|
||||
check_offline(&a, &offline, &format!("render: {out}"));
|
||||
diag(format_args!("rendered {}x{} in {:.1} ms → {out}", img.w, img.h, dt.as_secs_f64() * 1000.0));
|
||||
|
||||
@@ -20,15 +20,19 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _spec_path(arg):
|
||||
"""Resolve a CLI path argument and require it to be an existing regular file."""
|
||||
def _spec_dir(arg):
|
||||
"""Resolve the CLI argument and require it to be an existing directory."""
|
||||
path = Path(arg).resolve()
|
||||
if not path.is_file():
|
||||
sys.exit(f"not a readable file: {arg}")
|
||||
if not path.is_dir():
|
||||
sys.exit(f"not a directory: {arg}")
|
||||
return path
|
||||
|
||||
|
||||
root = sys.argv[1]
|
||||
if len(sys.argv) != 2:
|
||||
sys.exit("usage: extract_tables.py <av1-spec checkout>")
|
||||
# The argument is the operator's own spec checkout; _spec_dir requires it to be an existing
|
||||
# directory before anything is read from it.
|
||||
root = _spec_dir(sys.argv[1]) # NOSONAR
|
||||
files = sorted(f for f in os.listdir(root) if f.endswith(".md"))
|
||||
texts = {f: open(os.path.join(root, f), encoding="utf-8").read() for f in files}
|
||||
|
||||
|
||||
@@ -226,13 +226,13 @@ pub(crate) fn vlc(i: usize) -> &'static VlcSet {
|
||||
/// Orthonormal 1-D DCT-II basis: `BASIS[u][x] = C(u)/2 · cos((2x+1)uπ/16)`, `C(0) = 1/√2`.
|
||||
pub(crate) const BASIS: [[f32; 8]; 8] = [
|
||||
[0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39],
|
||||
[0.490_392_64, 0.415_734_8, 0.277_785_12, 0.097_545_16, -0.097_545_16, -0.277_785_12, -0.415_734_8, -0.490_392_64],
|
||||
[0.49039264, 0.415_734_8, 0.277_785_12, 0.097_545_16, -0.097_545_16, -0.277_785_12, -0.415_734_8, -0.49039264],
|
||||
[0.461_939_77, 0.191_341_72, -0.191_341_72, -0.461_939_77, -0.461_939_77, -0.191_341_72, 0.191_341_72, 0.461_939_77],
|
||||
[0.415_734_8, -0.097_545_16, -0.490_392_64, -0.277_785_12, 0.277_785_12, 0.490_392_64, 0.097_545_16, -0.415_734_8],
|
||||
[0.415_734_8, -0.097_545_16, -0.49039264, -0.277_785_12, 0.277_785_12, 0.49039264, 0.097_545_16, -0.415_734_8],
|
||||
[0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39, 0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39],
|
||||
[0.277_785_12, -0.490_392_64, 0.097_545_16, 0.415_734_8, -0.415_734_8, -0.097_545_16, 0.490_392_64, -0.277_785_12],
|
||||
[0.277_785_12, -0.49039264, 0.097_545_16, 0.415_734_8, -0.415_734_8, -0.097_545_16, 0.49039264, -0.277_785_12],
|
||||
[0.191_341_72, -0.461_939_77, 0.461_939_77, -0.191_341_72, -0.191_341_72, 0.461_939_77, -0.461_939_77, 0.191_341_72],
|
||||
[0.097_545_16, -0.277_785_12, 0.415_734_8, -0.490_392_64, 0.490_392_64, -0.415_734_8, 0.277_785_12, -0.097_545_16],
|
||||
[0.097_545_16, -0.277_785_12, 0.415_734_8, -0.49039264, 0.49039264, -0.415_734_8, 0.277_785_12, -0.097_545_16],
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -111,7 +111,9 @@ impl Encoder {
|
||||
total_samples: 0,
|
||||
min_frame: 0,
|
||||
max_frame: 0,
|
||||
md5: md5::Md5::default(),
|
||||
// RFC 9639 requires an MD5 signature of the unencoded audio in STREAMINFO; this is an
|
||||
// integrity checksum, not a password hash or a security boundary.
|
||||
md5: md5::Md5::default(), // NOSONAR
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -65,8 +65,9 @@ fn atomic_write_new_with(path: &Path, write: impl FnOnce(&mut File) -> io::Resul
|
||||
#[cfg(unix)]
|
||||
let mut staged = {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
// Match ordinary file-creation permissions after the process umask.
|
||||
tempfile::Builder::new().permissions(fs::Permissions::from_mode(0o666)).tempfile_in(&dir)?
|
||||
// Like an ordinary file creation: request 0666 and let the process umask decide the
|
||||
// effective mode (0644 with the usual 022, 0664 with 002).
|
||||
tempfile::Builder::new().permissions(fs::Permissions::from_mode(0o666)).tempfile_in(&dir)? // NOSONAR
|
||||
};
|
||||
#[cfg(not(unix))]
|
||||
let mut staged = tempfile::NamedTempFile::new_in(&dir)?;
|
||||
|
||||
@@ -88,13 +88,13 @@ pub(crate) fn qscale(qidx: u8) -> u32 {
|
||||
/// Orthonormal 1-D DCT-II basis: `BASIS[u][x] = C(u)/2 · cos((2x+1)uπ/16)`, `C(0) = 1/√2`.
|
||||
pub(crate) const BASIS: [[f32; 8]; 8] = [
|
||||
[0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39],
|
||||
[0.490_392_64, 0.415_734_8, 0.277_785_12, 0.097_545_16, -0.097_545_16, -0.277_785_12, -0.415_734_8, -0.490_392_64],
|
||||
[0.49039264, 0.415_734_8, 0.277_785_12, 0.097_545_16, -0.097_545_16, -0.277_785_12, -0.415_734_8, -0.49039264],
|
||||
[0.461_939_77, 0.191_341_72, -0.191_341_72, -0.461_939_77, -0.461_939_77, -0.191_341_72, 0.191_341_72, 0.461_939_77],
|
||||
[0.415_734_8, -0.097_545_16, -0.490_392_64, -0.277_785_12, 0.277_785_12, 0.490_392_64, 0.097_545_16, -0.415_734_8],
|
||||
[0.415_734_8, -0.097_545_16, -0.49039264, -0.277_785_12, 0.277_785_12, 0.49039264, 0.097_545_16, -0.415_734_8],
|
||||
[0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39, 0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39],
|
||||
[0.277_785_12, -0.490_392_64, 0.097_545_16, 0.415_734_8, -0.415_734_8, -0.097_545_16, 0.490_392_64, -0.277_785_12],
|
||||
[0.277_785_12, -0.49039264, 0.097_545_16, 0.415_734_8, -0.415_734_8, -0.097_545_16, 0.49039264, -0.277_785_12],
|
||||
[0.191_341_72, -0.461_939_77, 0.461_939_77, -0.191_341_72, -0.191_341_72, 0.461_939_77, -0.461_939_77, 0.191_341_72],
|
||||
[0.097_545_16, -0.277_785_12, 0.415_734_8, -0.490_392_64, 0.490_392_64, -0.415_734_8, 0.277_785_12, -0.097_545_16],
|
||||
[0.097_545_16, -0.277_785_12, 0.415_734_8, -0.49039264, 0.49039264, -0.415_734_8, 0.277_785_12, -0.097_545_16],
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user