mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
studio zipped shots - ffmpg + upload work
This commit is contained in:
1 parent
ba369dab95
commit
46447448fc
4 files changed
+137
-45
No files matched your search
+9
-7
@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
|
||||
use std::io::{BufReader, Cursor, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use log::{error, info, warn};
|
||||
use log::{debug, error, info, warn};
|
||||
use once_cell::sync::Lazy;
|
||||
use zip::ZipArchive;
|
||||
|
||||
@@ -48,10 +48,10 @@ pub fn extract_frames_from_zip<P: AsRef<Path>>(
|
||||
let entries = get_relevant_zip_entries(&mut archive)?;
|
||||
|
||||
for entry in entries.iter() {
|
||||
info!("Entry: {:?}", entry);
|
||||
debug!("Entry: {:?}", entry);
|
||||
|
||||
if !entry.is_frame {
|
||||
info!("Skipping entry (not a frame): {:?}", entry);
|
||||
debug!("Skipping entry (not a frame): {:?}", entry);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,8 @@ pub fn extract_frames_from_zip<P: AsRef<Path>>(
|
||||
|
||||
let output_path = frame_temp_dir.as_ref().join(filesystem_name);
|
||||
|
||||
info!("Creating: {:?}", output_path);
|
||||
|
||||
let mut output_file = std::fs::File::create(&output_path)
|
||||
.map_err(|err| {
|
||||
error!("Problem creating file {:?} : {:?}", output_path, err);
|
||||
@@ -103,7 +105,7 @@ fn get_relevant_zip_entries(archive: &mut ZipArchive<BufReader<Cursor<&[u8]>>>)
|
||||
let mut entries = Vec::new();
|
||||
|
||||
for i in 0..(archive.len()) {
|
||||
info!("Reading file {}...", i);
|
||||
debug!("Reading file {}...", i);
|
||||
|
||||
let mut file = archive.by_index(i)
|
||||
.map_err(|err| {
|
||||
@@ -114,8 +116,8 @@ fn get_relevant_zip_entries(archive: &mut ZipArchive<BufReader<Cursor<&[u8]>>>)
|
||||
let filename = file.name();
|
||||
let filename_lowercase = filename.to_lowercase();
|
||||
|
||||
info!("File {} is {:?} - is file = {}", i, filename, file.is_file());
|
||||
info!("Enclosed name: {:?}", file.enclosed_name());
|
||||
debug!("File {} is {:?} - is file = {}", i, filename, file.is_file());
|
||||
debug!("Enclosed name: {:?}", file.enclosed_name());
|
||||
|
||||
if file.is_dir() {
|
||||
continue;
|
||||
@@ -147,7 +149,7 @@ fn get_relevant_zip_entries(archive: &mut ZipArchive<BufReader<Cursor<&[u8]>>>)
|
||||
}
|
||||
|
||||
for entry in entries.iter() {
|
||||
info!("Entry: {:?}", entry);
|
||||
debug!("Entry: {:?}", entry);
|
||||
}
|
||||
|
||||
Ok(entries)
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::bail;
|
||||
use log::{error, info};
|
||||
|
||||
use errors::AnyhowResult;
|
||||
use filesys::path_to_string::path_to_string;
|
||||
use videos::ffprobe_get_info::{ffprobe_get_info, VideoDimensions, VideoDuration};
|
||||
|
||||
#[derive(Copy,Clone,Debug)]
|
||||
pub enum FrameType {
|
||||
Png,
|
||||
Jpg,
|
||||
}
|
||||
|
||||
pub struct OutputFile {
|
||||
pub path: PathBuf,
|
||||
pub dimensions: Option<VideoDimensions>,
|
||||
pub duration: Option<VideoDuration>,
|
||||
}
|
||||
|
||||
pub fn ffmpeg_frames_to_mp4(frame_input_directory: &Path, frame_type: FrameType, frame_rate: u16) -> AnyhowResult<OutputFile> {
|
||||
let glob_pattern = match frame_type {
|
||||
FrameType::Png => format!("{}/*.png", path_to_string(frame_input_directory)),
|
||||
FrameType::Jpg => format!("{}/*.jpg", path_to_string(frame_input_directory)),
|
||||
};
|
||||
|
||||
let output_file_path = frame_input_directory.join("output.mp4");
|
||||
|
||||
info!("Calling ffmpeg to convert frames to mp4...");
|
||||
|
||||
// ffmpeg -framerate 30 -pattern_type glob -i '*.png' \
|
||||
// -c:v libx264 -pix_fmt yuv420p out.mp4
|
||||
let output = Command::new("ffmpeg")
|
||||
.arg("-framerate")
|
||||
.arg(format!("{}", frame_rate))
|
||||
.arg("-pattern_type")
|
||||
.arg("glob")
|
||||
.arg("-i")
|
||||
.arg(glob_pattern)
|
||||
.arg("-c:v")
|
||||
.arg("libx264")
|
||||
.arg("-pix_fmt")
|
||||
.arg("yuv420p")
|
||||
.arg(&output_file_path)
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
error!("bad exit status: {}", output.status);
|
||||
|
||||
let _r = std::io::stdout().write_all(&output.stdout);
|
||||
let _r = std::io::stderr().write_all(&output.stderr);
|
||||
|
||||
bail!("ffmpeg failed: {:?}", output.status.to_string());
|
||||
}
|
||||
|
||||
let info = ffprobe_get_info(&output_file_path)?;
|
||||
|
||||
Ok(OutputFile {
|
||||
path: output_file_path,
|
||||
duration: info.duration,
|
||||
dimensions: info.dimensions,
|
||||
})
|
||||
}
|
||||
+2
-1
@@ -1,2 +1,3 @@
|
||||
pub mod extract_frames_from_zip;
|
||||
pub mod upload_studio_shot_media_file_handler;
|
||||
pub mod upload_studio_shot_media_file_handler;
|
||||
mod ffmpeg_frames_to_mp4;
|
||||
+60
-37
@@ -20,6 +20,7 @@ use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCat
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes;
|
||||
use hashing::sha256::sha256_hash_file::sha256_hash_file;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes;
|
||||
use mimetypes::mimetype_to_extension::mimetype_to_extension;
|
||||
@@ -34,6 +35,7 @@ use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileU
|
||||
use crate::http_server::endpoints::media_files::upload::upload_new_scene_media_file_handler::UploadNewSceneMediaFileForm;
|
||||
use crate::http_server::endpoints::media_files::upload::upload_pmx::extract_and_upload_pmx_files::{extract_and_upload_pmx_files, PmxError};
|
||||
use crate::http_server::endpoints::media_files::upload::upload_studio_shot::extract_frames_from_zip::{extract_frames_from_zip, ExtractFramesError};
|
||||
use crate::http_server::endpoints::media_files::upload::upload_studio_shot::ffmpeg_frames_to_mp4::{ffmpeg_frames_to_mp4, FrameType};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, RequireModeratorError, UseDatabase};
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
@@ -216,7 +218,7 @@ pub async fn upload_studio_shot_media_file_handler(
|
||||
MediaFileUploadError::ServerError
|
||||
})?;
|
||||
|
||||
extract_frames_from_zip(&file_bytes, frame_temp_dir)
|
||||
extract_frames_from_zip(&file_bytes, frame_temp_dir.path())
|
||||
.map_err(|err| {
|
||||
warn!("Extract frames error: {:?}", err);
|
||||
match err {
|
||||
@@ -232,46 +234,67 @@ pub async fn upload_studio_shot_media_file_handler(
|
||||
|
||||
// ==================== FFMPEG ==================== //
|
||||
|
||||
let video_file_details = ffmpeg_frames_to_mp4(frame_temp_dir.path(), FrameType::Png, 30)
|
||||
.map_err(|err| {
|
||||
warn!("FFMPEG error: {:?}", err);
|
||||
MediaFileUploadError::ServerError
|
||||
})?;
|
||||
|
||||
let hash = sha256_hash_file(&video_file_details.path)
|
||||
.map_err(|io_error| {
|
||||
error!("Problem hashing bytes: {:?}", io_error);
|
||||
MediaFileUploadError::ServerError
|
||||
})?;
|
||||
|
||||
// ==================== UPLOAD AND SAVE ==================== //
|
||||
// ==================== UPLOAD ==================== //
|
||||
|
||||
// TODO DELETE ME
|
||||
let token = MediaFileToken::new("test_token".to_string());
|
||||
const PREFIX : Option<&str> = Some("upload_");
|
||||
const SUFFIX : Option<&str> = Some(".mp4");
|
||||
|
||||
// const PREFIX : Option<&str> = Some("upload_");
|
||||
// const SUFFIX : Option<&str> = Some(".pmx");
|
||||
//
|
||||
// // TODO(bt, 2024-02-22): This should be a transaction.
|
||||
// let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
// maybe_media_class: Some(MediaFileClass::Dimensional),
|
||||
// media_file_type: MediaFileType::Pmx,
|
||||
// maybe_creator_user_token: Some(&user_session.get_strongly_typed_user_token()),
|
||||
// maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
// creator_ip_address: &ip_address,
|
||||
// creator_set_visibility,
|
||||
// upload_type: UploadType::Filesystem,
|
||||
// maybe_engine_category: Some(engine_category),
|
||||
// maybe_animation_type,
|
||||
// maybe_mime_type: Some("application/octet-stream"),
|
||||
// file_size_bytes: pmx_details.file_size_bytes,
|
||||
// maybe_duration_millis,
|
||||
// sha256_checksum: &pmx_details.sha256_checksum,
|
||||
// maybe_scene_source_media_file_token: None,
|
||||
// is_intermediate_system_file: false,
|
||||
// maybe_title: maybe_title.as_deref(),
|
||||
// public_bucket_directory_hash: pmx_details.pmx_public_upload_path.get_object_hash(),
|
||||
// maybe_public_bucket_prefix: PREFIX,
|
||||
// maybe_public_bucket_extension: SUFFIX,
|
||||
// pool: &server_state.mysql_pool,
|
||||
// })
|
||||
// .await
|
||||
// .map_err(|err| {
|
||||
// warn!("New file creation DB error: {:?}", err);
|
||||
// MediaFileUploadError::ServerError
|
||||
// })?;
|
||||
//
|
||||
// info!("new media file id: {} token: {:?}", record_id, &token);
|
||||
let bucket_path = MediaFileBucketPath::generate_new(PREFIX, SUFFIX);
|
||||
|
||||
server_state.public_bucket_client.upload_filename_with_content_type(
|
||||
bucket_path.get_full_object_path_str(),
|
||||
&video_file_details.path,
|
||||
"video/mp4")
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Upload video to bucket error: {:?}", e);
|
||||
MediaFileUploadError::ServerError
|
||||
})?;
|
||||
|
||||
// ==================== SAVE RECORD ==================== //
|
||||
|
||||
// TODO(bt, 2024-02-22): This should be a transaction.
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Video),
|
||||
media_file_type: MediaFileType::Mp4,
|
||||
maybe_creator_user_token: Some(&user_session.user_token_typed),
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
upload_type: UploadType::StorytellerEngine,
|
||||
maybe_engine_category: None,
|
||||
maybe_animation_type: None,
|
||||
maybe_mime_type: Some("video/mp4"),
|
||||
file_size_bytes: 0, // TODO
|
||||
maybe_duration_millis: video_file_details.duration.map(|duration| duration.millis as u64),
|
||||
sha256_checksum: &hash,
|
||||
maybe_scene_source_media_file_token: None,
|
||||
is_intermediate_system_file: false, // TODO
|
||||
maybe_title: maybe_title.as_deref(),
|
||||
public_bucket_directory_hash: bucket_path.get_object_hash(),
|
||||
maybe_public_bucket_prefix: PREFIX,
|
||||
maybe_public_bucket_extension: SUFFIX,
|
||||
pool: &server_state.mysql_pool,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("New file creation DB error: {:?}", err);
|
||||
MediaFileUploadError::ServerError
|
||||
})?;
|
||||
|
||||
info!("new media file id: {} token: {:?}", record_id, &token);
|
||||
|
||||
Ok(Json(UploadStudioShotSuccessResponse {
|
||||
success: true,
|
||||
|
||||
Reference in new issue
Block a user