Skip to content

Commit

Permalink
Add Vapoursynth decoding support to allow for avoiding piping (#168)
Browse files Browse the repository at this point in the history
  • Loading branch information
shssoichiro authored May 22, 2024
1 parent e6725c7 commit 574d0c1
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 92 deletions.
141 changes: 69 additions & 72 deletions .github/workflows/av-scenechange.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,88 +9,85 @@ on:
- master

jobs:

clippy-rustfmt:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt

- name: Install nasm
env:
LINK: http://debian-archive.trafficmanager.net/debian/pool/main/n/nasm
NASM_VERSION: 2.15.05-1
NASM_SHA256: >-
c860caec653b865d5b83359452d97b11f1b3ba5b18b07cac554cf72550b3bfc9
run: |
curl -O "$LINK/nasm_${NASM_VERSION}_amd64.deb"
echo "$NASM_SHA256 nasm_${NASM_VERSION}_amd64.deb" | sha256sum --check
sudo dpkg -i "nasm_${NASM_VERSION}_amd64.deb"
- name: Run rustfmt
run: |
cargo fmt -- --check --verbose
- name: Run clippy
uses: clechasseur/rs-clippy-check@v3
with:
args: -- -D warnings --verbose -A clippy::wrong-self-convention -A clippy::many_single_char_names -A clippy::upper-case-acronyms
- uses: actions/checkout@v4

- name: Install stable
uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt

- name: Install nasm
env:
LINK: http://debian-archive.trafficmanager.net/debian/pool/main/n/nasm
NASM_VERSION: 2.15.05-1
NASM_SHA256: >-
c860caec653b865d5b83359452d97b11f1b3ba5b18b07cac554cf72550b3bfc9
run: |
curl -O "$LINK/nasm_${NASM_VERSION}_amd64.deb"
echo "$NASM_SHA256 nasm_${NASM_VERSION}_amd64.deb" | sha256sum --check
sudo dpkg -i "nasm_${NASM_VERSION}_amd64.deb"
- name: Run rustfmt
run: |
cargo fmt -- --check --verbose
- name: Run clippy
uses: clechasseur/rs-clippy-check@v3
with:
args: -- -D warnings --verbose -A clippy::wrong-self-convention -A clippy::many_single_char_names -A clippy::upper-case-acronyms

build:

strategy:
matrix:
platform: [ubuntu-latest, windows-latest]

runs-on: ${{ matrix.platform }}

steps:
- uses: actions/checkout@v4

- name: Install stable
uses: dtolnay/rust-toolchain@stable

- name: Install nasm for Ubuntu
if: matrix.platform == 'ubuntu-latest'
env:
LINK: http://debian-archive.trafficmanager.net/debian/pool/main/n/nasm
NASM_VERSION: 2.15.05-1
NASM_SHA256: >-
c860caec653b865d5b83359452d97b11f1b3ba5b18b07cac554cf72550b3bfc9
run: |
curl -O "$LINK/nasm_${NASM_VERSION}_amd64.deb"
echo "$NASM_SHA256 nasm_${NASM_VERSION}_amd64.deb" | sha256sum --check
sudo dpkg -i "nasm_${NASM_VERSION}_amd64.deb"
- name: Install nasm for Windows
if: matrix.platform == 'windows-latest'
run: |
$NASM_VERSION="2.15.05"
$LINK="https://www.nasm.us/pub/nasm/releasebuilds/$NASM_VERSION/win64"
curl -LO "$LINK/nasm-$NASM_VERSION-win64.zip"
7z e -y "nasm-$NASM_VERSION-win64.zip" -o"C:\nasm"
echo "C:\nasm" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Set MSVC x86_64 linker path
if: matrix.platform == 'windows-latest'
run: |
$LinkGlob = "VC\Tools\MSVC\*\bin\Hostx64\x64"
$env:PATH = "$env:PATH;${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer"
$LinkPath = vswhere -latest -products * -find "$LinkGlob" |
Select-Object -Last 1
echo "$LinkPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Build
run: cargo build --all-features --tests --benches

- name: Run tests
run: cargo test --all-features

- name: Generate docs
run: cargo doc --all-features --no-deps
- uses: actions/checkout@v4

- name: Install stable
uses: dtolnay/rust-toolchain@stable

- name: Install nasm for Ubuntu
if: matrix.platform == 'ubuntu-latest'
env:
LINK: http://debian-archive.trafficmanager.net/debian/pool/main/n/nasm
NASM_VERSION: 2.15.05-1
NASM_SHA256: >-
c860caec653b865d5b83359452d97b11f1b3ba5b18b07cac554cf72550b3bfc9
run: |
curl -O "$LINK/nasm_${NASM_VERSION}_amd64.deb"
echo "$NASM_SHA256 nasm_${NASM_VERSION}_amd64.deb" | sha256sum --check
sudo dpkg -i "nasm_${NASM_VERSION}_amd64.deb"
- name: Install nasm for Windows
if: matrix.platform == 'windows-latest'
run: |
$NASM_VERSION="2.15.05"
$LINK="https://www.nasm.us/pub/nasm/releasebuilds/$NASM_VERSION/win64"
curl -LO "$LINK/nasm-$NASM_VERSION-win64.zip"
7z e -y "nasm-$NASM_VERSION-win64.zip" -o"C:\nasm"
echo "C:\nasm" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Set MSVC x86_64 linker path
if: matrix.platform == 'windows-latest'
run: |
$LinkGlob = "VC\Tools\MSVC\*\bin\Hostx64\x64"
$env:PATH = "$env:PATH;${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer"
$LinkPath = vswhere -latest -products * -find "$LinkGlob" |
Select-Object -Last 1
echo "$LinkPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: Build
run: cargo build --features binary,devel,tracing,serialize --tests --benches

- name: Run tests
run: cargo test --features binary,devel,tracing,serialize

- name: Generate docs
run: cargo doc --features binary,devel,tracing,serialize --no-deps
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
**/*.rs.bk
/.idea
/.vscode
29 changes: 29 additions & 0 deletions Cargo.lock

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

18 changes: 14 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ clap = { version = "4.0.22", optional = true, features = ["derive"] }
serde = { version = "1.0.123", optional = true, features = ["derive"] }
serde_json = { version = "1.0.62", optional = true }
rav1e = { version = "0.7.0", default-features = false, features = [
"asm",
"scenechange",
"threading",
"asm",
"scenechange",
"threading",
] }
log = { version = "0.4.14", optional = true }
console = { version = "0.15", optional = true }
Expand All @@ -25,6 +25,16 @@ tracing-subscriber = { version = "0.3.18", optional = true }
tracing-chrome = { version = "0.7.1", optional = true }
tracing = { version = "0.1.40", optional = true }

[dependencies.vapoursynth]
version = "0.4.0"
features = [
"vsscript-functions",
"vapoursynth-functions",
"vapoursynth-api-32",
"vsscript-api-31",
]
optional = true

[features]
default = ["binary"]
binary = ["clap", "serialize"]
Expand All @@ -34,7 +44,7 @@ tracing = [
"tracing-subscriber",
"tracing-chrome",
"dep:tracing",
"rav1e/tracing"
"rav1e/tracing",
]

[[bin]]
Expand Down
40 changes: 40 additions & 0 deletions src/decoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::io::Read;

use rav1e::{Frame, Pixel};

#[cfg(feature = "vapoursynth")]
use crate::vapoursynth::VapoursynthDecoder;
use crate::y4m::VideoDetails;

pub enum Decoder<R: Read> {
Y4m(y4m::Decoder<R>),
#[cfg(feature = "vapoursynth")]
Vapoursynth(VapoursynthDecoder),
}

impl<R: Read> Decoder<R> {
/// # Errors
///
/// - If using a Vapoursynth script that contains an unsupported video format.
pub fn get_video_details(&self) -> anyhow::Result<VideoDetails> {
match self {
Decoder::Y4m(dec) => Ok(crate::y4m::get_video_details(dec)),
#[cfg(feature = "vapoursynth")]
Decoder::Vapoursynth(dec) => dec.get_video_details(),
}
}

/// # Errors
///
/// - If a frame cannot be read.
pub fn read_video_frame<T: Pixel>(
&mut self,
video_details: &VideoDetails,
) -> anyhow::Result<Frame<T>> {
match self {
Decoder::Y4m(dec) => crate::y4m::read_video_frame::<R, T>(dec, video_details),
#[cfg(feature = "vapoursynth")]
Decoder::Vapoursynth(dec) => dec.read_video_frame::<T>(video_details),
}
}
}
33 changes: 22 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@
#![warn(clippy::missing_errors_doc)]
#![warn(clippy::missing_panics_doc)]

pub mod decoder;
#[cfg(feature = "vapoursynth")]
pub mod vapoursynth;
mod y4m;

use std::{
Expand All @@ -98,7 +101,8 @@ use std::{
time::Instant,
};

use ::y4m::Decoder;
pub use ::y4m::Decoder as Y4mDecoder;
use decoder::Decoder;
pub use rav1e::scenechange::SceneChangeDetector;
use rav1e::{
config::{CpuFeatureLevel, EncoderConfig},
Expand Down Expand Up @@ -152,11 +156,14 @@ pub struct DetectionResults {
pub speed: f64,
}

/// # Errors
///
/// - If using a Vapoursynth script that contains an unsupported video format.
pub fn new_detector<R: Read, T: Pixel>(
dec: &mut Decoder<R>,
opts: DetectionOptions,
) -> SceneChangeDetector<T> {
let video_details = y4m::get_video_details(dec);
) -> anyhow::Result<SceneChangeDetector<T>> {
let video_details = dec.get_video_details()?;
let mut config =
EncoderConfig::with_speed_preset(if opts.analysis_speed == SceneDetectionSpeed::Fast {
10
Expand All @@ -178,7 +185,7 @@ pub fn new_detector<R: Read, T: Pixel>(
config.speed_settings.transform.tx_domain_distortion = true;

let sequence = Arc::new(Sequence::new(&config));
SceneChangeDetector::new(
Ok(SceneChangeDetector::new(
config,
CpuFeatureLevel::default(),
if opts.detect_flashes {
Expand All @@ -187,7 +194,7 @@ pub fn new_detector<R: Read, T: Pixel>(
1
},
sequence,
)
))
}

/// Runs through a y4m video clip,
Expand All @@ -204,6 +211,10 @@ pub fn new_detector<R: Read, T: Pixel>(
/// analyzed, and the number of keyframes detected. This is generally useful
/// for displaying progress, etc.
///
/// # Errors
///
/// - If using a Vapoursynth script that contains an unsupported video format.
///
/// # Panics
///
/// - If `opts.lookahead_distance` is 0.
Expand All @@ -213,11 +224,11 @@ pub fn detect_scene_changes<R: Read, T: Pixel>(
opts: DetectionOptions,
frame_limit: Option<usize>,
progress_callback: Option<&dyn Fn(usize, usize)>,
) -> DetectionResults {
) -> anyhow::Result<DetectionResults> {
assert!(opts.lookahead_distance >= 1);

let mut detector = new_detector(dec, opts);
let video_details = y4m::get_video_details(dec);
let mut detector = new_detector::<R, T>(dec, opts)?;
let video_details = dec.get_video_details()?;
let mut frame_queue = BTreeMap::new();
let mut keyframes = BTreeSet::new();
keyframes.insert(0);
Expand All @@ -229,7 +240,7 @@ pub fn detect_scene_changes<R: Read, T: Pixel>(
while next_input_frameno
< (frameno + opts.lookahead_distance + 1).min(frame_limit.unwrap_or(usize::MAX))
{
let frame = y4m::read_video_frame::<R, T>(dec, &video_details);
let frame = dec.read_video_frame(&video_details);
if let Ok(frame) = frame {
frame_queue.insert(next_input_frameno, Arc::new(frame));
next_input_frameno += 1;
Expand Down Expand Up @@ -275,11 +286,11 @@ pub fn detect_scene_changes<R: Read, T: Pixel>(
}
}
}
DetectionResults {
Ok(DetectionResults {
scene_changes: keyframes.into_iter().map(|val| val as usize).collect(),
frame_count: frameno,
speed: frameno as f64 / start_time.elapsed().as_secs_f64(),
}
})
}

#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq)]
Expand Down
Loading

0 comments on commit 574d0c1

Please sign in to comment.