nu_plugin_audio_hook 0.105.1

A nushell plugin to make and play sounds
Documentation
use nu_plugin::{self, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{Category, Example, LabeledError, Signature, SyntaxShape, Value};
use rodio::{source::Source, Decoder, OutputStream};

use std::{fs::File, io::BufReader, time::Duration};

use crate::Sound;

pub struct SoundPlayCmd;
impl SimplePluginCommand for SoundPlayCmd {
    type Plugin = Sound;

    fn name(&self) -> &str {
        "sound play"
    }

    fn signature(&self) -> nu_protocol::Signature {
        Signature::new("sound play")
            .required("File Path", SyntaxShape::Filepath, "file to play")
            .named(
                "duration",
                SyntaxShape::Duration,
                "duration of file (mandatory for non-wave formats like mp3) (default 1 hour)",
                Some('d'),
            )
            .category(Category::Experimental)
    }
    fn examples(&self) -> Vec<Example> {
        vec![
            Example {
                description: "play a sound and exits after 5min",
                example: "sound play audio.mp4 -d 5min",
                result: None,
            },
            Example {
                description: "play a sound for its duration",
                example: "sound meta audio.mp4 | sound play audio.mp3 -d $in.duration",
                result: None,
            },
        ]
    }
    fn description(&self) -> &str {
        "play an audio file, by default supports flac,Wav,mp3 and ogg files, install plugin with `all-decoders` feature to include aac and mp4(audio)"
    }

    fn run(
        &self,
        _plugin: &Self::Plugin,
        _engine: &nu_plugin::EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, nu_protocol::LabeledError> {
        play_audio(call)
    }
}

fn play_audio(call: &EvaluatedCall) -> Result<Value, LabeledError> {
    let (file_span, file_value) = match load_file(call) {
        Ok(value) => value,
        Err(value) => return value,
    };

    let (_stream, stream_handle) = match OutputStream::try_default() {
        Ok(value) => value,
        Err(err) => {
            return Err(
                LabeledError::new(err.to_string()).with_label("audio stream exception", call.head)
            )
        }
    };
    let file = BufReader::new(file_value);

    let source = match Decoder::new(file) {
        Ok(value) => value,
        Err(err) => {
            return Err(
                LabeledError::new(err.to_string()).with_label("audio decoder exception", file_span)
            )
        }
    };

    let duration = source.total_duration();

    match stream_handle.play_raw(source.convert_samples()) {
        Ok(_) => {}
        Err(err) => {
            return Err(
                LabeledError::new(err.to_string()).with_label("audio player exception", file_span)
            )
        }
    }

    let sleep_duration: Duration = match load_duration_from(call, "duration") {
        Some(duration) => duration,
        None => match duration {
            Some(duration) => duration,
            None => Duration::from_secs(3600),
        },
    };

    std::thread::sleep(sleep_duration);
    Ok(Value::nothing(call.head))
}
fn load_duration_from(call: &EvaluatedCall, name: &str) -> Option<Duration> {
    match call.get_flag_value(name) {
        Some(Value::Duration { val, .. }) => {
            Some(Duration::from_nanos(u64::from_ne_bytes(val.to_ne_bytes())))
        }
        _ => None,
    }
}
fn load_file(
    call: &EvaluatedCall,
) -> Result<(nu_protocol::Span, File), Result<Value, LabeledError>> {
    let file: Value = match call.req(0) {
        Ok(value) => value,
        Err(err) => {
            return Err(Err(LabeledError::new(err.to_string())
                .with_label("Frequency value not found", call.head)))
        }
    };
    let file_span = file.span();
    let file_value: File = match file {
        Value::String { val, .. } => match File::open(val) {
            Ok(file) => file,
            Err(err) => {
                return Err(Err(LabeledError::new(err.to_string())
                    .with_label("error trying to open the file", file_span)))
            }
        },
        _ => return Err(Err(LabeledError::new("cannot access file path"))),
    };
    Ok((file_span, file_value))
}