Added playlist downloading to cli

This commit is contained in:
Gvidas Juknevičius 2024-09-14 19:00:43 +03:00
parent 52a55d8be2
commit 29c7e452b0
Signed by: MCorange
GPG Key ID: 12B1346D720B7FBB
8 changed files with 669 additions and 604 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,26 @@
use std::str::FromStr;
use anyhow::bail;
use crate::{config::ConfigWrapper, downloader::Downloader, manifest::{song::Song, Manifest}, util::is_supported_host};
pub async fn add(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &Option<String>, name: &Option<String>, playlist: &Option<String>) -> anyhow::Result<()> {
pub async fn add(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &String, name: &String, playlist: &String) -> anyhow::Result<()> {
log::debug!("Playlist: {playlist:?}");
log::debug!("url: {url:?}");
log::debug!("name: {name:?}");
let mut playlists = manifest.get_playlists().keys().map(|f| f.clone()).collect::<Vec<String>>();
playlists.sort();
let playlist = playlist.clone().unwrap_or_else( || {
let g = crate::prompt::prompt_with_list_or_str("Enter song playlist", &playlists);
log::info!("Playlist: {g}");
g
});
let url = url.clone().unwrap_or_else( ||
crate::prompt::simple_prompt("Enter song youtube url, make sure its not a playlist, (yt only for now)")
);
if !is_supported_host(url::Url::from_str(&url)?) {
log::error!("Invalid or unsupported host name");
return Ok(());
}
let name = name.clone().unwrap_or_else( ||
crate::prompt::simple_prompt("Enter song name with like this: {Author} - {Song name}")
);
let song = Song::from_url_str(url)?;
manifest.add_song(playlist.clone(), name.clone(), song.clone());
let song = Song::from_url_str(url.clone())?;
manifest.add_song(playlist, name.clone(), song.clone());
manifest.save(None)?;
let should_download = crate::prompt::prompt_bool("Download song now?", Some(false));
@ -48,3 +32,24 @@ pub async fn add(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut
Ok(())
}
pub async fn add_playlist(cfg: &ConfigWrapper, manifest: &mut Manifest, downloader: &mut Downloader, url: &String, name: &String) -> anyhow::Result<()> {
let songs = downloader.download_playlist_nb(cfg, url, name, manifest.get_format())?;
if manifest.get_playlist(name).is_some() {
log::error!("Playlist {name} already exists");
bail!("")
}
manifest.add_playlist(name.clone());
let playlist = manifest.get_playlist_mut(name).expect("Unreachable");
for (sname, song) in songs {
playlist.add_song(sname, song);
}
manifest.save(None)?;
while downloader.download_all_nb_poll(cfg)?.is_some() {};
Ok(())
}

View File

@ -24,7 +24,7 @@ impl Gui {
let (playlist, song_name) = self.song_editor.song.clone();
let Some(song) = self.manifest.get_song(playlist.clone(), &song_name) else {
let Some(song) = self.manifest.get_song(&playlist, &song_name) else {
return;
};
let song = song.clone();
@ -65,14 +65,14 @@ impl Gui {
if save {
{
let Some(song) = self.manifest.get_song_mut(playlist.clone(), &song_name) else {
let Some(song) = self.manifest.get_song_mut(&playlist, &song_name) else {
return;
};
*song.get_url_str_mut() = self.song_editor.ed_url.clone();
}
let Some(playlist) = self.manifest.get_playlist_mut(playlist.clone()) else {
let Some(playlist) = self.manifest.get_playlist_mut(&playlist) else {
return;
};
@ -126,7 +126,7 @@ impl Gui {
});
if save {
let Some(playlist) = self.manifest.get_playlist_mut(self.song_editor.ed_playlist.clone().unwrap()) else {
let Some(playlist) = self.manifest.get_playlist_mut(&self.song_editor.ed_playlist.clone().unwrap()) else {
panic!("couldnt find playlist from a preset playlist list????????????");
};
@ -137,7 +137,6 @@ impl Gui {
let _ = self.manifest.save(None);
save = false;
self.song_editor.is_new_open = false;
}
}

View File

@ -23,6 +23,11 @@ pub async fn command_run(cfg: &ConfigWrapper, manifest: &mut Manifest) -> anyhow
(Some(c), _) => {
match c {
CliCommand::Download => unreachable!(),
CliCommand::AddPlaylist { url, name } => {
if let Err(e) = add::add_playlist(cfg, manifest, &mut downloader, url, name).await {
log::error!("Failed to run 'add-playlist' commmand: {e}");
}
}
CliCommand::Add { url, name, playlist } => {
if let Err(e) = add::add(cfg, manifest, &mut downloader, url, name, playlist).await {
log::error!("Failed to run 'add' command: {e}");

View File

@ -30,11 +30,17 @@ pub enum CliCommand {
Download,
Add {
#[arg(long, short)]
url: Option<String>,
url: String,
#[arg(long, short)]
name: Option<String>,
name: String,
#[arg(long, short)]
playlist: Option<String>
playlist: String
},
AddPlaylist {
#[arg(long, short)]
url: String,
#[arg(long, short)]
name: String
},
Gui
}

View File

@ -40,7 +40,7 @@ impl Downloader {
self.nb_cache.len()
}
pub fn download_all_nb(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result<usize> {
pub fn download_all_nb(&mut self, manifest: &Manifest, cfg: &ConfigWrapper) -> anyhow::Result<Option<usize>> {
for (pname, playlist) in manifest.get_playlists() {
for (sname, song) in playlist.get_songs() {
self.nb_cache.push((pname.clone(), sname.clone(), song.clone(), manifest.get_format().clone()));
@ -51,14 +51,20 @@ impl Downloader {
self.download_all_nb_poll(cfg)
}
pub fn download_all_nb_poll(&mut self, cfg: &ConfigWrapper) -> anyhow::Result<usize> {
pub fn download_all_nb_poll(&mut self, cfg: &ConfigWrapper) -> anyhow::Result<Option<usize>> {
if !crate::process_manager::is_proc_queue_full(10) {
if let Some((pname, sname, song, format)) = self.nb_cache.pop() {
self.download_song(cfg, &sname, &song, &pname, &format)?;
}
}
Ok(crate::process_manager::purge_done_procs())
if self.nb_cache.is_empty() {
self.nb_initial_song_count = 0;
}
if crate::process_manager::proc_count() == 0 && self.nb_cache.is_empty() {
Ok(None)
} else {
Ok(Some(crate::process_manager::purge_done_procs()))
}
}
@ -76,6 +82,47 @@ impl Downloader {
Ok(self.count)
}
pub fn download_playlist(&mut self, cfg: &ConfigWrapper, url: &String, pname: &String, format: &Format) -> anyhow::Result<usize> {
self.download_playlist_nb(cfg, url, pname, format)?;
let mut count = 0;
while let Some(c) = self.download_all_nb_poll(cfg)? {
count += c;
}
Ok(count)
}
pub fn download_playlist_nb(&mut self, cfg: &ConfigWrapper, url: &String, pname: &String, format: &Format) -> anyhow::Result<HashMap<String, Song>> {
log::warn!("This automatically assumes its a youtube link as it is currently the only supported playlist source");
let mut cmd = tokio::process::Command::new(&cfg.cfg.ytdlp.path);
cmd.args([
"--flat-playlist",
"--simulate",
"-O", "%(url)s|%(title)s",
url.as_str()
]);
cmd
.stderr(Stdio::null())
.stdout(Stdio::piped());
let ftr = cmd.output();
let mut ret = HashMap::new();
let out = futures::executor::block_on(ftr)?.stdout;
let out = String::from_utf8(out)?;
for line in out.lines() {
let mut split_text = line.split("|").collect::<Vec<&str>>();
let url = split_text.swap_remove(0).to_string();
let sname = split_text.join("|");
let song = Song::from_url_str(url)?.set_type(SongType::Youtube).clone();
self.nb_cache.push((pname.clone(), sname.clone(), song.clone(), format.clone()));
ret.insert(sname, song.clone());
}
self.nb_initial_song_count += out.lines().count();
self.download_all_nb_poll(cfg)?;
Ok(ret)
}
pub fn download_song(&mut self, cfg: &ConfigWrapper, name: &String, song: &Song, playlist: &String, format: &Format) -> anyhow::Result<()> {
let dl_dir = format!("{}/{playlist}", cfg.cli.output);
let dl_file = format!("{dl_dir}/{}.{}", name, &format);

View File

@ -43,23 +43,23 @@ impl Manifest {
pub fn get_format(&self) -> &Format {
&self.format
}
pub fn add_song(&mut self, playlist_name: String, name: SongName, song: Song) -> Option<Song> {
pub fn add_song(&mut self, playlist_name: &String, name: SongName, song: Song) -> Option<Song> {
self.get_playlist_mut(playlist_name)?.add_song(name, song)
}
pub fn get_song(&self, playlist_name: String, name: &String) -> Option<&Song> {
pub fn get_song(&self, playlist_name: &String, name: &String) -> Option<&Song> {
self.get_playlist(playlist_name)?.get_song(name)
}
pub fn get_song_mut(&mut self, playlist_name: String, name: &String) -> Option<&mut Song> {
pub fn get_song_mut(&mut self, playlist_name: &String, name: &String) -> Option<&mut Song> {
self.get_playlist_mut(playlist_name)?.get_song_mut(name)
}
pub fn add_playlist(&mut self, playlist_name: String) {
self.playlists.insert(playlist_name, Default::default());
}
pub fn get_playlist(&self, playlist_name: String) -> Option<&playlist::Playlist> {
self.playlists.get(&playlist_name)
pub fn get_playlist(&self, playlist_name: &String) -> Option<&playlist::Playlist> {
self.playlists.get(playlist_name)
}
pub fn get_playlist_mut(&mut self, playlist_name: String) -> Option<&mut playlist::Playlist> {
self.playlists.get_mut(&playlist_name)
pub fn get_playlist_mut(&mut self, playlist_name: &String) -> Option<&mut playlist::Playlist> {
self.playlists.get_mut(playlist_name)
}
pub fn get_playlists(&self) -> &HashMap<String, playlist::Playlist> {
&self.playlists

View File

@ -38,6 +38,9 @@ pub fn add_proc(mut cmd: Command, msg: String) -> anyhow::Result<()> {
Ok(())
}
pub fn proc_count() -> usize {
PROCESSES.lock().unwrap().read().unwrap().len()
}
pub fn is_proc_queue_full(max: usize) -> bool {
let proc_cnt = PROCESSES.lock().unwrap().read().unwrap().len();