diff --git a/gst-plugin/src/caps.rs b/gst-plugin/src/caps.rs index e8e92517..1064c10f 100644 --- a/gst-plugin/src/caps.rs +++ b/gst-plugin/src/caps.rs @@ -20,7 +20,6 @@ use std::os::raw::c_void; use std::ffi::CString; use std::ffi::CStr; use std::fmt; - use value::*; pub struct Caps(*mut c_void); diff --git a/gst-plugin/src/lib.rs b/gst-plugin/src/lib.rs index 2f5b8866..f663e9b4 100644 --- a/gst-plugin/src/lib.rs +++ b/gst-plugin/src/lib.rs @@ -42,3 +42,4 @@ pub mod log; pub mod value; pub mod caps; pub mod bytes; +pub mod tags; diff --git a/gst-plugin/src/tags.rs b/gst-plugin/src/tags.rs new file mode 100644 index 00000000..a661c900 --- /dev/null +++ b/gst-plugin/src/tags.rs @@ -0,0 +1,222 @@ +// Copyright (C) 2016 Sebastian Dröge +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Library General Public +// License as published by the Free Software Foundation; either +// version 2 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Library General Public License for more details. +// +// You should have received a copy of the GNU Library General Public +// License along with this library; if not, write to the +// Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +// Boston, MA 02110-1301, USA. + +use std::os::raw::c_void; +use std::fmt; +use libc::c_char; +use std::ffi::{CStr, CString}; +use utils::*; +use value::*; + +pub trait Tag { + type TagType: ValueType; + fn tag_name() -> &'static str; +} + +macro_rules! impl_tag( + ($name:ident, $t:ty, $tag:expr) => { + pub struct $name; + impl Tag for $name { + type TagType = $t; + fn tag_name() -> &'static str { + $tag + } + } + }; +); + +impl_tag!(Title, String, "title"); +impl_tag!(Album, String, "album"); +impl_tag!(Artist, String, "artist"); +impl_tag!(Encoder, String, "encoder"); +impl_tag!(AudioCodec, String, "audio-codec"); +impl_tag!(VideoCodec, String, "video-codec"); +impl_tag!(SubtitleCodec, String, "subtitle-codec"); +impl_tag!(ContainerFormat, String, "container-format"); +// TODO: Should ideally enforce this to be ISO-639 +impl_tag!(LanguageCode, String, "language-code"); +impl_tag!(Duration, u64, "duration"); +impl_tag!(NominalBitrate, u32, "nominal-bitrate"); + +#[repr(C)] +pub enum MergeMode { + ReplaceAll = 1, + Replace, + Append, + Prepend, + Keep, + KeepAll, +} + +pub struct TagList(*mut c_void); + +impl TagList { + pub fn new() -> Self { + extern "C" { + fn gst_tag_list_new_empty() -> *mut c_void; + } + + TagList(unsafe { gst_tag_list_new_empty() }) + } + + pub fn add(&mut self, value: T::TagType, mode: MergeMode) + where Value: From<::TagType> + { + extern "C" { + fn gst_tag_list_add_value(list: *mut c_void, + mode: u32, + tag: *const c_char, + value: *const GValue); + } + + let v = Value::from(value); + let gvalue = v.to_gvalue(); + let tag_name = CString::new(T::tag_name()).unwrap(); + + unsafe { + gst_tag_list_add_value(self.0, + mode as u32, + tag_name.as_ptr(), + &gvalue as *const GValue); + } + } + + pub fn get(&mut self) -> Option> + where Value: From<::TagType> + { + extern "C" { + fn gst_tag_list_copy_value(value: *mut GValue, + list: *mut c_void, + tag: *const c_char) + -> GBoolean; + } + + let mut gvalue = GValue::new(); + let tag_name = CString::new(T::tag_name()).unwrap(); + + let found = unsafe { + gst_tag_list_copy_value(&mut gvalue as *mut GValue, self.0, tag_name.as_ptr()) + }; + + if !found.to_bool() { + return None; + } + + match Value::from_gvalue(&gvalue) { + Some(value) => Some(TypedValue::new(value)), + None => None, + } + } + + pub fn to_string(&self) -> String { + extern "C" { + fn gst_tag_list_to_string(tag_list: *mut c_void) -> *mut c_char; + } + + unsafe { CStr::from_ptr(gst_tag_list_to_string(self.0)).to_string_lossy().into_owned() } + } + + pub unsafe fn as_ptr(&self) -> *const c_void { + self.0 + } + + pub fn make_writable(self: TagList) -> TagList { + extern "C" { + fn gst_mini_object_make_writable(obj: *mut c_void) -> *mut c_void; + } + + let raw = unsafe { gst_mini_object_make_writable(self.0) }; + + TagList(raw) + } + + pub fn copy(&self) -> TagList { + extern "C" { + fn gst_mini_object_copy(obj: *const c_void) -> *mut c_void; + } + unsafe { TagList(gst_mini_object_copy(self.0)) } + } +} + +impl Clone for TagList { + fn clone(&self) -> Self { + extern "C" { + fn gst_mini_object_ref(mini_object: *mut c_void) -> *mut c_void; + } + + unsafe { TagList(gst_mini_object_ref(self.0)) } + } +} + +impl Drop for TagList { + fn drop(&mut self) { + extern "C" { + fn gst_mini_object_unref(mini_object: *mut c_void); + } + + unsafe { gst_mini_object_unref(self.0) } + } +} + +impl fmt::Debug for TagList { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&self.to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use value::*; + use std::ptr; + use std::os::raw::c_void; + + fn init() { + extern "C" { + fn gst_init(argc: *mut c_void, argv: *mut c_void); + } + + unsafe { + gst_init(ptr::null_mut(), ptr::null_mut()); + } + } + + #[test] + fn test_add() { + init(); + + let mut tags = TagList::new(); + assert_eq!(tags.to_string(), "taglist;"); + tags.add::("some title".into(), MergeMode::Append); + tags.add::<Duration>((1000u64 * 1000 * 1000 * 120).into(), MergeMode::Append); + assert_eq!(tags.to_string(), + "taglist, title=(string)\"some\\ title\", duration=(guint64)120000000000;"); + } + + #[test] + fn test_get() { + init(); + + let mut tags = TagList::new(); + assert_eq!(tags.to_string(), "taglist;"); + tags.add::<Title>("some title".into(), MergeMode::Append); + tags.add::<Duration>((1000u64 * 1000 * 1000 * 120).into(), MergeMode::Append); + + assert_eq!(*tags.get::<Title>().unwrap(), "some title"); + assert_eq!(*tags.get::<Duration>().unwrap(), (1000u64 * 1000 * 1000 * 120)); + } +} diff --git a/gst-plugin/src/utils.rs b/gst-plugin/src/utils.rs index ec6e2b27..51e5f86f 100644 --- a/gst-plugin/src/utils.rs +++ b/gst-plugin/src/utils.rs @@ -189,6 +189,7 @@ pub fn gcd(mut a: u32, mut b: u32) -> u32 { #[cfg(test)] mod tests { use super::*; + use num_rational::Rational32; #[test] fn test_gcd() { @@ -198,9 +199,10 @@ mod tests { #[test] fn test_f64_to_fraction() { - assert_eq!(f64_to_fraction(2.0), Some((2, 1))); - assert_eq!(f64_to_fraction(2.5), Some((5, 2))); - assert_eq!(f64_to_fraction(0.127659574), Some((29013539, 227272723))); - assert_eq!(f64_to_fraction(29.97), Some((2997, 100))); + assert_eq!(f64_to_fraction(2.0), Some(Rational32::new(2, 1))); + assert_eq!(f64_to_fraction(2.5), Some(Rational32::new(5, 2))); + assert_eq!(f64_to_fraction(0.127659574), + Some(Rational32::new(29013539, 227272723))); + assert_eq!(f64_to_fraction(29.97), Some(Rational32::new(2997, 100))); } } diff --git a/gst-plugin/src/value.rs b/gst-plugin/src/value.rs index ed1b0a35..2e63977a 100644 --- a/gst-plugin/src/value.rs +++ b/gst-plugin/src/value.rs @@ -17,8 +17,10 @@ use libc::c_char; use std::os::raw::c_void; -use std::ffi::CString; +use std::ffi::{CString, CStr}; use std::mem; +use std::marker::PhantomData; +use std::ops::Deref; pub use num_rational::Rational32; @@ -28,75 +30,51 @@ use buffer::*; pub enum Value { Bool(bool), Int(i32), + UInt(u32), + Int64(i64), + UInt64(u64), String(String), Fraction(Rational32), Buffer(Buffer), Array(Vec<Value>), } +pub trait ValueType { + fn extract(v: &Value) -> Option<&Self>; +} + +macro_rules! impl_value_type( + ($t:ty, $variant:ident) => { + impl ValueType for $t { + fn extract(v: &Value) -> Option<&$t> { + if let Value::$variant(ref b) = *v { + return Some(b); + } + None + } + } + }; +); + +impl_value_type!(bool, Bool); +impl_value_type!(i32, Int); +impl_value_type!(u32, UInt); +impl_value_type!(i64, Int64); +impl_value_type!(u64, UInt64); +impl_value_type!(String, String); +impl_value_type!(Rational32, Fraction); +impl_value_type!(Buffer, Buffer); +impl_value_type!(Vec<Value>, Array); + #[repr(C)] pub struct GValue { typ: usize, data: [u64; 2], } -// See gtype.h -const TYPE_BOOLEAN: usize = (5 << 2); -const TYPE_INT: usize = (6 << 2); -const TYPE_STRING: usize = (16 << 2); - -impl Value { - pub fn to_gvalue(&self) -> GValue { - extern "C" { - fn g_value_init(value: *mut GValue, gtype: usize); - fn g_value_set_boolean(value: *mut GValue, value: i32); - fn g_value_set_int(value: *mut GValue, value: i32); - fn g_value_set_string(value: *mut GValue, value: *const c_char); - fn gst_value_set_fraction(value: *mut GValue, value_n: i32, value_d: i32); - fn gst_fraction_get_type() -> usize; - fn g_value_set_boxed(value: *mut GValue, boxed: *const c_void); - fn gst_buffer_get_type() -> usize; - fn gst_value_array_get_type() -> usize; - fn gst_value_array_append_and_take_value(value: *mut GValue, element: *mut GValue); - } - - let mut gvalue: GValue = unsafe { mem::zeroed() }; - - match *self { - Value::Bool(v) => unsafe { - g_value_init(&mut gvalue as *mut GValue, TYPE_BOOLEAN); - g_value_set_boolean(&mut gvalue as *mut GValue, if v { 1 } else { 0 }); - }, - Value::Int(v) => unsafe { - g_value_init(&mut gvalue as *mut GValue, TYPE_INT); - g_value_set_int(&mut gvalue as *mut GValue, v); - }, - Value::String(ref v) => unsafe { - let v_cstr = CString::new(String::from(v.clone())).unwrap(); - - g_value_init(&mut gvalue as *mut GValue, TYPE_STRING); - g_value_set_string(&mut gvalue as *mut GValue, v_cstr.as_ptr()); - }, - Value::Fraction(ref v) => unsafe { - g_value_init(&mut gvalue as *mut GValue, gst_fraction_get_type()); - gst_value_set_fraction(&mut gvalue as *mut GValue, *v.numer(), *v.denom()); - }, - Value::Buffer(ref buffer) => unsafe { - g_value_init(&mut gvalue as *mut GValue, gst_buffer_get_type()); - g_value_set_boxed(&mut gvalue as *mut GValue, buffer.as_ptr()); - }, - Value::Array(ref array) => unsafe { - g_value_init(&mut gvalue as *mut GValue, gst_value_array_get_type()); - - for e in array { - let mut e_value = e.to_gvalue(); - gst_value_array_append_and_take_value(&mut gvalue as *mut GValue, - &mut e_value as *mut GValue); - // Takes ownership, invalidate GValue - e_value.typ = 0; - } - }, - } +impl GValue { + pub fn new() -> GValue { + let gvalue: GValue = unsafe { mem::zeroed() }; gvalue } @@ -114,6 +92,180 @@ impl Drop for GValue { } } +// See gtype.h +const TYPE_BOOLEAN: usize = (5 << 2); +const TYPE_INT: usize = (6 << 2); +const TYPE_UINT: usize = (7 << 2); +const TYPE_INT64: usize = (10 << 2); +const TYPE_UINT64: usize = (11 << 2); +const TYPE_STRING: usize = (16 << 2); + +extern "C" { + fn gst_buffer_get_type() -> usize; + fn gst_fraction_get_type() -> usize; + fn gst_value_array_get_type() -> usize; +} + +lazy_static! { + static ref TYPE_BUFFER: usize = unsafe { gst_buffer_get_type() }; + static ref TYPE_FRACTION: usize = unsafe { gst_fraction_get_type() }; + static ref TYPE_GST_VALUE_ARRAY: usize = unsafe { gst_value_array_get_type() }; +} + +impl Value { + pub fn to_gvalue(&self) -> GValue { + extern "C" { + fn g_value_init(value: *mut GValue, gtype: usize); + fn g_value_set_boolean(value: *mut GValue, value: i32); + fn g_value_set_int(value: *mut GValue, value: i32); + fn g_value_set_uint(value: *mut GValue, value: u32); + fn g_value_set_int64(value: *mut GValue, value: i64); + fn g_value_set_uint64(value: *mut GValue, value: u64); + fn g_value_set_string(value: *mut GValue, value: *const c_char); + fn gst_value_set_fraction(value: *mut GValue, value_n: i32, value_d: i32); + fn g_value_set_boxed(value: *mut GValue, boxed: *const c_void); + fn gst_value_array_append_and_take_value(value: *mut GValue, element: *mut GValue); + } + + let mut gvalue = GValue::new(); + + match *self { + Value::Bool(v) => unsafe { + g_value_init(&mut gvalue as *mut GValue, TYPE_BOOLEAN); + g_value_set_boolean(&mut gvalue as *mut GValue, if v { 1 } else { 0 }); + }, + Value::Int(v) => unsafe { + g_value_init(&mut gvalue as *mut GValue, TYPE_INT); + g_value_set_int(&mut gvalue as *mut GValue, v); + }, + Value::UInt(v) => unsafe { + g_value_init(&mut gvalue as *mut GValue, TYPE_UINT); + g_value_set_uint(&mut gvalue as *mut GValue, v); + }, + Value::Int64(v) => unsafe { + g_value_init(&mut gvalue as *mut GValue, TYPE_INT64); + g_value_set_int64(&mut gvalue as *mut GValue, v); + }, + Value::UInt64(v) => unsafe { + g_value_init(&mut gvalue as *mut GValue, TYPE_UINT64); + g_value_set_uint64(&mut gvalue as *mut GValue, v); + }, + Value::String(ref v) => unsafe { + let v_cstr = CString::new(String::from(v.clone())).unwrap(); + + g_value_init(&mut gvalue as *mut GValue, TYPE_STRING); + g_value_set_string(&mut gvalue as *mut GValue, v_cstr.as_ptr()); + }, + Value::Fraction(ref v) => unsafe { + g_value_init(&mut gvalue as *mut GValue, *TYPE_FRACTION); + gst_value_set_fraction(&mut gvalue as *mut GValue, *v.numer(), *v.denom()); + }, + Value::Buffer(ref buffer) => unsafe { + g_value_init(&mut gvalue as *mut GValue, *TYPE_BUFFER); + g_value_set_boxed(&mut gvalue as *mut GValue, buffer.as_ptr()); + }, + Value::Array(ref array) => unsafe { + g_value_init(&mut gvalue as *mut GValue, *TYPE_GST_VALUE_ARRAY); + + for e in array { + let mut e_value = e.to_gvalue(); + gst_value_array_append_and_take_value(&mut gvalue as *mut GValue, + &mut e_value as *mut GValue); + // Takes ownership, invalidate GValue + e_value.typ = 0; + } + }, + } + + gvalue + } + + pub fn from_gvalue(gvalue: &GValue) -> Option<Self> { + extern "C" { + fn g_value_get_boolean(value: *const GValue) -> i32; + fn g_value_get_int(value: *const GValue) -> i32; + fn g_value_get_uint(value: *const GValue) -> u32; + fn g_value_get_int64(value: *const GValue) -> i64; + fn g_value_get_uint64(value: *const GValue) -> u64; + fn g_value_get_string(value: *const GValue) -> *const c_char; + fn gst_value_get_fraction_numerator(value: *const GValue) -> i32; + fn gst_value_get_fraction_denominator(value: *const GValue) -> i32; + fn g_value_get_boxed(value: *const GValue) -> *mut c_void; + fn gst_value_array_get_size(value: *const GValue) -> u32; + fn gst_value_array_get_value(value: *const GValue, index: u32) -> *const GValue; + } + + match gvalue.typ { + TYPE_BOOLEAN => unsafe { + Some(Value::Bool(if g_value_get_boolean(gvalue as *const GValue) == 0 { + false + } else { + true + })) + }, + TYPE_INT => unsafe { Some(Value::Int(g_value_get_int(gvalue as *const GValue))) }, + TYPE_UINT => unsafe { Some(Value::UInt(g_value_get_uint(gvalue as *const GValue))) }, + TYPE_INT64 => unsafe { Some(Value::Int64(g_value_get_int64(gvalue as *const GValue))) }, + TYPE_UINT64 => unsafe { + Some(Value::UInt64(g_value_get_uint64(gvalue as *const GValue))) + }, + TYPE_STRING => unsafe { + let s = g_value_get_string(gvalue as *const GValue); + if s.is_null() { + return None; + } + + let cstr = CStr::from_ptr(s); + match cstr.to_str() { + Err(_) => None, + Ok(s) => Some(Value::String(s.into())), + } + }, + typ if typ == *TYPE_FRACTION => unsafe { + let n = gst_value_get_fraction_numerator(gvalue as *const GValue); + let d = gst_value_get_fraction_denominator(gvalue as *const GValue); + + Some(Value::Fraction(Rational32::new(n, d))) + }, + typ if typ == *TYPE_BUFFER => unsafe { + let b = g_value_get_boxed(gvalue as *const GValue); + + if b.is_null() { + return None; + } + + Some(Value::Buffer(Buffer::new_from_ptr(b))) + }, + typ if typ == *TYPE_GST_VALUE_ARRAY => unsafe { + let n = gst_value_array_get_size(gvalue as *const GValue); + + let mut vec = Vec::with_capacity(n as usize); + + for i in 0..n { + let val = gst_value_array_get_value(gvalue as *const GValue, i); + + if val.is_null() { + return None; + } + + if let Some(val) = Value::from_gvalue(&*val) { + vec.push(val); + } else { + return None; + } + } + + Some(Value::Array(vec)) + }, + _ => None, + } + } + + pub fn try_get<T: ValueType>(&self) -> Option<&T> { + T::extract(self) + } +} + impl From<bool> for Value { fn from(f: bool) -> Value { Value::Bool(f) @@ -126,6 +278,24 @@ impl From<i32> for Value { } } +impl From<u32> for Value { + fn from(f: u32) -> Value { + Value::UInt(f) + } +} + +impl From<i64> for Value { + fn from(f: i64) -> Value { + Value::Int64(f) + } +} + +impl From<u64> for Value { + fn from(f: u64) -> Value { + Value::UInt64(f) + } +} + impl From<String> for Value { fn from(f: String) -> Value { Value::String(f) @@ -173,3 +343,83 @@ impl<'a> From<&'a [Value]> for Value { Value::Array(f.to_vec()) } } + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TypedValue<T> + where T: ValueType +{ + value: Value, + phantom: PhantomData<T>, +} + +impl<T> TypedValue<T> + where T: ValueType +{ + pub fn new(value: Value) -> Self { + TypedValue { + value: value, + phantom: PhantomData, + } + } + + pub fn into_value(self) -> Value { + self.value + } +} + +impl<T> Deref for TypedValue<T> + where T: ValueType +{ + type Target = T; + fn deref(&self) -> &T { + self.value.try_get().unwrap() + } +} + +impl<T> AsRef<T> for TypedValue<T> + where T: ValueType +{ + fn as_ref(&self) -> &T { + self.value.try_get().unwrap() + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct TypedValueRef<'a, T> + where T: ValueType +{ + value: &'a Value, + phantom: PhantomData<T>, +} + +impl<'a, T> TypedValueRef<'a, T> + where T: ValueType +{ + pub fn new(value: &'a Value) -> TypedValueRef<'a, T> { + TypedValueRef { + value: value, + phantom: PhantomData, + } + } + + pub fn value(self) -> &'a Value { + self.value + } +} + +impl<'a, T> Deref for TypedValueRef<'a, T> + where T: ValueType +{ + type Target = T; + fn deref(&self) -> &T { + self.value.try_get().unwrap() + } +} + +impl<'a, T> AsRef<T> for TypedValueRef<'a, T> + where T: ValueType +{ + fn as_ref(&self) -> &T { + self.value.try_get().unwrap() + } +}