유튜브 재생목록 다운로드 과정을 순차적으로 설명
- 프론트엔드 (DownloadButton 컴포넌트)에서 시작:
// components/DownloadButton.tsx
const handleDownload = () => {
// URL 유효성 검사
if (!isValidYouTubeUrl(url)) {
setError("올바른 YouTube 재생목록 URL을 입력해주세요.");
return;
}
// 설정 정보와 함께 IPC 메시지 전송
if (window.electron) {
window.electron.ipcRenderer.send('download-playlist', {
url,
settings: {
preferredCodec: 'mp3', // 설정에서 가져온 값
preferredQuality: '192', // 설정에서 가져온 값
downloadPath: './downloads' // 설정에서 가져온 값
}
});
}
};
- Electron의 preload.js를 통한 IPC 통신 설정:
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electron', {
ipcRenderer: {
send: (channel, data) => ipcRenderer.send(channel, data),
on: (channel, func) => ipcRenderer.on(channel, func),
once: (channel, func) => ipcRenderer.once(channel, func),
}
});
- Electron 메인 프로세스(main.js)에서 IPC 메시지 수신 및 처리:
// main.js
ipcMain.on('download-playlist', (event, { url, settings }) => {
// pydownloader 실행 파일 경로 설정
const pythonScriptPath = path.join(__dirname, 'dist', 'pydownloader');
// pydownloader 프로세스 실행
const downloadProcess = spawn(pythonScriptPath, [
url, // 유튜브 URL
settings.preferredCodec, // 선호 코덱
settings.preferredQuality, // 음질
settings.downloadPath // 저장 경로
]);
// 표준 출력 처리
downloadProcess.stdout.on('data', (data) => {
const message = data.toString();
if (message.startsWith("progress:")) {
// 진행률 업데이트
const progress = parseFloat(message.replace("progress:", "").trim());
event.sender.send('download-progress', { progress });
} else if (message.startsWith("file:")) {
// 파일 다운로드 완료
const filePath = message.replace("file:", "").trim();
event.sender.send('file-downloaded', { filePath });
}
});
// 에러 출력 처리
downloadProcess.stderr.on('data', (data) => {
event.sender.send('download-error', {
error: data.toString()
});
});
// 프로세스 종료 처리
downloadProcess.on('close', (code) => {
event.sender.send('download-complete', {
success: code === 0
});
});
});
- pydownloader 실행 파일 실행:
# downloader.py (pyinstaller로 변환됨)
def download_playlist(url, preferred_codec='mp3', preferred_quality='192', download_directory='./downloads'):
try:
# URL 유효성 검사
if not is_valid_youtube_url(url):
print(f"error:Invalid YouTube URL: {url}")
return
# yt-dlp 설정
ydl_opts = {
'format': 'bestaudio/best',
'outtmpl': os.path.join(download_directory, '%(title)s.%(ext)s'),
'progress_hooks': [progress_hook],
'postprocessors': [{
'key': 'FFmpegExtractAudio',
'preferredcodec': preferred_codec,
'preferredquality': preferred_quality,
}],
}
# 다운로드 실행
with YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
except Exception as e:
print(f"error:{str(e)}")
- 다운로드 진행 상황은 다시 프론트엔드로 전달:
// DownloadButton.tsx
useEffect(() => {
if (window.electron) {
// 진행률 업데이트 리스너
window.electron.ipcRenderer.on('download-progress', (event, { progress }) => {
setProgress(progress);
});
// 다운로드 완료 리스너
window.electron.ipcRenderer.on('download-complete', (event, { success }) => {
if (success) {
setStatus('완료');
}
});
// 에러 리스너
window.electron.ipcRenderer.on('download-error', (event, { error }) => {
setError(error);
});
}
}, []);
전체 프로세스 흐름:
- 사용자가 다운로드 버튼 클릭
- 프론트엔드에서 URL 검증 후 IPC 메시지 전송
- preload.js를 통해 메시지가 메인 프로세스로 전달
- 메인 프로세스에서 pydownloader 실행 파일 실행
- pydownloader가 yt-dlp를 사용하여 실제 다운로드 수행
- 다운로드 진행 상황이 표준 출력을 통해 메인 프로세스로 전달
- 메인 프로세스가 IPC를 통해 프론트엔드로 상태 업데이트
- 프론트엔드에서 진행 상황 표시