Skip to content

Commit 850f115

Browse files
authored
Allow the profile of a codec to be set as well as queried
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.
1 parent 2ec8513 commit 850f115

File tree

6 files changed

+96
-9
lines changed

6 files changed

+96
-9
lines changed

av/codec/context.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ class CodecContext:
5959
type: Literal["video", "audio", "data", "subtitle", "attachment"]
6060
options: dict[str, str]
6161
profile: str | None
62+
@property
63+
def profiles(self) -> list[str]: ...
6264
extradata: bytes | None
6365
time_base: Fraction
6466
codec_tag: str

av/codec/context.pyx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,10 +497,55 @@ cdef class CodecContext:
497497
def type(self):
498498
return self.codec.type
499499

500+
@property
501+
def profiles(self):
502+
"""
503+
List the available profiles for this stream.
504+
505+
:type: list[str]
506+
"""
507+
ret = []
508+
if not self.ptr.codec or not self.codec.desc or not self.codec.desc.profiles:
509+
return ret
510+
511+
# Profiles are always listed in the codec descriptor, but not necessarily in
512+
# the codec itself. So use the descriptor here.
513+
desc = self.codec.desc
514+
cdef int i = 0
515+
while desc.profiles[i].profile != lib.FF_PROFILE_UNKNOWN:
516+
ret.append(desc.profiles[i].name)
517+
i += 1
518+
519+
return ret
520+
500521
@property
501522
def profile(self):
502-
if self.ptr.codec and lib.av_get_profile_name(self.ptr.codec, self.ptr.profile):
503-
return lib.av_get_profile_name(self.ptr.codec, self.ptr.profile)
523+
if not self.ptr.codec or not self.codec.desc or not self.codec.desc.profiles:
524+
return
525+
526+
# Profiles are always listed in the codec descriptor, but not necessarily in
527+
# the codec itself. So use the descriptor here.
528+
desc = self.codec.desc
529+
cdef int i = 0
530+
while desc.profiles[i].profile != lib.FF_PROFILE_UNKNOWN:
531+
if desc.profiles[i].profile == self.ptr.profile:
532+
return desc.profiles[i].name
533+
i += 1
534+
535+
@profile.setter
536+
def profile(self, value):
537+
if not self.codec or not self.codec.desc or not self.codec.desc.profiles:
538+
return
539+
540+
# Profiles are always listed in the codec descriptor, but not necessarily in
541+
# the codec itself. So use the descriptor here.
542+
desc = self.codec.desc
543+
cdef int i = 0
544+
while desc.profiles[i].profile != lib.FF_PROFILE_UNKNOWN:
545+
if desc.profiles[i].name == value:
546+
self.ptr.profile = desc.profiles[i].profile
547+
return
548+
i += 1
504549

505550
@property
506551
def time_base(self):

av/stream.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class Stream:
1919
codec_context: CodecContext
2020
metadata: dict[str, str]
2121
id: int
22+
profiles: list[str]
2223
profile: str
2324
index: int
2425
time_base: Fraction | None

av/stream.pyx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,18 @@ cdef class Stream:
145145
else:
146146
self.ptr.id = value
147147

148+
@property
149+
def profiles(self):
150+
"""
151+
List the available profiles for this stream.
152+
153+
:type: list[str]
154+
"""
155+
if self.codec_context:
156+
return self.codec_context.profiles
157+
else:
158+
return []
159+
148160
@property
149161
def profile(self):
150162
"""

include/libavcodec/avcodec.pxd

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,9 @@ cdef extern from "libavcodec/avcodec.h" nogil:
146146
FF_COMPLIANCE_UNOFFICIAL
147147
FF_COMPLIANCE_EXPERIMENTAL
148148

149+
cdef enum:
150+
FF_PROFILE_UNKNOWN = -99
151+
149152
cdef enum AVCodecID:
150153
AV_CODEC_ID_NONE
151154
AV_CODEC_ID_MPEG2VIDEO
@@ -178,12 +181,17 @@ cdef extern from "libavcodec/avcodec.h" nogil:
178181
cdef int av_codec_is_encoder(AVCodec*)
179182
cdef int av_codec_is_decoder(AVCodec*)
180183

184+
cdef struct AVProfile:
185+
int profile
186+
char *name
187+
181188
cdef struct AVCodecDescriptor:
182189
AVCodecID id
183190
char *name
184191
char *long_name
185192
int props
186193
char **mime_types
194+
AVProfile *profiles
187195

188196
AVCodecDescriptor* avcodec_descriptor_get(AVCodecID)
189197

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

267275
cdef AVClass* avcodec_get_class()
268276

269-
cdef struct AVCodecDescriptor:
270-
AVCodecID id
271-
AVMediaType type
272-
char *name
273-
char *long_name
274-
int props
275-
276277
cdef AVCodec* avcodec_find_decoder(AVCodecID id)
277278
cdef AVCodec* avcodec_find_encoder(AVCodecID id)
278279

tests/test_encode.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,3 +449,29 @@ def test_qmin_qmax(self) -> None:
449449

450450
factor = 1.3 # insist at least 30% larger each time
451451
assert all(small * factor < large for small, large in zip(sizes, sizes[1:]))
452+
453+
454+
class TestProfiles(TestCase):
455+
def test_profiles(self) -> None:
456+
"""
457+
Test that we can set different encoder profiles.
458+
"""
459+
# Let's try a video and an audio codec.
460+
file = io.BytesIO()
461+
codecs = (
462+
("h264", 30),
463+
("aac", 48000),
464+
)
465+
466+
for codec_name, rate in codecs:
467+
print("Testing:", codec_name)
468+
container = av.open(file, mode="w", format="mp4")
469+
stream = container.add_stream(codec_name, rate=rate)
470+
assert len(stream.profiles) >= 1 # check that we're testing something!
471+
472+
# It should be enough to test setting and retrieving the code. That means
473+
# libav has recognised the profile and set it correctly.
474+
for profile in stream.profiles:
475+
stream.profile = profile
476+
print("Set", profile, "got", stream.profile)
477+
assert stream.profile == profile

0 commit comments

Comments
 (0)