Skip to main content

playlist_dl/
playlist_dl.rs

1// This example shows using vsd as library in other rust project.
2// Also see https://github.com/clitic/vsd/blob/main/vsd/src/core/playlist.rs
3//
4// [dependencies]
5// vsd = { version = "0.5", default-features = false, features = ["rustls-tls"]}
6
7use std::{io::Write, sync::Arc};
8use vsd::{
9    Error, Muxer, PlaylistDownloader, Result,
10    playlist::MediaType,
11    progress::{ByteSize, Eta, ProgressCallback, ProgressState},
12    reqwest::Client,
13    tokio,
14    tokio_util::sync::CancellationToken,
15};
16
17struct Progress;
18
19impl ProgressCallback for Progress {
20    fn on_progress(&self, state: &ProgressState) {
21        let stderr = std::io::stderr();
22        let mut handle = stderr.lock();
23        write!(
24            handle,
25            "\r\x1B[2K[{}/~{}({:.0}%) PT:{}/{} DL:{} ETA:{}]",
26            ByteSize(state.downloaded_bytes),
27            ByteSize(state.estimated_bytes),
28            state.percent,
29            state.downloaded_parts,
30            state.total_parts,
31            ByteSize(state.speed_bps as usize),
32            Eta(state.eta_seconds)
33        )
34        .unwrap();
35        handle.flush().unwrap();
36    }
37
38    fn on_finish(&self, state: &ProgressState) {
39        self.on_progress(state);
40        eprintln!();
41    }
42}
43
44#[tokio::main(flavor = "multi_thread")]
45async fn main() -> Result<()> {
46    let client = Client::new();
47
48    // We use ./target as temporary directory for downloaded files.
49    let dl = PlaylistDownloader::new(&client).directory("target");
50    let config = dl.get_config();
51
52    let mp = dl
53        .parse(
54            "https://media.axprod.net/TestVectors/Dash/not_protected_dash_1080p_h264/manifest.mpd",
55            false,
56        )
57        .await?;
58
59    // You can clone this token and call .cancel() to pause a download.
60    let token = CancellationToken::new();
61    let mut muxer = Muxer::new();
62
63    // Download first subtitle stream.
64    for stream in mp.streams {
65        if stream.media_type == MediaType::Subtitles {
66            println!(
67                "Downloading {} subtitles",
68                stream.language.as_deref().unwrap_or("unknown")
69            );
70
71            // We download to a temporary file, mux it in, and then clean up.
72            // You could also just move the file after download and avoid muxing if you want.
73            let dl_info = match stream.download(config, Arc::new(Progress), &token).await {
74                Ok(info) => info,
75                Err(Error::MissingSegments) => {
76                    println!("Stream has no segments");
77                    continue;
78                }
79                Err(Error::UnsupportedEncryption(e)) => {
80                    println!("Unsupported encryption {}", e);
81                    continue;
82                }
83                Err(Error::MissingKey(key_id)) => {
84                    println!("Missing decryption key for {key_id}");
85                    continue;
86                }
87                Err(Error::DownloadInterrupted) => {
88                    println!("Download paused");
89                    std::process::exit(0);
90                }
91                Err(e) => return Err(e),
92            };
93
94            println!("Downloaded {}", dl_info.path.to_string_lossy());
95            muxer.push(dl_info);
96
97            break;
98        }
99    }
100
101    println!("Muxing to target/output.srt");
102    muxer
103        .mux(&vsd::find_ffmpeg().unwrap(), "target/output.srt", "srt")
104        .await?;
105    muxer.clean(config.directory.as_deref()).await?;
106
107    Ok(())
108}