alllllmost ported i think

main
Bel LaPointe 2023-12-26 21:13:07 -05:00
parent fb5205c12a
commit 75534a383c
2 changed files with 226 additions and 0 deletions

224
src-lib/src/ffmpeg.rs Normal file
View File

@ -0,0 +1,224 @@
use std::str::FromStr;
use core::cmp::Ordering;
fn inspect(file: &String) -> Result<Inspection, String> {
match std::process::Command::new("ffmpeg")
.args([
"-i", file,
"-vf", "mpdecimate",
"-af", "silencedetect=n=-50dB:d=1",
"-loglevel", "debug",
"-f", "null",
"-",
])
.output() {
Ok(output) => {
let stderr = String::from_utf8(output.stderr).unwrap();
let stdout = String::from_utf8(output.stdout).unwrap();
let line_iter = stderr.split("\n").chain(stdout.split("\n"));
Ok(Inspection{
lines: line_iter.map(|x| x.to_string()).collect(),
})
},
Err(msg) => Err(format!("failed to ffmpeg {}: {}", file, msg)),
}
}
struct Inspection {
lines: Vec<String>,
}
impl Inspection {
pub fn content_spans(&self) -> Vec<ContentSpan> {
self.overlap_spans(
&self.visual_spans(),
&self.audible_spans(),
)
}
fn overlap_spans(&self, a: &Vec<ContentSpan>, b: &Vec<ContentSpan>) -> Vec<ContentSpan> {
self.merge_unsorted_spans(a.iter().chain(b.iter()).map(|x| *x).collect())
}
fn audible_spans(&self) -> Vec<ContentSpan> {
self.spans_from_transitions(self.audible_transitions())
}
fn visual_spans(&self) -> Vec<ContentSpan> {
self.spans_from_transitions(self.visual_transitions())
}
fn visual_transitions(&self) -> Vec<f32> {
let lines: Vec<_> = self.lines.iter()
.filter(|x| x.contains("Parsed_mpdecimate_0"))
.filter(|x| x.contains("keep pts:") || x.contains("drop pts:"))
.collect();
let mut lines_with_transitions = vec![];
for i in 0..lines.len() {
if lines_with_transitions.len() == 0 {
lines_with_transitions.push(lines[i]);
} else {
let this_is_keep = lines[i].contains("keep");
let prev_was_keep = lines_with_transitions.last().unwrap().contains("keep");
if this_is_keep != prev_was_keep {
lines_with_transitions.push(lines[i-1]);
lines_with_transitions.push(lines[i]);
}
}
}
lines_with_transitions.iter()
.skip(2)
.filter(|x| x.contains("keep"))
.map(|x| x.split("pts_time:").nth(1).unwrap().split(" ").nth(0).unwrap())
.map(|x| f32::from_str(x).unwrap())
.collect()
}
fn audible_transitions(&self) -> Vec<f32> {
let lines: Vec<_> = self.lines.iter()
.filter(|x| x.contains("[silencedetect @"))
.map(|x| x.split(" ").nth(4).unwrap())
.map(|x| f32::from_str(x).unwrap())
.collect();
let mut lines_with_transitions = vec![];
lines_with_transitions.push(0.0);
lines.iter().for_each(|x| lines_with_transitions.push(*x));
lines_with_transitions.push(self.duration());
lines_with_transitions
}
fn duration(&self) -> f32 {
let ts = self.lines.iter()
.filter(|x| (*x).contains("Duration: "))
.filter(|x| (*x).contains("start: "))
.filter(|x| (*x).contains("bitrate: "))
.nth(0)
.unwrap()
.split(",").nth(0).unwrap()
.split(": ").nth(1).unwrap();
let pieces: Vec<_> = ts.split(":")
.map(|x| f32::from_str(x).unwrap())
.collect();
assert_eq!(3, pieces.len());
let hours = pieces[0] * 60.0 * 60.0;
let minutes = pieces[1] * 60.0;
let seconds = pieces[2];
hours + minutes + seconds
}
fn spans_from_transitions(&self, transitions: Vec<f32>) -> Vec<ContentSpan> {
let spans: Vec<_> = self.raw_spans_from_transitions(transitions).iter()
.filter(|x| x.stop - x.start > 0.25) // TODO const
.map(|x| ContentSpan{
start: (x.start - 1.0).clamp(0.0, self.duration()),
stop: (x.stop + 1.0).clamp(0.0, self.duration()), // TODO const
})
.collect();
self.merge_spans(spans)
}
fn merge_unsorted_spans(&self, spans: Vec<ContentSpan>) -> Vec<ContentSpan> {
let mut spans: Vec<_> = spans.iter().map(|x| *x).collect();
spans.sort_by(|x, y| match x.start.partial_cmp(&y.start).unwrap() {
Ordering::Less => Ordering::Less,
Ordering::Equal => x.stop.partial_cmp(&y.stop).unwrap(),
Ordering::Greater => Ordering::Greater,
});
self.merge_spans(spans)
}
fn merge_spans(&self, spans: Vec<ContentSpan>) -> Vec<ContentSpan> {
let mut result: Vec<_> = vec![];
let mut cur = *spans.iter().nth(0).or(Some(&ContentSpan{start: 0.0, stop: 0.0})).unwrap();
for i in 1..spans.len() {
if spans[i].start - cur.stop > 5.0 { // TODO const
if !cur.empty() {
result.push(cur);
}
cur = spans[i];
} else if spans[i].stop > cur.stop {
cur.stop = spans[i].stop
}
}
result.push(cur);
result
}
fn raw_spans_from_transitions(&self, transitions: Vec<f32>) -> Vec<ContentSpan> {
let mut result = vec![];
for i in (0..transitions.len()).step_by(2) {
let start = transitions[i];
let mut stop = self.duration();
if transitions.len() > i+1 {
stop = transitions[i+1];
}
result.push(ContentSpan{start: start, stop: stop});
}
result
}
}
#[cfg(test)]
mod test_inspection {
use super::*;
#[test]
fn test_inspect() {
let file = "/Users/breel/Movies/bel_1_1.mp4".to_string();
let inspection = inspect(&file);
assert_eq!(true, inspection.is_ok());
let inspection = inspection.unwrap();
assert_eq!(28.14, inspection.duration());
assert_eq!(4, inspection.visual_transitions().len());
assert_eq!(
vec![ContentSpan{start: 1.0520501, stop: 22.0043}],
inspection.visual_spans(),
);
assert_eq!(8, inspection.audible_transitions().len());
assert_eq!(
vec![ContentSpan{start: 3.3723102, stop: 20.8207}],
inspection.audible_spans(),
);
assert_eq!(1, inspection.content_spans().len());
assert_eq!(
vec![ContentSpan{start: 1.0520501, stop: 22.0043}],
inspection.content_spans(),
);
let lines = inspection.lines;
assert!(lines.iter().filter(|x| x.contains("Parsed_mpdecimate_0")).count() > 0);
assert!(lines.iter().filter(|x| x.contains("Duration: 00")).count() > 0);
assert!(lines.iter().filter(|x| x.contains("silence_end: ")).count() > 0);
assert!(lines.iter().filter(|x| x.contains("silence_start: ")).count() > 0);
}
}
#[derive(Debug, PartialEq, Clone, Copy, PartialOrd)]
pub struct ContentSpan {
start: f32,
stop: f32,
}
impl ContentSpan {
pub fn duration(&self) -> f32 {
self.stop - self.start
}
fn empty(&self) -> bool {
*self == (ContentSpan{start: 0.0, stop: 0.0})
}
}
#[cfg(test)]
mod test_content_span {
use super::*;
#[test]
fn test_duration() {
assert_eq!(1.0, ContentSpan{start: 1.0, stop: 2.0}.duration());
}
}

View File

@ -1,3 +1,5 @@
mod ffmpeg;
pub fn add(left: usize, right: usize) -> usize { pub fn add(left: usize, right: usize) -> usize {
left + right left + right
} }