Av1an/av1an-core/src/vapoursynth.rs

272 lines
7 KiB
Rust
Raw Normal View History

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;
2021-09-03 19:01:11 +00:00
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");
2021-07-13 16:51:34 +00:00
let plugins = core.plugins();
plugins
2021-07-13 16:51:34 +00:00
.keys()
.filter_map(|plugin| {
plugins
.get::<&[u8]>(plugin)
.ok()
.and_then(|slice| simdutf8::basic::from_utf8(slice).ok())
2021-07-13 16:51:34 +00:00
.and_then(|s| s.split(';').nth(1))
.map(ToOwned::to_owned)
2021-07-13 16:51:34 +00:00
})
.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
2021-07-13 16:51:34 +00:00
} else {
ChunkMethod::Hybrid
2021-07-13 16:51:34 +00:00
}
}
Scene detect changes (#383) * Remove sc-downscale-height CLI option The standard scenecut detection method at 1080p is currently as fast as downsampling to 720p and running scenecut detection on that, and the fast scenecut method is significantly faster without downsampling, because the algorithm is faster than ffmpeg's resizing algorithm. As such, it doesn't make sense to have this option anymore. * Fix conditional use of newer VS APIs It makes sense to enable these by default, since the vast majority of users should have a moderately recent version of Vapoursynth. We also needed to disable the static ffmpeg as the default for now. libaom returns None for the pixel format, so it is not usable for the changes needed later in this PR. I attempted to get the git version of ffmpeg-next compiling with libdav1d, but was running into issues, I believe because Arch's package manager only includes a dynamic library for libdav1d and not a static version. It probably makes sense to disable it anyway given the issues Windows users have been having with it. * No longer convert input to 8-bit for scene detect It appears that downconverting does have an impact on scene detection accuracy. The speed penalty for running in 10-bit compared to 8-bit is only 10%. Given this, it seems preferable to run the scene detect in the more accurate format. * Skip ffmpeg intermediate step for vapoursynth input * Fix skipping of ffmpeg pipe step This optimization added in #379 was never triggered. Because the initialize() method always added a set of default params to self.ffmpeg_params, the clause to skip the ffmpeg pipe, self.ffmpeg_params.is_empty(), was unreachable. * Revert "Remove sc-downscale-height CLI option" This reverts commit a51763607faddae07a595f567d4f4cf815de3299. * Validate pixel formats based on encoder support * Fix CI build The dynamic linking of ffmpeg doesn't seem to play nicely with the old version of ffmpeg which is packaged in Ubuntu's last LTS. This enables static linking when we are running in CI to work around that issue, given that that workaround is easier than overhauling which docker container we use.
2021-10-20 16:54:48 +00:00
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> {
Scene detect changes (#383) * Remove sc-downscale-height CLI option The standard scenecut detection method at 1080p is currently as fast as downsampling to 720p and running scenecut detection on that, and the fast scenecut method is significantly faster without downsampling, because the algorithm is faster than ffmpeg's resizing algorithm. As such, it doesn't make sense to have this option anymore. * Fix conditional use of newer VS APIs It makes sense to enable these by default, since the vast majority of users should have a moderately recent version of Vapoursynth. We also needed to disable the static ffmpeg as the default for now. libaom returns None for the pixel format, so it is not usable for the changes needed later in this PR. I attempted to get the git version of ffmpeg-next compiling with libdav1d, but was running into issues, I believe because Arch's package manager only includes a dynamic library for libdav1d and not a static version. It probably makes sense to disable it anyway given the issues Windows users have been having with it. * No longer convert input to 8-bit for scene detect It appears that downconverting does have an impact on scene detection accuracy. The speed penalty for running in 10-bit compared to 8-bit is only 10%. Given this, it seems preferable to run the scene detect in the more accurate format. * Skip ffmpeg intermediate step for vapoursynth input * Fix skipping of ffmpeg pipe step This optimization added in #379 was never triggered. Because the initialize() method always added a set of default params to self.ffmpeg_params, the clause to skip the ffmpeg pipe, self.ffmpeg_params.is_empty(), was unreachable. * Revert "Remove sc-downscale-height CLI option" This reverts commit a51763607faddae07a595f567d4f4cf815de3299. * Validate pixel formats based on encoder support * Fix CI build The dynamic linking of ffmpeg doesn't seem to play nicely with the old version of ffmpeg which is packaged in Ubuntu's last LTS. This enables static linking when we are running in CI to work around that issue, given that that workaround is easier than overhauling which docker container we use.
2021-10-20 16:54:48 +00:00
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");
}
Scene detect changes (#383) * Remove sc-downscale-height CLI option The standard scenecut detection method at 1080p is currently as fast as downsampling to 720p and running scenecut detection on that, and the fast scenecut method is significantly faster without downsampling, because the algorithm is faster than ffmpeg's resizing algorithm. As such, it doesn't make sense to have this option anymore. * Fix conditional use of newer VS APIs It makes sense to enable these by default, since the vast majority of users should have a moderately recent version of Vapoursynth. We also needed to disable the static ffmpeg as the default for now. libaom returns None for the pixel format, so it is not usable for the changes needed later in this PR. I attempted to get the git version of ffmpeg-next compiling with libdav1d, but was running into issues, I believe because Arch's package manager only includes a dynamic library for libdav1d and not a static version. It probably makes sense to disable it anyway given the issues Windows users have been having with it. * No longer convert input to 8-bit for scene detect It appears that downconverting does have an impact on scene detection accuracy. The speed penalty for running in 10-bit compared to 8-bit is only 10%. Given this, it seems preferable to run the scene detect in the more accurate format. * Skip ffmpeg intermediate step for vapoursynth input * Fix skipping of ffmpeg pipe step This optimization added in #379 was never triggered. Because the initialize() method always added a set of default params to self.ffmpeg_params, the clause to skip the ffmpeg pipe, self.ffmpeg_params.is_empty(), was unreachable. * Revert "Remove sc-downscale-height CLI option" This reverts commit a51763607faddae07a595f567d4f4cf815de3299. * Validate pixel formats based on encoder support * Fix CI build The dynamic linking of ffmpeg doesn't seem to play nicely with the old version of ffmpeg which is packaged in Ubuntu's last LTS. This enables static linking when we are running in CI to work around that issue, given that that workaround is easier than overhauling which docker container we use.
2021-10-20 16:54:48 +00:00
#[cfg(feature = "vapoursynth_new_api")]
let num_frames = info.num_frames;
Scene detect changes (#383) * Remove sc-downscale-height CLI option The standard scenecut detection method at 1080p is currently as fast as downsampling to 720p and running scenecut detection on that, and the fast scenecut method is significantly faster without downsampling, because the algorithm is faster than ffmpeg's resizing algorithm. As such, it doesn't make sense to have this option anymore. * Fix conditional use of newer VS APIs It makes sense to enable these by default, since the vast majority of users should have a moderately recent version of Vapoursynth. We also needed to disable the static ffmpeg as the default for now. libaom returns None for the pixel format, so it is not usable for the changes needed later in this PR. I attempted to get the git version of ffmpeg-next compiling with libdav1d, but was running into issues, I believe because Arch's package manager only includes a dynamic library for libdav1d and not a static version. It probably makes sense to disable it anyway given the issues Windows users have been having with it. * No longer convert input to 8-bit for scene detect It appears that downconverting does have an impact on scene detection accuracy. The speed penalty for running in 10-bit compared to 8-bit is only 10%. Given this, it seems preferable to run the scene detect in the more accurate format. * Skip ffmpeg intermediate step for vapoursynth input * Fix skipping of ffmpeg pipe step This optimization added in #379 was never triggered. Because the initialize() method always added a set of default params to self.ffmpeg_params, the clause to skip the ffmpeg pipe, self.ffmpeg_params.is_empty(), was unreachable. * Revert "Remove sc-downscale-height CLI option" This reverts commit a51763607faddae07a595f567d4f4cf815de3299. * Validate pixel formats based on encoder support * Fix CI build The dynamic linking of ffmpeg doesn't seem to play nicely with the old version of ffmpeg which is packaged in Ubuntu's last LTS. This enables static linking when we are running in CI to work around that issue, given that that workaround is easier than overhauling which docker container we use.
2021-10-20 16:54:48 +00:00
#[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)
}
2021-09-03 19:01:11 +00:00
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),
}
}
Scene detect changes (#383) * Remove sc-downscale-height CLI option The standard scenecut detection method at 1080p is currently as fast as downsampling to 720p and running scenecut detection on that, and the fast scenecut method is significantly faster without downsampling, because the algorithm is faster than ffmpeg's resizing algorithm. As such, it doesn't make sense to have this option anymore. * Fix conditional use of newer VS APIs It makes sense to enable these by default, since the vast majority of users should have a moderately recent version of Vapoursynth. We also needed to disable the static ffmpeg as the default for now. libaom returns None for the pixel format, so it is not usable for the changes needed later in this PR. I attempted to get the git version of ffmpeg-next compiling with libdav1d, but was running into issues, I believe because Arch's package manager only includes a dynamic library for libdav1d and not a static version. It probably makes sense to disable it anyway given the issues Windows users have been having with it. * No longer convert input to 8-bit for scene detect It appears that downconverting does have an impact on scene detection accuracy. The speed penalty for running in 10-bit compared to 8-bit is only 10%. Given this, it seems preferable to run the scene detect in the more accurate format. * Skip ffmpeg intermediate step for vapoursynth input * Fix skipping of ffmpeg pipe step This optimization added in #379 was never triggered. Because the initialize() method always added a set of default params to self.ffmpeg_params, the clause to skip the ffmpeg pipe, self.ffmpeg_params.is_empty(), was unreachable. * Revert "Remove sc-downscale-height CLI option" This reverts commit a51763607faddae07a595f567d4f4cf815de3299. * Validate pixel formats based on encoder support * Fix CI build The dynamic linking of ffmpeg doesn't seem to play nicely with the old version of ffmpeg which is packaged in Ubuntu's last LTS. This enables static linking when we are running in CI to work around that issue, given that that workaround is easier than overhauling which docker container we use.
2021-10-20 16:54:48 +00:00
/// 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)
}
2021-09-03 19:01:11 +00:00
pub fn create_vs_file(
temp: &str,
source: &Path,
2021-09-03 19:01:11 +00:00
chunk_method: ChunkMethod,
) -> anyhow::Result<PathBuf> {
let temp: &Path = temp.as_ref();
let source = source.canonicalize()?;
2021-09-03 19:01:11 +00:00
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")),
}
)))?;
2021-09-03 19:01:11 +00:00
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\
2021-09-06 15:59:49 +00:00
core.max_cache_size=1024\n\
2021-09-03 19:01:11 +00:00
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)
2021-09-03 19:01:11 +00:00
}
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)
}
Scene detect changes (#383) * Remove sc-downscale-height CLI option The standard scenecut detection method at 1080p is currently as fast as downsampling to 720p and running scenecut detection on that, and the fast scenecut method is significantly faster without downsampling, because the algorithm is faster than ffmpeg's resizing algorithm. As such, it doesn't make sense to have this option anymore. * Fix conditional use of newer VS APIs It makes sense to enable these by default, since the vast majority of users should have a moderately recent version of Vapoursynth. We also needed to disable the static ffmpeg as the default for now. libaom returns None for the pixel format, so it is not usable for the changes needed later in this PR. I attempted to get the git version of ffmpeg-next compiling with libdav1d, but was running into issues, I believe because Arch's package manager only includes a dynamic library for libdav1d and not a static version. It probably makes sense to disable it anyway given the issues Windows users have been having with it. * No longer convert input to 8-bit for scene detect It appears that downconverting does have an impact on scene detection accuracy. The speed penalty for running in 10-bit compared to 8-bit is only 10%. Given this, it seems preferable to run the scene detect in the more accurate format. * Skip ffmpeg intermediate step for vapoursynth input * Fix skipping of ffmpeg pipe step This optimization added in #379 was never triggered. Because the initialize() method always added a set of default params to self.ffmpeg_params, the clause to skip the ffmpeg pipe, self.ffmpeg_params.is_empty(), was unreachable. * Revert "Remove sc-downscale-height CLI option" This reverts commit a51763607faddae07a595f567d4f4cf815de3299. * Validate pixel formats based on encoder support * Fix CI build The dynamic linking of ffmpeg doesn't seem to play nicely with the old version of ffmpeg which is packaged in Ubuntu's last LTS. This enables static linking when we are running in CI to work around that issue, given that that workaround is easier than overhauling which docker container we use.
2021-10-20 16:54:48 +00:00
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)
}