Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions av/codec/context.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class CodecContext:
type: Literal["video", "audio", "data", "subtitle", "attachment"]
options: dict[str, str]
profile: str | None
@property
def profiles(self) -> list[str]: ...
extradata: bytes | None
time_base: Fraction
codec_tag: str
Expand Down
49 changes: 47 additions & 2 deletions av/codec/context.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -497,10 +497,55 @@ cdef class CodecContext:
def type(self):
return self.codec.type

@property
def profiles(self):
"""
List the available profiles for this stream.

:type: list[str]
"""
ret = []
if not self.ptr.codec or not self.codec.desc or not self.codec.desc.profiles:
return ret

# Profiles are always listed in the codec descriptor, but not necessarily in
# the codec itself. So use the descriptor here.
desc = self.codec.desc
cdef int i = 0
while desc.profiles[i].profile != lib.FF_PROFILE_UNKNOWN:
ret.append(desc.profiles[i].name)
i += 1

return ret

@property
def profile(self):
if self.ptr.codec and lib.av_get_profile_name(self.ptr.codec, self.ptr.profile):
return lib.av_get_profile_name(self.ptr.codec, self.ptr.profile)
if not self.ptr.codec or not self.codec.desc or not self.codec.desc.profiles:
return

# Profiles are always listed in the codec descriptor, but not necessarily in
# the codec itself. So use the descriptor here.
desc = self.codec.desc
cdef int i = 0
while desc.profiles[i].profile != lib.FF_PROFILE_UNKNOWN:
if desc.profiles[i].profile == self.ptr.profile:
return desc.profiles[i].name
i += 1

@profile.setter
def profile(self, value):
if not self.codec or not self.codec.desc or not self.codec.desc.profiles:
return

# Profiles are always listed in the codec descriptor, but not necessarily in
# the codec itself. So use the descriptor here.
desc = self.codec.desc
cdef int i = 0
while desc.profiles[i].profile != lib.FF_PROFILE_UNKNOWN:
if desc.profiles[i].name == value:
self.ptr.profile = desc.profiles[i].profile
return
i += 1

@property
def time_base(self):
Expand Down
1 change: 1 addition & 0 deletions av/stream.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Stream:
codec_context: CodecContext
metadata: dict[str, str]
id: int
profiles: list[str]
profile: str
index: int
time_base: Fraction | None
Expand Down
12 changes: 12 additions & 0 deletions av/stream.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,18 @@ cdef class Stream:
else:
self.ptr.id = value

@property
def profiles(self):
"""
List the available profiles for this stream.

:type: list[str]
"""
if self.codec_context:
return self.codec_context.profiles
else:
return []

@property
def profile(self):
"""
Expand Down
15 changes: 8 additions & 7 deletions include/libavcodec/avcodec.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ cdef extern from "libavcodec/avcodec.h" nogil:
FF_COMPLIANCE_UNOFFICIAL
FF_COMPLIANCE_EXPERIMENTAL

cdef enum:
FF_PROFILE_UNKNOWN = -99

cdef enum AVCodecID:
AV_CODEC_ID_NONE
AV_CODEC_ID_MPEG2VIDEO
Expand Down Expand Up @@ -178,12 +181,17 @@ cdef extern from "libavcodec/avcodec.h" nogil:
cdef int av_codec_is_encoder(AVCodec*)
cdef int av_codec_is_decoder(AVCodec*)

cdef struct AVProfile:
int profile
char *name

cdef struct AVCodecDescriptor:
AVCodecID id
char *name
char *long_name
int props
char **mime_types
AVProfile *profiles

AVCodecDescriptor* avcodec_descriptor_get(AVCodecID)

Expand Down Expand Up @@ -266,13 +274,6 @@ cdef extern from "libavcodec/avcodec.h" nogil:

cdef AVClass* avcodec_get_class()

cdef struct AVCodecDescriptor:
AVCodecID id
AVMediaType type
char *name
char *long_name
int props

cdef AVCodec* avcodec_find_decoder(AVCodecID id)
cdef AVCodec* avcodec_find_encoder(AVCodecID id)

Expand Down
26 changes: 26 additions & 0 deletions tests/test_encode.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,3 +449,29 @@ def test_qmin_qmax(self) -> None:

factor = 1.3 # insist at least 30% larger each time
assert all(small * factor < large for small, large in zip(sizes, sizes[1:]))


class TestProfiles(TestCase):
def test_profiles(self) -> None:
"""
Test that we can set different encoder profiles.
"""
# Let's try a video and an audio codec.
file = io.BytesIO()
codecs = (
("h264", 30),
("aac", 48000),
)

for codec_name, rate in codecs:
print("Testing:", codec_name)
container = av.open(file, mode="w", format="mp4")
stream = container.add_stream(codec_name, rate=rate)
assert len(stream.profiles) >= 1 # check that we're testing something!

# It should be enough to test setting and retrieving the code. That means
# libav has recognised the profile and set it correctly.
for profile in stream.profiles:
stream.profile = profile
print("Set", profile, "got", stream.profile)
assert stream.profile == profile