Av1an/av1an-core/src/vapoursynth.rs

272 lines
7 KiB
Rust

use std::collections::HashSet;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail};
use once_cell::sync::Lazy;
use path_abs::PathAbs;
use vapoursynth::prelude::*;
use vapoursynth::video_info::VideoInfo;
use super::ChunkMethod;
static VAPOURSYNTH_PLUGINS: Lazy<HashSet<String>> = Lazy::new(|| {
let environment = Environment::new().expect("Failed to initialize VapourSynth environment");
let core = environment
.get_core()
.expect("Failed to get VapourSynth core");
let plugins = core.plugins();
plugins
.keys()
.filter_map(|plugin| {
plugins
.get::<&[u8]>(plugin)
.ok()
.and_then(|slice| simdutf8::basic::from_utf8(slice).ok())
.and_then(|s| s.split(';').nth(1))
.map(ToOwned::to_owned)
})
.collect()
});
pub fn is_lsmash_installed() -> bool {
static LSMASH_PRESENT: Lazy<bool> =
Lazy::new(|| VAPOURSYNTH_PLUGINS.contains("systems.innocent.lsmas"));
*LSMASH_PRESENT
}
pub fn is_ffms2_installed() -> bool {
static FFMS2_PRESENT: Lazy<bool> =
Lazy::new(|| VAPOURSYNTH_PLUGINS.contains("com.vapoursynth.ffms2"));
*FFMS2_PRESENT
}
pub fn best_available_chunk_method() -> ChunkMethod {
if is_lsmash_installed() {
ChunkMethod::LSMASH
} else if is_ffms2_installed() {
ChunkMethod::FFMS2
} else {
ChunkMethod::Hybrid
}
}
fn get_clip_info(env: &mut Environment) -> VideoInfo {
// Get the output node.
const OUTPUT_INDEX: i32 = 0;
#[cfg(feature = "vapoursynth_new_api")]
let (node, _) = env.get_output(OUTPUT_INDEX).unwrap();
#[cfg(not(feature = "vapoursynth_new_api"))]
let node = env.get_output(OUTPUT_INDEX).unwrap();
node.info()
}
/// Get the number of frames from an environment that has already been
/// evaluated on a script.
fn get_num_frames(env: &mut Environment) -> anyhow::Result<usize> {
let info = get_clip_info(env);
let num_frames = {
if Property::Variable == info.format {
bail!("Cannot output clips with varying format");
}
if Property::Variable == info.resolution {
bail!("Cannot output clips with varying dimensions");
}
if Property::Variable == info.framerate {
bail!("Cannot output clips with varying framerate");
}
#[cfg(feature = "vapoursynth_new_api")]
let num_frames = info.num_frames;
#[cfg(not(feature = "vapoursynth_new_api"))]
let num_frames = {
match info.num_frames {
Property::Variable => {
bail!("Cannot output clips with unknown length");
}
Property::Constant(x) => x,
}
};
num_frames
};
assert!(num_frames != 0, "vapoursynth reported 0 frames");
Ok(num_frames)
}
fn get_frame_rate(env: &mut Environment) -> anyhow::Result<f64> {
let info = get_clip_info(env);
match info.framerate {
Property::Variable => bail!("Cannot output clips with varying framerate"),
Property::Constant(fps) => Ok(fps.numerator as f64 / fps.denominator as f64),
}
}
/// Get the bit depth from an environment that has already been
/// evaluated on a script.
fn get_bit_depth(env: &mut Environment) -> anyhow::Result<usize> {
let info = get_clip_info(env);
let bits_per_sample = {
match info.format {
Property::Variable => {
bail!("Cannot output clips with variable format");
}
Property::Constant(x) => x.bits_per_sample(),
}
};
Ok(bits_per_sample as usize)
}
/// Get the resolution from an environment that has already been
/// evaluated on a script.
fn get_resolution(env: &mut Environment) -> anyhow::Result<(u32, u32)> {
let info = get_clip_info(env);
let resolution = {
match info.resolution {
Property::Variable => {
bail!("Cannot output clips with variable resolution");
}
Property::Constant(x) => x,
}
};
Ok((resolution.width as u32, resolution.height as u32))
}
/// Get the transfer characteristics from an environment that has already been
/// evaluated on a script.
fn get_transfer(env: &mut Environment) -> anyhow::Result<u8> {
// Get the output node.
const OUTPUT_INDEX: i32 = 0;
#[cfg(feature = "vapoursynth_new_api")]
let (node, _) = env.get_output(OUTPUT_INDEX).unwrap();
#[cfg(not(feature = "vapoursynth_new_api"))]
let node = env.get_output(OUTPUT_INDEX).unwrap();
let frame = node.get_frame(0)?;
let transfer = frame
.props()
.get::<i64>("_Transfer")
.map_err(|_| anyhow::anyhow!("Failed to get transfer characteristics from VS script"))?
as u8;
Ok(transfer)
}
pub fn create_vs_file(
temp: &str,
source: &Path,
chunk_method: ChunkMethod,
) -> anyhow::Result<PathBuf> {
let temp: &Path = temp.as_ref();
let source = source.canonicalize()?;
let load_script_path = temp.join("split").join("loadscript.vpy");
let mut load_script = File::create(&load_script_path)?;
let cache_file = PathAbs::new(temp.join("split").join(format!(
"cache.{}",
match chunk_method {
ChunkMethod::FFMS2 => "ffindex",
ChunkMethod::LSMASH => "lwi",
_ => return Err(anyhow!("invalid chunk method")),
}
)))?;
load_script.write_all(
// TODO should probably check if the syntax for rust strings and escaping utf and stuff like that is the same as in python
format!(
"from vapoursynth import core\n\
core.max_cache_size=1024\n\
core.{}({:?}, cachefile={:?}).set_output()",
match chunk_method {
ChunkMethod::FFMS2 => "ffms2.Source",
ChunkMethod::LSMASH => "lsmas.LWLibavSource",
_ => unreachable!(),
},
source,
cache_file
)
.as_bytes(),
)?;
Ok(load_script_path)
}
pub fn num_frames(source: &Path) -> anyhow::Result<usize> {
// Create a new VSScript environment.
let mut environment = Environment::new().unwrap();
// Evaluate the script.
environment
.eval_file(source, EvalFlags::SetWorkingDir)
.unwrap();
get_num_frames(&mut environment)
}
pub fn bit_depth(source: &Path) -> anyhow::Result<usize> {
// Create a new VSScript environment.
let mut environment = Environment::new().unwrap();
// Evaluate the script.
environment
.eval_file(source, EvalFlags::SetWorkingDir)
.unwrap();
get_bit_depth(&mut environment)
}
pub fn frame_rate(source: &Path) -> anyhow::Result<f64> {
// Create a new VSScript environment.
let mut environment = Environment::new().unwrap();
// Evaluate the script.
environment
.eval_file(source, EvalFlags::SetWorkingDir)
.unwrap();
get_frame_rate(&mut environment)
}
pub fn resolution(source: &Path) -> anyhow::Result<(u32, u32)> {
// Create a new VSScript environment.
let mut environment = Environment::new().unwrap();
// Evaluate the script.
environment
.eval_file(source, EvalFlags::SetWorkingDir)
.unwrap();
get_resolution(&mut environment)
}
/// Transfer characteristics as specified in ITU-T H.265 Table E.4.
pub fn transfer_characteristics(source: &Path) -> anyhow::Result<u8> {
// Create a new VSScript environment.
let mut environment = Environment::new().unwrap();
// Evaluate the script.
environment
.eval_file(source, EvalFlags::SetWorkingDir)
.unwrap();
get_transfer(&mut environment)
}