or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

audio.mdcodecs.mdcontainers.mdfilters.mdindex.mdstreams.mdvideo.md

filters.mddocs/

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

```