diff options
author | D. Scott Boggs <scott@tams.tech> | 2023-01-31 10:23:47 -0500 |
---|---|---|
committer | D. Scott Boggs <scott@tams.tech> | 2023-01-31 10:25:36 -0500 |
commit | c39d8cd42580554018dd8978066aa9e5b4867ed7 (patch) | |
tree | 7718b96b449c5da72a964dffc12b28024d6e4492 | |
parent | a81e5277eda4fb3eb4a870499563330f6a6d702c (diff) |
Comb through Attachment entitycomb-entities/media-attachment
-rw-r--r-- | entities/src/attachment.rs | 292 |
1 files changed, 258 insertions, 34 deletions
diff --git a/entities/src/attachment.rs b/entities/src/attachment.rs index 489d0fa..007773d 100644 --- a/entities/src/attachment.rs +++ b/entities/src/attachment.rs @@ -2,28 +2,41 @@ use crate::AttachmentId; use serde::{Deserialize, Serialize}; +use url::Url; -/// A struct representing a media attachment. +/// Represents a file or media attachment that can be added to a status. +/// +/// See also [the API documentation](https://docs.joinmastodon.org/entities/MediaAttachment/) #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] +#[serde(deny_unknown_fields)] pub struct Attachment { /// ID of the attachment. pub id: AttachmentId, - /// The media type of an attachment. + /// The type of the attachment. #[serde(rename = "type")] pub media_type: MediaType, - /// URL of the locally hosted version of the image. - pub url: Option<String>, - /// For remote images, the remote URL of the original image. - pub remote_url: Option<String>, - /// URL of the preview image. - pub preview_url: String, + /// The location of the original full-size attachment, if the media has + /// been processed. Is `None` if the media is still processing on the + /// server. + #[serde(skip_serializing_if = "Option::is_none")] + pub url: Option<Url>, + /// The location of a scaled-down preview of the attachment. + pub preview_url: Url, + /// The location of the full-size original attachment on the remote website. + pub remote_url: Option<Url>, /// Shorter URL for the image, for insertion into text /// (only present on local images) - pub text_url: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub text_url: Option<Url>, /// Meta information about the attachment. + #[serde(skip_serializing_if = "Option::is_none")] pub meta: Option<Meta>, /// Noop will be removed. pub description: Option<String>, + /// A hash computed by [the BlurHash algorithm](https://github.com/woltapp/blurhash), + /// for generating colorful preview thumbnails when media has not been + /// downloaded yet. + pub blurhash: Option<String>, } impl Attachment { @@ -39,45 +52,94 @@ impl Attachment { } } -/// Information about the attachment itself. +/// Metadata about some attachment. #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] pub struct Meta { - /// Original version. - pub original: Option<ImageDetails>, - /// Smaller version. - pub small: Option<ImageDetails>, -} - -/// Dimensions of an attachement. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] -pub struct ImageDetails { - /// width of attachment. - pub width: u64, - /// height of attachment. - pub height: u64, - /// A string of `widthxheight`. + #[serde(skip_serializing_if = "Option::is_none")] + pub length: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub duration: Option<f64>, + #[serde(skip_serializing_if = "Option::is_none")] + pub fps: Option<i64>, + #[serde(skip_serializing_if = "Option::is_none")] pub size: Option<String>, - /// The aspect ratio of the attachment. + #[serde(skip_serializing_if = "Option::is_none")] + pub width: Option<i64>, + #[serde(skip_serializing_if = "Option::is_none")] + pub height: Option<i64>, + #[serde(skip_serializing_if = "Option::is_none")] pub aspect: Option<f64>, + #[serde(skip_serializing_if = "Option::is_none")] + pub audio_encode: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub audio_bitrate: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub audio_channels: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub original: Option<SizeSpecificDetails>, + #[serde(skip_serializing_if = "Option::is_none")] + pub small: Option<SizeSpecificDetails>, + #[serde(skip_serializing_if = "Option::is_none")] + pub focus: Option<FocalPoint>, } /// The type of media attachment. +/// +/// See also [the API documentation](https://docs.joinmastodon.org/entities/MediaAttachment/#type) #[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] pub enum MediaType { + /// Audio track + Audio, /// An image. - #[serde(rename = "image")] Image, /// A video file. - #[serde(rename = "video")] Video, - /// A gifv format file. - #[serde(rename = "gifv")] + /// Looping, soundless animation Gifv, /// Unknown format. - #[serde(rename = "unknown")] Unknown, } +/// Metadata about a video attachment +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct SizeSpecificDetails { + /// How many pixels wide the video or image is. + #[serde(skip_serializing_if = "Option::is_none")] + pub width: Option<i64>, + /// How many pixels tall the video is. + #[serde(skip_serializing_if = "Option::is_none")] + pub height: Option<i64>, + /// The frame rate of the video. + #[serde(skip_serializing_if = "Option::is_none")] + pub frame_rate: Option<String>, + /// The duration of the video, in seconds. + #[serde(skip_serializing_if = "Option::is_none")] + pub duration: Option<f64>, + /// The bitrate of the video. + #[serde(skip_serializing_if = "Option::is_none")] + pub bitrate: Option<i64>, + /// The aspect ratio of the video + #[serde(skip_serializing_if = "Option::is_none")] + pub aspect: Option<f64>, + /// The size of the video, expressed like WIDTHxHEIGHT. + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option<String>, +} + +/// A point on an image which should remain in view when cropped for previews. +/// +/// There is some more information [here](https://docs.joinmastodon.org/api/guidelines/#focal-points) +/// and [here](https://docs.joinmastodon.org/entities/MediaAttachment/#meta) in +/// the API documentation. +#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq)] +pub struct FocalPoint { + /// The point on the horizontal plane which should remain in focus. + pub x: f64, + /// The point on the vertical plane which should remain in focus. + pub y: f64, +} + /// A media attachment which has been processed and has a URL. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] pub struct ProcessedAttachment { @@ -87,16 +149,178 @@ pub struct ProcessedAttachment { #[serde(rename = "type")] pub media_type: MediaType, /// URL of the locally hosted version of the image. - pub url: String, + pub url: Url, /// For remote images, the remote URL of the original image. - pub remote_url: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_url: Option<Url>, /// URL of the preview image. - pub preview_url: String, + pub preview_url: Url, /// Shorter URL for the image, for insertion into text /// (only present on local images) - pub text_url: Option<String>, + #[serde(skip_serializing_if = "Option::is_none")] + pub text_url: Option<Url>, /// Meta information about the attachment. + #[serde(skip_serializing_if = "Option::is_none")] pub meta: Option<Meta>, /// Noop will be removed. + #[serde(skip_serializing_if = "Option::is_none")] pub description: Option<String>, } + +#[cfg(test)] +mod tests { + use serde_json::Value; + + use super::*; + + #[test] + fn test_deserialize_with_image() { + let example = r##"{ + "id": "22345792", + "type": "image", + "url": "https://files.mastodon.social/media_attachments/files/022/345/792/original/57859aede991da25.jpeg", + "preview_url": "https://files.mastodon.social/media_attachments/files/022/345/792/small/57859aede991da25.jpeg", + "remote_url": null, + "text_url": "https://mastodon.social/media/2N4uvkuUtPVrkZGysms", + "meta": { + "original": { + "width": 640, + "height": 480, + "size": "640x480", + "aspect": 1.3333333333333333 + }, + "small": { + "width": 461, + "height": 346, + "size": "461x346", + "aspect": 1.3323699421965318 + }, + "focus": { + "x": -0.27, + "y": 0.51 + } + }, + "description": "test media description", + "blurhash": "UFBWY:8_0Jxv4mx]t8t64.%M-:IUWGWAt6M}" +}"##; + let example = example.replace(char::is_whitespace, ""); + let subject: Attachment = serde_json::from_str(&example).expect("deserialize"); + assert_eq!( + serde_json::to_value(subject).expect("convert to value"), + serde_json::from_str::<Value>(&example).expect("deserialize (to Value)") + ) + } + #[test] + fn test_deserialize_with_audio() { + let example = r##"{ + "id": "21165404", + "type": "audio", + "url": "https://files.mastodon.social/media_attachments/files/021/165/404/original/a31a4a46cd713cd2.mp3", + "preview_url": "https://files.mastodon.social/media_attachments/files/021/165/404/small/a31a4a46cd713cd2.mp3", + "remote_url": null, + "text_url": "https://mastodon.social/media/5O4uILClVqBWx0NNgvo", + "meta": { + "length": "0:06:42.86", + "duration": 402.86, + "audio_encode": "mp3", + "audio_bitrate": "44100 Hz", + "audio_channels": "stereo", + "original": { + "duration": 402.860408, + "bitrate": 166290 + } + }, + "description": null, + "blurhash": null +}"##; + let subject: Attachment = serde_json::from_str(example).expect("deserialize"); + assert_eq!( + serde_json::to_value(subject).expect("convert to value"), + serde_json::from_str::<Value>(example).expect("deserialize (to Value)") + ) + } + #[test] + fn test_deserialize_with_video() { + let example = r##"{ + "id": "22546306", + "type": "video", + "url": "https://files.mastodon.social/media_attachments/files/022/546/306/original/dab9a597f68b9745.mp4", + "preview_url": "https://files.mastodon.social/media_attachments/files/022/546/306/small/dab9a597f68b9745.png", + "remote_url": null, + "text_url": "https://mastodon.social/media/wWd1HJIBmH1MZGDfg50", + "meta": { + "length": "0:01:28.65", + "duration": 88.65, + "fps": 24, + "size": "1280x720", + "width": 1280, + "height": 720, + "aspect": 1.7777777777777777, + "audio_encode": "aac (LC) (mp4a / 0x6134706D)", + "audio_bitrate": "44100 Hz", + "audio_channels": "stereo", + "original": { + "width": 1280, + "height": 720, + "frame_rate": "6159375/249269", + "duration": 88.654, + "bitrate": 862056 + }, + "small": { + "width": 400, + "height": 225, + "size": "400x225", + "aspect": 1.7777777777777777 + } + }, + "description": null, + "blurhash": "U58E0g8_0M.94T?bIr00?bD%NGoM?bD%oLt7" +}"##; + let subject: Attachment = serde_json::from_str(example).expect("deserialize"); + assert_eq!( + serde_json::to_value(subject).expect("convert to value"), + serde_json::from_str::<Value>(&example).expect("deserialize (to Value)") + ) + } + #[test] + fn test_deserialize_with_gifv() { + let example = r##"{ + "id": "21130559", + "type": "gifv", + "url": "https://files.mastodon.social/media_attachments/files/021/130/559/original/bc84838f77991326.mp4", + "preview_url": "https://files.mastodon.social/media_attachments/files/021/130/559/small/bc84838f77991326.png", + "remote_url": null, + "text_url": "https://mastodon.social/media/2ICiasGyjezmi7cQYM8", + "meta": { + "length": "0:00:01.11", + "duration": 1.11, + "fps": 33, + "size": "600x332", + "width": 600, + "height": 332, + "aspect": 1.8072289156626506, + "original": { + "width": 600, + "height": 332, + "frame_rate": "100/3", + "duration": 1.11, + "bitrate": 1627639 + }, + "small": { + "width": 400, + "height": 221, + "size": "400x221", + "aspect": 1.8099547511312217 + } + }, + "description": null, + "blurhash": "URHT%Jm,2a1d%MRO%LozkrNH$*n*oMn$Rjt7" +} +"##; + let subject: Attachment = serde_json::from_str(example).expect("deserialize"); + assert_eq!( + serde_json::to_value(subject).expect("convert to value"), + serde_json::from_str::<Value>(&example).expect("deserialize (to Value)") + ) + } +} |