format: split input and output formats
This commit is contained in:
parent
fbf5cd94eb
commit
1f80007e5e
@ -123,7 +123,7 @@ impl Packet {
|
||||
SideDataIter::new(&self.0)
|
||||
}
|
||||
|
||||
pub fn read(&mut self, format: &mut format::Context) -> Result<(), Error> {
|
||||
pub fn read(&mut self, format: &mut format::context::Input) -> Result<(), Error> {
|
||||
unsafe {
|
||||
match av_read_frame(format.as_mut_ptr(), self.as_mut_ptr()) {
|
||||
0 => Ok(()),
|
||||
@ -132,7 +132,7 @@ impl Packet {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, format: &mut format::Context) -> Result<bool, Error> {
|
||||
pub fn write(&self, format: &mut format::context::Output) -> Result<bool, Error> {
|
||||
unsafe {
|
||||
match av_write_frame(format.as_mut_ptr(), self.as_ptr()) {
|
||||
1 => Ok(true),
|
||||
@ -142,7 +142,7 @@ impl Packet {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_interleaved(&self, format: &mut format::Context) -> Result<bool, Error> {
|
||||
pub fn write_interleaved(&self, format: &mut format::context::Output) -> Result<bool, Error> {
|
||||
unsafe {
|
||||
match av_interleaved_write_frame(format.as_mut_ptr(), self.as_ptr()) {
|
||||
1 => Ok(true),
|
||||
|
@ -3,7 +3,7 @@ use std::marker::PhantomData;
|
||||
|
||||
use ffi::*;
|
||||
use ::Error;
|
||||
use ::format::Context;
|
||||
use ::format::context::common::Context;
|
||||
use ::device;
|
||||
use libc::c_int;
|
||||
|
||||
|
@ -1,419 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr;
|
||||
|
||||
use libc::{c_int, c_uint};
|
||||
use ffi::*;
|
||||
use ::{Error, Codec, Stream, StreamMut, Packet, Dictionary, media};
|
||||
|
||||
pub struct Context {
|
||||
ptr: *mut AVFormatContext,
|
||||
|
||||
_input: bool,
|
||||
}
|
||||
|
||||
unsafe impl Send for Context { }
|
||||
|
||||
impl Context {
|
||||
pub unsafe fn input(ptr: *mut AVFormatContext) -> Self {
|
||||
Context {
|
||||
ptr: ptr,
|
||||
|
||||
_input: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn output(ptr: *mut AVFormatContext) -> Self {
|
||||
Context {
|
||||
ptr: ptr,
|
||||
|
||||
_input: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn as_ptr(&self) -> *const AVFormatContext {
|
||||
self.ptr as *const _
|
||||
}
|
||||
|
||||
pub unsafe fn as_mut_ptr(&mut self) -> *mut AVFormatContext {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new() -> Self {
|
||||
unsafe {
|
||||
Context {
|
||||
ptr: avformat_alloc_context(),
|
||||
|
||||
_input: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_input(&self) -> bool {
|
||||
self._input
|
||||
}
|
||||
|
||||
pub fn is_output(&self) -> bool {
|
||||
!self._input
|
||||
}
|
||||
|
||||
pub fn write_header(&mut self) -> Result<(), Error> {
|
||||
unsafe {
|
||||
match avformat_write_header(self.as_mut_ptr(), ptr::null_mut()) {
|
||||
0 => Ok(()),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_header_with(&mut self, options: Dictionary) -> Result<(), Error> {
|
||||
unsafe {
|
||||
let mut opts = options.take();
|
||||
let status = avformat_write_header(self.as_mut_ptr(), &mut opts);
|
||||
|
||||
Dictionary::own(opts);
|
||||
|
||||
match status {
|
||||
0 => Ok(()),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_trailer(&mut self) -> Result<(), Error> {
|
||||
unsafe {
|
||||
match av_write_trailer(self.as_mut_ptr()) {
|
||||
0 => Ok(()),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stream(&self, index: usize) -> Option<Stream> {
|
||||
unsafe {
|
||||
if index >= (*self.as_ptr()).nb_streams as usize {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(Stream::wrap(*(*self.ptr).streams.offset(index as isize)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stream_mut(&mut self, index: usize) -> Option<StreamMut> {
|
||||
unsafe {
|
||||
if index >= (*self.as_ptr()).nb_streams as usize {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(StreamMut::wrap(*(*self.ptr).streams.offset(index as isize)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn streams(&self) -> StreamIter {
|
||||
unsafe {
|
||||
StreamIter::new(self.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn streams_mut(&mut self) -> StreamIterMut {
|
||||
unsafe {
|
||||
StreamIterMut::new(self.as_mut_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_stream(&mut self, codec: &Codec) -> Option<StreamMut> {
|
||||
unsafe {
|
||||
let ptr = avformat_new_stream(self.as_mut_ptr(), codec.as_ptr());
|
||||
|
||||
if ptr.is_null() {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(StreamMut::wrap(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> Dictionary {
|
||||
unsafe {
|
||||
Dictionary::wrap((*self.as_ptr()).metadata)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn probe_score(&self) -> i32 {
|
||||
unsafe {
|
||||
av_format_get_probe_score(self.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn video_codec(&self) -> Option<Codec> {
|
||||
unsafe {
|
||||
let ptr = av_format_get_video_codec(self.as_ptr());
|
||||
|
||||
if ptr.is_null() {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(Codec::wrap(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_video_codec(&mut self, mut value: Codec) {
|
||||
unsafe {
|
||||
av_format_set_video_codec(self.as_mut_ptr(), value.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn audio_codec(&self) -> Option<Codec> {
|
||||
unsafe {
|
||||
let ptr = av_format_get_audio_codec(self.as_ptr());
|
||||
|
||||
if ptr.is_null() {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(Codec::wrap(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_audio_codec(&mut self, mut value: Codec) {
|
||||
unsafe {
|
||||
av_format_set_audio_codec(self.as_mut_ptr(), value.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subtitle_codec(&self) -> Option<Codec> {
|
||||
unsafe {
|
||||
let ptr = av_format_get_subtitle_codec(self.as_ptr());
|
||||
|
||||
if ptr.is_null() {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(Codec::wrap(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_subtitle_codec(&mut self, mut value: Codec) {
|
||||
unsafe {
|
||||
av_format_set_subtitle_codec(self.as_mut_ptr(), value.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data_codec(&self) -> Option<Codec> {
|
||||
unsafe {
|
||||
let ptr = av_format_get_data_codec(self.as_ptr());
|
||||
|
||||
if ptr.is_null() {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(Codec::wrap(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_data_codec(&mut self, mut value: Codec) {
|
||||
unsafe {
|
||||
av_format_set_data_codec(self.as_mut_ptr(), value.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn packets(&mut self) -> PacketIter {
|
||||
PacketIter::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Context {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if self._input {
|
||||
avformat_close_input(&mut self.as_mut_ptr());
|
||||
}
|
||||
else {
|
||||
avformat_free_context(self.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Best<'a> {
|
||||
ptr: *const AVFormatContext,
|
||||
|
||||
wanted: i32,
|
||||
related: i32,
|
||||
|
||||
_marker: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Best<'a> {
|
||||
pub unsafe fn new<'b>(ptr: *const AVFormatContext) -> Best<'b> {
|
||||
Best {
|
||||
ptr: ptr,
|
||||
|
||||
wanted: -1,
|
||||
related: -1,
|
||||
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wanted<'b: 'a>(mut self, stream: &'b Stream) -> Best<'a> {
|
||||
self.wanted = stream.index() as i32;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn related<'b: 'a>(mut self, stream: &'b Stream) -> Best<'a> {
|
||||
self.related = stream.index() as i32;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn best(self, kind: media::Type) -> Option<Stream<'a>> {
|
||||
unsafe {
|
||||
let mut decoder = ptr::null_mut();
|
||||
let index = av_find_best_stream(self.ptr,
|
||||
kind.into(), self.wanted as c_int, self.related as c_int,
|
||||
&mut decoder, 0);
|
||||
|
||||
if index >= 0 && !decoder.is_null() {
|
||||
Some(Stream::wrap(*(*self.ptr).streams.offset(index as isize)))
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StreamIter<'a> {
|
||||
ptr: *const AVFormatContext,
|
||||
cur: c_uint,
|
||||
|
||||
_marker: PhantomData<&'a Context>,
|
||||
}
|
||||
|
||||
impl<'a> StreamIter<'a> {
|
||||
pub fn new(ptr: *const AVFormatContext) -> Self {
|
||||
StreamIter { ptr: ptr, cur: 0, _marker: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> StreamIter<'a> {
|
||||
pub fn wanted<'b: 'a>(&'a self, stream: &'b Stream) -> Best<'a> {
|
||||
unsafe {
|
||||
Best::new(self.ptr).wanted(stream)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn related<'b: 'a>(&'a self, stream: &'b Stream) -> Best<'a> {
|
||||
unsafe {
|
||||
Best::new(self.ptr).related(stream)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn best(&'a self, kind: media::Type) -> Option<Stream<'a>> {
|
||||
unsafe {
|
||||
Best::new(self.ptr).best(kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for StreamIter<'a> {
|
||||
type Item = Stream<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
unsafe {
|
||||
if self.cur >= (*self.ptr).nb_streams {
|
||||
None
|
||||
}
|
||||
else {
|
||||
self.cur += 1;
|
||||
Some(Stream::wrap(*(*self.ptr).streams.offset((self.cur - 1) as isize)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
unsafe {
|
||||
((*self.ptr).nb_streams as usize, Some((*self.ptr).nb_streams as usize))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for StreamIter<'a> { }
|
||||
|
||||
pub struct StreamIterMut<'a> {
|
||||
ptr: *const AVFormatContext,
|
||||
cur: c_uint,
|
||||
|
||||
_marker: PhantomData<&'a Context>,
|
||||
}
|
||||
|
||||
impl<'a> StreamIterMut<'a> {
|
||||
pub fn new(ptr: *mut AVFormatContext) -> Self {
|
||||
StreamIterMut { ptr: ptr, cur: 0, _marker: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for StreamIterMut<'a> {
|
||||
type Item = StreamMut<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
unsafe {
|
||||
if self.cur >= (*self.ptr).nb_streams {
|
||||
None
|
||||
}
|
||||
else {
|
||||
self.cur += 1;
|
||||
Some(StreamMut::wrap(*(*self.ptr).streams.offset((self.cur - 1) as isize)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
unsafe {
|
||||
((*self.ptr).nb_streams as usize, Some((*self.ptr).nb_streams as usize))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for StreamIterMut<'a> { }
|
||||
|
||||
pub struct PacketIter<'a> {
|
||||
context: &'a mut Context,
|
||||
}
|
||||
|
||||
impl<'a> PacketIter<'a> {
|
||||
pub fn new(context: &mut Context) -> PacketIter {
|
||||
PacketIter { context: context }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PacketIter<'a> {
|
||||
type Item = (Stream<'a>, Packet);
|
||||
|
||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
let mut packet = Packet::empty();
|
||||
|
||||
loop {
|
||||
match packet.read(self.context) {
|
||||
Ok(..) =>
|
||||
return Some((unsafe {
|
||||
Stream::wrap(*(*self.context.as_ptr()).streams.offset(packet.stream() as isize))
|
||||
}, packet)),
|
||||
|
||||
Err(Error::Eof) =>
|
||||
return None,
|
||||
|
||||
Err(..) =>
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
210
src/format/context/common.rs
Normal file
210
src/format/context/common.rs
Normal file
@ -0,0 +1,210 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::ptr;
|
||||
|
||||
use ffi::*;
|
||||
use libc::{c_int, c_uint};
|
||||
use ::{media, Stream, StreamMut, Dictionary};
|
||||
|
||||
pub struct Context {
|
||||
ptr: *mut AVFormatContext,
|
||||
}
|
||||
|
||||
unsafe impl Send for Context { }
|
||||
|
||||
impl Context {
|
||||
pub unsafe fn wrap(ptr: *mut AVFormatContext) -> Self {
|
||||
Context { ptr: ptr }
|
||||
}
|
||||
|
||||
pub unsafe fn as_ptr(&self) -> *const AVFormatContext {
|
||||
self.ptr as *const _
|
||||
}
|
||||
|
||||
pub unsafe fn as_mut_ptr(&mut self) -> *mut AVFormatContext {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn stream<'a, 'b>(&'a self, index: usize) -> Option<Stream<'b>> where 'a: 'b {
|
||||
unsafe {
|
||||
if index >= (*self.as_ptr()).nb_streams as usize {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(Stream::wrap(*(*self.as_ptr()).streams.offset(index as isize)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stream_mut<'a, 'b>(&'a mut self, index: usize) -> Option<StreamMut<'b>> where 'a: 'b {
|
||||
unsafe {
|
||||
if index >= (*self.as_ptr()).nb_streams as usize {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(StreamMut::wrap(*(*self.as_mut_ptr()).streams.offset(index as isize)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn streams(&self) -> StreamIter {
|
||||
unsafe {
|
||||
StreamIter::new(self.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn streams_mut(&mut self) -> StreamIterMut {
|
||||
unsafe {
|
||||
StreamIterMut::new(self.as_mut_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> Dictionary {
|
||||
unsafe {
|
||||
Dictionary::wrap((*self.as_ptr()).metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Best<'a> {
|
||||
ptr: *const AVFormatContext,
|
||||
|
||||
wanted: i32,
|
||||
related: i32,
|
||||
|
||||
_marker: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Best<'a> {
|
||||
pub unsafe fn new<'b>(ptr: *const AVFormatContext) -> Best<'b> {
|
||||
Best {
|
||||
ptr: ptr,
|
||||
|
||||
wanted: -1,
|
||||
related: -1,
|
||||
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wanted<'b>(mut self, stream: &'b Stream) -> Best<'a> where 'a: 'b {
|
||||
self.wanted = stream.index() as i32;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn related<'b>(mut self, stream: &'b Stream) -> Best<'a> where 'a: 'b {
|
||||
self.related = stream.index() as i32;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn best<'b>(self, kind: media::Type) -> Option<Stream<'b>> where 'a: 'b {
|
||||
unsafe {
|
||||
let mut decoder = ptr::null_mut();
|
||||
let index = av_find_best_stream(self.ptr,
|
||||
kind.into(), self.wanted as c_int, self.related as c_int,
|
||||
&mut decoder, 0);
|
||||
|
||||
if index >= 0 && !decoder.is_null() {
|
||||
Some(Stream::wrap(*(*self.ptr).streams.offset(index as isize)))
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StreamIter<'a> {
|
||||
ptr: *const AVFormatContext,
|
||||
cur: c_uint,
|
||||
|
||||
_marker: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> StreamIter<'a> {
|
||||
pub fn new(ptr: *const AVFormatContext) -> Self {
|
||||
StreamIter { ptr: ptr, cur: 0, _marker: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> StreamIter<'a> {
|
||||
pub fn wanted<'b: 'a, 'c: 'a>(&'a self, stream: &'b Stream) -> Best<'a> {
|
||||
unsafe {
|
||||
Best::new(self.ptr).wanted(stream)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn related<'b: 'a>(&'a self, stream: &'b Stream) -> Best<'a> {
|
||||
unsafe {
|
||||
Best::new(self.ptr).related(stream)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn best<'b: 'a>(&'a self, kind: media::Type) -> Option<Stream<'b>> {
|
||||
unsafe {
|
||||
Best::new(self.ptr).best(kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for StreamIter<'a> {
|
||||
type Item = Stream<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
unsafe {
|
||||
if self.cur >= (*self.ptr).nb_streams {
|
||||
None
|
||||
}
|
||||
else {
|
||||
self.cur += 1;
|
||||
Some(Stream::wrap(*(*self.ptr).streams.offset((self.cur - 1) as isize)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
unsafe {
|
||||
((*self.ptr).nb_streams as usize, Some((*self.ptr).nb_streams as usize))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for StreamIter<'a> { }
|
||||
|
||||
pub struct StreamIterMut<'a> {
|
||||
ptr: *const AVFormatContext,
|
||||
cur: c_uint,
|
||||
|
||||
_marker: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> StreamIterMut<'a> {
|
||||
pub fn new(ptr: *mut AVFormatContext) -> Self {
|
||||
StreamIterMut { ptr: ptr, cur: 0, _marker: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for StreamIterMut<'a> {
|
||||
type Item = StreamMut<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
unsafe {
|
||||
if self.cur >= (*self.ptr).nb_streams {
|
||||
None
|
||||
}
|
||||
else {
|
||||
self.cur += 1;
|
||||
Some(StreamMut::wrap(*(*self.ptr).streams.offset((self.cur - 1) as isize)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
unsafe {
|
||||
((*self.ptr).nb_streams as usize, Some((*self.ptr).nb_streams as usize))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for StreamIterMut<'a> { }
|
180
src/format/context/input.rs
Normal file
180
src/format/context/input.rs
Normal file
@ -0,0 +1,180 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
use std::ffi::CString;
|
||||
|
||||
use ffi::*;
|
||||
use ::{Error, Codec, Stream, Packet};
|
||||
use super::common::Context;
|
||||
|
||||
pub struct Input {
|
||||
ptr: *mut AVFormatContext,
|
||||
ctx: Context,
|
||||
}
|
||||
|
||||
unsafe impl Send for Input { }
|
||||
|
||||
impl Input {
|
||||
pub unsafe fn wrap(ptr: *mut AVFormatContext) -> Self {
|
||||
Input { ptr: ptr, ctx: Context::wrap(ptr) }
|
||||
}
|
||||
|
||||
pub unsafe fn as_ptr(&self) -> *const AVFormatContext {
|
||||
self.ptr as *const _
|
||||
}
|
||||
|
||||
pub unsafe fn as_mut_ptr(&mut self) -> *mut AVFormatContext {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl Input {
|
||||
pub fn video_codec(&self) -> Option<Codec> {
|
||||
unsafe {
|
||||
let ptr = av_format_get_video_codec(self.as_ptr());
|
||||
|
||||
if ptr.is_null() {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(Codec::wrap(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_video_codec(&mut self, mut value: Codec) {
|
||||
unsafe {
|
||||
av_format_set_video_codec(self.as_mut_ptr(), value.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn audio_codec(&self) -> Option<Codec> {
|
||||
unsafe {
|
||||
let ptr = av_format_get_audio_codec(self.as_ptr());
|
||||
|
||||
if ptr.is_null() {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(Codec::wrap(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_audio_codec(&mut self, mut value: Codec) {
|
||||
unsafe {
|
||||
av_format_set_audio_codec(self.as_mut_ptr(), value.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subtitle_codec(&self) -> Option<Codec> {
|
||||
unsafe {
|
||||
let ptr = av_format_get_subtitle_codec(self.as_ptr());
|
||||
|
||||
if ptr.is_null() {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(Codec::wrap(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_subtitle_codec(&mut self, mut value: Codec) {
|
||||
unsafe {
|
||||
av_format_set_subtitle_codec(self.as_mut_ptr(), value.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn data_codec(&self) -> Option<Codec> {
|
||||
unsafe {
|
||||
let ptr = av_format_get_data_codec(self.as_ptr());
|
||||
|
||||
if ptr.is_null() {
|
||||
None
|
||||
}
|
||||
else {
|
||||
Some(Codec::wrap(ptr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_data_codec(&mut self, mut value: Codec) {
|
||||
unsafe {
|
||||
av_format_set_data_codec(self.as_mut_ptr(), value.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn probe_score(&self) -> i32 {
|
||||
unsafe {
|
||||
av_format_get_probe_score(self.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn packets(&mut self) -> PacketIter {
|
||||
PacketIter::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Input {
|
||||
type Target = Context;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Input {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Input {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
avformat_close_input(&mut self.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PacketIter<'a> {
|
||||
context: &'a mut Input,
|
||||
}
|
||||
|
||||
impl<'a> PacketIter<'a> {
|
||||
pub fn new(context: &mut Input) -> PacketIter {
|
||||
PacketIter { context: context }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PacketIter<'a> {
|
||||
type Item = (Stream<'a>, Packet);
|
||||
|
||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
let mut packet = Packet::empty();
|
||||
|
||||
loop {
|
||||
match packet.read(self.context) {
|
||||
Ok(..) =>
|
||||
return Some((unsafe {
|
||||
Stream::wrap(*(*self.context.as_ptr()).streams.offset(packet.stream() as isize))
|
||||
}, packet)),
|
||||
|
||||
Err(Error::Eof) =>
|
||||
return None,
|
||||
|
||||
Err(..) =>
|
||||
()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump(ctx: &Input, index: i32, url: Option<&str>) {
|
||||
let url = url.map(|u| CString::new(u).unwrap());
|
||||
|
||||
unsafe {
|
||||
av_dump_format(ctx.as_ptr(), index,
|
||||
url.map(|u| u.as_ptr()).unwrap_or(ptr::null()), 0);
|
||||
}
|
||||
}
|
51
src/format/context/mod.rs
Normal file
51
src/format/context/mod.rs
Normal file
@ -0,0 +1,51 @@
|
||||
pub mod input;
|
||||
pub use self::input::Input;
|
||||
|
||||
pub mod output;
|
||||
pub use self::output::Output;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod common;
|
||||
|
||||
pub enum Context {
|
||||
Input(Input),
|
||||
Output(Output),
|
||||
}
|
||||
|
||||
unsafe impl Send for Context { }
|
||||
|
||||
impl Context {
|
||||
pub fn is_input(&self) -> bool {
|
||||
if let &Context::Input(..) = self {
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn input(self) -> Input {
|
||||
if let Context::Input(context) = self {
|
||||
return context;
|
||||
}
|
||||
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
pub fn is_output(&self) -> bool {
|
||||
if let &Context::Output(..) = self {
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn output(self) -> Output {
|
||||
if let Context::Output(context) = self {
|
||||
return context;
|
||||
}
|
||||
|
||||
unreachable!();
|
||||
}
|
||||
}
|
105
src/format/context/output.rs
Normal file
105
src/format/context/output.rs
Normal file
@ -0,0 +1,105 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::ptr;
|
||||
use std::ffi::CString;
|
||||
|
||||
use ffi::*;
|
||||
use ::{Error, Codec, StreamMut, Dictionary};
|
||||
use super::common::Context;
|
||||
|
||||
pub struct Output {
|
||||
ptr: *mut AVFormatContext,
|
||||
ctx: Context,
|
||||
}
|
||||
|
||||
unsafe impl Send for Output { }
|
||||
|
||||
impl Output {
|
||||
pub unsafe fn wrap(ptr: *mut AVFormatContext) -> Self {
|
||||
Output { ptr: ptr, ctx: Context::wrap(ptr) }
|
||||
}
|
||||
|
||||
pub unsafe fn as_ptr(&self) -> *const AVFormatContext {
|
||||
self.ptr as *const _
|
||||
}
|
||||
|
||||
pub unsafe fn as_mut_ptr(&mut self) -> *mut AVFormatContext {
|
||||
self.ptr
|
||||
}
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn write_header(&mut self) -> Result<(), Error> {
|
||||
unsafe {
|
||||
match avformat_write_header(self.as_mut_ptr(), ptr::null_mut()) {
|
||||
0 => Ok(()),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_header_with(&mut self, options: Dictionary) -> Result<(), Error> {
|
||||
unsafe {
|
||||
let mut opts = options.take();
|
||||
let status = avformat_write_header(self.as_mut_ptr(), &mut opts);
|
||||
|
||||
Dictionary::own(opts);
|
||||
|
||||
match status {
|
||||
0 => Ok(()),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_trailer(&mut self) -> Result<(), Error> {
|
||||
unsafe {
|
||||
match av_write_trailer(self.as_mut_ptr()) {
|
||||
0 => Ok(()),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_stream(&mut self, codec: &Codec) -> StreamMut {
|
||||
unsafe {
|
||||
let ptr = avformat_new_stream(self.as_mut_ptr(), codec.as_ptr());
|
||||
|
||||
if ptr.is_null() {
|
||||
panic!("out of memory");
|
||||
}
|
||||
|
||||
StreamMut::wrap(ptr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Output {
|
||||
type Target = Context;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Output {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.ctx
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Output {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
avformat_free_context(self.as_mut_ptr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump(ctx: &Output, index: i32, url: Option<&str>) {
|
||||
let url = url.map(|u| CString::new(u).unwrap());
|
||||
|
||||
unsafe {
|
||||
av_dump_format(ctx.as_ptr(), index,
|
||||
url.map(|u| u.as_ptr()).unwrap_or(ptr::null()), 1);
|
||||
}
|
||||
}
|
@ -162,12 +162,18 @@ pub fn list() -> FormatIter {
|
||||
pub struct FormatIter {
|
||||
input: *mut AVInputFormat,
|
||||
output: *mut AVOutputFormat,
|
||||
step: usize,
|
||||
step: Step,
|
||||
}
|
||||
|
||||
enum Step {
|
||||
Input,
|
||||
Output,
|
||||
Done,
|
||||
}
|
||||
|
||||
impl FormatIter {
|
||||
pub fn new() -> Self {
|
||||
FormatIter { input: ptr::null_mut(), output: ptr::null_mut(), step: 0 }
|
||||
FormatIter { input: ptr::null_mut(), output: ptr::null_mut(), step: Step::Input }
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,11 +183,11 @@ impl Iterator for FormatIter {
|
||||
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
|
||||
unsafe {
|
||||
match self.step {
|
||||
0 => {
|
||||
Step::Input => {
|
||||
let ptr = av_iformat_next(self.input);
|
||||
|
||||
if ptr.is_null() && !self.input.is_null() {
|
||||
self.step = 1;
|
||||
self.step = Step::Output;
|
||||
|
||||
self.next()
|
||||
}
|
||||
@ -190,13 +196,13 @@ impl Iterator for FormatIter {
|
||||
|
||||
Some(Format::Input(Input::wrap(ptr)))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
1 => {
|
||||
Step::Output => {
|
||||
let ptr = av_oformat_next(self.output);
|
||||
|
||||
if ptr.is_null() && !self.output.is_null() {
|
||||
self.step = 2;
|
||||
self.step = Step::Done;
|
||||
|
||||
self.next()
|
||||
}
|
||||
@ -205,9 +211,10 @@ impl Iterator for FormatIter {
|
||||
|
||||
Some(Format::Output(Output::wrap(ptr)))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
_ => None
|
||||
Step::Done =>
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,176 +56,156 @@ pub fn license() -> &'static str {
|
||||
}
|
||||
|
||||
// XXX: use to_cstring when stable
|
||||
fn from_path<T: AsRef<Path>>(path: &T) -> CString {
|
||||
fn from_path<P: AsRef<Path>>(path: &P) -> CString {
|
||||
CString::new(path.as_ref().as_os_str().to_str().unwrap()).unwrap()
|
||||
}
|
||||
|
||||
pub fn open_input<T: AsRef<Path>>(path: &T) -> Result<Context, Error> {
|
||||
// NOTE: this will be better with specialization or anonymous return types
|
||||
pub fn open<P: AsRef<Path>>(path: &P, format: &Format) -> Result<Context, Error> {
|
||||
unsafe {
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
let status = avformat_open_input(&mut ps, path.as_ptr(), ptr::null_mut(), ptr::null_mut());
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
|
||||
match status {
|
||||
0 => {
|
||||
let ctx = Context::input(ps);
|
||||
match format {
|
||||
&Format::Input(ref format) => {
|
||||
match avformat_open_input(&mut ps, path.as_ptr(), format.as_ptr(), ptr::null_mut()) {
|
||||
0 => {
|
||||
match avformat_find_stream_info(ps, ptr::null_mut()) {
|
||||
0 => Ok(Context::Input(context::Input::wrap(ps))),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
|
||||
match avformat_find_stream_info(ps, ptr::null_mut()) {
|
||||
0 => Ok(ctx),
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
&Format::Output(ref format) => {
|
||||
match avformat_alloc_output_context2(&mut ps, format.as_ptr(), ptr::null(), path.as_ptr()) {
|
||||
0 => {
|
||||
match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
|
||||
0 => Ok(Context::Output(context::Output::wrap(ps))),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_with<P: AsRef<Path>>(path: &P, format: &Format, options: Dictionary) -> Result<Context, Error> {
|
||||
unsafe {
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
let mut opts = options.take();
|
||||
|
||||
match format {
|
||||
&Format::Input(ref format) => {
|
||||
match avformat_open_input(&mut ps, path.as_ptr(), format.as_ptr(), &mut opts) {
|
||||
0 => {
|
||||
Dictionary::own(opts);
|
||||
|
||||
match avformat_find_stream_info(ps, ptr::null_mut()) {
|
||||
0 => Ok(Context::Input(context::Input::wrap(ps))),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
|
||||
&Format::Output(ref format) => {
|
||||
match avformat_alloc_output_context2(&mut ps, format.as_ptr(), ptr::null(), path.as_ptr()) {
|
||||
0 => {
|
||||
match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
|
||||
0 => Ok(Context::Output(context::Output::wrap(ps))),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
}
|
||||
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn input<P: AsRef<Path>>(path: &P) -> Result<context::Input, Error> {
|
||||
unsafe {
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
|
||||
match avformat_open_input(&mut ps, path.as_ptr(), ptr::null_mut(), ptr::null_mut()) {
|
||||
0 => {
|
||||
match avformat_find_stream_info(ps, ptr::null_mut()) {
|
||||
0 => Ok(context::Input::wrap(ps)),
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_input_with<T: AsRef<Path>>(path: &T, options: Dictionary) -> Result<Context, Error> {
|
||||
pub fn input_with<P: AsRef<Path>>(path: &P, options: Dictionary) -> Result<context::Input, Error> {
|
||||
unsafe {
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
let mut opts = options.take();
|
||||
let status = avformat_open_input(&mut ps, path.as_ptr(), ptr::null_mut(), &mut opts);
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
let mut opts = options.take();
|
||||
|
||||
Dictionary::own(opts);
|
||||
|
||||
match status {
|
||||
match avformat_open_input(&mut ps, path.as_ptr(), ptr::null_mut(), &mut opts) {
|
||||
0 => {
|
||||
let ctx = Context::input(ps);
|
||||
Dictionary::own(opts);
|
||||
|
||||
match avformat_find_stream_info(ps, ptr::null_mut()) {
|
||||
0 => Ok(ctx),
|
||||
0 => Ok(context::Input::wrap(ps)),
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_input_as<T: AsRef<Path>>(path: &T, format: &Format) -> Result<Context, Error> {
|
||||
if let &Format::Input(ref format) = format {
|
||||
unsafe {
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
let status = avformat_open_input(&mut ps, path.as_ptr(), format.as_ptr(), ptr::null_mut());
|
||||
|
||||
match status {
|
||||
0 => {
|
||||
let ctx = Context::input(ps);
|
||||
|
||||
match avformat_find_stream_info(ps, ptr::null_mut()) {
|
||||
0 => Ok(ctx),
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
},
|
||||
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Err(Error::Bug)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_input_as_with<T: AsRef<Path>>(path: &T, format: &Format, options: Dictionary) -> Result<Context, Error> {
|
||||
if let &Format::Input(ref format) = format {
|
||||
unsafe {
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
let mut opts = options.take();
|
||||
let status = avformat_open_input(&mut ps, path.as_ptr(), format.as_ptr(), &mut opts);
|
||||
|
||||
Dictionary::own(opts);
|
||||
|
||||
match status {
|
||||
0 => {
|
||||
let ctx = Context::input(ps);
|
||||
|
||||
match avformat_find_stream_info(ps, ptr::null_mut()) {
|
||||
0 => Ok(ctx),
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
},
|
||||
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Err(Error::Bug)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_output<T: AsRef<Path>>(path: &T) -> Result<Context, Error> {
|
||||
pub fn output<P: AsRef<Path>>(path: &P) -> Result<context::Output, Error> {
|
||||
unsafe {
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
let status = avformat_alloc_output_context2(&mut ps, ptr::null_mut(), ptr::null(), path.as_ptr());
|
||||
|
||||
match status {
|
||||
match avformat_alloc_output_context2(&mut ps, ptr::null_mut(), ptr::null(), path.as_ptr()) {
|
||||
0 => {
|
||||
match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
|
||||
0 => Ok(Context::output(ps)),
|
||||
e => Err(Error::from(e)),
|
||||
0 => Ok(context::Output::wrap(ps)),
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_output_as<T: AsRef<Path>>(path: &T, format: &Format) -> Result<Context, Error> {
|
||||
if let &Format::Output(ref format) = format {
|
||||
unsafe {
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
let status = avformat_alloc_output_context2(&mut ps, format.as_ptr(), ptr::null(), path.as_ptr());
|
||||
|
||||
match status {
|
||||
0 => {
|
||||
match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
|
||||
0 => Ok(Context::output(ps)),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
},
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
Err(Error::Bug)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_output_as_string<T: AsRef<Path>>(path: &T, format: &str) -> Result<Context, Error> {
|
||||
pub fn output_as<P: AsRef<Path>>(path: &P, format: &str) -> Result<context::Output, Error> {
|
||||
unsafe {
|
||||
let mut ps = ptr::null_mut();
|
||||
let path = from_path(path);
|
||||
let format = CString::new(format).unwrap();
|
||||
let status = avformat_alloc_output_context2(&mut ps, ptr::null_mut(), format.as_ptr(), path.as_ptr());
|
||||
|
||||
match status {
|
||||
0 => {
|
||||
match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
|
||||
0 => Ok(Context::output(ps)),
|
||||
e => Err(Error::from(e)),
|
||||
}
|
||||
},
|
||||
match avformat_alloc_output_context2(&mut ps, ptr::null_mut(), format.as_ptr(), path.as_ptr()) {
|
||||
0 => {
|
||||
match avio_open(&mut (*ps).pb, path.as_ptr(), AVIO_FLAG_WRITE) {
|
||||
0 => Ok(context::Output::wrap(ps)),
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
|
||||
e => Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dump(ctx: &Context, index: i32, url: Option<&str>) {
|
||||
let url = url.map(|u| CString::new(u).unwrap());
|
||||
|
||||
unsafe {
|
||||
av_dump_format(ctx.as_ptr(), index,
|
||||
url.map(|u| u.as_ptr()).unwrap_or(ptr::null()),
|
||||
if ctx.is_input() { 0 } else { 1 });
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user