Skip to content

Commit

Permalink
Fix VMAF score parse failure of certain successful ffmpeg outputs
Browse files Browse the repository at this point in the history
  • Loading branch information
alexheretic committed Jul 8, 2024
1 parent cbd22f0 commit accc5e5
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 60 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Unreleased
* Fix VMAF score parse failure of certain successful ffmpeg outputs.

# v0.7.15
* Show full ffmpeg command after errors.
* For *_vaapi encoders map `--crf` to ffmpeg `-q` (instead of `-qp`).
Expand Down
72 changes: 36 additions & 36 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 31 additions & 20 deletions src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub fn exit_ok_stderr(
pub fn cmd_err(err: impl Display, cmd_str: &str, stderr: &Chunks) -> anyhow::Error {
anyhow!(
"{err}\n----cmd-----\n{cmd_str}\n---stderr---\n{}\n------------",
stderr.out.trim()
String::from_utf8_lossy(&stderr.out).trim()
)
}

Expand Down Expand Up @@ -148,44 +148,55 @@ fn parse_label_size(label: &str, line: &str) -> Option<u64> {
/// Stores up to ~4k chunk data on the heap.
#[derive(Default)]
pub struct Chunks {
out: String,
out: Vec<u8>,
}

impl Chunks {
/// Append a chunk.
pub fn push(&mut self, chunk: &[u8]) {
const MAX_LEN: usize = 4000;

self.out.push_str(&String::from_utf8_lossy(chunk));
self.out.extend(chunk);

// truncate beginning if too long
let len = self.out.len();
if len > MAX_LEN + 100 {
self.out = String::from_utf8_lossy(&self.out.as_bytes()[len - MAX_LEN..]).into();
if self.out.len() > MAX_LEN + 100 {
// remove lines until small
while self.out.len() > MAX_LEN {
let mut next_eol = self
.out
.iter()
.position(|b| *b == b'\n')
.unwrap_or(self.out.len() - 1);
if self.out.get(next_eol + 1) == Some(&b'\r') {
next_eol += 1;
}

self.out.splice(..next_eol + 1, []);
}
}
}

fn rlines(&self) -> impl Iterator<Item = &'_ str> {
self.out
.rsplit_terminator('\n')
.flat_map(|l| l.rsplit_terminator('\r'))
pub fn rfind_line(&self, predicate: impl Fn(&str) -> bool) -> Option<&str> {
let lines = self
.out
.rsplit(|b| *b == b'\n')
.flat_map(|l| l.rsplit(|b| *b == b'\r'));
for line in lines {
if let Ok(line) = std::str::from_utf8(line) {
if predicate(line) {
return Some(line);
}
}
}
None
}

/// Returns last non-empty line, if any.
pub fn last_line(&self) -> &str {
self.rlines().find(|l| !l.is_empty()).unwrap_or_default()
self.rfind_line(|l| !l.is_empty()).unwrap_or_default()
}
}

#[test]
fn rlines_rn() {
let mut chunks = Chunks::default();
chunks.push(b"something \r fooo \r\n");
let mut rlines = chunks.rlines();
assert_eq!(rlines.next(), Some(" fooo "));
assert_eq!(rlines.next(), Some("something "));
}

#[test]
fn parse_ffmpeg_progress_chunk() {
let out = "frame= 288 fps= 94 q=-0.0 size=N/A time=01:23:12.34 bitrate=N/A speed=3.94x \r";
Expand Down
Loading

0 comments on commit accc5e5

Please sign in to comment.