Add De/Serialization for most bitflag types

Represents combinations of flags with a '+' separated string of nicks,
or an empty string for no flags set.

Note that most flag types will ignore any flags using multiple bits when
serializing, since in most cases these flags cover all used bits.
This commit is contained in:
Vivienne Watermeier 2022-05-05 12:40:07 +02:00 committed by Sebastian Dröge
parent 94b5a13f4b
commit 1b22be2e15
34 changed files with 1631 additions and 158 deletions

View file

@ -221,11 +221,14 @@ plugins-update-nightly:
- |
get_features() {
crate=$1
if [ "$crate" = "gstreamer" ]; then
echo "--features=ser_de,v1_22"
else
echo "--features=v1_22"
fi
case "$crate" in
gstreamer-audio|gstreamer-editing-services|gstreamer-gl|gstreamer-pbutils|gstreamer-rtp|gstreamer-rtsp|gstreamer-video|gstreamer)
echo "--features=ser_de,v1_22"
;;
*)
echo "--features=v1_22"
;;
esac
}
for crate in gstreamer* gstreamer-gl/{egl,wayland,x11}; do
if [ -e $crate/Cargo.toml ]; then
@ -368,11 +371,14 @@ clippy:
- |
get_features() {
crate=$1
if [ "$crate" = "gstreamer" ]; then
echo "--features=ser_de,v1_22"
else
echo "--features=v1_22"
fi
case "$crate" in
gstreamer-audio|gstreamer-editing-services|gstreamer-gl|gstreamer-pbutils|gstreamer-rtp|gstreamer-rtsp|gstreamer-video|gstreamer)
echo "--features=ser_de,v1_22"
;;
*)
echo "--features=v1_22"
;;
esac
}
for crate in gstreamer* gstreamer-gl/{egl,wayland,x11}; do
if [ -e $crate/Cargo.toml ]; then

View file

@ -23,9 +23,11 @@ gst = { package = "gstreamer", path = "../gstreamer" }
gst-base = { package = "gstreamer-base", path = "../gstreamer-base" }
array-init = "2.0"
once_cell = "1.0"
serde = { version = "1.0", optional = true }
[dev-dependencies]
itertools = "0.10"
serde_json = "1.0"
gir-format-check = "0.1"
[features]
@ -34,7 +36,8 @@ v1_16 = ["gst/v1_16", "gst-base/v1_16", "ffi/v1_16"]
v1_18 = ["gst/v1_18", "gst-base/v1_18", "ffi/v1_18", "v1_16"]
v1_20 = ["gst/v1_20", "gst-base/v1_20", "ffi/v1_20", "v1_18"]
v1_22 = ["gst/v1_22", "gst-base/v1_22", "ffi/v1_22", "v1_20"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox", "gst-base/dox"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox", "gst-base/dox", "ser_de"]
ser_de = ["serde", "gst/ser_de"]
[package.metadata.docs.rs]
features = ["dox"]

View file

@ -0,0 +1,76 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use glib::translate::{from_glib, ToGlibPtr};
use glib::{FlagsClass, StaticType, ToValue};
use gst::bitflags_serde_impl;
bitflags_serde_impl!(crate::AudioFlags);
bitflags_serde_impl!(crate::AudioFormatFlags);
bitflags_serde_impl!(crate::AudioPackFlags);
#[cfg(test)]
mod tests {
macro_rules! check_serialize {
($flags:expr, $expected:expr) => {
let actual = serde_json::to_string(&$flags).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_deserialize {
($ty:ty, $expected:expr, $json:expr) => {
let actual: $ty = serde_json::from_str(&$json).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_roundtrip {
($ty:ty, $flags:expr) => {
let json = serde_json::to_string(&$flags).unwrap();
let deserialized: $ty = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, $flags);
};
}
#[test]
fn test_serialize() {
gst::init().unwrap();
check_serialize!(crate::AudioFlags::all(), "\"unpositioned\"");
check_serialize!(
crate::AudioFormatFlags::all(),
"\"integer+float+signed+complex+unpack\""
);
check_serialize!(crate::AudioPackFlags::all(), "\"truncate-range\"");
}
#[test]
fn test_deserialize() {
gst::init().unwrap();
check_deserialize!(
crate::AudioFlags,
crate::AudioFlags::all(),
"\"unpositioned\""
);
check_deserialize!(
crate::AudioFormatFlags,
crate::AudioFormatFlags::all(),
"\"integer+float+signed+complex+unpack\""
);
check_deserialize!(
crate::AudioPackFlags,
crate::AudioPackFlags::all(),
"\"truncate-range\""
);
}
#[test]
fn test_serde_roundtrip() {
gst::init().unwrap();
check_roundtrip!(crate::AudioFlags, crate::AudioFlags::all());
check_roundtrip!(crate::AudioFormatFlags, crate::AudioFormatFlags::all());
check_roundtrip!(crate::AudioPackFlags, crate::AudioPackFlags::all());
}
}

View file

@ -31,6 +31,9 @@ macro_rules! skip_assert_initialized {
mod auto;
pub use crate::auto::*;
#[cfg(feature = "ser_de")]
mod flag_serde;
mod audio_format;
pub use crate::audio_format::*;
mod audio_format_info;

View file

@ -22,16 +22,19 @@ gio = { git = "https://github.com/gtk-rs/gtk-rs-core" }
gst = { package = "gstreamer", path = "../gstreamer" }
gst-base = { package = "gstreamer-base", path = "../gstreamer-base" }
gst-pbutils = { package = "gstreamer-pbutils", path = "../gstreamer-pbutils" }
serde = { version = "1.0", optional = true }
[dev-dependencies]
gir-format-check = "0.1"
serde_json = "1.0"
[features]
v1_16 = ["gst/v1_16", "gst-base/v1_16", "gst-pbutils/v1_16", "ffi/v1_16"]
v1_18 = ["gst/v1_18", "gst-base/v1_18", "gst-pbutils/v1_18", "ffi/v1_18", "v1_16"]
v1_20 = ["gst/v1_20", "gst-base/v1_20", "gst-pbutils/v1_20", "ffi/v1_20", "v1_18"]
v1_22 = ["gst/v1_22", "gst-base/v1_22", "gst-pbutils/v1_22", "ffi/v1_22", "v1_20"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gio/dox", "gst/dox", "gst-base/dox", "gst-pbutils/dox"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gio/dox", "gst/dox", "gst-base/dox", "gst-pbutils/dox", "ser_de"]
ser_de = ["serde", "gst/ser_de", "gst-pbutils/ser_de"]
[package.metadata.docs.rs]
features = ["dox"]

View file

@ -0,0 +1,90 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use glib::translate::{from_glib, ToGlibPtr};
use glib::{FlagsClass, StaticType, ToValue};
use gst::bitflags_serde_impl;
bitflags_serde_impl!(crate::MarkerFlags, "v1_20");
bitflags_serde_impl!(crate::MetaFlag);
bitflags_serde_impl!(crate::PipelineFlags);
bitflags_serde_impl!(crate::TrackType);
#[cfg(test)]
mod tests {
macro_rules! check_serialize {
($flags:expr, $expected:expr) => {
let actual = serde_json::to_string(&$flags).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_deserialize {
($ty:ty, $expected:expr, $json:expr) => {
let actual: $ty = serde_json::from_str(&$json).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_roundtrip {
($ty:ty, $flags:expr) => {
let json = serde_json::to_string(&$flags).unwrap();
let deserialized: $ty = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, $flags);
};
}
#[test]
fn test_serialize() {
gst::init().unwrap();
#[cfg(feature = "v1_20")]
check_serialize!(crate::MarkerFlags::all(), "\"snappable\"");
check_serialize!(crate::MetaFlag::all(), "\"readable+writable\"");
check_serialize!(
crate::PipelineFlags::all(),
"\"audio_preview+video_preview+render+smart_render\""
);
check_serialize!(
crate::TrackType::all(),
"\"unknown+audio+video+text+custom\""
);
}
#[test]
fn test_deserialize() {
gst::init().unwrap();
#[cfg(feature = "v1_20")]
check_deserialize!(
crate::MarkerFlags,
crate::MarkerFlags::all(),
"\"snappable\""
);
check_deserialize!(
crate::MetaFlag,
crate::MetaFlag::all(),
"\"readable+writable\""
);
check_deserialize!(
crate::PipelineFlags,
crate::PipelineFlags::all(),
"\"audio_preview+video_preview+render+smart_render\""
);
check_deserialize!(
crate::TrackType,
crate::TrackType::all(),
"\"unknown+audio+video+text+custom\""
);
}
#[test]
fn test_serde_roundtrip() {
gst::init().unwrap();
#[cfg(feature = "v1_20")]
check_roundtrip!(crate::MarkerFlags, crate::MarkerFlags::all());
check_roundtrip!(crate::MetaFlag, crate::MetaFlag::all());
check_roundtrip!(crate::PipelineFlags, crate::PipelineFlags::all());
check_roundtrip!(crate::TrackType, crate::TrackType::all());
}
}

View file

@ -62,6 +62,9 @@ macro_rules! skip_assert_initialized {
mod auto;
pub use crate::auto::*;
#[cfg(feature = "ser_de")]
mod flag_serde;
// Re-export all the traits in a prelude module, so that applications
// can always "use ges::prelude::*" without getting conflicts
pub mod prelude {

View file

@ -26,9 +26,11 @@ glib = { git = "https://github.com/gtk-rs/gtk-rs-core" }
gst = { package = "gstreamer", path = "../gstreamer" }
gst-base = { package = "gstreamer-base", path = "../gstreamer-base" }
gst-video = { package = "gstreamer-video", path = "../gstreamer-video" }
serde = { version = "1.0", optional = true }
[dev-dependencies]
gir-format-check = "0.1"
serde_json = "1.0"
[features]
default = []
@ -36,7 +38,8 @@ v1_16 = ["gst/v1_16", "gst-base/v1_16", "gst-video/v1_16", "ffi/v1_16"]
v1_18 = ["gst/v1_18", "gst-base/v1_18", "gst-video/v1_18", "ffi/v1_18", "v1_16"]
v1_20 = ["gst/v1_20", "gst-base/v1_20", "gst-video/v1_20", "ffi/v1_20", "v1_18"]
v1_22 = ["gst/v1_22", "gst-base/v1_22", "gst-video/v1_22", "ffi/v1_22", "v1_20"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox", "gst-base/dox", "gst-video/dox"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox", "gst-base/dox", "gst-video/dox", "ser_de"]
ser_de = ["serde", "gst/ser_de", "gst-video/ser_de"]
[package.metadata.docs.rs]
features = ["dox"]

View file

@ -0,0 +1,110 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use glib::translate::{from_glib, ToGlibPtr};
use glib::{FlagsClass, StaticType, ToValue};
use gst::bitflags_serde_impl;
bitflags_serde_impl!(crate::GLAPI);
bitflags_serde_impl!(crate::GLConfigSurfaceType, "v1_20");
bitflags_serde_impl!(crate::GLDisplayType);
bitflags_serde_impl!(crate::GLPlatform);
bitflags_serde_impl!(crate::GLSLProfile);
#[cfg(test)]
mod tests {
macro_rules! check_serialize {
($flags:expr, $expected:expr) => {
let actual = serde_json::to_string(&$flags).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_deserialize {
($ty:ty, $expected:expr, $json:expr) => {
let actual: $ty = serde_json::from_str(&$json).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_roundtrip {
($ty:ty, $flags:expr) => {
let json = serde_json::to_string(&$flags).unwrap();
let deserialized: $ty = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, $flags);
};
}
#[test]
fn test_serialize() {
gst::init().unwrap();
check_serialize!(crate::GLAPI::all(), "\"opengl+opengl3+gles1+gles2\"");
#[cfg(feature = "v1_20")]
check_serialize!(
crate::GLConfigSurfaceType::all(),
"\"window+pbuffer+pixmap\""
);
#[cfg(feature = "v1_20")]
check_serialize!(
crate::GLDisplayType::all(),
concat!(
"\"x11+wayland+cocoa+win32+dispmanx+egl+viv-fb+gbm+egl-device",
"+eagl+winrt+android\""
)
);
check_serialize!(crate::GLPlatform::all(), "\"egl+glx+wgl+cgl+eagl\"");
check_serialize!(crate::GLSLProfile::all(), "\"es+core+compatibility\"");
}
#[test]
fn test_deserialize() {
gst::init().unwrap();
check_deserialize!(
crate::GLAPI,
crate::GLAPI::all(),
"\"opengl+opengl3+gles1+gles2\""
);
#[cfg(feature = "v1_20")]
check_deserialize!(
crate::GLConfigSurfaceType,
crate::GLConfigSurfaceType::all(),
"\"none+window+pbuffer+pixmap\""
);
#[cfg(feature = "v1_20")]
check_deserialize!(
crate::GLDisplayType,
crate::GLDisplayType::all(),
concat!(
"\"x11+wayland+cocoa+win32+dispmanx+egl+viv-fb+gbm+egl-device",
"+eagl+winrt+android\""
)
);
check_deserialize!(
crate::GLPlatform,
crate::GLPlatform::all(),
"\"egl+glx+wgl+cgl+eagl\""
);
check_deserialize!(
crate::GLSLProfile,
crate::GLSLProfile::all(),
"\"es+core+compatibility\""
);
}
#[test]
fn test_serde_roundtrip() {
gst::init().unwrap();
check_roundtrip!(crate::GLAPI, crate::GLAPI::all());
#[cfg(feature = "v1_20")]
check_roundtrip!(
crate::GLConfigSurfaceType,
crate::GLConfigSurfaceType::all()
);
#[cfg(feature = "v1_20")]
check_roundtrip!(crate::GLDisplayType, crate::GLDisplayType::all());
check_roundtrip!(crate::GLPlatform, crate::GLPlatform::all());
check_roundtrip!(crate::GLSLProfile, crate::GLSLProfile::all());
}
}

View file

@ -32,6 +32,9 @@ mod auto;
pub use crate::auto::functions::*;
pub use crate::auto::*;
#[cfg(feature = "ser_de")]
mod flag_serde;
mod caps_features;
pub use crate::caps_features::CAPS_FEATURES_MEMORY_GL_MEMORY;
mod context;

View file

@ -20,9 +20,11 @@ ffi = { package = "gstreamer-pbutils-sys", path = "sys" }
glib = { git = "https://github.com/gtk-rs/gtk-rs-core" }
gst = { package = "gstreamer", path = "../gstreamer" }
thiserror = "1.0"
serde = { version = "1.0", optional = true }
[dev-dependencies]
gir-format-check = "0.1"
serde_json = "1.0"
[features]
default = []
@ -30,7 +32,8 @@ v1_16 = ["gst/v1_16", "ffi/v1_16"]
v1_18 = ["gst/v1_18", "ffi/v1_18", "v1_16"]
v1_20 = ["gst/v1_20", "ffi/v1_20", "v1_18"]
v1_22 = ["gst/v1_22", "ffi/v1_22", "v1_20"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox", "ser_de"]
ser_de = ["serde", "gst/ser_de"]
[package.metadata.docs.rs]
features = ["dox"]

View file

@ -0,0 +1,77 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use glib::translate::{from_glib, ToGlibPtr};
use glib::{FlagsClass, StaticType, ToValue};
use gst::bitflags_serde_impl;
bitflags_serde_impl!(crate::DiscovererSerializeFlags);
bitflags_serde_impl!(crate::PbUtilsCapsDescriptionFlags, "v1_20");
#[cfg(test)]
mod tests {
macro_rules! check_serialize {
($flags:expr, $expected:expr) => {
let actual = serde_json::to_string(&$flags).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_deserialize {
($ty:ty, $expected:expr, $json:expr) => {
let actual: $ty = serde_json::from_str(&$json).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_roundtrip {
($ty:ty, $flags:expr) => {
let json = serde_json::to_string(&$flags).unwrap();
let deserialized: $ty = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, $flags);
};
}
#[test]
fn test_serialize() {
gst::init().unwrap();
check_serialize!(crate::DiscovererSerializeFlags::all(), "\"caps+tags+misc\"");
#[cfg(feature = "v1_20")]
check_serialize!(
crate::PbUtilsCapsDescriptionFlags::all(),
"\"container+audio+video+image+subtitle+tag+generic\""
);
}
#[test]
fn test_deserialize() {
gst::init().unwrap();
check_deserialize!(
crate::DiscovererSerializeFlags,
crate::DiscovererSerializeFlags::all(),
"\"caps+tags+misc\""
);
#[cfg(feature = "v1_20")]
check_deserialize!(
crate::PbUtilsCapsDescriptionFlags,
crate::PbUtilsCapsDescriptionFlags::all(),
"\"container+audio+video+image+subtitle+tag+generic\""
);
}
#[test]
fn test_serde_roundtrip() {
gst::init().unwrap();
check_roundtrip!(
crate::DiscovererSerializeFlags,
crate::DiscovererSerializeFlags::all()
);
#[cfg(feature = "v1_20")]
check_roundtrip!(
crate::PbUtilsCapsDescriptionFlags,
crate::PbUtilsCapsDescriptionFlags::all()
);
}
}

View file

@ -38,6 +38,9 @@ mod auto;
pub use crate::auto::functions::*;
pub use crate::auto::*;
#[cfg(feature = "ser_de")]
mod flag_serde;
mod discoverer;
pub use crate::discoverer::*;

View file

@ -20,9 +20,11 @@ libc = "0.2"
ffi = { package = "gstreamer-rtp-sys", path = "sys" }
glib = { git = "https://github.com/gtk-rs/gtk-rs-core" }
gst = { package = "gstreamer", path = "../gstreamer" }
serde = { version = "1.0", optional = true }
[dev-dependencies]
gir-format-check = "0.1"
serde_json = "1.0"
[features]
default = []
@ -30,7 +32,8 @@ v1_16 = ["gst/v1_16", "ffi/v1_16"]
v1_18 = ["gst/v1_18", "ffi/v1_18", "v1_16"]
v1_20 = ["gst/v1_20", "ffi/v1_20", "v1_18"]
v1_22 = ["gst/v1_22", "ffi/v1_22", "v1_20"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox", "ser_de"]
ser_de = ["serde", "gst/ser_de"]
[package.metadata.docs.rs]
features = ["dox"]

View file

@ -0,0 +1,114 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use glib::translate::{from_glib, ToGlibPtr};
use glib::{FlagsClass, StaticType, ToValue};
use gst::{bitflags_deserialize_impl, bitflags_serde_impl, bitflags_serialize_impl};
bitflags_serialize_impl!(crate::RTPBufferFlags, by_ones_decreasing);
bitflags_deserialize_impl!(crate::RTPBufferFlags);
bitflags_serde_impl!(crate::RTPBufferMapFlags);
// use this implementation, since serializing to "sendonly+recvonly"
// wouldn't make much sense
bitflags_serialize_impl!(
crate::RTPHeaderExtensionDirection,
by_ones_decreasing,
"v1_20"
);
bitflags_deserialize_impl!(crate::RTPHeaderExtensionDirection, "v1_20");
bitflags_serde_impl!(crate::RTPHeaderExtensionFlags, "v1_20");
#[cfg(test)]
mod tests {
macro_rules! check_serialize {
($flags:expr, $expected:expr) => {
let actual = serde_json::to_string(&$flags).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_deserialize {
($ty:ty, $expected:expr, $json:expr) => {
let actual: $ty = serde_json::from_str(&$json).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_roundtrip {
($ty:ty, $flags:expr) => {
let json = serde_json::to_string(&$flags).unwrap();
let deserialized: $ty = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, $flags);
};
}
#[test]
fn test_serialize() {
gst::init().unwrap();
check_serialize!(crate::RTPBufferFlags::all(), "\"retransmission+redundant\"");
check_serialize!(crate::RTPBufferMapFlags::all(), "\"skip-padding\"");
#[cfg(feature = "v1_20")]
check_serialize!(
crate::RTPHeaderExtensionDirection::all(),
"\"sendrecv+inherited\""
);
#[cfg(feature = "v1_20")]
check_serialize!(
crate::RTPHeaderExtensionDirection::INACTIVE
| crate::RTPHeaderExtensionDirection::SENDONLY
| crate::RTPHeaderExtensionDirection::INHERITED,
"\"sendonly+inherited\""
);
#[cfg(feature = "v1_20")]
check_serialize!(
crate::RTPHeaderExtensionFlags::all(),
"\"one-byte+two-byte\""
);
}
#[test]
fn test_deserialize() {
gst::init().unwrap();
check_deserialize!(
crate::RTPBufferFlags,
crate::RTPBufferFlags::all(),
"\"retransmission+redundant\""
);
check_deserialize!(
crate::RTPBufferMapFlags,
crate::RTPBufferMapFlags::all(),
"\"skip-padding\""
);
#[cfg(feature = "v1_20")]
check_deserialize!(
crate::RTPHeaderExtensionDirection,
crate::RTPHeaderExtensionDirection::all(),
"\"sendrecv+inactive+inherited\""
);
#[cfg(feature = "v1_20")]
check_deserialize!(
crate::RTPHeaderExtensionFlags,
crate::RTPHeaderExtensionFlags::all(),
"\"one-byte+two-byte\""
);
}
#[test]
fn test_serde_roundtrip() {
gst::init().unwrap();
check_roundtrip!(crate::RTPBufferFlags, crate::RTPBufferFlags::all());
check_roundtrip!(crate::RTPBufferMapFlags, crate::RTPBufferMapFlags::all());
#[cfg(feature = "v1_20")]
check_roundtrip!(
crate::RTPHeaderExtensionDirection,
crate::RTPHeaderExtensionDirection::all()
);
#[cfg(feature = "v1_20")]
check_roundtrip!(
crate::RTPHeaderExtensionFlags,
crate::RTPHeaderExtensionFlags::all()
);
}
}

View file

@ -32,6 +32,9 @@ mod auto;
pub use crate::auto::functions::*;
pub use crate::auto::*;
#[cfg(feature = "ser_de")]
mod flag_serde;
pub mod subclass;
pub mod rtp_buffer;

View file

@ -24,9 +24,11 @@ gst = { package = "gstreamer", path = "../gstreamer" }
gst-sdp = { package = "gstreamer-sdp", path = "../gstreamer-sdp" }
gst-rtsp = { package = "gstreamer-rtsp", path = "../gstreamer-rtsp" }
gst-net = { package = "gstreamer-net", path = "../gstreamer-net" }
serde = { version = "1.0", optional = true }
[dev-dependencies]
gir-format-check = "0.1"
serde_json = "1.0"
[features]
default = []
@ -34,7 +36,8 @@ v1_16 = ["gst/v1_16", "gst-sdp/v1_16", "gst-rtsp/v1_16", "gst-net/v1_16", "ffi/v
v1_18 = ["gst/v1_18", "gst-sdp/v1_18", "gst-rtsp/v1_18", "gst-net/v1_18", "ffi/v1_18", "v1_16"]
v1_20 = ["gst/v1_20", "gst-sdp/v1_20", "gst-rtsp/v1_20", "gst-net/v1_20", "ffi/v1_20", "v1_18"]
v1_22 = ["gst/v1_22", "gst-sdp/v1_22", "gst-rtsp/v1_22", "gst-net/v1_22", "ffi/v1_22", "v1_20"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gio/dox", "gst/dox", "gst-sdp/dox", "gst-rtsp/dox", "gst-net/dox"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gio/dox", "gst/dox", "gst-sdp/dox", "gst-rtsp/dox", "gst-net/dox", "ser_de"]
ser_de = ["serde", "gst/ser_de", "gst-rtsp/ser_de"]
[package.metadata.docs.rs]
features = ["dox"]

View file

@ -0,0 +1,57 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use glib::translate::{from_glib, ToGlibPtr};
use glib::{FlagsClass, StaticType, ToValue};
use gst::bitflags_serde_impl;
bitflags_serde_impl!(crate::RTSPTransportMode);
#[cfg(test)]
mod tests {
macro_rules! check_serialize {
($flags:expr, $expected:expr) => {
let actual = serde_json::to_string(&$flags).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_deserialize {
($ty:ty, $expected:expr, $json:expr) => {
let actual: $ty = serde_json::from_str(&$json).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_roundtrip {
($ty:ty, $flags:expr) => {
let json = serde_json::to_string(&$flags).unwrap();
let deserialized: $ty = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, $flags);
};
}
#[test]
fn test_serialize() {
gst::init().unwrap();
check_serialize!(crate::RTSPTransportMode::all(), "\"play+record\"");
}
#[test]
fn test_deserialize() {
gst::init().unwrap();
check_deserialize!(
crate::RTSPTransportMode,
crate::RTSPTransportMode::all(),
"\"play+record\""
);
}
#[test]
fn test_serde_roundtrip() {
gst::init().unwrap();
check_roundtrip!(crate::RTSPTransportMode, crate::RTSPTransportMode::all());
}
}

View file

@ -35,6 +35,9 @@ macro_rules! skip_assert_initialized {
mod auto;
pub use crate::auto::*;
#[cfg(feature = "ser_de")]
mod flag_serde;
mod rtsp_address_pool;
mod rtsp_auth;
mod rtsp_client;

View file

@ -20,9 +20,11 @@ ffi = { package = "gstreamer-rtsp-sys", path = "sys" }
glib = { git = "https://github.com/gtk-rs/gtk-rs-core" }
gst = { package = "gstreamer", path = "../gstreamer" }
gst-sdp = { package = "gstreamer-sdp", path = "../gstreamer-sdp" }
serde = { version = "1.0", optional = true }
[dev-dependencies]
gir-format-check = "0.1"
serde_json = "1.0"
[features]
default = []
@ -30,7 +32,8 @@ v1_16 = ["gst/v1_16", "gst-sdp/v1_16", "ffi/v1_16"]
v1_18 = ["gst/v1_18", "gst-sdp/v1_18", "ffi/v1_18", "v1_16"]
v1_20 = ["gst/v1_20", "gst-sdp/v1_20", "ffi/v1_20", "v1_18"]
v1_22 = ["gst/v1_22", "gst-sdp/v1_22", "ffi/v1_22", "v1_20"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox", "gst-sdp/dox"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox", "gst-sdp/dox", "ser_de"]
ser_de = ["serde", "gst/ser_de"]
[package.metadata.docs.rs]
features = ["dox"]

View file

@ -0,0 +1,97 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use glib::translate::{from_glib, ToGlibPtr};
use glib::{FlagsClass, StaticType, ToValue};
use gst::bitflags_serde_impl;
bitflags_serde_impl!(crate::RTSPEvent);
bitflags_serde_impl!(crate::RTSPLowerTrans);
bitflags_serde_impl!(crate::RTSPMethod);
bitflags_serde_impl!(crate::RTSPProfile);
bitflags_serde_impl!(crate::RTSPTransMode);
#[cfg(test)]
mod tests {
macro_rules! check_serialize {
($flags:expr, $expected:expr) => {
let actual = serde_json::to_string(&$flags).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_deserialize {
($ty:ty, $expected:expr, $json:expr) => {
let actual: $ty = serde_json::from_str(&$json).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_roundtrip {
($ty:ty, $flags:expr) => {
let json = serde_json::to_string(&$flags).unwrap();
let deserialized: $ty = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, $flags);
};
}
#[test]
fn test_serialize() {
gst::init().unwrap();
check_serialize!(crate::RTSPEvent::all(), "\"read+write\"");
check_serialize!(
crate::RTSPLowerTrans::all(),
"\"udp+udp-mcast+tcp+http+tls\""
);
check_serialize!(
crate::RTSPMethod::all(),
concat!(
"\"describe+announce+get-parameter+options+pause+play+record",
"+redirect+setup+set-parameter+teardown+get+post\""
)
);
check_serialize!(crate::RTSPProfile::all(), "\"avp+savp+avpf+savpf\"");
check_serialize!(crate::RTSPTransMode::all(), "\"rtp+rdt\"");
}
#[test]
fn test_deserialize() {
gst::init().unwrap();
check_deserialize!(crate::RTSPEvent, crate::RTSPEvent::all(), "\"read+write\"");
check_deserialize!(
crate::RTSPLowerTrans,
crate::RTSPLowerTrans::all(),
"\"udp+udp-mcast+tcp+http+tls\""
);
check_deserialize!(
crate::RTSPMethod,
crate::RTSPMethod::all(),
concat!(
"\"describe+announce+get-parameter+options+pause+play+record",
"+redirect+setup+set-parameter+teardown+get+post\""
)
);
check_deserialize!(
crate::RTSPProfile,
crate::RTSPProfile::all(),
"\"avp+savp+avpf+savpf\""
);
check_deserialize!(
crate::RTSPTransMode,
crate::RTSPTransMode::all(),
"\"rtp+rdt\""
);
}
#[test]
fn test_serde_roundtrip() {
gst::init().unwrap();
check_roundtrip!(crate::RTSPEvent, crate::RTSPEvent::all());
check_roundtrip!(crate::RTSPLowerTrans, crate::RTSPLowerTrans::all());
check_roundtrip!(crate::RTSPMethod, crate::RTSPMethod::all());
check_roundtrip!(crate::RTSPProfile, crate::RTSPProfile::all());
check_roundtrip!(crate::RTSPTransMode, crate::RTSPTransMode::all());
}
}

View file

@ -29,6 +29,9 @@ macro_rules! skip_assert_initialized {
mod auto;
pub use crate::auto::*;
#[cfg(feature = "ser_de")]
mod flag_serde;
// Re-export all the traits in a prelude module, so that applications
// can always "use gst_rtsp::prelude::*" without getting conflicts
pub mod prelude {

View file

@ -36,8 +36,8 @@ v1_16 = ["gst/v1_16", "gst-base/v1_16", "ffi/v1_16"]
v1_18 = ["gst/v1_18", "gst-base/v1_18", "ffi/v1_18", "v1_16"]
v1_20 = ["gst/v1_20", "gst-base/v1_20", "ffi/v1_20", "v1_18"]
v1_22 = ["gst/v1_22", "gst-base/v1_22", "ffi/v1_22", "v1_20"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox", "gst-base/dox"]
ser_de = ["serde"]
dox = ["v1_22", "ffi/dox", "glib/dox", "gst/dox", "gst-base/dox", "ser_de"]
ser_de = ["serde", "gst/ser_de"]
[package.metadata.docs.rs]
features = ["dox"]

View file

@ -0,0 +1,231 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use glib::translate::{from_glib, ToGlibPtr};
use glib::{FlagsClass, StaticType, ToValue};
use gst::bitflags_serde_impl;
bitflags_serde_impl!(crate::NavigationModifierType, "v1_22");
bitflags_serde_impl!(crate::VideoBufferFlags);
bitflags_serde_impl!(crate::VideoChromaSite);
bitflags_serde_impl!(crate::VideoCodecFrameFlags, "v1_20");
bitflags_serde_impl!(crate::VideoDecoderRequestSyncPointFlags, "v1_20");
bitflags_serde_impl!(crate::VideoFlags);
bitflags_serde_impl!(crate::VideoFormatFlags);
bitflags_serde_impl!(crate::VideoFrameFlags);
bitflags_serde_impl!(crate::VideoMultiviewFlags);
bitflags_serde_impl!(crate::VideoOverlayFormatFlags, "v1_16");
bitflags_serde_impl!(crate::VideoPackFlags);
bitflags_serde_impl!(crate::VideoTimeCodeFlags, "v1_18");
#[cfg(test)]
mod tests {
macro_rules! check_serialize {
($flags:expr, $expected:expr) => {
let actual = serde_json::to_string(&$flags).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_deserialize {
($ty:ty, $expected:expr, $json:expr) => {
let actual: $ty = serde_json::from_str(&$json).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_roundtrip {
($ty:ty, $flags:expr) => {
let json = serde_json::to_string(&$flags).unwrap();
let deserialized: $ty = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, $flags);
};
}
#[test]
fn test_serialize() {
gst::init().unwrap();
#[cfg(feature = "v1_22")]
check_serialize!(
crate::NavigationModifierType::all(),
concat!(
"\"shift-mask+lock-mask+control-mask+alt-mask+button1-mask",
"+button2-mask+button3-mask+button4-mask+button5-mask",
"+super-mask+hyper-mask+meta-mask\""
)
);
#[cfg(feature = "v1_18")]
check_serialize!(
crate::VideoBufferFlags::all(),
"\"interlaced+tff+rff+onefield+multiple-view+first-in-bundle+marker\""
);
check_serialize!(
crate::VideoChromaSite::all(),
"\"none+h-cosited+v-cosited+alt-line\""
);
#[cfg(feature = "v1_20")]
check_serialize!(
crate::VideoCodecFrameFlags::all(),
"\"decode-only+sync-point+force-keyframe+force-keyframe-headers+corrupted\""
);
#[cfg(feature = "v1_20")]
check_serialize!(
crate::VideoDecoderRequestSyncPointFlags::all(),
"\"discard-input+corrupt-output\""
);
check_serialize!(
crate::VideoFlags::all(),
"\"variable-fps+premultiplied-alpha\""
);
#[cfg(feature = "v1_22")]
check_serialize!(
crate::VideoFormatFlags::all(),
"\"yuv+rgb+gray+alpha+le+palette+complex+unpack+tiled+subtiles\""
);
check_serialize!(
crate::VideoFrameFlags::all(),
"\"interlaced+tff+rff+onefield+multiple-view+first-in-bundle\""
);
check_serialize!(
crate::VideoMultiviewFlags::all(),
concat!(
"\"right-view-first+left-flipped+left-flopped+right-flipped",
"+right-flopped+half-aspect+mixed-mono\""
)
);
#[cfg(feature = "v1_16")]
check_serialize!(
crate::VideoOverlayFormatFlags::all(),
"\"premultiplied-alpha+global-alpha\""
);
check_serialize!(
crate::VideoPackFlags::all(),
"\"truncate-range+interlaced\""
);
#[cfg(feature = "v1_18")]
check_serialize!(
crate::VideoTimeCodeFlags::all(),
"\"drop-frame+interlaced\""
);
}
#[test]
fn test_deserialize() {
gst::init().unwrap();
#[cfg(feature = "v1_22")]
check_deserialize!(
crate::NavigationModifierType,
crate::NavigationModifierType::all(),
concat!(
"\"shift-mask+lock-mask+control-mask+alt-mask+button1-mask",
"+button2-mask+button3-mask+button4-mask+button5-mask",
"+super-mask+hyper-mask+meta-mask\""
)
);
#[cfg(feature = "v1_18")]
check_deserialize!(
crate::VideoBufferFlags,
crate::VideoBufferFlags::all(),
"\"interlaced+tff+rff+onefield+multiple-view+first-in-bundle+marker\""
);
check_deserialize!(
crate::VideoChromaSite,
crate::VideoChromaSite::all(),
"\"none+h-cosited+v-cosited+alt-line\""
);
#[cfg(feature = "v1_20")]
check_deserialize!(
crate::VideoCodecFrameFlags,
crate::VideoCodecFrameFlags::all(),
"\"decode-only+sync-point+force-keyframe+force-keyframe-headers+corrupted\""
);
#[cfg(feature = "v1_20")]
check_deserialize!(
crate::VideoDecoderRequestSyncPointFlags,
crate::VideoDecoderRequestSyncPointFlags::all(),
"\"discard-input+corrupt-output\""
);
check_deserialize!(
crate::VideoFlags,
crate::VideoFlags::all(),
"\"variable-fps+premultiplied-alpha\""
);
#[cfg(feature = "v1_22")]
check_deserialize!(
crate::VideoFormatFlags,
crate::VideoFormatFlags::all(),
"\"yuv+rgb+gray+alpha+le+palette+complex+unpack+tiled+subtiles\""
);
check_deserialize!(
crate::VideoFrameFlags,
crate::VideoFrameFlags::all(),
"\"interlaced+tff+rff+onefield+multiple-view+first-in-bundle\""
);
check_deserialize!(
crate::VideoMultiviewFlags,
crate::VideoMultiviewFlags::all(),
concat!(
"\"right-view-first+left-flipped+left-flopped+right-flipped",
"+right-flopped+half-aspect+mixed-mono\""
)
);
#[cfg(feature = "v1_16")]
check_deserialize!(
crate::VideoOverlayFormatFlags,
crate::VideoOverlayFormatFlags::all(),
"\"premultiplied-alpha+global-alpha\""
);
check_deserialize!(
crate::VideoPackFlags,
crate::VideoPackFlags::all(),
"\"truncate-range+interlaced\""
);
#[cfg(feature = "v1_18")]
check_deserialize!(
crate::VideoTimeCodeFlags,
crate::VideoTimeCodeFlags::all(),
"\"drop-frame+interlaced\""
);
}
#[test]
fn test_serde_roundtrip() {
gst::init().unwrap();
#[cfg(feature = "v1_22")]
check_roundtrip!(
crate::NavigationModifierType,
crate::NavigationModifierType::all()
);
#[cfg(feature = "v1_18")]
check_roundtrip!(crate::VideoBufferFlags, crate::VideoBufferFlags::all());
check_roundtrip!(crate::VideoChromaSite, crate::VideoChromaSite::all());
#[cfg(feature = "v1_20")]
check_roundtrip!(
crate::VideoCodecFrameFlags,
crate::VideoCodecFrameFlags::all()
);
#[cfg(feature = "v1_20")]
check_roundtrip!(
crate::VideoDecoderRequestSyncPointFlags,
crate::VideoDecoderRequestSyncPointFlags::all()
);
check_roundtrip!(crate::VideoFlags, crate::VideoFlags::all());
#[cfg(feature = "v1_22")]
check_roundtrip!(crate::VideoFormatFlags, crate::VideoFormatFlags::all());
check_roundtrip!(crate::VideoFrameFlags, crate::VideoFrameFlags::all());
check_roundtrip!(
crate::VideoMultiviewFlags,
crate::VideoMultiviewFlags::all()
);
#[cfg(feature = "v1_16")]
check_roundtrip!(
crate::VideoOverlayFormatFlags,
crate::VideoOverlayFormatFlags::all()
);
check_roundtrip!(crate::VideoPackFlags, crate::VideoPackFlags::all());
#[cfg(feature = "v1_18")]
check_roundtrip!(crate::VideoTimeCodeFlags, crate::VideoTimeCodeFlags::all());
}
}

View file

@ -32,6 +32,9 @@ macro_rules! skip_assert_initialized {
mod auto;
pub use crate::auto::*;
#[cfg(feature = "ser_de")]
mod flag_serde;
mod navigation;
mod caps_features;

View file

@ -9,10 +9,6 @@ use std::mem;
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_22")))]
use crate::NavigationModifierType;
#[cfg(all(feature = "ser_de", any(feature = "v1_22", feature = "dox")))]
#[cfg_attr(feature = "dox", feature = "ser_de", doc(cfg(feature = "v1_22")))]
use glib::{FlagsClass, StaticType};
// FIXME: Copy from gstreamer/src/event.rs
macro_rules! event_builder_generic_impl {
($new_fn:expr) => {
@ -1267,81 +1263,6 @@ impl NavigationEvent {
}
}
#[cfg(all(feature = "ser_de", any(feature = "v1_22", feature = "dox")))]
#[cfg_attr(feature = "dox", feature = "ser_de", doc(cfg(feature = "v1_22")))]
impl serde::Serialize for NavigationModifierType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let class = FlagsClass::new(NavigationModifierType::static_type()).unwrap();
let mut handled = NavigationModifierType::empty();
let mut res = "".to_owned();
for v in class.values() {
let value = v.value();
if value.count_ones() != 1 || value & handled.bits() != 0 {
continue;
} else if (value & self.bits()) == value {
if !res.is_empty() {
res.push('+');
}
res.push_str(v.nick());
handled.insert(NavigationModifierType::from_bits(value).unwrap());
}
}
serializer.serialize_str(&res)
}
}
#[cfg(all(feature = "ser_de", any(feature = "v1_22", feature = "dox")))]
#[cfg_attr(feature = "dox", feature = "ser_de", doc(cfg(feature = "v1_22")))]
struct NavigationModifierTypeVisitor;
#[cfg(all(feature = "ser_de", any(feature = "v1_22", feature = "dox")))]
#[cfg_attr(feature = "dox", feature = "ser_de", doc(cfg(feature = "v1_22")))]
impl<'de> serde::de::Visitor<'de> for NavigationModifierTypeVisitor {
type Value = NavigationModifierType;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("one or more mask names separated by plus signs, or the empty string")
}
fn visit_str<E: serde::de::Error>(self, value: &str) -> std::result::Result<Self::Value, E> {
if value.is_empty() {
return Ok(NavigationModifierType::empty());
}
let mut gvalue = glib::Value::from_type(NavigationModifierType::static_type());
let tokens = value.split('+');
let class = FlagsClass::new(NavigationModifierType::static_type()).unwrap();
for token in tokens {
gvalue = class
.set_by_nick(gvalue, token)
.map_err(|_| serde::de::Error::custom(&format!("Invalid value: {}", token)))?;
}
Ok(unsafe {
from_glib(glib::gobject_ffi::g_value_get_flags(
gvalue.to_glib_none().0,
))
})
}
}
#[cfg(all(feature = "ser_de", any(feature = "v1_22", feature = "dox")))]
#[cfg_attr(feature = "dox", feature = "ser_de", doc(cfg(feature = "v1_22")))]
impl<'de> serde::Deserialize<'de> for NavigationModifierType {
fn deserialize<D: serde::de::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
skip_assert_initialized!();
deserializer.deserialize_str(NavigationModifierTypeVisitor)
}
}
#[cfg(test)]
mod tests {
#[test]

View file

@ -104,9 +104,7 @@ mod tests {
" duration: Some(5),",
" offset: 3,",
" offset_end: 4,",
" flags: (",
" bits: 80,",
" ),",
" flags: \"live+discont\",",
" buffer: \"AQIDBA==\",",
")"
)
@ -123,7 +121,7 @@ mod tests {
"\"duration\":5,",
"\"offset\":3,",
"\"offset_end\":4,",
"\"flags\":{\"bits\":80},",
"\"flags\":\"live+discont\",",
"\"buffer\":[1,2,3,4]",
"}"
)
@ -143,9 +141,7 @@ mod tests {
duration: Some(5),
offset: 3,
offset_end: 4,
flags: (
bits: 80,
),
flags: "live+discont",
buffer: "AQIDBA==",
)
"#;
@ -168,7 +164,7 @@ mod tests {
"duration":5,
"offset":3,
"offset_end":4,
"flags":{"bits":80},
"flags":"live+discont",
"buffer":[1,2,3,4]
}
"#;

View file

@ -107,9 +107,7 @@ mod tests {
" duration: Some(4),",
" offset: 0,",
" offset_end: 4,",
" flags: (",
" bits: 0,",
" ),",
" flags: \"\",",
" buffer: \"AQIDBA==\",",
" ),",
" (",
@ -118,9 +116,7 @@ mod tests {
" duration: Some(2),",
" offset: 4,",
" offset_end: 6,",
" flags: (",
" bits: 0,",
" ),",
" flags: \"\",",
" buffer: \"BQY=\",",
" ),",
"]"
@ -142,9 +138,7 @@ mod tests {
duration: Some(4),
offset: 0,
offset_end: 4,
flags: (
bits: 0,
),
flags: "",
buffer: "AQIDBA==",
),
(
@ -153,9 +147,7 @@ mod tests {
duration: Some(2),
offset: 4,
offset_end: 6,
flags: (
bits: 0,
),
flags: "",
buffer: "BQY=",
),
]

533
gstreamer/src/flag_serde.rs Normal file
View file

@ -0,0 +1,533 @@
// Take a look at the license at the top of the repository in the LICENSE file.
use glib::translate::{from_glib, ToGlibPtr};
use glib::{FlagsClass, StaticType, ToValue};
bitflags_serde_impl!(crate::BinFlags);
bitflags_serde_impl!(crate::BufferCopyFlags);
bitflags_serde_impl!(crate::BufferFlags);
bitflags_serde_impl!(crate::BufferPoolAcquireFlags);
bitflags_serde_impl!(crate::ClockFlags);
impl serde::Serialize for crate::DebugColorFlags {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!(
"{}+{}{}{}",
match *self & Self::from_bits(0b111).expect("Failed to create value from fg-color mask")
{
Self::FG_BLACK => "fg-black",
Self::FG_RED => "fg-red",
Self::FG_GREEN => "fg-green",
Self::FG_YELLOW => "fg-yellow",
Self::FG_BLUE => "fg-blue",
Self::FG_MAGENTA => "fg-magenta",
Self::FG_CYAN => "fg-cyan",
Self::FG_WHITE => "fg-white",
_ => unreachable!(),
},
match *self
& Self::from_bits(0b111_0000).expect("Failed to create value from bg-color mask")
{
Self::BG_BLACK => "bg-black",
Self::BG_RED => "bg-red",
Self::BG_GREEN => "bg-green",
Self::BG_YELLOW => "bg-yellow",
Self::BG_BLUE => "bg-blue",
Self::BG_MAGENTA => "bg-magenta",
Self::BG_CYAN => "bg-cyan",
Self::BG_WHITE => "bg-white",
_ => unreachable!(),
},
if self.contains(Self::BOLD) {
"+bold"
} else {
""
},
if self.contains(Self::UNDERLINE) {
"+underline"
} else {
""
}
))
}
}
bitflags_deserialize_impl!(crate::DebugColorFlags);
bitflags_serialize_impl!(crate::DebugGraphDetails, by_ones_decreasing);
bitflags_deserialize_impl!(crate::DebugGraphDetails);
bitflags_serde_impl!(crate::ElementFlags);
bitflags_serde_impl!(crate::EventTypeFlags);
bitflags_serde_impl!(crate::GapFlags, "v1_20");
bitflags_serde_impl!(crate::MemoryFlags);
bitflags_serde_impl!(crate::MetaFlags);
bitflags_serde_impl!(crate::ObjectFlags);
bitflags_serde_impl!(crate::PadFlags);
bitflags_serde_impl!(crate::PadLinkCheck);
bitflags_serde_impl!(crate::PadProbeType);
bitflags_serde_impl!(crate::ParseFlags);
bitflags_serde_impl!(crate::PluginAPIFlags, "v1_18");
bitflags_serde_impl!(crate::PluginDependencyFlags);
bitflags_serde_impl!(crate::PluginFlags);
bitflags_serde_impl!(crate::SchedulingFlags);
bitflags_serde_impl!(crate::SeekFlags);
bitflags_serde_impl!(crate::SegmentFlags);
bitflags_serde_impl!(crate::SerializeFlags, "v1_20");
bitflags_serde_impl!(crate::StackTraceFlags);
bitflags_serde_impl!(crate::StreamFlags);
bitflags_serde_impl!(crate::StreamType);
#[cfg(test)]
mod tests {
macro_rules! check_serialize {
($flags:expr, $expected:expr) => {
let actual = serde_json::to_string(&$flags).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_deserialize {
($ty:ty, $expected:expr, $json:expr) => {
let actual: $ty = serde_json::from_str(&$json).unwrap();
assert_eq!(actual, $expected);
};
}
macro_rules! check_roundtrip {
($ty:ty, $flags:expr) => {
let json = serde_json::to_string(&$flags).unwrap();
let deserialized: $ty = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, $flags);
};
}
#[test]
fn test_serialize() {
crate::init().unwrap();
check_serialize!(crate::BinFlags::empty(), "\"\"");
check_serialize!(crate::BinFlags::all(), "\"no-resync+streams-aware\"");
check_serialize!(
crate::BufferCopyFlags::all(),
"\"flags+timestamps+meta+memory+merge+deep\""
);
check_serialize!(
crate::BufferFlags::all(),
concat!(
"\"live+decode-only+discont+resync+corrupted+marker+header+gap",
"+droppable+delta-unit+tag-memory+sync-after+non-droppable\""
)
);
check_serialize!(
crate::BufferPoolAcquireFlags::all(),
"\"key-unit+dontwait+discont\""
);
check_serialize!(
crate::ClockFlags::all(),
concat!(
"\"can-do-single-sync+can-do-single-async",
"+can-do-periodic-sync+can-do-periodic-async",
"+can-set-resolution+can-set-master+needs-startup-sync\""
)
);
check_serialize!(
crate::DebugColorFlags::all(),
"\"fg-white+bg-white+bold+underline\""
);
check_serialize!(
crate::DebugColorFlags::FG_MAGENTA | crate::DebugColorFlags::BOLD,
"\"fg-magenta+bg-black+bold\""
);
check_serialize!(
crate::DebugColorFlags::FG_RED
| crate::DebugColorFlags::FG_BLUE
| crate::DebugColorFlags::BG_BLACK,
"\"fg-magenta+bg-black\""
);
check_serialize!(crate::DebugGraphDetails::all(), "\"verbose\"");
check_serialize!(
crate::DebugGraphDetails::MEDIA_TYPE
| crate::DebugGraphDetails::CAPS_DETAILS
| crate::DebugGraphDetails::NON_DEFAULT_PARAMS
| crate::DebugGraphDetails::STATES
| crate::DebugGraphDetails::FULL_PARAMS
| crate::DebugGraphDetails::ALL,
"\"all+full-params\""
);
check_serialize!(
crate::DebugGraphDetails::MEDIA_TYPE
| crate::DebugGraphDetails::CAPS_DETAILS
| crate::DebugGraphDetails::NON_DEFAULT_PARAMS
| crate::DebugGraphDetails::STATES
| crate::DebugGraphDetails::FULL_PARAMS,
"\"all+full-params\""
);
check_serialize!(
crate::ElementFlags::all(),
"\"locked-state+sink+source+provide-clock+require-clock+indexable\""
);
check_serialize!(
crate::EventTypeFlags::all(),
"\"upstream+downstream+serialized+sticky+sticky-multi\""
);
#[cfg(feature = "v1_20")]
check_serialize!(crate::GapFlags::all(), "\"data\"");
check_serialize!(
crate::MemoryFlags::all(),
concat!(
"\"readonly+no-share+zero-prefixed+zero-padded",
"+physically-contiguous+not-mappable\""
)
);
check_serialize!(crate::MetaFlags::all(), "\"readonly+pooled+locked\"");
check_serialize!(crate::ObjectFlags::all(), "\"may-be-leaked\"");
check_serialize!(
crate::PadFlags::all(),
concat!(
"\"blocked+flushing+eos+blocking+need-parent+need-reconfigure",
"+pending-events+fixed-caps+proxy-caps+proxy-allocation",
"+proxy-scheduling+accept-intersect+accept-template\""
)
);
check_serialize!(
crate::PadLinkCheck::all(),
"\"hierarchy+template-caps+caps+no-reconfigure\""
);
check_serialize!(
crate::PadProbeType::all(),
concat!(
"\"idle+block+buffer+buffer-list+event-downstream",
"+event-upstream+event-flush+query-downstream+query-upstream",
"+push+pull\""
)
);
check_serialize!(
crate::ParseFlags::all(),
"\"fatal-errors+no-single-element-bins+place-in-bin\""
);
#[cfg(feature = "v1_18")]
check_serialize!(crate::PluginAPIFlags::all(), "\"members\"");
check_serialize!(
crate::PluginDependencyFlags::all(),
concat!(
"\"recurse+paths-are-default-only+file-name-is-suffix",
"+file-name-is-prefix+paths-are-relative-to-exe\""
)
);
check_serialize!(crate::PluginFlags::all(), "\"cached+blacklisted\"");
check_serialize!(
crate::SchedulingFlags::all(),
"\"seekable+sequential+bandwidth-limited\""
);
#[cfg(feature = "v1_18")]
check_serialize!(
crate::SeekFlags::all(),
concat!(
"\"flush+accurate+key-unit+segment+trickmode+snap-before",
"+snap-after+trickmode-key-units+trickmode-no-audio",
"+trickmode-forward-predicted+instant-rate-change\""
)
);
#[cfg(feature = "v1_18")]
check_serialize!(
crate::SegmentFlags::all(),
concat!(
"\"reset+trickmode+segment+trickmode-key-units",
"+trickmode-forward-predicted+trickmode-no-audio\""
)
);
#[cfg(feature = "v1_20")]
check_serialize!(crate::SerializeFlags::all(), "\"backward-compat\"");
check_serialize!(crate::StackTraceFlags::all(), "\"full\"");
check_serialize!(crate::StreamFlags::all(), "\"sparse+select+unselect\"");
check_serialize!(
crate::StreamType::all(),
"\"unknown+audio+video+container+text\""
);
}
#[test]
fn test_deserialize() {
crate::init().unwrap();
check_deserialize!(crate::BinFlags, crate::BinFlags::empty(), "\"\"");
check_deserialize!(
crate::BinFlags,
crate::BinFlags::all(),
"\"no-resync+streams-aware\""
);
check_deserialize!(
crate::BufferCopyFlags,
crate::BufferCopyFlags::all(),
"\"flags+timestamps+meta+memory+merge+deep\""
);
check_deserialize!(
crate::BufferFlags,
crate::BufferFlags::all(),
concat!(
"\"live+decode-only+discont+resync+corrupted+marker+header+gap",
"+droppable+delta-unit+tag-memory+sync-after+non-droppable\""
)
);
check_deserialize!(
crate::BufferPoolAcquireFlags,
crate::BufferPoolAcquireFlags::all(),
"\"key-unit+dontwait+discont\""
);
check_deserialize!(
crate::ClockFlags,
crate::ClockFlags::all(),
concat!(
"\"can-do-single-sync+can-do-single-async",
"+can-do-periodic-sync+can-do-periodic-async",
"+can-set-resolution+can-set-master+needs-startup-sync\""
)
);
check_deserialize!(
crate::DebugColorFlags,
crate::DebugColorFlags::all(),
"\"fg-white+bg-white+bold+underline\""
);
check_deserialize!(
crate::DebugColorFlags,
crate::DebugColorFlags::FG_MAGENTA | crate::DebugColorFlags::BOLD,
"\"fg-magenta+bg-black+bold\""
);
check_deserialize!(
crate::DebugColorFlags,
crate::DebugColorFlags::FG_RED
| crate::DebugColorFlags::FG_BLUE
| crate::DebugColorFlags::BG_BLACK,
"\"fg-magenta+bg-black\""
);
check_deserialize!(
crate::DebugGraphDetails,
crate::DebugGraphDetails::all(),
"\"verbose\""
);
check_deserialize!(
crate::DebugGraphDetails,
crate::DebugGraphDetails::MEDIA_TYPE
| crate::DebugGraphDetails::CAPS_DETAILS
| crate::DebugGraphDetails::NON_DEFAULT_PARAMS
| crate::DebugGraphDetails::STATES
| crate::DebugGraphDetails::FULL_PARAMS
| crate::DebugGraphDetails::ALL,
"\"all+full-params\""
);
check_deserialize!(
crate::DebugGraphDetails,
crate::DebugGraphDetails::MEDIA_TYPE
| crate::DebugGraphDetails::CAPS_DETAILS
| crate::DebugGraphDetails::NON_DEFAULT_PARAMS
| crate::DebugGraphDetails::STATES
| crate::DebugGraphDetails::FULL_PARAMS,
"\"all+full-params\""
);
check_deserialize!(
crate::ElementFlags,
crate::ElementFlags::all(),
"\"locked-state+sink+source+provide-clock+require-clock+indexable\""
);
check_deserialize!(
crate::EventTypeFlags,
crate::EventTypeFlags::all(),
"\"upstream+downstream+serialized+sticky+sticky-multi\""
);
#[cfg(feature = "v1_20")]
check_deserialize!(crate::GapFlags, crate::GapFlags::all(), "\"data\"");
check_deserialize!(
crate::MemoryFlags,
crate::MemoryFlags::all(),
concat!(
"\"readonly+no-share+zero-prefixed+zero-padded",
"+physically-contiguous+not-mappable\""
)
);
check_deserialize!(
crate::MetaFlags,
crate::MetaFlags::all(),
"\"readonly+pooled+locked\""
);
check_deserialize!(
crate::ObjectFlags,
crate::ObjectFlags::all(),
"\"may-be-leaked\""
);
check_deserialize!(
crate::PadFlags,
crate::PadFlags::all(),
concat!(
"\"blocked+flushing+eos+blocking+need-parent+need-reconfigure",
"+pending-events+fixed-caps+proxy-caps+proxy-allocation",
"+proxy-scheduling+accept-intersect+accept-template\""
)
);
check_deserialize!(
crate::PadLinkCheck,
crate::PadLinkCheck::all(),
"\"hierarchy+template-caps+caps+no-reconfigure\""
);
check_deserialize!(
crate::PadProbeType,
crate::PadProbeType::all(),
concat!(
"\"idle+block+buffer+buffer-list+event-downstream",
"+event-upstream+event-flush+query-downstream+query-upstream",
"+push+pull\""
)
);
check_deserialize!(
crate::ParseFlags,
crate::ParseFlags::all(),
"\"fatal-errors+no-single-element-bins+place-in-bin\""
);
#[cfg(feature = "v1_18")]
check_deserialize!(
crate::PluginAPIFlags,
crate::PluginAPIFlags::all(),
"\"members\""
);
check_deserialize!(
crate::PluginDependencyFlags,
crate::PluginDependencyFlags::all(),
concat!(
"\"recurse+paths-are-default-only+file-name-is-suffix",
"+file-name-is-prefix+paths-are-relative-to-exe\""
)
);
check_deserialize!(
crate::PluginFlags,
crate::PluginFlags::all(),
"\"cached+blacklisted\""
);
check_deserialize!(
crate::SchedulingFlags,
crate::SchedulingFlags::all(),
"\"seekable+sequential+bandwidth-limited\""
);
#[cfg(feature = "v1_18")]
check_deserialize!(
crate::SeekFlags,
crate::SeekFlags::all(),
concat!(
"\"flush+accurate+key-unit+segment+trickmode+snap-before",
"+snap-after+trickmode-key-units+trickmode-no-audio",
"+trickmode-forward-predicted+instant-rate-change\""
)
);
#[cfg(feature = "v1_18")]
check_deserialize!(
crate::SegmentFlags,
crate::SegmentFlags::all(),
concat!(
"\"reset+trickmode+segment+trickmode-key-units",
"+trickmode-forward-predicted+trickmode-no-audio\""
)
);
#[cfg(feature = "v1_20")]
check_deserialize!(
crate::SerializeFlags,
crate::SerializeFlags::all(),
"\"backward-compat\""
);
check_deserialize!(
crate::StackTraceFlags,
crate::StackTraceFlags::all(),
"\"full\""
);
check_deserialize!(
crate::StreamFlags,
crate::StreamFlags::all(),
"\"sparse+select+unselect\""
);
check_deserialize!(
crate::StreamType,
crate::StreamType::all(),
"\"unknown+audio+video+container+text\""
);
}
#[test]
fn test_serde_roundtrip() {
crate::init().unwrap();
check_roundtrip!(crate::BinFlags, crate::BinFlags::empty());
check_roundtrip!(crate::BinFlags, crate::BinFlags::all());
check_roundtrip!(crate::BufferCopyFlags, crate::BufferCopyFlags::all());
check_roundtrip!(crate::BufferFlags, crate::BufferFlags::all());
check_roundtrip!(
crate::BufferPoolAcquireFlags,
crate::BufferPoolAcquireFlags::all()
);
check_roundtrip!(crate::ClockFlags, crate::ClockFlags::all());
check_roundtrip!(crate::DebugColorFlags, crate::DebugColorFlags::all());
check_roundtrip!(
crate::DebugColorFlags,
crate::DebugColorFlags::FG_MAGENTA | crate::DebugColorFlags::BOLD
);
check_roundtrip!(
crate::DebugColorFlags,
crate::DebugColorFlags::FG_RED
| crate::DebugColorFlags::FG_BLUE
| crate::DebugColorFlags::BG_BLACK
);
check_roundtrip!(crate::DebugGraphDetails, crate::DebugGraphDetails::all());
check_roundtrip!(
crate::DebugGraphDetails,
crate::DebugGraphDetails::MEDIA_TYPE
| crate::DebugGraphDetails::CAPS_DETAILS
| crate::DebugGraphDetails::NON_DEFAULT_PARAMS
| crate::DebugGraphDetails::STATES
| crate::DebugGraphDetails::FULL_PARAMS
| crate::DebugGraphDetails::ALL
);
check_roundtrip!(
crate::DebugGraphDetails,
crate::DebugGraphDetails::MEDIA_TYPE
| crate::DebugGraphDetails::CAPS_DETAILS
| crate::DebugGraphDetails::NON_DEFAULT_PARAMS
| crate::DebugGraphDetails::STATES
| crate::DebugGraphDetails::FULL_PARAMS
);
check_roundtrip!(crate::ElementFlags, crate::ElementFlags::all());
check_roundtrip!(crate::EventTypeFlags, crate::EventTypeFlags::all());
#[cfg(feature = "v1_20")]
check_roundtrip!(crate::GapFlags, crate::GapFlags::all());
check_roundtrip!(crate::MemoryFlags, crate::MemoryFlags::all());
check_roundtrip!(crate::MetaFlags, crate::MetaFlags::all());
check_roundtrip!(crate::ObjectFlags, crate::ObjectFlags::all());
check_roundtrip!(crate::PadFlags, crate::PadFlags::all());
check_roundtrip!(crate::PadLinkCheck, crate::PadLinkCheck::all());
check_roundtrip!(crate::PadProbeType, crate::PadProbeType::all());
check_roundtrip!(crate::ParseFlags, crate::ParseFlags::all());
#[cfg(feature = "v1_18")]
check_roundtrip!(crate::PluginAPIFlags, crate::PluginAPIFlags::all());
check_roundtrip!(
crate::PluginDependencyFlags,
crate::PluginDependencyFlags::all()
);
check_roundtrip!(crate::PluginFlags, crate::PluginFlags::all());
check_roundtrip!(crate::SchedulingFlags, crate::SchedulingFlags::all());
#[cfg(feature = "v1_18")]
check_roundtrip!(crate::SeekFlags, crate::SeekFlags::all());
#[cfg(feature = "v1_18")]
check_roundtrip!(crate::SegmentFlags, crate::SegmentFlags::all());
#[cfg(feature = "v1_20")]
check_roundtrip!(crate::SerializeFlags, crate::SerializeFlags::all());
check_roundtrip!(crate::StackTraceFlags, crate::StackTraceFlags::all());
check_roundtrip!(crate::StreamFlags, crate::StreamFlags::all());
check_roundtrip!(crate::StreamType, crate::StreamType::all());
}
}

View file

@ -40,6 +40,12 @@ pub use crate::auto::*;
#[macro_use]
mod macros;
#[macro_use]
#[cfg(feature = "ser_de")]
mod serde_macros;
#[cfg(feature = "ser_de")]
pub use crate::serde_macros::*;
#[macro_use]
mod log;
pub use crate::log::*;
@ -62,6 +68,9 @@ pub use crate::value::{
#[macro_use]
mod value_serde;
#[cfg(feature = "ser_de")]
mod flag_serde;
pub mod structure;
pub use crate::structure::{Structure, StructureRef};
#[cfg(feature = "ser_de")]

View file

@ -142,9 +142,7 @@ mod tests {
" duration: Some(4),",
" offset: 0,",
" offset_end: 4,",
" flags: (",
" bits: 0,",
" ),",
" flags: \"\",",
" buffer: \"AQIDBA==\",",
" )),",
" buffer_list: None,",
@ -155,9 +153,7 @@ mod tests {
" ]), None),",
" ])),",
" segment: Some((",
" flags: (",
" bits: 9,",
" ),",
" flags: \"reset+segment\",",
" rate: 1,",
" applied_rate: 0.9,",
" format: Time,",
@ -202,17 +198,13 @@ mod tests {
" duration: Some(4),",
" offset: 0,",
" offset_end: 4,",
" flags: (",
" bits: 0,",
" ),",
" flags: \"\",",
" buffer: \"AQIDBA==\",",
" )),",
" buffer_list: None,",
" caps: None,",
" segment: Some((",
" flags: (",
" bits: 0,",
" ),",
" flags: \"\",",
" rate: 1,",
" applied_rate: 1,",
" format: Time,",
@ -244,9 +236,7 @@ mod tests {
duration: Some(4),
offset: 0,
offset_end: 4,
flags: (
bits: 0,
),
flags: "",
buffer: "AQIDBA==",
)),
buffer_list: None,
@ -257,9 +247,7 @@ mod tests {
]), None),
])),
segment: Some((
flags: (
bits: 0,
),
flags: "",
rate: 1,
applied_rate: 0.9,
format: Time,
@ -298,9 +286,7 @@ mod tests {
duration: Some(4),
offset: 0,
offset_end: 4,
flags: (
bits: 0,
),
flags: "",
buffer: "AQIDBA==",
),
]),

View file

@ -138,9 +138,7 @@ mod tests {
assert_eq!(
Ok(concat!(
"(",
" flags: (",
" bits: 9,",
" ),",
" flags: \"reset+segment\",",
" rate: 1,",
" applied_rate: 0.9,",
" format: Time,",
@ -164,9 +162,7 @@ mod tests {
let segment_ron = r#"
(
flags: (
bits: 9,
),
flags: "reset+segment",
rate: 1,
applied_rate: 0.9,
format: Time,
@ -228,9 +224,7 @@ mod tests {
let segment_ron = r#"
(
flags: (
bits: 9,
),
flags: "reset+segment",
rate: 1,
applied_rate: 0.9,
format: Time,

View file

@ -0,0 +1,145 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#[macro_export]
macro_rules! bitflags_serialize_impl {
// this implementation serializes only flags using only one bit,
// ignoring all other flags
($type:ty, single_bit_flags$(, $feature:expr)?) => {
$(#[cfg(any(feature = $feature, feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = $feature)))])?
impl serde::Serialize for $type {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let class = FlagsClass::new(Self::static_type()).unwrap();
let this = self.to_value();
let mut handled = Self::empty().to_value();
let mut res = String::new();
for v in class.values() {
let value = v.value();
if value.count_ones() == 1 && class.is_set(&this, value) && !class.is_set(&handled, value) {
if !res.is_empty() {
res.push('+');
}
res.push_str(v.nick());
handled = class.set(handled, value).expect("Failed to set flag");
}
}
serializer.serialize_str(&res)
}
}
};
// considers the flags using the most bits first
($type:ty, by_ones_decreasing$(, $feature:expr)?) => {
$(#[cfg(any(feature = $feature, feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = $feature)))])?
impl serde::Serialize for $type {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use once_cell::sync::Lazy;
let mut handled = Self::empty();
let mut res = String::new();
static SORTED_VALUES: Lazy<Vec<(u32, String)>> = Lazy::new(|| {
let class = FlagsClass::new(<$type>::static_type()).unwrap();
let mut sorted_values: Vec<(u32, String)> =
class.values().iter()
.map(|f| (f.value(), f.nick().to_owned()))
.collect();
sorted_values.sort_by(|(a, _), (b, _)| {
b.count_ones().cmp(&a.count_ones())
});
sorted_values
});
for (bits, nick) in SORTED_VALUES.iter() {
// not all values defined in the class are always also defined
// in the bitflags struct, see RTPBufferFlags for example
if let Some(value) = Self::from_bits(*bits) {
if !value.is_empty() && self.contains(value) && !handled.intersects(value) {
if !res.is_empty() {
res.push('+');
}
res.push_str(nick);
handled.insert(value);
}
}
}
serializer.serialize_str(&res)
}
}
};
}
#[macro_export]
macro_rules! bitflags_deserialize_impl {
($type:ty$(, $feature:expr)?) => {
$(#[cfg(any(feature = $feature, feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = $feature)))])?
impl<'de> serde::Deserialize<'de> for $type {
fn deserialize<D: serde::de::Deserializer<'de>>(
deserializer: D,
) -> std::result::Result<Self, D::Error> {
skip_assert_initialized!();
struct FlagsVisitor;
impl<'de> serde::de::Visitor<'de> for FlagsVisitor {
type Value = $type;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(
"one or more mask names separated by plus signs, or the empty string",
)
}
fn visit_str<E: serde::de::Error>(
self,
value: &str,
) -> std::result::Result<Self::Value, E> {
if value.is_empty() {
return Ok(Self::Value::empty());
}
let mut gvalue = glib::Value::from_type(Self::Value::static_type());
let tokens = value.split('+');
let class = FlagsClass::new(Self::Value::static_type()).unwrap();
for token in tokens {
gvalue = class.set_by_nick(gvalue, token).map_err(|_| {
serde::de::Error::custom(&format!("Invalid value: {}", token))
})?;
}
Ok(unsafe {
from_glib(glib::gobject_ffi::g_value_get_flags(
gvalue.to_glib_none().0,
))
})
}
}
deserializer.deserialize_str(FlagsVisitor)
}
}
};
}
#[macro_export]
macro_rules! bitflags_serde_impl {
($type:ty$(, $feature:expr)?) => {
$crate::bitflags_serialize_impl!($type, single_bit_flags$(, $feature)?);
$crate::bitflags_deserialize_impl!($type$(, $feature)?);
};
}

View file

@ -387,17 +387,13 @@ mod tests {
r#" duration: None,"#,
r#" offset: 0,"#,
r#" offset_end: 0,"#,
r#" flags: ("#,
r#" bits: 0,"#,
r#" ),"#,
r#" flags: "","#,
r#" buffer: "AQIDBA==","#,
r#" )),"#,
r#" buffer_list: None,"#,
r#" caps: None,"#,
r#" segment: Some(("#,
r#" flags: ("#,
r#" bits: 0,"#,
r#" ),"#,
r#" flags: "","#,
r#" rate: 1,"#,
r#" applied_rate: 1,"#,
r#" format: Time,"#,
@ -449,9 +445,7 @@ mod tests {
duration: None,
offset: 0,
offset_end: 0,
flags: (
bits: 0,
),
flags: "",
buffer: "AQIDBA==",
)),
buffer_list: None,
@ -500,7 +494,7 @@ mod tests {
["replaygain-track-gain", [1.0]],
["date",[{"YMD":[2018,5,28]}]],
["datetime",[{"YMD":[2018,5,28]}]],
["image",[{"buffer":{"pts":null,"dts":null,"duration":null,"offset":0,"offset_end":0,"flags":{"bits":0},"buffer":[1,2,3,4]},"buffer_list":null,"caps":null,"segment":null,"info":null}]]
["image",[{"buffer":{"pts":null,"dts":null,"duration":null,"offset":0,"offset_end":0,"flags":"","buffer":[1,2,3,4]},"buffer_list":null,"caps":null,"segment":null,"info":null}]]
]
}
"#;