feat: extract timestamps from video links
This commit is contained in:
parent
ed522e622d
commit
e4b10fcc83
3 changed files with 101 additions and 62 deletions
|
|
@ -573,8 +573,9 @@ mod tests {
|
|||
url: "https://smarturl.it/aespa_BlackMamba"
|
||||
- Text: "\n🐍The Debut Stage "
|
||||
- Video:
|
||||
title: "https://youtu.be/Ky5RT5oGg0w"
|
||||
text: "https://youtu.be/Ky5RT5oGg0w"
|
||||
id: Ky5RT5oGg0w
|
||||
start_time: 0
|
||||
- Text: "\n\n🎟️ aespa Showcase SYNK in LA! Tickets now on sale: "
|
||||
- Web:
|
||||
text: "https://www.ticketmaster.com/event/0A..."
|
||||
|
|
@ -843,8 +844,9 @@ mod tests {
|
|||
url: "https://www.twitch.tv/linustech"
|
||||
- Text: "\n\nMUSIC CREDIT\n---------------------------------------------------\nIntro: Laszlo - Supernova\nVideo Link: "
|
||||
- Video:
|
||||
title: "https://www.youtube.com/watch?v=PKfxm..."
|
||||
text: "https://www.youtube.com/watch?v=PKfxm..."
|
||||
id: PKfxmFU3lWY
|
||||
start_time: 0
|
||||
- Text: "\niTunes Download Link: "
|
||||
- Web:
|
||||
text: "https://itunes.apple.com/us/album/sup..."
|
||||
|
|
@ -855,8 +857,9 @@ mod tests {
|
|||
url: "https://soundcloud.com/laszlomusic"
|
||||
- Text: "\n\nOutro: Approaching Nirvana - Sugar High\nVideo Link: "
|
||||
- Video:
|
||||
title: "https://www.youtube.com/watch?v=ngsGB..."
|
||||
text: "https://www.youtube.com/watch?v=ngsGB..."
|
||||
id: ngsGBSCDwcI
|
||||
start_time: 0
|
||||
- Text: "\nListen on Spotify: "
|
||||
- Web:
|
||||
text: "http://spoti.fi/UxWkUw"
|
||||
|
|
@ -883,60 +886,74 @@ mod tests {
|
|||
url: "https://geni.us/Ps3XfE"
|
||||
- Text: "\n\nCHAPTERS\n---------------------------------------------------\n"
|
||||
- Video:
|
||||
title: "0:00"
|
||||
text: "0:00"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 0
|
||||
- Text: " Intro\n"
|
||||
- Video:
|
||||
title: "0:42"
|
||||
text: "0:42"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 42
|
||||
- Text: " The PC Built for Super Efficiency\n"
|
||||
- Video:
|
||||
title: "2:41"
|
||||
text: "2:41"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 161
|
||||
- Text: " Our BURIAL ENCLOSURE?!\n"
|
||||
- Video:
|
||||
title: "3:31"
|
||||
text: "3:31"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 211
|
||||
- Text: " Our Power Solution (Thanks Jackery!)\n"
|
||||
- Video:
|
||||
title: "4:47"
|
||||
text: "4:47"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 287
|
||||
- Text: " Diggin' Holes\n"
|
||||
- Video:
|
||||
title: "5:30"
|
||||
text: "5:30"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 330
|
||||
- Text: " Colonoscopy?\n"
|
||||
- Video:
|
||||
title: "7:04"
|
||||
text: "7:04"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 424
|
||||
- Text: " Diggin' like a man\n"
|
||||
- Video:
|
||||
title: "8:29"
|
||||
text: "8:29"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 509
|
||||
- Text: " The world's worst woodsman\n"
|
||||
- Video:
|
||||
title: "9:03"
|
||||
text: "9:03"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 543
|
||||
- Text: " Backyard cable management\n"
|
||||
- Video:
|
||||
title: "10:02"
|
||||
text: "10:02"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 602
|
||||
- Text: " Time to bury this boy\n"
|
||||
- Video:
|
||||
title: "10:46"
|
||||
text: "10:46"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 646
|
||||
- Text: " Solar Power Generation\n"
|
||||
- Video:
|
||||
title: "11:37"
|
||||
text: "11:37"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 697
|
||||
- Text: " Issues\n"
|
||||
- Video:
|
||||
title: "12:08"
|
||||
text: "12:08"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 728
|
||||
- Text: " First Play Test\n"
|
||||
- Video:
|
||||
title: "13:20"
|
||||
text: "13:20"
|
||||
id: nFDBxBUfE74
|
||||
start_time: 800
|
||||
- Text: " Conclusion"
|
||||
"###);
|
||||
|
||||
|
|
@ -1045,8 +1062,9 @@ mod tests {
|
|||
---
|
||||
- Text: "Live NASA - Views Of Earth from Space\nLive video feed of Earth from the International Space Station (ISS) Cameras\n-----------------------------------------------------------------------------------------------------\nWatch our latest video - The Sun - 4K Video / Solar Flares\n"
|
||||
- Video:
|
||||
title: "https://www.youtube.com/watch?v=SEzK4..."
|
||||
text: "https://www.youtube.com/watch?v=SEzK4..."
|
||||
id: SEzK4ZfMvUQ
|
||||
start_time: 0
|
||||
- Text: "\n-----------------------------------------------------------------------------------------------------\nNasa ISS live stream from aboard the International Space Station as it circles the earth at 240 miles above the planet, on the edge of space in low earth orbit. \n\nThe station is crewed by NASA astronauts as well as Russian Cosmonauts and a mixture of Japanese, Canadian and European astronauts as well.\n\n"
|
||||
- Text: " "
|
||||
- Text: " "
|
||||
|
|
|
|||
|
|
@ -10,15 +10,19 @@ pub enum TextComponent {
|
|||
/// Web link
|
||||
Web { text: String, url: String },
|
||||
/// Link to a YouTube video
|
||||
Video { title: String, id: String },
|
||||
Video {
|
||||
text: String,
|
||||
id: String,
|
||||
start_time: u32,
|
||||
},
|
||||
/// Link to a YouTube channel
|
||||
Channel { name: String, id: String },
|
||||
Channel { text: String, id: String },
|
||||
/// Link to a YouTube playlist
|
||||
Playlist { name: String, id: String },
|
||||
Playlist { text: String, id: String },
|
||||
/// Link to a YouTube Music artist
|
||||
Artist { name: String, id: String },
|
||||
Artist { text: String, id: String },
|
||||
/// Link to a YouTube Music album
|
||||
Album { name: String, id: String },
|
||||
Album { text: String, id: String },
|
||||
}
|
||||
|
||||
/// Trait for converting rich text to plain text.
|
||||
|
|
@ -46,12 +50,27 @@ pub trait ToHtml {
|
|||
fn to_html_yt_host(&self, yt_host: &str) -> String;
|
||||
}
|
||||
|
||||
impl ToPlaintext for TextComponent {
|
||||
fn to_plaintext_yt_host(&self, yt_host: &str) -> String {
|
||||
impl TextComponent {
|
||||
pub fn get_text<'a>(&'a self) -> &'a str {
|
||||
match self {
|
||||
TextComponent::Text(text) => text.to_owned(),
|
||||
TextComponent::Text(text) => text,
|
||||
TextComponent::Web { text, .. } => text,
|
||||
TextComponent::Video { text, .. } => text,
|
||||
TextComponent::Channel { text, .. } => text,
|
||||
TextComponent::Playlist { text, .. } => text,
|
||||
TextComponent::Artist { text, .. } => text,
|
||||
TextComponent::Album { text, .. } => text,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_url(&self, yt_host: &str) -> String {
|
||||
match self {
|
||||
TextComponent::Text(_) => "".to_owned(),
|
||||
TextComponent::Web { url, .. } => url.to_owned(),
|
||||
TextComponent::Video { id, .. } => format!("{}/watch?v={}", yt_host, id),
|
||||
TextComponent::Video { id, start_time, .. } => match start_time {
|
||||
0 => format!("{}/watch?v={}", yt_host, id),
|
||||
n => format!("{}/watch?v={}&t={}s", yt_host, id, n),
|
||||
},
|
||||
TextComponent::Channel { id, .. } | TextComponent::Artist { id, .. } => {
|
||||
format!("{}/channel/{}", yt_host, id)
|
||||
}
|
||||
|
|
@ -62,6 +81,15 @@ impl ToPlaintext for TextComponent {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToPlaintext for TextComponent {
|
||||
fn to_plaintext_yt_host(&self, yt_host: &str) -> String {
|
||||
match self {
|
||||
TextComponent::Text(text) => text.to_owned(),
|
||||
_ => self.get_url(yt_host),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "html")]
|
||||
impl ToHtml for TextComponent {
|
||||
fn to_html_yt_host(&self, yt_host: &str) -> String {
|
||||
|
|
@ -69,35 +97,18 @@ impl ToHtml for TextComponent {
|
|||
TextComponent::Text(text) => askama_escape::escape(&text, askama_escape::Html)
|
||||
.to_string()
|
||||
.replace("\n", "<br>"),
|
||||
TextComponent::Web { text, url } => {
|
||||
TextComponent::Web { text, .. } => {
|
||||
format!(
|
||||
r#"<a href="{}" target="_blank" rel="noreferrer">{}</a>"#,
|
||||
url,
|
||||
askama_escape::escape(&text, askama_escape::Html)
|
||||
self.get_url(yt_host),
|
||||
askama_escape::escape(text, askama_escape::Html)
|
||||
)
|
||||
}
|
||||
TextComponent::Video { title, id } => {
|
||||
_ => {
|
||||
format!(
|
||||
r#"<a href="{}/watch?v={}" rel="noreferrer">{}</a>"#,
|
||||
yt_host,
|
||||
id,
|
||||
askama_escape::escape(&title, askama_escape::Html)
|
||||
)
|
||||
}
|
||||
TextComponent::Channel { name, id } | TextComponent::Artist { name, id } => {
|
||||
format!(
|
||||
r#"<a href="{}/channel/{}" rel="noreferrer">{}</a>"#,
|
||||
yt_host,
|
||||
id,
|
||||
askama_escape::escape(&name, askama_escape::Html)
|
||||
)
|
||||
}
|
||||
TextComponent::Playlist { name, id } | TextComponent::Album { name, id } => {
|
||||
format!(
|
||||
r#"<a href="{}/playlist?list={}" rel="noreferrer">{}</a>"#,
|
||||
yt_host,
|
||||
id,
|
||||
askama_escape::escape(&name, askama_escape::Html)
|
||||
r#"<a href="{}">{}</a>"#,
|
||||
self.get_url(yt_host),
|
||||
askama_escape::escape(self.get_text(), askama_escape::Html)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -133,7 +144,7 @@ mod tests {
|
|||
text::TextComponent::Text { text: "🎧Listen and download aespa's debut single \"Black Mamba\": ".to_owned() },
|
||||
text::TextComponent::Web { text: "https://smarturl.it/aespa_BlackMamba".to_owned(), url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbFY1QmpQamJPSms0Z1FnVTlQUS00ZFhBZnBJZ3xBQ3Jtc0tuRGJBanludGoyRnphb2dZWVd3cUNnS3dEd0FnNHFOZEY1NHBJaHFmLXpaWUJwX3ZucDZxVnpGeHNGX1FpMzFkZW9jQkI2Mi1wNGJ1UVFNN3h1MnN3R3JLMzdxU01nZ01POHBGcmxHU2puSUk1WHRzQQ&q=https%3A%2F%2Fsmarturl.it%2Faespa_BlackMamba&v=ZeerrnuLi5E".to_owned() },
|
||||
text::TextComponent::Text { text: "\n🐍The Debut Stage ".to_owned() },
|
||||
text::TextComponent::Video { title: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned() },
|
||||
text::TextComponent::Video { text: "https://youtu.be/Ky5RT5oGg0w".to_owned(), video_id: "Ky5RT5oGg0w".to_owned(), start_time: 0 },
|
||||
text::TextComponent::Text { text: "\n\n🎟️ aespa Showcase SYNK in LA! Tickets now on sale: ".to_owned() },
|
||||
text::TextComponent::Web { text: "https://www.ticketmaster.com/event/0A...".to_owned(), url: "https://www.youtube.com/redirect?event=video_description&redir_token=QUFFLUhqbFpUMEZiaXJWWkszaVZXaEM0emxWU1JQV3NoQXxBQ3Jtc0tuU2g4VWNPNE5UY3hoSWYtamFzX0h4bUVQLVJiRy1ubDZrTnh3MUpGdDNSaUo0ZlMyT3lUM28ycUVBdHJLMndGcDhla3BkOFpxSVFfOS1QdVJPVHBUTEV1LXpOV0J2QXdhV05lV210cEJtZUJMeHdaTQ&q=https%3A%2F%2Fwww.ticketmaster.com%2Fevent%2F0A005CCD9E871F6E&v=ZeerrnuLi5E".to_owned() },
|
||||
text::TextComponent::Text { text: "\n\nSubscribe to aespa Official YouTube Channel!\n".to_owned() },
|
||||
|
|
@ -198,7 +209,7 @@ aespa 에스파 'Black Mamba' MV ℗ SM Entertainment"#
|
|||
let html = richtext.to_html_yt_host("https://piped.kavin.rocks");
|
||||
assert_eq!(
|
||||
html,
|
||||
"🎧Listen and download aespa's debut single "Black Mamba": <a href=\"https://smarturl.it/aespa_BlackMamba\" target=\"_blank\" rel=\"noreferrer\">https://smarturl.it/aespa_BlackMamba</a><br>🐍The Debut Stage <a href=\"https://piped.kavin.rocks/watch?v=Ky5RT5oGg0w\" rel=\"noreferrer\">https://youtu.be/Ky5RT5oGg0w</a><br><br>🎟\u{fe0f} aespa Showcase SYNK in LA! Tickets now on sale: <a href=\"https://www.ticketmaster.com/event/0A005CCD9E871F6E\" target=\"_blank\" rel=\"noreferrer\">https://www.ticketmaster.com/event/0A...</a><br><br>Subscribe to aespa Official YouTube Channel!<br><a href=\"https://www.youtube.com/aespa?sub_confirmation=1\" target=\"_blank\" rel=\"noreferrer\">https://www.youtube.com/aespa?sub_con...</a><br><br>aespa official<br><a href=\"https://www.youtube.com/c/aespa\" target=\"_blank\" rel=\"noreferrer\">https://www.youtube.com/c/aespa</a><br><a href=\"https://www.instagram.com/aespa_official\" target=\"_blank\" rel=\"noreferrer\">https://www.instagram.com/aespa_official</a><br><a href=\"https://www.tiktok.com/@aespa_official\" target=\"_blank\" rel=\"noreferrer\">https://www.tiktok.com/@aespa_official</a><br><a href=\"https://twitter.com/aespa_Official\" target=\"_blank\" rel=\"noreferrer\">https://twitter.com/aespa_Official</a><br><a href=\"https://www.facebook.com/aespa.official\" target=\"_blank\" rel=\"noreferrer\">https://www.facebook.com/aespa.official</a><br><a href=\"https://weibo.com/aespa\" target=\"_blank\" rel=\"noreferrer\">https://weibo.com/aespa</a><br><br>#aespa #æspa #BlackMamba #블랙맘바 #에스파<br>aespa 에스파 'Black Mamba' MV ℗ SM Entertainment"
|
||||
"🎧Listen and download aespa's debut single "Black Mamba": <a href=\"https://smarturl.it/aespa_BlackMamba\" target=\"_blank\" rel=\"noreferrer\">https://smarturl.it/aespa_BlackMamba</a><br>🐍The Debut Stage <a href=\"https://piped.kavin.rocks/watch?v=Ky5RT5oGg0w\">https://youtu.be/Ky5RT5oGg0w</a><br><br>🎟\u{fe0f} aespa Showcase SYNK in LA! Tickets now on sale: <a href=\"https://www.ticketmaster.com/event/0A005CCD9E871F6E\" target=\"_blank\" rel=\"noreferrer\">https://www.ticketmaster.com/event/0A...</a><br><br>Subscribe to aespa Official YouTube Channel!<br><a href=\"https://www.youtube.com/aespa?sub_confirmation=1\" target=\"_blank\" rel=\"noreferrer\">https://www.youtube.com/aespa?sub_con...</a><br><br>aespa official<br><a href=\"https://www.youtube.com/c/aespa\" target=\"_blank\" rel=\"noreferrer\">https://www.youtube.com/c/aespa</a><br><a href=\"https://www.instagram.com/aespa_official\" target=\"_blank\" rel=\"noreferrer\">https://www.instagram.com/aespa_official</a><br><a href=\"https://www.tiktok.com/@aespa_official\" target=\"_blank\" rel=\"noreferrer\">https://www.tiktok.com/@aespa_official</a><br><a href=\"https://twitter.com/aespa_Official\" target=\"_blank\" rel=\"noreferrer\">https://twitter.com/aespa_Official</a><br><a href=\"https://www.facebook.com/aespa.official\" target=\"_blank\" rel=\"noreferrer\">https://www.facebook.com/aespa.official</a><br><a href=\"https://weibo.com/aespa\" target=\"_blank\" rel=\"noreferrer\">https://weibo.com/aespa</a><br><br>#aespa #æspa #BlackMamba #블랙맘바 #에스파<br>aespa 에스파 'Black Mamba' MV ℗ SM Entertainment"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,8 +86,9 @@ pub struct TextComponents(pub Vec<TextComponent>);
|
|||
#[derive(Debug, Clone)]
|
||||
pub enum TextComponent {
|
||||
Video {
|
||||
title: String,
|
||||
text: String,
|
||||
video_id: String,
|
||||
start_time: u32,
|
||||
},
|
||||
Browse {
|
||||
text: String,
|
||||
|
|
@ -141,6 +142,8 @@ struct NavigationEndpoint {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
struct WatchEndpoint {
|
||||
video_id: String,
|
||||
#[serde(default)]
|
||||
start_time_seconds: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
@ -202,8 +205,9 @@ fn map_richtext_run(lr: &RichTextRun) -> Option<TextComponent> {
|
|||
|
||||
Some(match &nav.watch_endpoint {
|
||||
Some(w) => TextComponent::Video {
|
||||
title: text,
|
||||
text,
|
||||
video_id: w.video_id.to_owned(),
|
||||
start_time: w.start_time_seconds,
|
||||
},
|
||||
None => match &nav.browse_endpoint {
|
||||
Some(b) => TextComponent::Browse {
|
||||
|
|
@ -284,9 +288,14 @@ impl TryFrom<TextComponent> for crate::model::ChannelId {
|
|||
impl From<TextComponent> for crate::model::richtext::TextComponent {
|
||||
fn from(component: TextComponent) -> Self {
|
||||
match component {
|
||||
TextComponent::Video { title, video_id } => Self::Video {
|
||||
title,
|
||||
TextComponent::Video {
|
||||
text,
|
||||
video_id,
|
||||
start_time,
|
||||
} => Self::Video {
|
||||
text,
|
||||
id: video_id,
|
||||
start_time,
|
||||
},
|
||||
TextComponent::Browse {
|
||||
text,
|
||||
|
|
@ -294,19 +303,19 @@ impl From<TextComponent> for crate::model::richtext::TextComponent {
|
|||
browse_id,
|
||||
} => match page_type {
|
||||
PageType::Artist => Self::Artist {
|
||||
name: text,
|
||||
text,
|
||||
id: browse_id,
|
||||
},
|
||||
PageType::Album => Self::Album {
|
||||
name: text,
|
||||
text,
|
||||
id: browse_id,
|
||||
},
|
||||
PageType::Channel => Self::Channel {
|
||||
name: text,
|
||||
text,
|
||||
id: browse_id,
|
||||
},
|
||||
PageType::Playlist => Self::Playlist {
|
||||
name: text,
|
||||
text,
|
||||
id: browse_id,
|
||||
},
|
||||
},
|
||||
|
|
@ -443,8 +452,9 @@ mod tests {
|
|||
insta::assert_debug_snapshot!(res, @r###"
|
||||
SLink {
|
||||
ln: Video {
|
||||
title: "DEEP",
|
||||
text: "DEEP",
|
||||
video_id: "wZIoIgz5mbs",
|
||||
start_time: 0,
|
||||
},
|
||||
}
|
||||
"###);
|
||||
|
|
|
|||
Reference in a new issue