diff --git a/av/container/core.pxd b/av/container/core.pxd index 01a9dfa0d0673748199fa0afb90c68e51c6c1bf1..cd2ba117a6fd27c7e99e12d90f74ead944851b7e 100644 --- a/av/container/core.pxd +++ b/av/container/core.pxd @@ -1,5 +1,6 @@ cimport libav as lib +from av.container.pyio cimport PyIOFile from av.container.streams cimport StreamContainer from av.dictionary cimport _Dictionary from av.format cimport ContainerFormat @@ -21,18 +22,7 @@ cdef class Container(object): cdef readonly str metadata_encoding cdef readonly str metadata_errors - # File-like source. - cdef readonly object file - cdef object fread - cdef object fwrite - cdef object fseek - cdef object ftell - - # Custom IO for above. - cdef lib.AVIOContext *iocontext - cdef unsigned char *buffer - cdef long pos - cdef bint pos_is_valid + cdef readonly PyIOFile file cdef bint input_was_opened cdef readonly ContainerFormat format diff --git a/av/container/core.pyx b/av/container/core.pyx index 32f622377efa03dc5e4f628beed7027d941514bb..5e0c9e7d79ea7f388bc602cdf51743459e8f3b35 100755 --- a/av/container/core.pyx +++ b/av/container/core.pyx @@ -10,10 +10,10 @@ cimport libav as lib from av.container.core cimport timeout_info from av.container.input cimport InputContainer from av.container.output cimport OutputContainer -from av.container.pyio cimport pyio_read, pyio_seek, pyio_write from av.enum cimport define_enum from av.error cimport err_check, stash_exception from av.format cimport build_container_format +from av.utils cimport avdict_to_dict from av.dictionary import Dictionary from av.logging import Capture as LogCapture @@ -112,7 +112,6 @@ cdef class Container(object): self.name = getattr(file_, 'name', '<none>') if not isinstance(self.name, str): raise TypeError("File's name attribute must be string-like.") - self.file = file_ self.options = dict(options or ()) self.container_options = dict(container_options or ()) @@ -163,42 +162,9 @@ cdef class Container(object): self.ptr.flags |= lib.AVFMT_FLAG_GENPTS # Setup Python IO. - if self.file is not None: - - self.fread = getattr(self.file, 'read', None) - self.fwrite = getattr(self.file, 'write', None) - self.fseek = getattr(self.file, 'seek', None) - self.ftell = getattr(self.file, 'tell', None) - - if self.writeable: - if self.fwrite is None: - raise ValueError("File object has no write method.") - else: - if self.fread is None: - raise ValueError("File object has no read method.") - - if self.fseek is not None and self.ftell is not None: - seek_func = pyio_seek - - self.pos = 0 - self.pos_is_valid = True - - # This is effectively the maximum size of reads. - self.buffer = <unsigned char*>lib.av_malloc(buffer_size) - - self.iocontext = lib.avio_alloc_context( - self.buffer, buffer_size, - self.writeable, # Writeable. - <void*>self, # User data. - pyio_read, - pyio_write, - seek_func - ) - - if seek_func: - self.iocontext.seekable = lib.AVIO_SEEKABLE_NORMAL - self.iocontext.max_packet_size = buffer_size - self.ptr.pb = self.iocontext + if not isinstance(file_, basestring): + self.file = PyIOFile(file_, buffer_size, self.writeable) + self.ptr.pb = self.file.iocontext cdef lib.AVInputFormat *ifmt cdef _Dictionary c_options @@ -226,18 +192,6 @@ cdef class Container(object): def __dealloc__(self): with nogil: - # FFmpeg will not release custom input, so it's up to us to free it. - # Do not touch our original buffer as it may have been freed and replaced. - if self.iocontext: - lib.av_freep(&self.iocontext.buffer) - lib.av_freep(&self.iocontext) - - # We likely errored badly if we got here, and so are still - # responsible for our buffer. - else: - lib.av_freep(&self.buffer) - - # Finish releasing the whole structure. lib.avformat_free_context(self.ptr) def __enter__(self): diff --git a/av/container/pyio.pxd b/av/container/pyio.pxd index 1292d2c710407cdb202b04f01824b9fd69f01c1d..b7597e9b3e395eec70bbee455b4acfd4c27493ad 100644 --- a/av/container/pyio.pxd +++ b/av/container/pyio.pxd @@ -1,4 +1,5 @@ from libc.stdint cimport int64_t, uint8_t +cimport libav as lib cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) nogil @@ -6,3 +7,18 @@ cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) nogil cdef int pyio_write(void *opaque, uint8_t *buf, int buf_size) nogil cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) nogil + +cdef class PyIOFile(object): + + # File-like source. + cdef readonly object file + cdef object fread + cdef object fwrite + cdef object fseek + cdef object ftell + + # Custom IO for above. + cdef lib.AVIOContext *iocontext + cdef unsigned char *buffer + cdef long pos + cdef bint pos_is_valid diff --git a/av/container/pyio.pyx b/av/container/pyio.pyx index 62629313d13aca3126b9053bc9fd1d1694931cea..ef844a3dc3c00da6e9221013464fa795cd0910df 100644 --- a/av/container/pyio.pyx +++ b/av/container/pyio.pyx @@ -1,19 +1,80 @@ from libc.string cimport memcpy cimport libav as lib -from av.container.core cimport Container from av.error cimport stash_exception +ctypedef int64_t (*seek_func_t)(void *opaque, int64_t offset, int whence) nogil + + +cdef class PyIOFile(object): + + def __cinit__(self, file, buffer_size, writeable=None): + + self.file = file + + cdef seek_func_t seek_func = NULL + + self.fread = getattr(self.file, 'read', None) + self.fwrite = getattr(self.file, 'write', None) + self.fseek = getattr(self.file, 'seek', None) + self.ftell = getattr(self.file, 'tell', None) + + if self.fseek is not None and self.ftell is not None: + seek_func = pyio_seek + + if writeable is None: + writeable = self.fwrite is not None + + if writeable: + if self.fwrite is None: + raise ValueError("File object has no write method.") + else: + if self.fread is None: + raise ValueError("File object has no read method.") + + self.pos = 0 + self.pos_is_valid = True + + # This is effectively the maximum size of reads. + self.buffer = <unsigned char*>lib.av_malloc(buffer_size) + + self.iocontext = lib.avio_alloc_context( + self.buffer, buffer_size, + writeable, + <void*>self, # User data. + pyio_read, + pyio_write, + seek_func + ) + + if seek_func: + self.iocontext.seekable = lib.AVIO_SEEKABLE_NORMAL + self.iocontext.max_packet_size = buffer_size + + def __dealloc__(self): + with nogil: + # FFmpeg will not release custom input, so it's up to us to free it. + # Do not touch our original buffer as it may have been freed and replaced. + if self.iocontext: + lib.av_freep(&self.iocontext.buffer) + lib.av_freep(&self.iocontext) + + # We likely errored badly if we got here, and so are still + # responsible for our buffer. + else: + lib.av_freep(&self.buffer) + + cdef int pyio_read(void *opaque, uint8_t *buf, int buf_size) nogil: with gil: return pyio_read_gil(opaque, buf, buf_size) cdef int pyio_read_gil(void *opaque, uint8_t *buf, int buf_size): - cdef Container self + cdef PyIOFile self cdef bytes res try: - self = <Container>opaque + self = <PyIOFile>opaque res = self.fread(buf_size) memcpy(buf, <void*><char*>res, len(res)) self.pos += len(res) @@ -29,11 +90,11 @@ cdef int pyio_write(void *opaque, uint8_t *buf, int buf_size) nogil: return pyio_write_gil(opaque, buf, buf_size) cdef int pyio_write_gil(void *opaque, uint8_t *buf, int buf_size): - cdef Container self + cdef PyIOFile self cdef bytes bytes_to_write cdef int bytes_written try: - self = <Container>opaque + self = <PyIOFile>opaque bytes_to_write = buf[:buf_size] ret_value = self.fwrite(bytes_to_write) bytes_written = ret_value if isinstance(ret_value, int) else buf_size @@ -53,9 +114,9 @@ cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) nogil: return pyio_seek_gil(opaque, offset, whence) cdef int64_t pyio_seek_gil(void *opaque, int64_t offset, int whence): - cdef Container self + cdef PyIOFile self try: - self = <Container>opaque + self = <PyIOFile>opaque res = self.fseek(offset, whence) # Track the position for the user. @@ -71,6 +132,5 @@ cdef int64_t pyio_seek_gil(void *opaque, int64_t offset, int whence): else: res = self.ftell() return res - except Exception as e: return stash_exception()