mirror of
https://gitverse.ru/ot/DownOnSpot
synced 2025-12-19 09:54:19 +00:00
Lint code
This commit is contained in:
parent
e81e0da7e6
commit
7a86351998
5 changed files with 900 additions and 909 deletions
|
|
@ -7,7 +7,7 @@ use crate::error::SpotifyError::{LameConverterError, InvalidFormat};
|
||||||
|
|
||||||
/// Converts audio to MP3
|
/// Converts audio to MP3
|
||||||
pub enum AudioConverter {
|
pub enum AudioConverter {
|
||||||
OGG {
|
Ogg {
|
||||||
decoder: OggStreamReader<ReadWrap>,
|
decoder: OggStreamReader<ReadWrap>,
|
||||||
lame: lame::Lame,
|
lame: lame::Lame,
|
||||||
lame_end: bool,
|
lame_end: bool,
|
||||||
|
|
@ -48,10 +48,10 @@ impl AudioConverter {
|
||||||
};
|
};
|
||||||
|
|
||||||
match format {
|
match format {
|
||||||
AudioFormat::AAC => todo!(),
|
AudioFormat::Aac => todo!(),
|
||||||
AudioFormat::MP4 => todo!(),
|
AudioFormat::Mp4 => todo!(),
|
||||||
// Lewton decoder
|
// Lewton decoder
|
||||||
AudioFormat::OGG => {
|
AudioFormat::Ogg => {
|
||||||
let decoder = OggStreamReader::new(ReadWrap::new(Box::new(read)))?;
|
let decoder = OggStreamReader::new(ReadWrap::new(Box::new(read)))?;
|
||||||
let sample_rate = decoder.ident_hdr.audio_sample_rate;
|
let sample_rate = decoder.ident_hdr.audio_sample_rate;
|
||||||
// Init lame
|
// Init lame
|
||||||
|
|
@ -64,13 +64,13 @@ impl AudioConverter {
|
||||||
Err(_) => return Err(LameConverterError("Init".to_string()))
|
Err(_) => return Err(LameConverterError("Init".to_string()))
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(AudioConverter::OGG {
|
Ok(AudioConverter::Ogg {
|
||||||
lame,
|
lame,
|
||||||
decoder,
|
decoder,
|
||||||
lame_end: false,
|
lame_end: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
AudioFormat::MP3 => panic!("No reencoding allowd!"),
|
AudioFormat::Mp3 => panic!("No reencoding allowd!"),
|
||||||
_ => Err(InvalidFormat),
|
_ => Err(InvalidFormat),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +79,7 @@ impl AudioConverter {
|
||||||
impl Read for AudioConverter {
|
impl Read for AudioConverter {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
match self {
|
match self {
|
||||||
AudioConverter::OGG {
|
AudioConverter::Ogg {
|
||||||
decoder,
|
decoder,
|
||||||
lame,
|
lame,
|
||||||
lame_end,
|
lame_end,
|
||||||
|
|
|
||||||
1397
src/downloader.rs
1397
src/downloader.rs
File diff suppressed because it is too large
Load diff
318
src/spotify.rs
318
src/spotify.rs
|
|
@ -1,5 +1,5 @@
|
||||||
use aspotify::{
|
use aspotify::{
|
||||||
Album, Client, ClientCredentials, Playlist, PlaylistItemType, Track, TrackSimplified, Artist
|
Album, Artist, Client, ClientCredentials, Playlist, PlaylistItemType, Track, TrackSimplified,
|
||||||
};
|
};
|
||||||
use librespot::core::authentication::Credentials;
|
use librespot::core::authentication::Credentials;
|
||||||
use librespot::core::config::SessionConfig;
|
use librespot::core::config::SessionConfig;
|
||||||
|
|
@ -10,191 +10,183 @@ use url::Url;
|
||||||
use crate::error::SpotifyError;
|
use crate::error::SpotifyError;
|
||||||
|
|
||||||
pub struct Spotify {
|
pub struct Spotify {
|
||||||
// librespotify sessopm
|
// librespotify sessopm
|
||||||
pub session: Session,
|
pub session: Session,
|
||||||
pub spotify: Client,
|
pub spotify: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Spotify {
|
impl Spotify {
|
||||||
|
/// Create new instance
|
||||||
|
pub async fn new(
|
||||||
|
username: &str,
|
||||||
|
password: &str,
|
||||||
|
client_id: &str,
|
||||||
|
client_secret: &str,
|
||||||
|
) -> Result<Spotify, SpotifyError> {
|
||||||
|
// librespot
|
||||||
|
let credentials = Credentials::with_password(username, password);
|
||||||
|
let session = Session::connect(SessionConfig::default(), credentials, None).await?;
|
||||||
|
//aspotify
|
||||||
|
let credentials = ClientCredentials {
|
||||||
|
id: client_id.to_string(),
|
||||||
|
secret: client_secret.to_string(),
|
||||||
|
};
|
||||||
|
let spotify = Client::new(credentials);
|
||||||
|
|
||||||
/// Create new instance
|
Ok(Spotify { session, spotify })
|
||||||
pub async fn new(
|
}
|
||||||
username: &str,
|
|
||||||
password: &str,
|
|
||||||
client_id: &str,
|
|
||||||
client_secret: &str,
|
|
||||||
) -> Result<Spotify, SpotifyError> {
|
|
||||||
// librespot
|
|
||||||
let credentials = Credentials::with_password(username, password);
|
|
||||||
let session = Session::connect(SessionConfig::default(), credentials, None).await?;
|
|
||||||
//aspotify
|
|
||||||
let credentials = ClientCredentials {
|
|
||||||
id: client_id.to_string(),
|
|
||||||
secret: client_secret.to_string(),
|
|
||||||
};
|
|
||||||
let spotify = Client::new(credentials);
|
|
||||||
|
|
||||||
Ok(Spotify { session, spotify })
|
/// Parse URI or URL into URI
|
||||||
}
|
pub fn parse_uri(uri: &str) -> Result<String, SpotifyError> {
|
||||||
|
// Already URI
|
||||||
|
if uri.starts_with("spotify:") {
|
||||||
|
if uri.split(':').count() < 3 {
|
||||||
|
return Err(SpotifyError::InvalidUri);
|
||||||
|
}
|
||||||
|
return Ok(uri.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse URI or URL into URI
|
// Parse URL
|
||||||
pub fn parse_uri(uri: &str) -> Result<String, SpotifyError> {
|
let url = Url::parse(uri)?;
|
||||||
// Already URI
|
// Spotify Web Player URL
|
||||||
if uri.starts_with("spotify:") {
|
if url.host_str() == Some("open.spotify.com") {
|
||||||
if uri.split(':').collect::<Vec<&str>>().len() < 3 {
|
let path = url
|
||||||
return Err(SpotifyError::InvalidUri);
|
.path_segments()
|
||||||
}
|
.ok_or_else(|| SpotifyError::Error("Missing URL path".into()))?
|
||||||
return Ok(uri.to_string());
|
.collect::<Vec<&str>>();
|
||||||
}
|
if path.len() < 2 {
|
||||||
|
return Err(SpotifyError::InvalidUri);
|
||||||
|
}
|
||||||
|
return Ok(format!("spotify:{}:{}", path[0], path[1]));
|
||||||
|
}
|
||||||
|
Err(SpotifyError::InvalidUri)
|
||||||
|
}
|
||||||
|
|
||||||
// Parse URL
|
/// Fetch data for URI
|
||||||
let url = Url::parse(uri)?;
|
pub async fn resolve_uri(&self, uri: &str) -> Result<SpotifyItem, SpotifyError> {
|
||||||
// Spotify Web Player URL
|
let parts = uri.split(':').skip(1).collect::<Vec<&str>>();
|
||||||
if url.host_str() == Some("open.spotify.com") {
|
let id = parts[1];
|
||||||
let path = url
|
match parts[0] {
|
||||||
.path_segments()
|
"track" => {
|
||||||
.ok_or(SpotifyError::Error("Missing URL path".into()))?
|
let track = self.spotify.tracks().get_track(id, None).await?;
|
||||||
.collect::<Vec<&str>>();
|
Ok(SpotifyItem::Track(track.data))
|
||||||
if path.len() < 2 {
|
}
|
||||||
return Err(SpotifyError::InvalidUri);
|
"playlist" => {
|
||||||
}
|
let playlist = self.spotify.playlists().get_playlist(id, None).await?;
|
||||||
return Ok(format!("spotify:{}:{}", path[0], path[1]));
|
Ok(SpotifyItem::Playlist(playlist.data))
|
||||||
}
|
}
|
||||||
Err(SpotifyError::InvalidUri)
|
"album" => {
|
||||||
}
|
let album = self.spotify.albums().get_album(id, None).await?;
|
||||||
|
Ok(SpotifyItem::Album(album.data))
|
||||||
|
}
|
||||||
|
"artist" => {
|
||||||
|
let artist = self.spotify.artists().get_artist(id).await?;
|
||||||
|
Ok(SpotifyItem::Artist(artist.data))
|
||||||
|
}
|
||||||
|
// Unsupported / Unimplemented
|
||||||
|
_ => Ok(SpotifyItem::Other(uri.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Fetch data for URI
|
/// Get all tracks from playlist
|
||||||
pub async fn resolve_uri(&self, uri: &str) -> Result<SpotifyItem, SpotifyError> {
|
pub async fn full_playlist(&self, id: &str) -> Result<Vec<Track>, SpotifyError> {
|
||||||
let parts = uri.split(':').skip(1).collect::<Vec<&str>>();
|
let mut items = vec![];
|
||||||
let id = parts[1];
|
let mut offset = 0;
|
||||||
match parts[0] {
|
loop {
|
||||||
"track" => {
|
let page = self
|
||||||
let track = self.spotify.tracks().get_track(id, None).await?;
|
.spotify
|
||||||
Ok(SpotifyItem::Track(track.data))
|
.playlists()
|
||||||
}
|
.get_playlists_items(id, 100, offset, None)
|
||||||
"playlist" => {
|
.await?;
|
||||||
let playlist = self.spotify.playlists().get_playlist(id, None).await?;
|
items.append(
|
||||||
Ok(SpotifyItem::Playlist(playlist.data))
|
&mut page
|
||||||
}
|
.data
|
||||||
"album" => {
|
.items
|
||||||
let album = self.spotify.albums().get_album(id, None).await?;
|
.iter()
|
||||||
Ok(SpotifyItem::Album(album.data))
|
.filter_map(|i| -> Option<Track> {
|
||||||
}
|
if let Some(PlaylistItemType::Track(t)) = &i.item {
|
||||||
"artist" => {
|
Some(t.to_owned())
|
||||||
let artist = self.spotify.artists().get_artist(id).await?;
|
} else {
|
||||||
Ok(SpotifyItem::Artist(artist.data))
|
None
|
||||||
}
|
}
|
||||||
// Unsupported / Unimplemented
|
})
|
||||||
_ => Ok(SpotifyItem::Other(uri.to_string())),
|
.collect(),
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all tracks from playlist
|
// End
|
||||||
pub async fn full_playlist(&self, id: &str) -> Result<Vec<Track>, SpotifyError> {
|
offset += page.data.items.len();
|
||||||
let mut items = vec![];
|
if page.data.total == offset {
|
||||||
let mut offset = 0;
|
return Ok(items);
|
||||||
loop {
|
}
|
||||||
let page = self
|
}
|
||||||
.spotify
|
}
|
||||||
.playlists()
|
|
||||||
.get_playlists_items(id, 100, offset, None)
|
|
||||||
.await?;
|
|
||||||
items.append(
|
|
||||||
&mut page
|
|
||||||
.data
|
|
||||||
.items
|
|
||||||
.iter()
|
|
||||||
.filter_map(|i| {
|
|
||||||
if let Some(item) = &i.item {
|
|
||||||
if let PlaylistItemType::Track(t) = item {
|
|
||||||
Some(t.to_owned())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// End
|
/// Get all tracks from album
|
||||||
offset += page.data.items.len();
|
pub async fn full_album(&self, id: &str) -> Result<Vec<TrackSimplified>, SpotifyError> {
|
||||||
if page.data.total == offset {
|
let mut items = vec![];
|
||||||
return Ok(items);
|
let mut offset = 0;
|
||||||
}
|
loop {
|
||||||
}
|
let page = self
|
||||||
}
|
.spotify
|
||||||
|
.albums()
|
||||||
|
.get_album_tracks(id, 50, offset, None)
|
||||||
|
.await?;
|
||||||
|
items.append(&mut page.data.items.to_vec());
|
||||||
|
|
||||||
/// Get all tracks from album
|
// End
|
||||||
pub async fn full_album(&self, id: &str) -> Result<Vec<TrackSimplified>, SpotifyError> {
|
offset += page.data.items.len();
|
||||||
let mut items = vec![];
|
if page.data.total == offset {
|
||||||
let mut offset = 0;
|
return Ok(items);
|
||||||
loop {
|
}
|
||||||
let page = self
|
}
|
||||||
.spotify
|
}
|
||||||
.albums()
|
|
||||||
.get_album_tracks(id, 50, offset, None)
|
|
||||||
.await?;
|
|
||||||
items.append(&mut page.data.items.to_vec());
|
|
||||||
|
|
||||||
// End
|
/// Get all tracks from artist
|
||||||
offset += page.data.items.len();
|
pub async fn full_artist(&self, id: &str) -> Result<Vec<TrackSimplified>, SpotifyError> {
|
||||||
if page.data.total == offset {
|
let mut items = vec![];
|
||||||
return Ok(items);
|
let mut offset = 0;
|
||||||
}
|
loop {
|
||||||
}
|
let page = self
|
||||||
}
|
.spotify
|
||||||
|
.artists()
|
||||||
|
.get_artist_albums(id, None, 50, offset, None)
|
||||||
|
.await?;
|
||||||
|
|
||||||
/// Get all tracks from artist
|
for album in &mut page.data.items.iter() {
|
||||||
pub async fn full_artist(&self, id: &str) -> Result<Vec<TrackSimplified>, SpotifyError> {
|
items.append(&mut self.full_album(&album.id).await?)
|
||||||
let mut items = vec![];
|
}
|
||||||
let mut offset = 0;
|
|
||||||
loop {
|
|
||||||
let page = self
|
|
||||||
.spotify
|
|
||||||
.artists()
|
|
||||||
.get_artist_albums(id, None, 50, offset, None)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for album in &mut page
|
// End
|
||||||
.data
|
offset += page.data.items.len();
|
||||||
.items
|
if page.data.total == offset {
|
||||||
.iter() {
|
return Ok(items);
|
||||||
items.append(&mut self.full_album(&album.id).await?)
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// End
|
|
||||||
offset += page.data.items.len();
|
|
||||||
if page.data.total == offset {
|
|
||||||
return Ok(items);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Spotify {
|
impl Clone for Spotify {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
session: self.session.clone(),
|
session: self.session.clone(),
|
||||||
spotify: Client::new(self.spotify.credentials.clone()),
|
spotify: Client::new(self.spotify.credentials.clone()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Basic debug implementation so can be used in other structs
|
/// Basic debug implementation so can be used in other structs
|
||||||
impl fmt::Debug for Spotify {
|
impl fmt::Debug for Spotify {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "<Spotify Instance>")
|
write!(f, "<Spotify Instance>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SpotifyItem {
|
pub enum SpotifyItem {
|
||||||
Track(Track),
|
Track(Track),
|
||||||
Album(Album),
|
Album(Album),
|
||||||
Playlist(Playlist),
|
Playlist(Playlist),
|
||||||
Artist(Artist),
|
Artist(Artist),
|
||||||
/// Unimplemented
|
/// Unimplemented
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,53 +5,53 @@ use crate::downloader::AudioFormat;
|
||||||
use crate::error::SpotifyError;
|
use crate::error::SpotifyError;
|
||||||
|
|
||||||
use self::id3::ID3Tag;
|
use self::id3::ID3Tag;
|
||||||
use ogg::OGGTag;
|
use ogg::OggTag;
|
||||||
|
|
||||||
mod id3;
|
mod id3;
|
||||||
mod ogg;
|
mod ogg;
|
||||||
|
|
||||||
pub enum TagWrap {
|
pub enum TagWrap {
|
||||||
OGG(OGGTag),
|
Ogg(OggTag),
|
||||||
ID3(ID3Tag),
|
Id3(ID3Tag),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TagWrap {
|
impl TagWrap {
|
||||||
/// Load from file
|
/// Load from file
|
||||||
pub fn new(path: impl AsRef<Path>, format: AudioFormat) -> Result<TagWrap, SpotifyError> {
|
pub fn new(path: impl AsRef<Path>, format: AudioFormat) -> Result<TagWrap, SpotifyError> {
|
||||||
match format {
|
match format {
|
||||||
AudioFormat::OGG => Ok(TagWrap::OGG(OGGTag::open(path)?)),
|
AudioFormat::Ogg => Ok(TagWrap::Ogg(OggTag::open(path)?)),
|
||||||
AudioFormat::MP3 => Ok(TagWrap::ID3(ID3Tag::open(path)?)),
|
AudioFormat::Mp3 => Ok(TagWrap::Id3(ID3Tag::open(path)?)),
|
||||||
_ => Err(SpotifyError::Error("Invalid format!".into())),
|
_ => Err(SpotifyError::Error("Invalid format!".into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get Tag trait
|
/// Get Tag trait
|
||||||
pub fn get_tag(&mut self) -> Box<&mut dyn Tag> {
|
pub fn get_tag(&mut self) -> Box<&mut dyn Tag> {
|
||||||
match self {
|
match self {
|
||||||
TagWrap::OGG(tag) => Box::new(tag),
|
TagWrap::Ogg(tag) => Box::new(tag),
|
||||||
TagWrap::ID3(tag) => Box::new(tag),
|
TagWrap::Id3(tag) => Box::new(tag),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Tag {
|
pub trait Tag {
|
||||||
// Set tag values separator
|
// Set tag values separator
|
||||||
fn set_separator(&mut self, separator: &str);
|
fn set_separator(&mut self, separator: &str);
|
||||||
fn set_raw(&mut self, tag: &str, value: Vec<String>);
|
fn set_raw(&mut self, tag: &str, value: Vec<String>);
|
||||||
fn set_field(&mut self, field: Field, value: Vec<String>);
|
fn set_field(&mut self, field: Field, value: Vec<String>);
|
||||||
fn set_release_date(&mut self, date: NaiveDate);
|
fn set_release_date(&mut self, date: NaiveDate);
|
||||||
fn add_cover(&mut self, mime: &str, data: Vec<u8>);
|
fn add_cover(&mut self, mime: &str, data: Vec<u8>);
|
||||||
fn save(&mut self) -> Result<(), SpotifyError>;
|
fn save(&mut self) -> Result<(), SpotifyError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Field {
|
pub enum Field {
|
||||||
Title,
|
Title,
|
||||||
Artist,
|
Artist,
|
||||||
Album,
|
Album,
|
||||||
TrackNumber,
|
TrackNumber,
|
||||||
DiscNumber,
|
DiscNumber,
|
||||||
AlbumArtist,
|
AlbumArtist,
|
||||||
Genre,
|
Genre,
|
||||||
Label,
|
Label,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,24 @@ use std::path::{Path, PathBuf};
|
||||||
use super::Field;
|
use super::Field;
|
||||||
use crate::error::SpotifyError;
|
use crate::error::SpotifyError;
|
||||||
|
|
||||||
pub struct OGGTag {
|
pub struct OggTag {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
tag: CommentHeader,
|
tag: CommentHeader,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OGGTag {
|
impl OggTag {
|
||||||
/// Load tag from file
|
/// Load tag from file
|
||||||
pub fn open(path: impl AsRef<Path>) -> Result<OGGTag, SpotifyError> {
|
pub fn open(path: impl AsRef<Path>) -> Result<OggTag, SpotifyError> {
|
||||||
let mut file = File::open(&path)?;
|
let mut file = File::open(&path)?;
|
||||||
let tag = read_comment_header(&mut file);
|
let tag = read_comment_header(&mut file);
|
||||||
Ok(OGGTag {
|
Ok(OggTag {
|
||||||
path: path.as_ref().to_owned(),
|
path: path.as_ref().to_owned(),
|
||||||
tag,
|
tag,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::Tag for OGGTag {
|
impl super::Tag for OggTag {
|
||||||
fn set_separator(&mut self, _separator: &str) {}
|
fn set_separator(&mut self, _separator: &str) {}
|
||||||
|
|
||||||
fn set_field(&mut self, field: Field, value: Vec<String>) {
|
fn set_field(&mut self, field: Field, value: Vec<String>) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue