Skip to content
Snippets Groups Projects
Commit 63b8cd43 authored by Falk Rehse's avatar Falk Rehse
Browse files

Fetching song info

parent 0ebadbc5
Branches
No related tags found
No related merge requests found
Pipeline #118086 failed
......@@ -120,31 +120,81 @@ fn generate_playlist_api(playlist: &Vec<Song>) -> Vec<ApiSong> {
Song::Queued { uuid, url, .. } => ApiSong {
uuid: uuid.to_string(),
url: url.to_string(),
title: None,
artist: None,
artist_url: None,
length: None,
thumbnail_url: None,
state: ApiSongState::Queued,
},
Song::Downloading { uuid, url, .. } => ApiSong {
Song::InfoFetching { uuid, url, .. } => ApiSong {
uuid: uuid.to_string(),
url: url.to_string(),
title: None,
artist: None,
artist_url: None,
length: None,
thumbnail_url: None,
state: ApiSongState::InfoFetching,
},
Song::InfoFetched { uuid, url, title, artist, artist_url, length, thumbnail_file, .. } => ApiSong {
uuid: uuid.to_string(),
url: url.to_string(),
title: title.to_owned(),
artist: artist.to_owned(),
artist_url: if let Some(artist_url) = artist_url { Some(artist_url.to_string()) } else { None },
length: length.to_owned(),
thumbnail_url: Some(thumbnail_file.to_owned()),
state: ApiSongState::InfoFetched,
},
Song::Downloading { uuid, url, title, artist, artist_url, length, thumbnail_file, .. } => ApiSong {
uuid: uuid.to_string(),
url: url.to_string(),
title: title.to_owned(),
artist: artist.to_owned(),
artist_url: if let Some(artist_url) = artist_url { Some(artist_url.to_string()) } else { None },
length: length.to_owned(),
thumbnail_url: Some(thumbnail_file.to_owned()),
state: ApiSongState::Downloading,
},
Song::Downloaded { uuid, url, .. } => ApiSong {
Song::Downloaded { uuid, url, title, artist, artist_url, length, thumbnail_file, .. } => ApiSong {
uuid: uuid.to_string(),
url: url.to_string(),
title: title.to_owned(),
artist: artist.to_owned(),
artist_url: if let Some(artist_url) = artist_url { Some(artist_url.to_string()) } else { None },
length: length.to_owned(),
thumbnail_url: Some(thumbnail_file.to_owned()),
state: ApiSongState::Downloaded,
},
Song::Converting { uuid, url, .. } => ApiSong {
Song::Converting { uuid, url, title, artist, artist_url, length, thumbnail_file, .. } => ApiSong {
uuid: uuid.to_string(),
url: url.to_string(),
title: title.to_owned(),
artist: artist.to_owned(),
artist_url: if let Some(artist_url) = artist_url { Some(artist_url.to_string()) } else { None },
length: length.to_owned(),
thumbnail_url: Some(thumbnail_file.to_owned()),
state: ApiSongState::Converting,
},
Song::Ready { uuid, url, .. } => ApiSong {
Song::Ready { uuid, url, title, artist, artist_url, length, thumbnail_file, .. } => ApiSong {
uuid: uuid.to_string(),
url: url.to_string(),
title: title.to_owned(),
artist: artist.to_owned(),
artist_url: if let Some(artist_url) = artist_url { Some(artist_url.to_string()) } else { None },
length: length.to_owned(),
thumbnail_url: Some(thumbnail_file.to_owned()),
state: ApiSongState::Ready,
},
Song::Playing { uuid, url, .. } => ApiSong {
Song::Playing { uuid, url, title, artist, artist_url, length, thumbnail_file, .. } => ApiSong {
uuid: uuid.to_string(),
url: url.to_string(),
title: title.to_owned(),
artist: artist.to_owned(),
artist_url: if let Some(artist_url) = artist_url { Some(artist_url.to_string()) } else { None },
length: length.to_owned(),
thumbnail_url: Some(thumbnail_file.to_owned()),
state: ApiSongState::Playing,
},
})
......
use camino::Utf8Path;
use futures_signals::signal::Mutable;
use serde_json::Value;
use std::process;
use std::thread::{sleep, spawn};
use std::time;
......@@ -11,12 +12,14 @@ use crate::util;
const TMP_DOWNLOAD_DIR: &str = "./tmp";
const SONGS_INFO_FETCHING_LIMIT: u32 = 5;
const SONGS_DOWNLOADING_LIMIT: u32 = 1;
const SONGS_CONVERTING_LIMIT: u32 = 1;
const SONGS_READY_TARGET: u32 = 3;
pub fn manage_downloads(playlist: Mutable<Vec<Song>>) -> () {
loop {
let mut songs_info_fetching = 0;
let mut songs_downloading = 0;
let mut songs_converting = 0;
let mut songs_ready = 0;
......@@ -24,31 +27,71 @@ pub fn manage_downloads(playlist: Mutable<Vec<Song>>) -> () {
let mut playlist_guard = playlist.lock_mut();
for song in (*playlist_guard).iter_mut() {
if songs_ready >= SONGS_READY_TARGET {
break;
}
match song {
Song::Queued { uuid, url } => {
if songs_info_fetching < SONGS_INFO_FETCHING_LIMIT {
let uuid_clone = uuid.clone();
let url_clone = url.clone();
*song = Song::InfoFetching {
uuid: uuid.clone(),
url: url.clone(),
};
let playlist = Mutable::clone(&playlist);
spawn(move || fetch_info(playlist, &uuid_clone, &url_clone));
songs_info_fetching += 1;
}
}
Song::InfoFetching { .. } => {
songs_info_fetching += 1;
}
Song::InfoFetched {
uuid,
url,
title,
artist,
artist_url,
length,
thumbnail_file,
} => {
if songs_downloading < SONGS_DOWNLOADING_LIMIT
&& songs_ready + songs_converting < SONGS_READY_TARGET
&& songs_ready + songs_converting + songs_downloading < SONGS_READY_TARGET
{
let tmp_file_name = util::random_base64();
let tmp_file_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(tmp_file_name);
let uuid = uuid.clone();
let uuid_clone = uuid.clone();
let url = url.clone();
let url_clone = url.clone();
let title_clone = title.clone();
let artist_clone = artist.clone();
let artist_url_clone = artist_url.clone();
let length_clone = length.clone();
let thumbnail_file_clone = thumbnail_file.clone();
*song = Song::Downloading {
uuid: uuid,
url: url,
audio_file: tmp_file_path.to_string(),
uuid: uuid.clone(),
url: url.clone(),
title: title.clone(),
artist: artist.clone(),
artist_url: artist_url.clone(),
length: length.to_owned(),
thumbnail_file: thumbnail_file.clone(),
};
let playlist = Mutable::clone(&playlist);
spawn(move || {
download_song(playlist, &uuid_clone, &url_clone, &tmp_file_path)
download_song(
playlist,
&uuid_clone,
&url_clone,
&title_clone,
&artist_clone,
&artist_url_clone,
&length_clone,
&thumbnail_file_clone,
)
});
songs_downloading += 1;
}
}
Song::Downloading { .. } => {
......@@ -57,23 +100,34 @@ pub fn manage_downloads(playlist: Mutable<Vec<Song>>) -> () {
Song::Downloaded {
uuid,
url,
title,
artist,
artist_url,
length,
thumbnail_file,
audio_file,
} => {
if songs_converting < SONGS_CONVERTING_LIMIT && songs_ready < SONGS_READY_TARGET
if songs_converting < SONGS_CONVERTING_LIMIT
&& songs_ready + songs_converting < SONGS_READY_TARGET
{
let tmp_file_name = format!("{}.raw", util::random_base64());
let tmp_file_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(tmp_file_name);
let uuid = uuid.clone();
let uuid_clone = uuid.clone();
let url = url.clone();
let url_clone = url.clone();
let audio_file = audio_file.clone();
let title_clone = title.clone();
let artist_clone = artist.clone();
let artist_url_clone = artist_url.clone();
let length_clone = length.clone();
let thumbnail_file_clone = thumbnail_file.clone();
let audio_file_clone = audio_file.clone();
*song = Song::Converting {
uuid: uuid.clone(),
url: url,
audio_file: audio_file,
raw_file: tmp_file_path.to_string(),
url: url.clone(),
title: title.clone(),
artist: artist.clone(),
artist_url: artist_url.clone(),
length: length.to_owned(),
thumbnail_file: thumbnail_file.clone(),
audio_file: audio_file.clone(),
};
let playlist = Mutable::clone(&playlist);
......@@ -82,13 +136,18 @@ pub fn manage_downloads(playlist: Mutable<Vec<Song>>) -> () {
playlist,
&uuid_clone,
&url_clone,
&title_clone,
&artist_clone,
&artist_url_clone,
&length_clone,
&thumbnail_file_clone,
&audio_file_clone,
&tmp_file_path,
)
});
}
songs_converting += 1;
}
}
Song::Converting { .. } => {
songs_converting += 1;
}
......@@ -105,24 +164,114 @@ pub fn manage_downloads(playlist: Mutable<Vec<Song>>) -> () {
}
}
fn download_song(playlist: Mutable<Vec<Song>>, uuid: &Uuid, url: &Url, audio_file: &Utf8Path) {
println!("Downloading from {} to {}!", url, audio_file);
fn fetch_info(playlist: Mutable<Vec<Song>>, uuid: &Uuid, url: &Url) {
println!("Fetching info for {}!", url);
let tmp_file_name = util::random_base64();
let tmp_file_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(tmp_file_name);
process::Command::new("yt-dlp")
.args([
"--output",
tmp_file_path.as_str(),
"--write-info-json",
"--write-thumbnail",
"--skip-download",
url.as_str(),
])
.output()
.expect("Failed to fetch info!");
let info_json_file = format!("{}.info.json", tmp_file_path);
let data = std::fs::read_to_string(info_json_file).expect("Could not find info.json!");
let info_json: Value = serde_json::from_str(&data).expect("Could not parse info.json!");
let mut title = None;
if let Value::String(string) = &info_json["title"] {
title = Some(string.to_owned());
} else {
eprintln!("Could not fetch title!");
}
let mut artist = None;
if let Value::String(string) = &info_json["channel"] {
artist = Some(string.to_owned());
} else {
eprintln!("Could not fetch artist!");
}
let mut artist_url = None;
if let Value::String(string) = &info_json["channel_url"] {
if let Ok(url) = Url::parse(&string) {
artist_url = Some(url);
} else {
eprintln!("Invalid artist_url!");
}
} else {
eprintln!("Could not fetch artist_url!");
}
let mut length = None;
if let Value::Number(number) = &info_json["duration"] {
length = number.as_u64();
} else {
eprintln!("Could not fetch artist_url!");
}
println!("Fetched all info!");
let thumbnail_file = format!("{}.webm", tmp_file_path);
let mut playlist = playlist.lock_mut();
if let Some(index) = (*playlist).iter().position(|song| song.equals_uuid(uuid)) {
playlist[index] = Song::InfoFetched {
uuid: uuid.to_owned(),
url: url.to_owned(),
title: title,
artist: artist,
artist_url: artist_url,
length: length,
thumbnail_file: thumbnail_file.to_owned(),
};
}
drop(playlist);
}
fn download_song(
playlist: Mutable<Vec<Song>>,
uuid: &Uuid,
url: &Url,
title: &Option<String>,
artist: &Option<String>,
artist_url: &Option<Url>,
length: &Option<u64>,
thumbnail_file: &str,
) {
let audio_file_name = util::random_base64();
let audio_file_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(audio_file_name);
println!("Downloading from {} to {}!", url, audio_file_path);
process::Command::new("yt-dlp")
.args(["--output", audio_file.as_str(), url.as_str()])
.args(["--output", audio_file_path.as_str(), url.as_str()])
.output()
.expect("Failed to download audio!");
println!("Download successful!");
let audio_file = format!("{}.webm", audio_file);
let audio_file_path = format!("{}.webm", audio_file_path);
let mut playlist = playlist.lock_mut();
if let Some(index) = (*playlist).iter().position(|song| song.equals_uuid(uuid)) {
playlist[index] = Song::Downloaded {
uuid: uuid.to_owned(),
url: url.to_owned(),
audio_file: audio_file.to_owned(),
title: title.to_owned(),
artist: artist.to_owned(),
artist_url: artist_url.to_owned(),
length: length.to_owned(),
thumbnail_file: thumbnail_file.to_owned(),
audio_file: audio_file_path.to_owned(),
};
}
drop(playlist);
......@@ -132,12 +281,19 @@ fn convert_song(
playlist: Mutable<Vec<Song>>,
uuid: &Uuid,
url: &Url,
title: &Option<String>,
artist: &Option<String>,
artist_url: &Option<Url>,
length: &Option<u64>,
thumbnail_file: &str,
audio_file: &str,
raw_file: &Utf8Path,
) {
let audio_file = Utf8Path::new(audio_file);
println!("Converting from {} to {}!", audio_file, raw_file);
let raw_file_name = format!("{}.raw", util::random_base64());
let raw_file_path = Utf8Path::new(TMP_DOWNLOAD_DIR).join(raw_file_name);
println!("Converting from {} to {}!", audio_file, raw_file_path);
process::Command::new("ffmpeg")
.args([
......@@ -151,7 +307,7 @@ fn convert_song(
"384k",
"-ac",
"1",
raw_file.as_str(),
raw_file_path.as_str(),
])
.output()
.expect("Failed to convert audio!");
......@@ -164,7 +320,12 @@ fn convert_song(
playlist[index] = Song::Ready {
uuid: uuid.to_owned(),
url: url.to_owned(),
raw_file: raw_file.as_str().to_owned(),
title: title.to_owned(),
artist: artist.to_owned(),
artist_url: artist_url.to_owned(),
length: length.to_owned(),
thumbnail_file: thumbnail_file.to_owned(),
raw_file: raw_file_path.as_str().to_owned(),
};
}
drop(playlist);
......
......@@ -33,12 +33,19 @@ fn main() {
.expect("No client token provided!"),
);
let bind_host =
env::var("SPOCCIFY_BIND_HOST").unwrap_or("localhost".to_owned());
let bind_port =
env::var("SPOCCIFY_BIND_PORT").unwrap_or("8080".to_owned());
let http_bind_host =
env::var("SPOCCIFY_HTTP_BIND_HOST").unwrap_or("localhost".to_owned());
let http_bind_port =
env::var("SPOCCIFY_HTTP_BIND_PORT").unwrap_or("9000".to_owned());
let bind_address = format!("{}:{}", bind_host, bind_port);
let http_bind_address = format!("{}:{}", http_bind_host, http_bind_port);
let websocket_bind_host =
env::var("SPOCCIFY_WEBSOCKET_BIND_HOST").unwrap_or("localhost".to_owned());
let websocket_bind_port =
env::var("SPOCCIFY_WEBSOCKET_BIND_PORT").unwrap_or("9001".to_owned());
let websocket_bind_address = format!("{}:{}", websocket_bind_host, websocket_bind_port);
println!("Listening for websocket connections on {}!", bind_address);
......
......@@ -86,6 +86,11 @@ fn get_next_song_file(playlist: &Mutable<Vec<Song>>) -> std::io::Result<File> {
if let Song::Ready {
uuid,
url,
title,
artist,
artist_url,
length,
thumbnail_file,
raw_file,
} = &playlist[0]
{
......@@ -96,6 +101,11 @@ fn get_next_song_file(playlist: &Mutable<Vec<Song>>) -> std::io::Result<File> {
playlist[0] = Song::Playing {
uuid: uuid.clone(),
url: url.clone(),
title: title.clone(),
artist: artist.clone(),
artist_url: artist_url.clone(),
length: length.to_owned(),
thumbnail_file: thumbnail_file.clone(),
raw_file: raw_file,
};
file = File::open(raw_file_clone);
......
......@@ -8,30 +8,66 @@ pub enum Song {
uuid: Uuid,
url: Url,
},
InfoFetching {
uuid: Uuid,
url: Url,
},
InfoFetched {
uuid: Uuid,
url: Url,
title: Option<String>,
artist: Option<String>,
artist_url: Option<Url>,
length: Option<u64>,
thumbnail_file: String,
},
Downloading {
uuid: Uuid,
url: Url,
audio_file: String,
title: Option<String>,
artist: Option<String>,
artist_url: Option<Url>,
length: Option<u64>,
thumbnail_file: String,
},
Downloaded {
uuid: Uuid,
url: Url,
title: Option<String>,
artist: Option<String>,
artist_url: Option<Url>,
length: Option<u64>,
thumbnail_file: String,
audio_file: String,
},
Converting {
uuid: Uuid,
url: Url,
title: Option<String>,
artist: Option<String>,
artist_url: Option<Url>,
length: Option<u64>,
thumbnail_file: String,
audio_file: String,
raw_file: String,
},
Ready {
uuid: Uuid,
url: Url,
title: Option<String>,
artist: Option<String>,
artist_url: Option<Url>,
length: Option<u64>,
thumbnail_file: String,
raw_file: String,
},
Playing {
uuid: Uuid,
url: Url,
title: Option<String>,
artist: Option<String>,
artist_url: Option<Url>,
length: Option<u64>,
thumbnail_file: String,
raw_file: String,
},
}
......@@ -40,6 +76,8 @@ impl Song {
pub fn equals_uuid(&self, uuid_compare: &Uuid) -> bool {
match self {
Self::Queued { uuid, .. } => uuid == uuid_compare,
Self::InfoFetching { uuid, .. } => uuid == uuid_compare,
Self::InfoFetched { uuid, .. } => uuid == uuid_compare,
Self::Downloading { uuid, .. } => uuid == uuid_compare,
Self::Downloaded { uuid, .. } => uuid == uuid_compare,
Self::Converting { uuid, .. } => uuid == uuid_compare,
......@@ -54,11 +92,18 @@ pub struct ApiSong {
pub uuid: String,
pub url: String,
pub state: ApiSongState,
pub title: Option<String>,
pub artist: Option<String>,
pub artist_url: Option<String>,
pub length: Option<u64>,
pub thumbnail_url: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ApiSongState {
Queued,
InfoFetching,
InfoFetched,
Downloading,
Downloaded,
Converting,
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment