mirror of
https://codeberg.org/simonrepp/hyper8.git
synced 2026-08-14 13:45:27 +02:00
Introduce playlist banner processing, improve in-memory cache state integrity
This commit is contained in:
+10
-6
@@ -126,10 +126,7 @@ impl Context {
|
||||
for job in jobs {
|
||||
if !job_queue
|
||||
.iter()
|
||||
.any(|existing_job|
|
||||
existing_job.kind == job.kind &&
|
||||
Arc::ptr_eq(&existing_job.video, &job.video)
|
||||
) {
|
||||
.any(|existing_job| existing_job.kind == job.kind) {
|
||||
job_queue.push(job);
|
||||
}
|
||||
}
|
||||
@@ -362,6 +359,12 @@ impl Context {
|
||||
}
|
||||
|
||||
fn queue_playlist_jobs(&self, playlist: &Arc<Playlist>, queue: &mut Vec<Job>) {
|
||||
if playlist.banner.as_ref().is_some_and(|banner| banner.assets.is_none()) {
|
||||
let job_kind = JobKind::PlaylistFastTasks(Arc::clone(playlist));
|
||||
let job = Job::new(job_kind);
|
||||
queue.push(job);
|
||||
}
|
||||
|
||||
for video in &playlist.videos {
|
||||
self.queue_video_jobs(video, queue);
|
||||
}
|
||||
@@ -411,7 +414,8 @@ impl Context {
|
||||
if video.versions.iter().any(|version| version.video_meta.is_none()) ||
|
||||
(video.poster.is_none() && !video.versions.is_empty()) ||
|
||||
video.poster.as_ref().is_some_and(|poster| poster.assets.is_none()) {
|
||||
let job = Job::new(JobKind::FastTasks, Arc::clone(video));
|
||||
let job_kind = JobKind::VideoFastTasks(Arc::clone(video));
|
||||
let job = Job::new(job_kind);
|
||||
queue.push(job);
|
||||
}
|
||||
|
||||
@@ -436,7 +440,7 @@ impl Context {
|
||||
.iter_mut()
|
||||
.filter(|job| {
|
||||
if high_priority_only {
|
||||
if let JobKind::Format(_) = job.kind {
|
||||
if let JobKind::VideoFormat { .. } = job.kind {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use actix_web::http::header::ContentType;
|
||||
use actix_web::web::Data;
|
||||
use indoc::formatdoc;
|
||||
|
||||
use crate::{Context, SiteContent};
|
||||
use crate::{Context, JobKind, SiteContent};
|
||||
use crate::editor::widgets::layout;
|
||||
|
||||
pub async fn index(context: Data<Arc<Context>>) -> HttpResponse {
|
||||
@@ -52,11 +52,14 @@ pub async fn index(context: Data<Arc<Context>>) -> HttpResponse {
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|job| {
|
||||
format!(
|
||||
"{video} {status}",
|
||||
status = if job.assigned { "assigned" } else { "idle" },
|
||||
video = job.video.label()
|
||||
)
|
||||
let label = match &job.kind {
|
||||
JobKind::PlaylistFastTasks(playlist) => playlist.label(),
|
||||
JobKind::VideoFastTasks(video) => video.label(),
|
||||
JobKind::VideoFormat { video, .. } => video.label(),
|
||||
};
|
||||
let status = if job.assigned { "assigned" } else { "idle" };
|
||||
|
||||
format!("{label} {status}")
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
|
||||
@@ -17,6 +17,8 @@ use crate::{
|
||||
ContextPath,
|
||||
Field,
|
||||
FileMeta,
|
||||
Job,
|
||||
JobKind,
|
||||
Playlist
|
||||
};
|
||||
use crate::editor::endpoints;
|
||||
@@ -643,16 +645,18 @@ pub async fn upload_banner(
|
||||
playlist.write_manifest(&context.site_dir);
|
||||
};
|
||||
|
||||
if context.update_playlist(mutation, &path.path).is_err() {
|
||||
fs::remove_file(&storage_path).unwrap();
|
||||
let http_response = not_found(&context, &format!("Playlist {} not found.", &path.path));
|
||||
return Ok(Either::Right(http_response));
|
||||
};
|
||||
|
||||
// TODO: Would need some architectural changes.
|
||||
// Maybe implement together with new job model? (which maybe does not use a job queue at all?)
|
||||
// let job = Job::new(JobKind::Poster, playlist.clone());
|
||||
// context.job_queue.lock().unwrap().push(job);
|
||||
match context.update_playlist(mutation, &path.path) {
|
||||
Ok(playlist) => {
|
||||
let job_kind = JobKind::PlaylistFastTasks(Arc::clone(&playlist));
|
||||
let job = Job::new(job_kind);
|
||||
context.job_queue.lock().unwrap().push(job);
|
||||
}
|
||||
Err(()) => {
|
||||
fs::remove_file(&storage_path).unwrap();
|
||||
let http_response = not_found(&context, &format!("Playlist {} not found.", &path.path));
|
||||
return Ok(Either::Right(http_response));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -120,7 +120,8 @@ pub async fn downsize(
|
||||
let video_format_string = format!("video:h264:mp4:speed:1.0:{}", form.width);
|
||||
let video_format = VideoFormat::parse(&video_format_string).unwrap();
|
||||
|
||||
let job = Job::new(JobKind::Format(video_format), Arc::clone(&video));
|
||||
let job_kind = JobKind::VideoFormat { format: video_format, video: Arc::clone(&video) };
|
||||
let job = Job::new(job_kind);
|
||||
context.job_queue.lock().unwrap().push(job);
|
||||
|
||||
let redirect_url = format!("/video/{}", &path.path);
|
||||
@@ -611,7 +612,8 @@ pub async fn upload_poster(
|
||||
}
|
||||
};
|
||||
|
||||
let job = Job::new(JobKind::FastTasks, Arc::clone(&video));
|
||||
let job_kind = JobKind::VideoFastTasks(Arc::clone(&video));
|
||||
let job = Job::new(job_kind);
|
||||
context.job_queue.lock().unwrap().push(job);
|
||||
|
||||
let redirect_url = format!("/video/{}", path.path);
|
||||
@@ -683,7 +685,7 @@ pub async fn upload_video(
|
||||
f = web::block(move || f.write_all(&chunk).map(|_| f)).await??;
|
||||
}
|
||||
|
||||
match &container {
|
||||
let video = match &container {
|
||||
GetAnyResult::Collection(_) => {
|
||||
let hyper_dir = HyperDir::read(storage_path.parent().unwrap());
|
||||
let video = Video::read_dir(&context, &hyper_dir);
|
||||
@@ -698,11 +700,7 @@ pub async fn upload_video(
|
||||
return Ok(Either::Right(not_found(&context, &format!("Collection {} not found.", &path.path))));
|
||||
}
|
||||
|
||||
let job = Job::new(JobKind::FastTasks, Arc::clone(&video));
|
||||
context.job_queue.lock().unwrap().push(job);
|
||||
|
||||
let redirect_url = format!("/video/{}", &video.path);
|
||||
return Ok(Either::Left(Redirect::to(redirect_url).see_other()))
|
||||
video
|
||||
}
|
||||
GetAnyResult::Playlist(_) => {
|
||||
let hyper_dir = HyperDir::read(storage_path.parent().unwrap());
|
||||
@@ -718,11 +716,7 @@ pub async fn upload_video(
|
||||
return Ok(Either::Right(not_found(&context, &format!("Playlist {} not found.", &path.path))));
|
||||
}
|
||||
|
||||
let job = Job::new(JobKind::FastTasks, Arc::clone(&video));
|
||||
context.job_queue.lock().unwrap().push(job);
|
||||
|
||||
let redirect_url = format!("/video/{}", &video.path);
|
||||
return Ok(Either::Left(Redirect::to(redirect_url).see_other()))
|
||||
video
|
||||
}
|
||||
GetAnyResult::Video(_) => {
|
||||
let file_meta = FileMeta::new(&storage_path);
|
||||
@@ -732,20 +726,21 @@ pub async fn upload_video(
|
||||
video.versions.push(VideoFile::new(file_meta, file_name, None));
|
||||
};
|
||||
|
||||
let video = match context.update_video(mutation, &path.path) {
|
||||
match context.update_video(mutation, &path.path) {
|
||||
Ok(video) => video,
|
||||
// TODO: Can only happen in weird time of check vs. time of use edge cases
|
||||
Err(()) => return Ok(Either::Right(not_found(&context, &format!("Video {} not found.", &path.path))))
|
||||
};
|
||||
|
||||
let job = Job::new(JobKind::FastTasks, Arc::clone(&video));
|
||||
context.job_queue.lock().unwrap().push(job);
|
||||
|
||||
let redirect_url = format!("/video/{}", &video.path);
|
||||
return Ok(Either::Left(Redirect::to(redirect_url).see_other()))
|
||||
}
|
||||
}
|
||||
GetAnyResult::None => unreachable!()
|
||||
}
|
||||
};
|
||||
|
||||
let job_kind = JobKind::VideoFastTasks(Arc::clone(&video));
|
||||
let job = Job::new(job_kind);
|
||||
context.job_queue.lock().unwrap().push(job);
|
||||
|
||||
let redirect_url = format!("/video/{}", &video.path);
|
||||
return Ok(Either::Left(Redirect::to(redirect_url).see_other()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ use crate::util::nanoid;
|
||||
|
||||
|
||||
pub enum ResizeMode {
|
||||
/// Perform a crop to a rectangle with the minimum target aspect ratio (if needed),
|
||||
/// then resize to a maximum width.
|
||||
/// Aspect ratio is width / height, e.g. 16/9 = 1.7777777
|
||||
CoverCroppedRectangle { max_width: u32, min_aspect: f32 },
|
||||
/// Perform a crop to a rectangle with the target aspect ratio (if needed),
|
||||
/// then resize to a maximum width.
|
||||
/// Aspect ratio is width / height, e.g. 16/9 = 1.7777777
|
||||
@@ -79,6 +83,18 @@ impl ImageProcessor {
|
||||
resize_mode: ResizeMode
|
||||
) -> (String, (u32, u32)) {
|
||||
match resize_mode {
|
||||
ResizeMode::CoverCroppedRectangle { max_width, min_aspect } => {
|
||||
let found_aspect = self.source_width as f32 / self.source_height as f32;
|
||||
if found_aspect < min_aspect {
|
||||
// too tall, reduce height
|
||||
let new_height = (self.source_width as f32 / min_aspect).floor() as u32;
|
||||
let y = (self.source_height - new_height) / 2;
|
||||
let cropped_image = self.source_image.crop_imm(0, y, self.source_width, new_height);
|
||||
self.resize_and_export(context, max_width, &cropped_image)
|
||||
} else {
|
||||
self.resize_and_export(context, max_width, &self.source_image)
|
||||
}
|
||||
}
|
||||
ResizeMode::CoverRectangle { aspect, max_width } => {
|
||||
let found_aspect = self.source_width as f32 / self.source_height as f32;
|
||||
if found_aspect < aspect {
|
||||
|
||||
+9
-3
@@ -38,12 +38,18 @@ pub struct PosterFile {
|
||||
}
|
||||
|
||||
impl PosterAssets {
|
||||
pub fn write_manifest(&self, context: &Context, path: &str, poster: &PosterFile) {
|
||||
let poster_path = format!("{}/{}", path, poster.file_name);
|
||||
pub fn write_cache_manifest(
|
||||
&self,
|
||||
context: &Context,
|
||||
file_meta: &FileMeta,
|
||||
file_name: &str,
|
||||
path: &str
|
||||
) {
|
||||
let poster_path = format!("{}/{}", path, file_name);
|
||||
|
||||
let cache_item = CacheItem::PosterAssets(PosterAssetsCached {
|
||||
assets: self.clone(),
|
||||
file_meta: poster.file_meta.clone(),
|
||||
file_meta: file_meta.clone(),
|
||||
path: poster_path.clone()
|
||||
});
|
||||
|
||||
|
||||
+46
-18
@@ -3,7 +3,7 @@ use std::sync::Arc;
|
||||
|
||||
use serde_derive::Serialize;
|
||||
|
||||
use crate::{Video, VideoFormat};
|
||||
use crate::{Playlist, Video, VideoFormat};
|
||||
use crate::util::{nanoid, timestamp};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -11,14 +11,17 @@ pub struct Job {
|
||||
pub assigned: bool,
|
||||
pub created: u128,
|
||||
pub id: String,
|
||||
pub kind: JobKind,
|
||||
pub video: Arc<Video>
|
||||
pub kind: JobKind
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum JobKind {
|
||||
FastTasks,
|
||||
Format(VideoFormat)
|
||||
PlaylistFastTasks(Arc<Playlist>),
|
||||
VideoFastTasks(Arc<Video>),
|
||||
VideoFormat {
|
||||
format: VideoFormat,
|
||||
video: Arc<Video>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
@@ -26,7 +29,7 @@ pub enum JobKind {
|
||||
pub struct Operation {
|
||||
pub id: usize,
|
||||
pub label: String,
|
||||
pub video_path: String
|
||||
pub path: String
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
@@ -37,13 +40,12 @@ pub struct Processing {
|
||||
}
|
||||
|
||||
impl Job {
|
||||
pub fn new(kind: JobKind, video: Arc<Video>) -> Job {
|
||||
pub fn new(kind: JobKind) -> Job {
|
||||
Job {
|
||||
assigned: false,
|
||||
created: timestamp(),
|
||||
id: nanoid(),
|
||||
kind,
|
||||
video
|
||||
kind
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +57,20 @@ impl Job {
|
||||
/// relevant), the one that exists longer is prioritized.
|
||||
pub fn cmp_priority(&self, other: &Job) -> Ordering {
|
||||
match &self.kind {
|
||||
JobKind::FastTasks => match other.kind {
|
||||
JobKind::FastTasks => (),
|
||||
JobKind::Format(_) => return Ordering::Greater
|
||||
JobKind::PlaylistFastTasks(_) => match other.kind {
|
||||
JobKind::PlaylistFastTasks(_) => (),
|
||||
JobKind::VideoFastTasks(_) => (),
|
||||
JobKind::VideoFormat { .. } => return Ordering::Greater
|
||||
}
|
||||
JobKind::Format(format) => match &other.kind {
|
||||
JobKind::FastTasks => return Ordering::Less,
|
||||
JobKind::Format(other_format) => match format.cmp_priority(other_format) {
|
||||
JobKind::VideoFastTasks(_) => match other.kind {
|
||||
JobKind::PlaylistFastTasks(_) => (),
|
||||
JobKind::VideoFastTasks(_) => (),
|
||||
JobKind::VideoFormat { .. } => return Ordering::Greater
|
||||
}
|
||||
JobKind::VideoFormat { format, .. } => match &other.kind {
|
||||
JobKind::PlaylistFastTasks(_) => return Ordering::Less,
|
||||
JobKind::VideoFastTasks(_) => return Ordering::Less,
|
||||
JobKind::VideoFormat { format: other_format, .. } => match format.cmp_priority(other_format) {
|
||||
Ordering::Equal => (),
|
||||
ordering => return ordering
|
||||
}
|
||||
@@ -72,6 +81,25 @@ impl Job {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for JobKind {
|
||||
fn eq(&self, other: &JobKind) -> bool {
|
||||
match self {
|
||||
JobKind::PlaylistFastTasks(playlist) => match other {
|
||||
JobKind::PlaylistFastTasks(other_playlist) => playlist.path == other_playlist.path,
|
||||
_ => false
|
||||
}
|
||||
JobKind::VideoFastTasks(video) => match other {
|
||||
JobKind::VideoFastTasks(other_video) => video.path == other_video.path,
|
||||
_ => false
|
||||
}
|
||||
JobKind::VideoFormat { video, .. } => match other {
|
||||
JobKind::VideoFormat { video: other_video, .. } => video.path == other_video.path,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Processing {
|
||||
pub fn new() -> Processing {
|
||||
Processing {
|
||||
@@ -82,7 +110,7 @@ impl Processing {
|
||||
|
||||
/// Add a background operation to the processing registry, return an id
|
||||
/// with which it can be identified in the registry again.
|
||||
pub fn push(&mut self, video_path: &str, label: String) -> usize {
|
||||
pub fn push(&mut self, path: &str, label: String) -> usize {
|
||||
let id = self.operations
|
||||
.iter()
|
||||
.last()
|
||||
@@ -92,7 +120,7 @@ impl Processing {
|
||||
println!("{label}");
|
||||
|
||||
let operation = Operation {
|
||||
video_path: video_path.to_owned(),
|
||||
path: path.to_owned(),
|
||||
id,
|
||||
label
|
||||
};
|
||||
|
||||
+24
-21
@@ -223,26 +223,6 @@ impl Video {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn persist_in_cache(&self, cache_dir: &Path) {
|
||||
for version in &self.versions {
|
||||
if let Some(video_meta) = &version.video_meta {
|
||||
let cache_item = CacheItem::VideoMeta(VideoMetaCached {
|
||||
file_meta: version.file_meta.clone(),
|
||||
path: format!("{}/{}", self.path, version.file_name),
|
||||
video_meta: video_meta.clone()
|
||||
});
|
||||
|
||||
let serialized = bincode::serialize(&cache_item).unwrap();
|
||||
|
||||
let version_path = format!("{}/{}", self.path, version.file_name);
|
||||
let file_name = format!("{}.bincode", hash(&version_path));
|
||||
let path = cache_dir.join(file_name);
|
||||
|
||||
fs::write(path, serialized).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_dir(context: &Context, hyper_dir: &HyperDir) -> Arc<Video> {
|
||||
let mut video = Video::new(hyper_dir.site_path(context));
|
||||
|
||||
@@ -401,4 +381,27 @@ impl VideoMeta {
|
||||
width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_cache_manifest(
|
||||
&self,
|
||||
context: &Context,
|
||||
file_meta: &FileMeta,
|
||||
file_name: &str,
|
||||
path: &str
|
||||
) {
|
||||
let version_path = format!("{}/{}", path, file_name);
|
||||
|
||||
let cache_item = CacheItem::VideoMeta(VideoMetaCached {
|
||||
file_meta: file_meta.clone(),
|
||||
path: version_path.clone(),
|
||||
video_meta: self.clone()
|
||||
});
|
||||
|
||||
let cache_item_serialized = bincode::serialize(&cache_item).unwrap();
|
||||
|
||||
let cache_item_file_name = format!("{}.bincode", hash(&version_path));
|
||||
let cache_item_path = context.cache_dir.join(cache_item_file_name);
|
||||
|
||||
fs::write(cache_item_path, cache_item_serialized).unwrap();
|
||||
}
|
||||
}
|
||||
+180
-34
@@ -9,8 +9,10 @@ use crate::{
|
||||
ImageProcessor,
|
||||
Job,
|
||||
JobKind,
|
||||
Playlist,
|
||||
PosterAsset,
|
||||
PosterAssets,
|
||||
PosterAssetsCached,
|
||||
PosterFile,
|
||||
ResizeMode,
|
||||
Video,
|
||||
@@ -18,6 +20,98 @@ use crate::{
|
||||
};
|
||||
use crate::transcode;
|
||||
|
||||
fn compute_banner_assets(context: &Context, playlist: Arc<Playlist>) -> Result<(), String> {
|
||||
if let Some(banner) = &playlist.banner {
|
||||
let source_path = context.site_dir.join(&playlist.path).join(&banner.file_name);
|
||||
|
||||
match ImageProcessor::open(&source_path) {
|
||||
Ok(source_processor) => {
|
||||
let min_overshoot = 1.1;
|
||||
|
||||
let cover_rectangle_160 = ResizeMode::CoverCroppedRectangle { max_width: 160, min_aspect: 1.7777777 };
|
||||
let max_160 = compute_poster_asset(context, &source_processor, cover_rectangle_160)?;
|
||||
|
||||
let max_320 = if source_processor.source_width as f32 > 160.0 * min_overshoot {
|
||||
let cover_rectangle_320 = ResizeMode::CoverCroppedRectangle { max_width: 320, min_aspect: 1.7777777 };
|
||||
Some(compute_poster_asset(context, &source_processor, cover_rectangle_320)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let max_480 = if source_processor.source_width as f32 > 320.0 * min_overshoot {
|
||||
let cover_rectangle_480 = ResizeMode::CoverCroppedRectangle { max_width: 480, min_aspect: 1.7777777 };
|
||||
Some(compute_poster_asset(context, &source_processor, cover_rectangle_480)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let max_800 = if source_processor.source_width as f32 > 480.0 * min_overshoot {
|
||||
let cover_rectangle_800 = ResizeMode::CoverCroppedRectangle { max_width: 800, min_aspect: 1.7777777 };
|
||||
Some(compute_poster_asset(context, &source_processor, cover_rectangle_800)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let max_1280 = if source_processor.source_width as f32 > 800.0 * min_overshoot {
|
||||
let cover_rectangle_1280 = ResizeMode::CoverCroppedRectangle { max_width: 1280, min_aspect: 1.7777777 };
|
||||
Some(compute_poster_asset(context, &source_processor, cover_rectangle_1280)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let banner_assets = PosterAssets {
|
||||
max_160,
|
||||
max_320,
|
||||
max_480,
|
||||
max_800,
|
||||
max_1280
|
||||
};
|
||||
|
||||
// TODO: This internally allocates a PosterAssetsCached which it then serializes,
|
||||
// but this could actually be re-used below (if we were to merge the two somehow would be good?)
|
||||
banner_assets.write_cache_manifest(context, &banner.file_meta, &banner.file_name, &playlist.path);
|
||||
|
||||
// Add/update poster assets in in-memory cache
|
||||
// TODO: This looks vaguely in need of encapsulation
|
||||
if let Ok(mut cache) = context.cache.lock() {
|
||||
let banner_path = format!("{}/{}", playlist.path, banner.file_name);
|
||||
|
||||
if let Some(poster_assets_cache) = cache.poster_assets
|
||||
.iter_mut()
|
||||
.find(|cached| cached.path == banner_path) {
|
||||
poster_assets_cache.assets = banner_assets.clone();
|
||||
// TODO: Not sure if updating the file_meta is necessary (should be identical?) - think through carefully.
|
||||
poster_assets_cache.file_meta = banner.file_meta.clone();
|
||||
} else {
|
||||
let poster_assets_cache = PosterAssetsCached {
|
||||
assets: banner_assets.clone(),
|
||||
file_meta: banner.file_meta.clone(),
|
||||
path: banner_path
|
||||
};
|
||||
|
||||
cache.poster_assets.push(poster_assets_cache);
|
||||
}
|
||||
}
|
||||
|
||||
let mutation = |playlist: &mut Playlist| {
|
||||
// Note that we expect the banner to be Some(), virtually always.
|
||||
// If it's missing that is an edge case with (likely) user interaction.
|
||||
if let Some(banner) = &mut playlist.banner {
|
||||
banner.assets = Some(banner_assets);
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(()) = context.update_playlist(mutation, &playlist.path) {
|
||||
return Err("Banner assets could not be assigned in the site tree as the playlist has meanwhile changed path.".to_string());
|
||||
}
|
||||
}
|
||||
Err(err) => return Err(err)
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_metadata(context: &Context, video: Arc<Video>) -> Arc<Video> {
|
||||
let mut versions_mut = video.versions.clone();
|
||||
|
||||
@@ -27,14 +121,30 @@ fn compute_metadata(context: &Context, video: Arc<Video>) -> Arc<Video> {
|
||||
if version.video_meta.is_some() { continue }
|
||||
|
||||
if let Ok(video_meta) = transcode::ffprobe(&context.site_dir.join(&video.path).join(&version.file_name)) {
|
||||
let version_path = format!("{}/{}", &video.path, version.file_name);
|
||||
let video_meta_cached = VideoMetaCached {
|
||||
file_meta: version.file_meta.clone(),
|
||||
path: version_path,
|
||||
video_meta: video_meta.clone()
|
||||
};
|
||||
video_meta.write_cache_manifest(context, &version.file_meta, &version.file_name, &video.path);
|
||||
|
||||
context.cache.lock().unwrap().video_meta.push(video_meta_cached);
|
||||
// Add/update video meta in in-memory cache
|
||||
// TODO: This looks vaguely in need of encapsulation
|
||||
if let Ok(mut cache) = context.cache.lock() {
|
||||
let version_path = format!("{}/{}", &video.path, version.file_name);
|
||||
|
||||
if let Some(video_meta_cached) = cache.video_meta
|
||||
.iter_mut()
|
||||
.find(|cached| cached.path == version_path) {
|
||||
// TODO: Not sure if updating the file_meta is necessary (should be identical?) - think through carefully.
|
||||
video_meta_cached.file_meta = version.file_meta.clone();
|
||||
video_meta_cached.video_meta = video_meta.clone();
|
||||
|
||||
} else {
|
||||
let video_meta_cached = VideoMetaCached {
|
||||
file_meta: version.file_meta.clone(),
|
||||
path: version_path,
|
||||
video_meta: video_meta.clone()
|
||||
};
|
||||
|
||||
cache.video_meta.push(video_meta_cached);
|
||||
}
|
||||
}
|
||||
|
||||
version.video_meta = Some(video_meta);
|
||||
|
||||
@@ -48,11 +158,8 @@ fn compute_metadata(context: &Context, video: Arc<Video>) -> Arc<Video> {
|
||||
};
|
||||
|
||||
if let Ok(video_updated) = context.update_video(mutation, &video.path) {
|
||||
video_updated.persist_in_cache(&context.cache_dir);
|
||||
|
||||
// TODO: Reactivate
|
||||
// context.create_version_jobs(&video);
|
||||
|
||||
return video_updated;
|
||||
}
|
||||
}
|
||||
@@ -79,11 +186,8 @@ fn compute_poster(context: &Context, video: Arc<Video>) -> Arc<Video> {
|
||||
video
|
||||
}
|
||||
|
||||
fn compute_poster_asset(context: &Context, source_image: &ImageProcessor, width: u32) -> Result<PosterAsset, String> {
|
||||
let (file_name, dimensions) = source_image.resize(
|
||||
context,
|
||||
ResizeMode::CoverRectangle { aspect: 1.7777777, max_width: width }
|
||||
);
|
||||
fn compute_poster_asset(context: &Context, source_image: &ImageProcessor, resize_mode: ResizeMode) -> Result<PosterAsset, String> {
|
||||
let (file_name, dimensions) = source_image.resize(context, resize_mode);
|
||||
|
||||
let filesize_bytes = match fs::metadata(context.cache_dir.join(&file_name)) {
|
||||
Ok(metadata) => metadata.len(),
|
||||
@@ -108,28 +212,33 @@ fn compute_poster_assets(context: &Context, video: Arc<Video>) -> Result<(), Str
|
||||
Ok(source_processor) => {
|
||||
let min_overshoot = 1.1;
|
||||
|
||||
let max_160 = compute_poster_asset(context, &source_processor, 160)?;
|
||||
let cover_rectangle_160 = ResizeMode::CoverRectangle { aspect: 1.7777777, max_width: 160 };
|
||||
let max_160 = compute_poster_asset(context, &source_processor, cover_rectangle_160)?;
|
||||
|
||||
let max_320 = if source_processor.source_width as f32 > 160.0 * min_overshoot {
|
||||
Some(compute_poster_asset(context, &source_processor, 320)?)
|
||||
let cover_rectangle_320 = ResizeMode::CoverRectangle { aspect: 1.7777777, max_width: 320 };
|
||||
Some(compute_poster_asset(context, &source_processor, cover_rectangle_320)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let max_480 = if source_processor.source_width as f32 > 320.0 * min_overshoot {
|
||||
Some(compute_poster_asset(context, &source_processor, 480)?)
|
||||
let cover_rectangle_480 = ResizeMode::CoverRectangle { aspect: 1.7777777, max_width: 480 };
|
||||
Some(compute_poster_asset(context, &source_processor, cover_rectangle_480)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let max_800 = if source_processor.source_width as f32 > 480.0 * min_overshoot {
|
||||
Some(compute_poster_asset(context, &source_processor, 800)?)
|
||||
let cover_rectangle_800 = ResizeMode::CoverRectangle { aspect: 1.7777777, max_width: 800 };
|
||||
Some(compute_poster_asset(context, &source_processor, cover_rectangle_800)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let max_1280 = if source_processor.source_width as f32 > 800.0 * min_overshoot {
|
||||
Some(compute_poster_asset(context, &source_processor, 1280)?)
|
||||
let cover_rectangle_1280 = ResizeMode::CoverRectangle { aspect: 1.7777777, max_width: 1280 };
|
||||
Some(compute_poster_asset(context, &source_processor, cover_rectangle_1280)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -142,7 +251,30 @@ fn compute_poster_assets(context: &Context, video: Arc<Video>) -> Result<(), Str
|
||||
max_1280
|
||||
};
|
||||
|
||||
poster_assets.write_manifest(context, &video.path, poster);
|
||||
poster_assets.write_cache_manifest(context, &poster.file_meta, &poster.file_name, &video.path);
|
||||
|
||||
// Add/update poster assets in in-memory cache
|
||||
// TODO: This looks vaguely in need of encapsulation
|
||||
if let Ok(mut cache) = context.cache.lock() {
|
||||
let poster_path = format!("{}/{}", video.path, poster.file_name);
|
||||
|
||||
if let Some(poster_assets_cache) = cache.poster_assets
|
||||
.iter_mut()
|
||||
.find(|cached| cached.path == poster_path) {
|
||||
poster_assets_cache.assets = poster_assets.clone();
|
||||
// TODO: Not sure if updating the file_meta is necessary (should be identical?) - think through carefully.
|
||||
poster_assets_cache.file_meta = poster.file_meta.clone();
|
||||
|
||||
} else {
|
||||
let poster_assets_cache = PosterAssetsCached {
|
||||
assets: poster_assets.clone(),
|
||||
file_meta: poster.file_meta.clone(),
|
||||
path: poster_path
|
||||
};
|
||||
|
||||
cache.poster_assets.push(poster_assets_cache);
|
||||
}
|
||||
}
|
||||
|
||||
let mutation = |video: &mut Video| {
|
||||
// Note that we expect the poster to be Some(), virtually always.
|
||||
@@ -183,9 +315,21 @@ pub fn start_threads(count: usize, context_ref: &Arc<Context>) {
|
||||
let job = job.unwrap();
|
||||
|
||||
match job.kind {
|
||||
JobKind::FastTasks => {
|
||||
let video: Arc<Video> = job.video;
|
||||
JobKind::PlaylistFastTasks(playlist) => {
|
||||
let label = format!("[Worker {worker_number}] Doing fast track processing for {}", &playlist.path);
|
||||
let process_id = context.processing.lock().unwrap().push(&playlist.path, label);
|
||||
|
||||
if let Some(banner) = &playlist.banner {
|
||||
if banner.assets.is_none() {
|
||||
if let Err(err) = compute_banner_assets(&context, playlist) {
|
||||
context.processing_errors.lock().unwrap().push(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.processing.lock().unwrap().remove(process_id);
|
||||
}
|
||||
JobKind::VideoFastTasks(video) => {
|
||||
let label = format!("[Worker {worker_number}] Doing fast track processing for {}", &video.path);
|
||||
let process_id = context.processing.lock().unwrap().push(&video.path, label);
|
||||
|
||||
@@ -207,24 +351,26 @@ pub fn start_threads(count: usize, context_ref: &Arc<Context>) {
|
||||
|
||||
context.processing.lock().unwrap().remove(process_id);
|
||||
}
|
||||
JobKind::Format(format) => {
|
||||
JobKind::VideoFormat { format, video } => {
|
||||
let format_label = format.label(); // Implement Display so we can just drop it right into interpolation?
|
||||
let label = format!("[Worker {worker_number}] Computing version {format_label} for {}", job.video.path);
|
||||
let process_id = context.processing.lock().unwrap().push(&job.video.path, label);
|
||||
let label = format!("[Worker {worker_number}] Computing version {format_label} for {}", video.path);
|
||||
let process_id = context.processing.lock().unwrap().push(&video.path, label);
|
||||
|
||||
if let Ok(version) = transcode::encode(&context, format, &job.video) {
|
||||
let mutation = |video: &mut Video| {
|
||||
video.versions.push(version);
|
||||
if let Ok(version) = transcode::encode(&context, format, &video) {
|
||||
let mutation = |video_mut: &mut Video| {
|
||||
video_mut.versions.push(version);
|
||||
};
|
||||
|
||||
if let Ok(video) = context.update_video(mutation, &job.video.path) {
|
||||
video.persist_in_cache(&context.cache_dir);
|
||||
match context.update_video(mutation, &video.path) {
|
||||
Ok(video_updated) => {
|
||||
let job_kind = JobKind::VideoFastTasks(Arc::clone(&video_updated));
|
||||
let follow_up_job = Job::new(job_kind);
|
||||
context.job_queue.lock().unwrap().push(follow_up_job);
|
||||
}
|
||||
Err(()) => context.processing_errors.lock().unwrap().push("Video could not be updated with newly computed version.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
let follow_up_job = Job::new(JobKind::FastTasks, Arc::clone(&job.video));
|
||||
context.job_queue.lock().unwrap().push(follow_up_job);
|
||||
|
||||
context.processing.lock().unwrap().remove(process_id);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user