forked from PyAV-Org/PyAV
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcore.pyx
More file actions
293 lines (220 loc) · 8.99 KB
/
Copy pathcore.pyx
File metadata and controls
293 lines (220 loc) · 8.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
from libc.stdint cimport uint8_t, int64_t
from libc.stdlib cimport malloc, free
from libc.string cimport memcpy
import sys
cimport libav as lib
from av.container.input cimport InputContainer
from av.container.output cimport OutputContainer
from av.format cimport build_container_format
from av.utils cimport err_check, stash_exception, dict_to_avdict
from av.dictionary import Dictionary # not cimport
from av.utils import AVError # not cimport
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 ContainerProxy self
cdef bytes res
try:
self = <ContainerProxy>opaque
res = self.fread(buf_size)
memcpy(buf, <void*><char*>res, len(res))
self.pos += len(res)
if not res:
return lib.AVERROR_EOF
return len(res)
except Exception as e:
return stash_exception()
cdef int pyio_write(void *opaque, uint8_t *buf, int buf_size) nogil:
with gil:
return pyio_write_gil(opaque, buf, buf_size)
cdef int pyio_write_gil(void *opaque, uint8_t *buf, int buf_size):
cdef ContainerProxy self
cdef bytes bytes_to_write
cdef int bytes_written
try:
self = <ContainerProxy>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
self.pos += bytes_written
return bytes_written
except Exception as e:
return stash_exception()
cdef int64_t pyio_seek(void *opaque, int64_t offset, int whence) nogil:
# Seek takes the standard flags, but also a ad-hoc one which means that
# the library wants to know how large the file is. We are generally
# allowed to ignore this.
if whence == lib.AVSEEK_SIZE:
return -1
with gil:
return pyio_seek_gil(opaque, offset, whence)
cdef int64_t pyio_seek_gil(void *opaque, int64_t offset, int whence):
cdef ContainerProxy self
try:
self = <ContainerProxy>opaque
res = self.fseek(offset, whence)
# Track the position for the user.
if whence == 0:
self.pos = offset
elif whence == 1:
self.pos += offset
else:
self.pos_is_valid = False
if res is None:
if self.pos_is_valid:
res = self.pos
else:
res = self.ftell()
return res
except Exception as e:
return stash_exception()
cdef object _cinit_sentinel = object()
cdef class ContainerProxy(object):
def __init__(self, sentinel, Container container):
cdef int res
if sentinel is not _cinit_sentinel:
raise RuntimeError('cannot construct ContainerProxy')
# Copy key attributes.
self.name = container.name
self.file = container.file
self.writeable = container.writeable
cdef char *name = self.name
cdef lib.AVOutputFormat *ofmt
if self.writeable:
ofmt = container.format.optr if container.format else lib.av_guess_format(NULL, name, NULL)
if ofmt == NULL:
raise ValueError("Could not determine output format")
with nogil:
# This does not actually open the file.
res = lib.avformat_alloc_output_context2(
&self.ptr,
ofmt,
NULL,
name,
)
self.err_check(res)
else:
# We need the context before we open the input AND setup Python IO.
self.ptr = lib.avformat_alloc_context()
self.ptr.flags |= lib.AVFMT_FLAG_GENPTS
self.ptr.max_analyze_duration = 10000000
# Setup Python IO.
if self.file is not None:
# TODO: Make sure we actually have these.
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)
self.pos = 0
self.pos_is_valid = True
# This is effectively the maximum size of reads.
self.bufsize = 32 * 1024
self.buffer = <unsigned char*>lib.av_malloc(self.bufsize)
self.iocontext = lib.avio_alloc_context(
self.buffer, self.bufsize,
self.writeable, # Writeable.
<void*>self, # User data.
pyio_read,
pyio_write,
pyio_seek
)
# Various tutorials say that we should set AVFormatContext.direct
# to AVIO_FLAG_DIRECT here, but that doesn't seem to do anything in
# FFMpeg and was deprecated.
self.iocontext.seekable = lib.AVIO_SEEKABLE_NORMAL
self.iocontext.max_packet_size = self.bufsize
self.ptr.pb = self.iocontext
#self.ptr.flags |= lib.AVFMT_FLAG_CUSTOM_IO
cdef lib.AVInputFormat *ifmt
cdef _Dictionary options
if not self.writeable:
ifmt = container.format.iptr if container.format else NULL
options = container.options.copy()
with nogil:
res = lib.avformat_open_input(
&self.ptr,
name,
ifmt,
&options.ptr
)
self.err_check(res)
def __dealloc__(self):
with nogil:
# Let FFmpeg handle it if it fully opened.
if self.ptr and not self.writeable:
lib.avformat_close_input(&self.ptr)
# Manually free things.
else:
if self.buffer:
lib.av_freep(&self.buffer)
if self.iocontext:
lib.av_freep(&self.iocontext)
cdef seek(self, int stream_index, lib.int64_t timestamp, str mode, bint backward, bint any_frame):
cdef int flags = 0
cdef int ret
if mode == 'frame':
flags |= lib.AVSEEK_FLAG_FRAME
elif mode == 'byte':
flags |= lib.AVSEEK_FLAG_BYTE
elif mode != 'time':
raise ValueError('mode must be one of "frame", "byte", or "time"')
if backward:
flags |= lib.AVSEEK_FLAG_BACKWARD
if any_frame:
flags |= lib.AVSEEK_FLAG_ANY
with nogil:
ret = lib.av_seek_frame(self.ptr, stream_index, timestamp, flags)
err_check(ret)
self.flush_buffers()
cdef flush_buffers(self):
cdef int i
cdef lib.AVStream *stream
with nogil:
for i in range(self.ptr.nb_streams):
stream = self.ptr.streams[i]
if stream.codec and stream.codec.codec and stream.codec.codec_id != lib.AV_CODEC_ID_NONE:
lib.avcodec_flush_buffers(stream.codec)
cdef int err_check(self, int value) except -1:
return err_check(value, filename=self.name)
cdef class Container(object):
def __cinit__(self, sentinel, file_, format_name, options):
if sentinel is not _cinit_sentinel:
raise RuntimeError('cannot construct base Container')
self.writeable = isinstance(self, OutputContainer)
if not self.writeable and not isinstance(self, InputContainer):
raise RuntimeError('Container cannot be extended except')
if isinstance(file_, basestring):
self.name = file_
else:
self.name = str(getattr(file_, 'name', None))
self.file = file_
if format_name is not None:
self.format = ContainerFormat(format_name)
self.options = Dictionary(**(options or {}))
self.proxy = ContainerProxy(_cinit_sentinel, self)
if format_name is None:
self.format = build_container_format(self.proxy.ptr.iformat, self.proxy.ptr.oformat)
def __repr__(self):
return '<av.%s %r>' % (self.__class__.__name__, self.file or self.name)
def open(file, mode=None, format=None, options=None):
"""open(file, mode='r', format=None, options=None)
Main entrypoint to opening files/streams.
:param str file: The file to open.
:param str mode: ``"r"`` for reading and ``"w"`` for writing.
:param str format: Specific format to use. Defaults to autodect.
:param dict options: Options to pass to the container and streams.
For devices (via `libavdevice`), pass the name of the device to ``format``,
e.g.::
>>> # Open webcam on OS X.
>>> av.open(format='avfoundation', file='0') # doctest: SKIP
"""
if mode is None:
mode = getattr(file, 'mode', None)
if mode is None:
mode = 'r'
if mode.startswith('r'):
return InputContainer(_cinit_sentinel, file, format, options)
if mode.startswith('w'):
return OutputContainer(_cinit_sentinel, file, format, options)
raise ValueError("mode must be 'r' or 'w'; got %r" % mode)