1use crate::video::{Studio, Video};
2use bytes::Bytes;
3use futures::{io, Stream};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::io::{ErrorKind, Read};
7use std::ops::DerefMut;
8use std::path::Path;
9use std::pin::Pin;
10use std::task::{Context, Poll};
11
12pub mod client;
13pub mod error;
14pub mod line;
15pub mod video;
16
17pub mod uploader {
18 use serde::{Deserialize, Serialize};
19 pub mod cos;
20 pub mod kodo;
21 pub mod retryable;
22 pub mod upos;
23
24 #[derive(Deserialize, Serialize, Debug)]
25 #[serde(rename_all = "lowercase")]
26 pub enum Uploader {
27 Upos,
28 Kodo,
29 Bos,
30 Gcs,
31 Cos,
32 }
33}
34
35#[derive(Debug, PartialEq, Serialize, Deserialize)]
36pub struct User {
37 pub account: Account,
38}
39
40#[derive(Debug, PartialEq, Serialize, Deserialize)]
41pub struct Account {
42 pub username: String,
43 pub password: String,
44}
45
46#[derive(Debug, Serialize, Deserialize)]
47pub struct Config {
48 pub user: Option<User>,
49 pub line: Option<String>,
50 #[serde(default = "default_limit")]
51 pub limit: usize,
52 pub streamers: HashMap<String, Studio>,
53}
54
55fn default_limit() -> usize {
56 3
57}
58
59pub fn load_config(config: &Path) -> error::Result<Config> {
60 let file = std::fs::File::open(config)?;
61 let config: Config = serde_yaml::from_reader(file)?;
62 Ok(config)
64}
65
66pub struct VideoStream {
67 pub capacity: usize,
68 buffer: Vec<u8>,
69 pub file: std::fs::File,
70}
71
72impl VideoStream {
73 pub fn with_capacity(file: std::fs::File, capacity: usize) -> Self {
74 VideoStream {
78 capacity,
79 buffer: vec![0u8; capacity],
80 file,
81 }
82 }
83
84 pub fn read(&mut self) -> io::Result<Option<Bytes>> {
85 let mut len = 0;
86 let mut buf = self.buffer.deref_mut();
87 while !buf.is_empty() {
88 match self.file.read(buf) {
89 Ok(0) => break,
90 Ok(n) => {
91 let tmp = buf;
92 len += n;
93 buf = &mut tmp[n..];
94 }
95 Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
96 Err(e) => return Err(e),
97 }
98 }
99 if len == 0 {
100 Ok(None)
101 } else {
102 Ok(Some(Bytes::copy_from_slice(&self.buffer[..len])))
103 }
104 }
105}
106
107impl Stream for VideoStream {
108 type Item = io::Result<Bytes>;
109
110 fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
111 match self.read()? {
112 None => Poll::Ready(None),
113 Some(b) => Poll::Ready(Some(Ok(b))),
114 }
115 }
116}
117
118pub struct VideoFile {
119 pub total_size: u64,
120 pub file_name: String,
121 pub filepath: std::path::PathBuf,
122 pub file: std::fs::File,
123}
124
125impl VideoFile {
126 pub fn new(filepath: &std::path::Path) -> io::Result<Self> {
127 let file = std::fs::File::open(&filepath)?;
128 let total_size = file.metadata()?.len();
129 let file_name = filepath
130 .file_name()
131 .and_then(|file_name| file_name.to_str())
132 .ok_or_else(|| io::Error::new(ErrorKind::NotFound, "the path terminates in .."))?;
133 Ok(Self {
134 file,
135 total_size,
137 file_name: file_name.into(),
138 filepath: filepath.into(),
139 })
140 }
141
142 pub fn get_stream(&self, capacity: usize) -> io::Result<VideoStream> {
143 Ok(VideoStream::with_capacity(self.file.try_clone()?, capacity))
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use crate::video::Vid;
150
151 use std::str::FromStr;
152
153 #[tokio::test]
154 async fn it_works() {
155 assert_eq!(Ok(Vid::Aid(971158452)), Vid::from_str("971158452"));
156 assert_eq!(Ok(Vid::Aid(971158452)), Vid::from_str("av971158452"));
157 assert_eq!(
158 Ok(Vid::Bvid("BV1ip4y1x7Gi".into())),
159 Vid::from_str("BV1ip4y1x7Gi")
160 );
161 }
162
163 #[test]
164 fn try_clone_stream() {
165 let chunks: Vec<Result<_, ::std::io::Error>> = vec![Ok("hello"), Ok(" "), Ok("world")];
166 let stream = futures::stream::iter(chunks);
167 let client = reqwest::Client::new();
168 retry(|| {
169 let builder = client
170 .get("http://httpbin.org/get")
171 .body(reqwest::Body::wrap_stream(stream));
172 let clone = builder.try_clone();
173 assert!(clone.is_none());
174 })
175 }
176
177 fn retry(f: impl FnOnce()) {
178 f()
179 }
180}