static_video_server/
lib.rs

1use clap::Parser;
2use std::{
3    collections::{HashMap, HashSet},
4    path::PathBuf,
5    sync::{
6        atomic::{AtomicUsize, Ordering},
7        Arc, Mutex,
8    },
9};
10use tracing::log::info;
11use lazy_static::lazy_static;
12
13lazy_static! {
14    pub static ref VIDEO_EXTENSIONS: Vec<String> = vec![
15        "mp4".into(),
16        "avi".into(),
17        "flv".into(),
18        "heic".into(),
19        "mkv".into(),
20        "mov".into(),
21        "mpg".into(),
22        "mpeg".into(),
23        "m4v".into(),
24        "webm".into(),
25        "wmv".into(),
26        "3gp".into()
27    ];
28}
29
30
31
32/// The configuration for the video server.
33#[derive(Parser, Debug, Clone)]
34pub struct VideoPlayerConfig {
35    #[clap(short, long, default_value = "assets")]
36    pub assets_root: String,
37
38    #[clap(short, long, default_value = "9092")]
39    pub port: u16,
40
41    #[clap(short, long, default_value = "0.0.0.0")]
42    pub host: String,
43}
44
45/// The video index state that is shared between all requests.
46/// Store a list of videos and their paths.
47#[derive(Default)]
48pub struct VideoPlayerState {
49    pub videos: HashMap<String, String>,
50    video_extensions: HashSet<String>,
51    next_index: AtomicUsize,
52    root: Option<String>,
53}
54
55pub type SharedState = Arc<Mutex<VideoPlayerState>>;
56
57impl VideoPlayerState {
58    /// Create a new video index state.
59    /// This will configure the video extensions that are interpreted as videos.
60    pub fn new() -> Self {
61        Self {
62            video_extensions: HashSet::from_iter(
63                VIDEO_EXTENSIONS.iter().map(|s| s.to_string()),
64            ),
65            ..Default::default()
66        }
67    }
68
69
70    fn advance_index(&mut self) {
71        self.next_index
72            .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
73    }
74
75    /// Check if a path is a supported video file.
76    pub fn is_video_file<P: AsRef<std::path::Path>>(&self, path: P) -> bool {
77        if let Some(extension) = path.as_ref().extension() {
78            if self.video_extensions.contains(extension.to_str().unwrap()) {
79                return true;
80            }
81        }
82        false
83    }
84
85    pub fn load_videos<P: AsRef<std::path::Path>>(&mut self, root: P) -> std::io::Result<()> {
86        self.visit_dirs(root)
87    }
88
89    /// Load a video from a path.
90    pub fn load_video(&mut self, path: PathBuf) {
91        let stored_file_name = path.to_str().unwrap().to_string();
92        let extension = path.extension().unwrap();
93        let server_path = format!(
94            "{}.{}",
95            self.next_index.load(Ordering::SeqCst),
96            extension.to_str().unwrap()
97        );
98        info!("Loading video: {} as {}", stored_file_name, server_path);
99        self.advance_index();
100        self.videos.insert(server_path, stored_file_name);
101    }
102
103    /// Recursively visit all directories and load videos from them.
104    pub fn visit_dirs<P: AsRef<std::path::Path>>(&mut self, root: P) -> std::io::Result<()> {
105        if root.as_ref().is_dir() {
106            if let Ok(dir) = std::fs::read_dir(root.as_ref()) {
107                for entry in dir {
108                    let entry = entry?;
109                    let path = entry.path();
110                    if path.is_dir() {
111                        self.visit_dirs(path)?;
112                    } else if self.is_video_file(path.as_path()) {
113                        self.load_video(path);
114                    }
115                }
116            }
117        }
118        Ok(())
119    }
120
121    /// Build a new video index state from a config.
122    pub fn build(config: &VideoPlayerConfig) -> Self {
123        let mut state = Self::new();
124        state.root = Some(config.assets_root.clone());
125        state.load_videos(state.root.clone().unwrap()).unwrap();
126        state
127    }
128
129    /// Reload the video index state.
130    pub fn reload(&mut self) {
131        self.next_index = AtomicUsize::new(0);
132        self.videos.clear();
133        self.load_videos(self.root.clone().unwrap()).unwrap();
134    }
135}