This commit is contained in:
2025-10-03 13:25:14 +02:00
parent 3b0774686d
commit 3c56a880da
9 changed files with 277 additions and 1 deletions

2
.gitignore vendored
View File

@ -173,4 +173,4 @@ cython_debug/
# PyPI configuration file
.pypirc
/test.mp4

0
mp4/__init__.py Normal file
View File

20
mp4/codec.py Normal file
View File

@ -0,0 +1,20 @@
# RozK
class NullCodec:
@property
def name(self):
return b"<None>"
class Codec:
__slots__ = '_ref'
def __init__(self, ref):
self._ref = ref
@property
def _as_parameter_(self):
return self._ref
@property
def name(self):
return self._ref.contents.name

54
mp4/context.py Normal file
View File

@ -0,0 +1,54 @@
# RozK
from . import libav
from .codec import Codec
from .stream import NullStream, Stream
from .packet import Packet
class Context:
__slots__ = '_ref'
def __init__(self):
self._ref = libav.alloc_context()
if not self._ref:
raise MemoryError
def __del__(self):
if self._ref:
libav.free_context(self._ref)
@property
def _as_parameter_(self):
return self._ref
def open_input(self, url):
if not self._ref:
return
errcode = libav.open_input(self._ref, url)
if errcode < 0:
raise Exception(f"Failed to open: {url}")
errcode = libav.find_stream_info(self._ref)
if errcode < 0:
libav.close_input(self._ref)
raise Exception("Failed to find stream info")
def close_input(self):
if self._ref:
libav.close_input(self._ref)
def find_stream(self, type):
if not self._ref:
return NullStream()
index, codec_ref = libav.find_best_stream(self._ref, type)
if index < 0 or not codec_ref:
return NullStream()
return Stream(index, Codec(codec_ref))
def read_packet(self):
if not self._ref:
return None
packet = Packet()
errcode = libav.read_frame(self._ref, packet)
if errcode < 0:
return None
return packet

19
mp4/demuxer.py Normal file
View File

@ -0,0 +1,19 @@
# RozK
from . import libav
from .context import Context
class Demuxer:
__slots__ = 'context', 'video_stream', 'audio_stream'
def __init__(self, path):
self.context = Context()
self.context.open_input("file:" + path)
self.video_stream = self.context.find_stream(libav.AVMEDIA_TYPE_VIDEO)
self.audio_stream = self.context.find_stream(libav.AVMEDIA_TYPE_AUDIO)
def read_packet(self):
return self.context.read_packet()
def close(self):
self.context.close_input()

112
mp4/libav.py Normal file
View File

@ -0,0 +1,112 @@
# RozK
# https://www.ffmpeg.org/doxygen/trunk/group__libavf.html
import ctypes
_avformat = ctypes.cdll.LoadLibrary('libavformat.so')
_avcodec = ctypes.cdll.LoadLibrary('libavcodec.so')
class AVFormatContext(ctypes.Structure):
pass
AVFormatContext_p = ctypes.POINTER(AVFormatContext)
AVFormatContext_pp = ctypes.POINTER(AVFormatContext_p)
_avformat.avformat_alloc_context.restype = AVFormatContext_p
_avformat.avformat_alloc_context.argtypes = None
def alloc_context():
return _avformat.avformat_alloc_context()
_avformat.avformat_free_context.restype = None
_avformat.avformat_free_context.argtypes = [AVFormatContext_p] # context
def free_context(context):
_avformat.avformat_free_context(context)
_avformat.avformat_open_input.restype = ctypes.c_int
_avformat.avformat_open_input.argtypes = [
AVFormatContext_pp, # context
ctypes.c_char_p, # url
ctypes.c_void_p, # format
ctypes.POINTER(ctypes.c_void_p)] # options
def open_input(context, url):
return _avformat.avformat_open_input(ctypes.byref(context), url.encode('ascii', 'ignore'), None, None)
_avformat.avformat_close_input.restype = None
_avformat.avformat_close_input.argtypes = [AVFormatContext_pp] # context
def close_input(context):
_avformat.avformat_close_input(ctypes.byref(context))
_avformat.avformat_find_stream_info.restype = ctypes.c_int
_avformat.avformat_find_stream_info.argtypes = [
AVFormatContext_p, # context
ctypes.POINTER(ctypes.c_void_p)] # options
def find_stream_info(context):
return _avformat.avformat_find_stream_info(context, None)
AVMEDIA_TYPE_UNKNOWN = -1
AVMEDIA_TYPE_VIDEO = 0
AVMEDIA_TYPE_AUDIO = 1
AVMEDIA_TYPE_DATA = 2
AVMEDIA_TYPE_SUBTITLE = 3
AVMEDIA_TYPE_ATTACHMENT = 4
class AVCodec(ctypes.Structure):
_fields_ = [
("name", ctypes.c_char_p),
("long_name", ctypes.c_char_p)]
# ...
AVCodec_p = ctypes.POINTER(AVCodec)
AVCodec_pp = ctypes.POINTER(AVCodec_p)
_avformat.av_find_best_stream.restype = ctypes.c_int
_avformat.av_find_best_stream.argtypes = [
AVFormatContext_p, # context
ctypes.c_int, # type
ctypes.c_int, # wanted stream
ctypes.c_int, # related stream
AVCodec_pp, # decoder
ctypes.c_int] # flags
def find_best_stream(context, type):
codec = AVCodec_p()
index = _avformat.av_find_best_stream(context, type, -1, -1, ctypes.byref(codec), 0)
return index, codec
class AVPacket(ctypes.Structure):
_fields_ = [
("buf", ctypes.c_void_p),
("pts", ctypes.c_int64),
("dts", ctypes.c_int64),
("data", ctypes.c_void_p),
("size", ctypes.c_int),
("stream_index", ctypes.c_int)]
# ...
AVPacket_p = ctypes.POINTER(AVPacket)
AVPacket_pp = ctypes.POINTER(AVPacket_p)
_avformat.av_packet_alloc.restype = AVPacket_p
_avformat.av_packet_alloc.argtypes = None
def packet_alloc():
return _avformat.av_packet_alloc()
_avformat.av_packet_free.restype = None
_avformat.av_packet_free.argtypes = [AVPacket_pp] # packet
def packet_free(packet):
_avformat.av_packet_free(ctypes.byref(packet))
_avformat.av_read_frame.restype = ctypes.c_int
_avformat.av_read_frame.argtypes = [
AVFormatContext_p, # context
AVPacket_p] # packet
def read_frame(context, packet):
return _avformat.av_read_frame(context, packet)

25
mp4/packet.py Normal file
View File

@ -0,0 +1,25 @@
# RozK
from . import libav
class Packet:
__slots__ = '_ref'
def __init__(self):
self._ref = libav.packet_alloc()
if not self._ref:
raise MemoryError
def __del__(self):
if self._ref:
libav.packet_free(self._ref)
@property
def _as_parameter_(self):
return self._ref
@property
def stream_index(self):
if self._ref:
return self._ref.contents.stream_index
return -1

25
mp4/stream.py Normal file
View File

@ -0,0 +1,25 @@
# RozK
from .codec import NullCodec
class NullStream:
@property
def index(self):
return -1
@property
def codec(self):
return NullCodec()
def contains(self, packet):
return False
class Stream:
__slots__ = 'index', 'codec'
def __init__(self, index, codec):
self.index = index
self.codec = codec
def contains(self, packet):
return (self.index == packet.stream_index)

21
pve.py Normal file
View File

@ -0,0 +1,21 @@
# RozK
from mp4.demuxer import Demuxer
demuxer = Demuxer('test.mp4')
print(demuxer.video_stream.codec.name)
print(demuxer.audio_stream.codec.name)
while True:
packet = demuxer.read_packet()
if packet is None:
break
if demuxer.video_stream.contains(packet):
continue
elif demuxer.audio_stream.contains(packet):
continue
else:
print("unkown packet")
demuxer.close()