Skip to main content

doobs_mpris/types/
metadata.rs

1// SPDX-License-Identifier: MPL-2.0
2use std::collections::HashMap;
3use std::fmt::{self, Debug, Display};
4use std::ops::{Deref, DerefMut};
5
6use jiff::{SignedDuration, Timestamp};
7use serde::{Deserialize, Serialize};
8use zvariant::{OwnedValue, Type, Value};
9
10use crate::types::TrackId;
11use crate::{Error, Result};
12
13// list of metadata properties, see: https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata/
14
15// MPRIS-specific properties
16
17/// D-Bus path: A unique identity for this track within the context of an MPRIS object (eg: tracklist).
18const FIELD_TRACK_ID: &str = "mpris:trackid";
19/// 64-bit integer: The duration of the track in microseconds.
20const FIELD_LENGTH: &str = "mpris:length";
21/// URI: The location of an image representing the track or album.
22/// Clients should not assume this will continue to exist when the media player stops giving out the URL.
23const FIELD_ART_URL: &str = "mpris:artUrl";
24
25// Common Xesam properties
26
27/// String: The album name.
28const FIELD_ALBUM: &str = "xesam:album";
29/// List of Strings: The album artist(s).
30const FIELD_ALBUM_ARTIST: &str = "xesam:albumArtist";
31/// List of Strings: The track artist(s).
32const FIELD_ARTIST: &str = "xesam:artist";
33/// String: The track lyrics.
34const FIELD_AS_TEXT: &str = "xesam:asText";
35/// Integer: The speed of the music, in beats per minute.
36const FIELD_AUDIO_BPM: &str = "xesam:audioBPM";
37/// Float: An automatically-generated rating, based on things such as how often it has been played.
38/// This should be in the range 0.0 to 1.0.
39const FIELD_AUTO_RATING: &str = "xesam:autoRating";
40/// List of Strings: A (list of) freeform comment(s).
41const FIELD_COMMENT: &str = "xesam:comment";
42/// List of Strings: The composer(s) of the track.
43const FIELD_COMPOSER: &str = "xesam:composer";
44/// Date/Time: When the track was created. Usually only the year component will be useful.
45const FIELD_CONTENT_CREATED: &str = "xesam:contentCreated";
46/// Integer: The disc number on the album that this track is from.
47const FIELD_DISC_NUMBER: &str = "xesam:discNumber";
48/// Date/Time: When the track was first played.
49const FIELD_FIRST_USED: &str = "xesam:firstUsed";
50/// List of Strings: The genre(s) of the track.
51const FIELD_GENRE: &str = "xesam:genre";
52/// Date/Time: When the track was last played.
53const FIELD_LAST_USED: &str = "xesam:lastUsed";
54/// List of Strings: The lyricist(s) of the track.
55const FIELD_LYRICIST: &str = "xesam:lyricist";
56/// String: The track title.
57const FIELD_TITLE: &str = "xesam:title";
58/// Integer: The track number on the album disc.
59const FIELD_TRACK_NUMBER: &str = "xesam:trackNumber";
60/// URI: The location of the media file.
61const FIELD_URL: &str = "xesam:url";
62/// Integer: The number of times the track has been played.
63const FIELD_USE_COUNT: &str = "xesam:useCount";
64/// Float: A user-specified rating. This should be in the range 0.0 to 1.0.
65const FIELD_USER_RATING: &str = "xesam:userRating";
66
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68#[serde(transparent)]
69pub struct Metadata {
70    inner: HashMap<String, OwnedValue>,
71}
72
73impl Metadata {
74    pub fn new(track_id: TrackId) -> Self {
75        let mut metadata = Self {
76            inner: HashMap::new(),
77        };
78        metadata.insert(FIELD_TRACK_ID.to_string(), track_id.into());
79        metadata
80    }
81
82    /// `xesam:album`: The track artist(s).
83    pub fn album(&self) -> Option<String> {
84        self.inner
85            .get(FIELD_ALBUM)
86            .cloned()
87            .and_then(|v| v.try_into().ok())
88    }
89
90    /// `xesam:artist`: The track artist(s).
91    pub fn artists(&self) -> Option<Vec<String>> {
92        self.inner
93            .get(FIELD_ARTIST)
94            .cloned()
95            .and_then(|artists| artists.try_into().ok())
96    }
97
98    /// `xesam:asText`: The track lyrics.
99    pub fn lyrics(&self) -> Option<String> {
100        self.inner
101            .get(FIELD_AS_TEXT)
102            .cloned()
103            .and_then(|v| v.try_into().ok())
104    }
105
106    /// `xesam:albumArtist`: The album artist(s).
107    pub fn album_artists(&self) -> Option<Vec<String>> {
108        self.inner
109            .get(FIELD_ALBUM_ARTIST)
110            .cloned()
111            .and_then(|artists| artists.try_into().ok())
112    }
113
114    /// `xesam:audioBPM`: The speed of the music, in beats per minute.
115    pub fn bpm(&self) -> Option<u32> {
116        self.inner
117            .get(FIELD_AUDIO_BPM)
118            .cloned()
119            .and_then(|v| try_value_into_integer(v).ok())
120    }
121
122    /// `xesam:autoRating`: An automatically-generated rating, based on things such as how often it has been played.
123    /// This should be in the range 0.0 to 1.0.
124    pub fn auto_rating(&self) -> Option<f64> {
125        self.inner
126            .get(FIELD_AUTO_RATING)
127            .cloned()
128            .and_then(|v| v.try_into().ok())
129    }
130
131    /// `xesam:comment`: A (list of) freeform comment(s).
132    pub fn comments(&self) -> Option<Vec<String>> {
133        self.inner
134            .get(FIELD_COMMENT)
135            .cloned()
136            .and_then(|v| v.try_into().ok())
137    }
138
139    /// `xesam:composer`: The composer(s) of the track.
140    pub fn composers(&self) -> Option<Vec<String>> {
141        self.inner
142            .get(FIELD_COMPOSER)
143            .cloned()
144            .and_then(|composers| composers.try_into().ok())
145    }
146
147    /// `xesam:contentCreated`: When the track was created. Usually only the year component will be useful.
148    pub fn created(&self) -> Option<Timestamp> {
149        self.inner
150            .get(FIELD_CONTENT_CREATED)
151            .cloned()
152            .and_then(|v| try_value_into_date(v).ok())
153    }
154
155    /// `xesam:discNumber`: The disc number on the album that this track is from.
156    pub fn disc_number(&self) -> Option<u32> {
157        self.inner
158            .get(FIELD_DISC_NUMBER)
159            .cloned()
160            .and_then(|v| try_value_into_integer(v).ok())
161    }
162
163    /// `xesam:firstUsed`: When the track was first played.
164    pub fn first_played(&self) -> Option<Timestamp> {
165        self.inner
166            .get(FIELD_FIRST_USED)
167            .cloned()
168            .and_then(|v| try_value_into_date(v).ok())
169    }
170
171    /// `xesam:genre`: The genre(s) of the track.
172    pub fn genres(&self) -> Option<Vec<String>> {
173        self.inner
174            .get(FIELD_GENRE)
175            .cloned()
176            .and_then(|genres| genres.try_into().ok())
177    }
178
179    /// `xesam:lastUsed`: When the track was last played.
180    pub fn last_played(&self) -> Option<Timestamp> {
181        self.inner
182            .get(FIELD_LAST_USED)
183            .cloned()
184            .and_then(|v| try_value_into_date(v).ok())
185    }
186
187    /// `xesam:lyricist`: The lyricist(s) of the track.
188    pub fn lyricists(&self) -> Option<Vec<String>> {
189        self.inner
190            .get(FIELD_LYRICIST)
191            .cloned()
192            .and_then(|lyricists| lyricists.try_into().ok())
193    }
194
195    /// `xesam:title`: The track title.
196    pub fn title(&self) -> Option<String> {
197        self.inner
198            .get(FIELD_TITLE)
199            .cloned()
200            .and_then(|v| v.try_into().ok())
201    }
202
203    /// `xesam:trackNumber`: The track number on the album that this track is from.
204    pub fn track_number(&self) -> Option<u32> {
205        self.inner
206            .get(FIELD_TRACK_NUMBER)
207            .cloned()
208            .and_then(|v| try_value_into_integer(v).ok())
209    }
210
211    /// `xesam:url`: The location of the media file.
212    pub fn url(&self) -> Option<String> {
213        self.inner
214            .get(FIELD_URL)
215            .cloned()
216            .and_then(|v| v.try_into().ok())
217    }
218
219    /// `xesam:useCount`: The number of times the track has been played.
220    pub fn play_count(&self) -> Option<u32> {
221        self.inner
222            .get(FIELD_USE_COUNT)
223            .cloned()
224            .and_then(|v| try_value_into_integer(v).ok())
225    }
226
227    /// `xesam:userRating`: The user's rating of the track.
228    pub fn user_rating(&self) -> Option<f64> {
229        self.inner
230            .get(FIELD_USER_RATING)
231            .cloned()
232            .and_then(|v| v.try_into().ok())
233    }
234
235    /// `mpris:trackid`: D-Bus path: A unique identity for this track within the context of an MPRIS object (eg: tracklist).
236    pub fn track_id(&self) -> Option<TrackId> {
237        self.inner
238            .get(FIELD_TRACK_ID)
239            .cloned()
240            .and_then(|path| TrackId::try_from(path).ok())
241    }
242
243    /// `mpris:length`: The length of the track in microseconds.
244    pub fn length(&self) -> Option<SignedDuration> {
245        self.inner
246            .get(FIELD_LENGTH)
247            .cloned()
248            .and_then(|v| i64::try_from(v).ok())
249            .map(SignedDuration::from_micros)
250    }
251
252    /// `mpris:artUrl`: The location of an image representing the track or album.
253    /// Clients should not assume this will continue to exist when the media player stops giving out the URL.
254    pub fn art_url(&self) -> Option<String> {
255        self.inner
256            .get(FIELD_ART_URL)
257            .cloned()
258            .and_then(|v| v.try_into().ok())
259    }
260}
261
262impl Deref for Metadata {
263    type Target = HashMap<String, OwnedValue>;
264
265    fn deref(&self) -> &Self::Target {
266        &self.inner
267    }
268}
269
270impl DerefMut for Metadata {
271    fn deref_mut(&mut self) -> &mut Self::Target {
272        &mut self.inner
273    }
274}
275
276impl Display for Metadata {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        write!(f, "{{")?;
279        let mut iter = self.inner.iter().peekable();
280        while let Some((k, v)) = iter.next() {
281            write!(f, "{k}: ")?;
282            write_zvalue(f, v)?;
283            if iter.peek().is_some() {
284                write!(f, ", ")?;
285            }
286        }
287        write!(f, "}}")
288    }
289}
290
291impl From<HashMap<String, OwnedValue>> for Metadata {
292    fn from(value: HashMap<String, OwnedValue>) -> Self {
293        Self { inner: value }
294    }
295}
296
297// traits necessary to use it as a type in zbus
298
299impl Type for Metadata {
300    const SIGNATURE: &'static zvariant::Signature = &zvariant::signature!("a{sv}");
301}
302
303impl From<Metadata> for Value<'_> {
304    fn from(value: Metadata) -> Self {
305        value.inner.into()
306    }
307}
308
309impl TryFrom<OwnedValue> for Metadata {
310    type Error = zvariant::Error;
311
312    fn try_from(value: OwnedValue) -> std::result::Result<Self, Self::Error> {
313        Ok(Self {
314            inner: value.clone().try_into()?,
315        })
316    }
317}
318
319/// Tries to convert the given value into an Integer.
320pub fn try_value_into_integer(value: OwnedValue) -> Result<u32> {
321    // There is no clear consensus what an "Integer" in the Xesam properties is.
322    // But most projects seemt to use i32 (`i`).
323    // This also aligns with the `track_length` being differentiated as a "64-bit integer".
324    // For maximum compatiblity we parse all possible integer types into a u32.
325    //
326    // A u32 (or rather u31, if we ignore the signed part) is big enough to hold all sensible values.
327    //
328    // * `bpm`/`track_number`/`disc_number` are comparitively small.
329    // * `use_count` could outgrow a u16 if someone listens to a track *a lot*, but not a u32.
330    match *value {
331        Value::U8(v) => Ok(v as u32),
332        Value::Bool(v) => Ok(v as u32),
333        Value::I16(v) => Ok(v as u32),
334        Value::U16(v) => Ok(v as u32),
335        Value::I32(v) => Ok(v as u32),
336        Value::U32(v) => Ok(v),
337        Value::I64(v) => Ok(v as u32),
338        Value::U64(v) => Ok(v as u32),
339        _ => Err(Error::IncorrectValue {
340            wanted: "Integer",
341            actual: value,
342        }),
343    }
344}
345
346pub fn try_value_into_date(value: OwnedValue) -> Result<Timestamp> {
347    let value: String = value
348        .clone()
349        .try_into()
350        .map_err(|_| Error::IncorrectValue {
351            wanted: "Str",
352            actual: value,
353        })?;
354    let value = value
355        .parse::<Timestamp>()
356        .map_err(Error::InvalidTimestamp)?;
357    Ok(value)
358}
359
360fn write_zvalue(f: &mut fmt::Formatter<'_>, value: &Value) -> fmt::Result {
361    match value {
362        Value::U8(b) => write!(f, "{b}"),
363        Value::Bool(b) => write!(f, "{b}"),
364        Value::I16(i) => write!(f, "{i}"),
365        Value::U16(u) => write!(f, "{u}"),
366        Value::I32(i) => write!(f, "{i}"),
367        Value::U32(u) => write!(f, "{u}"),
368        Value::I64(i) => write!(f, "{i}"),
369        Value::U64(u) => write!(f, "{u}"),
370        Value::F64(d) => write!(f, "{d}"),
371        Value::Str(s) => write!(f, "\"{s}\""),
372        Value::ObjectPath(p) => write!(f, "\"{p}\""),
373        Value::Array(a) => {
374            write!(f, "[")?;
375            let mut iter = a.iter().peekable();
376            while let Some(value) = iter.next() {
377                if iter.peek().is_some() {
378                    write!(f, "{value}, ")?;
379                } else {
380                    write!(f, "{value}")?;
381                }
382            }
383            write!(f, "]")
384        }
385        Value::Dict(d) => {
386            write!(f, "{{")?;
387            let mut iter = d.iter().peekable();
388            while let Some((k, v)) = iter.next() {
389                if iter.peek().is_some() {
390                    write!(f, "{k}: {v}, ")?;
391                } else {
392                    write!(f, "{k}: {v}")?;
393                }
394            }
395            write!(f, "}}")
396        }
397        Value::Value(value) => write_zvalue(f, value),
398        _ => write!(f, "{value:?}"),
399    }
400}
401
402#[cfg(test)]
403mod test {
404    use std::collections::HashMap;
405    use std::str::FromStr;
406
407    use jiff::SignedDuration;
408    use zvariant::serialized::Context;
409    use zvariant::{
410        Array, Dict, LE, ObjectPath, OwnedValue, Signature, Value, signature, to_bytes,
411        to_bytes_for_signature,
412    };
413
414    use super::*;
415
416    #[test]
417    fn success() {
418        let mut expected: Metadata = HashMap::new().into();
419        expected.insert(
420            FIELD_TRACK_ID.to_string(),
421            ObjectPath::try_from("/hii").unwrap().into(),
422        );
423
424        let mut v = Dict::new(&Signature::Str, &Signature::Variant);
425        v.add(
426            FIELD_TRACK_ID,
427            Value::ObjectPath("/hii".try_into().unwrap()),
428        )
429        .unwrap();
430        let v = Value::Dict(v);
431        let ov = OwnedValue::try_from(v.clone()).unwrap();
432        let metadata = Metadata::try_from(ov).unwrap();
433
434        assert_eq!(metadata, expected);
435        assert_eq!(v, Value::from(expected));
436    }
437
438    #[test]
439    fn wrong_dbus_type() {
440        let v = Value::U64(5);
441        let ov = OwnedValue::try_from(v).unwrap();
442        let err = Metadata::try_from(ov).unwrap_err();
443
444        assert_eq!(zvariant::Error::IncorrectType, err);
445    }
446
447    #[test]
448    fn new() {
449        let op = TrackId::try_from("/hii").unwrap();
450        let metadata = Metadata::new(op);
451        assert_eq!(
452            metadata.track_id(),
453            Some(TrackId::try_from("/hii").unwrap())
454        );
455    }
456
457    #[test]
458    fn get_track_id() {
459        let metadata = new_metadata(FIELD_TRACK_ID, ObjectPath::try_from("/hii").unwrap());
460        assert_eq!(
461            metadata.track_id(),
462            Some(TrackId::try_from("/hii").unwrap())
463        );
464    }
465
466    #[test]
467    fn get_length() {
468        let metadata = new_metadata(FIELD_LENGTH, Value::I64(42 * 1000 * 1000));
469        assert_eq!(metadata.length(), Some(SignedDuration::from_secs(42)));
470    }
471
472    #[test]
473    fn get_art_url() {
474        let metadata = new_metadata(FIELD_ART_URL, Value::Str("https://my/cool/art".into()));
475        assert_eq!(metadata.art_url(), Some("https://my/cool/art".to_string()));
476    }
477
478    #[test]
479    fn get_album() {
480        let metadata = new_metadata(FIELD_ALBUM, Value::Str("The Album".into()));
481        assert_eq!(metadata.album(), Some("The Album".to_string()));
482    }
483
484    #[test]
485    fn get_album_artist() {
486        let mut arr = Array::new(&Signature::Str);
487        arr.append(Value::Str("Foo Artist".into())).unwrap();
488        arr.append(Value::Str("Bar Artist".into())).unwrap();
489        let metadata = new_metadata(FIELD_ALBUM_ARTIST, arr);
490        assert_eq!(
491            metadata.album_artists(),
492            Some(vec!["Foo Artist".to_string(), "Bar Artist".to_string()])
493        );
494    }
495
496    #[test]
497    fn get_artist() {
498        let mut arr = Array::new(&Signature::Str);
499        arr.append(Value::Str("Foo Artist".into())).unwrap();
500        arr.append(Value::Str("Bar Artist".into())).unwrap();
501        let metadata = new_metadata(FIELD_ARTIST, arr);
502        assert_eq!(
503            metadata.artists(),
504            Some(vec!["Foo Artist".to_string(), "Bar Artist".to_string()])
505        );
506    }
507
508    #[test]
509    fn get_lyrics() {
510        let metadata = new_metadata(
511            FIELD_AS_TEXT,
512            Value::Str("If I can live through this, I can do anything".into()),
513        );
514        assert_eq!(
515            metadata.lyrics(),
516            Some("If I can live through this, I can do anything".to_string())
517        );
518    }
519
520    #[test]
521    fn get_bpm() {
522        let metadata = new_metadata(FIELD_AUDIO_BPM, Value::I32(120));
523        assert_eq!(metadata.bpm(), Some(120));
524    }
525
526    #[test]
527    fn get_auto_rating() {
528        let metadata = new_metadata(FIELD_AUTO_RATING, Value::F64(0.7));
529        assert_eq!(metadata.auto_rating(), Some(0.7));
530    }
531
532    #[test]
533    fn get_comments() {
534        let mut arr = Array::new(&Signature::Str);
535        arr.append(Value::Str("Some comment".into())).unwrap();
536        arr.append(Value::Str("Another comment".into())).unwrap();
537        let metadata = new_metadata(FIELD_COMMENT, arr);
538        assert_eq!(
539            metadata.comments(),
540            Some(vec![
541                "Some comment".to_string(),
542                "Another comment".to_string()
543            ])
544        );
545    }
546
547    #[test]
548    fn get_composers() {
549        let mut arr = Array::new(&Signature::Str);
550        arr.append(Value::Str("Chopin".into())).unwrap();
551        arr.append(Value::Str("Franchomme".into())).unwrap();
552        let metadata = new_metadata(FIELD_COMPOSER, arr);
553        assert_eq!(
554            metadata.composers(),
555            Some(vec!["Chopin".to_string(), "Franchomme".to_string()])
556        );
557    }
558
559    #[test]
560    fn get_created() {
561        let metadata = new_metadata(
562            FIELD_CONTENT_CREATED,
563            Value::Str("2007-04-29T13:56+01:00".into()),
564        );
565        assert_eq!(
566            metadata.created(),
567            Some(Timestamp::from_str("2007-04-29T13:56+01:00").unwrap())
568        );
569    }
570
571    #[test]
572    fn get_disc_number() {
573        let metadata = new_metadata(FIELD_DISC_NUMBER, Value::I32(2));
574        assert_eq!(metadata.disc_number(), Some(2));
575    }
576
577    #[test]
578    fn get_first_played() {
579        let metadata = new_metadata(
580            FIELD_FIRST_USED,
581            Value::Str("2007-04-29T13:56+01:00".into()),
582        );
583        assert_eq!(
584            metadata.first_played(),
585            Some(Timestamp::from_str("2007-04-29T13:56+01:00").unwrap())
586        );
587    }
588
589    #[test]
590    fn get_genres() {
591        let mut arr = Array::new(&Signature::Str);
592        arr.append(Value::Str("Pop".into())).unwrap();
593        arr.append(Value::Str("Rock".into())).unwrap();
594        let metadata = new_metadata(FIELD_GENRE, arr);
595        assert_eq!(
596            metadata.genres(),
597            Some(vec!["Pop".to_string(), "Rock".to_string()])
598        );
599    }
600
601    #[test]
602    fn get_last_played() {
603        let metadata = new_metadata(FIELD_LAST_USED, Value::Str("2007-04-29T13:56+01:00".into()));
604        assert_eq!(
605            metadata.last_played(),
606            Some(Timestamp::from_str("2007-04-29T13:56+01:00").unwrap())
607        );
608    }
609
610    #[test]
611    fn get_lyricists() {
612        let mut arr = Array::new(&Signature::Str);
613        arr.append(Value::Str("Frog in a Car".into())).unwrap();
614        arr.append(Value::Str("Potato Chip".into())).unwrap();
615        let metadata = new_metadata(FIELD_LYRICIST, arr);
616        assert_eq!(
617            metadata.lyricists(),
618            Some(vec!["Frog in a Car".to_string(), "Potato Chip".to_string()])
619        );
620    }
621
622    #[test]
623    fn get_title() {
624        let metadata = new_metadata(FIELD_TITLE, Value::Str("The Grid".into()));
625        assert_eq!(metadata.title(), Some("The Grid".to_string()));
626    }
627
628    #[test]
629    fn get_track_number() {
630        let metadata = new_metadata(FIELD_TRACK_NUMBER, Value::I32(7));
631        assert_eq!(metadata.track_number(), Some(7));
632    }
633
634    #[test]
635    fn get_url() {
636        let metadata = new_metadata(FIELD_URL, Value::Str("https://the/best/song/ever".into()));
637        assert_eq!(
638            metadata.url(),
639            Some("https://the/best/song/ever".to_string())
640        );
641    }
642
643    #[test]
644    fn get_play_count() {
645        let metadata = new_metadata(FIELD_USE_COUNT, Value::I32(123));
646        assert_eq!(metadata.play_count(), Some(123));
647    }
648
649    #[test]
650    fn get_user_rating() {
651        let metadata = new_metadata(FIELD_USER_RATING, Value::F64(0.9));
652        assert_eq!(metadata.user_rating(), Some(0.9));
653    }
654
655    #[test]
656    fn serialize() {
657        let ctxt = Context::new_dbus(LE, 0);
658        let encoded = to_bytes(ctxt, &new_metadata(FIELD_TRACK_NUMBER, 7)).unwrap();
659        let decoded: HashMap<String, OwnedValue> = encoded.deserialize().unwrap().0;
660
661        assert_eq!(
662            decoded.get(FIELD_TRACK_NUMBER).unwrap().deref(),
663            &Value::from(7)
664        );
665    }
666
667    #[test]
668    fn deserialize() {
669        let mut hashmap: HashMap<String, OwnedValue> = HashMap::new();
670        hashmap.insert(FIELD_TRACK_NUMBER.to_string(), OwnedValue::from(7i32));
671
672        let ctxt = Context::new_dbus(LE, 0);
673        let encoded = to_bytes_for_signature(ctxt, signature!("a{sv}"), &hashmap).unwrap();
674        let decoded: Metadata = encoded.deserialize().unwrap().0;
675
676        assert_eq!(
677            decoded.get(FIELD_TRACK_NUMBER).unwrap().deref(),
678            &Value::from(7)
679        );
680    }
681
682    fn new_metadata<'a, K, V>(key: K, value: V) -> Metadata
683    where
684        K: Into<String>,
685        V: Into<Value<'a>>,
686    {
687        let mut metadata: Metadata = HashMap::new().into();
688        metadata.insert(key.into(), value.into().try_into().unwrap());
689        metadata
690    }
691}