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()