1use crate::{renditions::RenditionsPage, MediaPage};
2use reqwest::header::{ACCEPT, AUTHORIZATION};
3use std::{cmp::min, io::Cursor};
4use tokio::{fs::File, io, sync::mpsc};
5use tokio_stream::Stream;
6use urlencoding::encode;
7
8#[derive(Default, Debug, Clone, PartialEq)]
10pub struct Client {
11 token: String,
12 site_id: String,
13}
14
15impl Client {
16 pub fn new(token: &str, site_id: &str) -> Self {
18 Self {
19 token: token.to_string(),
20 site_id: site_id.to_string(),
21 }
22 }
23
24 pub async fn get_renditions(&self, media_id: &str) -> Result<Option<RenditionsPage>, String> {
26 let endpoint = format!(
27 "https://api.jwplayer.com/v2/sites/{}/media/{}/media_renditions/",
28 self.site_id, media_id
29 );
30 let client = reqwest::Client::new();
31 let response = client
32 .get(endpoint)
33 .header(ACCEPT, "application/json")
34 .header(AUTHORIZATION, &self.token)
35 .send()
36 .await;
37 match response {
38 Ok(data) => {
39 let str_data = data.text().await.expect("Error getting API response");
40 let media_renditions: RenditionsPage = serde_json::from_str(&str_data.as_str())
41 .expect("Error deserializing JSON response");
42 Ok(Some(media_renditions))
43 }
44 Err(_) => Err("Error getting video renditions".to_string()),
45 }
46 }
47
48 pub async fn get_library_excluding_tags(
52 &self,
53 tags: Vec<&str>,
54 chunk_size: Option<i32>,
55 ) -> Result<Option<MediaPage>, reqwest::Error> {
56 let query = match tags.len() {
57 0 => String::new(),
58 1 => format!("tags: NOT ({})", tags[0]),
59 _ => format!("tags: NOT ({})", tags.join(" OR ")),
60 };
61 let page_length = min(chunk_size.unwrap_or(10), 10000).clone();
62 println!("{}", query);
63 let endpoint = format!(
64 "https://api.jwplayer.com/v2/sites/{}/media/?&page_length={}&q={}&sort=created:dsc",
65 self.site_id, page_length, query
66 );
67 let client = reqwest::Client::new();
68 let response = client
69 .get(endpoint)
70 .header(ACCEPT, "application/json")
71 .header(AUTHORIZATION, &self.token)
72 .send()
73 .await;
74
75 match response {
76 Ok(data) => {
77 let str_data = data.text().await.expect("Error getting API response");
78 let media: MediaPage = serde_json::from_str(&str_data.as_str())
79 .expect("Error deserializing JSON response");
80 Ok(Some(media))
81 }
82 Err(e) => Err(e),
83 }
84 }
85
86 pub async fn get_library_by_tags(
90 &self,
91 tags: Vec<&str>,
92 chunk_size: Option<i32>,
93 ) -> Result<Option<MediaPage>, reqwest::Error> {
94 let query = match tags.len() {
95 0 => String::new(),
96 1 => format!("tags: ({})", tags[0]),
97 _ => format!("tags: ({})", tags.join(" OR ")),
98 };
99 let page_length = min(chunk_size.unwrap_or(10), 10000).clone();
100
101 let endpoint = format!(
102 "https://api.jwplayer.com/v2/sites/{}/media/?&page_length={}&q={}&sort=created:dsc",
103 self.site_id, page_length, query
104 );
105 let client = reqwest::Client::new();
106 let response = client
107 .get(endpoint)
108 .header(ACCEPT, "application/json")
109 .header(AUTHORIZATION, &self.token)
110 .send()
111 .await;
112
113 match response {
114 Ok(data) => {
115 let str_data = data.text().await.expect("Error getting API response");
116 let media: MediaPage = serde_json::from_str(&str_data.as_str())
117 .expect("Error deserializing JSON response");
118 Ok(Some(media))
119 }
120 Err(e) => Err(e),
121 }
122 }
123
124 pub async fn get_library(
128 &self,
129 chunk_size: Option<i32>,
130 ) -> Result<Option<MediaPage>, reqwest::Error> {
131 let page_length = min(chunk_size.unwrap_or(10), 10000).clone();
132
133 let endpoint = format!(
134 "https://api.jwplayer.com/v2/sites/{}/media/?page_length={}&sort=created:dsc",
135 self.site_id, page_length
136 );
137 let client = reqwest::Client::new();
138 let response = client
139 .get(endpoint)
140 .header(ACCEPT, "application/json")
141 .header(AUTHORIZATION, &self.token)
142 .send()
143 .await;
144
145 match response {
146 Ok(data) => {
147 let str_data = data.text().await.expect("Error getting API response");
148 let media: MediaPage = serde_json::from_str(&str_data.as_str())
149 .expect("Error deserializing JSON response");
150 Ok(Some(media))
151 }
152 Err(e) => Err(e),
153 }
154 }
155
156 pub async fn get_library_stream(
162 &self,
163 chunk_size: Option<i32>,
164 query: Option<String>,
165 ) -> impl Stream<Item = Result<MediaPage, reqwest::Error>> {
166 let (tx, rx) = mpsc::channel(10);
167
168 tokio::spawn({
169 let this = self.clone();
170 let page_length = min(chunk_size.unwrap_or(10), 10000).clone();
171 async move {
172 let mut start_date = "2000-01-01".to_string();
173 let end_date = "3000-01-01".to_string();
174 loop {
175 match this
176 .stream_helper(&start_date, &end_date, &page_length, query.clone())
177 .await
178 {
179 Ok(Some(media_list)) if !media_list.media.is_empty() => {
180 if tx.send(Ok(media_list.clone())).await.is_err() {
181 break;
182 }
183 if let Some(last_item) = media_list.media.last() {
184 let new_start_date = encode(&last_item.created[..19]).to_string();
185
186 if new_start_date == start_date
187 || media_list.total < media_list.page_length
188 {
189 break;
190 }
191 start_date = new_start_date;
192 }
193 }
194 Ok(_) => {
195 break;
196 }
197 Err(e) => {
198 let _ = tx.send(Err(e)).await;
199 break;
200 }
201 }
202 }
203 }
204 });
205
206 tokio_stream::wrappers::ReceiverStream::new(rx)
207 }
208
209 async fn stream_helper(
211 &self,
212 start_date: &str,
213 end_date: &str,
214 page_length: &i32,
215 query: Option<String>,
216 ) -> Result<Option<MediaPage>, reqwest::Error> {
217 let full_query = match query {
218 Some(q) => {
219 format!("( created:[{} TO {}] ) AND ( {} )", start_date, end_date, q)
220 }
221 None => {
222 format!("created:[{} TO {}]", start_date, end_date)
223 }
224 };
225 let endpoint = format!(
226 "https://api.jwplayer.com/v2/sites/{}/media/?page_length={}&q={}&sort=created:asc",
227 self.site_id, page_length, full_query
228 );
229 let client = reqwest::Client::new();
230 let response = client
231 .get(endpoint)
232 .header(ACCEPT, "application/json")
233 .header(AUTHORIZATION, &self.token)
234 .send()
235 .await;
236
237 match response {
238 Ok(data) => {
239 let str_data = data.text().await.expect("Error getting API response");
240 let media: MediaPage = serde_json::from_str(&str_data.as_str())
241 .expect("Error deserializing JSON response");
242 Ok(Some(media))
243 }
244 Err(e) => Err(e),
245 }
246 }
247
248 pub async fn download(&self, media_id: &str, path: &str) {
250 let renditions = self.get_renditions(media_id).await;
251 if let Ok(Some(data)) = renditions {
252 let url = data
253 .media_renditions
254 .last()
255 .expect("No rendition entries in the media")
256 .delivery_url
257 .as_str();
258 let resp = reqwest::get(url).await.expect("URL Request Failed");
259 let body = resp.bytes().await.expect("Invalid Video Data");
260
261 let mut body_reader = Cursor::new(body);
263
264 let path_str = format!("{}/{}.mp4", path, data.media_renditions[0].id);
265 let mut out = File::create(&path_str)
266 .await
267 .expect("Failed to Create File");
268
269 io::copy(&mut body_reader, &mut out)
271 .await
272 .expect("Failed to Download Video");
273 }
274 }
275
276 pub async fn delete_meida(&self, media_id: &str) -> Result<(), String> {
278 let endpoint = format!(
279 "https://api.jwplayer.com/v2/sites/{}/media/{}/",
280 self.site_id, media_id
281 );
282 let client = reqwest::Client::new();
283 let response = client
284 .delete(endpoint)
285 .header(ACCEPT, "application/json")
286 .header(AUTHORIZATION, &self.token)
287 .send()
288 .await;
289 match response {
290 Ok(_) => Ok(()),
291 Err(_) => Err("Error deleting video".to_string()),
292 }
293 }
294}