use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use actix_web::{HttpRequest, HttpResponse, web};
use actix_web::http::StatusCode;
use minijinja::{context, Environment};
use serde::Deserialize;
use url::form_urlencoded;
use crate::{constant, squire};
use crate::routes;
#[derive(Deserialize)]
pub struct Payload {
file: String,
}
struct Subtitles {
srt: PathBuf,
vtt: PathBuf,
vtt_file: String,
}
fn url_encode(path: &String) -> String {
form_urlencoded::byte_serialize(path.as_bytes())
.collect::<Vec<_>>()
.join("")
}
fn subtitles(true_path: PathBuf, relative_path: &String) -> Subtitles {
let srt = true_path.with_extension("srt");
let vtt = true_path.with_extension("vtt");
let vtt_filepath = PathBuf::new().join(relative_path).with_extension("vtt");
let vtt_file = vtt_filepath.to_string_lossy().to_string();
Subtitles { srt, vtt, vtt_file }
}
#[get("/track")]
pub async fn track(config: web::Data<Arc<squire::settings::Config>>,
request: HttpRequest, info: web::Query<Payload>) -> HttpResponse {
let auth_response = squire::authenticator::verify_token(&request, &config);
if !auth_response.ok {
return routes::auth::failed_auth(auth_response, &config);
}
squire::logger::log_connection(&request);
log::debug!("{}", auth_response.detail);
log::debug!("Track requested: {}", &info.file);
let filepath = Path::new(&config.video_source).join(&info.file);
log::debug!("Track file lookup: {}", &filepath.to_string_lossy());
match std::fs::read_to_string(&filepath) {
Ok(content) => HttpResponse::Ok()
.content_type("text/plain")
.body(content),
Err(_) => HttpResponse::NotFound().json(routes::auth::DetailError {
detail: format!("'{}' was not found", &info.file)
})
}
}
#[get("/stream/{video_path:.*}")]
pub async fn stream(config: web::Data<Arc<squire::settings::Config>>,
environment: web::Data<Arc<Mutex<Environment<'static>>>>,
request: HttpRequest, video_path: web::Path<String>) -> HttpResponse {
let auth_response = squire::authenticator::verify_token(&request, &config);
if !auth_response.ok {
return routes::auth::failed_auth(auth_response, &config);
}
squire::logger::log_connection(&request);
log::debug!("{}", auth_response.detail);
let filepath = video_path.to_string();
let __target = config.video_source.join(&filepath);
if !__target.exists() {
return HttpResponse::NotFound().json(routes::auth::DetailError {
detail: format!("'{}' was not found", filepath)
});
}
let __target_str = __target.to_string_lossy().to_string();
let __filename = __target.file_name().unwrap().to_string_lossy().to_string();
let template = environment.lock().unwrap();
if __target.is_file() {
let landing = template.get_template("landing").unwrap();
let rust_iter = squire::content::get_iter(&__target, &config.file_formats);
let render_path = format!("/video?file={}", url_encode(&filepath));
let mut response_body = landing.render(context!(
video_title => &filepath, path => render_path,
previous => &rust_iter.previous,
next => &rust_iter.next,
previous_title => &rust_iter.previous,
next_title => &rust_iter.next,
)).unwrap();
let subtitle = subtitles(__target, &filepath);
if subtitle.vtt.exists() {
let sfx_file = format!("/track?file={}", url_encode(&subtitle.vtt_file));
response_body = landing.render(context!(
video_title => &filepath, path => render_path,
previous => &rust_iter.previous,
next => &rust_iter.next,
previous_title => &rust_iter.previous,
next_title => &rust_iter.next,
track => sfx_file
)).unwrap();
} else if subtitle.srt.exists() {
log::info!("Converting {:?} to {:?} for subtitles",
subtitle.srt.file_name().unwrap(),
subtitle.vtt.file_name().unwrap());
match squire::subtitles::srt_to_vtt(&subtitle.srt) {
Ok(_) => {
log::debug!("Successfully converted srt to vtt file");
let sfx_file = format!("/track?file={}", url_encode(&subtitle.vtt_file));
response_body = landing.render(context!(
video_title => &filepath, path => render_path,
previous => &rust_iter.previous,
next => &rust_iter.next,
previous_title => &rust_iter.previous,
next_title => &rust_iter.next,
track => sfx_file
)).unwrap();
}
Err(err) => log::error!("Failed to convert srt to vtt: {}", err),
}
}
return HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8").body(response_body);
} else if __target.is_dir() {
let child_dir = __target.iter().last().unwrap().to_string_lossy().to_string();
let listing_page = squire::content::get_dir_stream_content(&__target_str, &child_dir, &config.file_formats);
let listing = template.get_template("listing").unwrap();
return HttpResponse::build(StatusCode::OK)
.content_type("text/html; charset=utf-8")
.body(listing.render(context!(
files => listing_page.files, directories => listing_page.directories)
).unwrap());
}
log::error!("Something went really wrong");
log::error!("Video Path: {}", filepath);
log::error!("Target: {}", __target_str);
HttpResponse::ExpectationFailed().json(routes::auth::DetailError {
detail: format!("'{}' was neither a file nor a folder", filepath)
})
}
#[get("/video")]
pub async fn streaming_endpoint(config: web::Data<Arc<squire::settings::Config>>,
request: HttpRequest, info: web::Query<Payload>) -> HttpResponse {
let auth_response = squire::authenticator::verify_token(&request, &config);
if !auth_response.ok {
return routes::auth::failed_auth(auth_response, &config);
}
squire::logger::log_connection(&request);
let host = request.connection_info().host().to_owned();
let video_path = config.video_source.join(&info.file);
if video_path.exists() {
let file = actix_files::NamedFile::open_async(video_path).await.unwrap();
let mut tracker = constant::HOST_SERVE.lock().unwrap();
if tracker.get(&host).unwrap() != &info.file {
log::info!("Streaming {}", info.file);
tracker.insert(request.connection_info().host().to_string(), info.file.to_string());
}
return file.into_response(&request);
}
let error = format!("File {:?} not found", video_path);
log::error!("{}", error);
HttpResponse::NotFound().body(error)
}