From c3e9c45e777ae638ec9a4b4675e602672eb22ffd Mon Sep 17 00:00:00 2001 From: Simon Repp Date: Thu, 20 Feb 2025 21:26:42 +0100 Subject: [PATCH] Support enabling/disabling download buttons for videos (disabled by default) --- docs/reference/01 Collection.md | 22 ++++++++++++++- docs/reference/02 Playlist.md | 22 ++++++++++++++- docs/reference/03 Video.md | 22 ++++++++++++++- src/build.rs | 16 ++++++++--- src/build/video.rs | 5 +++- src/collection.rs | 34 +++++++++++++++++++++++ src/editor/endpoints/collection.rs | 27 +++++++++++++++++- src/editor/endpoints/collection/update.rs | 9 ++++++ src/editor/endpoints/playlist.rs | 27 +++++++++++++++++- src/editor/endpoints/playlist/update.rs | 9 ++++++ src/editor/endpoints/video.rs | 27 +++++++++++++++++- src/editor/endpoints/video/update.rs | 9 ++++++ src/playlist.rs | 34 +++++++++++++++++++++++ src/video.rs | 34 +++++++++++++++++++++++ 14 files changed, 286 insertions(+), 11 deletions(-) diff --git a/docs/reference/01 Collection.md b/docs/reference/01 Collection.md index 87c2ac3..74515c9 100644 --- a/docs/reference/01 Collection.md +++ b/docs/reference/01 Collection.md @@ -37,12 +37,13 @@ Content in a collection currently appears in the following order on the built si ## The collection.eno manifest -> All options at a glance: [description](#description), [embedding](#embedding), [link](#link), [offline](#offline), [title](#title), [unlisted](#unlisted), [video_order](#video_order) +> All options at a glance: [description](#description), [download](#download), [embedding](#embedding), [link](#link), [offline](#offline), [title](#title), [unlisted](#unlisted), [video_order](#video_order) Manifest for tagging and configuring a collection. ```eno title: Soothing clips +download: enabled offline video_order: sort_number_desc unlisted @@ -70,6 +71,25 @@ additionally use markdown-style inline links such as `[Example](https://example. the text of a link (Note that no other markdown syntax is supported inside `description` though). Any number of empty lines in the description field separates paragraphs in the text. +## `download` + +This opt-in setting enables download buttons on video pages, openly inviting +and allowing visitors to conveniently choose and download a specific version +of a video to their computer. + +```eno +download: enabled +``` + +Note that the absence of a download button (i.e. the default setting of this +option) does not mean that it is not possible to download your videos - this +is always easily possible for anyone with some technical knowledge. Enabling +download buttons however is an invitation to your visitors that you want them +to be easily able to do so, and inversely, not enabling it - as on every +other website - simply implies that you do not openly invite downloads, and +visitors will have to research or inquire about your video's license before +making any use of it beyond watching it on your site. + ## `embedding` This allows visitors/you to copy embed codes (html) that they/you can use to diff --git a/docs/reference/02 Playlist.md b/docs/reference/02 Playlist.md index d8d02bf..ce38c2c 100644 --- a/docs/reference/02 Playlist.md +++ b/docs/reference/02 Playlist.md @@ -30,7 +30,7 @@ sequential playback. ## The playlist.eno manifest -> All options at a glance: [banner](#banner), [description](#description), [embedding](#embedding), [link](#link), [offline](#offline), [order](#order), [title](#title), [unlisted](#unlisted) +> All options at a glance: [banner](#banner), [description](#description), [download](#download), [embedding](#embedding), [link](#link), [offline](#offline), [order](#order), [title](#title), [unlisted](#unlisted) Manifest for tagging and configuring a playlist. @@ -39,6 +39,7 @@ banner: description = Colorful flags in the wind file = my_poster.jpg +download: enabled embedding: enabled offline order: release_date_asc @@ -78,6 +79,25 @@ additionally use markdown-style inline links such as `[Example](https://example. the text of a link (Note that no other markdown syntax is supported inside `description` though). Any number of empty lines in the description field separates paragraphs in the text. +## `download` + +This opt-in setting enables download buttons on video pages, openly inviting +and allowing visitors to conveniently choose and download a specific version +of a video to their computer. + +```eno +download: enabled +``` + +Note that the absence of a download button (i.e. the default setting of this +option) does not mean that it is not possible to download your videos - this +is always easily possible for anyone with some technical knowledge. Enabling +download buttons however is an invitation to your visitors that you want them +to be easily able to do so, and inversely, not enabling it - as on every +other website - simply implies that you do not openly invite downloads, and +visitors will have to research or inquire about your video's license before +making any use of it beyond watching it on your site. + ## `embedding` This allows visitors/you to copy embed codes (html) that they/you can use to diff --git a/docs/reference/03 Video.md b/docs/reference/03 Video.md index f67a2b7..c9a36a9 100644 --- a/docs/reference/03 Video.md +++ b/docs/reference/03 Video.md @@ -39,7 +39,7 @@ subtitles while `fr.vtt` would indicate french subtitles. ## The video.eno manifest -> All options at a glance: [description](#description), [embedding](#embedding), [link](#link), [offline](#offline), [poster](#poster), [release_date](#release_date), [sort_number](#sort_number), [title](#title), [unlisted](#unlisted) +> All options at a glance: [description](#description), [download](#download), [embedding](#embedding), [link](#link), [offline](#offline), [poster](#poster), [release_date](#release_date), [sort_number](#sort_number), [title](#title), [unlisted](#unlisted) Manifest for tagging and configuring a video, which can be placed inside the video directory. A video directory can be identified as a video directory @@ -51,6 +51,7 @@ poster: description = Confused looking people in an office file = episode_1.jpg +download: enabled offline release_date: 2024-02-12 sort_number: 1 @@ -79,6 +80,25 @@ additionally use markdown-style inline links such as `[Example](https://example. the text of a link (Note that no other markdown syntax is supported inside `description` though). Any number of empty lines in the description field separates paragraphs in the text. +## `download` + +This opt-in setting enables a download button on the video page, openly inviting +and allowing visitors to conveniently choose and download a specific version +of the video to their computer. + +```eno +download: enabled +``` + +Note that the absence of a download button (i.e. the default setting of this +option) does not mean that it is not possible to download your video - this +is always easily possible for anyone with some technical knowledge. Enabling +the download button however is an invitation to your visitors that you want them +to be easily able to do so, and inversely, not enabling it - as on every +other website - simply implies that you do not openly invite downloading, and +visitors will have to research or inquire about your video's license before +making any use of it beyond watching it on your site. + ## `embedding` This allows visitors/you to copy embed codes (html) that they/you can use to diff --git a/src/build.rs b/src/build.rs index a10fb07..b791491 100644 --- a/src/build.rs +++ b/src/build.rs @@ -35,16 +35,17 @@ use video::video_html; /// Some options need to be evaluated in the context of the entire site tree /// before they are applied at the individual node level. This includes -/// embedding, where the option is inherited down the tree but potentially -/// overriden at the node level, as well as the unlisted option, which -/// unconditionally applies to all descendants of a site tree branch whose -/// (in)direct parent node is marked as unlisted. +/// embedding and download, where the option is inherited down the tree but +/// potentially overriden at the node level, as well as the unlisted option, +/// which unconditionally applies to all descendants of a site tree branch +/// whose(in)direct parent node is marked as unlisted. /// /// MergeOptions represents these options, and at the node level we only /// consider the options on this struct (after we have merged in the options /// from the node level beforehand of course). #[derive(Clone)] struct MergedOptions { + pub download: bool, pub embedding: bool, pub unlisted: bool } @@ -52,36 +53,43 @@ struct MergedOptions { impl MergedOptions { pub fn default() -> MergedOptions { MergedOptions { + download: false, embedding: false, unlisted: false } } pub fn merge_collection_options(&self, collection: &Collection) -> MergedOptions { + let download = collection.download.unwrap_or(self.download); let embedding = collection.embedding.unwrap_or(self.embedding); let unlisted = self.unlisted || collection.unlisted; MergedOptions { + download, embedding, unlisted } } pub fn merge_playlist_options(&self, playlist: &Playlist) -> MergedOptions { + let download = playlist.download.unwrap_or(self.download); let embedding = playlist.embedding.unwrap_or(self.embedding); let unlisted = self.unlisted || playlist.unlisted; MergedOptions { + download, embedding, unlisted } } pub fn merge_video_options(&self, video: &Video) -> MergedOptions { + let download = video.download.unwrap_or(self.download); let embedding = video.embedding.unwrap_or(self.embedding); let unlisted = self.unlisted || video.unlisted; MergedOptions { + download, embedding, unlisted } diff --git a/src/build/video.rs b/src/build/video.rs index b80016c..751979d 100644 --- a/src/build/video.rs +++ b/src/build/video.rs @@ -38,7 +38,10 @@ pub fn video_html( }; let e_title = html_escape_outside_attribute(title); - let r_downloads = downloads(site, video); + let r_downloads = match merged_options.download { + true => downloads(site, video), + false => String::new() + }; let next_video = if video.unlisted { None diff --git a/src/collection.rs b/src/collection.rs index a159023..5144349 100644 --- a/src/collection.rs +++ b/src/collection.rs @@ -32,6 +32,7 @@ use crate::util::checked_remove_dir_all; const COLLECTION_OPTIONS: &[&str] = &[ "banner", "description", + "download", "embedding", "link", "offline", @@ -44,6 +45,7 @@ const COLLECTION_OPTIONS: &[&str] = &[ pub struct Collection { pub banner: Option, pub description: Option, + pub download: Option, pub embedding: Option, pub errors: Vec, pub links: Vec, @@ -141,6 +143,33 @@ impl Collection { self.errors.push(error); } } + "download" => 'download: { + if let Ok(field) = element.as_field() { + if let Ok(result) = field.value() { + if let Some(value) = result { + match value { + "enabled" => { + self.download = Some(true); + } + "disabled" => { + self.download = Some(false); + } + other => { + let message = format!("The download option only supports the value 'enabled' or 'disabled'. The given value '{other}' was not recognized."); + let error = element_error(element, manifest_path, &message); + self.errors.push(error); + } + } + + break 'download; + } + } + } + + let message = "download needs to be provided as a field with the value 'enabled' or 'disabled', e.g.: 'download: enabled'"; + let error = element_error(element, manifest_path, message); + self.errors.push(error); + } "embedding" => 'embedding: { if let Ok(field) = element.as_field() { if let Ok(result) = field.value() { @@ -598,6 +627,7 @@ impl Collection { Collection { banner: None, description: None, + download: None, embedding: None, errors: Vec::new(), links: Vec::new(), @@ -1003,6 +1033,10 @@ impl Collection { eno.push_str(&format!("file = {}\n", banner.file_name)); } + if let Some(download) = self.download { + eno.push_str(&format!("download: {}\n", if download { "enabled" } else { "disabled" })); + } + if let Some(embedding) = self.embedding { eno.push_str(&format!("embedding: {}\n", if embedding { "enabled" } else { "disabled" })); } diff --git a/src/editor/endpoints/collection.rs b/src/editor/endpoints/collection.rs index 693d396..73b315f 100644 --- a/src/editor/endpoints/collection.rs +++ b/src/editor/endpoints/collection.rs @@ -65,6 +65,7 @@ pub enum CollectionFormFeedback<'a> { Site(endpoints::site::SiteFormFeedback), Update { description: &'a str, + download: Option, embedding: Option, offline: bool, title: Field, @@ -145,10 +146,16 @@ pub fn edit_page( let mut input_offline = Checkbox::new("offline", translations.offline); let mut input_title = Input::text("title", translations.title); let mut input_unlisted = Checkbox::new("unlisted", translations.unlisted); + let mut select_download = Select::new("download", translations.download); let mut select_embedding = Select::new("embedding", translations.embedding); let mut select_video_order = Select::new("video_order", translations.video_order); let mut textarea_description = Textarea::markdown_subset(language, "description", translations.description); + // TODO: Dynamically determine and additionally display what the default or inherited value is here. + select_download.option("", translations.inherit); + select_download.option("enabled", translations.enabled); + select_download.option("disabled", translations.disabled); + // TODO: Dynamically determine and additionally display what the default or inherited value is here. select_embedding.option("", translations.inherit); select_embedding.option("enabled", translations.enabled); @@ -167,12 +174,19 @@ pub fn edit_page( let feedback_update = if let CollectionFormFeedback::Update { description, + download, embedding, offline, title, unlisted, video_order } = &form_feedback { + let download_value = match download { + Some(true) => "enabled", + Some(false) => "disabled", + None => "" + }; + let embedding_value = match embedding { Some(true) => "enabled", Some(false) => "disabled", @@ -182,12 +196,19 @@ pub fn edit_page( input_offline.checked(*offline); input_title.validated_value(title); input_unlisted.checked(*unlisted); + select_download.selected(download_value); select_embedding.selected(embedding_value); select_video_order.selected(video_order); textarea_description.value(description); format!(r#""#) } else { + let download = match collection.download { + Some(true) => "enabled", + Some(false) => "disabled", + None => "" + }; + let embedding = match collection.embedding { Some(true) => "enabled", Some(false) => "disabled", @@ -197,6 +218,7 @@ pub fn edit_page( input_offline.checked(collection.offline); input_title.value_option(&collection.title); input_unlisted.checked(collection.unlisted); + select_download.selected(download); select_embedding.selected(embedding); select_video_order.selected(collection.video_order.key()); textarea_description.value_option(&collection.description); @@ -432,7 +454,10 @@ pub fn edit_page( {input_title} {textarea_description} {select_video_order} - {select_embedding} +
+ {select_download} + {select_embedding} +
{input_offline} {input_unlisted_conditionally_hidden} diff --git a/src/editor/endpoints/collection/update.rs b/src/editor/endpoints/collection/update.rs index 2736698..cfed468 100644 --- a/src/editor/endpoints/collection/update.rs +++ b/src/editor/endpoints/collection/update.rs @@ -16,6 +16,7 @@ use super::CollectionFormFeedback; #[derive(Deserialize)] pub struct UpdateForm { description: String, + download: String, embedding: String, offline: Option, title: String, @@ -32,6 +33,12 @@ pub async fn update( let description_trimmed = form.description.trim(); + let download = match form.download.as_str() { + "disabled" => Some(false), + "enabled" => Some(true), + _ => None + }; + let embedding = match form.embedding.as_str() { "disabled" => Some(false), "enabled" => Some(true), @@ -47,6 +54,7 @@ pub async fn update( if true { let mutation = |collection_mut: &mut Collection| { collection_mut.description = if description_trimmed.is_empty() { None } else { Some(description_trimmed.to_owned()) }; + collection_mut.download = download; collection_mut.embedding = embedding; collection_mut.offline = offline; collection_mut.title = if title_trimmed.is_empty() { None } else { Some(title_trimmed.to_owned()) }; @@ -72,6 +80,7 @@ pub async fn update( let form_feedback = CollectionFormFeedback::Update { description: description_trimmed, + download, embedding, offline, title: Field::valid(title_trimmed), diff --git a/src/editor/endpoints/playlist.rs b/src/editor/endpoints/playlist.rs index 4910ef1..72409e8 100644 --- a/src/editor/endpoints/playlist.rs +++ b/src/editor/endpoints/playlist.rs @@ -53,6 +53,7 @@ pub enum PlaylistFormFeedback<'a> { Site(endpoints::site::SiteFormFeedback), Update { description: &'a str, + download: Option, embedding: Option, offline: bool, order: &'a str, @@ -135,10 +136,16 @@ pub fn edit_page( let mut input_offline = Checkbox::new("offline", translations.offline); let mut input_title = Input::text("title", translations.title); let mut input_unlisted = Checkbox::new("unlisted", translations.unlisted); + let mut select_download = Select::new("download", translations.download); let mut select_embedding = Select::new("embedding", translations.embedding); let mut select_order = Select::new("order", translations.order); let mut textarea_description = Textarea::markdown_subset(language, "description", translations.description); + // TODO: Dynamically determine and additionally display what the default or inherited value is here. + select_download.option("", translations.inherit); + select_download.option("enabled", translations.enabled); + select_download.option("disabled", translations.disabled); + // TODO: Dynamically determine and additionally display what the default or inherited value is here. select_embedding.option("", translations.inherit); select_embedding.option("enabled", translations.enabled); @@ -156,12 +163,19 @@ pub fn edit_page( let feedback_update = if let Some(PlaylistFormFeedback::Update { description, + download, embedding, offline, order, title, unlisted }) = &form_feedback { + let download_value = match download { + Some(true) => "enabled", + Some(false) => "disabled", + None => "" + }; + let embedding_value = match embedding { Some(true) => "enabled", Some(false) => "disabled", @@ -171,12 +185,19 @@ pub fn edit_page( input_offline.checked(*offline); input_title.validated_value(title); input_unlisted.checked(*unlisted); + select_download.selected(download_value); select_embedding.selected(embedding_value); select_order.selected(order); textarea_description.value(description); format!(r#""#) } else { + let download = match playlist.download { + Some(true) => "enabled", + Some(false) => "disabled", + None => "" + }; + let embedding = match playlist.embedding { Some(true) => "enabled", Some(false) => "disabled", @@ -186,6 +207,7 @@ pub fn edit_page( input_offline.checked(playlist.offline); input_title.value_option(&playlist.title); input_unlisted.checked(playlist.unlisted); + select_download.selected(download); select_embedding.selected(embedding); select_order.selected(playlist.order.key()); textarea_description.value_option(&playlist.description); @@ -338,7 +360,10 @@ pub fn edit_page( {input_title} {textarea_description} {select_order} - {select_embedding} +
+ {select_download} + {select_embedding} +
{input_offline} {input_unlisted_conditionally_hidden} diff --git a/src/editor/endpoints/playlist/update.rs b/src/editor/endpoints/playlist/update.rs index 0ce01a6..ab8e45e 100644 --- a/src/editor/endpoints/playlist/update.rs +++ b/src/editor/endpoints/playlist/update.rs @@ -16,6 +16,7 @@ use super::PlaylistFormFeedback; #[derive(Deserialize)] pub struct PlaylistUpdateForm { description: String, + download: String, embedding: String, offline: Option, order: String, @@ -32,6 +33,12 @@ pub async fn update( let description_trimmed = form.description.trim(); + let download = match form.download.as_str() { + "disabled" => Some(false), + "enabled" => Some(true), + _ => None + }; + let embedding = match form.embedding.as_str() { "disabled" => Some(false), "enabled" => Some(true), @@ -47,6 +54,7 @@ pub async fn update( if true { let mutation = |playlist_mut: &mut Playlist| { playlist_mut.description = if description_trimmed.is_empty() { None } else { Some(description_trimmed.to_owned()) }; + playlist_mut.download = download; playlist_mut.embedding = embedding; playlist_mut.offline = offline; playlist_mut.order = order; @@ -70,6 +78,7 @@ pub async fn update( let form_feedback = PlaylistFormFeedback::Update { description: description_trimmed, + download, embedding, offline, order: &form.order, diff --git a/src/editor/endpoints/video.rs b/src/editor/endpoints/video.rs index 17b8014..c1acb06 100644 --- a/src/editor/endpoints/video.rs +++ b/src/editor/endpoints/video.rs @@ -63,6 +63,7 @@ pub enum VideoFormFeedback<'a> { Site(endpoints::site::SiteFormFeedback), Update { description: &'a str, + download: Option, embedding: Option, offline: bool, release_date: Field, @@ -417,9 +418,15 @@ pub fn edit_page( let mut input_sort_number = Input::number("sort_number", translations.sort_number); let mut input_title = Input::text("title", translations.title); let mut input_unlisted = Checkbox::new("unlisted", translations.unlisted); + let mut select_download = Select::new("download", translations.download); let mut select_embedding = Select::new("embedding", translations.embedding); let mut textarea_description = Textarea::markdown_subset(language, "description", translations.description); + // TODO: Dynamically determine and additionally display what the default or inherited value is here. + select_download.option("", translations.inherit); + select_download.option("enabled", translations.enabled); + select_download.option("disabled", translations.disabled); + // TODO: Dynamically determine and additionally display what the default or inherited value is here. select_embedding.option("", translations.inherit); select_embedding.option("enabled", translations.enabled); @@ -429,6 +436,7 @@ pub fn edit_page( let feedback_update = if let Some(VideoFormFeedback::Update { description, + download, embedding, offline, release_date, @@ -436,6 +444,12 @@ pub fn edit_page( title, unlisted }) = &form_feedback { + let download_value = match download { + Some(true) => "enabled", + Some(false) => "disabled", + None => "" + }; + let embedding_value = match embedding { Some(true) => "enabled", Some(false) => "disabled", @@ -447,11 +461,18 @@ pub fn edit_page( input_sort_number.validated_value(sort_number); input_title.validated_value(title); input_unlisted.checked(*unlisted); + select_download.selected(download_value); select_embedding.selected(embedding_value); textarea_description.value(description); format!(r#""#) } else { + let download = match video.download { + Some(true) => "enabled", + Some(false) => "disabled", + None => "" + }; + let embedding = match video.embedding { Some(true) => "enabled", Some(false) => "disabled", @@ -465,6 +486,7 @@ pub fn edit_page( input_sort_number.value_option(&sort_number); input_title.value_option(&video.title); input_unlisted.checked(video.unlisted); + select_download.selected(download); select_embedding.selected(embedding); textarea_description.value_option(&video.description); @@ -573,7 +595,10 @@ pub fn edit_page( {input_release_date} {input_sort_number} - {select_embedding} +
+ {select_download} + {select_embedding} +
{input_offline} {input_unlisted_conditionally_hidden} diff --git a/src/editor/endpoints/video/update.rs b/src/editor/endpoints/video/update.rs index c01141e..d32767f 100644 --- a/src/editor/endpoints/video/update.rs +++ b/src/editor/endpoints/video/update.rs @@ -17,6 +17,7 @@ use super::VideoFormFeedback; #[derive(Deserialize)] pub struct VideoUpdateForm { description: String, + download: String, embedding: String, offline: Option, release_date: String, @@ -34,6 +35,12 @@ pub async fn update( let description_trimmed = form.description.trim(); + let download = match form.download.as_str() { + "disabled" => Some(false), + "enabled" => Some(true), + _ => None + }; + let embedding = match form.embedding.as_str() { "disabled" => Some(false), "enabled" => Some(true), @@ -74,6 +81,7 @@ pub async fn update( let form_feedback = VideoFormFeedback::Update { description: description_trimmed, + download, embedding, offline, release_date: Field::validated(release_date_trimmed, release_date.and_then(|release_date| release_date.err())), @@ -88,6 +96,7 @@ pub async fn update( } else { let mutation = |video_mut: &mut Video| { video_mut.description = if description_trimmed.is_empty() { None } else { Some(description_trimmed.to_owned()) }; + video_mut.download = download; video_mut.embedding = embedding; video_mut.offline = offline; video_mut.release_date = release_date.and_then(|release_date| release_date.ok()); diff --git a/src/playlist.rs b/src/playlist.rs index 732f367..333a366 100644 --- a/src/playlist.rs +++ b/src/playlist.rs @@ -30,6 +30,7 @@ use crate::util::checked_remove_dir_all; const PLAYLIST_OPTIONS: &[&str] = &[ "banner", "description", + "download", "embedding", "link", "offline", @@ -42,6 +43,7 @@ const PLAYLIST_OPTIONS: &[&str] = &[ pub struct Playlist { pub banner: Option, pub description: Option, + pub download: Option, pub embedding: Option, pub errors: Vec, pub links: Vec, @@ -146,6 +148,33 @@ impl Playlist { self.errors.push(error); } } + "download" => 'download: { + if let Ok(field) = element.as_field() { + if let Ok(result) = field.value() { + if let Some(value) = result { + match value { + "enabled" => { + self.download = Some(true); + } + "disabled" => { + self.download = Some(false); + } + other => { + let message = format!("The download option only supports the value 'enabled' or 'disabled'. The given value '{other}' was not recognized."); + let error = element_error(element, manifest_path, &message); + self.errors.push(error); + } + } + + break 'download; + } + } + } + + let message = "download needs to be provided as a field with the value 'enabled' or 'disabled', e.g.: 'download: enabled'"; + let error = element_error(element, manifest_path, message); + self.errors.push(error); + } "embedding" => 'embedding: { if let Ok(field) = element.as_field() { if let Ok(result) = field.value() { @@ -418,6 +447,7 @@ impl Playlist { Playlist { banner: None, description: None, + download: None, embedding: None, errors: Vec::new(), links: Vec::new(), @@ -630,6 +660,10 @@ impl Playlist { eno.push_str(&format!("file = {}\n", banner.file_name)); } + if let Some(download) = self.download { + eno.push_str(&format!("download: {}\n", if download { "enabled" } else { "disabled" })); + } + if let Some(embedding) = self.embedding { eno.push_str(&format!("embedding: {}\n", if embedding { "enabled" } else { "disabled" })); } diff --git a/src/video.rs b/src/video.rs index 529f942..f688cb1 100644 --- a/src/video.rs +++ b/src/video.rs @@ -29,6 +29,7 @@ use crate::util::{checked_remove_dir_all, hash}; const VIDEO_OPTIONS: &[&str] = &[ "description", + "download", "embedding", "link", "offline", @@ -42,6 +43,7 @@ const VIDEO_OPTIONS: &[&str] = &[ #[derive(Clone, Debug)] pub struct Video { pub description: Option, + pub download: Option, pub embedding: Option, pub errors: Vec, pub links: Vec, @@ -122,6 +124,33 @@ impl Video { self.errors.push(error); } } + "download" => 'download: { + if let Ok(field) = element.as_field() { + if let Ok(result) = field.value() { + if let Some(value) = result { + match value { + "enabled" => { + self.download = Some(true); + } + "disabled" => { + self.download = Some(false); + } + other => { + let message = format!("The download option only supports the value 'enabled' or 'disabled'. The given value '{other}' was not recognized."); + let error = element_error(element, manifest_path, &message); + self.errors.push(error); + } + } + + break 'download; + } + } + } + + let message = "download needs to be provided as a field with the value 'enabled' or 'disabled', e.g.: 'download: enabled'"; + let error = element_error(element, manifest_path, message); + self.errors.push(error); + } "embedding" => 'embedding: { if let Ok(field) = element.as_field() { if let Ok(result) = field.value() { @@ -570,6 +599,7 @@ impl Video { pub fn new(path: String) -> Video { Video { description: None, + download: None, embedding: None, errors: Vec::new(), links: Vec::new(), @@ -725,6 +755,10 @@ impl Video { eno.push_str(&link.to_eno()); } + if let Some(download) = self.download { + eno.push_str(&format!("download: {}\n", if download { "enabled" } else { "disabled" })); + } + if let Some(embedding) = self.embedding { eno.push_str(&format!("embedding: {}\n", if embedding { "enabled" } else { "disabled" })); }