mirror of
https://github.com/OMGeeky/twba.splitter.git
synced 2026-01-20 10:00:56 +01:00
Initial version (should work pretty well)
This commit is contained in:
157
src/client.rs
Normal file
157
src/client.rs
Normal file
@@ -0,0 +1,157 @@
|
||||
use crate::errors::SplitterError;
|
||||
use crate::prelude::*;
|
||||
use backup_config::Conf;
|
||||
use chrono::Duration;
|
||||
use local_db::prelude::{Status, Videos, VideosColumn, VideosModel};
|
||||
use local_db::re_exports::sea_orm::{
|
||||
ActiveModelTrait, ActiveValue, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel,
|
||||
QueryFilter,
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Instant;
|
||||
use tokio::fs;
|
||||
|
||||
mod utils;
|
||||
use utils::ffmpeg::run_ffmpeg_split;
|
||||
|
||||
pub struct SplitterClient {
|
||||
conf: Conf,
|
||||
db: DatabaseConnection,
|
||||
}
|
||||
|
||||
impl SplitterClient {
|
||||
pub fn new(conf: Conf, db: DatabaseConnection) -> Self {
|
||||
Self { conf, db }
|
||||
}
|
||||
}
|
||||
|
||||
impl SplitterClient {
|
||||
#[tracing::instrument(skip(self))]
|
||||
async fn split_video(&self, video: VideosModel) -> Result<()> {
|
||||
//
|
||||
let id = video.twitch_id.clone();
|
||||
let mut video = video.into_active_model();
|
||||
video.status = ActiveValue::Set(Status::Splitting);
|
||||
video.clone().update(&self.db).await?;
|
||||
let result = self.inner_split_video(id.clone()).await;
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
video.status = ActiveValue::Set(Status::Split);
|
||||
video.clone().update(&self.db).await?;
|
||||
}
|
||||
Err(err) => {
|
||||
video.status = ActiveValue::Set(Status::SplitFailed);
|
||||
video.clone().update(&self.db).await?;
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn inner_split_video(&self, id: String) -> Result<()> {
|
||||
let base_path = Path::new(&self.conf.download_folder_path);
|
||||
let input_path = base_path.join(format!("{}.mp4", id));
|
||||
let output_folder_path = base_path.join(&id);
|
||||
|
||||
info!("Splitting video with id: {}", id);
|
||||
verify_paths(base_path, &input_path, &output_folder_path).await?;
|
||||
let output_path_pattern = output_folder_path.join("%03d.mp4");
|
||||
let output_path_pattern = output_path_pattern
|
||||
.to_str()
|
||||
.ok_or_else(|| SplitterError::PathToString(output_path_pattern.clone()))?
|
||||
.to_string();
|
||||
|
||||
let split_playlist_path = output_folder_path.join("output.m3u8");
|
||||
debug!("output_path_pattern: {}", output_path_pattern);
|
||||
let duration_soft_cap = Duration::minutes(
|
||||
self.conf
|
||||
.google
|
||||
.youtube
|
||||
.default_video_length_minutes_soft_cap,
|
||||
);
|
||||
let duration_hard_cap = Duration::minutes(
|
||||
self.conf
|
||||
.google
|
||||
.youtube
|
||||
.default_video_length_minutes_hard_cap,
|
||||
);
|
||||
//todo: get a user specific soft and hard cap
|
||||
info!("splitting video at path: {:?}", input_path);
|
||||
let start_time = Instant::now();
|
||||
run_ffmpeg_split(
|
||||
&input_path,
|
||||
&output_path_pattern,
|
||||
&split_playlist_path,
|
||||
&duration_soft_cap,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let duration = Instant::now().duration_since(start_time);
|
||||
info!("FFMPEG-Splitting took: {:?}", duration);
|
||||
let split_info = utils::get_playlist_info(&split_playlist_path).await?;
|
||||
tokio::fs::remove_file(&split_playlist_path)
|
||||
.await
|
||||
.map_err(SplitterError::Write)?;
|
||||
trace!(
|
||||
"total duration: {} in {} parts",
|
||||
split_info.total_duration.to_string(),
|
||||
split_info.parts.len()
|
||||
);
|
||||
let paths =
|
||||
utils::join_last_parts_if_needed(split_info, &output_folder_path, duration_hard_cap)
|
||||
.await?;
|
||||
|
||||
debug!("removing original file: {:?}", input_path);
|
||||
tokio::fs::remove_file(&input_path)
|
||||
.await
|
||||
.map_err(SplitterError::Write)?;
|
||||
|
||||
let duration = Instant::now().duration_since(start_time);
|
||||
info!("Done Splitting. Whole operation took: {:?}", duration);
|
||||
debug!("paths: {:?}", paths);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub async fn split_videos(&self) -> Result<()> {
|
||||
info!("Splitting videos");
|
||||
let videos = Videos::find()
|
||||
.filter(VideosColumn::Status.eq(Status::Downloaded))
|
||||
.all(&self.db)
|
||||
.await?;
|
||||
|
||||
for video in videos {
|
||||
info!("Splitting video: {:?}", video);
|
||||
let id = video.id;
|
||||
let success = self.split_video(video).await;
|
||||
if let Err(err) = success {
|
||||
error!(
|
||||
"Could not split video with id: {} because of err: {:?}",
|
||||
id, err
|
||||
);
|
||||
} else {
|
||||
info!("Split video with id: {}", id);
|
||||
}
|
||||
}
|
||||
|
||||
info!("Finished splitting videos");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn verify_paths(
|
||||
base_path: &Path,
|
||||
input_path: &Path,
|
||||
output_folder_path: &PathBuf,
|
||||
) -> Result<()> {
|
||||
if !base_path.exists() || !input_path.exists() {
|
||||
return Err(SplitterError::NotFound(input_path.to_path_buf()));
|
||||
}
|
||||
if !input_path.is_file() {
|
||||
return Err(SplitterError::InvalidInputFile(input_path.to_path_buf()));
|
||||
}
|
||||
fs::create_dir_all(&output_folder_path)
|
||||
.await
|
||||
.map_err(SplitterError::CreateFolder)?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user