Skip to content

Commit

Permalink
Add DGDecNV and BestSource chunk method (master-of-zen#776)
Browse files Browse the repository at this point in the history
  • Loading branch information
Simulping authored Nov 26, 2023
1 parent a9d5d65 commit aa7058c
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 24 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ Prerequisites:
Optional:

- [L-SMASH](https://github.com/AkarinVS/L-SMASH-Works) VapourSynth plugin for better chunking (recommended)
- [DGDecNV](https://www.rationalqm.us/dgdecnv/dgdecnv.html) Vapoursynth plugin for very fast and accurate chunking, `dgindexnv` executable needs to be present in system path and an NVIDIA GPU with CUVID
- [ffms2](https://github.com/FFMS/ffms2) VapourSynth plugin for better chunking
- [bestsource](https://github.com/vapoursynth/bestsource) Vapoursynth plugin for slow but accurate chunking
- [mkvmerge](https://mkvtoolnix.download/) to use mkvmerge instead of FFmpeg for file concatenation
- [VMAF](https://github.com/Netflix/vmaf) to calculate VMAF scores and to use [target quality mode](docs/TargetQuality.md)

Expand Down
7 changes: 5 additions & 2 deletions av1an-core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ impl Av1anContext {
// generated when vspipe is first called), so it's not worth adding all the extra complexity.
if (self.args.input.is_vapoursynth()
|| (self.args.input.is_video()
&& matches!(self.args.chunk_method, ChunkMethod::LSMASH | ChunkMethod::FFMS2)))
&& matches!(self.args.chunk_method, ChunkMethod::LSMASH | ChunkMethod::FFMS2 | ChunkMethod::DGDECNV | ChunkMethod::BESTSOURCE)))
&& !self.args.resume
{
self.vs_script = Some(match &self.args.input {
Expand Down Expand Up @@ -671,7 +671,10 @@ impl Av1anContext {
fn create_encoding_queue(&mut self, scenes: &[Scene]) -> anyhow::Result<Vec<Chunk>> {
let mut chunks = match &self.args.input {
Input::Video(_) => match self.args.chunk_method {
ChunkMethod::FFMS2 | ChunkMethod::LSMASH => {
ChunkMethod::FFMS2
| ChunkMethod::LSMASH
| ChunkMethod::DGDECNV
| ChunkMethod::BESTSOURCE => {
let vs_script = self.vs_script.as_ref().unwrap().as_path();
self.create_video_queue_vs(scenes, vs_script)
}
Expand Down
4 changes: 4 additions & 0 deletions av1an-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,10 @@ pub enum ChunkMethod {
FFMS2,
#[strum(serialize = "lsmash")]
LSMASH,
#[strum(serialize = "dgdecnv")]
DGDECNV,
#[strum(serialize = "bestsource")]
BESTSOURCE,
}

#[derive(
Expand Down
16 changes: 15 additions & 1 deletion av1an-core/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use crate::concat::ConcatMethod;
use crate::encoder::Encoder;
use crate::parse::valid_params;
use crate::target_quality::TargetQuality;
use crate::vapoursynth::{is_ffms2_installed, is_lsmash_installed};
use crate::vapoursynth::{
is_bestsource_installed, is_dgdecnv_installed, is_ffms2_installed, is_lsmash_installed,
};
use crate::vmaf::validate_libvmaf;
use crate::{ChunkMethod, ChunkOrdering, Input, ScenecutMethod, SplitMethod, Verbosity};

Expand Down Expand Up @@ -125,6 +127,18 @@ properly into a mkv file. Specify mkvmerge as the concatenation method by settin
"FFMS2 is not installed, but it was specified as the chunk method"
);
}
if self.chunk_method == ChunkMethod::DGDECNV && which::which("dgindexnv").is_err() {
ensure!(
is_dgdecnv_installed(),
"Either DGDecNV is not installed or DGIndexNV is not in system path, but it was specified as the chunk method"
);
}
if self.chunk_method == ChunkMethod::BESTSOURCE {
ensure!(
is_bestsource_installed(),
"BestSource is not installed, but it was specified as the chunk method"
);
}
if self.chunk_method == ChunkMethod::Select {
warn!("It is not recommended to use the \"select\" chunk method, as it is very slow");
}
Expand Down
87 changes: 71 additions & 16 deletions av1an-core/src/vapoursynth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail};
use once_cell::sync::Lazy;
use path_abs::PathAbs;
use std::process::Command;
use vapoursynth::prelude::*;
use vapoursynth::video_info::VideoInfo;

Expand Down Expand Up @@ -45,11 +46,29 @@ pub fn is_ffms2_installed() -> bool {
*FFMS2_PRESENT
}

pub fn is_dgdecnv_installed() -> bool {
static DGDECNV_PRESENT: Lazy<bool> =
Lazy::new(|| VAPOURSYNTH_PLUGINS.contains("com.vapoursynth.dgdecodenv"));

*DGDECNV_PRESENT
}

pub fn is_bestsource_installed() -> bool {
static BESTSOURCE_PRESENT: Lazy<bool> =
Lazy::new(|| VAPOURSYNTH_PLUGINS.contains("com.vapoursynth.bestsource"));

*BESTSOURCE_PRESENT
}

pub fn best_available_chunk_method() -> ChunkMethod {
if is_lsmash_installed() {
ChunkMethod::LSMASH
} else if is_ffms2_installed() {
ChunkMethod::FFMS2
} else if is_dgdecnv_installed() {
ChunkMethod::DGDECNV
} else if is_bestsource_installed() {
ChunkMethod::BESTSOURCE
} else {
ChunkMethod::Hybrid
}
Expand Down Expand Up @@ -185,26 +204,62 @@ pub fn create_vs_file(
match chunk_method {
ChunkMethod::FFMS2 => "ffindex",
ChunkMethod::LSMASH => "lwi",
ChunkMethod::DGDECNV => "dgi",
ChunkMethod::BESTSOURCE => "json",
_ => 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(),
)?;
if chunk_method == ChunkMethod::DGDECNV {
// Run dgindexnv to generate the .dgi index file
let dgindexnv_output = temp.join("split").join("index.dgi");

Command::new("dgindexnv")
.arg("-h")
.arg("-i")
.arg(source)
.arg("-o")
.arg(&dgindexnv_output)
.output()?;

let dgindex_path = dgindexnv_output.canonicalize()?;
load_script.write_all(
format!(
"from vapoursynth import core\n\
core.max_cache_size=1024\n\
core.dgdecodenv.DGSource(source={:?}).set_output()",
dgindex_path
)
.as_bytes(),
)?;
} else if chunk_method == ChunkMethod::BESTSOURCE {
load_script.write_all(
format!(
"from vapoursynth import core\n\
core.max_cache_size=1024\n\
core.bs.VideoSource({:?}, cachepath={:?}).set_output()",
source, cache_file
)
.as_bytes(),
)?;
} else {
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)
}
Expand Down
17 changes: 14 additions & 3 deletions av1an/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ fn version() -> &'static str {
"\
* VapourSynth Plugins
systems.innocent.lsmas : {}
com.vapoursynth.ffms2 : {}",
com.vapoursynth.ffms2 : {}
com.vapoursynth.dgdecodenv : {}
com.vapoursynth.bestsource : {}",
isfound(vapoursynth::is_lsmash_installed()),
isfound(vapoursynth::is_ffms2_installed())
isfound(vapoursynth::is_ffms2_installed()),
isfound(vapoursynth::is_dgdecnv_installed()),
isfound(vapoursynth::is_bestsource_installed())
)
}

Expand Down Expand Up @@ -332,6 +336,13 @@ pub struct CliOpts {
/// cause artifacts in the piped output). Slightly faster than lsmash for y4m input. Requires the ffms2 vapoursynth plugin to be
/// installed.
///
/// dgdecnv - Very fast, but only decodes AVC, HEVC, MPEG-2, and VC1. Does not require intermediate files.
/// Requires dgindexnv to be present in system path, NVIDIA GPU that support CUDA video decoding, and dgdecnv vapoursynth plugin
/// to be installed.
///
/// bestsource - Very slow but accurate. Linearly decodes input files, very slow. Does not require intermediate files, requires the BestSource vapoursynth plugin
/// to be installed.
///
/// Methods that only require ffmpeg:
///
/// hybrid - Uses a combination of segment and select. Usually accurate but requires intermediate files (which can be large). Avoids
Expand All @@ -344,7 +355,7 @@ pub struct CliOpts {
/// segment - Create chunks based on keyframes in the source. Not frame exact, as it can only split on keyframes in the source.
/// Requires intermediate files (which can be large).
///
/// Default: lsmash (if available), otherwise ffms2 (if available), otherwise hybrid.
/// Default: lsmash (if available), otherwise ffms2 (if available), otherwise DGDecNV (if available), otherwise bestsource (if available), otherwise hybrid.
#[clap(short = 'm', long, help_heading = "Encoding")]
pub chunk_method: Option<ChunkMethod>,

Expand Down
11 changes: 9 additions & 2 deletions docs/CLI.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,13 @@
bugs that are not present in lsmash (that can cause artifacts in the piped output).
Slightly faster than lsmash for y4m input. Requires the ffms2 vapoursynth plugin to
be installed.
dgdecnv - Very fast, but only decodes AVC, HEVC, MPEG-2, and VC1. Does not require intermediate files.
Requires dgindexnv to be present in system path, NVIDIA GPU that support CUDA video decoding, and dgdecnv vapoursynth plugin
to be installed.
bestsource - Very slow but accurate. Linearly decodes input files. Does not require intermediate files, requires the BestSource vapoursynth plugin
to be installed.
Methods that only require ffmpeg:
Expand All @@ -253,9 +260,9 @@
segment - Create chunks based on keyframes in the source. Not frame exact, as it can
only split on keyframes in the source. Requires intermediate files (which can be large).
Default: lsmash (if available), otherwise ffms2 (if available), otherwise hybrid.
Default: lsmash (if available), otherwise ffms2 (if available), otherwise DGDecNV (if available), otherwise bestsource (if available), otherwise hybrid.
[possible values: segment, select, ffms2, lsmash, hybrid]
[possible values: segment, select, ffms2, lsmash, dgdecnv, bestsource, hybrid]
--chunk-order <CHUNK_ORDER>
The order in which av1an will encode chunks
Expand Down

0 comments on commit aa7058c

Please sign in to comment.