biliget 0.6.8

简单的B站视频下载工具 支持免登录下载B站高清视频
use crate::processer::ffmpeg::FfmpegError::FileNameError;
use std::path::Path;
use thiserror::Error;
use tokio::process::Command;
use tokio_util::sync::CancellationToken;

#[derive(Debug, Error)]
pub(crate) enum FfmpegError {
    #[error("找不到相关文件")]
    FileNotFound(),

    #[error("文件名错误")]
    FileNameError(),

    #[error("合并音视频失败: {0}")]
    MergeError(String),

    #[error("格式转换失败: {0}")]
    ConvertError(String),

    #[error("操作已取消")]
    Cancelled,
}

pub(crate) async fn merge_video(
    video_file: &Path,
    audio_file: &Path,
    output_file: &Path,
    cancel: CancellationToken,
) -> Result<(), FfmpegError> {
    if !video_file.exists() || !audio_file.exists() {
        return Err(FfmpegError::FileNotFound());
    }

    let mut child = Command::new("ffmpeg")
        .args([
            "-i",
            video_file.to_str().ok_or_else(FileNameError)?,
            "-i",
            audio_file.to_str().ok_or_else(FileNameError)?,
            "-c:v",
            "copy",
            "-c:a",
            "aac",
            "-y",
            output_file.to_str().ok_or_else(FileNameError)?,
        ])
        .kill_on_drop(true)
        .spawn()
        .map_err(|e| FfmpegError::MergeError(format!("启动 ffmpeg 失败: {}", e)))?;

    let status = tokio::select! {
        () = cancel.cancelled() => {
            let _ = child.kill().await;
            return Err(FfmpegError::Cancelled);
        }
        status = child.wait() => status,
    };

    match status {
        Ok(status) if status.success() => Ok(()),
        Ok(status) => Err(FfmpegError::MergeError(format!(
            "ffmpeg 执行失败 exit code: {}",
            status.code().unwrap_or(-1)
        ))),
        Err(e) => Err(FfmpegError::MergeError(format!(
            "等待 ffmpeg 进程失败: {}",
            e
        ))),
    }
}

pub(crate) async fn convert_audio(
    audio_file: &Path,
    output_file: &Path,
    cancel: CancellationToken,
) -> Result<(), FfmpegError> {
    if !audio_file.exists() {
        return Err(FfmpegError::FileNotFound());
    }

    let mut child = Command::new("ffmpeg")
        .args([
            "-i",
            audio_file.to_str().ok_or_else(FileNameError)?,
            "-vn",
            "-ar",
            "44100",
            "-ac",
            "2",
            "-y",
            output_file.to_str().ok_or_else(FileNameError)?,
        ])
        .kill_on_drop(true)
        .spawn()
        .map_err(|e| FfmpegError::ConvertError(format!("启动 ffmpeg 失败: {}", e)))?;

    let status = tokio::select! {
        () = cancel.cancelled() => {
            let _ = child.kill().await;
            return Err(FfmpegError::Cancelled);
        }
        status = child.wait() => status,
    };

    match status {
        Ok(status) if status.success() => Ok(()),
        Ok(status) => Err(FfmpegError::ConvertError(format!(
            "ffmpeg 执行失败 exit code: {}",
            status.code().unwrap_or(-1)
        ))),
        Err(e) => Err(FfmpegError::ConvertError(format!(
            "等待 ffmpeg 进程失败: {}",
            e
        ))),
    }
}