IT 인터넷/일반
[Rust] 유튜브 다운로드
Banjubu
2024. 10. 17. 14:30
반응형
macOS (애플 실리콘) 기준이에요.
아래 파일 저장해서 컴파일 한 다음 실행하면 되요.
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