I got tired of googling yt-dlp flags every time I wanted to download something. So I just built for the progress bar. The tricky part is that yt-dlp spits out lines that look like this:
[download] 47.3% of 128.40MiB at 3.20MiB/s ETA 00:25
So I just do a simple string search for the percentage and feed it into the bar:
if let Some(pos) = line.find('%') {
if let Some(start) = line[..pos].rfind(|c: char| c == ' ' || c == '[') {
let percent_str = line[start + 1..pos].trim();
if let Ok(pct) = percent_str.parse::<u64>() {
bar.set_position(pct);
}
}
}
No regex, no extra deps, just string slicing. The bar itself is a plain line with no spinner:
ProgressStyle::with_template(" [{bar:50.cyan/237}] {pos:>3}%")?
.progress_chars("━━─")
Auto-downloading yt-dlp
I didn't want users to have to go install yt-dlp manually before they could use trawl. So on startup it checks if yt-dlp is already in PATH. If not, it offers to grab it automatically and saves it to ~/.trawl/yt-dlp.
The download is just the GitHub releases page, with the right binary picked based on the platform:
let url = match (std::env::consts::OS, std::env::consts::ARCH) {
("macos", "aarch64") => "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos",
("linux", _) => "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp",
("windows", _) => "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe",
...
};
For HTTP I went with with the derive API. The whole CLI is just a struct with attributes, which I find way cleaner than manually matching arguments:
#[derive(Parser)]
struct Args {
url: String,
#[arg(short = 'a', long)]
audio_only: bool,
#[arg(short = 'F', long, default_value = "opus")]
audio_format: String,
...
}
One annoying thing: clap underlines section headers in the help output by default and it looks rough in most terminals. You have to manually override the styles to turn that off:
fn __styles() -> clap::builder::Styles {
clap::builder::Styles::styled()
.header(AnsiColor::White.on_default())
...
}
Things that surprised me along the way
mp4 files not opening on macOS. The default yt-dlp format selector picks VP9 video which QuickTime just refuses to play. Had to write a custom selector that explicitly requests H.264: bestvideo[ext=mp4][vcodec^=avc1]+bestaudio[ext=m4a]/best[ext=mp4]/best
yt-dlp output leaking into the terminal. Early on I used Stdio::inherit() for stdout. That sent everything straight to the terminal and completely bypassed the progress bar. Switching to Stdio::piped() on both streams and handling them manually sorted it out.
The Arabic comma thing. Already mentioned this above but it genuinely surprised me. Worth keeping in mind if you're ever scraping text from music platforms.
Give it a shot
cargo install trawl
GitHub: https://github.com/NotKiwy/trawl
If something breaks or you have questions about how any of this works, happy to chat in the comments.
SOCIAL SHARE CARD GENERATOR