Introduce playlist banner processing, improve in-memory cache state integrity

This commit is contained in:
Simon Repp
2024-03-19 22:05:21 +01:00
parent bd4e751a94
commit 50575d016d
9 changed files with 325 additions and 120 deletions
+10 -6
View File
@@ -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;
}
}
+9 -6
View File
@@ -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");
+14 -10
View File
@@ -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));
}
}
}
}
+17 -22
View File
@@ -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()))
}
}
}
+16
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}