Skip to content

Commit

Permalink
Merge pull request #652 from Stremio/feat/stream-source-rar-and-zip
Browse files Browse the repository at this point in the history
feat(`StreamSource`): `Rar` & `Zip` urls and Torrent `fileMustInclude`
  • Loading branch information
elpiel authored Mar 25, 2024
2 parents 542bfb1 + e54f623 commit 44679c0
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 14 deletions.
178 changes: 169 additions & 9 deletions src/types/resource/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,23 @@ impl Stream {
behavior_hints: Default::default(),
})
}

pub fn download_url(&self) -> Option<String> {
match &self.source {
StreamSource::Url { url } if url.scheme() == "magnet" => {
self.magnet_url().map(|magnet_url| magnet_url.to_string())
}
StreamSource::Url { url } => Some(url.to_string()),
StreamSource::Rar {
rar_urls: _,
file_idx: _,
file_must_include: _,
} => None,
StreamSource::Zip {
zip_urls: _,
file_idx: _,
file_must_include: _,
} => None,
StreamSource::Torrent { .. } => {
self.magnet_url().map(|magnet_url| magnet_url.to_string())
}
Expand All @@ -126,6 +137,7 @@ impl Stream {
StreamSource::PlayerFrame { player_frame_url } => Some(player_frame_url.to_string()),
}
}

pub fn m3u_data_uri(&self, streaming_server_url: Option<&Url>) -> Option<String> {
self.streaming_url(streaming_server_url).map(|url| {
format!(
Expand All @@ -134,6 +146,7 @@ impl Stream {
)
})
}

pub fn streaming_url(&self, streaming_server_url: Option<&Url>) -> Option<String> {
match (&self.source, streaming_server_url) {
(StreamSource::Url { url }, streaming_server_url) if url.scheme() != "magnet" => {
Expand Down Expand Up @@ -175,27 +188,39 @@ impl Stream {
info_hash,
file_idx,
announce,
file_must_include,
},
Some(streaming_server_url),
) => {
let mut url = streaming_server_url.to_owned();
match url.path_segments_mut() {
Ok(mut path) => {
path.push(&hex::encode(info_hash));
// When fileIndex is not provided use -1, which will tell the
// streaming server to choose the file with the largest size from the torrent
path.push(
path.extend([
&hex::encode(info_hash),
// When fileIndex is not provided use -1, which will tell the
// streaming server to choose the file with the largest size from the torrent
&file_idx.map_or_else(|| "-1".to_string(), |idx| idx.to_string()),
);
]);
}
_ => return None,
};
}

let mut query = vec![];
if !announce.is_empty() {
let mut query = url.query_pairs_mut();
query.extend_pairs(announce.iter().map(|tracker| ("tr", tracker)));
};
query.extend(announce.iter().map(|tracker| ("tr", tracker.to_owned())));
}

if !file_must_include.is_empty() {
let json_string = serde_json::to_value(file_must_include).ok()?.to_string();
query.push(("f", json_string));
}

url.query_pairs_mut().extend_pairs(query);

Some(url.to_string())
}
(StreamSource::Zip { .. }, Some(_streaming_server_url)) => None,
(StreamSource::Rar { .. }, Some(_streaming_server_url)) => None,
(StreamSource::YouTube { yt_id }, Some(streaming_server_url)) => {
let mut url = streaming_server_url.to_owned();
match url.path_segments_mut() {
Expand Down Expand Up @@ -233,6 +258,122 @@ impl Stream {
}
}

///
/// # Examples
///
/// Stream source Url
///
/// [`StreamSource::Rar`] with `rarUrls` field:
///
/// ```
/// use stremio_core::types::resource::StreamSource;
///
/// let streams_json = serde_json::json!([
/// {
/// "rarUrls": ["https://example-source.com/file.rar", "https://example-source2.com/file2.rar"],
/// // ...Stream
/// },
/// {
/// "rarUrls": ["https://example-source3.com/file.rar", "https://example-source4.com/file2.rar"],
/// "fileIdx": 1,
/// "fileMustInclude": ["includeFile1"],
/// // ...Stream
/// },
/// {
/// "rarUrls": ["https://example-source5.com/file.rar", "https://example-source6.com/file2.rar"],
/// "fileMustInclude": ["includeFile2"],
/// // ...Stream
/// },
/// {
/// "rarUrls": ["https://example-source7.com/file.rar", "https://example-source8.com/file2.rar"],
/// "fileIdx": 2,
/// // ...Stream
/// }
/// ]);
///
/// let expected = vec![
/// StreamSource::Rar {
/// rar_urls: vec!["https://example-source.com/file.rar".parse().unwrap(), "https://example-source2.com/file2.rar".parse().unwrap()],
/// file_idx: None,
/// file_must_include: vec![],
/// },
/// StreamSource::Rar {
/// rar_urls: vec!["https://example-source3.com/file.rar".parse().unwrap(), "https://example-source4.com/file2.rar".parse().unwrap()],
/// file_idx: Some(1),
/// file_must_include: vec!["includeFile1".into()]
/// },
/// StreamSource::Rar {
/// rar_urls: vec!["https://example-source5.com/file.rar".parse().unwrap(), "https://example-source6.com/file2.rar".parse().unwrap()],
/// file_idx: None,
/// file_must_include: vec!["includeFile2".into()]
/// },
/// StreamSource::Rar {
/// rar_urls: vec!["https://example-source7.com/file.rar".parse().unwrap(), "https://example-source8.com/file2.rar".parse().unwrap()],
/// file_idx: Some(2),
/// file_must_include: vec![],
/// },
/// ];
///
/// let streams: Vec<StreamSource> = serde_json::from_value(streams_json).expect("Deserialize all StreamSources");
///
/// pretty_assertions::assert_eq!(streams, expected);
/// ```
///
/// [`StreamSource::Zip`] with `zipUrls` field:
///
/// ```
/// use stremio_core::types::resource::StreamSource;
///
/// let streams_json = serde_json::json!([
/// {
/// "zipUrls": ["https://example-source.com/file.rar", "https://example-source2.com/file2.rar"],
/// // ...Stream
/// },
/// {
/// "zipUrls": ["https://example-source3.com/file.rar", "https://example-source4.com/file2.rar"],
/// "fileIdx": 1,
/// "fileMustInclude": ["includeFile1"],
/// // ...Stream
/// },
/// {
/// "zipUrls": ["https://example-source5.com/file.rar", "https://example-source6.com/file2.rar"],
/// "fileMustInclude": ["includeFile2"],
/// // ...Stream
/// },
/// {
/// "zipUrls": ["https://example-source7.com/file.rar", "https://example-source8.com/file2.rar"],
/// "fileIdx": 2,
/// // ...Stream
/// }
/// ]);
///
/// let expected = vec![
/// StreamSource::Zip {
/// zip_urls: vec!["https://example-source.com/file.rar".parse().unwrap(), "https://example-source2.com/file2.rar".parse().unwrap()],
/// file_idx: None,
/// file_must_include: vec![],
/// },
/// StreamSource::Zip {
/// zip_urls: vec!["https://example-source3.com/file.rar".parse().unwrap(), "https://example-source4.com/file2.rar".parse().unwrap()],
/// file_idx: Some(1),
/// file_must_include: vec!["includeFile1".into()],
/// },
/// StreamSource::Zip {
/// zip_urls: vec!["https://example-source5.com/file.rar".parse().unwrap(), "https://example-source6.com/file2.rar".parse().unwrap()],
/// file_idx: None,
/// file_must_include: vec!["includeFile2".into()],
/// },
/// StreamSource::Zip {
/// zip_urls: vec!["https://example-source7.com/file.rar".parse().unwrap(), "https://example-source8.com/file2.rar".parse().unwrap()],
/// file_idx: Some(2),
/// file_must_include: vec![],
/// },
/// ];
///
/// let streams: Vec<StreamSource> = serde_json::from_value(streams_json).expect("Deserialize all StreamSources");
///
/// pretty_assertions::assert_eq!(streams, expected);
/// ```
#[serde_as]
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
#[cfg_attr(test, derive(Derivative))]
Expand All @@ -248,13 +389,32 @@ pub enum StreamSource {
yt_id: String,
},
#[serde(rename_all = "camelCase")]
Rar {
rar_urls: Vec<Url>,
#[serde(default)]
file_idx: Option<u16>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
file_must_include: Vec<String>,
},
#[serde(rename_all = "camelCase")]
Zip {
zip_urls: Vec<Url>,
#[serde(default)]
file_idx: Option<u16>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
file_must_include: Vec<String>,
},
#[serde(rename_all = "camelCase")]
Torrent {
#[serde(with = "SerHex::<Strict>")]
info_hash: [u8; 20],
file_idx: Option<u16>,
#[serde_as(deserialize_as = "DefaultOnNull")]
#[serde(default, alias = "sources")]
announce: Vec<String>,
#[serde_as(deserialize_as = "DefaultOnNull")]
#[serde(default, skip_serializing_if = "Vec::is_empty")]
file_must_include: Vec<String>,
},
#[serde(rename_all = "camelCase")]
PlayerFrame {
Expand Down
1 change: 1 addition & 0 deletions src/unit_tests/deep_links/external_player_link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ fn external_player_link_torrent() {
info_hash,
file_idx: Some(file_idx),
announce,
file_must_include: vec![],
},
name: None,
description: None,
Expand Down
1 change: 1 addition & 0 deletions src/unit_tests/deep_links/library_item_deep_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const TORRENT_STREAMS_ITEM: Lazy<StreamsItem> = Lazy::new(|| {
.unwrap(),
file_idx: Some(0),
announce: vec![],
file_must_include: vec![],
},
name: Some("PaidTV".to_owned()),
description: Some("Ahsoka S01E05 Part Five 1080p".to_owned()),
Expand Down
2 changes: 2 additions & 0 deletions src/unit_tests/deep_links/stream_deep_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ fn stream_deep_links_torrent() {
info_hash,
file_idx: Some(file_idx),
announce,
file_must_include: vec![],
},
name: None,
description: None,
Expand Down Expand Up @@ -218,6 +219,7 @@ fn stream_deep_links_torrent_without_file_index() {
info_hash,
file_idx: None,
announce,
file_must_include: vec![],
},
name: None,
description: None,
Expand Down
Loading

0 comments on commit 44679c0

Please sign in to comment.