Skip to content

Commit

Permalink
Allow the profile of a codec to be set as well as queried
Browse files Browse the repository at this point in the history
The `profile` property of a stream can now be set. To help applications 
find appropriate profiles, a `profiles` property has been added which 
lists the available profile names.
  • Loading branch information
davidplowman authored Nov 12, 2024
1 parent 2ec8513 commit 850f115
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 9 deletions.
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

0 comments on commit 850f115

Please sign in to comment.