diff --git a/src/channel.rs b/src/channel.rs index f7ef12f..7d8d33f 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -12,11 +12,11 @@ impl Requester { pub fn send(&self, data: T) -> Result, SendError> { let (response_tx, response_rx) = mpsc::channel(); match self.tx.send(Request { - data, + data: Some(data), tx: response_tx, }) { Ok(_) => Ok(Response { rx: response_rx }), - Err(e) => Err(SendError(e.0.data)), + Err(e) => Err(SendError(e.0.data.unwrap())), } } } @@ -40,14 +40,14 @@ impl Responder { pub fn channel() -> (Requester, Responder) { let (requester_tx, responder_rx) = mpsc::channel(); - return ( + ( Requester { tx: requester_tx }, Responder { rx: responder_rx }, - ); + ) } pub struct Request { - pub data: T, + pub data: Option, tx: Sender, } diff --git a/src/download.rs b/src/download.rs index 37125fb..f29b533 100644 --- a/src/download.rs +++ b/src/download.rs @@ -1,11 +1,33 @@ use std::{path::Path, process::Command}; -use crate::track::TrackInfo; +use crate::{file::FileHandle, track::{Track, TrackInfo}}; #[derive(Debug, Clone, Copy)] pub struct DownloadError; -pub fn download_from_youtube(url: &str) -> Result { +pub fn download_from_youtube(url: &str) -> Result { + let output = Command::new("yt-dlp") + .args([ + "--print", + "%(id)s %(duration)i", + "--no-playlist", + "--no-warnings", + "--", + url, + ]).output().unwrap(); + + let items: Vec = std::str::from_utf8(&output.stdout).unwrap().split(' ').map(|s| s.replace("\n", "")).collect(); + + let filename = items[0].clone() + ".mp3"; + let duration: u32 = items[1].parse().unwrap(); + + println!("{}", filename); + println!("{}", duration); + + if duration > 900 { + return Err(DownloadError); + } + let output = Command::new("yt-dlp") .args([ "-o", @@ -13,22 +35,22 @@ pub fn download_from_youtube(url: &str) -> Result { "--extract-audio", "--audio-format", "mp3", - "--print", - "%(id)s", - "--no-simulate", + "--no-playlist", + "--no-warnings", + "--", url, ]) .output() .unwrap(); if !output.stderr.is_empty() || output.stdout.is_empty() { + println!("{}", std::str::from_utf8(output.stderr.as_slice()).unwrap()); + println!("{}", std::str::from_utf8(output.stdout.as_slice()).unwrap()); return Err(DownloadError); } - let filename = std::str::from_utf8(output.stdout.as_slice()) - .unwrap() - .replace('\n', "") - + ".mp3"; + let file_handle = FileHandle::new(Path::new(filename.as_str())); + let info = TrackInfo::new(&filename); - Ok(TrackInfo::new(&Path::new(filename.as_str()))) + Ok(Track::new(info, file_handle)) } diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..1ccf365 --- /dev/null +++ b/src/file.rs @@ -0,0 +1,25 @@ +use std::fs; +use std::path::{Path, PathBuf}; + +#[derive(Debug, Clone)] +pub struct FileHandle { + path: PathBuf, +} + +impl FileHandle { + pub fn new(path: &Path) -> FileHandle { + FileHandle { + path: PathBuf::from(path), + } + } + + pub fn get_path(&self) -> &Path { + &self.path + } +} + +impl Drop for FileHandle { + fn drop(&mut self) { + let _ = fs::remove_file(self.path.clone()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 6f1f52b..059045d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,8 @@ pub mod channel; pub mod download; +pub mod file; pub mod player; pub mod telegram; mod track; -pub use track::TrackInfo; +pub use track::Track; diff --git a/src/main.rs b/src/main.rs index fceed9a..329e05e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use music_bot::{player::MusicPlayer, telegram}; fn main() { - let player = MusicPlayer::new(); + let player = MusicPlayer::build(); let mut bot = telegram::TelegramBot::build(player); bot.telegram_main(); } diff --git a/src/player.rs b/src/player.rs index 2aa004b..4ea60a7 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,12 +1,12 @@ -use std::{collections::VecDeque, fs::File, io::BufReader, thread, thread::JoinHandle}; use rodio::{Decoder, OutputStream, OutputStreamHandle, Sink}; +use std::{collections::VecDeque, fs::File, io::BufReader, thread, thread::JoinHandle}; use crate::channel::{self, Requester, Responder, TryRecvError}; -use crate::track::TrackInfo; +use crate::track::{Track, TrackInfo}; -#[derive(Debug, Clone)] +#[derive(Debug)] enum WorkerRequest { - AddTrack(TrackInfo), + AddTrack(Track), Pause, Play, Stop, @@ -14,7 +14,7 @@ enum WorkerRequest { ListTracks, } -#[derive(Debug, Clone)] +#[derive(Debug)] enum WorkerResponse { TrackList(VecDeque), None, @@ -25,19 +25,20 @@ use WorkerResponse::*; pub struct MusicPlayer { requester: Requester, - _worker: JoinHandle<()>, + worker: Option>, } struct Worker { _stream: OutputStream, _handle: OutputStreamHandle, sink: Sink, - queue: VecDeque, + queue: VecDeque, + current: Option, responder: Responder, } -fn get_source(track: TrackInfo) -> Decoder> { - let file = BufReader::new(File::open(&track.path).unwrap()); +fn get_source(track: &Track) -> Decoder> { + let file = BufReader::new(File::open(track.file.get_path()).unwrap()); Decoder::new(file).unwrap() } @@ -59,28 +60,34 @@ impl Worker { Stop => { self.sink.stop(); self.queue.clear(); + self.current.take(); None } SkipOne => { self.sink.skip_one(); + self.current.take(); None } - ListTracks => TrackList(self.queue.clone()), + ListTracks => TrackList(self.queue.iter().map(|track| track.info.clone()).collect::>().clone()), } } pub fn main(&mut self) { loop { match self.responder.try_recv() { - Ok(req) => { - let _ = req.respond(self.match_request(req.data.clone())); + Ok(mut req) => { + let data = req.data.take().unwrap(); + let _ = req.respond(self.match_request(data)); } Err(TryRecvError::Empty) => {} Err(TryRecvError::Disconnected) => break, }; if self.sink.empty() && !self.queue.is_empty() { let next_track = self.queue.pop_front().unwrap(); - self.sink.append(get_source(next_track)); + self.sink.append(get_source(&next_track)); + self.current = Some(next_track); + } else if self.sink.empty() { + self.current.take(); } } } @@ -88,25 +95,26 @@ impl Worker { pub fn build(responder: Responder) -> Worker { let (_stream, _handle) = OutputStream::try_default().unwrap(); let sink = Sink::try_new(&_handle).unwrap(); - let queue: VecDeque = VecDeque::new(); + let queue: VecDeque = VecDeque::new(); Worker { _stream, _handle, sink, queue, + current: Option::None, responder, } } } impl MusicPlayer { - pub fn new() -> MusicPlayer { + pub fn build() -> MusicPlayer { let (requester, responder) = channel::channel(); - let _worker = thread::spawn(move || Worker::build(responder).main()); - MusicPlayer { requester, _worker } + let worker = thread::spawn(move || Worker::build(responder).main()); + MusicPlayer { requester, worker: Some(worker) } } - pub fn enqueue(&mut self, track: TrackInfo) { + pub fn enqueue(&mut self, track: Track) { self.requester.send(AddTrack(track)).unwrap(); } @@ -134,3 +142,10 @@ impl MusicPlayer { } } } + +impl Drop for MusicPlayer { + fn drop(&mut self) { + self.stop(); + self.worker.take().unwrap().join().unwrap(); + } +} diff --git a/src/telegram.rs b/src/telegram.rs index d777e77..076c812 100644 --- a/src/telegram.rs +++ b/src/telegram.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, Mutex}; use teloxide::{prelude::*, utils::command::BotCommands}; -use crate::{player::MusicPlayer, download}; +use crate::{download, player::MusicPlayer}; #[derive(BotCommands, Clone)] #[command( @@ -41,17 +41,15 @@ impl TelegramBot { cmd: Command, ) -> Result<(), teloxide::RequestError> { match cmd { - Command::Play(url) => { - match download::download_from_youtube(&url) { - Ok(track_info) => { - player.lock().unwrap().enqueue(track_info); - bot.send_message(msg.chat.id, "Added to the queue.").await?; - }, - Err(_) => { - bot.send_message(msg.chat.id, format!("Failed to download.")).await?; - } + Command::Play(url) => match download::download_from_youtube(&url) { + Ok(track_info) => { + player.lock().unwrap().enqueue(track_info); + bot.send_message(msg.chat.id, "Added to the queue.").await?; } - } + Err(_) => { + bot.send_message(msg.chat.id, "Failed to download.").await?; + } + }, Command::Stop => { player.lock().unwrap().stop(); bot.send_message(msg.chat.id, "Stopped.").await?; @@ -70,15 +68,17 @@ impl TelegramBot { } Command::List => { let tracks = player.lock().unwrap().list_tracks(); - bot.send_message( - msg.chat.id, - tracks + let message: String; + if tracks.is_empty() { + message = String::from("The queue is empty."); + } else { + message = tracks .iter() - .map(|t| t.path.to_str().unwrap()) + .map(|t| t.name.as_str()) .collect::>() - .join("\n"), - ) - .await?; + .join("\n"); + } + bot.send_message(msg.chat.id, message).await?; } }; Ok(()) diff --git a/src/track.rs b/src/track.rs index b96f8de..820abc9 100644 --- a/src/track.rs +++ b/src/track.rs @@ -1,13 +1,24 @@ -use std::path::{Path, PathBuf}; +use crate::file::FileHandle; + #[derive(Debug, Clone)] pub struct TrackInfo { - pub path: PathBuf, + pub name: String, } impl TrackInfo { - pub fn new(path: &Path) -> TrackInfo { - TrackInfo { - path: PathBuf::from(path), - } + pub fn new(name: &str) -> TrackInfo { + TrackInfo { name: String::from(name) } + } +} + +#[derive(Debug)] +pub struct Track { + pub info: TrackInfo, + pub file: FileHandle, +} + +impl Track { + pub fn new(info: TrackInfo, file: FileHandle) -> Track { + Track { info, file } } }