summaryrefslogtreecommitdiffstats
path: root/src/bin
diff options
context:
space:
mode:
authors-ol <s+removethis@s-ol.nu>2026-05-04 21:45:39 +0000
committers-ol <s+removethis@s-ol.nu>2026-05-14 15:00:13 +0000
commit0bdd9ae4e181283ac1d21b52cce7b28c565d936f (patch)
tree141632d39cda63d9a605b182041006ada7031ec2 /src/bin
parentswitch to WESL shaders (diff)
downloadwgsl-view-0bdd9ae4e181283ac1d21b52cce7b28c565d936f.tar.gz
wgsl-view-0bdd9ae4e181283ac1d21b52cce7b28c565d936f.zip
tsv-video-*: share code
Diffstat (limited to 'src/bin')
-rw-r--r--src/bin/tsv_video_buffer.rs150
-rw-r--r--src/bin/tsv_video_stream.rs89
2 files changed, 59 insertions, 180 deletions
diff --git a/src/bin/tsv_video_buffer.rs b/src/bin/tsv_video_buffer.rs
index 9c8ee99..3a244a8 100644
--- a/src/bin/tsv_video_buffer.rs
+++ b/src/bin/tsv_video_buffer.rs
@@ -2,77 +2,7 @@ use std::io::Read;
use std::process::{Command, Stdio};
use ash::vk;
-use wgsl_view::{gpu, hap};
-
-/// Options that ffmpeg supports but ffprobe does not.
-const FFMPEG_ONLY_OPTIONS: &[&str] = &["-stream_loop", "-t"];
-
-fn filter_probe_args(ff_args: &[String]) -> Vec<String> {
- let mut out = Vec::new();
- let mut skip_next = false;
- for arg in ff_args {
- if skip_next {
- skip_next = false;
- continue;
- }
- if FFMPEG_ONLY_OPTIONS.contains(&arg.as_str()) {
- skip_next = true;
- continue;
- }
- out.push(arg.clone());
- }
- out
-}
-
-struct VideoInfo {
- width: u32,
- height: u32,
- num_frames: u32,
- codec_tag: String,
-}
-
-fn probe_video(ff_args: &[String], max_frames: Option<u32>) -> VideoInfo {
- let probe_args = filter_probe_args(ff_args);
- let output = Command::new("ffprobe")
- .args(["-v", "error", "-select_streams", "v:0"])
- .args([
- "-show_entries",
- "stream=width,height,nb_frames,codec_tag_string",
- ])
- .args(["-of", "csv=p=0"])
- .args(&probe_args)
- .output()
- .expect("failed to run ffprobe");
-
- if !output.status.success() {
- let stderr = String::from_utf8_lossy(&output.stderr);
- panic!("ffprobe failed: {stderr}");
- }
-
- let stdout = String::from_utf8_lossy(&output.stdout);
- let line = stdout.trim();
- let parts: Vec<&str> = line.split(',').collect();
- if parts.len() < 4 {
- panic!("unexpected ffprobe output: {line}");
- }
-
- let num_frames = parts[3].parse().unwrap_or_else(|_| {
- max_frames.unwrap_or_else(|| {
- panic!(
- "could not determine frame count (nb_frames={}).\n\
- Use --frames N or use tsv-video-stream instead.",
- parts[3]
- )
- })
- });
-
- VideoInfo {
- codec_tag: parts[0].to_string(),
- width: parts[1].parse().expect("parse width"),
- height: parts[2].parse().expect("parse height"),
- num_frames,
- }
-}
+use wgsl_view::{ffmpeg, gpu, hap};
fn is_hap_codec(codec_tag: &str) -> Option<hap::HapFormat> {
match codec_tag {
@@ -119,28 +49,29 @@ fn main() {
}
}
- let mut info = probe_video(ff_args, max_frames);
- if let Some(max) = max_frames {
- info.num_frames = info.num_frames.min(max);
- }
+ let mut info = ffmpeg::probe_video(ff_args, max_frames);
+ let num_frames = match (info.num_frames, max_frames) {
+ (Some(a), Some(b)) => u32::min(a, b),
+ (Some(a), None) => a,
+ (None, Some(b)) => b,
+ _ => panic!("failed to detect duration, please specify --frames"),
+ };
+ info.num_frames = Some(num_frames);
let hap_format = is_hap_codec(&info.codec_tag);
- let (texture_format, tsv_img_format) = match hap_format {
- Some(hap::HapFormat::Bc1) => (wgpu::TextureFormat::Bc1RgbaUnorm, gpu::ImgFormat::BC1_RGBA),
- Some(hap::HapFormat::Bc3) => (wgpu::TextureFormat::Bc3RgbaUnorm, gpu::ImgFormat::BC3_RGBA),
- None => (
- wgpu::TextureFormat::Rgba8UnormSrgb,
- gpu::ImgFormat::R8G8B8A8,
- ),
+ let format = match hap_format {
+ Some(hap::HapFormat::Bc1) => gpu::ImgFormat::BC1_RGBA,
+ Some(hap::HapFormat::Bc3) => gpu::ImgFormat::BC3_RGBA,
+ None => gpu::ImgFormat::R8G8B8A8,
};
log::info!(
- "{}x{}, {} frames, codec_tag={}, format={:?}, tsv image: {}",
+ "{}x{}, {} frames, codec_tag={}, format={:?}, tsv image: {:?}",
info.width,
info.height,
- info.num_frames,
+ num_frames,
info.codec_tag,
- texture_format,
+ format,
name
);
@@ -156,10 +87,10 @@ fn main() {
wgpu::Features::empty()
};
- let (tsv_img_type, wgpu_dimension) = if use_array {
- (gpu::ImgType::D2, wgpu::TextureDimension::D2)
+ let img_type = if use_array {
+ gpu::ImgType::D2
} else {
- (gpu::ImgType::D3, wgpu::TextureDimension::D3)
+ gpu::ImgType::D3
};
let instance = gpu::create_instance();
@@ -172,9 +103,9 @@ fn main() {
&name,
info.width,
info.height,
- info.num_frames,
- tsv_img_format,
- tsv_img_type,
+ num_frames,
+ format,
+ img_type,
true,
)
.expect("init tsv image");
@@ -186,12 +117,12 @@ fn main() {
size: wgpu::Extent3d {
width: info.width,
height: info.height,
- depth_or_array_layers: info.num_frames,
+ depth_or_array_layers: num_frames,
},
mip_level_count: 1,
sample_count: 1,
- dimension: wgpu_dimension,
- format: texture_format,
+ dimension: gpu::img_type_to_wgpu(img_type, num_frames),
+ format: gpu::img_format_to_wgpu(format),
usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
@@ -219,10 +150,7 @@ fn main() {
std::process::exit(1);
}
- log::info!(
- "all {} frames uploaded and shared, waiting...",
- info.num_frames
- );
+ log::info!("all {} frames uploaded and shared, waiting...", num_frames);
// Keep the process alive so the shared image remains available
loop {
@@ -235,7 +163,7 @@ fn upload_hap_frames(
queue: &wgpu::Queue,
texture: &wgpu::Texture,
ff_args: &[String],
- info: &VideoInfo,
+ info: &ffmpeg::VideoInfo,
hap_fmt: hap::HapFormat,
) {
// For HAP, we need raw packet data (not decoded pixels).
@@ -254,6 +182,7 @@ fn upload_hap_frames(
let compressed_frame_size = hap_fmt.compressed_size(info.width, info.height);
let mut decode_buf = vec![0u8; compressed_frame_size + 1024 * 1024]; // extra margin
let mut frame_index: u32 = 0;
+ let num_frames = info.num_frames.unwrap();
let block_bytes = hap_fmt.block_bytes();
let blocks_x = info.width.div_ceil(4);
@@ -293,18 +222,18 @@ fn upload_hap_frames(
);
frame_index += 1;
- if frame_index.is_multiple_of(10) || frame_index == info.num_frames {
- log::info!("uploaded frame {}/{}", frame_index, info.num_frames);
+ if frame_index.is_multiple_of(10) || frame_index == num_frames {
+ log::info!("uploaded frame {}/{}", frame_index, num_frames);
}
- if frame_index >= info.num_frames {
+ if frame_index >= num_frames {
break;
}
}
- if frame_index < info.num_frames {
+ if frame_index < num_frames {
log::warn!(
"expected {} frames but only decoded {frame_index}",
- info.num_frames
+ num_frames
);
}
}
@@ -314,9 +243,10 @@ fn upload_raw_frames(
queue: &wgpu::Queue,
texture: &wgpu::Texture,
ff_args: &[String],
- info: &VideoInfo,
+ info: &ffmpeg::VideoInfo,
) {
let frame_size = (info.width * info.height * 4) as usize;
+ let num_frames = info.num_frames.unwrap();
let mut ffmpeg = Command::new("ffmpeg")
.args(["-v", "quiet"])
@@ -358,10 +288,10 @@ fn upload_raw_frames(
);
frame_index += 1;
- if frame_index.is_multiple_of(10) || frame_index == info.num_frames {
- log::info!("uploaded frame {}/{}", frame_index, info.num_frames);
+ if frame_index.is_multiple_of(10) || frame_index == num_frames {
+ log::info!("uploaded frame {}/{}", frame_index, num_frames);
}
- if frame_index >= info.num_frames {
+ if frame_index >= num_frames {
break;
}
}
@@ -371,10 +301,10 @@ fn upload_raw_frames(
drop(reader);
ffmpeg.wait().ok();
- if frame_index < info.num_frames {
+ if frame_index < num_frames {
log::warn!(
"expected {} frames but only decoded {frame_index}",
- info.num_frames
+ num_frames
);
}
}
diff --git a/src/bin/tsv_video_stream.rs b/src/bin/tsv_video_stream.rs
index 3e12f6d..c2db35c 100644
--- a/src/bin/tsv_video_stream.rs
+++ b/src/bin/tsv_video_stream.rs
@@ -4,63 +4,7 @@ use std::thread;
use std::time::{Duration, Instant};
use ash::vk;
-use wgsl_view::gpu;
-
-/// Options that ffmpeg supports but ffprobe does not.
-const FFMPEG_ONLY_OPTIONS: &[&str] = &["-stream_loop"];
-
-fn filter_probe_args(ff_args: &[String]) -> Vec<String> {
- let mut out = Vec::new();
- let mut skip_next = false;
- for arg in ff_args {
- if skip_next {
- skip_next = false;
- continue;
- }
- if FFMPEG_ONLY_OPTIONS.contains(&arg.as_str()) {
- skip_next = true;
- continue;
- }
- out.push(arg.clone());
- }
- out
-}
-
-fn probe_video(ff_args: &[String]) -> (u32, u32, f64) {
- let probe_args = filter_probe_args(ff_args);
- let output = Command::new("ffprobe")
- .args(["-v", "error", "-select_streams", "v:0"])
- .args(["-show_entries", "stream=width,height,r_frame_rate"])
- .args(["-of", "csv=p=0"])
- .args(&probe_args)
- .output()
- .expect("failed to run ffprobe");
-
- if !output.status.success() {
- let stderr = String::from_utf8_lossy(&output.stderr);
- panic!("ffprobe failed: {stderr}");
- }
-
- let stdout = String::from_utf8_lossy(&output.stdout);
- let line = stdout.trim();
- let parts: Vec<&str> = line.split(',').collect();
- if parts.len() < 3 {
- panic!("unexpected ffprobe output: {line}");
- }
-
- let width: u32 = parts[0].parse().expect("parse width");
- let height: u32 = parts[1].parse().expect("parse height");
-
- let fps: f64 = if let Some((num, den)) = parts[2].split_once('/') {
- let n: f64 = num.parse().expect("parse fps numerator");
- let d: f64 = den.parse().expect("parse fps denominator");
- n / d
- } else {
- parts[2].parse().expect("parse fps")
- };
-
- (width, height, fps)
-}
+use wgsl_view::{ffmpeg, gpu};
fn main() {
env_logger::init();
@@ -89,8 +33,13 @@ fn main() {
}
}
- let (width, height, fps) = probe_video(ff_args);
- log::info!("{width}x{height} @ {fps:.2}fps, tsv image: {name}");
+ let info = ffmpeg::probe_video(ff_args, None);
+ log::info!(
+ "{}x{} @ {:.2}fps, tsv image: {name}",
+ info.width,
+ info.height,
+ info.fps
+ );
let instance = gpu::create_instance();
let adapter = gpu::create_adapter(&instance, None);
@@ -100,10 +49,10 @@ fn main() {
client
.init_image(
&name,
- width,
- height,
+ info.width,
+ info.height,
1,
- gpu::TSV_FORMAT,
+ gpu::ImgFormat::R8G8B8A8,
gpu::ImgType::D2,
true,
)
@@ -114,20 +63,20 @@ fn main() {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("video_frame"),
size: wgpu::Extent3d {
- width,
- height,
+ width: info.width,
+ height: info.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
- format: wgpu::TextureFormat::Rgba8UnormSrgb,
+ format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::COPY_DST | wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
- let frame_size = (width * height * 4) as usize;
- let frame_time = Duration::from_secs_f64(1.0 / fps);
+ let frame_size = (info.width * info.height * 4) as usize;
+ let frame_time = Duration::from_secs_f64(1.0 / info.fps);
let mut ffmpeg = Command::new("ffmpeg")
.args(["-v", "quiet"])
@@ -159,12 +108,12 @@ fn main() {
&frame_buf,
wgpu::TexelCopyBufferLayout {
offset: 0,
- bytes_per_row: Some(width * 4),
+ bytes_per_row: Some(info.width * 4),
rows_per_image: None,
},
wgpu::Extent3d {
- width,
- height,
+ width: info.width,
+ height: info.height,
depth_or_array_layers: 1,
},
);