yt-dlp 2.4.0

๐ŸŽฌ๏ธ A Rust library (with auto dependencies installation) for Youtube downloading
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
# ๐Ÿค Contributing to yt-dlp

Thank you for your interest in contributing! This guide will help you understand our codebase conventions and write code that feels like it belongs here. Every rule exists because it's already applied consistently across the entire codebase โ€” please follow them to keep things uniform.

---

## ๐Ÿ“‹ Table of Contents

- [๐Ÿš€ Getting Started]#-getting-started
- [๐Ÿ—๏ธ Project Architecture]#๏ธ-project-architecture
- [โœ๏ธ Code Style]#๏ธ-code-style
- [๐Ÿšจ Error Handling]#-error-handling
- [๐Ÿ”ง Builder Patterns]#-builder-patterns
- [๐Ÿ“ฆ Model & Data Types]#-model--data-types
- [๐Ÿงฌ Trait Design]#-trait-design
- [๐Ÿ”’ Shared State & Concurrency]#-shared-state--concurrency
- [โšก Async Programming]#-async-programming
- [๐Ÿ”” Event System]#-event-system
- [๐ŸŽฏ Feature Flags]#-feature-flags
- [๐Ÿ“ Tracing & Logging]#-tracing--logging
- [๐Ÿ“– Documentation]#-documentation
- [๐Ÿ” Contributing to media-seek]#-contributing-to-media-seek
- [โœ… Verification Checklist]#-verification-checklist

---

## ๐Ÿš€ Getting Started

### Prerequisites

- **Rust** (edition 2024) โ€” install via [rustup]https://rustup.rs/
- **cargo-hack** โ€” `cargo install cargo-hack`
- **cargo-deny** โ€” `cargo install cargo-deny`

### Running the checks

Every PR must pass these commands:
```bash
# media-seek standalone lint
cargo clippy -p media-seek -- -D warnings

# Lint each feature in isolation (workspace-wide, covers both crates)
cargo hack clippy --workspace --each-feature --exclude-all-features -- -D warnings

# Lint tiered cache combinations (L1 Moka + L2 persistent)
cargo clippy --workspace --features cache-memory,cache-json -- -D warnings
cargo clippy --workspace --features cache-memory,cache-redb -- -D warnings
cargo clippy --workspace --features cache-memory,cache-redis -- -D warnings

# Run all doc-tests (workspace-wide)
cargo test --doc --workspace

# Check dependencies (licenses, advisories, bans)
cargo deny check
```

### Branch workflow

1. Fork the repository and create a branch from `develop`
2. Make your changes following the guidelines below
3. Run the verification checks above
4. Open a PR against `develop`

---

## ๐Ÿ—๏ธ Project Architecture

The codebase is a Cargo workspace with two crates. Understanding the layout is essential before making changes:

```
yt-dlp/
โ”œโ”€โ”€ Cargo.toml               โ† workspace manifest ([workspace] + [package])
โ”œโ”€โ”€ src/                     โ† yt-dlp crate source
โ””โ”€โ”€ crates/
    โ””โ”€โ”€ media-seek/          โ† standalone container index parsing crate
        โ”œโ”€โ”€ Cargo.toml
        โ””โ”€โ”€ src/
            โ”œโ”€โ”€ lib.rs       โ€” RangeFetcher trait + parse() dispatch
            โ”œโ”€โ”€ error.rs     โ€” Error enum + Result<T> alias
            โ”œโ”€โ”€ detect.rs    โ€” magic-byte format detection
            โ”œโ”€โ”€ index.rs     โ€” ContainerIndex, SegmentEntry, Inner
            โ”œโ”€โ”€ audio/       โ€” mp3, ogg, flac, pcm (wav+aiff), adts
            โ””โ”€โ”€ video/       โ€” mp4, webm, flv, avi, ts
```

The `yt-dlp` crate module hierarchy:

```
src/
โ”œโ”€โ”€ lib.rs              # ๐Ÿ  Crate root โ€” Downloader struct lives here (NOT in a submodule)
โ”œโ”€โ”€ prelude.rs          # ๐Ÿ“ค Convenience re-exports for `use yt_dlp::prelude::*`
โ”œโ”€โ”€ macros.rs           # ๐Ÿงฉ Macros: youtube!, ytdlp_args!, install_libraries!, ternary!
โ”œโ”€โ”€ error.rs            # ๐Ÿšจ Single unified Error enum + type Result<T>
โ”‚
โ”œโ”€โ”€ client/             # ๐Ÿ”ง Builder, download builder, proxy, deps, stream orchestration
โ”‚   โ”œโ”€โ”€ builder.rs      #    DownloaderBuilder (fluent builder)
โ”‚   โ”œโ”€โ”€ download_builder.rs  # DownloadBuilder<'a> (fluent download API)
โ”‚   โ”œโ”€โ”€ proxy.rs        #    ProxyConfig, ProxyType
โ”‚   โ”œโ”€โ”€ deps/           #    ๐Ÿ“ฆ Auto-installation of yt-dlp & ffmpeg from GitHub releases
โ”‚   โ””โ”€โ”€ streams/        #    ๐Ÿงฉ Format selection (VideoSelection trait), orchestration
โ”‚
โ”œโ”€โ”€ download/           # ๐Ÿ“ฅ DownloadManager, Fetcher, segment-based parallel downloads
โ”œโ”€โ”€ events/             # ๐Ÿ”” EventBus, DownloadEvent, EventFilter, hooks, webhooks
โ”œโ”€โ”€ executor/           # โš™๏ธ Process runner, FfmpegArgs builder, temp-file+rename
โ”œโ”€โ”€ extractor/          # ๐Ÿ“ก VideoExtractor trait, Youtube & Generic extractors
โ”œโ”€โ”€ metadata/           # ๐Ÿท๏ธ MP3/MP4/FFmpeg/Lofty metadata writing, chapter injection
โ”œโ”€โ”€ model/              # ๐Ÿ“Š Data types: Video, Format, Chapter, Playlist, Caption, etc.
โ”‚   โ”œโ”€โ”€ utils/          #    Serde helpers
โ”‚   โ””โ”€โ”€ selector.rs     #    VideoQuality, AudioQuality, StoryboardQuality enums
โ”œโ”€โ”€ cache/              # ๐Ÿ” VideoCache, DownloadCache, PlaylistCache (feature-gated)
โ”‚   โ””โ”€โ”€ backend/        #    Backend trait + implementations (memory/moka, json, redb, redis)
โ”œโ”€โ”€ live/               # ๐Ÿ”ด Live stream recording (feature: live-recording)
โ”‚   โ”œโ”€โ”€ hls.rs          #    HLS manifest parsing via m3u8-rs
โ”‚   โ”œโ”€โ”€ recording.rs    #    Reqwest-based HLS segment recorder (primary)
โ”‚   โ””โ”€โ”€ ffmpeg_recording.rs  # FFmpeg-based recorder (fallback)
โ”œโ”€โ”€ stats/              # ๐Ÿ“Š StatisticsTracker, GlobalSnapshot (feature: statistics)
โ””โ”€โ”€ utils/              # ๐Ÿ› ๏ธ fs, http, platform, retry, validation, url_expiry, subtitle
```

### ๐Ÿ“ Module conventions

| Rule | Example |
|------|---------|
| Each directory has a `mod.rs` that declares submodules and re-exports public types | `pub use video::VideoCache;` in `cache/mod.rs` |
| `lib.rs` re-exports the most-used types to crate root | `pub use client::{DownloadBuilder, DownloaderBuilder};` |
| `prelude.rs` re-exports everything for basic usage | Feature-gated with `#[cfg(feature = "...")]` |
| Module-level `//!` doc comments on every `mod.rs` | Describes the module's purpose and architecture |
| Feature-gated modules in `lib.rs` | `#[cfg(feature = "statistics")] pub mod stats;` |

### ๐Ÿ‘๏ธ Visibility rules

| Visibility | When to use | Example |
|-----------|-------------|---------|
| `pub` | Types and methods exposed to library users | `pub fn fetch_video_infos(...)` |
| `pub(crate)` | All fields of `Downloader`, internal helpers | `pub(crate) youtube_extractor: Youtube` |
| Private | Implementation details | `fn audio_codec_for_mux(...)` |

> ๐Ÿ’ก Builder struct fields are always **private**. `TypedBuilder` config struct fields are always **`pub`**.

---

## โœ๏ธ Code Style

### ๐ŸŒ Language

All comments, docs, variable names, error messages, and log messages must be in **English**. No exceptions.

### ๐Ÿ“ฅ Imports

```rust
// โœ… GOOD โ€” All imports at the top of the file
use crate::error::Result;
use crate::model::Video;
use std::path::PathBuf;

#[cfg(target_os = "windows")]
use std::os::windows::process::CommandExt;

// โŒ BAD โ€” Never import inside function bodies
fn my_function() {
    use std::collections::HashMap; // WRONG
}
```

> ๐Ÿงฉ **Exception**: inside `macro_rules!` definitions, `$crate::` paths may require local imports.

### ๐Ÿท๏ธ Naming conventions

| Item | Convention | Example |
|------|-----------|---------|
| Variables & functions | `snake_case` | `download_video`, `is_ready` |
| Types & structs | `PascalCase` | `DownloaderBuilder`, `VideoQuality` |
| Constants | `SCREAMING_SNAKE_CASE` | `DEFAULT_RETRY_ATTEMPTS`, `FORMAT_URL_LIFETIME` |
| Constants prefix | Context prefix | `DEFAULT_`, `CONSERVATIVE_`, `BALANCED_`, `AGGRESSIVE_` |
| Booleans | Intent-driven | `is_ready`, `has_data`, `include_full_data` |

### ๐ŸŽฏ Parameter types

Use the most appropriate type for public API parameters:

```rust
// โœ… GOOD โ€” Flexible public API
pub fn new(url: impl Into<String>) -> Self { ... }
pub fn with_cookies(mut self, path: impl Into<PathBuf>) -> Self { ... }
pub fn input(mut self, path: impl AsRef<str>) -> Self { ... }

// โŒ BAD โ€” Too restrictive
pub fn new(url: String) -> Self { ... }
pub fn new(url: &str) -> Self { ... }
```

For internal functions, use the most optimized type for the operations applied:
- `&str` if you only read the string
- `String` if you need ownership
- `&Path` if you only read the path
- `PathBuf` if you need ownership

### ๐Ÿงช Testing

There are **no `#[cfg(test)]` modules** in `src/`. All testing is done via:
- ๐Ÿ“ **Doctests** โ€” `cargo test --doc` (111+ tests)
- ๐Ÿ“Š **Benchmarks** โ€” `benches/benchmarks.rs` with [criterion]https://crates.io/crates/criterion
- ๐Ÿงช **Integration examples** โ€” `examples/` directory

---

## ๐Ÿšจ Error Handling

We use a **single unified error type** in `src/error.rs`. Never introduce new error enums (except `HookError` which already exists for hook-specific failures).

### Rules

| Rule | Detail |
|------|--------|
| **One `Error` enum** | All variants in one enum, grouped by `// === Category ===` comment banners |
| **Type alias** | `pub type Result<T> = std::result::Result<T, Error>;` โ€” import as `use crate::error::Result;` |
| **Structured fields** | Every variant uses named fields (`operation`, `url`, `reason`, `path`, `source`) โ€” never just a string |
| **`#[source]`** | Always on the inner error field for proper chaining |
| **Helper constructors** | `Error::io(...)`, `Error::http(...)` โ€” each logs `tracing::warn!`/`tracing::error!` before constructing |
| **`From` impls** | For `std::io::Error`, `reqwest::Error`, `serde_json::Error`, `JoinError`, `ZipError` โ€” each logs with `"(automatic conversion)"` suffix |
| **Parameter style** | `impl Into<String>` โ€” not concrete types |
| **Feature-gated** | `#[cfg(feature = "cache-redb")] Database { ... }`, `#[cfg(feature = "cache-redis")] Redis { ... }` |
| **No `anyhow`** | Always use the crate's own `Error` / `Result` |

### Example: Adding a new error variant

```rust
// In src/error.rs, add to the appropriate category section:

// ==================== Video & Format Errors ====================

/// My new error description.
#[error("Something failed for {video_id}: {reason}")]
MyNewError {
    video_id: String,
    reason: String,
},
```

And add a helper constructor:
```rust
pub fn my_new_error(video_id: impl Into<String>, reason: impl Into<String>) -> Self {
    let video_id = video_id.into();
    let reason = reason.into();

    tracing::warn!(video_id = video_id, reason = reason, "Something failed");

    Self::MyNewError { video_id, reason }
}
```

---

## ๐Ÿ”ง Builder Patterns

Two builder styles coexist โ€” use the right one for the right job:

### A) Manual builder (consuming `mut self`)

Used for: `DownloaderBuilder`, `DownloadBuilder`, `WebhookConfig`, `FfmpegArgs`

```rust
// โœ… Builder methods prefixed with `with_` and consuming `mut self`
pub fn with_timeout(mut self, timeout: Duration) -> Self {
    self.timeout = timeout;
    self
}

// โœ… Terminal method
pub async fn build(self) -> Result<Downloader> { ... }
```

| Rule | Detail |
|------|--------|
| Method prefix | `with_` (e.g. `with_args`, `with_timeout`, `with_proxy`, `with_cache`) |
| Self parameter | Always `mut self` (consuming) โ€” **never `&mut self`** |
| Terminal method | `.build()` or `.execute()` |
| Field visibility | Private |

### B) `TypedBuilder` derive

Used for: config structs (`ManagerConfig`, `RetryPolicy`, `ExpiryConfig`)

```rust
#[derive(Debug, Clone, TypedBuilder)]
pub struct ManagerConfig {
    #[builder(default = SpeedProfile::default().max_concurrent_downloads())]
    pub max_concurrent_downloads: usize,
}
```

| Rule | Detail |
|------|--------|
| Field visibility | `pub` |
| Defaults | `#[builder(default = ...)]` |

### C) Post-build mutation on `Downloader`

After `.build()`, use `set_*`/`add_*` methods (not `with_*`) to mutate the `Downloader` instance:

```rust
downloader.set_user_agent("my-agent");
downloader.set_timeout(Duration::from_secs(30));
downloader.set_args(vec!["--no-playlist".into()]);
downloader.add_arg("--flat-playlist");
downloader.set_cookies("cookies.txt");
downloader.set_cookies_from_browser("chrome");
downloader.set_netrc();
```

| Rule | Detail |
|------|--------|
| Self parameter | `&mut self` (borrowing) โ€” returns `&mut Self` for chaining |
| Prefix for replacing | `set_` (e.g. `set_cookies`, `set_user_agent`, `set_timeout`) |
| Prefix for appending | `add_` (e.g. `add_arg`) |

> ๐Ÿ’ก **Don't confuse** builder `with_*` methods (consuming `mut self`, used before `.build()`) with post-build `set_*`/`add_*` methods (borrowing `&mut self`, used after `.build()`).

---

## ๐Ÿ“ฆ Model & Data Types

### Standard derive sets

| Type | Derives |
|------|---------|
| **Simple enums** | `Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize` + `Default` with `#[default]` |
| **Complex structs** (with `f64`) | `Debug, Clone, PartialEq, Serialize, Deserialize` โ€” manual `Eq`/`Hash` |
| **Simple structs** (no floats) | `Debug, Clone, PartialEq, Eq, Serialize, Deserialize` |

### Serde patterns

| Pattern | Usage |
|---------|-------|
| `#[serde(flatten)]` | Struct composition (e.g. `Format` flattens `CodecInfo`, `VideoResolution`, etc.) |
| `#[serde(rename = "...")]` | Field name mapping from JSON (`"timestamp"`, `"acodec"`) |
| `#[serde(rename_all = "snake_case")]` | Enum variant renaming |
| `#[serde(default)]` | Optional collections and fields |
| `#[serde(other)]` | `Unknown` variant for forward compatibility |
| `#[serde(skip)]` | Derived/internal fields (e.g. `video_id` on `Format`) |
| `json_none` deserializer | Turns `"none"` strings to `Option::None` (in `model/utils/serde.rs`) |
| `#[serde_as(deserialize_as = "DefaultOnNull")]` | From `serde_with`, for nullable JSON fields |
| Custom `Deserialize` visitor | Polymorphic types (e.g. `DrmStatus` accepts bool or string) |
| `ordered_float::OrderedFloat<f64>` | Only when `f64` needs `Hash`/`Eq` |

### ๐Ÿ–จ๏ธ Display format

**Always** use the format `TypeName(key=value, key=value)`:

```rust
impl fmt::Display for Video {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Video(id={}, title={:?}, channel={:?}, formats={})",
            self.id, self.title, self.channel.as_deref().unwrap_or("Unknown"), self.formats.len())
    }
}
```

| Rule | Detail |
|------|--------|
| Only essential fields | Never full serialization |
| `Option` fields | `as_deref().unwrap_or("none")` or `unwrap_or("unknown")` |
| Enum constant variants | `f.write_str("VariantName")` |
| Enum variants with fields | `write!(f, "Variant(key={})", val)` |

### ๐Ÿ”‘ Custom `Hash` implementations

Hash **only identity fields** โ€” not all struct fields:

```rust
impl Hash for Video {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.id.hash(state);
        self.title.hash(state);
        self.channel.hash(state);
        self.channel_id.hash(state);
    }
}
```

---

## ๐Ÿงฌ Trait Design

### Which pattern to use?

| Pattern | When | Example |
|---------|------|---------|
| `#[async_trait]` | Trait used as `dyn Trait` (trait objects) | `VideoExtractor`, `EventHook` |
| RPITIT (`impl Future + Send`) | Dispatched via concrete enum, never `dyn` | Cache backend traits |
| `DynClone + clone_trait_object!` | Need to clone trait objects | `EventHook` |
| `Downcast + impl_downcast!` | Runtime downcasting of trait objects | `VideoExtractor` |

### `#[async_trait]` example

```rust
#[async_trait]
pub trait VideoExtractor: Downcast + Send + Sync + fmt::Debug {
    async fn fetch_video(&self, url: &str) -> Result<Video>;
    fn name(&self) -> ExtractorName;
    fn supports_url(&self, url: &str) -> bool;
}
impl_downcast!(VideoExtractor);
```

### RPITIT example

```rust
pub trait VideoBackend: Send + Sync + std::fmt::Debug {
    fn get(&self, url: &str) -> impl Future<Output = Result<Option<Video>>> + Send;
    fn put(&self, url: String, video: Video) -> impl Future<Output = Result<()>> + Send;
}
```

> ๐Ÿ“– Trait method declarations carry **full rustdoc**; implementations may add only a brief clarifying comment.

---

## ๐Ÿ”’ Shared State & Concurrency

### Primitives used

| Primitive | Purpose |
|-----------|---------|
| `Arc<reqwest::Client>` | Shared HTTP client with connection pooling |
| `Arc<Mutex<...>>` | Mutable shared state (download queues, task maps, next_id counter) |
| `Arc<Semaphore>` | Concurrency limit for parallel downloads |
| `Arc<AtomicU64>` / `Arc<AtomicBool>` | Lock-free counters and flags |
| `Arc<RwLock<...>>` | Read-heavy shared state (hook registry, stats, webhooks) |
| `Arc<DownloadEvent>` | Events in broadcast channel (efficient cloning) |
| `Arc<dyn Fn(...) + Send + Sync>` | Callbacks and filter predicates |
| `tokio_util::sync::CancellationToken` | Graceful shutdown |

### โš ๏ธ Important rules

| Rule | Detail |
|------|--------|
| Async locks | Use `tokio::sync::Mutex` and `tokio::sync::RwLock` |
| Sync locks | `std::sync::Mutex` **only** for progress counters and non-async contexts |
| Lock safety | **Never** hold a `tokio` lock across `.await` points |
| Simple counters | Prefer `Arc<AtomicU64>` over `Arc<Mutex<u64>>` |
| Caches on Downloader | `Option<Arc<VideoCache>>` |

---

## โšก Async Programming

| Rule | Detail |
|------|--------|
| Runtime | `tokio` (multi-threaded) |
| Task spawning | `tokio::spawn` for concurrency |
| Multiple tasks | `tokio::select!` for managing cancellations |
| Structured concurrency | Prefer scoped tasks and clean cancellation paths |
| Timeouts | `tokio::time::timeout` with kill on timeout |
| Blocking work | Offload to `tokio::task::spawn_blocking` (used for `serde_json::from_reader`, CPU-intensive parsing) |
| Time operations | `tokio::time::sleep` and `tokio::time::interval` |
| HTTP | `reqwest` with `Arc<Client>` connection pooling |

### Channels

| Channel | Usage |
|---------|-------|
| `tokio::sync::mpsc` | Webhook delivery queue (bounded, backpressure) |
| `tokio::sync::broadcast` | Event broadcasting to multiple subscribers |
| `tokio::sync::oneshot` | One-time task communication |

---

## ๐Ÿ”” Event System

The event system lives in `src/events/` and follows a three-phase delivery pattern:

### Architecture

| Component | Role |
|-----------|------|
| `EventBus` | Wraps `broadcast::Sender<Arc<DownloadEvent>>` |
| `DownloadEvent` | Large enum โ€” **all variants use named fields** (no tuple variants) |
| `EventFilter` | Predicate-based with `Vec<Arc<dyn Fn(&DownloadEvent) -> bool + Send + Sync>>` |
| `HookRegistry` | `Arc<RwLock<Vec<Box<dyn EventHook>>>>` |
| `simple_hook!` | Macro to create hooks from closures |

### Event emission order (in `Downloader::emit_event()`)

1. ๐Ÿช **Hooks** โ€” with timeout (`#[cfg(feature = "hooks")]`)
2. ๐Ÿ“ก **Webhooks** โ€” non-blocking (`#[cfg(feature = "webhooks")]`)
3. ๐Ÿ“ข **Broadcast bus** โ€” always

### Adding a new event variant

```rust
// In DownloadEvent โ€” always use named fields:
// โœ… GOOD
MyNewEvent {
    download_id: u64,
    reason: String,
},

// โŒ BAD โ€” No tuple variants
MyNewEvent(u64, String),
```

---

## ๐ŸŽฏ Feature Flags

### Available features

| Feature | Purpose | Dependencies |
|---------|---------|-------------|
| `hooks` | Rust event callbacks | None |
| `webhooks` | HTTP event delivery | None |
| `statistics` | Real-time analytics | None |
| `cache-memory` *(default)* | In-memory Moka cache | `moka` |
| `cache-json` | JSON file backend | None |
| `cache-redb` | Embedded redb backend | `redb` |
| `cache-redis` | Distributed Redis backend | `redis` |
| `live-recording` | Live stream recording (HLS) | `m3u8-rs` |
| `rustls` | TLS backend | `reqwest/rustls` |
| `hickory-dns` | Async DNS resolver | `reqwest/hickory-dns` |
| `profiling` | Heap profiler | `dhat` |

### โš™๏ธ `cache` cfg is emitted by `build.rs`

The `cache` cfg is **not** a Cargo feature โ€” it is a custom `cfg` emitted by `build.rs` when any cache backend
(`cache-memory`, `cache-json`, `cache-redb`, or `cache-redis`) is enabled. Users cannot activate it directly,
and it is invisible in `Cargo.toml`. Use `#[cfg(cache)]` to guard code that requires any cache backend.

### Backend selection

`build.rs` emits `persistent_cache` when any of `cache-json`, `cache-redb`, or `cache-redis` is enabled, and `multiple_persistent_backends` if more than one is active (which triggers a `compile_error!`). At most one persistent backend may be enabled at a time.

### Conditional compilation patterns

```rust
// Module-level guard for all cache code (cfg emitted by build.rs)
#[cfg(cache)]

// Backend-specific modules
#[cfg(feature = "cache-json")]
pub mod json;

// Persistent backend guard (any of json/redb/redis)
#[cfg(persistent_cache)]

// Feature-gated struct fields
#[cfg(feature = "hooks")]
pub(crate) hook_registry: Option<events::HookRegistry>,
```

---

## ๐Ÿ“ Tracing & Logging

Tracing is an **unconditional dependency** โ€” every important function must have tracing.

### Rules at a glance

| Rule | Detail |
|------|--------|
| Macro style | Always fully-qualified: `tracing::debug!(...)` โ€” **never import the macros** |
| No `#[instrument]` | Never use the `#[instrument]` attribute |
| Structured fields | `key = value`, `key = ?value` (Debug), `key = %value` (Display) |
| No interpolation | Never `tracing::debug!("msg {}", var)` โ€” always structured fields |

### Log levels

| Level | Usage | Emoji? |
|-------|-------|--------|
| `trace` | Hot paths, data transforms (rare โ€” prefer deleting) | โœ… Yes |
| `debug` | Function entry/exit, parameters, config, internal ops | โœ… Yes |
| `info` | Key milestones (download start/end, fetch, install, shutdown) | โœ… Yes |
| `warn` | Recoverable failures, retries, fallbacks | โŒ No emoji |
| `error` | Unrecoverable per-item failures | โŒ No emoji |

### ๐ŸŽจ Emoji prefixes

Every `trace`/`debug`/`info` message **must** start with one domain emoji:

| Emoji | Domain |
|-------|--------|
| ๐Ÿ“ฆ | Install / dependencies |
| ๐Ÿ“ก | Fetch / extract |
| ๐Ÿ“ฅ | Download |
| ๐ŸŽฌ | Combine / mux |
| โœ‚๏ธ | Postprocess / ffmpeg |
| ๐Ÿท๏ธ | Metadata |
| ๐Ÿ’ฌ | Subtitle |
| ๐Ÿ–ผ๏ธ | Thumbnail |
| ๐Ÿ“‹ | Playlist |
| โœ… | Success / completion |
| ๐Ÿ”„ | Retry / update |
| ๐Ÿ”ง | Config / setup / builder |
| ๐Ÿ” | Cache / lookup |
| โš™๏ธ | Internal / utility |
| ๐Ÿ“Š | Statistics |
| ๐Ÿ”” | Events |
| ๐Ÿงฉ | Format selection |
| ๐Ÿ›‘ | Shutdown |

### Example

```rust
// โœ… GOOD
tracing::debug!(url = %url, timeout = ?timeout, "๐Ÿ“ฅ Starting download");
tracing::info!(video_id = video_id, formats = formats.len(), "๐Ÿ“ก Video fetched");
tracing::warn!(url = %url, attempt = attempt, "Retry after failure");

// โŒ BAD
tracing::debug!("Starting download for {}", url);  // No interpolation
tracing::info!("Video fetched");                     // No structured fields
tracing::warn!("โš ๏ธ Retry");                          // No emoji on warn
```

### What NOT to trace

- โŒ Trivial getters/setters that just return or set a field
- โŒ Pure transforms (`to_ffmpeg_name`, `is_empty`, enum-to-string)
- โŒ Simple constant lookups / match on enum returning a value

---

## ๐Ÿ“– Documentation

Every public function, method, and trait method must have a **rustdoc comment**:

### Template

```rust
/// Brief one-line description.
///
/// Optional extended description.
///
/// # Arguments
///
/// * `param` - Description
///
/// # Errors
///
/// Returns an error if ...
///
/// # Returns
///
/// Description of return value.
///
/// # Examples
///
/// ```rust,no_run
/// # use yt_dlp::prelude::*;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let downloader = Downloader::builder(libraries, "output").build().await?;
/// # Ok(())
/// # }
/// ```
```

### Section rules

| Section | When to include |
|---------|----------------|
| `# Arguments` | Only if params beyond `&self`/`&mut self` |
| `# Errors` | Only if returns `Result` |
| `# Returns` | Only if returns a value (not `()`) |
| `# Examples` | Main public API entry points (`Downloader::new`, `download`, `fetch`, etc.) |

### Additional rules

| Rule | Detail |
|------|--------|
| Trait methods | Full rustdoc on the **trait declaration**; impls may add only a brief comment |
| Getters | Minimum one-liner + `# Returns` |
| Setters | Minimum one-liner + `# Arguments` |
| Builder methods | Minimum one-liner + `# Arguments` |
| Examples | Use `no_run` or `ignore` for network/binary-dependent code |

---

## โš™๏ธ Process Execution

The crate runs external processes (`yt-dlp`, `ffmpeg`) through a controlled abstraction:

| Component | Location | Purpose |
|-----------|----------|---------|
| `Executor` | `src/executor/mod.rs` | Wraps `tokio::process::Command` with piped I/O and timeout |
| `ProcessOutput` | `src/executor/process.rs` | `{ stdout, stderr, code }` |
| `FfmpegArgs` | `src/executor/ffmpeg.rs` | Fluent builder: `.input()`, `.codec_copy()`, `.args()`, `.output()`, `.build()` |
| `run_ffmpeg_with_tempfile()` | `src/executor/ffmpeg.rs` | Temp file + rename pattern for atomic writes |

### Key patterns

- โฑ๏ธ **Timeout**: `tokio::time::timeout` + `process.kill()` on timeout
- ๐ŸชŸ **Windows**: `command.creation_flags(0x08000000)` (CREATE_NO_WINDOW) behind `#[cfg(target_os = "windows")]`
- ๐Ÿ”„ **Temp + rename**: FFmpeg writes to a temp file, then renames atomically โ€” never write directly to the final output
- ๐Ÿงต **CPU-heavy parsing**: `tokio::task::spawn_blocking` for `serde_json::from_reader` and other CPU-intensive work

---

## ๐Ÿงฉ Macros

Defined in `src/macros.rs` and `src/events/hooks.rs`:

| Macro | Purpose |
|-------|---------|
| `youtube!($yt_dlp, $ffmpeg, $output)` | Convenience `Downloader` constructor |
| `ytdlp_args![...]` | Args builder (string list or key-value pairs) |
| `install_libraries!($dir)` | Async binary installation |
| `ternary!($cond, $true, $false)` | Ternary operator |
| `simple_hook!` | Create an `EventHook` from a closure |

All macros must use `$crate::` fully-qualified paths for robustness. The `use` inside `macro_rules!` bodies is the **only** exception to the "imports at module top" rule.

---

## ๐Ÿ” Contributing to media-seek

`crates/media-seek/` is a standalone crate published independently to [crates.io](https://crates.io/crates/media-seek). Changes to it follow the same code conventions as the main crate, with a few important constraints.

### Constraints

| Rule | Detail |
|------|--------|
| **No feature flags** | All formats are always compiled in โ€” no conditional compilation inside `media-seek` |
| **No `reqwest`** | The crate is transport-agnostic. Callers implement `RangeFetcher`. |
| **No `serde`** | No serialization โ€” pure parsing only |
| **No `async_trait`** | `RangeFetcher` uses RPITIT (`impl Future + Send`), not `#[async_trait]` |

### Where to make changes

| Change | Location |
|--------|---------|
| Audio format parser | `crates/media-seek/src/audio/` (`mp3.rs`, `ogg.rs`, `flac.rs`, `pcm.rs`, `adts.rs`) |
| Video format parser | `crates/media-seek/src/video/` (`mp4.rs`, `webm.rs`, `flv.rs`, `avi.rs`, `ts.rs`) |
| Format detection | `crates/media-seek/src/detect.rs` |
| Index data types | `crates/media-seek/src/index.rs` |
| Error handling | `crates/media-seek/src/error.rs` |
| Public API | `crates/media-seek/src/lib.rs` |

### Tracing conventions

Every `pub(crate) fn parse()` / `pub(crate) async fn parse()` must have entry and success tracing:

```rust
// At function start:
tracing::debug!(probe_len = probe.len(), "โš™๏ธ Parsing <Format> stream");

// Just before each successful return:
tracing::debug!(segments = result.len(), "โœ… <Format> index parsed");
```

Use `โš™๏ธ` for internal operations and `โœ…` for success โ€” same as the main crate. No emoji on `warn!` or `error!`.

### Checking your changes

```bash
# media-seek standalone lint
cargo clippy -p media-seek -- -D warnings

# Doc-tests (both crates)
cargo test --doc --workspace
```

---

## โœ… Verification Checklist

Before submitting your PR, make sure:

- [ ] ๐Ÿ” `cargo clippy -p media-seek -- -D warnings` โ€” zero warnings
- [ ] ๐Ÿ” `cargo hack clippy --workspace --each-feature --exclude-all-features -- -D warnings` โ€” zero warnings
- [ ] ๐Ÿ” `cargo clippy --workspace --features cache-memory,cache-json -- -D warnings` โ€” zero warnings
- [ ] ๐Ÿ” `cargo clippy --workspace --features cache-memory,cache-redb -- -D warnings` โ€” zero warnings
- [ ] ๐Ÿ” `cargo clippy --workspace --features cache-memory,cache-redis -- -D warnings` โ€” zero warnings
- [ ] ๐Ÿงช `cargo test --doc --workspace` โ€” all doc-tests pass
- [ ] ๐Ÿ” `cargo deny check` โ€” no dependency issues
- [ ] ๐Ÿ“ All new public items have rustdoc following the template
- [ ] ๐ŸŽจ All tracing uses structured fields + emoji prefix
- [ ] ๐Ÿšจ Errors use the existing `Error` enum with structured fields
- [ ] ๐Ÿ“ฅ All `use` imports are at the top of the file
- [ ] ๐ŸŒ All text (comments, docs, logs) is in English

---

<div align="center">
  <strong>Thank you for contributing! ๐ŸŽ‰</strong>
  <br>
  <sub>If you have questions, open a <a href="https://github.com/boul2gom/yt-dlp/discussions">Discussion</a> โ€” we're happy to help.</sub>
</div>