0
# Filter System
1
2
Audio and video filtering capabilities using FFmpeg's filter system. PyAV provides access to FFmpeg's comprehensive filter library for audio and video processing pipelines.
3
4
## Capabilities
5
6
### Filter Graph
7
8
Filter graphs organize and execute audio/video processing pipelines.
9
10
```python { .api }
11
class Graph:
12
"""Filter graph for audio/video processing."""
13
14
# Properties
15
configured: bool # True if graph is configured
16
17
def __init__(self):
18
"""Create new filter graph."""
19
20
def add(self, filter_name, *args, **kwargs) -> FilterContext:
21
"""
22
Add filter to graph.
23
24
Parameters:
25
- filter_name: str - Filter name
26
- *args: Positional filter arguments
27
- **kwargs: Named filter arguments
28
29
Returns:
30
FilterContext for the added filter
31
"""
32
33
def add_buffer(self, template=None, width=None, height=None, format=None,
34
name=None, **kwargs) -> FilterContext:
35
"""
36
Add video buffer source.
37
38
Parameters:
39
- template: VideoStream - Template stream for properties
40
- width: int - Frame width
41
- height: int - Frame height
42
- format: str - Pixel format
43
- name: str - Buffer name
44
45
Returns:
46
Video buffer FilterContext
47
"""
48
49
def add_abuffer(self, template=None, format=None, layout=None, rate=None,
50
name=None, **kwargs) -> FilterContext:
51
"""
52
Add audio buffer source.
53
54
Parameters:
55
- template: AudioStream - Template stream for properties
56
- format: str - Audio format
57
- layout: str - Channel layout
58
- rate: int - Sample rate
59
- name: str - Buffer name
60
61
Returns:
62
Audio buffer FilterContext
63
"""
64
65
def configure(self) -> None:
66
"""Configure the filter graph for processing."""
67
68
def link_nodes(self, output_ctx, input_ctx, output_idx=0, input_idx=0) -> None:
69
"""
70
Link filter contexts.
71
72
Parameters:
73
- output_ctx: FilterContext - Source context
74
- input_ctx: FilterContext - Destination context
75
- output_idx: int - Output pad index
76
- input_idx: int - Input pad index
77
"""
78
79
def set_audio_frame_size(self, nb_samples) -> None:
80
"""Set audio frame size for the graph."""
81
82
def push(self, frame) -> None:
83
"""Push frame to graph input."""
84
85
def pull(self) -> Frame | None:
86
"""Pull frame from graph output."""
87
88
def vpush(self, frame) -> None:
89
"""Push video frame to graph."""
90
91
def vpull(self) -> VideoFrame | None:
92
"""Pull video frame from graph."""
93
```
94
95
### Filter Information
96
97
Filter discovery and introspection.
98
99
```python { .api }
100
# Available filters
101
filters_available: set[str] # Set of available filter names
102
103
class Filter:
104
"""Filter information."""
105
106
# Properties
107
name: str # Filter name
108
description: str # Filter description
109
descriptor: Descriptor # Filter descriptor with options
110
options: tuple[Option, ...] # Available options
111
flags: int # Filter flags
112
113
def __init__(self, name):
114
"""
115
Get filter by name.
116
117
Parameters:
118
- name: str - Filter name
119
"""
120
```
121
122
### Filter Context
123
124
Individual filter instances within a graph.
125
126
```python { .api }
127
class FilterContext:
128
"""Filter instance in a graph."""
129
130
# Properties
131
name: str | None # Filter context name
132
graph: Graph # Parent graph
133
134
def init(self, args=None, **kwargs) -> None:
135
"""
136
Initialize filter context.
137
138
Parameters:
139
- args: str - Filter argument string
140
- **kwargs: Named arguments
141
"""
142
143
def link_to(self, target, output_idx=0, input_idx=0) -> None:
144
"""
145
Link this context to another.
146
147
Parameters:
148
- target: FilterContext - Target context
149
- output_idx: int - Output pad index
150
- input_idx: int - Input pad index
151
"""
152
153
def push(self, frame) -> None:
154
"""
155
Push frame to this filter.
156
157
Parameters:
158
- frame: Frame - Input frame
159
"""
160
161
def pull(self) -> Frame | None:
162
"""
163
Pull processed frame.
164
165
Returns:
166
Processed frame or None
167
"""
168
```
169
170
### Audio Normalization
171
172
Specialized audio loudness normalization filter.
173
174
```python { .api }
175
def stats(loudnorm_args: str, stream: AudioStream) -> bytes:
176
"""
177
Generate loudness normalization statistics.
178
179
Parameters:
180
- loudnorm_args: str - Loudnorm filter arguments
181
- stream: AudioStream - Input audio stream
182
183
Returns:
184
Statistics data for second-pass normalization
185
"""
186
```
187
188
## Usage Examples
189
190
### Basic Video Filtering
191
192
```python
193
import av
194
195
# Open input video
196
input_container = av.open('input.mp4')
197
input_stream = input_container.streams.video[0]
198
199
# Create output
200
output_container = av.open('filtered.mp4', 'w')
201
output_stream = output_container.add_stream('h264', rate=input_stream.framerate)
202
output_stream.width = input_stream.width
203
output_stream.height = input_stream.height
204
output_stream.pix_fmt = 'yuv420p'
205
206
# Create filter graph
207
graph = av.filter.Graph()
208
209
# Add input buffer
210
buffer_ctx = graph.add_buffer(template=input_stream)
211
212
# Add scale filter (resize)
213
scale_ctx = graph.add('scale', width=1280, height=720)
214
215
# Add color adjustment filter
216
colorbalance_ctx = graph.add('colorbalance',
217
rs=0.1, # Red shadows
218
gs=-0.1, # Green shadows
219
bs=0.05) # Blue shadows
220
221
# Add output buffersink
222
buffersink_ctx = graph.add('buffersink')
223
224
# Link filters: buffer -> scale -> colorbalance -> buffersink
225
buffer_ctx.link_to(scale_ctx)
226
scale_ctx.link_to(colorbalance_ctx)
227
colorbalance_ctx.link_to(buffersink_ctx)
228
229
# Configure graph
230
graph.configure()
231
232
# Process frames
233
for frame in input_container.decode(input_stream):
234
# Push frame to filter graph
235
buffer_ctx.push(frame)
236
237
# Pull filtered frames
238
while True:
239
try:
240
filtered_frame = buffersink_ctx.pull()
241
if filtered_frame is None:
242
break
243
244
# Set timing for output
245
filtered_frame.pts = frame.pts
246
filtered_frame.time_base = input_stream.time_base
247
248
# Encode and write
249
for packet in output_stream.encode(filtered_frame):
250
output_container.mux(packet)
251
252
except av.BlockingIOError:
253
break
254
255
# Flush
256
for packet in output_stream.encode():
257
output_container.mux(packet)
258
259
input_container.close()
260
output_container.close()
261
```
262
263
### Audio Filtering
264
265
```python
266
import av
267
268
# Open input audio
269
input_container = av.open('input.wav')
270
input_stream = input_container.streams.audio[0]
271
272
# Create output
273
output_container = av.open('filtered.wav', 'w')
274
output_stream = output_container.add_stream('pcm_s16le', rate=input_stream.sample_rate)
275
output_stream.channels = input_stream.channels
276
output_stream.layout = input_stream.layout
277
278
# Create audio filter graph
279
graph = av.filter.Graph()
280
281
# Add audio input buffer
282
abuffer_ctx = graph.add_abuffer(template=input_stream)
283
284
# Add volume filter
285
volume_ctx = graph.add('volume', volume=1.5) # Increase volume by 50%
286
287
# Add high-pass filter
288
highpass_ctx = graph.add('highpass', frequency=80, poles=2)
289
290
# Add low-pass filter for noise reduction
291
lowpass_ctx = graph.add('lowpass', frequency=15000, poles=2)
292
293
# Add audio output
294
abuffersink_ctx = graph.add('abuffersink')
295
296
# Link audio filters
297
abuffer_ctx.link_to(volume_ctx)
298
volume_ctx.link_to(highpass_ctx)
299
highpass_ctx.link_to(lowpass_ctx)
300
lowpass_ctx.link_to(abuffersink_ctx)
301
302
# Configure graph
303
graph.configure()
304
305
# Process audio frames
306
for frame in input_container.decode(input_stream):
307
abuffer_ctx.push(frame)
308
309
while True:
310
try:
311
filtered_frame = abuffersink_ctx.pull()
312
if filtered_frame is None:
313
break
314
315
# Maintain timing
316
filtered_frame.pts = frame.pts
317
filtered_frame.time_base = input_stream.time_base
318
319
# Encode filtered audio
320
for packet in output_stream.encode(filtered_frame):
321
output_container.mux(packet)
322
323
except av.BlockingIOError:
324
break
325
326
# Flush encoder
327
for packet in output_stream.encode():
328
output_container.mux(packet)
329
330
input_container.close()
331
output_container.close()
332
```
333
334
### Complex Video Filter Chain
335
336
```python
337
import av
338
339
def create_complex_video_filter(input_stream):
340
"""Create complex video processing filter chain."""
341
342
graph = av.filter.Graph()
343
344
# Input buffer
345
buffer_ctx = graph.add_buffer(template=input_stream)
346
347
# 1. Deinterlace if needed
348
deinterlace_ctx = graph.add('yadif', mode=0, parity=-1)
349
350
# 2. Denoise
351
denoise_ctx = graph.add('hqdn3d', luma_temporal=4.0, chroma_temporal=3.0)
352
353
# 3. Color correction
354
curves_ctx = graph.add('curves',
355
red='0/0 0.5/0.58 1/1', # Slight red lift
356
green='0/0 0.5/0.5 1/1', # No green change
357
blue='0/0 0.5/0.42 1/1') # Slight blue reduction
358
359
# 4. Sharpen
360
sharpen_ctx = graph.add('unsharp',
361
luma_msize_x=5, luma_msize_y=5,
362
luma_amount=1.2,
363
chroma_msize_x=3, chroma_msize_y=3,
364
chroma_amount=0.8)
365
366
# 5. Scale to target resolution
367
scale_ctx = graph.add('scale', width=1920, height=1080)
368
369
# 6. Add subtle vignette
370
vignette_ctx = graph.add('vignette', angle='PI/4', x0='w/2', y0='h/2')
371
372
# Output
373
buffersink_ctx = graph.add('buffersink')
374
375
# Link all filters
376
buffer_ctx.link_to(deinterlace_ctx)
377
deinterlace_ctx.link_to(denoise_ctx)
378
denoise_ctx.link_to(curves_ctx)
379
curves_ctx.link_to(sharpen_ctx)
380
sharpen_ctx.link_to(scale_ctx)
381
scale_ctx.link_to(vignette_ctx)
382
vignette_ctx.link_to(buffersink_ctx)
383
384
graph.configure()
385
386
return graph, buffer_ctx, buffersink_ctx
387
388
# Use complex filter
389
input_container = av.open('input.mov')
390
input_stream = input_container.streams.video[0]
391
392
output_container = av.open('processed.mp4', 'w')
393
output_stream = output_container.add_stream('h264', rate=input_stream.framerate)
394
output_stream.width = 1920
395
output_stream.height = 1080
396
output_stream.pix_fmt = 'yuv420p'
397
398
# Create filter chain
399
graph, buffer_input, buffer_output = create_complex_video_filter(input_stream)
400
401
print("Processing with complex filter chain:")
402
print("- Deinterlacing")
403
print("- Noise reduction")
404
print("- Color correction")
405
print("- Sharpening")
406
print("- Scaling to 1080p")
407
print("- Vignette effect")
408
409
# Process video
410
frame_count = 0
411
for frame in input_container.decode(input_stream):
412
buffer_input.push(frame)
413
414
while True:
415
try:
416
processed_frame = buffer_output.pull()
417
if processed_frame is None:
418
break
419
420
processed_frame.pts = frame_count
421
processed_frame.time_base = output_stream.time_base
422
423
for packet in output_stream.encode(processed_frame):
424
output_container.mux(packet)
425
426
frame_count += 1
427
428
except av.BlockingIOError:
429
break
430
431
# Flush
432
for packet in output_stream.encode():
433
output_container.mux(packet)
434
435
print(f"Processed {frame_count} frames")
436
input_container.close()
437
output_container.close()
438
```
439
440
### Audio Loudness Normalization
441
442
```python
443
import av
444
445
def normalize_audio_loudness(input_file, output_file, target_lufs=-23.0):
446
"""Normalize audio to target loudness using two-pass process."""
447
448
# First pass: analyze audio
449
print("First pass: analyzing audio...")
450
451
input_container = av.open(input_file)
452
input_stream = input_container.streams.audio[0]
453
454
# Create analysis filter
455
graph = av.filter.Graph()
456
abuffer = graph.add_abuffer(template=input_stream)
457
458
# Loudnorm filter for analysis
459
loudnorm = graph.add('loudnorm',
460
I=target_lufs, # Target integrated loudness
461
TP=-2.0, # Target true peak
462
LRA=11.0, # Target loudness range
463
print_format='json')
464
465
abuffersink = graph.add('abuffersink')
466
467
abuffer.link_to(loudnorm)
468
loudnorm.link_to(abuffersink)
469
graph.configure()
470
471
# Process for analysis (discard output)
472
for frame in input_container.decode(input_stream):
473
abuffer.push(frame)
474
475
while True:
476
try:
477
abuffersink.pull() # Discard frame, just analyze
478
except av.BlockingIOError:
479
break
480
481
input_container.close()
482
483
# Get analysis results (in real implementation, this would be extracted from filter)
484
# For this example, we'll simulate the stats
485
analysis_stats = {
486
'input_i': -16.0, # Input integrated loudness
487
'input_tp': -1.5, # Input true peak
488
'input_lra': 15.2, # Input loudness range
489
'input_thresh': -26.8, # Input threshold
490
'target_offset': -7.0 # Calculated offset
491
}
492
493
print(f"Analysis complete:")
494
print(f" Input integrated loudness: {analysis_stats['input_i']} LUFS")
495
print(f" Target offset: {analysis_stats['target_offset']} dB")
496
497
# Second pass: apply normalization
498
print("Second pass: applying normalization...")
499
500
input_container = av.open(input_file)
501
input_stream = input_container.streams.audio[0]
502
503
output_container = av.open(output_file, 'w')
504
output_stream = output_container.add_stream('aac', rate=input_stream.sample_rate)
505
output_stream.channels = input_stream.channels
506
output_stream.layout = input_stream.layout
507
508
# Create normalization filter with analysis results
509
graph = av.filter.Graph()
510
abuffer = graph.add_abuffer(template=input_stream)
511
512
loudnorm = graph.add('loudnorm',
513
I=target_lufs,
514
TP=-2.0,
515
LRA=11.0,
516
measured_I=analysis_stats['input_i'],
517
measured_TP=analysis_stats['input_tp'],
518
measured_LRA=analysis_stats['input_lra'],
519
measured_thresh=analysis_stats['input_thresh'],
520
offset=analysis_stats['target_offset'],
521
linear=True)
522
523
abuffersink = graph.add('abuffersink')
524
525
abuffer.link_to(loudnorm)
526
loudnorm.link_to(abuffersink)
527
graph.configure()
528
529
# Process and encode normalized audio
530
for frame in input_container.decode(input_stream):
531
abuffer.push(frame)
532
533
while True:
534
try:
535
normalized_frame = abuffersink.pull()
536
if normalized_frame is None:
537
break
538
539
for packet in output_stream.encode(normalized_frame):
540
output_container.mux(packet)
541
542
except av.BlockingIOError:
543
break
544
545
# Flush
546
for packet in output_stream.encode():
547
output_container.mux(packet)
548
549
input_container.close()
550
output_container.close()
551
552
print(f"Normalization complete: {output_file}")
553
554
# Normalize audio file
555
normalize_audio_loudness('input.wav', 'normalized.aac', target_lufs=-16.0)
556
```
557
558
### Custom Filter Chain Builder
559
560
```python
561
import av
562
563
class FilterChainBuilder:
564
"""Builder for creating filter chains."""
565
566
def __init__(self):
567
self.graph = av.filter.Graph()
568
self.contexts = []
569
self.last_context = None
570
571
def add_input_buffer(self, stream):
572
"""Add input buffer for audio or video stream."""
573
if stream.type == 'video':
574
ctx = self.graph.add_buffer(template=stream)
575
elif stream.type == 'audio':
576
ctx = self.graph.add_abuffer(template=stream)
577
else:
578
raise ValueError(f"Unsupported stream type: {stream.type}")
579
580
self.contexts.append(ctx)
581
self.last_context = ctx
582
return self
583
584
def add_filter(self, filter_name, **kwargs):
585
"""Add filter to chain."""
586
ctx = self.graph.add(filter_name, **kwargs)
587
588
if self.last_context:
589
self.last_context.link_to(ctx)
590
591
self.contexts.append(ctx)
592
self.last_context = ctx
593
return self
594
595
def add_output_buffer(self):
596
"""Add output buffer sink."""
597
if hasattr(self.contexts[0], 'template'):
598
# Determine type from first context
599
template = self.contexts[0].template
600
if template and hasattr(template, 'width'):
601
ctx = self.graph.add('buffersink')
602
else:
603
ctx = self.graph.add('abuffersink')
604
else:
605
ctx = self.graph.add('buffersink') # Default to video
606
607
if self.last_context:
608
self.last_context.link_to(ctx)
609
610
self.contexts.append(ctx)
611
self.last_context = ctx
612
return self
613
614
def build(self):
615
"""Configure and return the filter graph."""
616
self.graph.configure()
617
return self.graph, self.contexts[0], self.contexts[-1]
618
619
# Example usage
620
def process_with_builder(input_file, output_file):
621
"""Process video using filter chain builder."""
622
623
input_container = av.open(input_file)
624
input_stream = input_container.streams.video[0]
625
626
output_container = av.open(output_file, 'w')
627
output_stream = output_container.add_stream('h264', rate=input_stream.framerate)
628
output_stream.width = 1280
629
output_stream.height = 720
630
output_stream.pix_fmt = 'yuv420p'
631
632
# Build filter chain
633
builder = FilterChainBuilder()
634
graph, input_ctx, output_ctx = (builder
635
.add_input_buffer(input_stream)
636
.add_filter('scale', width=1280, height=720)
637
.add_filter('eq', brightness=0.1, contrast=1.1, saturation=1.2)
638
.add_filter('unsharp', luma_amount=0.8)
639
.add_output_buffer()
640
.build())
641
642
print("Created filter chain: input -> scale -> eq -> unsharp -> output")
643
644
# Process frames
645
for frame in input_container.decode(input_stream):
646
input_ctx.push(frame)
647
648
while True:
649
try:
650
filtered_frame = output_ctx.pull()
651
if filtered_frame is None:
652
break
653
654
filtered_frame.pts = frame.pts
655
filtered_frame.time_base = input_stream.time_base
656
657
for packet in output_stream.encode(filtered_frame):
658
output_container.mux(packet)
659
660
except av.BlockingIOError:
661
break
662
663
# Flush
664
for packet in output_stream.encode():
665
output_container.mux(packet)
666
667
input_container.close()
668
output_container.close()
669
670
# Use builder
671
process_with_builder('input.mp4', 'enhanced.mp4')
672
```
673
674
### Available Filters Reference
675
676
```python
677
import av
678
679
def list_available_filters():
680
"""List all available filters by category."""
681
682
print(f"Total available filters: {len(av.filters_available)}")
683
684
# Categorize filters
685
video_filters = []
686
audio_filters = []
687
other_filters = []
688
689
for filter_name in sorted(av.filters_available):
690
try:
691
filter_obj = av.Filter(filter_name)
692
description = filter_obj.description
693
694
if 'video' in description.lower():
695
video_filters.append((filter_name, description))
696
elif 'audio' in description.lower():
697
audio_filters.append((filter_name, description))
698
else:
699
other_filters.append((filter_name, description))
700
except:
701
other_filters.append((filter_name, "No description"))
702
703
print(f"\nVideo filters ({len(video_filters)}):")
704
for name, desc in video_filters[:10]: # Show first 10
705
print(f" {name:20} - {desc[:60]}...")
706
707
print(f"\nAudio filters ({len(audio_filters)}):")
708
for name, desc in audio_filters[:10]: # Show first 10
709
print(f" {name:20} - {desc[:60]}...")
710
711
print(f"\nOther filters ({len(other_filters)}):")
712
for name, desc in other_filters[:5]: # Show first 5
713
print(f" {name:20} - {desc[:60]}...")
714
715
# List filters
716
list_available_filters()
717
718
# Get detailed info about specific filter
719
scale_filter = av.Filter('scale')
720
print(f"\nScale filter details:")
721
print(f" Name: {scale_filter.name}")
722
print(f" Description: {scale_filter.description}")
723
print(f" Options: {len(scale_filter.options)} available")
724
```