반업주부의 일상 배움사

[Rust] 유튜브 다운로드 본문

IT 인터넷/일반

[Rust] 유튜브 다운로드

Banjubu 2024. 10. 17. 14:30
반응형

 

macOS (애플 실리콘) 기준이에요.

 

아래 파일 저장해서 컴파일 한 다음 실행하면 되요.

 

youtube-downloader.rs
0.01MB

 

youtube-downloader.rs

use std::process::Command;
use std::io::{self, Write};
use std::path::Path;

fn check_program_installed(program: &str, version_check: &str) -> bool {
    Command::new(program)
        .arg(version_check)
        .output()
        .map(|output| output.status.success())
        .unwrap_or(false)
}

fn install_yt_dlp() -> Result<(), Box<dyn std::error::Error>> {
    if cfg!(target_os = "windows") {
        Command::new("pip").arg("install").arg("yt-dlp").status()?;
    } else if cfg!(target_os = "linux") {
        Command::new("pip3").arg("install").arg("yt-dlp").status()?;
    } else if cfg!(target_os = "macos") {
        Command::new("brew").arg("install").arg("yt-dlp").status()?;
    }
    Ok(())
}

fn install_ffmpeg() -> Result<(), Box<dyn std::error::Error>> {
    if cfg!(target_os = "windows") {
        Command::new("choco").arg("install").arg("ffmpeg").status()?;
    } else if cfg!(target_os = "linux") {
        Command::new("sudo").arg("apt").arg("install").arg("-y").arg("ffmpeg").status()?;
    } else if cfg!(target_os = "macos") {
        Command::new("brew").arg("install").arg("ffmpeg").status()?;
    }
    Ok(())
}

fn download_youtube_video(url: &str, output: &str, slow_download: bool) -> Result<(), Box<dyn std::error::Error>> {
    // yt-dlp로 비디오 다운로드
    let mut yt_dlp_cmd = Command::new("yt-dlp");
    if !slow_download {
        yt_dlp_cmd
            .arg("-R").arg("99")  // 재시도 횟수
            .arg("-f").arg("bestvideo[height<=4096][fps<=30][ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best")  // 품질 필터링
            .arg("-S").arg("vcodec:h264")  // 비디오 코덱 순서 필터링
            .arg("--windows-filenames")  // Windows 호환 파일 이름
            .arg("--restrict-filenames")  // 제한된 파일 이름
            .arg("--write-auto-subs")  // 자동 자막 작성
            .arg("--sub-lang").arg("en.*")  // 자막 언어 설정
            .arg("--embed-subs")  // 자막을 비디오에 포함
            .arg("--add-chapters")  // 챕터 정보 추가
            .arg("--add-metadata")  // 메타데이터 추가
            .arg("-N").arg("4")  // 병렬 다운로드 수
            .arg("-ci")  // 계속할 수 있을 때만 다운로드
            .arg("--verbose")  // 상세 정보 출력
            .arg("--remux-video").arg("mp4/mkv")  // 비디오 포맷 변환
            .status()?;  // 명령 실행
    } else {
        yt_dlp_cmd
            .arg("-vU")
            .arg("-f")
            .arg("bestvideo[height=4096]+bestaudio/best")
            .arg("--merge-output-format")
            .arg("mkv");  // 고화질 다운로드
    }

    yt_dlp_cmd
              .arg("-o")
              .arg(output)
              .arg(url);

    let status = yt_dlp_cmd.status()?;

    if status.success() {
        println!("다운로드 완료: {}", output);
    } else {
        eprintln!("다운로드 실패");
        return Err("다운로드 실패".into());
    }

    if slow_download {
        // MKV -> MP4 변환
        let mkv_file = format!("{}.mp4.mkv", output.trim_end_matches(".mp4"));
        let mp4_file = format!("{}.mp4", output.trim_end_matches(".mp4"));

        convert_mkv_to_mp4(&mkv_file, &mp4_file)?;

        // 파일 위치 열기
        open_file_location(&mp4_file)?;
    } else {
        // 파일 위치 열기
        open_file_location(&output)?;
    }

    Ok(())
}

fn convert_mkv_to_mp4(input_file: &str, output_file: &str) -> Result<(), Box<dyn std::error::Error>> {
    let status = Command::new("ffmpeg")
        .arg("-i")
        .arg(input_file)
        .arg("-c:v")
        .arg("h264_videotoolbox")  // M1 하드웨어 가속 인코더
        .arg("-c:a")
        .arg("aac")  // 오디오 코덱
        .arg("-b:a")
        .arg("192k")  // 오디오 비트레이트
        .arg(output_file)
        .status()?;

    if status.success() {
        println!("변환 완료: {} -> {}", input_file, output_file);
    } else {
        eprintln!("변환 실패");
        return Err("ffmpeg 변환 실패".into());
    }

    Ok(())
}

fn open_file_location(file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
    let path = Path::new(file_path);

    #[cfg(target_os = "macos")]
    Command::new("open").arg(path.parent().unwrap()).status()?;

    #[cfg(target_os = "windows")]
    Command::new("explorer").arg(path.parent().unwrap()).status()?;

    #[cfg(target_os = "linux")]
    Command::new("xdg-open").arg(path.parent().unwrap()).status()?;

    Ok(())
}

fn main() {
    // yt-dlp 설치 확인
    if !check_program_installed("yt-dlp", "--version") {
        println!("yt-dlp가 설치되지 않았습니다. 설치를 시작합니다.");
        if let Err(e) = install_yt_dlp() {
            eprintln!("yt-dlp 설치 실패: {}", e);
            return;
        }
    }

    // ffmpeg 설치 확인
    if !check_program_installed("ffmpeg", "-version") {
        println!("ffmpeg가 설치되지 않았습니다. 설치를 시작합니다.");
        if let Err(e) = install_ffmpeg() {
            eprintln!("ffmpeg 설치 실패: {}", e);
            return;
        }
    }

    // 유튜브 URL 입력 받기
    let mut video_url = String::new();
    print!("다운로드할 유튜브 URL을 입력하세요: ");
    io::stdout().flush().unwrap();
    io::stdin().read_line(&mut video_url).expect("URL 입력 실패");
    let video_url = video_url.trim();

    // 출력 파일 이름 입력 받기
    let mut output_path = String::new();
    print!("출력할 파일 이름을 입력하세요 (기본: downloaded_video.mp4): ");
    io::stdout().flush().unwrap();
    io::stdin().read_line(&mut output_path).expect("출력 파일 이름 입력 실패");
    let output_path = output_path.trim();

    let output_path = if output_path.is_empty() {
        "downloaded_video.mp4"
    } else {
        output_path
    };

    // 빨리 받기 여부 확인
    let mut fast_download_input = String::new();
    print!("느리게 받기를 원하시면 y를 입력하세요 (기본: 빨리받기): ");
    io::stdout().flush().unwrap();
    io::stdin().read_line(&mut fast_download_input).expect("입력 실패");
    let slow_download = fast_download_input.trim().to_lowercase() == "y";

    // 다운로드 시작
    if let Err(e) = download_youtube_video(video_url, output_path, slow_download) {
        eprintln!("오류 발생: {}", e);
    }
}

 

컴파일

rustc youtube-downloader.rs

 

 

yt-dlp, ffmpeg 설치

pip install yt-dlp
pip install --upgrade yt-dlp
brew install ffmpeg

 

 

반응형
LIST
Comments