Skip to content

Commit 6f47858

Browse files
committed
Updates to dependencies and Rust edition 2024
1 parent 7daa0c0 commit 6f47858

19 files changed

Lines changed: 616 additions & 371 deletions

Cargo.lock

Lines changed: 386 additions & 271 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

av1an-core/Cargo.toml

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[package]
22
name = "av1an-core"
33
version = "0.5.2"
4-
rust-version = "1.88"
5-
edition = "2021"
4+
rust-version = "1.89"
5+
edition = "2024"
66
description = """
77
Cross-platform command-line AV1 / VP9 / HEVC / H264 encoding framework with per scene quality encoding [Core library]
88
"""
@@ -17,14 +17,14 @@ readme = "../README.md"
1717
[dependencies]
1818
anyhow = { workspace = true }
1919
arrayvec = "0.7.2"
20-
av-decoders = { version = "0.8.2", features = ["vapoursynth"] }
20+
av-decoders = { version = "0.9.1", features = ["vapoursynth"] }
2121
av-format = "0.7.0"
2222
av-ivf = "0.5.0"
23-
av-scenechange = { version = "0.21.1", default-features = false, features = [
23+
av-scenechange = { version = "0.23.0", default-features = false, features = [
2424
"asm",
2525
"vapoursynth",
2626
] }
27-
av1-grain = { version = "0.2.5", default-features = false, features = [
27+
av1-grain = { version = "0.4.1", default-features = false, features = [
2828
"create",
2929
] }
3030
cfg-if = "1.0.4"
@@ -43,7 +43,7 @@ plotters = { version = "0.3.1", default-features = false, features = [
4343
"svg_backend",
4444
"line_series",
4545
] }
46-
rand = "0.9.2"
46+
rand = "0.10.0"
4747
serde = { version = "1.0", features = ["derive"] }
4848
serde_json = "1.0"
4949
simdutf8 = "0.1.3"
@@ -53,8 +53,8 @@ smallvec = { version = "1.15.1", default-features = false, features = [
5353
"union",
5454
] }
5555
strsim = "0.11.0"
56-
strum = { version = "0.27.2", features = ["derive"] }
57-
sysinfo = "0.37.2"
56+
strum = { version = "0.28.0", features = ["derive"] }
57+
sysinfo = "0.38.4"
5858
textwrap = "0.16.0"
5959
thiserror = "2.0.18"
6060
tracing = { workspace = true }
@@ -78,6 +78,9 @@ tempfile = { workspace = true }
7878
[features]
7979
default = []
8080

81+
[lints.rust]
82+
unsafe_op_in_unsafe_fn = "allow"
83+
8184
[lints.clippy]
8285
# Performance
8386
clear_with_drain = "warn"

av1an-core/src/broker.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,17 @@ impl Broker<'_> {
172172
}
173173

174174
while let Ok(mut chunk) = rx.recv() {
175-
if terminations_requested.load(Ordering::SeqCst) == 0 {
176-
if let Err(e) = queue.encode_chunk(&mut chunk, worker_id, &terminations_requested, total_chunks) {
177-
error!("[chunk {index}] {e}", index = chunk.index);
178-
tx.send(()).expect("should send successfully");
179-
return Err(());
180-
}
175+
if terminations_requested.load(Ordering::SeqCst) == 0
176+
&& let Err(e) = queue.encode_chunk(
177+
&mut chunk,
178+
worker_id,
179+
&terminations_requested,
180+
total_chunks,
181+
)
182+
{
183+
error!("[chunk {index}] {e}", index = chunk.index);
184+
tx.send(()).expect("should send successfully");
185+
return Err(());
181186
}
182187
}
183188
Ok(())
@@ -249,12 +254,11 @@ impl Broker<'_> {
249254
}
250255

251256
if chunk.target_quality.params_copied
252-
&& chunk.tq_cq.is_some()
253257
&& chunk.target_quality.probing_rate == 1
254258
&& self.project.args.ffmpeg_filter_args.is_empty()
255259
&& chunk.proxy.is_none()
260+
&& let Some(optimal_q) = chunk.tq_cq
256261
{
257-
let optimal_q = chunk.tq_cq.expect("tq_cq is some");
258262
let extension = match self.project.args.encoder {
259263
crate::encoder::Encoder::x264 => "264",
260264
crate::encoder::Encoder::x265 => "hevc",

av1an-core/src/chunk/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ use av1_grain::{generate_photon_noise_params, write_grain_table, NoiseGenArgs};
77
use serde::{Deserialize, Serialize};
88
use tracing::debug;
99

10-
use crate::{encoder::Encoder, settings::insert_noise_table_params, Input, TargetQuality};
10+
use crate::{
11+
encoder::Encoder,
12+
settings::insert_noise_table_params,
13+
ColorRange,
14+
Input,
15+
TargetQuality,
16+
};
1117

1218
#[derive(Debug, Clone, Serialize, Deserialize)]
1319
pub struct Chunk {
@@ -56,6 +62,7 @@ impl Chunk {
5662
&mut self,
5763
photon_noise: Option<u8>,
5864
chroma_noise: bool,
65+
color_range: Option<ColorRange>,
5966
) -> anyhow::Result<()> {
6067
if let Some(strength) = photon_noise {
6168
let iso_setting = u32::from(strength) * 100;
@@ -78,6 +85,7 @@ impl Chunk {
7885
height,
7986
transfer_function,
8087
chroma_grain: chroma_noise,
88+
full_range: matches!(color_range, Some(ColorRange::Full)),
8189
random_seed: None,
8290
});
8391
write_grain_table(&grain_table, &[params])?;

av1an-core/src/chunk/tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ fn apply_photon_noise_args_with_noise() -> anyhow::Result<()> {
159159
ignore_frame_mismatch: false,
160160
};
161161

162-
ch.apply_photon_noise_args(Some(8), true)?;
162+
ch.apply_photon_noise_args(Some(8), true, None)?;
163163
assert!(ch.video_params.iter().any(|p| p.contains("fgs-table")));
164164
Ok(())
165165
}
@@ -197,7 +197,7 @@ fn apply_photon_noise_args_no_noise() -> anyhow::Result<()> {
197197
ignore_frame_mismatch: false,
198198
};
199199

200-
ch.apply_photon_noise_args(None, false)?;
200+
ch.apply_photon_noise_args(None, false, None)?;
201201
assert!(!ch.video_params.iter().any(|p| p.contains("fgs-table")));
202202
Ok(())
203203
}
@@ -235,6 +235,6 @@ fn apply_photon_noise_args_unsupported_encoder() -> anyhow::Result<()> {
235235
ignore_frame_mismatch: false,
236236
};
237237

238-
assert!(ch.apply_photon_noise_args(Some(8), true).is_err());
238+
assert!(ch.apply_photon_noise_args(Some(8), true, None).is_err());
239239
Ok(())
240240
}

av1an-core/src/context.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,8 @@ impl Av1anContext {
238238
{
239239
self.vs_script = Some(cache_vs_input(&self.args.input)?);
240240
}
241-
if let Some(proxy) = &self.args.proxy {
242-
if proxy.is_vapoursynth()
241+
if let Some(proxy) = &self.args.proxy
242+
&& (proxy.is_vapoursynth()
243243
|| (proxy.is_video()
244244
&& matches!(
245245
self.args.chunk_method,
@@ -248,10 +248,9 @@ impl Av1anContext {
248248
| ChunkMethod::DGDECNV
249249
| ChunkMethod::BESTSOURCE
250250
)
251-
&& !self.args.resume)
252-
{
253-
self.vs_proxy_script = Some(cache_vs_input(proxy)?);
254-
}
251+
&& !self.args.resume))
252+
{
253+
self.vs_proxy_script = Some(cache_vs_input(proxy)?);
255254
}
256255

257256
let clip_info = self.args.input.clip_info()?;
@@ -488,10 +487,10 @@ impl Av1anContext {
488487
{temp}",
489488
temp = self.args.temp
490489
);
491-
} else if !self.args.keep {
492-
if let Err(e) = fs::remove_dir_all(&self.args.temp) {
493-
warn!("Failed to delete temp directory: {e}");
494-
}
490+
} else if !self.args.keep
491+
&& let Err(e) = fs::remove_dir_all(&self.args.temp)
492+
{
493+
warn!("Failed to delete temp directory: {e}");
495494
}
496495

497496
Ok(())
@@ -749,17 +748,16 @@ impl Av1anContext {
749748
enc_stderr.push_str(line);
750749
enc_stderr.push('\n');
751750

752-
if current_pass == chunk.passes {
753-
if let Some(new) = chunk.encoder.parse_encoded_frames(line) {
754-
if new > frame {
755-
if self.args.verbosity == Verbosity::Normal {
756-
inc_bar(new - frame);
757-
} else if self.args.verbosity == Verbosity::Verbose {
758-
inc_mp_bar(new - frame);
759-
}
760-
frame = new;
761-
}
751+
if current_pass == chunk.passes
752+
&& let Some(new) = chunk.encoder.parse_encoded_frames(line)
753+
&& new > frame
754+
{
755+
if self.args.verbosity == Verbosity::Normal {
756+
inc_bar(new - frame);
757+
} else if self.args.verbosity == Verbosity::Verbose {
758+
inc_mp_bar(new - frame);
762759
}
760+
frame = new;
763761
}
764762
}
765763

@@ -993,9 +991,11 @@ impl Av1anContext {
993991
tq_cq: None,
994992
ignore_frame_mismatch: self.args.ignore_frame_mismatch,
995993
};
994+
let color_range = self.args.input.clip_info()?.color_range;
996995
chunk.apply_photon_noise_args(
997996
overrides.map_or(self.args.photon_noise, |ovr| ovr.photon_noise),
998997
self.args.chroma_noise,
998+
color_range,
999999
)?;
10001000
if chunk.target_quality.target.is_some() {
10011001
chunk.tq_cq = Some(chunk.target_quality.per_shot_target_quality(
@@ -1104,6 +1104,7 @@ impl Av1anContext {
11041104
tq_cq: None,
11051105
ignore_frame_mismatch: self.args.ignore_frame_mismatch,
11061106
};
1107+
let color_range = self.args.input.clip_info()?.color_range;
11071108
chunk.apply_photon_noise_args(
11081109
scene
11091110
.zone_overrides
@@ -1113,6 +1114,7 @@ impl Av1anContext {
11131114
.zone_overrides
11141115
.as_ref()
11151116
.map_or(self.args.chroma_noise, |ovr| ovr.chroma_noise),
1117+
color_range,
11161118
)?;
11171119
Ok(chunk)
11181120
}
@@ -1343,9 +1345,11 @@ impl Av1anContext {
13431345
tq_cq: None,
13441346
ignore_frame_mismatch: self.args.ignore_frame_mismatch,
13451347
};
1348+
let color_range = self.args.input.clip_info()?.color_range;
13461349
chunk.apply_photon_noise_args(
13471350
overrides.map_or(self.args.photon_noise, |ovr| ovr.photon_noise),
13481351
self.args.chroma_noise,
1352+
color_range,
13491353
)?;
13501354
Ok(chunk)
13511355
}

av1an-core/src/ffmpeg.rs

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
1212
use tracing::warn;
1313
use vapoursynth::format::PresetFormat;
1414

15-
use crate::{into_array, into_vec, ClipInfo, InputPixelFormat};
15+
use crate::{into_array, into_vec, ClipInfo, ColorRange, InputPixelFormat};
1616

1717
#[inline]
1818
pub fn compose_ffmpeg_pipe<S: Into<String>>(
@@ -47,6 +47,7 @@ struct FfProbeStreamInfo {
4747
pub width: u32,
4848
pub height: u32,
4949
pub pix_fmt: String,
50+
pub color_range: Option<String>,
5051
pub color_transfer: Option<String>,
5152
pub avg_frame_rate: String,
5253
pub nb_frames: Option<String>,
@@ -62,7 +63,7 @@ pub fn get_clip_info(source: &Path) -> anyhow::Result<ClipInfo> {
6263
.arg("-print_format")
6364
.arg("json")
6465
.arg("-show_entries")
65-
.arg("stream=width,height,pix_fmt,avg_frame_rate,nb_frames,color_transfer")
66+
.arg("stream=width,height,pix_fmt,avg_frame_rate,nb_frames,color_range,color_transfer")
6667
.arg(source)
6768
.output()?
6869
.stdout;
@@ -71,24 +72,49 @@ pub fn get_clip_info(source: &Path) -> anyhow::Result<ClipInfo> {
7172
.streams
7273
.first()
7374
.ok_or_else(|| anyhow::anyhow!("no video streams found in source file"))?;
75+
let format = FFPixelFormat::from_str(&stream_info.pix_fmt)?;
76+
let color_range = stream_info.color_range.as_deref().map_or_else(
77+
|| infer_color_range_from_pix_fmt(format),
78+
parse_ffprobe_color_range,
79+
);
7480

7581
Ok(ClipInfo {
76-
format_info: InputPixelFormat::FFmpeg {
77-
format: FFPixelFormat::from_str(&stream_info.pix_fmt)?,
82+
format_info: InputPixelFormat::FFmpeg {
83+
format,
7884
},
79-
frame_rate: parse_frame_rate(&stream_info.avg_frame_rate)?,
80-
resolution: (stream_info.width, stream_info.height),
85+
frame_rate: parse_frame_rate(&stream_info.avg_frame_rate)?,
86+
resolution: (stream_info.width, stream_info.height),
87+
color_range,
8188
transfer_characteristics: match stream_info.color_transfer.as_deref() {
8289
Some("smpte2084") => av1_grain::TransferFunction::SMPTE2084,
8390
_ => av1_grain::TransferFunction::BT1886,
8491
},
85-
num_frames: match stream_info.nb_frames.as_deref().map(str::parse) {
92+
num_frames: match stream_info.nb_frames.as_deref().map(str::parse) {
8693
Some(Ok(nb_frames)) => nb_frames,
8794
_ => get_num_frames(source)?,
8895
},
8996
})
9097
}
9198

99+
#[inline]
100+
const fn infer_color_range_from_pix_fmt(pix_fmt: FFPixelFormat) -> Option<ColorRange> {
101+
match pix_fmt {
102+
FFPixelFormat::YUVJ420P | FFPixelFormat::YUVJ422P | FFPixelFormat::YUVJ444P => {
103+
Some(ColorRange::Full)
104+
},
105+
_ => None,
106+
}
107+
}
108+
109+
#[inline]
110+
fn parse_ffprobe_color_range(color_range: &str) -> Option<ColorRange> {
111+
match color_range {
112+
"pc" | "jpeg" | "full" => Some(ColorRange::Full),
113+
"tv" | "mpeg" | "limited" => Some(ColorRange::Limited),
114+
_ => None,
115+
}
116+
}
117+
92118
/// Get frame count using FFmpeg
93119
#[inline]
94120
pub fn get_num_frames(source: &Path) -> anyhow::Result<usize> {
@@ -461,3 +487,39 @@ impl FromStr for FFPixelFormat {
461487
})
462488
}
463489
}
490+
491+
#[cfg(test)]
492+
mod tests {
493+
use super::*;
494+
495+
#[test]
496+
fn parse_ffprobe_color_range_aliases() {
497+
assert_eq!(parse_ffprobe_color_range("pc"), Some(ColorRange::Full));
498+
assert_eq!(parse_ffprobe_color_range("jpeg"), Some(ColorRange::Full));
499+
assert_eq!(parse_ffprobe_color_range("full"), Some(ColorRange::Full));
500+
assert_eq!(parse_ffprobe_color_range("tv"), Some(ColorRange::Limited));
501+
assert_eq!(parse_ffprobe_color_range("mpeg"), Some(ColorRange::Limited));
502+
assert_eq!(
503+
parse_ffprobe_color_range("limited"),
504+
Some(ColorRange::Limited)
505+
);
506+
assert_eq!(parse_ffprobe_color_range("unknown"), None);
507+
}
508+
509+
#[test]
510+
fn infer_color_range_from_legacy_jpeg_formats() {
511+
assert_eq!(
512+
infer_color_range_from_pix_fmt(FFPixelFormat::YUVJ420P),
513+
Some(ColorRange::Full)
514+
);
515+
assert_eq!(
516+
infer_color_range_from_pix_fmt(FFPixelFormat::YUVJ422P),
517+
Some(ColorRange::Full)
518+
);
519+
assert_eq!(
520+
infer_color_range_from_pix_fmt(FFPixelFormat::YUVJ444P),
521+
Some(ColorRange::Full)
522+
);
523+
assert_eq!(infer_color_range_from_pix_fmt(FFPixelFormat::YUV420P), None);
524+
}
525+
}

0 commit comments

Comments
 (0)