odysee-dl

odysee.com channel content downloader
git clone https://git.ea.contact/odysee-dl
Log | Files | Refs | README

post.rs (2947B)


      1 use crate::error::Error;
      2 use crate::event::Event;
      3 
      4 pub(crate) fn with_retry<F, T, C>(f: F, on_rate_limited: C) -> Result<T, Error>
      5 where
      6     F: Fn() -> Result<T, ureq::Error>,
      7     C: Fn(),
      8 {
      9     loop {
     10         match f() {
     11             Ok(val) => return Ok(val),
     12             Err(ureq::Error::StatusCode(429)) => {
     13                 on_rate_limited();
     14                 std::thread::sleep(std::time::Duration::from_secs(60));
     15             }
     16             Err(e) => return Err(e.into()),
     17         }
     18     }
     19 }
     20 
     21 pub struct Post {
     22     pub name: String,
     23     pub filename: String,
     24     pub streaming_url: String,
     25 }
     26 
     27 impl Post {
     28     pub fn from_json(json: &serde_json::Value) -> Result<Self, Error> {
     29         let name = json["name"]
     30             .as_str()
     31             .ok_or_else(|| Error::Http("name not found".to_string()))?
     32             .to_string();
     33         let claim_id = json["claim_id"]
     34             .as_str()
     35             .ok_or_else(|| Error::Http("claim_id not found".to_string()))?;
     36         let sd_hash = json["value"]["source"]["sd_hash"]
     37             .as_str()
     38             .ok_or_else(|| Error::Http("sd_hash not found".to_string()))?;
     39         // URL format: confirmed by inspecting the LBRY `get` API response.
     40         // CDN only checks Referer: https://odysee.com/ — no auth token required.
     41         let streaming_url = format!(
     42             "https://player.odycdn.com/v6/streams/{}/{}.mp4",
     43             claim_id, &sd_hash[..6]
     44         );
     45         let filename = json["value"]["source"]["name"]
     46             .as_str()
     47             .unwrap_or(&name)
     48             .to_string();
     49         Ok(Post { name, filename, streaming_url })
     50     }
     51 
     52     pub fn content_length(&self, tx: &std::sync::mpsc::Sender<Event>) -> Option<u64> {
     53         with_retry(
     54             || {
     55                 ureq::head(&self.streaming_url)
     56                     .header("Referer", "https://odysee.com/")
     57                     .header("Origin", "https://odysee.com")
     58                     .call()
     59             },
     60             || { tx.send(Event::RateLimited(self.name.clone())).ok(); },
     61         )
     62         .ok()
     63         .and_then(|r| {
     64             r.headers()
     65                 .get("content-length")?
     66                 .to_str()
     67                 .ok()?
     68                 .parse()
     69                 .ok()
     70         })
     71     }
     72 
     73     pub fn download(&self, dir: &std::path::Path, tx: &std::sync::mpsc::Sender<Event>) -> Result<(), Error> {
     74         let path = dir.join(&self.filename);
     75         let mut response = with_retry(
     76             || {
     77                 ureq::get(&self.streaming_url)
     78                     .header("Referer", "https://odysee.com/")
     79                     .header("Origin", "https://odysee.com")
     80                     .call()
     81             },
     82             || { tx.send(Event::RateLimited(self.name.clone())).ok(); },
     83         )?;
     84         let mut file = std::fs::File::create(&path)?;
     85         let mut reader = response.body_mut().as_reader();
     86         std::io::copy(&mut reader, &mut file)?;
     87         Ok(())
     88     }
     89 }