Compare commits
No commits in common. "b4a6658e33c8cbe7f6463edee2c6ab824032508b" and "6c41ef2fb2531e10a12c271e2d48504510a3b0bf" have entirely different histories.
b4a6658e33
...
6c41ef2fb2
11 changed files with 82 additions and 112 deletions
|
@ -8,7 +8,7 @@ use futures::stream::{self, StreamExt};
|
||||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||||
use reqwest::{Client, ClientBuilder};
|
use reqwest::{Client, ClientBuilder};
|
||||||
use rustypipe::{
|
use rustypipe::{
|
||||||
client::{ClientType, RustyPipe},
|
client::RustyPipe,
|
||||||
model::{UrlTarget, VideoId, YouTubeItem},
|
model::{UrlTarget, VideoId, YouTubeItem},
|
||||||
param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter},
|
param::{search_filter, ChannelVideoTab, Country, Language, StreamFilter},
|
||||||
};
|
};
|
||||||
|
@ -81,8 +81,6 @@ enum Commands {
|
||||||
/// Get the player
|
/// Get the player
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
player: bool,
|
player: bool,
|
||||||
#[clap(long)]
|
|
||||||
player_type: Option<PlayerType>,
|
|
||||||
},
|
},
|
||||||
/// Search YouTube
|
/// Search YouTube
|
||||||
Search {
|
Search {
|
||||||
|
@ -191,14 +189,6 @@ enum MusicSearchCategory {
|
||||||
PlaylistsCommunity,
|
PlaylistsCommunity,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
|
|
||||||
enum PlayerType {
|
|
||||||
Desktop,
|
|
||||||
Tv,
|
|
||||||
Android,
|
|
||||||
Ios,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<SearchItemType> for search_filter::ItemType {
|
impl From<SearchItemType> for search_filter::ItemType {
|
||||||
fn from(value: SearchItemType) -> Self {
|
fn from(value: SearchItemType) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
@ -241,17 +231,6 @@ impl From<SearchOrder> for search_filter::Order {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PlayerType> for ClientType {
|
|
||||||
fn from(value: PlayerType) -> Self {
|
|
||||||
match value {
|
|
||||||
PlayerType::Desktop => Self::Desktop,
|
|
||||||
PlayerType::Tv => Self::TvHtml5Embed,
|
|
||||||
PlayerType::Android => Self::Android,
|
|
||||||
PlayerType::Ios => Self::Ios,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn download_single_video(
|
async fn download_single_video(
|
||||||
video_id: &str,
|
video_id: &str,
|
||||||
|
@ -561,7 +540,6 @@ async fn main() {
|
||||||
comments,
|
comments,
|
||||||
lyrics,
|
lyrics,
|
||||||
player,
|
player,
|
||||||
player_type,
|
|
||||||
} => {
|
} => {
|
||||||
let target = rp.query().resolve_string(&id, false).await.unwrap();
|
let target = rp.query().resolve_string(&id, false).await.unwrap();
|
||||||
|
|
||||||
|
@ -580,12 +558,7 @@ async fn main() {
|
||||||
let details = rp.query().music_details(&id).await.unwrap();
|
let details = rp.query().music_details(&id).await.unwrap();
|
||||||
print_data(&details, format, pretty);
|
print_data(&details, format, pretty);
|
||||||
} else if player {
|
} else if player {
|
||||||
let player = if let Some(player_type) = player_type {
|
let player = rp.query().player(&id).await.unwrap();
|
||||||
rp.query().player_from_client(&id, player_type.into()).await
|
|
||||||
} else {
|
|
||||||
rp.query().player(&id).await
|
|
||||||
}
|
|
||||||
.unwrap();
|
|
||||||
print_data(&player, format, pretty);
|
print_data(&player, format, pretty);
|
||||||
} else {
|
} else {
|
||||||
let mut details = rp.query().video_details(&id).await.unwrap();
|
let mut details = rp.query().video_details(&id).await.unwrap();
|
||||||
|
|
|
@ -192,15 +192,19 @@ const YOUTUBE_MUSIC_V1_URL: &str = "https://music.youtube.com/youtubei/v1/";
|
||||||
const YOUTUBE_HOME_URL: &str = "https://www.youtube.com/";
|
const YOUTUBE_HOME_URL: &str = "https://www.youtube.com/";
|
||||||
const YOUTUBE_MUSIC_HOME_URL: &str = "https://music.youtube.com/";
|
const YOUTUBE_MUSIC_HOME_URL: &str = "https://music.youtube.com/";
|
||||||
|
|
||||||
const DISABLE_PRETTY_PRINT_PARAMETER: &str = "prettyPrint=false";
|
const DISABLE_PRETTY_PRINT_PARAMETER: &str = "&prettyPrint=false";
|
||||||
|
|
||||||
// Desktop client
|
// Desktop client
|
||||||
const DESKTOP_CLIENT_VERSION: &str = "2.20230126.00.00";
|
const DESKTOP_CLIENT_VERSION: &str = "2.20230126.00.00";
|
||||||
|
const DESKTOP_API_KEY: &str = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
|
||||||
const TVHTML5_CLIENT_VERSION: &str = "2.0";
|
const TVHTML5_CLIENT_VERSION: &str = "2.0";
|
||||||
|
const DESKTOP_MUSIC_API_KEY: &str = "AIzaSyC9XL3ZjWddXya6X74dJoCTL-WEYFDNX30";
|
||||||
const DESKTOP_MUSIC_CLIENT_VERSION: &str = "1.20230123.01.01";
|
const DESKTOP_MUSIC_CLIENT_VERSION: &str = "1.20230123.01.01";
|
||||||
|
|
||||||
// Mobile client
|
// Mobile client
|
||||||
const MOBILE_CLIENT_VERSION: &str = "18.03.33";
|
const MOBILE_CLIENT_VERSION: &str = "18.03.33";
|
||||||
|
const ANDROID_API_KEY: &str = "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w";
|
||||||
|
const IOS_API_KEY: &str = "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc";
|
||||||
const IOS_DEVICE_MODEL: &str = "iPhone14,5";
|
const IOS_DEVICE_MODEL: &str = "iPhone14,5";
|
||||||
|
|
||||||
static CLIENT_VERSION_REGEX: Lazy<Regex> =
|
static CLIENT_VERSION_REGEX: Lazy<Regex> =
|
||||||
|
@ -1185,7 +1189,7 @@ impl RustyPipeQuery {
|
||||||
.inner
|
.inner
|
||||||
.http
|
.http
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{YOUTUBEI_V1_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
|
"{YOUTUBEI_V1_URL}{endpoint}?key={DESKTOP_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
||||||
))
|
))
|
||||||
.header(header::ORIGIN, YOUTUBE_HOME_URL)
|
.header(header::ORIGIN, YOUTUBE_HOME_URL)
|
||||||
.header(header::REFERER, YOUTUBE_HOME_URL)
|
.header(header::REFERER, YOUTUBE_HOME_URL)
|
||||||
|
@ -1200,7 +1204,7 @@ impl RustyPipeQuery {
|
||||||
.inner
|
.inner
|
||||||
.http
|
.http
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{YOUTUBE_MUSIC_V1_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
|
"{YOUTUBE_MUSIC_V1_URL}{endpoint}?key={DESKTOP_MUSIC_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
||||||
))
|
))
|
||||||
.header(header::ORIGIN, YOUTUBE_MUSIC_HOME_URL)
|
.header(header::ORIGIN, YOUTUBE_MUSIC_HOME_URL)
|
||||||
.header(header::REFERER, YOUTUBE_MUSIC_HOME_URL)
|
.header(header::REFERER, YOUTUBE_MUSIC_HOME_URL)
|
||||||
|
@ -1208,14 +1212,14 @@ impl RustyPipeQuery {
|
||||||
.header("X-YouTube-Client-Name", "67")
|
.header("X-YouTube-Client-Name", "67")
|
||||||
.header(
|
.header(
|
||||||
"X-YouTube-Client-Version",
|
"X-YouTube-Client-Version",
|
||||||
self.client.get_music_client_version().await,
|
self.client.get_music_client_version().await
|
||||||
),
|
),
|
||||||
ClientType::TvHtml5Embed => self
|
ClientType::TvHtml5Embed => self
|
||||||
.client
|
.client
|
||||||
.inner
|
.inner
|
||||||
.http
|
.http
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{YOUTUBEI_V1_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
|
"{YOUTUBEI_V1_URL}{endpoint}?key={DESKTOP_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
||||||
))
|
))
|
||||||
.header(header::ORIGIN, YOUTUBE_HOME_URL)
|
.header(header::ORIGIN, YOUTUBE_HOME_URL)
|
||||||
.header(header::REFERER, YOUTUBE_HOME_URL)
|
.header(header::REFERER, YOUTUBE_HOME_URL)
|
||||||
|
@ -1226,7 +1230,7 @@ impl RustyPipeQuery {
|
||||||
.inner
|
.inner
|
||||||
.http
|
.http
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{YOUTUBEI_V1_GAPIS_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
|
"{YOUTUBEI_V1_GAPIS_URL}{endpoint}?key={ANDROID_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
||||||
))
|
))
|
||||||
.header(
|
.header(
|
||||||
header::USER_AGENT,
|
header::USER_AGENT,
|
||||||
|
@ -1241,7 +1245,7 @@ impl RustyPipeQuery {
|
||||||
.inner
|
.inner
|
||||||
.http
|
.http
|
||||||
.post(format!(
|
.post(format!(
|
||||||
"{YOUTUBEI_V1_GAPIS_URL}{endpoint}?{DISABLE_PRETTY_PRINT_PARAMETER}"
|
"{YOUTUBEI_V1_GAPIS_URL}{endpoint}?key={IOS_API_KEY}{DISABLE_PRETTY_PRINT_PARAMETER}"
|
||||||
))
|
))
|
||||||
.header(
|
.header(
|
||||||
header::USER_AGENT,
|
header::USER_AGENT,
|
||||||
|
|
|
@ -306,14 +306,19 @@ impl MapResponse<Lyrics> for response::MusicLyrics {
|
||||||
) -> Result<MapResult<Lyrics>, ExtractionError> {
|
) -> Result<MapResult<Lyrics>, ExtractionError> {
|
||||||
let lyrics = self
|
let lyrics = self
|
||||||
.contents
|
.contents
|
||||||
.into_res()
|
.section_list_renderer
|
||||||
.map_err(|msg| ExtractionError::NotFound {
|
.and_then(|sl| {
|
||||||
id: id.to_owned(),
|
sl.contents
|
||||||
msg: msg.into(),
|
.into_iter()
|
||||||
})?
|
.find_map(|item| item.music_description_shelf_renderer)
|
||||||
.into_iter()
|
})
|
||||||
.find_map(|item| item.music_description_shelf_renderer)
|
.ok_or(match self.contents.message_renderer {
|
||||||
.ok_or(ExtractionError::InvalidData(Cow::Borrowed("no content")))?;
|
Some(msg) => ExtractionError::NotFound {
|
||||||
|
id: id.to_owned(),
|
||||||
|
msg: msg.text.into(),
|
||||||
|
},
|
||||||
|
None => ExtractionError::InvalidData(Cow::Borrowed("no content")),
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(MapResult {
|
Ok(MapResult {
|
||||||
c: Lyrics {
|
c: Lyrics {
|
||||||
|
@ -328,39 +333,36 @@ impl MapResponse<Lyrics> for response::MusicLyrics {
|
||||||
impl MapResponse<MusicRelated> for response::MusicRelated {
|
impl MapResponse<MusicRelated> for response::MusicRelated {
|
||||||
fn map_response(
|
fn map_response(
|
||||||
self,
|
self,
|
||||||
id: &str,
|
_id: &str,
|
||||||
lang: Language,
|
lang: Language,
|
||||||
_deobf: Option<&crate::deobfuscate::DeobfData>,
|
_deobf: Option<&crate::deobfuscate::DeobfData>,
|
||||||
_vdata: Option<&str>,
|
_vdata: Option<&str>,
|
||||||
) -> Result<MapResult<MusicRelated>, ExtractionError> {
|
) -> Result<MapResult<MusicRelated>, ExtractionError> {
|
||||||
let contents = self
|
|
||||||
.contents
|
|
||||||
.into_res()
|
|
||||||
.map_err(|msg| ExtractionError::NotFound {
|
|
||||||
id: id.to_owned(),
|
|
||||||
msg: msg.into(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Find artist
|
// Find artist
|
||||||
let artist_id = contents.iter().find_map(|section| match section {
|
let artist_id = self
|
||||||
response::music_item::ItemSection::MusicCarouselShelfRenderer(shelf) => {
|
.contents
|
||||||
shelf.header.as_ref().and_then(|h| {
|
.section_list_renderer
|
||||||
h.music_carousel_shelf_basic_header_renderer
|
.contents
|
||||||
.title
|
.iter()
|
||||||
.0
|
.find_map(|section| match section {
|
||||||
.iter()
|
response::music_item::ItemSection::MusicCarouselShelfRenderer(shelf) => {
|
||||||
.find_map(|c| {
|
shelf.header.as_ref().and_then(|h| {
|
||||||
let artist = ArtistId::from(c.clone());
|
h.music_carousel_shelf_basic_header_renderer
|
||||||
if artist.id.is_some() {
|
.title
|
||||||
Some(artist)
|
.0
|
||||||
} else {
|
.iter()
|
||||||
None
|
.find_map(|c| {
|
||||||
}
|
let artist = ArtistId::from(c.clone());
|
||||||
})
|
if artist.id.is_some() {
|
||||||
})
|
Some(artist)
|
||||||
}
|
} else {
|
||||||
_ => None,
|
None
|
||||||
});
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
|
||||||
let mut mapper_tracks = MusicListMapper::new(lang);
|
let mut mapper_tracks = MusicListMapper::new(lang);
|
||||||
let mut mapper = match artist_id {
|
let mut mapper = match artist_id {
|
||||||
|
@ -368,7 +370,7 @@ impl MapResponse<MusicRelated> for response::MusicRelated {
|
||||||
None => MusicListMapper::new(lang),
|
None => MusicListMapper::new(lang),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut sections = contents.into_iter();
|
let mut sections = self.contents.section_list_renderer.contents.into_iter();
|
||||||
if let Some(response::music_item::ItemSection::MusicCarouselShelfRenderer(shelf)) =
|
if let Some(response::music_item::ItemSection::MusicCarouselShelfRenderer(shelf)) =
|
||||||
sections.next()
|
sections.next()
|
||||||
{
|
{
|
||||||
|
|
|
@ -128,8 +128,8 @@ impl RustyPipeQuery {
|
||||||
video_id,
|
video_id,
|
||||||
content_check_ok: true,
|
content_check_ok: true,
|
||||||
racy_check_ok: true,
|
racy_check_ok: true,
|
||||||
// Source: https://github.com/TeamNewPipe/NewPipeExtractor/pull/1168
|
// Source: https://github.com/TeamNewPipe/NewPipeExtractor/pull/1084
|
||||||
params: Some("CgIIAQ%3D%3D").filter(|_| client_type == ClientType::Android),
|
params: Some("CgIQBg").filter(|_| client_type == ClientType::Android),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use super::AlertRenderer;
|
||||||
use super::ContentsRenderer;
|
use super::ContentsRenderer;
|
||||||
use super::{
|
use super::{
|
||||||
music_item::{ItemSection, PlaylistPanelRenderer},
|
music_item::{ItemSection, PlaylistPanelRenderer},
|
||||||
ContentRenderer,
|
ContentRenderer, SectionList,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Response model for YouTube Music track details
|
/// Response model for YouTube Music track details
|
||||||
|
@ -108,14 +108,14 @@ pub(crate) struct PlaylistPanel {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct MusicLyrics {
|
pub(crate) struct MusicLyrics {
|
||||||
pub contents: ListOrMessage<LyricsSection>,
|
pub contents: LyricsContents,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) enum ListOrMessage<T> {
|
pub(crate) struct LyricsContents {
|
||||||
SectionListRenderer(ContentsRenderer<T>),
|
pub message_renderer: Option<AlertRenderer>,
|
||||||
MessageRenderer(AlertRenderer),
|
pub section_list_renderer: Option<ContentsRenderer<LyricsSection>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
|
@ -137,14 +137,5 @@ pub(crate) struct LyricsRenderer {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub(crate) struct MusicRelated {
|
pub(crate) struct MusicRelated {
|
||||||
pub contents: ListOrMessage<ItemSection>,
|
pub contents: SectionList<ItemSection>,
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ListOrMessage<T> {
|
|
||||||
pub fn into_res(self) -> Result<Vec<T>, String> {
|
|
||||||
match self {
|
|
||||||
ListOrMessage::SectionListRenderer(c) => Ok(c.contents),
|
|
||||||
ListOrMessage::MessageRenderer(msg) => Err(msg.text),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "aGd3VKSOTxY",
|
id: "aGd3VKSOTxY",
|
||||||
name: "Ich wache auf",
|
name: "Ich wache auf",
|
||||||
duration: Some(222),
|
duration: Some(221),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -43,7 +43,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "Jz-26iiDuYs",
|
id: "Jz-26iiDuYs",
|
||||||
name: "Waldbrand",
|
name: "Waldbrand",
|
||||||
duration: Some(209),
|
duration: Some(208),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -64,7 +64,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "Bu26uFtpt58",
|
id: "Bu26uFtpt58",
|
||||||
name: "Verlernt",
|
name: "Verlernt",
|
||||||
duration: Some(224),
|
duration: Some(223),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -85,7 +85,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "RgwNqqiVqdY",
|
id: "RgwNqqiVqdY",
|
||||||
name: "In Farbe",
|
name: "In Farbe",
|
||||||
duration: Some(222),
|
duration: Some(221),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -106,7 +106,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "2TuOh30XbCI",
|
id: "2TuOh30XbCI",
|
||||||
name: "Stadt im Hinterland",
|
name: "Stadt im Hinterland",
|
||||||
duration: Some(198),
|
duration: Some(197),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
|
|
@ -22,7 +22,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "aGd3VKSOTxY",
|
id: "aGd3VKSOTxY",
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
duration: Some(222),
|
duration: Some(221),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -43,7 +43,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "Jz-26iiDuYs",
|
id: "Jz-26iiDuYs",
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
duration: Some(209),
|
duration: Some(208),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -64,7 +64,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "Bu26uFtpt58",
|
id: "Bu26uFtpt58",
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
duration: Some(224),
|
duration: Some(223),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -85,7 +85,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "RgwNqqiVqdY",
|
id: "RgwNqqiVqdY",
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
duration: Some(222),
|
duration: Some(221),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -106,7 +106,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "2TuOh30XbCI",
|
id: "2TuOh30XbCI",
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
duration: Some(198),
|
duration: Some(197),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
|
|
@ -63,11 +63,11 @@ MusicAlbum(
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("UC_KQPMiRQl3CFAIKTVfCHwA"),
|
id: Some("UCUhWwvF6gIPWTYlYb4-icLA"),
|
||||||
name: "L.r. Eswari",
|
name: "L.r. Eswari",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
artist_id: Some("UC_KQPMiRQl3CFAIKTVfCHwA"),
|
artist_id: Some("UCUhWwvF6gIPWTYlYb4-icLA"),
|
||||||
album: Some(AlbumId(
|
album: Some(AlbumId(
|
||||||
id: "MPREb_bqWA6mAZFWS",
|
id: "MPREb_bqWA6mAZFWS",
|
||||||
name: "Pedha Rasi Peddamma Katha",
|
name: "Pedha Rasi Peddamma Katha",
|
||||||
|
|
|
@ -63,11 +63,11 @@ MusicAlbum(
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("UC_KQPMiRQl3CFAIKTVfCHwA"),
|
id: Some("UCUhWwvF6gIPWTYlYb4-icLA"),
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
artist_id: Some("UC_KQPMiRQl3CFAIKTVfCHwA"),
|
artist_id: Some("UCUhWwvF6gIPWTYlYb4-icLA"),
|
||||||
album: Some(AlbumId(
|
album: Some(AlbumId(
|
||||||
id: "MPREb_bqWA6mAZFWS",
|
id: "MPREb_bqWA6mAZFWS",
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
|
|
|
@ -26,7 +26,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "AKJ3IJZKPWc",
|
id: "AKJ3IJZKPWc",
|
||||||
name: "Oh Javaraala",
|
name: "Oh Javaraala",
|
||||||
duration: Some(229),
|
duration: Some(228),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -51,7 +51,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "WnpZuHNB33E",
|
id: "WnpZuHNB33E",
|
||||||
name: "Siva Manoranjani",
|
name: "Siva Manoranjani",
|
||||||
duration: Some(267),
|
duration: Some(266),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -72,7 +72,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "pRqoDGXg1-I",
|
id: "pRqoDGXg1-I",
|
||||||
name: "Gulabi Buggalunna",
|
name: "Gulabi Buggalunna",
|
||||||
duration: Some(155),
|
duration: Some(154),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -93,7 +93,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "20vIKLJxjBY",
|
id: "20vIKLJxjBY",
|
||||||
name: "Kuluku Nadakula",
|
name: "Kuluku Nadakula",
|
||||||
duration: Some(179),
|
duration: Some(178),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
|
|
@ -26,7 +26,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "AKJ3IJZKPWc",
|
id: "AKJ3IJZKPWc",
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
duration: Some(229),
|
duration: Some(228),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -51,7 +51,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "WnpZuHNB33E",
|
id: "WnpZuHNB33E",
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
duration: Some(267),
|
duration: Some(266),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -72,7 +72,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "pRqoDGXg1-I",
|
id: "pRqoDGXg1-I",
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
duration: Some(155),
|
duration: Some(154),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
@ -93,7 +93,7 @@ MusicAlbum(
|
||||||
TrackItem(
|
TrackItem(
|
||||||
id: "20vIKLJxjBY",
|
id: "20vIKLJxjBY",
|
||||||
name: "[name]",
|
name: "[name]",
|
||||||
duration: Some(179),
|
duration: Some(178),
|
||||||
cover: [],
|
cover: [],
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
|
|
Loading…
Reference in a new issue