biliup/
lib.rs

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    // println!("body = {:?}", client);
63    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        // self.capacity = capacity;
75        // self.buffer = vec![0u8; capacity];
76        // self.buf = BytesMut::with_capacity(capacity);
77        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            // capacity: 10485760,
136            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}