ソースを参照

Improve subprocess error handling

Frans Bergman 2 年 前
コミット
e876a0d594
4 ファイル変更44 行追加40 行削除
  1. 14 5
      src/audio/audio.rs
  2. 1 8
      src/audio/audio_state.rs
  3. 10 18
      src/audio/song.rs
  4. 19 9
      src/audio/subprocess.rs

+ 14 - 5
src/audio/audio.rs

@@ -1,4 +1,6 @@
 use super::audio_state::AudioState;
+use super::song::Song;
+
 use lazy_static::lazy_static;
 use serenity::{
     client::Context,
@@ -146,17 +148,24 @@ async fn disconnect(ctx: &Context, msg: &Message) -> CommandResult {
 async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
     let query = args.rest();
 
+    message_react(ctx, msg, "🎶").await;
+
     let audio_state = get_audio_state(ctx, msg).await;
     let audio_state = match audio_state {
         Some(audio_state) => audio_state,
         None => return Ok(()),
     };
 
-    message_react(ctx, msg, "🎶").await;
-
-    AudioState::add_audio(audio_state, query).await;
-
-    message_react(ctx, msg, "✅").await;
+    match Song::from_query(query).await {
+        Ok(song) => {
+            AudioState::add_audio(audio_state, song).await;
+            message_react(ctx, msg, "✅").await;
+        }
+        Err(why) => {
+            message_react(ctx, msg, "❎").await;
+            send_embed(ctx, msg, &format!("Error: {}", why)).await;
+        }
+    }
 
     Ok(())
 }

+ 1 - 8
src/audio/audio_state.rs

@@ -128,14 +128,7 @@ impl AudioState {
         *track_handle = Some(handle);
     }
 
-    pub async fn add_audio(audio_state: Arc<AudioState>, query: &str) {
-        let song = match Song::from_query(query).await {
-            Ok(song) => song,
-            Err(why) => {
-                println!("Error add_audio: {}", why);
-                return;
-            }
-        };
+    pub async fn add_audio(audio_state: Arc<AudioState>, song: Song) {
         audio_state.queue.push(vec![song]).await;
         let current_song = audio_state.current_song.lock().await;
         if current_song.is_none() {

+ 10 - 18
src/audio/song.rs

@@ -1,16 +1,12 @@
 use super::subprocess::ytdl;
 
-pub struct SongMetadata {
+pub struct Song {
+    pub url: String,
     pub artist: Option<String>,
     pub title: Option<String>,
     pub duration: Option<u64>,
 }
 
-pub struct Song {
-    pub url: String,
-    metadata: SongMetadata,
-}
-
 impl Song {
     pub async fn from_query(query: &str) -> Result<Song, std::io::Error> {
         let query = if query.contains("watch?v=") {
@@ -19,26 +15,22 @@ impl Song {
             format!("ytsearch:{}", query)
         };
 
-        let (title, url) = ytdl(&query).await?;
+        let stdout = ytdl(&query).await?;
+        let mut lines = stdout.lines();
 
-        let metadata = SongMetadata {
+        let song = Song {
             artist: None,
-            title: Some(title),
+            title: lines.next().map(|t| t.to_string()),
+            url: lines.next().unwrap().to_string(),
             duration: None,
         };
-
-        let song = Song { url, metadata };
         Ok(song)
     }
 
     pub async fn get_string(&self) -> String {
-        let artist = self
-            .metadata
-            .artist
-            .clone()
-            .unwrap_or("unknown".to_string());
-        let title = self.metadata.title.clone().unwrap_or("unknown".to_string());
-        let duration = match self.metadata.duration {
+        let artist = self.artist.clone().unwrap_or("unknown".to_string());
+        let title = self.title.clone().unwrap_or("unknown".to_string());
+        let duration = match self.duration {
             Some(duration) => {
                 let mins = duration / 60;
                 let secs = duration - mins * 60;

+ 19 - 9
src/audio/subprocess.rs

@@ -6,7 +6,7 @@ use std::{
 use symphonia_core::io::ReadOnlySource;
 use tokio::process::Command as TokioCommand;
 
-pub async fn ytdl(query: &str) -> Result<(String, String), std::io::Error> {
+pub async fn ytdl(query: &str) -> Result<String, std::io::Error> {
     let mut cmd = TokioCommand::new("youtube-dl");
     let cmd = cmd
         .arg("-x")
@@ -15,14 +15,24 @@ pub async fn ytdl(query: &str) -> Result<(String, String), std::io::Error> {
         .arg("--get-url")
         .arg("--audio-quality")
         .arg("128k")
-        .arg(query);
-    let out = cmd.output().await?;
-    let out = String::from_utf8(out.stdout).unwrap();
-    let mut lines = out.lines();
-    Ok((
-        lines.next().unwrap().to_string(),
-        lines.next().unwrap().to_string(),
-    ))
+        .arg(query)
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped());
+    let child = cmd.spawn()?;
+    let output = child.wait_with_output().await?;
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    let stderr = String::from_utf8(output.stderr).unwrap();
+    if output.status.success() {
+        Ok(stdout)
+    } else {
+        Err(std::io::Error::new(
+            std::io::ErrorKind::Other,
+            format!(
+                "ytdl command exited with status {}: {}",
+                output.status, stderr
+            ),
+        ))
+    }
 }
 
 pub async fn ffmpeg_pcm(url: &str) -> Result<Box<dyn MediaSource + Send>, String> {