|
@@ -3,26 +3,26 @@ use axum::{
|
|
|
routing::{get, get_service},
|
|
|
Router,
|
|
|
};
|
|
|
+use ffmpeg::VideoStream;
|
|
|
use minijinja::Environment;
|
|
|
use ost::client::OSClient;
|
|
|
use serde::Serialize;
|
|
|
use std::{
|
|
|
fs::{self, DirEntry},
|
|
|
+ num::NonZeroUsize,
|
|
|
path::Path,
|
|
|
thread,
|
|
|
};
|
|
|
use tower_http::services::ServeDir;
|
|
|
use tracing::{debug, info, Level};
|
|
|
|
|
|
-use crate::ffmpeg::{Codec, FFProbeOutput, SubtitleMeta, VideoMeta};
|
|
|
+use crate::ffmpeg::{FFProbeOutput, SubtitleStream};
|
|
|
|
|
|
pub mod error;
|
|
|
pub mod ffmpeg;
|
|
|
pub mod ost;
|
|
|
pub mod routes;
|
|
|
|
|
|
-const MAX_THREADS: usize = 4;
|
|
|
-
|
|
|
#[derive(Debug, Clone)]
|
|
|
pub struct State {
|
|
|
client: OSClient,
|
|
@@ -40,6 +40,10 @@ lazy_static::lazy_static! {
|
|
|
std::fs::read_to_string("assets/filecontainer.html").expect("missing template");
|
|
|
}
|
|
|
|
|
|
+lazy_static::lazy_static! {
|
|
|
+ pub static ref MAX_THREADS: usize = std::thread::available_parallelism().unwrap_or(NonZeroUsize::new(1).unwrap()).into();
|
|
|
+}
|
|
|
+
|
|
|
#[tokio::main]
|
|
|
async fn main() {
|
|
|
dotenv::dotenv().expect("could not load env");
|
|
@@ -48,9 +52,13 @@ async fn main() {
|
|
|
.with_max_level(Level::DEBUG)
|
|
|
.init();
|
|
|
|
|
|
+ info!("Starting ntitled");
|
|
|
+
|
|
|
+ info!("Max threads: {}", *MAX_THREADS);
|
|
|
+
|
|
|
let client = OSClient::init().await;
|
|
|
|
|
|
- info!("Successfully OST loaded client");
|
|
|
+ info!("Successfully loaded OST client");
|
|
|
|
|
|
let base_path = std::env::var("BASE_PATH").expect("base path not configured");
|
|
|
let port = std::env::var("PORT").unwrap_or(String::from("3001"));
|
|
@@ -108,9 +116,9 @@ pub fn scan_dir_contents(path: impl AsRef<Path>) -> std::io::Result<Vec<FileType
|
|
|
let mut files = thread::scope(|scope| {
|
|
|
let mut handles = vec![];
|
|
|
|
|
|
- let mut acc = Vec::with_capacity(MAX_THREADS);
|
|
|
+ let mut acc = Vec::with_capacity(*MAX_THREADS);
|
|
|
|
|
|
- for _ in 0..MAX_THREADS {
|
|
|
+ for _ in 0..*MAX_THREADS {
|
|
|
acc.push(vec![]);
|
|
|
}
|
|
|
|
|
@@ -118,18 +126,16 @@ pub fn scan_dir_contents(path: impl AsRef<Path>) -> std::io::Result<Vec<FileType
|
|
|
.into_iter()
|
|
|
.enumerate()
|
|
|
.fold(acc, |mut acc, (i, el)| {
|
|
|
- acc[i % MAX_THREADS].push(el);
|
|
|
+ acc[i % *MAX_THREADS].push(el);
|
|
|
acc
|
|
|
});
|
|
|
|
|
|
- let subs = &subs;
|
|
|
-
|
|
|
for entries in jobs {
|
|
|
let task = scope.spawn(|| {
|
|
|
let mut files = vec![];
|
|
|
|
|
|
for entry in entries {
|
|
|
- if let Some(file) = extract_entry(subs, &entry) {
|
|
|
+ if let Some(file) = extract_entry(&subs, &entry) {
|
|
|
files.push(file)
|
|
|
}
|
|
|
}
|
|
@@ -179,38 +185,36 @@ fn extract_entry(subs: &[String], entry: &DirEntry) -> Option<FileType> {
|
|
|
|
|
|
let path_str = path.to_str()?;
|
|
|
|
|
|
- let codecs = Codec::probe(path_str).ok()?;
|
|
|
-
|
|
|
- if codecs.is_none() || codecs.is_some_and(|codec| codec.is_empty()) {
|
|
|
- return None;
|
|
|
- }
|
|
|
-
|
|
|
- let video_meta = match FFProbeOutput::<VideoMeta>::probe(path_str) {
|
|
|
- Ok(video_meta) => video_meta,
|
|
|
+ let out = match FFProbeOutput::probe(path_str) {
|
|
|
+ Ok(streams) => streams,
|
|
|
Err(e) => {
|
|
|
debug!("File {path_str} is not a video: {e}");
|
|
|
return None;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- let Some(video_meta) = video_meta.streams else {
|
|
|
+ let Some(streams) = out.streams else {
|
|
|
return None;
|
|
|
};
|
|
|
|
|
|
- let sub_meta = match FFProbeOutput::<SubtitleMeta>::probe(path_str) {
|
|
|
- Ok(meta) => meta.streams,
|
|
|
- Err(e) => {
|
|
|
- debug!("File {path_str} has no subs: {e}");
|
|
|
- None
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
let ext = path.extension()?.to_str()?;
|
|
|
|
|
|
let (name, _) = path.file_name()?.to_str()?.split_once(ext)?;
|
|
|
|
|
|
let subs_file = subs.contains(&name.to_string());
|
|
|
- let subs_embedded = sub_meta.as_ref().is_some_and(|s| !s.is_empty());
|
|
|
+
|
|
|
+ let mut video_meta = vec![];
|
|
|
+ let mut sub_meta = vec![];
|
|
|
+
|
|
|
+ for stream in streams {
|
|
|
+ match stream {
|
|
|
+ ffmpeg::Stream::Video(s) => video_meta.push(s),
|
|
|
+ ffmpeg::Stream::Sub(s) => sub_meta.push(s),
|
|
|
+ ffmpeg::Stream::Audio(s) => {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ let subs_embedded = !sub_meta.is_empty();
|
|
|
|
|
|
debug!("File: {name}; Subs: {}", subs_embedded || subs_file);
|
|
|
|
|
@@ -236,9 +240,9 @@ pub struct File {
|
|
|
name: String,
|
|
|
path: String,
|
|
|
|
|
|
- video_meta: Vec<VideoMeta>,
|
|
|
+ video_meta: Vec<VideoStream>,
|
|
|
|
|
|
- sub_meta: Option<Vec<SubtitleMeta>>,
|
|
|
+ sub_meta: Vec<SubtitleStream>,
|
|
|
|
|
|
/// `true` if a same file exists with a `.srt` extension found
|
|
|
/// in the same directory
|