This commit is contained in:
		
							parent
							
								
									05289581a1
								
							
						
					
					
						commit
						837525e7b6
					
				
					 8 changed files with 178 additions and 6 deletions
				
			
		
							
								
								
									
										2
									
								
								.github/workflows/rust.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/rust.yml
									
										
									
									
										vendored
									
									
								
							|  | @ -26,7 +26,7 @@ jobs: | ||||||
|       - name: Install rust toolchain |       - name: Install rust toolchain | ||||||
|         run: sudo apt-get install -qy cargo-1.80 rust-1.80-clippy |         run: sudo apt-get install -qy cargo-1.80 rust-1.80-clippy | ||||||
|       - name: Install system dependencies |       - name: Install system dependencies | ||||||
|         run: sudo apt-get install -qy liblzma-dev libpipewire-0.3-dev libclang-dev libdbus-1-dev |         run: sudo apt-get install -qy liblzma-dev libpipewire-0.3-dev libclang-dev libdbus-1-dev ffmpeg libavutil-dev | ||||||
| 
 | 
 | ||||||
|       - name: Run Clippy |       - name: Run Clippy | ||||||
|         run: cargo clippy --all-targets --all-features |         run: cargo clippy --all-targets --all-features | ||||||
|  |  | ||||||
							
								
								
									
										64
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										64
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							|  | @ -162,6 +162,24 @@ dependencies = [ | ||||||
|  "syn", |  "syn", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "bindgen" | ||||||
|  | version = "0.70.1" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" | ||||||
|  | dependencies = [ | ||||||
|  |  "bitflags 2.9.0", | ||||||
|  |  "cexpr", | ||||||
|  |  "clang-sys", | ||||||
|  |  "itertools", | ||||||
|  |  "proc-macro2", | ||||||
|  |  "quote", | ||||||
|  |  "regex", | ||||||
|  |  "rustc-hash", | ||||||
|  |  "shlex", | ||||||
|  |  "syn", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "bit_field" | name = "bit_field" | ||||||
| version = "0.10.2" | version = "0.10.2" | ||||||
|  | @ -633,6 +651,31 @@ dependencies = [ | ||||||
|  "simd-adler32", |  "simd-adler32", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "ffmpeg-next" | ||||||
|  | version = "7.1.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "da02698288e0275e442a47fc12ca26d50daf0d48b15398ba5906f20ac2e2a9f9" | ||||||
|  | dependencies = [ | ||||||
|  |  "bitflags 2.9.0", | ||||||
|  |  "ffmpeg-sys-next", | ||||||
|  |  "libc", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "ffmpeg-sys-next" | ||||||
|  | version = "7.1.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "2bc3234d0a4b2f7d083699d0860c6c9dd83713908771b60f94a96f8704adfe45" | ||||||
|  | dependencies = [ | ||||||
|  |  "bindgen 0.70.1", | ||||||
|  |  "cc", | ||||||
|  |  "libc", | ||||||
|  |  "num_cpus", | ||||||
|  |  "pkg-config", | ||||||
|  |  "vcpkg", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "flate2" | name = "flate2" | ||||||
| version = "1.1.1" | version = "1.1.1" | ||||||
|  | @ -842,6 +885,12 @@ version = "0.5.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "hermit-abi" | ||||||
|  | version = "0.3.9" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "http" | name = "http" | ||||||
| version = "1.3.1" | version = "1.3.1" | ||||||
|  | @ -1056,7 +1105,7 @@ version = "0.8.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f" | checksum = "bf0d9716420364790e85cbb9d3ac2c950bde16a7dd36f3209b7dfdfc4a24d01f" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bindgen", |  "bindgen 0.69.5", | ||||||
|  "cc", |  "cc", | ||||||
|  "system-deps", |  "system-deps", | ||||||
| ] | ] | ||||||
|  | @ -1237,6 +1286,16 @@ dependencies = [ | ||||||
|  "autocfg", |  "autocfg", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "num_cpus" | ||||||
|  | version = "1.16.0" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" | ||||||
|  | dependencies = [ | ||||||
|  |  "hermit-abi", | ||||||
|  |  "libc", | ||||||
|  | ] | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "objc" | name = "objc" | ||||||
| version = "0.2.7" | version = "0.2.7" | ||||||
|  | @ -1346,7 +1405,7 @@ version = "0.8.0" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112" | checksum = "849e188f90b1dda88fe2bfe1ad31fe5f158af2c98f80fb5d13726c44f3f01112" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bindgen", |  "bindgen 0.69.5", | ||||||
|  "libspa-sys", |  "libspa-sys", | ||||||
|  "system-deps", |  "system-deps", | ||||||
| ] | ] | ||||||
|  | @ -1751,6 +1810,7 @@ dependencies = [ | ||||||
|  "clap", |  "clap", | ||||||
|  "env_logger", |  "env_logger", | ||||||
|  "fast_image_resize", |  "fast_image_resize", | ||||||
|  |  "ffmpeg-next", | ||||||
|  "image", |  "image", | ||||||
|  "log", |  "log", | ||||||
|  "scap", |  "scap", | ||||||
|  |  | ||||||
|  | @ -20,6 +20,7 @@ scap = "0.0.8" | ||||||
| image = "0.25.5" | image = "0.25.5" | ||||||
| fast_image_resize = { version = "5.1", features = ["image"] } | fast_image_resize = { version = "5.1", features = ["image"] } | ||||||
| tungstenite = "0.26" | tungstenite = "0.26" | ||||||
|  | ffmpeg-next = "7.1.0" | ||||||
| 
 | 
 | ||||||
| [dependencies.servicepoint] | [dependencies.servicepoint] | ||||||
| package = "servicepoint" | package = "servicepoint" | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								README.md
									
										
									
									
									
								
							|  | @ -69,6 +69,7 @@ Commands: | ||||||
|   flip    Invert the state of all pixels [aliases: f] |   flip    Invert the state of all pixels [aliases: f] | ||||||
|   on      Set all pixels to the on state |   on      Set all pixels to the on state | ||||||
|   image   Send an image file (e.g. jpeg or png) to the display. [aliases: i] |   image   Send an image file (e.g. jpeg or png) to the display. [aliases: i] | ||||||
|  |   video   Stream a video file (e.g. mp4) to the display. [aliases: v] | ||||||
|   screen  Stream the default screen capture source to the display. On Linux Wayland, this pops up a screen or window chooser, but it also may directly start streaming your main screen. [aliases: s] |   screen  Stream the default screen capture source to the display. On Linux Wayland, this pops up a screen or window chooser, but it also may directly start streaming your main screen. [aliases: s] | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | @ -91,6 +92,25 @@ Options: | ||||||
|       --no-aspect   Do not keep aspect ratio when resizing. |       --no-aspect   Do not keep aspect ratio when resizing. | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | #### Video file | ||||||
|  | 
 | ||||||
|  | ```text | ||||||
|  | Stream a video file (e.g. mp4) to the display. | ||||||
|  | 
 | ||||||
|  | Usage: servicepoint-cli pixels video [OPTIONS] <FILE_NAME> | ||||||
|  | 
 | ||||||
|  | Arguments: | ||||||
|  |   <FILE_NAME>   | ||||||
|  | 
 | ||||||
|  | Options: | ||||||
|  |       --no-hist     Disable histogram correction | ||||||
|  |       --no-blur     Disable blur | ||||||
|  |       --no-sharp    Disable sharpening | ||||||
|  |       --no-dither   Disable dithering. Brightness will be adjusted so that around half of the pixels are on. | ||||||
|  |       --no-spacers  Do not remove the spacers from the image. | ||||||
|  |       --no-aspect   Do not keep aspect ratio when resizing. | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| #### Screen | #### Screen | ||||||
| 
 | 
 | ||||||
| ```text | ```text | ||||||
|  |  | ||||||
|  | @ -90,8 +90,7 @@ | ||||||
|         { |         { | ||||||
|           default = pkgs.mkShell rec { |           default = pkgs.mkShell rec { | ||||||
|             inputsFrom = [ self.packages.${system}.default ]; |             inputsFrom = [ self.packages.${system}.default ]; | ||||||
|             packages = [ |             packages = with pkgs; [ | ||||||
|               pkgs.gdb |  | ||||||
|               (pkgs.symlinkJoin { |               (pkgs.symlinkJoin { | ||||||
|                 name = "rust-toolchain"; |                 name = "rust-toolchain"; | ||||||
|                 paths = with pkgs; [ |                 paths = with pkgs; [ | ||||||
|  | @ -103,7 +102,11 @@ | ||||||
|                   cargo-expand |                   cargo-expand | ||||||
|                 ]; |                 ]; | ||||||
|               }) |               }) | ||||||
|               pkgs.cargo-flamegraph | 
 | ||||||
|  |               cargo-flamegraph | ||||||
|  |               gdb | ||||||
|  | 
 | ||||||
|  |               ffmpeg-headless | ||||||
|             ]; |             ]; | ||||||
|             LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (builtins.concatMap (d: d.buildInputs) inputsFrom)}"; |             LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath (builtins.concatMap (d: d.buildInputs) inputsFrom)}"; | ||||||
|             RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; |             RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; | ||||||
|  |  | ||||||
							
								
								
									
										10
									
								
								src/cli.rs
									
										
									
									
									
								
							
							
						
						
									
										10
									
								
								src/cli.rs
									
										
									
									
									
								
							|  | @ -74,6 +74,16 @@ pub enum PixelCommand { | ||||||
|         #[command(flatten)] |         #[command(flatten)] | ||||||
|         image_processing_options: ImageProcessingOptions, |         image_processing_options: ImageProcessingOptions, | ||||||
|     }, |     }, | ||||||
|  |     #[command(
 | ||||||
|  |         visible_alias = "v", | ||||||
|  |         about = "Stream a video file (e.g. mp4) to the display." | ||||||
|  |     )] | ||||||
|  |     Video { | ||||||
|  |         #[command(flatten)] | ||||||
|  |         send_image_options: SendImageOptions, | ||||||
|  |         #[command(flatten)] | ||||||
|  |         image_processing_options: ImageProcessingOptions, | ||||||
|  |     }, | ||||||
|     #[command(
 |     #[command(
 | ||||||
|         visible_alias = "s", |         visible_alias = "s", | ||||||
|         about = "Stream the default screen capture source to the display. \ |         about = "Stream the default screen capture source to the display. \ | ||||||
|  |  | ||||||
|  | @ -35,6 +35,7 @@ impl ImageProcessingPipeline { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[must_use] | ||||||
|     pub fn process(&mut self, frame: DynamicImage) -> Bitmap { |     pub fn process(&mut self, frame: DynamicImage) -> Bitmap { | ||||||
|         let start_time = Instant::now(); |         let start_time = Instant::now(); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,8 @@ use crate::{ | ||||||
|     stream_window::stream_window, |     stream_window::stream_window, | ||||||
|     transport::Transport, |     transport::Transport, | ||||||
| }; | }; | ||||||
|  | use ffmpeg_next as ffmpeg; | ||||||
|  | use image::{DynamicImage, RgbImage}; | ||||||
| use log::info; | use log::info; | ||||||
| use servicepoint::{ | use servicepoint::{ | ||||||
|     BinaryOperation, BitVecCommand, BitmapCommand, ClearCommand, CompressionCode, DisplayBitVec, |     BinaryOperation, BitVecCommand, BitmapCommand, ClearCommand, CompressionCode, DisplayBitVec, | ||||||
|  | @ -23,6 +25,10 @@ pub(crate) fn pixels(connection: &Transport, pixel_command: PixelCommand) { | ||||||
|             stream_options, |             stream_options, | ||||||
|             image_processing, |             image_processing, | ||||||
|         } => stream_window(connection, stream_options, image_processing), |         } => stream_window(connection, stream_options, image_processing), | ||||||
|  |         PixelCommand::Video { | ||||||
|  |             image_processing_options: processing_options, | ||||||
|  |             send_image_options: image_options, | ||||||
|  |         } => pixels_video(connection, image_options, processing_options), | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -78,3 +84,74 @@ fn pixels_image( | ||||||
|         .expect("failed to send image command"); |         .expect("failed to send image command"); | ||||||
|     info!("sent image to display"); |     info!("sent image to display"); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | fn pixels_video( | ||||||
|  |     connection: &Transport, | ||||||
|  |     options: SendImageOptions, | ||||||
|  |     processing_options: ImageProcessingOptions, | ||||||
|  | ) { | ||||||
|  |     ffmpeg::init().unwrap(); | ||||||
|  | 
 | ||||||
|  |     let mut ictx = ffmpeg::format::input(&options.file_name).expect("failed to open video input file"); | ||||||
|  | 
 | ||||||
|  |     let input = ictx | ||||||
|  |         .streams() | ||||||
|  |         .best(ffmpeg::media::Type::Video) | ||||||
|  |         .ok_or(ffmpeg::Error::StreamNotFound) | ||||||
|  |         .expect("could not get video stream from input file"); | ||||||
|  |     let video_stream_index = input.index(); | ||||||
|  | 
 | ||||||
|  |     let context_decoder = ffmpeg::codec::context::Context::from_parameters(input.parameters()) | ||||||
|  |         .expect("could not extract video context from parameters"); | ||||||
|  |     let mut decoder = context_decoder.decoder().video() | ||||||
|  |         .expect("failed to create decoder for video stream"); | ||||||
|  | 
 | ||||||
|  |     let src_width = decoder.width(); | ||||||
|  |     let src_height = decoder.height(); | ||||||
|  |     
 | ||||||
|  |     let mut scaler = ffmpeg::software::scaling::Context::get( | ||||||
|  |         decoder.format(), | ||||||
|  |         src_width, | ||||||
|  |         src_height, | ||||||
|  |         ffmpeg::format::Pixel::RGB24, | ||||||
|  |         src_width, | ||||||
|  |         src_height, | ||||||
|  |         ffmpeg::software::scaling::Flags::BILINEAR, | ||||||
|  |     ).expect("failed to create scaling context"); | ||||||
|  | 
 | ||||||
|  |     let mut frame_index = 0; | ||||||
|  | 
 | ||||||
|  |     let mut processing_pipeline = ImageProcessingPipeline::new(processing_options); | ||||||
|  | 
 | ||||||
|  |     let mut receive_and_process_decoded_frames = | ||||||
|  |         |decoder: &mut ffmpeg::decoder::Video| -> Result<(), ffmpeg::Error> { | ||||||
|  |             let mut decoded = ffmpeg::util::frame::video::Video::empty(); | ||||||
|  |             let mut rgb_frame = ffmpeg::util::frame::video::Video::empty(); | ||||||
|  |             while decoder.receive_frame(&mut decoded).is_ok() { | ||||||
|  |                 scaler.run(&decoded, &mut rgb_frame) | ||||||
|  |                     .expect("failed to scale frame"); | ||||||
|  | 
 | ||||||
|  |                 let image = RgbImage::from_raw(src_width, src_height, rgb_frame.data(0).to_owned()) | ||||||
|  |                     .expect("could not read rgb data to image"); | ||||||
|  |                 let image = DynamicImage::from(image); | ||||||
|  |                 let bitmap= processing_pipeline.process(image); | ||||||
|  |                 connection.send_command(BitmapCommand::from(bitmap)) | ||||||
|  |                     .expect("failed to send image command"); | ||||||
|  | 
 | ||||||
|  |                 frame_index += 1; | ||||||
|  |             } | ||||||
|  |             Ok(()) | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |     for (stream, packet) in ictx.packets() { | ||||||
|  |         if stream.index() == video_stream_index { | ||||||
|  |             decoder.send_packet(&packet) | ||||||
|  |                 .expect("failed to send video packet"); | ||||||
|  |             receive_and_process_decoded_frames(&mut decoder) | ||||||
|  |                 .expect("failed to process video packet"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     decoder.send_eof().expect("failed to send eof"); | ||||||
|  |     receive_and_process_decoded_frames(&mut decoder) | ||||||
|  |         .expect("failed to eof packet"); | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Vinzenz Schroeter
						Vinzenz Schroeter