File size: 8,196 Bytes
073d3e8
 
 
 
 
4a45930
073d3e8
57d8b30
073d3e8
 
 
 
 
bb03802
 
073d3e8
 
 
 
 
 
 
 
 
 
 
 
57d8b30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
073d3e8
 
57d8b30
073d3e8
 
 
 
 
 
 
 
 
 
 
57d8b30
 
 
 
073d3e8
 
 
 
1966494
073d3e8
57d8b30
 
 
 
 
 
 
 
 
 
 
 
 
1966494
 
 
 
 
 
 
 
 
 
 
57d8b30
1966494
 
 
 
 
 
57d8b30
1966494
57d8b30
1966494
57d8b30
 
1966494
 
57d8b30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
073d3e8
 
4a45930
073d3e8
 
 
57d8b30
 
 
073d3e8
57d8b30
073d3e8
57d8b30
1966494
57d8b30
 
 
 
 
 
 
bb03802
 
57d8b30
 
 
 
 
 
 
 
 
bb03802
 
57d8b30
 
 
 
 
 
 
 
 
 
bb03802
 
57d8b30
 
 
 
 
 
 
 
 
 
 
 
 
bb03802
 
073d3e8
 
 
 
bb03802
073d3e8
 
4a45930
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import gradio as gr
from gradio_client import Client
import json
import logging
import ast
import openai
import os
import random
import re

logging.basicConfig(filename='youtube_script_extractor.log', level=logging.DEBUG, 
                    format='%(asctime)s - %(levelname)s - %(message)s')

openai.api_key = os.getenv("OPENAI_API_KEY")

def parse_api_response(response):
    try:
        if isinstance(response, str):
            response = ast.literal_eval(response)
        if isinstance(response, list) and len(response) > 0:
            response = response[0]
        if not isinstance(response, dict):
            raise ValueError(f"μ˜ˆμƒμΉ˜ λͺ»ν•œ 응닡 ν˜•μ‹μž…λ‹ˆλ‹€. 받은 데이터 νƒ€μž…: {type(response)}")
        return response
    except Exception as e:
        raise ValueError(f"API 응닡 νŒŒμ‹± μ‹€νŒ¨: {str(e)}")

def split_sentences(text):
    sentences = re.split(r"(λ‹ˆλ‹€|μ—μš”|κ΅¬λ‚˜|ν•΄μš”|κ΅°μš”|κ² μ–΄μš”|μ‹œμ˜€|해라|μ˜ˆμš”|μ•„μš”|λ°μš”|λŒ€μš”|μ„Έμš”|μ–΄μš”|κ²Œμš”|κ΅¬μš”|κ³ μš”|λ‚˜μš”|ν•˜μ£ )(?![\w])", text)
    combined_sentences = []
    current_sentence = ""
    for i in range(0, len(sentences), 2):
        if i + 1 < len(sentences):
            sentence = sentences[i] + sentences[i + 1]
        else:
            sentence = sentences[i]
        if len(current_sentence) + len(sentence) > 100:
            combined_sentences.append(current_sentence.strip())
            current_sentence = sentence.strip()
        else:
            current_sentence += sentence
        if sentence.endswith(('.', '?', '!')):
            combined_sentences.append(current_sentence.strip())
            current_sentence = ""
    if current_sentence:
        combined_sentences.append(current_sentence.strip())
    return combined_sentences

def get_youtube_script(url):
    logging.info(f"슀크립트 μΆ”μΆœ μ‹œμž‘: URL = {url}")

    client = Client("whispersound/YT_Ts_R")

    try:
        logging.debug("API 호좜 μ‹œμž‘")
        result = client.predict(youtube_url=url, api_name="/predict")
        logging.debug("API 호좜 μ™„λ£Œ")

        parsed_result = parse_api_response(result)
        
        title = parsed_result["data"][0]["title"]
        transcription_text = parsed_result["data"][0]["transcriptionAsText"]
        sections = parsed_result["data"][0]["sections"]

        logging.info("슀크립트 μΆ”μΆœ μ™„λ£Œ")
        return title, transcription_text, sections

    except Exception as e:
        error_msg = f"슀크립트 μΆ”μΆœ 쀑 였λ₯˜ λ°œμƒ: {str(e)}"
        logging.exception(error_msg)
        return "", "", []

def call_api(prompt, max_tokens, temperature, top_p):
    try:
        response = openai.ChatCompletion.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
            max_tokens=max_tokens,
            temperature=temperature,
            top_p=top_p
        )
        return response['choices'][0]['message']['content']
    except Exception as e:
        logging.exception("LLM API 호좜 쀑 였λ₯˜ λ°œμƒ")
        return "μš”μ•½μ„ μƒμ„±ν•˜λŠ” λ™μ•ˆ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ‚˜μ€‘μ— λ‹€μ‹œ μ‹œλ„ν•΄ μ£Όμ„Έμš”."

def summarize_section(section_text):
    prompt = f"""
λ‹€μŒ 유튜브 λŒ€λ³Έ μ„Ήμ…˜μ˜ 핡심 λ‚΄μš©μ„ κ°„κ²°ν•˜κ²Œ μš”μ•½ν•˜μ„Έμš”:
1. ν•œκΈ€λ‘œ μž‘μ„±ν•˜μ„Έμš”.
2. μ£Όμš” 논점과 μ€‘μš”ν•œ 세뢀사항을 ν¬ν•¨ν•˜μ„Έμš”.
3. μš”μ•½μ€ 2-3λ¬Έμž₯으둜 μ œν•œν•˜μ„Έμš”.

μ„Ήμ…˜ λ‚΄μš©:
{section_text}
"""
    return call_api(prompt, max_tokens=150, temperature=0.3, top_p=0.9)

def format_time(seconds):
    minutes, seconds = divmod(seconds, 60)
    hours, minutes = divmod(minutes, 60)
    return f"{int(hours):02d}:{int(minutes):02d}:{int(seconds):02d}"

def generate_timeline_summary(sections):
    timeline_summary = ""
    for i, section in enumerate(sections, 1):
        start_time = format_time(section['start_time'])
        summary = summarize_section(section['text'])
        timeline_summary += f"{start_time} {i}. {summary}\n\n"
    return timeline_summary

def summarize_text(text):
    prompt = f"""
1. λ‹€μŒ μ£Όμ–΄μ§€λŠ” 유튜브 λŒ€λ³Έμ˜ 핡심 μ£Όμ œμ™€ λͺ¨λ“  μ£Όμš” λ‚΄μš©μ„ μƒμ„Έν•˜κ²Œ μš”μ•½ν•˜λΌ
2. λ°˜λ“œμ‹œ ν•œκΈ€λ‘œ μž‘μ„±ν•˜λΌ
3. μš”μ•½λ¬Έλ§ŒμœΌλ‘œλ„ μ˜μƒμ„ 직접 μ‹œμ²­ν•œ 것과 λ™μΌν•œ μˆ˜μ€€μœΌλ‘œ λ‚΄μš©μ„ 이해할 수 μžˆλ„λ‘ μƒμ„Ένžˆ μž‘μ„±
4. 글을 λ„ˆλ¬΄ μ••μΆ•ν•˜κ±°λ‚˜ ν•¨μΆ•ν•˜μ§€ 말고, μ€‘μš”ν•œ λ‚΄μš©κ³Ό 세뢀사항을 λͺ¨λ‘ 포함
5. λ°˜λ“œμ‹œ λŒ€λ³Έμ˜ 흐름과 논리 ꡬ쑰λ₯Ό μœ μ§€
6. λ°˜λ“œμ‹œ μ‹œκ°„ μˆœμ„œλ‚˜ μ‚¬κ±΄μ˜ μ „κ°œ 과정을 λͺ…ν™•ν•˜κ²Œ 반영
7. λ“±μž₯인물, μž₯μ†Œ, 사건 λ“± μ€‘μš”ν•œ μš”μ†Œλ₯Ό μ •ν™•ν•˜κ²Œ μž‘μ„±
8. λŒ€λ³Έμ—μ„œ μ „λ‹¬ν•˜λŠ” κ°μ •μ΄λ‚˜ λΆ„μœ„κΈ°λ„ 포함
9. λ°˜λ“œμ‹œ 기술적 μš©μ–΄λ‚˜ μ „λ¬Έ μš©μ–΄κ°€ μžˆμ„ 경우, 이λ₯Ό μ •ν™•ν•˜κ²Œ μ‚¬μš©
10. λŒ€λ³Έμ˜ λͺ©μ μ΄λ‚˜ μ˜λ„λ₯Ό νŒŒμ•…ν•˜κ³ , 이λ₯Ό μš”μ•½μ— λ°˜λ“œμ‹œ 반영
11. 전체글을 보고 

---

이 ν”„λ‘¬ν”„νŠΈκ°€ 도움이 λ˜μ‹œκΈΈ λ°”λžλ‹ˆλ‹€.    
    \n\n
    {text}"""

    try:
        return call_api(prompt, max_tokens=10000, temperature=0.3, top_p=0.9)
    except Exception as e:
        logging.exception("μš”μ•½ 생성 쀑 였λ₯˜ λ°œμƒ")
        return "μš”μ•½μ„ μƒμ„±ν•˜λŠ” λ™μ•ˆ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. λ‚˜μ€‘μ— λ‹€μ‹œ μ‹œλ„ν•΄ μ£Όμ„Έμš”."

with gr.Blocks() as demo:
    gr.Markdown("## YouTube 슀크립트 μΆ”μΆœ 및 μš”μ•½ 도ꡬ")

    youtube_url_input = gr.Textbox(label="YouTube URL μž…λ ₯")
    analyze_button = gr.Button("λΆ„μ„ν•˜κΈ°")
    script_output = gr.HTML(label="슀크립트")
    timeline_output = gr.HTML(label="νƒ€μž„λΌμΈ μš”μ•½")
    summary_output = gr.HTML(label="전체 μš”μ•½")
    
    cached_data = gr.State({"url": "", "title": "", "script": "", "sections": []})

    def extract_and_cache(url, cache):
        if url == cache["url"]:
            return cache["title"], cache["script"], cache["sections"], cache

        title, script, sections = get_youtube_script(url)
        new_cache = {"url": url, "title": title, "script": script, "sections": sections}
        return title, script, sections, new_cache

    def display_script(title, script):
        if not script:
            return "<p>슀크립트λ₯Ό μΆ”μΆœν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€. URL을 ν™•μΈν•˜κ³  λ‹€μ‹œ μ‹œλ„ν•΄ μ£Όμ„Έμš”.</p>"
        formatted_script = "\n".join(split_sentences(script))
        script_html = f"""<h2 style='font-size:24px;'>{title}</h2>
        <details>
            <summary><h3>원문 슀크립트 (ν΄λ¦­ν•˜μ—¬ 펼치기)</h3></summary>
            <div style="white-space: pre-wrap;">{formatted_script}</div>
        </details>"""
        return script_html

    def display_timeline(sections):
        if not sections:
            return "<p>νƒ€μž„λΌμΈμ„ μƒμ„±ν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€. 슀크립트 μΆ”μΆœμ— μ‹€νŒ¨ν–ˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€.</p>"
        timeline_summary = generate_timeline_summary(sections)
        timeline_html = f"""
        <h3>νƒ€μž„λΌμΈ μš”μ•½:</h3>
        <div style="white-space: pre-wrap; max-height: 400px; overflow-y: auto; border: 1px solid #ccc; padding: 10px;">
            {timeline_summary}
        </div>
        """
        return timeline_html

    def generate_summary(script):
        if not script:
            return "<p>전체 μš”μ•½μ„ μƒμ„±ν•˜μ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€. 슀크립트 μΆ”μΆœμ— μ‹€νŒ¨ν–ˆμ„ 수 μžˆμŠ΅λ‹ˆλ‹€.</p>"
        summary = summarize_text(script)
        summary_html = f"""
        <h3>전체 μš”μ•½:</h3>
        <div style="white-space: pre-wrap; max-height: 400px; overflow-y: auto; border: 1px solid #ccc; padding: 10px;">
            {summary}
        </div>
        """
        return summary_html

    def analyze(url, cache):
        title, script, sections, new_cache = extract_and_cache(url, cache)
        script_html = display_script(title, script)
        timeline_html = display_timeline(sections)
        summary_html = generate_summary(script)
        return script_html, timeline_html, summary_html, new_cache

    analyze_button.click(
        analyze, 
        inputs=[youtube_url_input, cached_data], 
        outputs=[script_output, timeline_output, summary_output, cached_data]
    )

demo.launch(share=True)