File size: 3,688 Bytes
2b25bb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import gradio as gr
import yt_dlp
import subprocess
import os
import ffmpeg
import whisper

output_dir = "mp4"
os.makedirs(output_dir, exist_ok=True)

global_format_map = {}

def fetch_merged_formats(url):
    global global_format_map
    global_format_map = {}
    ydl_opts = {'quiet': True}
    labels = []

    with yt_dlp.YoutubeDL(ydl_opts) as ydl:
        info = ydl.extract_info(url, download=False)
        formats = info.get("formats", [])
        has_audio_140 = any(f["format_id"] == "140" for f in formats)

        for f in formats:
            if f.get("vcodec") != "none":
                resolution = f.get("resolution") or f"{f.get('width')}x{f.get('height')}"
                acodec = f.get("acodec", "none")
                fid = f["format_id"]

                if acodec == "none" and has_audio_140:
                    label = f"{resolution} - mp4 + m4a"
                    format_id = f"{fid}+140"
                elif acodec != "none":
                    label = f"{resolution} - video + audio"
                    format_id = fid
                else:
                    continue

                if label not in global_format_map:
                    global_format_map[label] = format_id
                    labels.append(label)

    return gr.update(choices=labels, value=labels[0] if labels else None)

def get_real_resolution(filepath):
    try:
        probe = ffmpeg.probe(filepath)
        stream = next((s for s in probe['streams'] if s['codec_type'] == 'video'), None)
        if stream:
            return f"{stream['width']}x{stream['height']}"
    except Exception as e:
        print(f"ffmpeg error: {e}")
    return "Unknown"

def download_merged_and_transcribe(url, selected_label):
    format_id = global_format_map.get(selected_label)
    if not format_id:
        return ["❌ 포맷을 찾을 수 없습니다."], "실패"

    # Step 1. mp4 다운로드
    mp4_output = os.path.join(output_dir, "%(title).100s.%(ext)s")
    subprocess.run([
        "yt-dlp", "-f", format_id,
        "--merge-output-format", "mp4",
        "-o", mp4_output,
        url
    ], check=True)

    files = [os.path.join(output_dir, f) for f in os.listdir(output_dir) if f.endswith(".mp4")]
    files.sort(key=os.path.getmtime, reverse=True)
    mp4_file = files[0] if files else None
    res = get_real_resolution(mp4_file)

    # Step 2. m4a 오디오만 따로 다운로드
    audio_path = os.path.join(output_dir, "audio.m4a")
    subprocess.run([
        "yt-dlp", "-f", "140",
        "-o", audio_path,
        url
    ], check=True)

    # Step 3. Whisper로 텍스트 변환
    model = whisper.load_model("base")
    result = model.transcribe(audio_path)
    txt_path = os.path.splitext(mp4_file)[0] + ".txt"
    with open(txt_path, "w", encoding="utf-8") as f:
        f.write(result["text"])

    return [mp4_file, txt_path], f"✅ 실제 해상도: {res}"

# ▶ Gradio 앱
with gr.Blocks() as app:
    gr.Markdown("# 🎬 유튜브 다운로드 + Whisper 자막")

    url_input = gr.Textbox(label="유튜브 URL")
    get_formats_btn = gr.Button("🔍 포맷 불러오기")
    format_radio = gr.Radio(label="🎞️ 해상도 및 포맷 선택", choices=[], interactive=True)
    download_btn = gr.Button("⬇️ 다운로드 및 자막 생성")
    file_output = gr.Files(label="📁 다운로드된 파일")
    resolution_display = gr.Textbox(label="ℹ️ 결과 요약", interactive=False)

    get_formats_btn.click(fn=fetch_merged_formats, inputs=url_input, outputs=format_radio)
    download_btn.click(fn=download_merged_and_transcribe, inputs=[url_input, format_radio], outputs=[file_output, resolution_display])

app.launch(share=True)