mirror of
https://github.com/OMGeeky/twba.splitter.git
synced 2026-01-06 19:45:22 +01:00
159 lines
5.2 KiB
Rust
159 lines
5.2 KiB
Rust
use crate::errors::SplitterError;
|
|
use crate::prelude::*;
|
|
use twba_backup_config::Conf;
|
|
use chrono::Duration;
|
|
use twba_local_db::prelude::{Status, Videos, VideosColumn, VideosModel};
|
|
use twba_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(count) => {
|
|
video.status = ActiveValue::Set(Status::Split);
|
|
video.part_count = ActiveValue::Set(count as i32);
|
|
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<usize> {
|
|
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(paths.len())
|
|
}
|
|
|
|
#[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(())
|
|
}
|