refactor: split music item mapping into multiple fns

This commit is contained in:
ThetaDev 2023-07-22 16:36:20 +02:00
parent 1d94d0241b
commit 68926b9ca2
3 changed files with 423 additions and 436 deletions

View file

@ -482,10 +482,35 @@ impl MusicListMapper {
} }
} }
/// Map a MusicResponseItem (list item or tile)
fn map_item(&mut self, item: MusicResponseItem) -> Result<Option<MusicItemType>, String> { fn map_item(&mut self, item: MusicResponseItem) -> Result<Option<MusicItemType>, String> {
match item { match item {
// List item // List item
MusicResponseItem::MusicResponsiveListItemRenderer(item) => { MusicResponseItem::MusicResponsiveListItemRenderer(item) => self.map_list_item(item),
// Tile
MusicResponseItem::MusicTwoRowItemRenderer(item) => self.map_tile(item),
MusicResponseItem::MessageRenderer(_) => Ok(None),
}
}
pub fn map_response(
&mut self,
mut res: MapResult<Vec<MusicResponseItem>>,
) -> Option<MusicItemType> {
let mut etype = None;
self.warnings.append(&mut res.warnings);
res.c.into_iter().for_each(|item| {
if let Some(et) = self.add_response_item(item) {
if etype.is_none() {
etype = Some(et);
}
}
});
etype
}
/// Map a ListMusicItem (album/playlist tile)
fn map_list_item(&mut self, item: ListMusicItem) -> Result<Option<MusicItemType>, String> {
let mut columns = item.flex_columns.into_iter(); let mut columns = item.flex_columns.into_iter();
let c1 = columns.next(); let c1 = columns.next();
let c2 = columns.next(); let c2 = columns.next();
@ -525,9 +550,7 @@ impl MusicListMapper {
( (
MusicPageType::Track { MusicPageType::Track {
is_video: self.album.is_none() is_video: self.album.is_none()
&& !first_tn && !first_tn.map(|tn| tn.height == tn.width).unwrap_or_default(),
.map(|tn| tn.height == tn.width)
.unwrap_or_default(),
}, },
d.video_id, d.video_id,
) )
@ -549,11 +572,9 @@ impl MusicListMapper {
match pt_id { match pt_id {
// Track // Track
Some((MusicPageType::Track { is_video }, id)) => { Some((MusicPageType::Track { is_video }, id)) => {
let title = let title = title.ok_or_else(|| format!("track {id}: could not get title"))?;
title.ok_or_else(|| format!("track {id}: could not get title"))?;
let (artists_p, album_p, duration_p) = match item.flex_column_display_style let (artists_p, album_p, duration_p) = match item.flex_column_display_style {
{
// Search result // Search result
FlexColumnDisplayStyle::TwoLines => { FlexColumnDisplayStyle::TwoLines => {
// Is this a related track? // Is this a related track?
@ -565,9 +586,7 @@ impl MusicListMapper {
) )
} else { } else {
let mut subtitle_parts = c2 let mut subtitle_parts = c2
.ok_or_else(|| { .ok_or_else(|| format!("track {id}: could not get subtitle"))?
format!("track {id}: could not get subtitle")
})?
.renderer .renderer
.text .text
.split(util::DOT_SEPARATOR) .split(util::DOT_SEPARATOR)
@ -607,8 +626,7 @@ impl MusicListMapper {
), ),
}; };
let duration = let duration = duration_p.and_then(|p| util::parse_video_length(p.first_str()));
duration_p.and_then(|p| util::parse_video_length(p.first_str()));
let (album, view_count) = match (item.flex_column_display_style, is_video) { let (album, view_count) = match (item.flex_column_display_style, is_video) {
// The album field contains the view count for search videos // The album field contains the view count for search videos
@ -623,9 +641,8 @@ impl MusicListMapper {
}), }),
), ),
(_, false) => ( (_, false) => (
album_p.and_then(|p| { album_p
p.0.into_iter().find_map(|c| AlbumId::try_from(c).ok()) .and_then(|p| p.0.into_iter().find_map(|c| AlbumId::try_from(c).ok())),
}),
None, None,
), ),
(FlexColumnDisplayStyle::Default, true) => (None, None), (FlexColumnDisplayStyle::Default, true) => (None, None),
@ -683,8 +700,7 @@ impl MusicListMapper {
.split(util::DOT_SEPARATOR) .split(util::DOT_SEPARATOR)
.into_iter(); .into_iter();
let title = let title = title.ok_or_else(|| format!("track {id}: could not get title"))?;
title.ok_or_else(|| format!("track {id}: could not get title"))?;
let subtitle_p1 = subtitle_parts.next(); let subtitle_p1 = subtitle_parts.next();
let subtitle_p2 = subtitle_parts.next(); let subtitle_p2 = subtitle_parts.next();
@ -717,8 +733,8 @@ impl MusicListMapper {
let artist_id = map_artist_id_fallback(item.menu, artists.first()); let artist_id = map_artist_id_fallback(item.menu, artists.first());
let year = subtitle_p3 let year =
.and_then(|st| util::parse_numeric(st.first_str()).ok()); subtitle_p3.and_then(|st| util::parse_numeric(st.first_str()).ok());
self.items.push(MusicItem::Album(AlbumItem { self.items.push(MusicItem::Album(AlbumItem {
id, id,
@ -782,8 +798,9 @@ impl MusicListMapper {
} }
} }
} }
// Tile
MusicResponseItem::MusicTwoRowItemRenderer(item) => { /// Map a CoverMusicItem (album/playlist tile)
fn map_tile(&mut self, item: CoverMusicItem) -> Result<Option<MusicItemType>, String> {
let mut subtitle_parts = item.subtitle.split(util::DOT_SEPARATOR).into_iter(); let mut subtitle_parts = item.subtitle.split(util::DOT_SEPARATOR).into_iter();
let subtitle_p1 = subtitle_parts.next(); let subtitle_p1 = subtitle_parts.next();
let subtitle_p2 = subtitle_parts.next(); let subtitle_p2 = subtitle_parts.next();
@ -845,28 +862,23 @@ impl MusicListMapper {
// "Album", "2022" (Artist albums) // "Album", "2022" (Artist albums)
(Some(atype_txt), Some(year_txt), Some(artists), true) => { (Some(atype_txt), Some(year_txt), Some(artists), true) => {
year = util::parse_numeric(year_txt.first_str()).ok(); year = util::parse_numeric(year_txt.first_str()).ok();
album_type = album_type = map_album_type(atype_txt.first_str(), self.lang);
map_album_type(atype_txt.first_str(), self.lang);
artists.clone() artists.clone()
} }
// Album on artist page with unknown year // Album on artist page with unknown year
(None, None, Some(artists), true) => artists.clone(), (None, None, Some(artists), true) => artists.clone(),
// "Album", <"Oonagh"> (Album variants, new releases) // "Album", <"Oonagh"> (Album variants, new releases)
(Some(atype_txt), Some(p2), _, false) => { (Some(atype_txt), Some(p2), _, false) => {
album_type = album_type = map_album_type(atype_txt.first_str(), self.lang);
map_album_type(atype_txt.first_str(), self.lang);
map_artists(Some(p2)) map_artists(Some(p2))
} }
// "Album" (Album variants, no artist) // "Album" (Album variants, no artist)
(Some(atype_txt), None, _, false) => { (Some(atype_txt), None, _, false) => {
album_type = album_type = map_album_type(atype_txt.first_str(), self.lang);
map_album_type(atype_txt.first_str(), self.lang);
(Vec::new(), true) (Vec::new(), true)
} }
_ => { _ => {
return Err(format!( return Err(format!("could not parse subtitle of album {id}"));
"could not parse subtitle of album {id}"
));
} }
}; };
@ -889,9 +901,8 @@ impl MusicListMapper {
.as_ref() .as_ref()
.and_then(|p| p.0.first()) .and_then(|p| p.0.first())
.map_or(true, util::is_ytm); .map_or(true, util::is_ytm);
let channel = subtitle_p2.and_then(|p| { let channel = subtitle_p2
p.0.into_iter().find_map(|c| ChannelId::try_from(c).ok()) .and_then(|p| p.0.into_iter().find_map(|c| ChannelId::try_from(c).ok()));
});
self.items.push(MusicItem::Playlist(MusicPlaylistItem { self.items.push(MusicItem::Playlist(MusicPlaylistItem {
id, id,
@ -912,26 +923,8 @@ impl MusicListMapper {
None => Err("could not determine item type".to_owned()), None => Err("could not determine item type".to_owned()),
} }
} }
MusicResponseItem::MessageRenderer(_) => Ok(None),
}
}
pub fn map_response(
&mut self,
mut res: MapResult<Vec<MusicResponseItem>>,
) -> Option<MusicItemType> {
let mut etype = None;
self.warnings.append(&mut res.warnings);
res.c.into_iter().for_each(|item| {
if let Some(et) = self.add_response_item(item) {
if etype.is_none() {
etype = Some(et);
}
}
});
etype
}
/// Map a MusicCardShelf (used for the top search result)
pub fn map_card(&mut self, card: MusicCardShelf) -> Option<MusicItemType> { pub fn map_card(&mut self, card: MusicCardShelf) -> Option<MusicItemType> {
/* /*
"Artist" "" "<subscriber count>" "Artist" "" "<subscriber count>"

View file

@ -297,7 +297,7 @@ impl<'de> DeserializeAs<'de, TextComponents> for AttributedText {
} }
impl TryFrom<TextComponent> for crate::model::ChannelId { impl TryFrom<TextComponent> for crate::model::ChannelId {
type Error = util::MappingError; type Error = ();
fn try_from(value: TextComponent) -> Result<Self, Self::Error> { fn try_from(value: TextComponent) -> Result<Self, Self::Error> {
match value { match value {
@ -310,9 +310,9 @@ impl TryFrom<TextComponent> for crate::model::ChannelId {
id: browse_id, id: browse_id,
name: text, name: text,
}), }),
_ => Err(util::MappingError("invalid channel link type".into())), _ => Err(()),
}, },
_ => Err(util::MappingError("invalid channel link".into())), _ => Err(()),
} }
} }
} }

View file

@ -8,7 +8,6 @@ pub use date::{now_sec, shift_months, shift_years};
pub use protobuf::{string_from_pb, ProtoBuilder}; pub use protobuf::{string_from_pb, ProtoBuilder};
use std::{ use std::{
borrow::{Borrow, Cow},
collections::BTreeMap, collections::BTreeMap,
str::{FromStr, SplitWhitespace}, str::{FromStr, SplitWhitespace},
}; };
@ -42,11 +41,6 @@ pub const ARTIST_DISCOGRAPHY_PREFIX: &str = "MPAD";
const CONTENT_PLAYBACK_NONCE_ALPHABET: &[u8; 64] = const CONTENT_PLAYBACK_NONCE_ALPHABET: &[u8; 64] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
/// Internal error
#[derive(thiserror::Error, Debug)]
#[error("mapping error: {0}")]
pub struct MappingError(pub(crate) Cow<'static, str>);
/// Return the given capture group that matches first in a list of regexes /// Return the given capture group that matches first in a list of regexes
pub fn get_cg_from_regexes<'a, I>(mut regexes: I, text: &str, cg: usize) -> Option<String> pub fn get_cg_from_regexes<'a, I>(mut regexes: I, text: &str, cg: usize) -> Option<String>
where where
@ -249,7 +243,7 @@ pub fn sanitize_yt_url(url: &str) -> String {
if parsed_url.query().is_some() { if parsed_url.query().is_some() {
let params = parsed_url let params = parsed_url
.query_pairs() .query_pairs()
.filter_map(|(k, v)| match k.borrow() { .filter_map(|(k, v)| match k.as_ref() {
"utm_source" | "utm_medium" | "utm_campaign" | "utm_content" => None, "utm_source" | "utm_medium" | "utm_campaign" | "utm_content" => None,
_ => Some((k.to_string(), v.to_string())), _ => Some((k.to_string(), v.to_string())),
}) })