quick fix 2
This commit is contained in:
53
Lib/site-packages/ffpyplayer/__init__.py
Normal file
53
Lib/site-packages/ffpyplayer/__init__.py
Normal file
@ -0,0 +1,53 @@
|
||||
'''
|
||||
FFPyPlayer library
|
||||
==================
|
||||
'''
|
||||
import sys
|
||||
import site
|
||||
import os
|
||||
from os.path import join
|
||||
import platform
|
||||
|
||||
__all__ = ('dep_bins', )
|
||||
|
||||
__version__ = '4.5.3'
|
||||
version = __version__
|
||||
|
||||
# the ffmpeg src git version tested and upto date with,
|
||||
# and including this commit
|
||||
_ffmpeg_git = 'c926140558c60786dc577b121df6b3c6b430bd98'
|
||||
# excludes commits bdf9ed41fe4bdf4e254615b7333ab0feb1977e98,
|
||||
# 1be3d8a0cb77f8d34c1f39b47bf5328fe10c82d7,
|
||||
# f1907faab4023517af7d10d746b5684cccc5cfcc, and
|
||||
# 0995e1f1b31f6e937a1b527407ed3e850f138098 because they require ffmpeg 5.1/5.2
|
||||
# which is too new as of now
|
||||
|
||||
# also skipped all show modes and subtitle display related functionality commits
|
||||
|
||||
# TODO:
|
||||
# * Implement CONFIG_SDL to be able to compile without needing SDL at all.
|
||||
# * Currently, it only supports text subtitles - bitmap subtitles are ignored.
|
||||
# Unless one uses a filter to overlay the subtitle.
|
||||
# * We can not yet visualize audio to video. Provide a filter chain link between
|
||||
# audio to video filters to acomplish this.
|
||||
|
||||
dep_bins = []
|
||||
'''A list of paths to the binaries used by the library. It can be used during
|
||||
packaging for including required binaries.
|
||||
|
||||
It is read only.
|
||||
'''
|
||||
|
||||
for d in [sys.prefix, site.USER_BASE]:
|
||||
if d is None:
|
||||
continue
|
||||
for lib in ('ffmpeg', 'sdl'):
|
||||
p = join(d, 'share', 'ffpyplayer', lib, 'bin')
|
||||
if os.path.isdir(p):
|
||||
os.environ["PATH"] = p + os.pathsep + os.environ["PATH"]
|
||||
if hasattr(os, 'add_dll_directory'):
|
||||
os.add_dll_directory(p)
|
||||
dep_bins.append(p)
|
||||
|
||||
if 'SDL_AUDIODRIVER' not in os.environ and platform.system() == 'Windows':
|
||||
os.environ['SDL_AUDIODRIVER'] = 'DirectSound'
|
||||
BIN
Lib/site-packages/ffpyplayer/pic.cp313-win_amd64.pyd
Normal file
BIN
Lib/site-packages/ffpyplayer/pic.cp313-win_amd64.pyd
Normal file
Binary file not shown.
44
Lib/site-packages/ffpyplayer/pic.pxd
Normal file
44
Lib/site-packages/ffpyplayer/pic.pxd
Normal file
@ -0,0 +1,44 @@
|
||||
include 'includes/ffmpeg.pxi'
|
||||
|
||||
|
||||
cdef class SWScale(object):
|
||||
cdef SwsContext *sws_ctx
|
||||
cdef bytes dst_pix_fmt
|
||||
cdef str dst_pix_fmt_s
|
||||
cdef int dst_h
|
||||
cdef int dst_w
|
||||
cdef AVPixelFormat src_pix_fmt
|
||||
cdef int src_h
|
||||
cdef int src_w
|
||||
|
||||
|
||||
cdef class Image(object):
|
||||
|
||||
cdef AVFrame *frame
|
||||
cdef list byte_planes
|
||||
cdef AVPixelFormat pix_fmt
|
||||
|
||||
cdef int cython_init(self, AVFrame *frame) nogil except 1
|
||||
cpdef is_ref(Image self)
|
||||
cpdef is_key_frame(Image self)
|
||||
cpdef get_linesizes(Image self, keep_align=*)
|
||||
cpdef get_size(Image self)
|
||||
cpdef get_pixel_format(Image self)
|
||||
cpdef get_buffer_size(Image self, keep_align=*)
|
||||
cpdef get_required_buffers(Image self)
|
||||
cpdef to_bytearray(Image self, keep_align=*)
|
||||
cpdef to_memoryview(Image self, keep_align=*)
|
||||
|
||||
|
||||
cdef class ImageLoader(object):
|
||||
cdef AVFormatContext *format_ctx
|
||||
cdef AVCodec *codec
|
||||
cdef AVCodecContext *codec_ctx
|
||||
cdef AVPacket pkt
|
||||
cdef AVFrame *frame
|
||||
cdef bytes filename
|
||||
cdef char msg[256]
|
||||
cdef int eof
|
||||
|
||||
cpdef next_frame(self)
|
||||
cdef inline object eof_frame(self)
|
||||
1116
Lib/site-packages/ffpyplayer/pic.pyx
Normal file
1116
Lib/site-packages/ffpyplayer/pic.pyx
Normal file
File diff suppressed because it is too large
Load Diff
BIN
Lib/site-packages/ffpyplayer/threading.cp313-win_amd64.pyd
Normal file
BIN
Lib/site-packages/ffpyplayer/threading.cp313-win_amd64.pyd
Normal file
Binary file not shown.
46
Lib/site-packages/ffpyplayer/threading.pxd
Normal file
46
Lib/site-packages/ffpyplayer/threading.pxd
Normal file
@ -0,0 +1,46 @@
|
||||
|
||||
include "includes/ffmpeg.pxi"
|
||||
|
||||
|
||||
cdef enum MT_lib:
|
||||
SDL_MT,
|
||||
Py_MT
|
||||
|
||||
cdef class MTMutex(object):
|
||||
cdef MT_lib lib
|
||||
cdef void* mutex
|
||||
|
||||
cdef int lock(MTMutex self) nogil except 2
|
||||
cdef int _lock_py(MTMutex self) nogil except 2
|
||||
cdef int unlock(MTMutex self) nogil except 2
|
||||
cdef int _unlock_py(MTMutex self) nogil except 2
|
||||
|
||||
cdef class MTCond(object):
|
||||
cdef MT_lib lib
|
||||
cdef MTMutex mutex
|
||||
cdef void *cond
|
||||
|
||||
cdef int lock(MTCond self) nogil except 2
|
||||
cdef int unlock(MTCond self) nogil except 2
|
||||
cdef int cond_signal(MTCond self) nogil except 2
|
||||
cdef int _cond_signal_py(MTCond self) nogil except 2
|
||||
cdef int cond_wait(MTCond self) nogil except 2
|
||||
cdef int _cond_wait_py(MTCond self) nogil except 2
|
||||
cdef int cond_wait_timeout(MTCond self, uint32_t val) nogil except 2
|
||||
cdef int _cond_wait_timeout_py(MTCond self, uint32_t val) nogil except 2
|
||||
|
||||
cdef class MTThread(object):
|
||||
cdef MT_lib lib
|
||||
cdef void* thread
|
||||
|
||||
cdef int create_thread(MTThread self, int_void_func func, const char *thread_name, void *arg) nogil except 2
|
||||
cdef int wait_thread(MTThread self, int *status) nogil except 2
|
||||
|
||||
|
||||
cdef class MTGenerator(object):
|
||||
cdef MT_lib mt_src
|
||||
|
||||
cdef int delay(MTGenerator self, int delay) nogil except 2
|
||||
cdef lockmgr_func get_lockmgr(MTGenerator self) nogil
|
||||
|
||||
cdef lockmgr_func get_lib_lockmgr(MT_lib lib) nogil
|
||||
259
Lib/site-packages/ffpyplayer/threading.pyx
Normal file
259
Lib/site-packages/ffpyplayer/threading.pyx
Normal file
@ -0,0 +1,259 @@
|
||||
|
||||
__all__ = ('MTGenerator', )
|
||||
|
||||
include "includes/ff_consts.pxi"
|
||||
include "includes/inline_funcs.pxi"
|
||||
|
||||
from cpython.ref cimport PyObject
|
||||
|
||||
cdef extern from "Python.h":
|
||||
void Py_INCREF(PyObject *)
|
||||
void Py_XINCREF(PyObject *)
|
||||
void Py_DECREF(PyObject *)
|
||||
|
||||
ctypedef int (*int_cls_method)(void *) nogil
|
||||
|
||||
import traceback
|
||||
|
||||
cdef int sdl_initialized = 0
|
||||
def initialize_sdl():
|
||||
'''Initializes sdl. Must be called before anything can be used.
|
||||
It is automatically called by the modules that use SDL.
|
||||
'''
|
||||
global sdl_initialized
|
||||
if sdl_initialized:
|
||||
return
|
||||
if SDL_Init(0):
|
||||
raise ValueError('Could not initialize SDL - %s' % SDL_GetError())
|
||||
sdl_initialized = 1
|
||||
initialize_sdl()
|
||||
|
||||
|
||||
cdef class MTMutex(object):
|
||||
|
||||
def __cinit__(MTMutex self, MT_lib lib):
|
||||
self.lib = lib
|
||||
self.mutex = NULL
|
||||
if lib == SDL_MT:
|
||||
self.mutex = SDL_CreateMutex()
|
||||
if self.mutex == NULL:
|
||||
raise Exception('Cannot create mutex.')
|
||||
elif lib == Py_MT:
|
||||
import threading
|
||||
mutex = threading.Lock()
|
||||
self.mutex = <PyObject *>mutex
|
||||
Py_INCREF(<PyObject *>self.mutex)
|
||||
|
||||
def __dealloc__(MTMutex self):
|
||||
if self.lib == SDL_MT:
|
||||
if self.mutex != NULL:
|
||||
SDL_DestroyMutex(<SDL_mutex *>self.mutex)
|
||||
elif self.lib == Py_MT:
|
||||
Py_DECREF(<PyObject *>self.mutex)
|
||||
|
||||
cdef int lock(MTMutex self) nogil except 2:
|
||||
if self.lib == SDL_MT:
|
||||
return SDL_mutexP(<SDL_mutex *>self.mutex)
|
||||
elif self.lib == Py_MT:
|
||||
return self._lock_py()
|
||||
|
||||
cdef int _lock_py(MTMutex self) nogil except 2:
|
||||
with gil:
|
||||
return not (<object>self.mutex).acquire()
|
||||
|
||||
cdef int unlock(MTMutex self) nogil except 2:
|
||||
if self.lib == SDL_MT:
|
||||
return SDL_mutexV(<SDL_mutex *>self.mutex)
|
||||
elif self.lib == Py_MT:
|
||||
return self._unlock_py()
|
||||
|
||||
cdef int _unlock_py(MTMutex self) nogil except 2:
|
||||
with gil:
|
||||
(<object>self.mutex).release()
|
||||
return 0
|
||||
|
||||
cdef class MTCond(object):
|
||||
|
||||
def __cinit__(MTCond self, MT_lib lib):
|
||||
self.lib = lib
|
||||
self.mutex = MTMutex.__new__(MTMutex, lib)
|
||||
self.cond = NULL
|
||||
if self.lib == SDL_MT:
|
||||
self.cond = SDL_CreateCond()
|
||||
if self.cond == NULL:
|
||||
raise Exception('Cannot create condition.')
|
||||
elif self.lib == Py_MT:
|
||||
import threading
|
||||
cond = threading.Condition(<object>self.mutex.mutex)
|
||||
self.cond = <PyObject *>cond
|
||||
Py_INCREF(<PyObject *>self.cond)
|
||||
|
||||
def __dealloc__(MTCond self):
|
||||
if self.lib == SDL_MT:
|
||||
if self.cond != NULL:
|
||||
SDL_DestroyCond(<SDL_cond *>self.cond)
|
||||
elif self.lib == Py_MT:
|
||||
Py_DECREF(<PyObject *>self.cond)
|
||||
|
||||
cdef int lock(MTCond self) nogil except 2:
|
||||
self.mutex.lock()
|
||||
|
||||
cdef int unlock(MTCond self) nogil except 2:
|
||||
self.mutex.unlock()
|
||||
|
||||
cdef int cond_signal(MTCond self) nogil except 2:
|
||||
if self.lib == SDL_MT:
|
||||
return SDL_CondSignal(<SDL_cond *>self.cond)
|
||||
elif self.lib == Py_MT:
|
||||
return self._cond_signal_py()
|
||||
|
||||
cdef int _cond_signal_py(MTCond self) nogil except 2:
|
||||
with gil:
|
||||
(<object>self.cond).notify()
|
||||
return 0
|
||||
|
||||
cdef int cond_wait(MTCond self) nogil except 2:
|
||||
if self.lib == SDL_MT:
|
||||
return SDL_CondWait(<SDL_cond *>self.cond, <SDL_mutex *>self.mutex.mutex)
|
||||
elif self.lib == Py_MT:
|
||||
return self._cond_wait_py()
|
||||
|
||||
cdef int _cond_wait_py(MTCond self) nogil except 2:
|
||||
with gil:
|
||||
(<object>self.cond).wait()
|
||||
return 0
|
||||
|
||||
cdef int cond_wait_timeout(MTCond self, uint32_t val) nogil except 2:
|
||||
if self.lib == SDL_MT:
|
||||
return SDL_CondWaitTimeout(<SDL_cond *>self.cond, <SDL_mutex *>self.mutex.mutex, val)
|
||||
elif self.lib == Py_MT:
|
||||
return self._cond_wait_timeout_py(val)
|
||||
|
||||
cdef int _cond_wait_timeout_py(MTCond self, uint32_t val) nogil except 2:
|
||||
with gil:
|
||||
(<object>self.cond).wait(val / 1000.)
|
||||
return 0
|
||||
|
||||
def enterance_func(target_func, target_arg):
|
||||
return (<int_void_func><uintptr_t>target_func)(<void *><uintptr_t>target_arg)
|
||||
|
||||
cdef class MTThread(object):
|
||||
|
||||
def __cinit__(MTThread self, MT_lib lib):
|
||||
self.lib = lib
|
||||
self.thread = NULL
|
||||
|
||||
def __dealloc__(MTThread self):
|
||||
if self.lib == Py_MT and self.thread != NULL:
|
||||
Py_DECREF(<PyObject *>self.thread)
|
||||
|
||||
cdef int create_thread(MTThread self, int_void_func func, const char *thread_name, void *arg) nogil except 2:
|
||||
if self.lib == SDL_MT:
|
||||
with gil:
|
||||
self.thread = SDL_CreateThread(func, thread_name, arg)
|
||||
if self.thread == NULL:
|
||||
raise Exception('Cannot create thread.')
|
||||
elif self.lib == Py_MT:
|
||||
with gil:
|
||||
import threading
|
||||
thread = threading.Thread(group=None, target=enterance_func,
|
||||
name=None, args=(<uintptr_t>func, <uintptr_t>arg), kwargs={})
|
||||
self.thread = <PyObject *>thread
|
||||
Py_INCREF(<PyObject *>self.thread)
|
||||
thread.start()
|
||||
return 0
|
||||
|
||||
cdef int wait_thread(MTThread self, int *status) nogil except 2:
|
||||
if self.lib == SDL_MT:
|
||||
if self.thread != NULL:
|
||||
SDL_WaitThread(<SDL_Thread *>self.thread, status)
|
||||
elif self.lib == Py_MT:
|
||||
with gil:
|
||||
(<object>self.thread).join()
|
||||
if status != NULL:
|
||||
status[0] = 0
|
||||
return 0
|
||||
|
||||
|
||||
cdef int_cls_method mutex_lock = <int_cls_method>MTMutex.lock
|
||||
cdef int_cls_method mutex_release = <int_cls_method>MTMutex.unlock
|
||||
|
||||
cdef int _SDL_lockmgr_py(void ** mtx, int op) with gil:
|
||||
cdef bytes msg
|
||||
cdef int res = 1
|
||||
cdef MTMutex mutex
|
||||
|
||||
try:
|
||||
if op == FF_LOCK_CREATE:
|
||||
mutex = MTMutex.__new__(MTMutex, SDL_MT)
|
||||
Py_INCREF(<PyObject *>mutex)
|
||||
mtx[0] = <PyObject *>mutex
|
||||
res = 0
|
||||
elif op == FF_LOCK_DESTROY:
|
||||
if mtx[0] != NULL:
|
||||
Py_DECREF(<PyObject *>mtx[0])
|
||||
res = 0
|
||||
except:
|
||||
msg = traceback.format_exc().encode('utf8')
|
||||
av_log(NULL, AV_LOG_ERROR, '%s', msg)
|
||||
return res
|
||||
|
||||
cdef int SDL_lockmgr(void ** mtx, int op) nogil:
|
||||
if op == FF_LOCK_OBTAIN:
|
||||
return not not mutex_lock(mtx[0])
|
||||
elif op == FF_LOCK_RELEASE:
|
||||
return not not mutex_release(mtx[0])
|
||||
else:
|
||||
return _SDL_lockmgr_py(mtx, op)
|
||||
|
||||
cdef int Py_lockmgr(void ** mtx, int op) with gil:
|
||||
cdef int res = 1
|
||||
cdef bytes msg
|
||||
cdef MTMutex mutex
|
||||
|
||||
try:
|
||||
if op == FF_LOCK_CREATE:
|
||||
mutex = MTMutex.__new__(MTMutex, Py_MT)
|
||||
Py_INCREF(<PyObject *>mutex)
|
||||
mtx[0] = <PyObject *>mutex
|
||||
res = 0
|
||||
elif op == FF_LOCK_OBTAIN:
|
||||
mutex = <MTMutex>mtx[0]
|
||||
res = not not mutex.lock() # force it to 0, or 1
|
||||
elif op == FF_LOCK_RELEASE:
|
||||
mutex = <MTMutex>mtx[0]
|
||||
res = not not mutex.unlock()
|
||||
elif op == FF_LOCK_DESTROY:
|
||||
if mtx[0] != NULL:
|
||||
Py_DECREF(<PyObject *>mtx[0])
|
||||
res = 0
|
||||
except:
|
||||
msg = traceback.format_exc().encode('utf8')
|
||||
av_log(NULL, AV_LOG_ERROR, '%s', msg)
|
||||
res = 1
|
||||
return res
|
||||
|
||||
|
||||
cdef lockmgr_func get_lib_lockmgr(MT_lib lib) nogil:
|
||||
if lib == SDL_MT:
|
||||
return SDL_lockmgr
|
||||
elif lib == Py_MT:
|
||||
return Py_lockmgr
|
||||
|
||||
|
||||
cdef class MTGenerator(object):
|
||||
|
||||
def __cinit__(MTGenerator self, MT_lib mt_src, **kwargs):
|
||||
self.mt_src = mt_src
|
||||
|
||||
cdef int delay(MTGenerator self, int delay) nogil except 2:
|
||||
if self.mt_src == SDL_MT:
|
||||
SDL_Delay(delay)
|
||||
elif self.mt_src == Py_MT:
|
||||
with gil:
|
||||
import time
|
||||
time.sleep(delay / 1000.)
|
||||
return 0
|
||||
|
||||
cdef lockmgr_func get_lockmgr(MTGenerator self) nogil:
|
||||
return get_lib_lockmgr(self.mt_src)
|
||||
BIN
Lib/site-packages/ffpyplayer/tools.cp313-win_amd64.pyd
Normal file
BIN
Lib/site-packages/ffpyplayer/tools.cp313-win_amd64.pyd
Normal file
Binary file not shown.
851
Lib/site-packages/ffpyplayer/tools.pyx
Normal file
851
Lib/site-packages/ffpyplayer/tools.pyx
Normal file
@ -0,0 +1,851 @@
|
||||
'''
|
||||
FFmpeg tools
|
||||
============
|
||||
|
||||
Module for manipulating and finding information of FFmpeg formats, codecs,
|
||||
devices, pixel formats and more.
|
||||
'''
|
||||
|
||||
__all__ = (
|
||||
'initialize_sdl_aud', 'loglevels', 'codecs_enc', 'codecs_dec', 'pix_fmts',
|
||||
'formats_in', 'formats_out', 'set_log_callback', 'get_log_callback',
|
||||
'set_loglevel', 'get_loglevel', 'get_codecs', 'get_fmts',
|
||||
'get_format_codec',
|
||||
'get_supported_framerates', 'get_supported_pixfmts', 'get_best_pix_fmt',
|
||||
'emit_library_info',
|
||||
'list_dshow_devices', 'encode_to_bytes', 'decode_to_unicode',
|
||||
'convert_to_str', 'list_dshow_opts')
|
||||
|
||||
include "includes/ffmpeg.pxi"
|
||||
include "includes/inline_funcs.pxi"
|
||||
|
||||
cdef extern from "stdlib.h" nogil:
|
||||
void *malloc(size_t)
|
||||
void free(void *)
|
||||
|
||||
from ffpyplayer.threading cimport Py_MT, MTMutex, get_lib_lockmgr, SDL_MT
|
||||
import ffpyplayer.threading # for sdl init
|
||||
import re
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
cdef int sdl_aud_initialized = 0
|
||||
def initialize_sdl_aud():
|
||||
'''Initializes sdl audio subsystem. Must be called before audio can be used.
|
||||
It is automatically called by the modules that use SDL audio.
|
||||
'''
|
||||
global sdl_aud_initialized
|
||||
if sdl_aud_initialized:
|
||||
return
|
||||
|
||||
# Try to work around an occasional ALSA buffer underflow issue when the
|
||||
# period size is NPOT due to ALSA resampling by forcing the buffer size.
|
||||
if not SDL_getenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE"):
|
||||
SDL_setenv("SDL_AUDIO_ALSA_SET_BUFFER_SIZE", "1", 0)
|
||||
|
||||
if SDL_InitSubSystem(SDL_INIT_AUDIO):
|
||||
raise ValueError('Could not initialize SDL audio - %s' % SDL_GetError())
|
||||
sdl_aud_initialized = 1
|
||||
|
||||
|
||||
cdef int ffmpeg_initialized = 0
|
||||
def _initialize_ffmpeg():
|
||||
'''Initializes ffmpeg libraries. Must be called before anything can be used.
|
||||
Called automatically when importing this module.
|
||||
'''
|
||||
global ffmpeg_initialized
|
||||
if not ffmpeg_initialized:
|
||||
av_log_set_flags(AV_LOG_SKIP_REPEATED)
|
||||
IF CONFIG_AVDEVICE:
|
||||
avdevice_register_all()
|
||||
avformat_network_init()
|
||||
ffmpeg_initialized = 1
|
||||
_initialize_ffmpeg()
|
||||
|
||||
|
||||
def _get_item0(x):
|
||||
return x[0]
|
||||
|
||||
'see http://ffmpeg.org/ffmpeg.html for log levels'
|
||||
loglevels = {
|
||||
"quiet": AV_LOG_QUIET, "panic": AV_LOG_PANIC, "fatal": AV_LOG_FATAL,
|
||||
"error": AV_LOG_ERROR, "warning": AV_LOG_WARNING, "info": AV_LOG_INFO,
|
||||
"verbose": AV_LOG_VERBOSE, "debug": AV_LOG_DEBUG, "trace": AV_LOG_TRACE}
|
||||
'''A dictionary with all the available ffmpeg log levels. The keys are the loglevels
|
||||
and the values are their ffmpeg values. The lower the value, the more important
|
||||
the log. Note, this is ooposite python where the higher the level the more important
|
||||
the log.
|
||||
'''
|
||||
_loglevel_inverse = {v:k for k, v in loglevels.iteritems()}
|
||||
|
||||
cdef object _log_callback = None
|
||||
cdef MTMutex _log_mutex= MTMutex(SDL_MT)
|
||||
cdef int log_level = AV_LOG_WARNING
|
||||
cdef int print_prefix = 1
|
||||
|
||||
cdef void gil_call_callback(char *line, int level):
|
||||
cdef object callback
|
||||
callback = _log_callback
|
||||
if callback is None:
|
||||
return
|
||||
callback(tcode(line), _loglevel_inverse[level])
|
||||
|
||||
cdef void call_callback(char *line, int level) nogil:
|
||||
with gil:
|
||||
gil_call_callback(line, level)
|
||||
|
||||
cdef void _log_callback_func(void* ptr, int level, const char* fmt, va_list vl) noexcept nogil:
|
||||
cdef char line[2048]
|
||||
if fmt == NULL or level > log_level:
|
||||
return
|
||||
|
||||
av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix)
|
||||
call_callback(line, level)
|
||||
|
||||
def _logger_callback(logger_dict, message, level):
|
||||
message = message.strip()
|
||||
if message:
|
||||
logger_dict[level]('FFPyPlayer: {}'.format(message))
|
||||
|
||||
def set_log_callback(object callback=None, logger=None, int default_only=False):
|
||||
'''Sets a callback to be used by ffmpeg when emitting logs.
|
||||
This function is thread safe.
|
||||
|
||||
See also :func:`set_loglevel`.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`callback`: callable or None
|
||||
A function which will be called with strings to be printed. It takes
|
||||
two parameters: ``message`` and ``level``. ``message`` is the string
|
||||
to be printed. ``level`` is the log level of the string and is one
|
||||
of the keys of the :attr:`loglevels` dict. If ``callback`` and ``logger``
|
||||
are None, the default ffmpeg log callback will be set, which prints to stderr.
|
||||
Defaults to None.
|
||||
`logger`: a python logger object or None
|
||||
If ``callback`` is None and this is not None, this logger object's
|
||||
``critical``, ``error``, ``warning``, ``info``, ``debug``, and ``trace``
|
||||
methods will be called directly to forward ffmpeg's log outputs.
|
||||
|
||||
.. note::
|
||||
|
||||
If the logger doesn't have a trace method, the trace output will be
|
||||
redirected to debug. However, the trace level outputs a lot of logs.
|
||||
|
||||
`default_only`: bool
|
||||
If True, when ``callback`` or ``logger`` are not ``None``, they
|
||||
will only be set if a callback or logger has not already been set.
|
||||
|
||||
:returns:
|
||||
|
||||
The previous callback set (None, if it has not been set).
|
||||
|
||||
>>> from ffpyplayer.tools import set_log_callback, loglevels
|
||||
>>> loglevel_emit = 'error' # This and worse errors will be emitted.
|
||||
>>> def log_callback(message, level):
|
||||
... message = message.strip()
|
||||
... if message and loglevels[level] <= loglevels[loglevel_emit]:
|
||||
... print '%s: %s' %(level, message.strip())
|
||||
>>> set_log_callback(log_callback)
|
||||
...
|
||||
>>> set_log_callback(None)
|
||||
'''
|
||||
global _log_callback
|
||||
if callback is not None and not callable(callback):
|
||||
raise Exception('Log callback needs to be callable.')
|
||||
|
||||
if callback is None and logger is not None:
|
||||
logger_dict = {
|
||||
'quiet': logger.critical, 'panic': logger.critical,
|
||||
'fatal': logger.critical, 'error': logger.error, 'warning': logger.warning,
|
||||
'info': logger.info, 'verbose': logger.debug, 'debug': logger.debug,
|
||||
'trace': getattr(logger, 'trace', logger.debug)}
|
||||
callback = partial(_logger_callback, logger_dict)
|
||||
|
||||
_log_mutex.lock()
|
||||
old_callback = _log_callback
|
||||
if callback is None:
|
||||
av_log_set_callback(&av_log_default_callback)
|
||||
_log_callback = None
|
||||
elif not default_only or old_callback is None:
|
||||
av_log_set_callback(&_log_callback_func)
|
||||
_log_callback = callback
|
||||
_log_mutex.unlock()
|
||||
return old_callback
|
||||
|
||||
def get_log_callback():
|
||||
'''Returns the last log callback set, or None if it has not been set.
|
||||
See :func:`set_log_callback`.
|
||||
'''
|
||||
_log_mutex.lock()
|
||||
old_callback = _log_callback
|
||||
_log_mutex.unlock()
|
||||
return old_callback
|
||||
|
||||
|
||||
def set_loglevel(loglevel):
|
||||
'''This sets the global FFmpeg log level. less important log levels are filtered
|
||||
and not passsed on to the logger or callback set by :func:`set_log_callback`.
|
||||
It also set the loglevel of FFmpeg if not callback or logger is set.
|
||||
|
||||
The global log level, if not set, defaults to ``'warning'``.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`loglevel`: str
|
||||
The log level. Can be one of the keys of :attr:`loglevels`.
|
||||
'''
|
||||
cdef int level
|
||||
global log_level
|
||||
if loglevel not in loglevels:
|
||||
raise ValueError('Invalid loglevel {}'.format(loglevel))
|
||||
level = loglevels[loglevel]
|
||||
_log_mutex.lock()
|
||||
av_log_set_level(level)
|
||||
log_level = level
|
||||
_log_mutex.unlock()
|
||||
set_loglevel(_loglevel_inverse[log_level])
|
||||
|
||||
def get_loglevel():
|
||||
'''Returns the log level set with :func:`set_loglevel`, or the default level if not
|
||||
set. It is one of the keys of :attr:`loglevels`.
|
||||
'''
|
||||
cdef int level
|
||||
_log_mutex.lock()
|
||||
level = log_level
|
||||
_log_mutex.unlock()
|
||||
return _loglevel_inverse[level]
|
||||
|
||||
|
||||
cpdef get_codecs(
|
||||
int encode=False, int decode=False, int video=False, int audio=False,
|
||||
int data=False, int subtitle=False, int attachment=False, other=False):
|
||||
'''Returns a list of codecs (e.g. h264) that is available by ffpyplayer for
|
||||
encoding or decoding and matches the media types, e.g. video or audio.
|
||||
|
||||
The parameters determine which codecs is included in the result. The parameters
|
||||
all default to False.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`encode`: bool
|
||||
If True, includes the encoding codecs in the result. Defaults to False.
|
||||
`decode`: bool
|
||||
If True, includes the decoding codecs in the result. Defaults to False.
|
||||
`video`: bool
|
||||
If True, includes the video codecs in the result. Defaults to False.
|
||||
`audio`: bool
|
||||
If True, includes the audio codecs in the result. Defaults to False.
|
||||
`data`: bool
|
||||
If True, includes the (continuous) side data codecs in the result. Defaults to False.
|
||||
`subtitle`: bool
|
||||
If True, includes the subtitle codecs in the result. Defaults to False.
|
||||
`attachment`: bool
|
||||
If True, includes the (sparse) data attachment codecs in the result. Defaults to False.
|
||||
`other`: bool
|
||||
If True, returns all the codec media types.
|
||||
|
||||
:returns:
|
||||
|
||||
A sorted list of the matching codec names.
|
||||
'''
|
||||
cdef list codecs = []
|
||||
cdef AVCodec *codec = NULL
|
||||
cdef void *iter_codec = NULL
|
||||
codec = av_codec_iterate(&iter_codec)
|
||||
|
||||
while codec != NULL:
|
||||
if ((encode and av_codec_is_encoder(codec) or
|
||||
decode and av_codec_is_decoder(codec)) and
|
||||
(video and codec.type == AVMEDIA_TYPE_VIDEO or
|
||||
audio and codec.type == AVMEDIA_TYPE_AUDIO or
|
||||
data and codec.type == AVMEDIA_TYPE_DATA or
|
||||
subtitle and codec.type == AVMEDIA_TYPE_SUBTITLE or
|
||||
attachment and codec.type == AVMEDIA_TYPE_ATTACHMENT or
|
||||
other)):
|
||||
codecs.append(tcode(codec.name))
|
||||
codec = av_codec_iterate(&iter_codec)
|
||||
return sorted(codecs)
|
||||
|
||||
codecs_enc = get_codecs(encode=True, video=True)
|
||||
'''A list of all the codecs available for encoding video. '''
|
||||
codecs_dec = get_codecs(decode=True, video=True, audio=True)
|
||||
'''A list of all the codecs available for decoding video and audio. '''
|
||||
|
||||
cdef list list_pixfmts():
|
||||
cdef list fmts = []
|
||||
cdef const AVPixFmtDescriptor *desc = NULL
|
||||
desc = av_pix_fmt_desc_next(desc)
|
||||
|
||||
while desc != NULL:
|
||||
fmts.append(tcode(desc.name))
|
||||
desc = av_pix_fmt_desc_next(desc)
|
||||
return sorted(fmts)
|
||||
|
||||
pix_fmts = list_pixfmts()
|
||||
'''A list of all the pixel formats available to ffmpeg. '''
|
||||
|
||||
cpdef get_fmts(int input=False, int output=False):
|
||||
'''Returns the formats available in FFmpeg.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`input`: bool
|
||||
If True, also includes input formats in the result. Defaults to False
|
||||
`output`: bool
|
||||
If True, also includes output formats in the result. Defaults to False
|
||||
|
||||
:returns:
|
||||
|
||||
A 3-tuple of 3 lists, ``formats``, ``full_names``, and ``extensions``.
|
||||
Each of the three lists are of identical length.
|
||||
|
||||
`formats`: list
|
||||
A list of the names of the formats.
|
||||
`full_names`: list
|
||||
A list of the corresponding human readable names for each of the
|
||||
formats. Can be the empty string if none is available.
|
||||
`extensions`: list
|
||||
A list of the extensions associated with the corresponding formats.
|
||||
Each item is a (possibly empty) list of extensions names.
|
||||
'''
|
||||
cdef list fmts = [], full_names = [], exts = []
|
||||
cdef AVOutputFormat *ofmt = NULL
|
||||
cdef AVInputFormat *ifmt = NULL
|
||||
cdef void *ifmt_opaque = NULL
|
||||
cdef void *ofmt_opaque = NULL
|
||||
cdef object names, full_name, ext
|
||||
|
||||
if output:
|
||||
ofmt = av_muxer_iterate(&ofmt_opaque)
|
||||
while ofmt != NULL:
|
||||
if ofmt.name != NULL:
|
||||
names = tcode(ofmt.name).split(',')
|
||||
full_name = tcode(ofmt.long_name) if ofmt.long_name != NULL else ''
|
||||
ext = tcode(ofmt.extensions).split(',') if ofmt.extensions != NULL else []
|
||||
|
||||
fmts.extend(names)
|
||||
full_names.extend([full_name, ] * len(names))
|
||||
exts.extend([ext, ] * len(names))
|
||||
ofmt = av_muxer_iterate(&ofmt_opaque)
|
||||
|
||||
if input:
|
||||
ifmt = av_demuxer_iterate(&ifmt_opaque)
|
||||
while ifmt != NULL:
|
||||
if ifmt.name != NULL:
|
||||
names = tcode(ifmt.name).split(',')
|
||||
full_name = tcode(ifmt.long_name) if ifmt.long_name != NULL else ''
|
||||
ext = tcode(ifmt.extensions).split(',') if ifmt.extensions != NULL else []
|
||||
|
||||
fmts.extend(names)
|
||||
full_names.extend([full_name, ] * len(names))
|
||||
exts.extend([ext, ] * len(names))
|
||||
ifmt = av_demuxer_iterate(&ifmt_opaque)
|
||||
|
||||
exts = [x for (y, x) in sorted(zip(fmts, exts), key=_get_item0)]
|
||||
full_names = [x for (y, x) in sorted(zip(fmts, full_names), key=_get_item0)]
|
||||
fmts = sorted(fmts)
|
||||
return fmts, full_names, exts
|
||||
|
||||
formats_in = get_fmts(input=True)[0]
|
||||
'''A list of all the formats (e.g. file formats) available for reading. '''
|
||||
formats_out = get_fmts(output=True)[0]
|
||||
'''A list of all the formats (e.g. file formats) available for writing. '''
|
||||
|
||||
def get_format_codec(filename=None, fmt=None):
|
||||
'''Returns the best codec associated with the file format. The format
|
||||
can be provided using either ``filename`` or ``fmt``.
|
||||
|
||||
:Parameters:
|
||||
`filename`: str or None
|
||||
The output filename. If provided, the extension of the filename
|
||||
is used to guess the format.
|
||||
`fmt`: str or None.
|
||||
The format to use. Can be one of :attr:`ffpyplayer.tools.formats_out`.
|
||||
|
||||
:returns:
|
||||
|
||||
str:
|
||||
The name from :attr:`ffpyplayer.tools.codecs_enc`
|
||||
of the best codec that can be used with this format.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> get_format_codecs('test.png')
|
||||
'mjpeg'
|
||||
>>> get_format_codecs('test.jpg')
|
||||
'mjpeg'
|
||||
>>> get_format_codecs('test.mkv')
|
||||
'libx264'
|
||||
>>> get_format_codecs(fmt='h264')
|
||||
'libx264'
|
||||
'''
|
||||
cdef int res
|
||||
cdef char *format_name = NULL
|
||||
cdef char *name = NULL
|
||||
cdef const AVCodec *codec_desc = NULL
|
||||
cdef AVFormatContext *fmt_ctx = NULL
|
||||
cdef char msg[256]
|
||||
cdef AVCodecID codec_id
|
||||
|
||||
if fmt:
|
||||
fmt = fmt.encode('utf8')
|
||||
format_name = fmt
|
||||
if filename:
|
||||
filename = filename.encode('utf8')
|
||||
name = filename
|
||||
|
||||
res = avformat_alloc_output_context2(&fmt_ctx, NULL, format_name, name)
|
||||
if res < 0 or fmt_ctx == NULL or fmt_ctx.oformat == NULL:
|
||||
raise Exception('Failed to find format: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
|
||||
codec_id = fmt_ctx.oformat.video_codec
|
||||
codec_desc = avcodec_find_encoder(codec_id)
|
||||
if codec_desc == NULL:
|
||||
raise Exception('Default codec not found for format')
|
||||
return tcode(codec_desc.name)
|
||||
|
||||
|
||||
def get_supported_framerates(codec_name, rate=()):
|
||||
'''Returns the supported frame rates for encoding codecs. If a desired rate is
|
||||
provided, it also returns the closest valid rate.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`codec_name`: str
|
||||
The name of a encoding codec.
|
||||
`rate`: 2-tuple of ints, or empty tuple.
|
||||
If provided, a 2-tuple where the first element is the numerator,
|
||||
and the second the denominator of the frame rate we wish to use. E.g.
|
||||
(2997, 100) means a frame rate of 29.97.
|
||||
|
||||
:returns:
|
||||
|
||||
(list of 2-tuples, or empty list):
|
||||
If there are no restrictions on the frame rate (i.e. all rates are valid)
|
||||
it returns a empty list, otherwise it returns a list with the valid
|
||||
frame rates. If `rate` is provided and there are restrictions on the frame
|
||||
rates, the closest frame rate is the zero'th element in the list.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> print get_supported_framerates('mpeg1video')
|
||||
[(24000, 1001), (24, 1), (25, 1), (30000, 1001), (30, 1), (50, 1),
|
||||
(60000, 1001), (60, 1), (15, 1), (5, 1), (10, 1), (12, 1), (15, 1)]
|
||||
|
||||
>>> print get_supported_framerates('mpeg1video', (2997, 100))
|
||||
[(30000, 1001), (24000, 1001), (24, 1), (25, 1), (30, 1), (50, 1),
|
||||
(60000, 1001), (60, 1), (15, 1), (5, 1), (10, 1), (12, 1), (15, 1)]
|
||||
'''
|
||||
cdef AVRational rate_struct
|
||||
cdef list rate_list = []
|
||||
cdef int i = 0
|
||||
cdef bytes name = codec_name if isinstance(codec_name, bytes) else codec_name.encode('utf8')
|
||||
cdef AVCodec *codec = avcodec_find_encoder_by_name(name)
|
||||
if codec == NULL:
|
||||
raise Exception('Encoder codec %s not available.' % codec_name)
|
||||
if codec.supported_framerates == NULL:
|
||||
return rate_list
|
||||
|
||||
while codec.supported_framerates[i].den:
|
||||
rate_list.append((codec.supported_framerates[i].num, codec.supported_framerates[i].den))
|
||||
i += 1
|
||||
if rate:
|
||||
rate_struct.num, rate_struct.den = rate
|
||||
i = av_find_nearest_q_idx(rate_struct, codec.supported_framerates)
|
||||
rate = rate_list[i]
|
||||
del rate_list[i]
|
||||
rate_list.insert(0, rate)
|
||||
return rate_list
|
||||
|
||||
def get_supported_pixfmts(codec_name, pix_fmt=''):
|
||||
'''Returns the supported pixel formats for encoding codecs. If a desired format
|
||||
is provided, it also returns the closest format (i.e. the format with minimum
|
||||
conversion loss).
|
||||
|
||||
:Parameters:
|
||||
|
||||
`codec_name`: str
|
||||
The name of a encoding codec.
|
||||
`pix_fmt`: str
|
||||
If not empty, the name of a pixel format we wish to use with this codec,
|
||||
e.g. 'rgb24'.
|
||||
|
||||
:returns:
|
||||
|
||||
(list of pixel formats, or empty list):
|
||||
If there are no restrictions on the pixel formats (i.e. all the formats
|
||||
are valid) it returns a empty list, otherwise it returns a list with the
|
||||
valid formats. If pix_fmt is not empty and there are restrictions to the
|
||||
formats, the closest format which results in the minimum loss when converting
|
||||
will be returned as the zero'th element in the list.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> print get_supported_pixfmts('ffv1')
|
||||
['yuv420p', 'yuva420p', 'yuva422p', 'yuv444p', 'yuva444p', 'yuv440p', ...
|
||||
'gray16le', 'gray', 'gbrp9le', 'gbrp10le', 'gbrp12le', 'gbrp14le']
|
||||
|
||||
>>> print get_supported_pixfmts('ffv1', 'gray')
|
||||
['gray', 'yuv420p', 'yuva420p', 'yuva422p', 'yuv444p', 'yuva444p', ...
|
||||
'gray16le', 'gbrp9le', 'gbrp10le', 'gbrp12le', 'gbrp14le']
|
||||
'''
|
||||
cdef AVPixelFormat fmt
|
||||
cdef bytes name = codec_name if isinstance(codec_name, bytes) else codec_name.encode('utf8')
|
||||
cdef bytes fmt_b = pix_fmt if isinstance(pix_fmt, bytes) else pix_fmt.encode('utf8')
|
||||
cdef list fmt_list = []
|
||||
cdef int i = 0, loss = 0, has_alpha = 0
|
||||
cdef AVCodec *codec = avcodec_find_encoder_by_name(name)
|
||||
if codec == NULL:
|
||||
raise Exception('Encoder codec %s not available.' % codec_name)
|
||||
if pix_fmt and av_get_pix_fmt(fmt_b) == AV_PIX_FMT_NONE:
|
||||
raise Exception('Pixel format not recognized.')
|
||||
if codec.pix_fmts == NULL:
|
||||
return fmt_list
|
||||
|
||||
while codec.pix_fmts[i] != AV_PIX_FMT_NONE:
|
||||
fmt_list.append(tcode(av_get_pix_fmt_name(codec.pix_fmts[i])))
|
||||
i += 1
|
||||
if pix_fmt:
|
||||
# XXX: fix this to check if NULL (although kinda already checked above)
|
||||
has_alpha = av_pix_fmt_desc_get(av_get_pix_fmt(fmt_b)).nb_components % 2 == 0
|
||||
fmt = avcodec_find_best_pix_fmt_of_list(codec.pix_fmts, av_get_pix_fmt(fmt_b),
|
||||
has_alpha, &loss)
|
||||
i = fmt_list.index(tcode(av_get_pix_fmt_name(fmt)))
|
||||
pix = fmt_list[i]
|
||||
del fmt_list[i]
|
||||
fmt_list.insert(0, pix)
|
||||
return fmt_list
|
||||
|
||||
def get_best_pix_fmt(pix_fmt, pix_fmts):
|
||||
'''Returns the best pixel format with the least conversion loss from the
|
||||
original pixel format, given a list of potential pixel formats.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`pix_fmt`: str
|
||||
The name of a original pixel format.
|
||||
`pix_fmts`: list-type of strings
|
||||
A list of possible pixel formats from which the best will be chosen.
|
||||
|
||||
:returns:
|
||||
|
||||
The pixel format with the least conversion loss.
|
||||
|
||||
.. note::
|
||||
|
||||
The returned pixel format seems to be somewhat sensitive to the order
|
||||
of the input pixel formats. Higher quality pixel formats should therefore
|
||||
be at the beginning of the list.
|
||||
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> get_best_pix_fmt('yuv420p', ['rgb24', 'rgba', 'yuv444p', 'gray'])
|
||||
'rgb24'
|
||||
>>> get_best_pix_fmt('gray', ['rgb24', 'rgba', 'yuv444p', 'gray'])
|
||||
'gray'
|
||||
>>> get_best_pix_fmt('rgb8', ['rgb24', 'yuv420p', 'rgba', 'yuv444p', 'gray'])
|
||||
'rgb24'
|
||||
'''
|
||||
cdef AVPixelFormat fmt, fmt_src
|
||||
cdef bytes fmt_src_b = pix_fmt if isinstance(pix_fmt, bytes) else pix_fmt.encode('utf8')
|
||||
cdef bytes fmt_b
|
||||
cdef int i = 0, loss = 0, has_alpha = 0
|
||||
cdef AVPixelFormat *fmts = NULL
|
||||
|
||||
if not pix_fmt or not pix_fmts:
|
||||
raise ValueError('Invalid arguments {}, {}'.format(pix_fmt, pix_fmts))
|
||||
fmt_src = av_get_pix_fmt(fmt_src_b)
|
||||
if fmt_src == AV_PIX_FMT_NONE:
|
||||
raise Exception('Pixel format {} not recognized.'.format(pix_fmt))
|
||||
|
||||
fmts = <AVPixelFormat *>malloc(sizeof(AVPixelFormat) * (len(pix_fmts) + 1))
|
||||
if fmts == NULL:
|
||||
raise MemoryError()
|
||||
|
||||
try:
|
||||
fmts[len(pix_fmts)] = AV_PIX_FMT_NONE
|
||||
|
||||
for i, fmt_s in enumerate(pix_fmts):
|
||||
fmt_b = fmt_s if isinstance(fmt_s, bytes) else fmt_s.encode('utf8')
|
||||
fmts[i] = av_get_pix_fmt(fmt_b)
|
||||
if fmts[i] == AV_PIX_FMT_NONE:
|
||||
raise Exception('Pixel format {} not recognized.'.format(fmt_s))
|
||||
|
||||
has_alpha = av_pix_fmt_desc_get(fmt_src).nb_components % 2 == 0
|
||||
fmt = avcodec_find_best_pix_fmt_of_list(fmts, fmt_src, has_alpha, &loss)
|
||||
finally:
|
||||
free(fmts)
|
||||
|
||||
return tcode(av_get_pix_fmt_name(fmt))
|
||||
|
||||
def emit_library_info():
|
||||
'''Prints to the ffmpeg log all the ffmpeg library's versions and configure
|
||||
options.
|
||||
'''
|
||||
print_all_libs_info(INDENT|SHOW_CONFIG, AV_LOG_INFO)
|
||||
print_all_libs_info(INDENT|SHOW_VERSION, AV_LOG_INFO)
|
||||
|
||||
def _dshow_log_callback(log, message, level):
|
||||
message = message.encode('utf8')
|
||||
|
||||
if not log:
|
||||
log.append((message, level))
|
||||
return
|
||||
|
||||
last_msg, last_level = log[-1]
|
||||
if last_level == level:
|
||||
log[-1] = last_msg + message, level
|
||||
else:
|
||||
log.append((message, level))
|
||||
|
||||
|
||||
cpdef int list_dshow_opts(list log, bytes stream, bytes option) except 1:
|
||||
cdef AVFormatContext *fmt = NULL
|
||||
cdef AVDictionary* opts = NULL
|
||||
cdef AVInputFormat *ifmt
|
||||
cdef object old_callback
|
||||
cdef int level
|
||||
cdef list temp_log = []
|
||||
cdef bytes item
|
||||
global log_level
|
||||
|
||||
ifmt = av_find_input_format(b"dshow")
|
||||
if ifmt == NULL:
|
||||
raise Exception('Direct show not found.')
|
||||
|
||||
av_dict_set(&opts, option, b"true", 0)
|
||||
_log_mutex.lock()
|
||||
old_callback = set_log_callback(partial(_dshow_log_callback, temp_log))
|
||||
level = log_level
|
||||
|
||||
av_log_set_level(AV_LOG_TRACE)
|
||||
log_level = AV_LOG_TRACE
|
||||
avformat_open_input(&fmt, stream, ifmt, &opts)
|
||||
|
||||
av_log_set_level(level)
|
||||
log_level = level
|
||||
set_log_callback(old_callback)
|
||||
|
||||
_log_mutex.unlock()
|
||||
avformat_close_input(&fmt)
|
||||
av_dict_free(&opts)
|
||||
|
||||
for item, l in temp_log:
|
||||
for line in item.splitlines():
|
||||
log.append((line, l))
|
||||
return 0
|
||||
|
||||
def list_dshow_devices():
|
||||
'''Returns a list of the dshow devices available.
|
||||
|
||||
:returns:
|
||||
|
||||
`3-tuple`: A 3-tuple, of (`video`, `audio`, `names`)
|
||||
|
||||
`video`: dict
|
||||
A dict of all the direct show **video** devices. The keys
|
||||
of the dict are the unique names of the available direct show devices. The values
|
||||
are a list of the available configurations for that device. Each
|
||||
element in the list has the following format:
|
||||
``(pix_fmt, codec_fmt, (frame_width, frame_height), (min_framerate, max_framerate))``
|
||||
`audio`: dict
|
||||
A dict of all the direct show **audio** devices. The keys
|
||||
of the dict are the unique names of the available direct show devices. The values
|
||||
are a list of the available configurations for that device. Each
|
||||
element in the list has the following format:
|
||||
``((min_num_channels, min_num_channels), (min_bits, max_bits), (min_rate, max_rate))``.
|
||||
`names`: dict
|
||||
A dict mapping the unique names of the video and audio devices to
|
||||
a more human friendly (possibly non-unique) name. Either of these
|
||||
names can be used when opening the device. However, if using the non-unique
|
||||
name, it's not guarenteed which of the devices sharing the name will be opened.
|
||||
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
>>> from ffpyplayer.player import MediaPlayer
|
||||
>>> from ffpyplayer.tools import list_dshow_devices
|
||||
>>> import time, weakref
|
||||
>>> dev = list_dshow_devices()
|
||||
>>> print dev
|
||||
({'@device_pnp_...223196\\global': [('bgr24', '', (160, 120), (5, 30)),
|
||||
('bgr24', '', (176, 144), (5, 30)), ('bgr24', '', (320, 176), (5, 30)),
|
||||
('bgr24', '', (320, 240), (5, 30)), ('bgr24', '', (352, 288), (5, 30)),
|
||||
...
|
||||
('yuv420p', '', (320, 240), (5, 30)), ('yuv420p', '', (352, 288), (5, 30))],
|
||||
'@device_pnp_...223196\\global': [('bgr24', '', (160, 120), (30, 30)),
|
||||
...
|
||||
('yuyv422', '', (352, 288), (30, 30)),
|
||||
('yuyv422', '', (640, 480), (30, 30))]},
|
||||
{'@device_cm_...2- HD Webcam C615)': [((1, 2), (8, 16), (11025, 44100))],
|
||||
'@device_cm_...HD Webcam C615)': [((1, 2), (8, 16), (11025, 44100))]},
|
||||
{'@device_cm_...- HD Webcam C615)': 'Microphone (2- HD Webcam C615)',
|
||||
'@device_cm_...2- HD Webcam C615)': 'Microphone (3- HD Webcam C615)',
|
||||
...
|
||||
'@device_pnp...223196\\global': 'HD Webcam C615',
|
||||
'@device_pnp...223196\\global': 'Laptop Integrated Webcam'})
|
||||
|
||||
See :ref:`dshow-example` for a full example.
|
||||
'''
|
||||
cdef list res = []
|
||||
cdef dict video = {}, audio = {}, curr = None
|
||||
cdef object last
|
||||
cdef bytes msg, msg2
|
||||
cdef dict name_map = {}
|
||||
|
||||
# list devices
|
||||
list_dshow_opts(res, b'dummy', b'list_devices')
|
||||
# primary dev name
|
||||
pname = re.compile(' *\[dshow *@ *[\w]+\] *"(.+)" *\\((video|audio)\\) *')
|
||||
# alternate dev name
|
||||
apname = re.compile(' *\[dshow *@ *[\w]+\] *Alternative name *"(.+)" *')
|
||||
m = None
|
||||
for msg, level in res:
|
||||
message = msg.decode('utf8')
|
||||
|
||||
# do we match a primary name - i.e. next device
|
||||
m_temp = pname.match(message)
|
||||
if m_temp:
|
||||
m = m_temp
|
||||
curr = audio if m.group(2) == 'audio' else video
|
||||
curr[m.group(1)] = []
|
||||
name_map[m.group(1)] = m.group(1)
|
||||
continue
|
||||
|
||||
m_temp = apname.match(message)
|
||||
# if we match alternate name and already have primary, then we're adding it
|
||||
if m_temp and m:
|
||||
curr[m_temp.group(1)] = []
|
||||
name_map[m_temp.group(1)] = m.group(1)
|
||||
del curr[m.group(1)]
|
||||
del name_map[m.group(1)]
|
||||
else:
|
||||
msg2 = message.encode('utf8')
|
||||
av_log(NULL, loglevels[level], '%s', msg2)
|
||||
|
||||
m = None
|
||||
|
||||
# list video devices options
|
||||
vid_opts = re.compile(' *\[dshow *@ *[\w]+\] +(pixel_format|vcodec)=([\w]+) +min +s=\d+x\d+ +fps=(\d+)\
|
||||
+max +s=(\d+)x(\d+) +fps=(\d+).*')
|
||||
pheader1 = re.compile(' *\[dshow *@ *[\w]+\] *(?:Pin|Selecting pin) (?:"Capture"|"Output"|Capture|Output).*')
|
||||
pheader2 = re.compile(' *\[dshow *@ *[\w]+\] *DirectShow (?:video|audio) (?:only )?device options.*')
|
||||
for video_stream in video:
|
||||
res = []
|
||||
list_dshow_opts(res, ("video=%s" % video_stream).encode('utf8'), b'list_options')
|
||||
|
||||
for msg, level in res:
|
||||
message = msg.decode('utf8')
|
||||
opts = vid_opts.match(message)
|
||||
|
||||
if not opts:
|
||||
if not pheader1.match(message) and not pheader2.match(message):
|
||||
av_log(NULL, loglevels[level], '%s', msg)
|
||||
continue
|
||||
|
||||
g1, g2, g3, g4, g5, g6 = opts.groups()
|
||||
if g1 == 'pixel_format':
|
||||
item = g2, "", (int(g4), int(g5)), (int(g3), int(g6))
|
||||
else:
|
||||
item = "", g2, (int(g4), int(g5)), (int(g3), int(g6))
|
||||
|
||||
if item not in video[video_stream]:
|
||||
video[video_stream].append(item)
|
||||
|
||||
video[video_stream] = sorted(video[video_stream])
|
||||
|
||||
# list audio devices options
|
||||
paud_opts = re.compile(' *\[dshow *@ *[\w]+\] +ch= *(\d+), +bits= *(\d+),\
|
||||
+rate= *(\d+).*')
|
||||
for audio_stream in audio:
|
||||
res = []
|
||||
list_dshow_opts(res, ("audio=%s" % audio_stream).encode('utf8'), b'list_options')
|
||||
for msg, level in res:
|
||||
message = msg.decode('utf8')
|
||||
mopts = paud_opts.match(message)
|
||||
|
||||
if mopts:
|
||||
opts = (int(mopts.group(1)), int(mopts.group(2)), int(mopts.group(3)))
|
||||
if opts not in audio[audio_stream]:
|
||||
audio[audio_stream].append(opts)
|
||||
elif (not pheader1.match(message)) and (not pheader2.match(message)):
|
||||
av_log(NULL, loglevels[level], '%s', msg)
|
||||
audio[audio_stream] = sorted(audio[audio_stream])
|
||||
|
||||
return video, audio, name_map
|
||||
|
||||
|
||||
cdef object encode_text(object item, int encode):
|
||||
if isinstance(item, basestring):
|
||||
if encode:
|
||||
return item.encode('utf8')
|
||||
return item.decode('utf8')
|
||||
|
||||
if isinstance(item, dict):
|
||||
for k, v in item.items():
|
||||
item[k] = encode_text(v, encode)
|
||||
return item
|
||||
|
||||
try:
|
||||
iter(item)
|
||||
except TypeError:
|
||||
return item
|
||||
|
||||
return item.__class__((encode_text(i, encode) for i in item))
|
||||
|
||||
def encode_to_bytes(item):
|
||||
'''Takes the item and walks it recursively whether it's a string, int, iterable,
|
||||
etc. and encodes all the strings to utf-8.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`item`: anything
|
||||
The object to be walked and encoded.
|
||||
|
||||
:returns:
|
||||
|
||||
An object identical to the ``item``, but with all strings encoded to utf-8.
|
||||
'''
|
||||
return encode_text(item, 1)
|
||||
|
||||
def decode_to_unicode(item):
|
||||
'''Takes the item and walks it recursively whether it's a string, int, iterable,
|
||||
etc. and encodes all the strings to utf-8.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`item`: anything
|
||||
The object to be walked and encoded.
|
||||
|
||||
:returns:
|
||||
|
||||
An object identical to the ``item``, but with all strings encoded to utf-8.
|
||||
'''
|
||||
return encode_text(item, 0)
|
||||
|
||||
def convert_to_str(item):
|
||||
'''Takes the item and walks it recursively whether it's a string, int, iterable,
|
||||
etc. and encodes all the strings to utf-8.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`item`: anything
|
||||
The object to be walked and encoded.
|
||||
|
||||
:returns:
|
||||
|
||||
An object identical to the ``item``, but with all strings encoded to utf-8.
|
||||
'''
|
||||
return encode_text(item, False)
|
||||
BIN
Lib/site-packages/ffpyplayer/writer.cp313-win_amd64.pyd
Normal file
BIN
Lib/site-packages/ffpyplayer/writer.cp313-win_amd64.pyd
Normal file
Binary file not shown.
49
Lib/site-packages/ffpyplayer/writer.pxd
Normal file
49
Lib/site-packages/ffpyplayer/writer.pxd
Normal file
@ -0,0 +1,49 @@
|
||||
include 'includes/ffmpeg.pxi'
|
||||
|
||||
|
||||
cdef class MediaWriter(object):
|
||||
cdef AVFormatContext *fmt_ctx
|
||||
cdef MediaStream *streams
|
||||
cdef int n_streams
|
||||
cdef list config
|
||||
cdef AVDictionary *format_opts
|
||||
cdef int64_t total_size
|
||||
cdef int closed
|
||||
|
||||
cpdef close(self)
|
||||
cdef void clean_up(MediaWriter self) nogil
|
||||
|
||||
|
||||
cdef struct MediaStream:
|
||||
# pointer to the stream to which we're adding frames.
|
||||
AVStream *av_stream
|
||||
int index
|
||||
AVCodec *codec
|
||||
AVCodecContext *codec_ctx
|
||||
# codec used to encode video
|
||||
AVCodecID codec_id
|
||||
# the size of the frame passed in
|
||||
int width_in
|
||||
int width_out
|
||||
# the size of the frame actually written to disk
|
||||
int height_in
|
||||
int height_out
|
||||
# The denominator of the frame rate of the stream
|
||||
int den
|
||||
# The numerator of the frame rate of the stream
|
||||
int num
|
||||
# the pixel format of the frame passed in
|
||||
AVPixelFormat pix_fmt_in
|
||||
# the pixel format of the frame actually written to disk
|
||||
# if it's -1 (AV_PIX_FMT_NONE) then input will be used. '''
|
||||
AVPixelFormat pix_fmt_out
|
||||
|
||||
# The frame in which the final image to be written to disk is held, when we
|
||||
# need to convert.
|
||||
AVFrame *av_frame
|
||||
SwsContext *sws_ctx
|
||||
int count
|
||||
int64_t pts
|
||||
int sync_fmt
|
||||
|
||||
AVDictionary *codec_opts
|
||||
582
Lib/site-packages/ffpyplayer/writer.pyx
Normal file
582
Lib/site-packages/ffpyplayer/writer.pyx
Normal file
@ -0,0 +1,582 @@
|
||||
'''
|
||||
FFmpeg based media writer
|
||||
=========================
|
||||
|
||||
A FFmpeg based python media writer. See :class:`MediaWriter` for details.
|
||||
Currently writes only video.
|
||||
'''
|
||||
|
||||
__all__ = ('MediaWriter', )
|
||||
|
||||
include "includes/inline_funcs.pxi"
|
||||
|
||||
cdef extern from "string.h" nogil:
|
||||
void *memset(void *, int, size_t)
|
||||
|
||||
cdef extern from "stdlib.h" nogil:
|
||||
void *malloc(size_t)
|
||||
void free(void *)
|
||||
|
||||
cdef extern from "math.h" nogil:
|
||||
double floor(double)
|
||||
|
||||
cdef extern from "errno.h" nogil:
|
||||
int ENOENT
|
||||
int EAGAIN
|
||||
|
||||
from ffpyplayer.pic cimport Image
|
||||
|
||||
import ffpyplayer.tools # required to init ffmpeg
|
||||
from ffpyplayer.tools import encode_to_bytes, convert_to_str
|
||||
from copy import deepcopy
|
||||
from ffpyplayer.tools import get_supported_framerates, get_supported_pixfmts
|
||||
|
||||
DEF VSYNC_PASSTHROUGH = 0
|
||||
DEF VSYNC_CFR = 1
|
||||
DEF VSYNC_VFR = 2
|
||||
DEF VSYNC_DROP = 0xff
|
||||
|
||||
cdef int AV_ENOENT = ENOENT if ENOENT < 0 else -ENOENT
|
||||
cdef int AV_EAGAIN = EAGAIN if EAGAIN < 0 else -EAGAIN
|
||||
|
||||
|
||||
cdef class MediaWriter(object):
|
||||
'''An FFmpeg based media writer class. Currently only supports video.
|
||||
|
||||
With this class one can write images frames stored in many different pixel
|
||||
formats into a multi-stream video file using :meth:`write_frame`. All FFmpeg
|
||||
codecs and pixel formats are supported.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`filename`: str
|
||||
The filename of the media file to create. Will be encoded using utf8
|
||||
berfore passing to FFmpeg.
|
||||
`streams`: list of dicts
|
||||
A list of streams to create in the file. ``streams``
|
||||
is a list of dicts, where each dict configures the corresponding stream.
|
||||
The keywords listed below are available. One can also specify default
|
||||
values for the keywords for all streams using ``kwargs``. Keywords also
|
||||
found in ``streams`` will overwrite those in ``kwargs``:
|
||||
|
||||
`pix_fmt_in`: str
|
||||
The pixel format of the :class:`~ffpyplayer.pic.Image`
|
||||
to be passed to :meth:`write_frame` for this stream. Can be one of
|
||||
:attr:`ffpyplayer.tools.pix_fmts`.
|
||||
`width_in`: int
|
||||
The width of the :class:`ffpyplayer.pic.Image` that will be
|
||||
passed to :meth:`write_frame` for this stream.
|
||||
`height_in`: int
|
||||
The height of the :class:`ffpyplayer.pic.Image` that will be
|
||||
passed to :meth:`write_frame` for this stream.
|
||||
`pix_fmt_out`: str
|
||||
The pixel format in which frames will be
|
||||
written to the file for this stream. Can be one of
|
||||
:attr:`ffpyplayer.tools.pix_fmts`. Defaults to ``pix_fmt_in``
|
||||
if not provided. Not every pixel format is supported for each
|
||||
encoding codec, see :func:`~ffpyplayer.tools.get_supported_pixfmts`
|
||||
for which pixel formats are supported for ``codec``.
|
||||
`width_out`: int
|
||||
The width at which frames will be written to the file for this
|
||||
stream. Defaults to ``width_in`` if not provided.
|
||||
`height_out`: int
|
||||
The height at which frames will be written to the file for this
|
||||
stream. Defaults to ``height_in`` if not provided.
|
||||
`codec`: str
|
||||
The codec used to write the frames to the file. Can be one of
|
||||
the encoding codecs in :attr:`ffpyplayer.tools.codecs_enc`.
|
||||
|
||||
If not provided, it defaults to the default best codec for the format
|
||||
provided in ``fmt`` or guessed from the ``filename``.
|
||||
See :func:`ffpyplayer.tools.get_format_codecs`
|
||||
`frame_rate`: 2-tuple of ints
|
||||
A 2-tuple of ints representing the frame rate to be used when writing
|
||||
the file. The first element is the numerator, while the second is the
|
||||
denuminator of a ratio describing the rate. E.g. (2997, 100) describes
|
||||
29.97 fps.
|
||||
|
||||
The timestamps of the frames written using :meth:`write_frame` do
|
||||
not necessarily need to be multiples of the frame rate because they might be
|
||||
forced to matching timestamps if required. Not every frame rate is
|
||||
supported for each encoding codec, see
|
||||
:func:`ffpyplayer.tools.get_supported_framerates` for which frame
|
||||
rates are supported for ``codec``.
|
||||
|
||||
`fmt`: str
|
||||
The format to use for the output. Can be one of
|
||||
:attr:`ffpyplayer.tools.formats_out`. Defaults to empty string.
|
||||
If not provided, ``filename`` will be used determine the format,
|
||||
otherwise this arg will be used.
|
||||
`lib_opts`: dict or list of dicts
|
||||
A dictionary of options that will be passed
|
||||
to the ffmpeg libraries, codecs, sws, and formats when opening them.
|
||||
This accepts most of the options that can be passed to ffmpeg libraries.
|
||||
See below for examples. Both the keywords and values must be strings.
|
||||
It can be passed a dict in which case it'll be applied to all the streams
|
||||
or a list containing a dict for each stream.
|
||||
`metadata`: dict or list of dicts
|
||||
Metadata that will be written to the streams, if
|
||||
supported by the stream. See below for examples. Both the keywords and
|
||||
values must be strings. It can be passed a dict in which case it'll be
|
||||
applied to all the streams or a list containing a dict for each stream.
|
||||
If (these) metadata is not supported, it will silently fail to write them.
|
||||
`overwrite`: bool
|
||||
Whether we should overwrite an existing file.
|
||||
If False, an error will be raised if the file already exists. If True,
|
||||
the file will be overwritten if it exists.
|
||||
`**kwargs`:
|
||||
Accepts default values for all ``streams`` which will be used if these
|
||||
keywords are not provided for any stream.
|
||||
|
||||
See :ref:`write-simple` and :ref:`write-h264` for examples.
|
||||
'''
|
||||
|
||||
def __cinit__(self, filename, streams, fmt='', lib_opts={}, metadata={},
|
||||
overwrite=False, **kwargs):
|
||||
cdef int res = 0, n = len(streams), r
|
||||
cdef char *format_name = NULL
|
||||
cdef char msg[256]
|
||||
cdef MediaStream *s
|
||||
cdef AVDictionaryEntry *dict_temp = NULL
|
||||
cdef bytes msg2
|
||||
cdef const AVCodec *codec_desc
|
||||
|
||||
filename = encode_to_bytes(filename)
|
||||
streams = encode_to_bytes(deepcopy(streams))
|
||||
if fmt:
|
||||
fmt = fmt.encode('utf8')
|
||||
lib_opts = encode_to_bytes(deepcopy(lib_opts))
|
||||
metadata = encode_to_bytes(deepcopy(metadata))
|
||||
kwargs = encode_to_bytes(deepcopy(kwargs))
|
||||
|
||||
self.total_size = 0
|
||||
self.closed = 0
|
||||
self.format_opts = NULL
|
||||
if fmt:
|
||||
format_name = fmt
|
||||
if not n:
|
||||
raise Exception('Streams parameters not provided.')
|
||||
conf = [deepcopy(kwargs) for i in streams]
|
||||
for r in range(n):
|
||||
conf[r].update(streams[r])
|
||||
self.config = conf
|
||||
|
||||
self.fmt_ctx = NULL
|
||||
res = avformat_alloc_output_context2(&self.fmt_ctx, NULL, format_name, filename)
|
||||
if res < 0 or self.fmt_ctx == NULL:
|
||||
raise Exception('Failed to create format context: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
self.streams = <MediaStream *>malloc(n * sizeof(MediaStream))
|
||||
if self.streams == NULL:
|
||||
self.clean_up()
|
||||
raise MemoryError()
|
||||
s = self.streams
|
||||
self.n_streams = n
|
||||
memset(s, 0, n * sizeof(MediaStream))
|
||||
if isinstance(lib_opts, dict):
|
||||
lib_opts = [lib_opts, ] * n
|
||||
elif len(lib_opts) == 1:
|
||||
lib_opts = lib_opts * n
|
||||
if isinstance(metadata, dict):
|
||||
metadata = [metadata, ] * n
|
||||
elif len(metadata) == 1:
|
||||
metadata = metadata * n
|
||||
|
||||
for r in range(n):
|
||||
s[r].codec_opts = NULL
|
||||
config = conf[r]
|
||||
if 'pix_fmt_out' not in config or not config['pix_fmt_out']:
|
||||
config['pix_fmt_out'] = config['pix_fmt_in']
|
||||
if 'width_out' not in config or not config['width_out']:
|
||||
config['width_out'] = config['width_in']
|
||||
if 'height_out' not in config or not config['height_out']:
|
||||
config['height_out'] = config['height_in']
|
||||
if 'codec' not in config or not config['codec']:
|
||||
s[r].codec_id = self.fmt_ctx.oformat.video_codec
|
||||
codec_desc = avcodec_find_encoder(s[r].codec_id)
|
||||
if codec_desc == NULL:
|
||||
raise Exception('Default codec not found for output file.')
|
||||
config['codec'] = codec_desc.name
|
||||
else:
|
||||
codec_desc = avcodec_find_encoder_by_name(config['codec'])
|
||||
if codec_desc == NULL:
|
||||
self.clean_up()
|
||||
raise Exception('Encoder codec %s not available.' % config['codec'])
|
||||
s[r].codec_id = codec_desc.id
|
||||
s[r].width_in = config['width_in']
|
||||
s[r].width_out = config['width_out']
|
||||
s[r].height_in = config['height_in']
|
||||
s[r].height_out = config['height_out']
|
||||
s[r].num, s[r].den = config['frame_rate']
|
||||
if av_get_pix_fmt(config['pix_fmt_in']) == AV_PIX_FMT_NONE:
|
||||
self.clean_up()
|
||||
raise Exception('Pixel format %s not found.' % config['pix_fmt_in'])
|
||||
if av_get_pix_fmt(config['pix_fmt_out']) == AV_PIX_FMT_NONE:
|
||||
self.clean_up()
|
||||
raise Exception('Pixel format %s not found.' % config['pix_fmt_out'])
|
||||
s[r].pix_fmt_in = av_get_pix_fmt(config['pix_fmt_in'])
|
||||
s[r].pix_fmt_out = av_get_pix_fmt(config['pix_fmt_out'])
|
||||
|
||||
s[r].codec = avcodec_find_encoder(s[r].codec_id)
|
||||
if s[r].codec == NULL:
|
||||
self.clean_up()
|
||||
raise Exception('Codec %s not found.' % config['codec'])
|
||||
s[r].av_stream = avformat_new_stream(self.fmt_ctx, NULL)
|
||||
if s[r].av_stream == NULL:
|
||||
self.clean_up()
|
||||
raise Exception("Couldn't create stream %d." % r)
|
||||
s[r].index = s[r].av_stream.index
|
||||
|
||||
s[r].codec_ctx = avcodec_alloc_context3(s[r].codec)
|
||||
if s[r].codec_ctx == NULL:
|
||||
self.clean_up()
|
||||
raise MemoryError("Couldn't create stream %d." % r)
|
||||
|
||||
s[r].codec_ctx.width = s[r].width_out
|
||||
s[r].codec_ctx.height = s[r].height_out
|
||||
supported_rates = get_supported_framerates(config['codec'], (s[r].num, s[r].den))
|
||||
if supported_rates and supported_rates[0] != (s[r].num, s[r].den):
|
||||
self.clean_up()
|
||||
raise Exception('%d/%d is not a supported frame rate for codec %s, the \
|
||||
closest valid rate is %d/%d' % (s[r].num, s[r].den, config['codec'],
|
||||
supported_rates[0][0], supported_rates[0][1]))
|
||||
s[r].av_stream.avg_frame_rate.num = s[r].num
|
||||
s[r].av_stream.avg_frame_rate.den = s[r].den
|
||||
s[r].av_stream.r_frame_rate.num = s[r].num
|
||||
s[r].av_stream.r_frame_rate.den = s[r].den
|
||||
s[r].codec_ctx.time_base.den = s[r].num
|
||||
s[r].codec_ctx.time_base.num = s[r].den
|
||||
s[r].codec_ctx.pix_fmt = s[r].pix_fmt_out
|
||||
|
||||
for k, v in metadata[r].items():
|
||||
k_b = k.encode('utf8')
|
||||
res = av_dict_set(&s[r].av_stream.metadata, k_b, v, 0)
|
||||
if res < 0:
|
||||
av_dict_free(&s[r].av_stream.metadata)
|
||||
self.clean_up()
|
||||
raise Exception('Failed to set option %s: %s for stream %d; %s'
|
||||
% (k, v, r, tcode(emsg(res, msg, sizeof(msg)))))
|
||||
# Some formats want stream headers to be separate
|
||||
if self.fmt_ctx.oformat.flags & AVFMT_GLOBALHEADER:
|
||||
s[r].codec_ctx.flags |= AV_CODEC_FLAG_GLOBAL_HEADER
|
||||
|
||||
supported_fmts = get_supported_pixfmts(config['codec'], config['pix_fmt_out'])
|
||||
if supported_fmts and supported_fmts[0] != config['pix_fmt_out'].decode('utf8'):
|
||||
self.clean_up()
|
||||
raise Exception('%s is not a supported pixel format for codec %s, the '
|
||||
'best valid format is %s' % (config['pix_fmt_out'], config['codec'],
|
||||
supported_fmts[0]))
|
||||
|
||||
if (s[r].codec_ctx.pix_fmt != s[r].pix_fmt_in or s[r].codec_ctx.width != s[r].width_in or
|
||||
s[r].codec_ctx.height != s[r].height_in):
|
||||
s[r].av_frame = av_frame_alloc()
|
||||
if s[r].av_frame == NULL:
|
||||
self.clean_up()
|
||||
raise MemoryError()
|
||||
s[r].av_frame.format = s[r].pix_fmt_out
|
||||
s[r].av_frame.width = s[r].width_out
|
||||
s[r].av_frame.height = s[r].height_out
|
||||
if av_frame_get_buffer(s[r].av_frame, 32) < 0:
|
||||
raise Exception('Cannot allocate frame buffers.')
|
||||
|
||||
s[r].sws_ctx = sws_getCachedContext(NULL, s[r].width_in, s[r].height_in,\
|
||||
s[r].pix_fmt_in, s[r].codec_ctx.width, s[r].codec_ctx.height,\
|
||||
s[r].codec_ctx.pix_fmt, SWS_BICUBIC, NULL, NULL, NULL)
|
||||
if s[r].sws_ctx == NULL:
|
||||
self.clean_up()
|
||||
raise Exception('Cannot find conversion context.')
|
||||
|
||||
for k, v in lib_opts[r].items():
|
||||
k_b = k.encode('utf8')
|
||||
if opt_default(k_b, v, s[r].sws_ctx, NULL, NULL, NULL, &self.format_opts, &s[r].codec_opts) < 0:
|
||||
raise Exception('library option %s: %s not found' % (k, v))
|
||||
|
||||
res = avcodec_open2(s[r].codec_ctx, s[r].codec, &s[r].codec_opts)
|
||||
bad_vals = ''
|
||||
dict_temp = av_dict_get(s[r].codec_opts, b"", dict_temp, AV_DICT_IGNORE_SUFFIX)
|
||||
while dict_temp != NULL:
|
||||
bad_vals += '%s: %s, ' % (dict_temp.key, dict_temp.value)
|
||||
dict_temp = av_dict_get(s[r].codec_opts, b"", dict_temp, AV_DICT_IGNORE_SUFFIX)
|
||||
av_dict_free(&s[r].codec_opts)
|
||||
if bad_vals:
|
||||
msg2 = ("The following options were not recognized: %s.\n" % bad_vals).encode('utf8')
|
||||
av_log(NULL, AV_LOG_ERROR, '%s', msg2)
|
||||
if res < 0:
|
||||
self.clean_up()
|
||||
raise Exception('Failed to open codec for stream %d; %s' % (r, tcode(emsg(res, msg, sizeof(msg)))))
|
||||
|
||||
res = avcodec_parameters_from_context(s[r].av_stream.codecpar, s[r].codec_ctx)
|
||||
if res < 0:
|
||||
self.clean_up()
|
||||
raise Exception('Failed to initialize stream parameters for stream %d; %s' % (r, tcode(emsg(res, msg, sizeof(msg)))))
|
||||
|
||||
s[r].pts = 0
|
||||
if self.fmt_ctx.oformat.flags & AVFMT_VARIABLE_FPS:
|
||||
if self.fmt_ctx.oformat.flags & AVFMT_NOTIMESTAMPS:
|
||||
s[r].sync_fmt = VSYNC_PASSTHROUGH
|
||||
else:
|
||||
s[r].sync_fmt = VSYNC_VFR
|
||||
else:
|
||||
s[r].sync_fmt = VSYNC_CFR
|
||||
|
||||
if not (self.fmt_ctx.oformat.flags & AVFMT_NOFILE):
|
||||
res = avio_check(filename, 0)
|
||||
if (not res) and not overwrite:
|
||||
self.clean_up()
|
||||
raise Exception('File %s already exists.' % filename)
|
||||
elif res < 0 and res != AV_ENOENT:
|
||||
self.clean_up()
|
||||
raise Exception('File error: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
res = avio_open2(&self.fmt_ctx.pb, filename, AVIO_FLAG_WRITE, NULL, NULL)
|
||||
if res < 0:
|
||||
self.clean_up()
|
||||
raise Exception('File error: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
res = avformat_write_header(self.fmt_ctx, &self.format_opts)
|
||||
bad_vals = ''
|
||||
dict_temp = av_dict_get(self.format_opts, b"", dict_temp, AV_DICT_IGNORE_SUFFIX)
|
||||
while dict_temp != NULL:
|
||||
bad_vals += '%s: %s, ' % (dict_temp.key, dict_temp.value)
|
||||
dict_temp = av_dict_get(self.format_opts, "", dict_temp, AV_DICT_IGNORE_SUFFIX)
|
||||
av_dict_free(&self.format_opts)
|
||||
if bad_vals:
|
||||
msg2 = ("The following options were not recognized: %s.\n" % bad_vals).encode('utf8')
|
||||
av_log(NULL, AV_LOG_ERROR, '%s', msg2)
|
||||
if res < 0:
|
||||
self.clean_up()
|
||||
raise Exception('Error writing header: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
|
||||
def __dealloc__(self):
|
||||
self.close()
|
||||
|
||||
cpdef close(self):
|
||||
'''Closes the writer and writes any frames cached and not yet written.
|
||||
|
||||
Until called, or until the instance is deleted (and this is implicitly called)
|
||||
the file is not fully written.
|
||||
|
||||
.. warning::
|
||||
|
||||
After calling this method, calling any other class method on this instance may
|
||||
result in a crash or program corruption.
|
||||
'''
|
||||
cdef int r, res, wrote = 0
|
||||
cdef char msg[256]
|
||||
cdef AVPacket pkt
|
||||
if self.closed:
|
||||
return
|
||||
self.closed = 1
|
||||
|
||||
with nogil:
|
||||
if self.fmt_ctx == NULL or (not self.n_streams) or self.streams[0].codec_ctx == NULL:
|
||||
self.clean_up()
|
||||
with gil:
|
||||
return
|
||||
|
||||
for r in range(self.n_streams):
|
||||
if not self.streams[r].count:
|
||||
continue
|
||||
wrote = 1
|
||||
|
||||
av_init_packet(&pkt)
|
||||
pkt.data = NULL
|
||||
pkt.size = 0
|
||||
|
||||
# flush
|
||||
res = avcodec_send_frame(self.streams[r].codec_ctx, NULL)
|
||||
if res < 0:
|
||||
with gil:
|
||||
raise Exception('Error sending NULL frame: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
|
||||
while True:
|
||||
res = avcodec_receive_packet(self.streams[r].codec_ctx, &pkt)
|
||||
if res < 0:
|
||||
if res != AVERROR_EOF:
|
||||
with gil:
|
||||
raise Exception('Error getting encoded packet: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
break
|
||||
|
||||
if pkt.pts != AV_NOPTS_VALUE:
|
||||
pkt.pts = av_rescale_q(pkt.pts, self.streams[r].codec_ctx.time_base, self.streams[r].av_stream.time_base)
|
||||
if pkt.dts != AV_NOPTS_VALUE:
|
||||
pkt.dts = av_rescale_q(pkt.dts, self.streams[r].codec_ctx.time_base, self.streams[r].av_stream.time_base)
|
||||
pkt.stream_index = self.streams[r].av_stream.index
|
||||
self.total_size += pkt.size
|
||||
|
||||
res = av_interleaved_write_frame(self.fmt_ctx, &pkt)
|
||||
if res < 0:
|
||||
with gil:
|
||||
raise Exception('Error writing packet: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
if wrote:
|
||||
av_write_trailer(self.fmt_ctx)
|
||||
self.clean_up()
|
||||
|
||||
def write_frame(MediaWriter self, Image img, double pts, int stream=0):
|
||||
'''Writes a :class:`ffpyplayer.pic.Image` frame to the specified stream.
|
||||
|
||||
If the input data is different than the frame written to disk in either
|
||||
size or pixel format as specified when creating the stream, the frame
|
||||
is converted before writing. But the input image must match the size and
|
||||
format as that specified when creating this stream.
|
||||
|
||||
:Parameters:
|
||||
|
||||
`img`: :class:`ffpyplayer.pic.Image`
|
||||
The :class:`ffpyplayer.pic.Image` instance containing the frame
|
||||
to be written to disk.
|
||||
`pts`: float
|
||||
The timestamp of this frame in video time. E.g. 0.5
|
||||
means the frame should be displayed by a player at 0.5 seconds after
|
||||
the video started playing. In a sense, the frame rate defines which
|
||||
timestamps are valid timestamps. However, this is not always the
|
||||
case, so if timestamps are invalid for a particular format, they are
|
||||
forced to valid values, if possible.
|
||||
`stream`: int
|
||||
The stream number to which to write this frame. Defaults to 0.
|
||||
|
||||
:returns:
|
||||
|
||||
(int): The approximate number of bytes written to disk so far for this file.
|
||||
|
||||
.. note::
|
||||
|
||||
This is not the same as the number of bytes passed to this function
|
||||
so far, because the encoders cache data before writing to disk.
|
||||
So although some frames may have been passed, the return value
|
||||
may not represent this.
|
||||
|
||||
An extreme example is where the same frame is passed many times
|
||||
to h264; the encoder will only write this frame once when the Writer
|
||||
object is closed and encoders are flushed, so this function
|
||||
will only return 0.
|
||||
|
||||
See :ref:`examples` for its usage.
|
||||
'''
|
||||
cdef int res = 0, got_pkt
|
||||
cdef int frame_cloned = 0
|
||||
cdef AVFrame *frame_in = img.frame
|
||||
cdef AVFrame *frame_out
|
||||
cdef MediaStream *s
|
||||
cdef double ipts, dpts
|
||||
cdef int64_t rounded_pts
|
||||
cdef AVPacket pkt
|
||||
cdef char msg[256]
|
||||
if stream >= self.n_streams:
|
||||
raise Exception('Invalid stream number %d' % stream)
|
||||
s = self.streams + stream
|
||||
if (frame_in.width != s.width_in or frame_in.height != s.height_in or
|
||||
frame_in.format != <AVPixelFormat>s.pix_fmt_in):
|
||||
raise Exception("Input image doesn't match stream specified parameters.")
|
||||
|
||||
with nogil:
|
||||
if s.av_frame != NULL:
|
||||
frame_out = s.av_frame
|
||||
sws_scale(s.sws_ctx, <const uint8_t *const *>frame_in.data, frame_in.linesize,
|
||||
0, frame_in.height, frame_out.data, frame_out.linesize)
|
||||
else:
|
||||
frame_out = av_frame_clone(frame_in)
|
||||
frame_cloned = 1
|
||||
if frame_out == NULL:
|
||||
with gil:
|
||||
raise MemoryError
|
||||
|
||||
rounded_pts = <int64_t>floor(pts / av_q2d(s.codec_ctx.time_base) + 0.5)
|
||||
frame_out.pict_type = AV_PICTURE_TYPE_NONE
|
||||
frame_out.pts = rounded_pts
|
||||
|
||||
av_init_packet(&pkt)
|
||||
pkt.data = NULL
|
||||
pkt.size = 0
|
||||
|
||||
res = avcodec_send_frame(s.codec_ctx, frame_out)
|
||||
if res < 0:
|
||||
if frame_cloned:
|
||||
av_frame_free(&frame_out)
|
||||
with gil:
|
||||
raise Exception('Error sending frame: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
|
||||
while True:
|
||||
res = avcodec_receive_packet(s.codec_ctx, &pkt)
|
||||
if res < 0:
|
||||
if frame_cloned:
|
||||
av_frame_free(&frame_out)
|
||||
if res != AVERROR_EOF and res != AV_EAGAIN:
|
||||
with gil:
|
||||
raise Exception('Error getting encoded packet: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
break
|
||||
|
||||
if pkt.pts != AV_NOPTS_VALUE:
|
||||
pkt.pts = av_rescale_q(pkt.pts, s.codec_ctx.time_base, s.av_stream.time_base)
|
||||
if pkt.dts != AV_NOPTS_VALUE:
|
||||
pkt.dts = av_rescale_q(pkt.dts, s.codec_ctx.time_base, s.av_stream.time_base)
|
||||
pkt.stream_index = s.av_stream.index
|
||||
self.total_size += pkt.size
|
||||
|
||||
res = av_interleaved_write_frame(self.fmt_ctx, &pkt)
|
||||
if res < 0:
|
||||
if frame_cloned:
|
||||
av_frame_free(&frame_out)
|
||||
with gil:
|
||||
raise Exception('Error writing packet: ' + tcode(emsg(res, msg, sizeof(msg))))
|
||||
|
||||
s.pts += 1
|
||||
s.count += 1
|
||||
if frame_cloned:
|
||||
av_frame_free(&frame_out)
|
||||
return self.total_size
|
||||
|
||||
def get_configuration(self):
|
||||
'''Returns the configuration parameters used to initialize all the streams for this
|
||||
instance.
|
||||
|
||||
This is not the same as the dicts passed when creating the file because
|
||||
this uses the actual parameters used.
|
||||
|
||||
:returns:
|
||||
|
||||
list: List of dicts for each stream.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ffpyplayer.writer import MediaWriter
|
||||
|
||||
w, h = 640, 480
|
||||
out_opts = {'pix_fmt_in':'rgb24', 'width_in':w, 'height_in':h, 'codec':'rawvideo',
|
||||
'frame_rate':(5, 1)}
|
||||
writer = MediaWriter('output.avi', [out_opts] * 2, width_out=w/2, height_out=h/2)
|
||||
|
||||
print writer.get_configuration()
|
||||
[{'height_in': 480, 'codec': 'rawvideo', 'width_in': 640, 'frame_rate': (5, 1),
|
||||
'pix_fmt_in': 'rgb24', 'width_out': 320, 'height_out': 240, 'pix_fmt_out': 'rgb24'},
|
||||
{'height_in': 480, 'codec': 'rawvideo', 'width_in': 640, 'frame_rate': (5, 1),
|
||||
'pix_fmt_in': 'rgb24', 'width_out': 320, 'height_out': 240, 'pix_fmt_out': 'rgb24'}]
|
||||
'''
|
||||
return convert_to_str(deepcopy(self.config))
|
||||
|
||||
cdef void clean_up(MediaWriter self) nogil:
|
||||
cdef int r
|
||||
|
||||
for r in range(self.n_streams):
|
||||
# If the in and out formats are different we must delete the out frame data buffer
|
||||
if self.streams[r].av_frame != NULL:
|
||||
av_frame_free(&self.streams[r].av_frame)
|
||||
self.streams[r].av_frame = NULL
|
||||
if self.streams[r].sws_ctx != NULL:
|
||||
sws_freeContext(self.streams[r].sws_ctx)
|
||||
self.streams[r].sws_ctx= NULL
|
||||
if self.streams[r].codec_opts:
|
||||
av_dict_free(&self.streams[r].codec_opts)
|
||||
if self.streams[r].codec_ctx:
|
||||
avcodec_free_context(&self.streams[r].codec_ctx)
|
||||
free(self.streams)
|
||||
self.streams = NULL
|
||||
self.n_streams = 0
|
||||
|
||||
if self.fmt_ctx != NULL:
|
||||
if self.fmt_ctx.pb != NULL and not (self.fmt_ctx.oformat.flags & AVFMT_NOFILE):
|
||||
avio_close(self.fmt_ctx.pb)
|
||||
avformat_free_context(self.fmt_ctx)
|
||||
self.fmt_ctx = NULL
|
||||
av_dict_free(&self.format_opts)
|
||||
self.total_size = 0
|
||||
|
||||
Reference in New Issue
Block a user