fix: rewritten album mv replacement
This commit is contained in:
parent
3574a44b77
commit
d0a8b6fabe
8 changed files with 841 additions and 45 deletions
|
|
@ -146,6 +146,14 @@ struct QBrowse<'a> {
|
|||
browse_id: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct QBrowseParams<'a> {
|
||||
context: YTContext<'a>,
|
||||
browse_id: &'a str,
|
||||
params: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct QContinuation<'a> {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use std::{borrow::Cow, rc::Rc};
|
|||
use futures::{stream, StreamExt};
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
error::{Error, ExtractionError},
|
||||
|
|
@ -14,17 +13,9 @@ use crate::{
|
|||
|
||||
use super::{
|
||||
response::{self, music_item::MusicListMapper, url_endpoint::PageType},
|
||||
ClientType, MapResponse, QBrowse, RustyPipeQuery, YTContext,
|
||||
ClientType, MapResponse, QBrowse, QBrowseParams, RustyPipeQuery,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct QBrowseParams<'a> {
|
||||
context: YTContext<'a>,
|
||||
browse_id: &'a str,
|
||||
params: &'a str,
|
||||
}
|
||||
|
||||
impl RustyPipeQuery {
|
||||
/// Get a YouTube Music artist page
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
error::{Error, ExtractionError},
|
||||
model::{MusicGenre, MusicGenreItem, MusicGenreSection},
|
||||
|
|
@ -10,17 +8,9 @@ use crate::{
|
|||
|
||||
use super::{
|
||||
response::{self, music_item::MusicListMapper},
|
||||
ClientType, MapResponse, QBrowse, RustyPipeQuery, YTContext,
|
||||
ClientType, MapResponse, QBrowse, QBrowseParams, RustyPipeQuery,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct QGenre<'a> {
|
||||
context: YTContext<'a>,
|
||||
browse_id: &'a str,
|
||||
params: &'a str,
|
||||
}
|
||||
|
||||
impl RustyPipeQuery {
|
||||
/// Get a list of moods and genres from YouTube Music
|
||||
pub async fn music_genres(&self) -> Result<Vec<MusicGenreItem>, Error> {
|
||||
|
|
@ -44,7 +34,7 @@ impl RustyPipeQuery {
|
|||
pub async fn music_genre<S: AsRef<str>>(&self, genre_id: S) -> Result<MusicGenre, Error> {
|
||||
let genre_id = genre_id.as_ref();
|
||||
let context = self.get_context(ClientType::DesktopMusic, true, None).await;
|
||||
let request_body = QGenre {
|
||||
let request_body = QBrowseParams {
|
||||
context,
|
||||
browse_id: "FEmusic_moods_and_genres_category",
|
||||
params: genre_id,
|
||||
|
|
|
|||
|
|
@ -57,6 +57,19 @@ impl RustyPipeQuery {
|
|||
)
|
||||
.await?;
|
||||
|
||||
// In rare cases, albums may have track numbers =0 (example: MPREb_RM0QfZ0eSKL)
|
||||
// They should be replaced with the track number derived from the previous track.
|
||||
let mut n_prev = 0;
|
||||
for track in album.tracks.iter_mut() {
|
||||
let tn = track.track_nr.unwrap_or_default();
|
||||
if tn == 0 {
|
||||
n_prev += 1;
|
||||
track.track_nr = Some(n_prev);
|
||||
} else {
|
||||
n_prev = tn;
|
||||
}
|
||||
}
|
||||
|
||||
// YouTube Music is replacing album tracks with their respective music videos. To get the original
|
||||
// tracks, we have to fetch the album as a playlist and replace the offending track ids.
|
||||
if let Some(playlist_id) = &album.playlist_id {
|
||||
|
|
@ -67,7 +80,7 @@ impl RustyPipeQuery {
|
|||
.enumerate()
|
||||
.filter_map(|(i, track)| {
|
||||
if track.is_video {
|
||||
Some((i, track.name.to_owned()))
|
||||
track.track_nr.map(|n| (i, n))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -75,27 +88,14 @@ impl RustyPipeQuery {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
if !to_replace.is_empty() {
|
||||
match self.music_playlist(playlist_id).await {
|
||||
Ok(playlist) => {
|
||||
for (i, title) in to_replace {
|
||||
let found_track = playlist.tracks.items.iter().find_map(|track| {
|
||||
if track.name == title && !track.is_video {
|
||||
Some((track.id.to_owned(), track.duration))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
if let Some((track_id, duration)) = found_track {
|
||||
album.tracks[i].id = track_id;
|
||||
if let Some(duration) = duration {
|
||||
album.tracks[i].duration = Some(duration);
|
||||
}
|
||||
album.tracks[i].is_video = false;
|
||||
}
|
||||
}
|
||||
let playlist = self.playlist_w_unavail(playlist_id).await?;
|
||||
|
||||
for (i, track_n) in to_replace {
|
||||
if let Some(t) = playlist.videos.items.get(track_n as usize - 1) {
|
||||
album.tracks[i].id = t.id.to_owned();
|
||||
album.tracks[i].duration = Some(t.length);
|
||||
album.tracks[i].is_video = false;
|
||||
}
|
||||
Err(Error::Extraction(_)) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ use crate::{
|
|||
util::{self, TryRemove},
|
||||
};
|
||||
|
||||
use super::{response, ClientType, MapResponse, MapResult, QBrowse, QContinuation, RustyPipeQuery};
|
||||
use super::{
|
||||
response, ClientType, MapResponse, MapResult, QBrowse, QBrowseParams, QContinuation,
|
||||
RustyPipeQuery,
|
||||
};
|
||||
|
||||
impl RustyPipeQuery {
|
||||
/// Get a YouTube playlist
|
||||
|
|
@ -31,6 +34,29 @@ impl RustyPipeQuery {
|
|||
.await
|
||||
}
|
||||
|
||||
/// Get a YouTube playlist including unavailable tracks
|
||||
pub(crate) async fn playlist_w_unavail<S: AsRef<str>>(
|
||||
&self,
|
||||
playlist_id: S,
|
||||
) -> Result<Playlist, Error> {
|
||||
let playlist_id = playlist_id.as_ref();
|
||||
let context = self.get_context(ClientType::Desktop, true, None).await;
|
||||
let request_body = QBrowseParams {
|
||||
context,
|
||||
browse_id: &format!("VL{playlist_id}"),
|
||||
params: "wgYCCAA%3D",
|
||||
};
|
||||
|
||||
self.execute_request::<response::Playlist, _, _>(
|
||||
ClientType::Desktop,
|
||||
"playlist",
|
||||
playlist_id,
|
||||
"browse",
|
||||
&request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get more playlist items using the given continuation token
|
||||
pub async fn playlist_continuation<S: AsRef<str>>(
|
||||
&self,
|
||||
|
|
|
|||
Reference in a new issue