diff --git a/divoom/src/animation/animation_builder.rs b/divoom/src/animation/animation_builder.rs index 448cab7..5db7db2 100644 --- a/divoom/src/animation/animation_builder.rs +++ b/divoom/src/animation/animation_builder.rs @@ -1,4 +1,3 @@ -use crate::animation::animation_builder_error::*; use crate::animation::animation_frame_builder::DivoomAnimationFrameBuilder; use crate::dto::*; use std::collections::BTreeMap; @@ -15,12 +14,12 @@ pub struct DivoomAnimationBuilder { // Ctor and basic functions impl DivoomAnimationBuilder { - pub fn new( - canvas_size: u32, - speed: Duration, - ) -> DivoomAnimationBuilderResult { + pub fn new(canvas_size: u32, speed: Duration) -> DivoomAPIResult { if canvas_size != 16 && canvas_size != 32 && canvas_size != 64 { - return Err(DivoomAnimationBuilderError::UnsupportedCanvasSize); + return Err(DivoomAPIError::ParameterError(format!( + "Invalid canvas size: {}. Only 16, 32 and 64 are supported.", + canvas_size + ))); } Ok(DivoomAnimationBuilder { @@ -123,7 +122,7 @@ impl DivoomAnimationBuilder { #[cfg(test)] mod tests { use crate::animation::*; - use crate::test_utils; + use crate::{test_utils, DivoomAPIError}; use std::time::Duration; #[test] @@ -139,7 +138,7 @@ mod tests { match result { Ok(_) => panic!("Canvas size is incorrect and we shall not create builder here."), Err(e) => match e { - DivoomAnimationBuilderError::UnsupportedCanvasSize => (), + DivoomAPIError::ParameterError(_) => (), _ => panic!("Incorrect error code!"), }, } diff --git a/divoom/src/animation/animation_builder_error.rs b/divoom/src/animation/animation_builder_error.rs deleted file mode 100644 index 2f56988..0000000 --- a/divoom/src/animation/animation_builder_error.rs +++ /dev/null @@ -1,24 +0,0 @@ -use thiserror::Error; - -/// Building animations can fail due to different reasons, such as failing to load image file or decode image file. -/// Hence we have defined the error code here. -#[derive(Debug, Error)] -pub enum DivoomAnimationBuilderError { - #[error("Failed to load file from disk")] - IOError { - #[from] - source: std::io::Error, - }, - - #[error("Failed to decode file")] - DecodeError { - #[from] - source: gif::DecodingError, - }, - - #[error("Canvas size not supported. Only 16, 32, and 64 are supported.")] - UnsupportedCanvasSize, -} - -/// Result that wraps the error. -pub type DivoomAnimationBuilderResult = std::result::Result; diff --git a/divoom/src/animation/animation_resource_loader.rs b/divoom/src/animation/animation_resource_loader.rs index 0042c52..8f22f79 100644 --- a/divoom/src/animation/animation_resource_loader.rs +++ b/divoom/src/animation/animation_resource_loader.rs @@ -1,11 +1,11 @@ -use crate::animation::animation_builder_error::DivoomAnimationBuilderResult; +use crate::{DivoomAPIError, DivoomAPIResult}; use std::fs::File; use tiny_skia::Pixmap; pub struct DivoomAnimationResourceLoader {} impl DivoomAnimationResourceLoader { - pub fn gif(file_path: &str) -> DivoomAnimationBuilderResult> { + pub fn gif(file_path: &str) -> DivoomAPIResult> { let mut frames = vec![]; let input = File::open(file_path)?; @@ -24,6 +24,12 @@ impl DivoomAnimationResourceLoader { } } +impl From for DivoomAPIError { + fn from(err: gif::DecodingError) -> Self { + DivoomAPIError::ResourceDecodeError(err.to_string()) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/divoom/src/animation/mod.rs b/divoom/src/animation/mod.rs index ec2bca4..af91b35 100644 --- a/divoom/src/animation/mod.rs +++ b/divoom/src/animation/mod.rs @@ -1,9 +1,7 @@ mod animation_builder; -mod animation_builder_error; mod animation_frame_builder; mod animation_resource_loader; pub use animation_builder::*; -pub use animation_builder_error::*; pub use animation_frame_builder::*; pub use animation_resource_loader::*; diff --git a/divoom/src/clients/pixoo/pixoo_client.rs b/divoom/src/clients/pixoo/pixoo_client.rs index 1c607a6..ebdfa42 100644 --- a/divoom/src/clients/pixoo/pixoo_client.rs +++ b/divoom/src/clients/pixoo/pixoo_client.rs @@ -8,6 +8,10 @@ use crate::divoom_contracts::pixoo::system::*; use crate::divoom_contracts::pixoo::tool::*; use crate::dto::*; use std::rc::Rc; +use std::time::Duration; + +#[cfg(feature = "animation-builder")] +use crate::animation::*; /// Pixoo device client /// @@ -300,6 +304,26 @@ impl PixooClient { () ); + /// Send GIF to the device to play as an animation. + /// + /// This API is different from `play_gif_file`, which is provided by divoom device directly. This API will try to leverage the animation API, + /// create a new animation, load the gif files and draw all the frames into the animation, and send the to device to play. + /// + /// The API `play_gif_file` doesn't seems to be very stable when the package is published, hence `send_gif_as_animation` is more preferred + /// as of now. + #[cfg(feature = "animation-builder")] + pub async fn send_gif_as_animation( + &self, + canvas_size: u32, + speed: Duration, + file_path: &str, + ) -> DivoomAPIResult<()> { + let animation_builder = DivoomAnimationBuilder::new(canvas_size, speed)?; + let gif = DivoomAnimationResourceLoader::gif(file_path)?; + let animation = animation_builder.draw_frames(&gif, 0).build(); + self.send_image_animation(animation).await + } + #[doc = include_str!("../../divoom_contracts/pixoo/animation/api_send_image_animation_frame.md")] pub async fn send_image_animation( &self, diff --git a/divoom/src/dto/divoom_api_error.rs b/divoom/src/dto/divoom_api_error.rs index 33efc11..8b64544 100644 --- a/divoom/src/dto/divoom_api_error.rs +++ b/divoom/src/dto/divoom_api_error.rs @@ -1,3 +1,4 @@ +use std::io; use thiserror::Error; /// This represents the error that returned from Divoom online service or Divoom devices. @@ -54,6 +55,15 @@ pub enum DivoomAPIError { #[error("Invalid parameter.")] ParameterError(String), + #[error("Failed to load resource")] + ResourceLoadError { + #[from] + source: io::Error, + }, + + #[error("Failed to decode resource")] + ResourceDecodeError(String), + #[error("Failed to send request")] RequestError { #[from] diff --git a/divoom_cli/README.md b/divoom_cli/README.md index 73d3ef7..078cf41 100644 --- a/divoom_cli/README.md +++ b/divoom_cli/README.md @@ -71,11 +71,22 @@ brightness: 67 # Set channel to clock with id 100 > divoom-cli 192.168.0.164 channel set-clock 100 +# Play a gif from Internet by calling the API provided by Divoom Device. +# Please note that: this API can be unstable and only accepts GIF with 16x16, 32x32 and 64x64 image size. +> divoom-cli 192.168.0.123 animation gif play --url https://www.gifandgif.eu/animated_gif/Planets/Animated%20Gif%20Planets%20(16).GIF + +# To help playing GIF in a more stable way, we can use the image animation API to craft an animation and draw the GIF +# frames into it and send to device to play, e.g.: +> divoom-cli 192.168.0.123 animation image send-gif "logo-16-rotate-4-frames.gif" 16 -s 100 + # Create a text animation -> divoom-cli 192.168.0.123 animation text set 1 "The gray fox jumped over the lazy dog" +# Please note that: this API only works after we use "animation image send-gif" API to draw anything. This API call will be ignored, +# when the device is showing other things, like clock or channel. +> divoom-cli 192.168.0.123 animation text set 1 "Hello world!" +> divoom-cli 192.168.0.123 animation text set 2 "The gray fox jumped over the lazy dog" -y 20 -# Play a gif from Internet -> divoom-cli 192.168.0.123 animation gif play --url https://www.gifandgif.eu/animated_gif/Planets/Animated%20Gif%20Planets%20(16).GIF +# Modify existing text animation. E.g. changing "Hello world!" above to "Hello Divoom!" +> divoom-cli 192.168.0.123 animation text set 1 "Hello Divoom!" # Send a raw request # diff --git a/divoom_cli/src/main.rs b/divoom_cli/src/main.rs index 107d8e4..1474a7c 100644 --- a/divoom_cli/src/main.rs +++ b/divoom_cli/src/main.rs @@ -3,6 +3,7 @@ mod opt; use crate::opt::*; use divoom::*; use serde::Serialize; +use std::time::Duration; use structopt::StructOpt; #[tokio::main] @@ -278,6 +279,16 @@ async fn handle_image_animation_api( } DivoomCliImageAnimationCommand::ResetId => pixoo.reset_next_animation_id().await, + + DivoomCliImageAnimationCommand::SendGif { + file_path, + size, + speed_in_ms, + } => { + pixoo + .send_gif_as_animation(size, Duration::from_millis(speed_in_ms), &file_path) + .await + } } } diff --git a/divoom_cli/src/opt.rs b/divoom_cli/src/opt.rs index 25e592c..8a25d65 100644 --- a/divoom_cli/src/opt.rs +++ b/divoom_cli/src/opt.rs @@ -305,6 +305,28 @@ pub enum DivoomCliImageAnimationCommand { #[structopt(about = "Reset next animation id")] ResetId, + + #[structopt( + about = "Send gif as animation. This is different from \"gif play\" command, which is provided directly by Divoom device. This command will create a regular animation and load the gif file and draw the frames into it in order to play it." + )] + SendGif { + #[structopt(help = "Gif file path")] + file_path: String, + + #[structopt( + default_value = "64", + help = "Animation size in pixels. Only 16 and 32 and 64 are allowed." + )] + size: u32, + + #[structopt( + short, + long = "speed", + default_value = "100", + help = "Animation play speed in milliseconds" + )] + speed_in_ms: u64, + }, } #[derive(StructOpt, Debug)]