feat: get YouTube visitor data

This commit is contained in:
ThetaDev 2022-10-28 18:49:10 +02:00
parent 4ebee5856e
commit 1c0c64a8bf
40 changed files with 62 additions and 5 deletions

View file

@ -115,6 +115,7 @@ impl MapResponse<Channel<Paginator<VideoItem>>> for response::Channel {
self.header,
self.metadata,
self.microformat,
self.response_context.visitor_data,
v_res.c,
id,
lang,
@ -146,6 +147,7 @@ impl MapResponse<Channel<Paginator<PlaylistItem>>> for response::Channel {
self.header,
self.metadata,
self.microformat,
self.response_context.visitor_data,
p_res.c,
id,
lang,
@ -204,6 +206,7 @@ impl MapResponse<Channel<ChannelInfo>> for response::Channel {
self.header,
self.metadata,
self.microformat,
self.response_context.visitor_data,
cinfo,
id,
lang,
@ -255,6 +258,7 @@ fn map_channel<T>(
header: Option<response::channel::Header>,
metadata: Option<response::channel::Metadata>,
microformat: Option<response::channel::Microformat>,
visitor_data: Option<String>,
content: T,
id: &str,
lang: Language,
@ -298,6 +302,7 @@ fn map_channel<T>(
banner: header.banner.into(),
mobile_banner: header.mobile_banner.into(),
tv_banner: header.tv_banner.into(),
visitor_data,
content,
},
response::channel::Header::CarouselHeaderRenderer(carousel) => {
@ -332,6 +337,7 @@ fn map_channel<T>(
banner: Vec::new(),
mobile_banner: Vec::new(),
tv_banner: Vec::new(),
visitor_data,
content,
}
}

View file

@ -275,6 +275,7 @@ impl MapResponse<VideoPlayer> for response::Player {
expires_in_seconds: streaming_data.expires_in_seconds,
hls_manifest_url: streaming_data.hls_manifest_url,
dash_manifest_url: streaming_data.dash_manifest_url,
visitor_data: self.response_context.visitor_data,
},
warnings,
})

View file

@ -169,6 +169,7 @@ impl MapResponse<Playlist> for response::Playlist {
channel,
last_update,
last_update_txt,
visitor_data: self.response_context.visitor_data,
},
warnings,
})

View file

@ -3,7 +3,7 @@ use serde_with::serde_as;
use serde_with::{DefaultOnError, VecSkipError};
use super::url_endpoint::NavigationEndpoint;
use super::{Alert, ChannelBadge};
use super::{Alert, ChannelBadge, ResponseContext};
use super::{ContentRenderer, ContentsRenderer};
use super::{Thumbnails, YouTubeListItem};
use crate::serializer::ignore_any;
@ -21,6 +21,7 @@ pub(crate) struct Channel {
pub microformat: Option<Microformat>,
#[serde_as(as = "Option<DefaultOnError>")]
pub alerts: Option<Vec<Alert>>,
pub response_context: ResponseContext,
}
#[derive(Debug, Deserialize)]

View file

@ -4,7 +4,7 @@ use serde::Deserialize;
use serde_with::serde_as;
use serde_with::{json::JsonString, DefaultOnError};
use super::Thumbnails;
use super::{ResponseContext, Thumbnails};
use crate::serializer::{text::Text, MapResult, VecLogError};
#[derive(Debug, Deserialize)]
@ -14,6 +14,7 @@ pub(crate) struct Player {
pub streaming_data: Option<StreamingData>,
pub captions: Option<Captions>,
pub video_details: Option<VideoDetails>,
pub response_context: ResponseContext,
}
#[derive(Debug, Deserialize)]

View file

@ -6,7 +6,8 @@ use crate::serializer::{ignore_any, MapResult, VecLogError};
use crate::util::MappingError;
use super::{
Alert, ContentRenderer, ContentsRenderer, ContinuationEndpoint, Thumbnails, ThumbnailsWrap,
Alert, ContentRenderer, ContentsRenderer, ContinuationEndpoint, ResponseContext, Thumbnails,
ThumbnailsWrap,
};
#[serde_as]
@ -18,6 +19,7 @@ pub(crate) struct Playlist {
pub sidebar: Option<Sidebar>,
#[serde_as(as = "Option<DefaultOnError>")]
pub alerts: Option<Vec<Alert>>,
pub response_context: ResponseContext,
}
#[serde_as]

View file

@ -1,7 +1,7 @@
use serde::Deserialize;
use serde_with::{json::JsonString, serde_as};
use super::video_item::YouTubeListRendererWrap;
use super::{video_item::YouTubeListRendererWrap, ResponseContext};
#[serde_as]
#[derive(Debug, Deserialize)]
@ -10,6 +10,7 @@ pub(crate) struct Search {
#[serde_as(as = "Option<JsonString>")]
pub estimated_results: Option<u64>,
pub contents: Contents,
pub response_context: ResponseContext,
}
#[derive(Debug, Deserialize)]

View file

@ -109,6 +109,7 @@ impl MapResponse<SearchResult> for response::Search {
c: SearchResult {
items: Paginator::new(self.estimated_results, mapper.items, mapper.ctoken),
corrected_query: mapper.corrected_query,
visitor_data: self.response_context.visitor_data,
},
warnings: mapper.warnings,
})

View file

@ -142,6 +142,7 @@ Channel(
height: 1192,
),
],
visitor_data: Some("CgszMUUzZDlGLWxiRSipqr2ZBg%3D%3D"),
content: ChannelInfo(
create_date: Some("2009-04-04"),
view_count: Some(186854342),

View file

@ -142,6 +142,7 @@ Channel(
height: 1192,
),
],
visitor_data: Some("CgttaWpyTVpUN1AyZyioqr2ZBg%3D%3D"),
content: Paginator(
count: None,
items: [

View file

@ -113,6 +113,7 @@ Channel(
height: 1192,
),
],
visitor_data: Some("CgtQdE9zVVR3NVBDbyjz0ZKaBg%3D%3D"),
content: Paginator(
count: None,
items: [

View file

@ -142,6 +142,7 @@ Channel(
height: 1192,
),
],
visitor_data: Some("Cgs4ZFVmMzVlU1dxbyiBqpeaBg%3D%3D"),
content: Paginator(
count: None,
items: [

View file

@ -142,6 +142,7 @@ Channel(
height: 1192,
),
],
visitor_data: Some("CgszNU5rbDVZS2hMcyim4K2ZBg%3D%3D"),
content: Paginator(
count: None,
items: [

View file

@ -30,6 +30,7 @@ Channel(
banner: [],
mobile_banner: [],
tv_banner: [],
visitor_data: Some("Cgtvc2s4UllvTGl6byigxseZBg%3D%3D"),
content: Paginator(
count: Some(0),
items: [],

View file

@ -126,6 +126,7 @@ Channel(
height: 1192,
),
],
visitor_data: Some("CgtkYXJITElwYmd4OCj85a2ZBg%3D%3D"),
content: Paginator(
count: Some(21),
items: [

View file

@ -113,6 +113,7 @@ Channel(
height: 1192,
),
],
visitor_data: Some("CgtCV1l2R2Rzb2ZSZyiu4a2ZBg%3D%3D"),
content: Paginator(
count: Some(0),
items: [],

View file

@ -113,6 +113,7 @@ Channel(
height: 1192,
),
],
visitor_data: Some("CgtneXVRbGtSMWtlYyj75a2ZBg%3D%3D"),
content: Paginator(
count: None,
items: [

View file

@ -130,6 +130,7 @@ Channel(
height: 1192,
),
],
visitor_data: Some("Cgs4Ri1tLW1KNWozNCjGk8yZBg%3D%3D"),
content: Paginator(
count: None,
items: [

View file

@ -424,4 +424,5 @@ VideoPlayer(
expires_in_seconds: 21540,
hls_manifest_url: None,
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYtOPEYSBgQeHmqbwAQ/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr5---sn-h0jeenek.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jeenek%2Csn-h0jelnez/ms/au%2Crdu/mv/m/mvi/5/pl/37/hfr/1/as/fmp4_audio_clear%2Cfmp4_sd_hd_clear/initcwndbps/1527500/vprv/1/mt/1659459429/fvip/4/itag_bl/376%2C377%2C384%2C385%2C612%2C613%2C617%2C619%2C623%2C628%2C655%2C656%2C660%2C662%2C666%2C671/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cvprv%2Citag/sig/AOq0QJ8wRAIgMm4a_MIHA3YUszKeruSy3exs5JwNjJAyLAwxL0yPdNMCIANb9GDMSTp_NT-PPhbvYMwRULJ5a9BO6MYD9FuWprC1/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgETSOwhwWVMy7gmrFXZlJu655ToLzSwOEsT16oRyrWhACIQDkvOEw1fImz5omu4iVIRNFe-z-JC9v8WUyx281dW2NOw%3D%3D"),
visitor_data: Some("Cgt2aHFtQU5YZFBvYyirsaWXBg%3D%3D"),
)

View file

@ -541,4 +541,5 @@ VideoPlayer(
expires_in_seconds: 21540,
hls_manifest_url: None,
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYtq3BJCX1gKVyJGQDg/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C26/mn/sn-h0jelnez%2Csn-4g5edn6k/ms/au%2Conr/mv/m/mvi/4/pl/37/hfr/all/as/fmp4_audio_clear%2Cwebm_audio_clear%2Cwebm2_audio_clear%2Cfmp4_sd_hd_clear%2Cwebm2_sd_hd_clear/initcwndbps/1513750/spc/lT-KhrZGE2opztWyVdAtyUNlb8dXPDs/vprv/1/mt/1659459429/fvip/4/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cspc%2Cvprv%2Citag/sig/AOq0QJ8wRgIhAPEjHK19PKVHqQeia6WF4qubuMYk74LGi8F8lk5ZMPkFAiEAsaB2pKQWBvuPnNUnbdQXHc-izgsHJUP793woC2xNJlg%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgOY4xu4H9wqPVZ7vF2i0hFcOnqrur1XGoA43a7ZEuuSUCIQCyPxBKXUQrKFmknNEGpX5GSWySKgMw_xHBikWpKpKwvg%3D%3D"),
visitor_data: Some("CgtoS1pCMVJTNUJISSirsaWXBg%3D%3D"),
)

View file

@ -365,4 +365,5 @@ VideoPlayer(
expires_in_seconds: 21540,
hls_manifest_url: None,
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659487474/ei/knDpYub6BojEgAf6jbLgDw/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr5---sn-h0jeenek.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jeenek%2Csn-h0jelnez/ms/au%2Crdu/mv/m/mvi/5/pl/37/hfr/all/as/fmp4_audio_clear%2Cwebm_audio_clear%2Cwebm2_audio_clear%2Cfmp4_sd_hd_clear%2Cwebm2_sd_hd_clear/initcwndbps/1418750/spc/lT-Khox4YuJQ2wmH79zYALRvsWTPCUc/vprv/1/mt/1659465669/fvip/4/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cspc%2Cvprv%2Citag/sig/AOq0QJ8wRAIgErABhAEaoKHUDu9dDbpxE_8gR4b8WWAi61fnu8UKnuICIEYrEKcHvqHdO4V3R7cvSGwi_HGH34IlQsKbziOfMBov/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgJxHmH0Sxo3cY_pW_ZzQ3hW9-7oz6K_pZWcUdrDDQ2sQCIQDJYNINQwLgKelgbO3CZYx7sMxdUAFpWdokmRBQ77vwvw%3D%3D"),
visitor_data: Some("CgszSHZWNWs0SDhpTSiS4aWXBg%3D%3D"),
)

View file

@ -160,4 +160,5 @@ VideoPlayer(
expires_in_seconds: 21540,
hls_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/hls_variant/expire/1659481355/ei/q1jpYq-xHs7NgQev0bfwAQ/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jelnez%2Csn-h0jeenek/ms/au%2Crdu/mv/m/mvi/4/pl/37/hfr/1/demuxed/1/tts_caps/1/maudio/1/initcwndbps/1513750/vprv/1/go/1/mt/1659459429/fvip/5/nvgoi/1/short_key/1/ncsapi/1/keepalive/yes/fexp/24001373%2C24007246/dover/13/itag/0/playlist_type/DVR/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cdemuxed%2Ctts_caps%2Cmaudio%2Cvprv%2Cgo%2Citag%2Cplaylist_type/sig/AOq0QJ8wRQIhAIYnEHvIgJtJ8hehAXNtVY3qsgsq_GdOhWf2hkJZe6lCAiBxaRY_nubYp6hBizcAg_KFkKnkG-t2XYLRQ5wGdM3AjA%3D%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRgIhAM_91Kk_0VLuSsR6nLCY7LdtWojyRAzXSScd_X9ShRROAiEA1AF4VY04F71NsAI8_j3iqjuXnWL9s6NoXHq7P8-bHx8%3D/file/index.m3u8"),
dash_manifest_url: None,
visitor_data: Some("Cgs4TXV4dk13WVEyWSirsaWXBg%3D%3D"),
)

View file

@ -541,4 +541,5 @@ VideoPlayer(
expires_in_seconds: 21540,
hls_manifest_url: None,
dash_manifest_url: Some("https://manifest.googlevideo.com/api/manifest/dash/expire/1659481355/ei/q1jpYv-eJ9uF6dsPhvyH8As/ip/2003%3Ade%3Aaf0e%3A2f00%3Ade47%3A297%3Aa6db%3A774e/id/a4fbddf14c6649b4/source/youtube/requiressl/yes/playback_host/rr4---sn-h0jelnez.googlevideo.com/mh/mQ/mm/31%2C29/mn/sn-h0jelnez%2Csn-h0jeenek/ms/au%2Crdu/mv/m/mvi/4/pl/37/hfr/all/as/fmp4_audio_clear%2Cfmp4_sd_hd_clear/initcwndbps/1527500/vprv/1/mt/1659459429/fvip/5/keepalive/yes/fexp/24001373%2C24007246/itag/0/sparams/expire%2Cei%2Cip%2Cid%2Csource%2Crequiressl%2Chfr%2Cas%2Cvprv%2Citag/sig/AOq0QJ8wRQIhANKWS7GCN4pSoHIQ6BMZdOaHAD0I25nHwRj7ds4qrxdEAiBsd9l8WIceqF7-2xyR82DGecCiS9hgUIPJhdNhkwVpHg%3D%3D/lsparams/playback_host%2Cmh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps/lsig/AG3C_xAwRQIgMbu-wTOcXGCwGh27y0YZHktumKM1sopgxfQf8LCcCnECIQDnhFbgddOxwiQbnMOIcCn6ncpN54UyALRNigUSCp9Deg%3D%3D"),
visitor_data: Some("CgtacUJOMG81dTI3cyirsaWXBg%3D%3D"),
)

View file

@ -3142,4 +3142,5 @@ Playlist(
)),
last_update: "[date]",
last_update_txt: Some("Last updated on Aug 7, 2022"),
visitor_data: Some("CgtZdi1GV3N3TnBuQSi46K-YBg%3D%3D"),
)

View file

@ -2088,4 +2088,5 @@ Playlist(
)),
last_update: "[date]",
last_update_txt: Some("Last updated on Jul 2, 2014"),
visitor_data: Some("CgtXY0lqNWN5VlNmTSik8q-YBg%3D%3D"),
)

View file

@ -3041,4 +3041,5 @@ Playlist(
channel: None,
last_update: "[date]",
last_update_txt: Some("Updated today"),
visitor_data: Some("CgtLZVdRQ1dkM2VDVSi46K-YBg%3D%3D"),
)

View file

@ -734,4 +734,5 @@ SearchResult(
endpoint: browse,
),
corrected_query: Some("doobydobap"),
visitor_data: Some("Cgs4MEJMc3FmVzVadyiNy4-aBg%3D%3D"),
)

View file

@ -10,4 +10,5 @@ SearchResult(
endpoint: browse,
),
corrected_query: None,
visitor_data: Some("Cgs1Q0NxX3llelBxWSi85ZGaBg%3D%3D"),
)

View file

@ -691,4 +691,5 @@ SearchResult(
endpoint: browse,
),
corrected_query: None,
visitor_data: Some("CgstZjhyS1IyR1R6dyiX4JGaBg%3D%3D"),
)

View file

@ -750,4 +750,5 @@ VideoDetails(
ctoken: Some("Eg0SC1plZXJybnVMaTVFGAYyOCIRIgtaZWVycm51TGk1RTABeAIwAUIhZW5nYWdlbWVudC1wYW5lbC1jb21tZW50cy1zZWN0aW9u"),
endpoint: next,
),
visitor_data: Some("CgtCeURHR09uNlJ5TSjOiLqZBg%3D%3D"),
)

View file

@ -824,4 +824,5 @@ VideoDetails(
ctoken: Some("Eg0SC1plZXJybnVMaTVFGAYyOCIRIgtaZWVycm51TGk1RTABeAIwAUIhZW5nYWdlbWVudC1wYW5lbC1jb21tZW50cy1zZWN0aW9u"),
endpoint: next,
),
visitor_data: Some("Cgs2V0p6ZW5ab1ozTSjkrpaaBg%3D%3D"),
)

View file

@ -1282,4 +1282,5 @@ VideoDetails(
ctoken: Some("Eg0SC25GREJ4QlVmRTc0GAYyOCIRIgtuRkRCeEJVZkU3NDABeAIwAUIhZW5nYWdlbWVudC1wYW5lbC1jb21tZW50cy1zZWN0aW9u"),
endpoint: next,
),
visitor_data: Some("Cgtidzg4MlRTb3FKSSiqipeaBg%3D%3D"),
)

View file

@ -54,4 +54,5 @@ VideoDetails(
ctoken: Some("Eg0SC0hSS3UwY3Zycl9vGAYyOCIRIgtIUkt1MGN2cnJfbzABeAIwAUIhZW5nYWdlbWVudC1wYW5lbC1jb21tZW50cy1zZWN0aW9u"),
endpoint: next,
),
visitor_data: Some("CgtxUUdrc1VSVE54Zyja1KiZBg%3D%3D"),
)

View file

@ -771,4 +771,5 @@ VideoDetails(
ctoken: Some("Eg0SCzByYjlDZk92b2prGAYyOCIRIgswcmI5Q2ZPdm9qazABeAIwAUIhZW5nYWdlbWVudC1wYW5lbC1jb21tZW50cy1zZWN0aW9u"),
endpoint: next,
),
visitor_data: Some("CgtoY1pQUF8wNW1qayjSjpSZBg%3D%3D"),
)

View file

@ -1239,4 +1239,5 @@ VideoDetails(
ctoken: Some("Eg0SC25GREJ4QlVmRTc0GAYyOCIRIgtuRkRCeEJVZkU3NDABeAIwAUIhZW5nYWdlbWVudC1wYW5lbC1jb21tZW50cy1zZWN0aW9u"),
endpoint: next,
),
visitor_data: Some("CgtIV0JjSUtDQm9LQSjUjpSZBg%3D%3D"),
)

View file

@ -830,4 +830,5 @@ VideoDetails(
ctoken: None,
endpoint: next,
),
visitor_data: Some("CgtnQS1WdzlNNkNCSSiSmKiZBg%3D%3D"),
)

View file

@ -576,4 +576,5 @@ VideoDetails(
ctoken: None,
endpoint: next,
),
visitor_data: Some("CgtzclhqZVpoajVhVSi76qeZBg%3D%3D"),
)

View file

@ -787,4 +787,5 @@ VideoDetails(
ctoken: Some("Eg0SC1plZXJybnVMaTVFGAYyOCIRIgtaZWVycm51TGk1RTABeAIwAUIhZW5nYWdlbWVudC1wYW5lbC1jb21tZW50cy1zZWN0aW9u"),
endpoint: next,
),
visitor_data: Some("Cgtjemd0bDVxU1N1QSjRjpSZBg%3D%3D"),
)

View file

@ -252,7 +252,7 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
let mut res = map_recommendations(
r,
sr.secondary_results.continuations,
self.response_context.visitor_data,
self.response_context.visitor_data.clone(),
lang,
);
warnings.append(&mut res.warnings);
@ -343,6 +343,7 @@ impl MapResponse<VideoDetails> for response::VideoDetails {
None,
crate::param::ContinuationEndpoint::Next,
),
visitor_data: self.response_context.visitor_data,
},
warnings,
})

View file

@ -112,8 +112,12 @@ pub struct VideoPlayer {
pub subtitles: Vec<Subtitle>,
/// Lifetime of the stream URLs in seconds
pub expires_in_seconds: u32,
/// HLS manifest URL (for livestreams)
pub hls_manifest_url: Option<String>,
/// Dash manifest URL (for livestreams)
pub dash_manifest_url: Option<String>,
/// YouTube visitor data cookie
pub visitor_data: Option<String>,
}
/// Video metadata from the player
@ -461,6 +465,8 @@ pub struct Playlist {
pub last_update: Option<Date>,
/// Textual last update date
pub last_update_txt: Option<String>,
/// YouTube visitor data cookie
pub visitor_data: Option<String>,
}
/// YouTube video extracted from a playlist
@ -543,6 +549,8 @@ pub struct VideoDetails {
///
/// Is initially empty.
pub latest_comments: Paginator<Comment>,
/// YouTube visitor data cookie
pub visitor_data: Option<String>,
}
/// Chapter of a video
@ -675,6 +683,8 @@ pub struct Channel<T> {
pub mobile_banner: Vec<Thumbnail>,
/// Banner image shown above the channel (16:9 fullscreen format for TV)
pub tv_banner: Vec<Thumbnail>,
/// YouTube visitor data cookie
pub visitor_data: Option<String>,
/// Content fetched from the channel
pub content: T,
}
@ -745,6 +755,8 @@ pub struct SearchResult {
/// for the corrected search term and displays it on top of the
/// search results page.
pub corrected_query: Option<String>,
/// YouTube visitor data cookie
pub visitor_data: Option<String>,
}
/// YouTube item (Video/Channel/Playlist)