-
-
Notifications
You must be signed in to change notification settings - Fork 50k
Description
What would you like to share?
import pandas as pd
from yt_dlp import YoutubeDL
import os
import time
import sys
import ctypes
import multiprocessing
import subprocess
import random
==========================================
[0] 사용자 설정 (FFmpeg 경로 유지)
==========================================
FFMPEG_PATH = r'E:\다운로드\ffmpeg-2026-01-05-git-2892815c45-full_build\ffmpeg-2026-01-05-git-2892815c45-full_build\bin\ffmpeg.exe'
DOWNLOAD_DIR = r'C:\YouTubeMusic_Downloads'
FINAL_DIR = os.path.join(DOWNLOAD_DIR, '정리완료_최종')
==========================================
[1] 유틸리티
==========================================
def get_program_path():
if getattr(sys, 'frozen', False):
return os.path.dirname(sys.executable)
else:
return os.path.dirname(os.path.abspath(file))
def is_admin():
try: return ctypes.windll.shell32.IsUserAnAdmin()
except: return False
==========================================
[2] 작업 함수
==========================================
def download_worker(args):
url, file_prefix, ffmpeg_path, download_dir = args
# [핵심 1] 차단 방지를 위해 2~5초 랜덤하게 쉬었다가 다운로드
time.sleep(random.uniform(2.0, 5.0))
ydl_opts = {
'ffmpeg_location': ffmpeg_path,
'format': 'bestaudio/best',
'writethumbnail': True,
# [핵심 2] PC가 아닌 '안드로이드 폰'인 척 위장 (쿠키 없이 403 우회)
'extractor_args': {
'youtube': {
'player_client': ['android', 'ios'],
}
},
# 보안 경고 무시 설정
'nocheckcertificate': True,
'ignoreerrors': True,
'no_warnings': True,
'postprocessors': [
{'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '192'},
{'key': 'EmbedThumbnail'}, # 썸네일 삽입
{'key': 'FFmpegMetadata', 'add_metadata': True}
],
'outtmpl': f'{download_dir}/{file_prefix}%(title)s.%(ext)s',
'restrictfilenames': False,
'windowsfilenames': True,
'noplaylist': True,
'quiet': True,
}
try:
with YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
return f"✅ [다운로드 성공] {file_prefix}"
except Exception as e:
# 에러 메시지 간소화
msg = str(e)
if "403" in msg:
return f"❌ [다운로드 차단됨] {file_prefix} (잠시 후 다시 시도하세요)"
return f"❌ [다운로드 실패] {file_prefix}: {msg[:30]}..."
def normalize_worker(args):
ffmpeg_path, input_p, output_p = args
command = [
ffmpeg_path,
'-hwaccel', 'cuda',
'-i', input_p,
'-map', '0',
'-c:v', 'copy',
'-id3v2_version', '3',
'-af', 'loudnorm=I=-14:LRA=11:TP=-1',
'-c:a', 'libmp3lame',
'-b:a', '192k',
'-y', output_p
]
try:
subprocess.run(command, check=True, capture_output=True)
return f"✅ [소리조정 완료] {os.path.basename(input_p)}"
except:
return f"❌ [소리조정 실패] {os.path.basename(input_p)}"
==========================================
[3] 메인 로직
==========================================
def run_main():
current_folder = get_program_path()
excel_file = os.path.join(current_folder, 'links.xlsx')
print(f"\n📂 프로그램 실행 위치: {current_folder}")
if not os.path.exists(DOWNLOAD_DIR): os.makedirs(DOWNLOAD_DIR)
if not os.path.exists(FINAL_DIR): os.makedirs(FINAL_DIR)
# ---------------------------------------------------------
# STEP 1: 다운로드 (안드로이드 모드)
# ---------------------------------------------------------
urls = []
if os.path.exists(excel_file):
try:
df = pd.read_excel(excel_file, engine='openpyxl')
urls = df.iloc[:, 0].dropna().tolist()
print(f"📋 다운로드 목록: {len(urls)}곡")
except:
print("❌ 엑셀 읽기 오류")
else:
print("⚠️ 엑셀 파일 없음. 다운로드는 건너뜁니다.")
if urls:
print("\n🚀 [1단계] 스마트폰 모드로 다운로드 시작 (차단 회피)")
print("ℹ️ 안전을 위해 동시 다운로드를 3개로 제한합니다.")
dl_args = [(url, f"{i:03d}. ", FFMPEG_PATH, DOWNLOAD_DIR) for i, url in enumerate(urls, start=1)]
# [중요] 다운로드는 3개까지만 동시에 (봇 탐지 방지)
with multiprocessing.Pool(processes=3) as pool:
results = pool.map(download_worker, dl_args)
for res in results: print(res)
# ---------------------------------------------------------
# STEP 2: 소리 조정 (GPU 가속)
# ---------------------------------------------------------
print("\n🎚️ [2단계] 소리 평준화 & 썸네일 정리")
files = [f for f in os.listdir(DOWNLOAD_DIR) if f.lower().endswith('.mp3')]
if files:
print(f"🔥 총 {len(files)}곡 변환 중 (Ryzen 12스레드 풀가동)...")
norm_args = [(FFMPEG_PATH, os.path.join(DOWNLOAD_DIR, f), os.path.join(FINAL_DIR, f)) for f in files]
# 소리 조정은 유튜브 서버와 상관없으므로 12개 풀가동
with multiprocessing.Pool(processes=12) as pool:
results = pool.map(normalize_worker, norm_args)
for res in results: print(res)
else:
print("📂 작업할 파일이 없습니다.")
print("\n✨ 작업 완료! '정리완료_최종' 폴더를 확인하세요.")
print(f"📂 위치: {FINAL_DIR}")
if name == 'main':
multiprocessing.freeze_support()
if is_admin():
run_main()
input("\n엔터를 누르면 종료됩니다...")
else:
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
Additional information
No response