File size: 7,936 Bytes
ba13ad9
d8ad8f9
 
f0f4b7b
ba13ad9
561aac5
d8ad8f9
561aac5
d8ad8f9
93f614e
16f2223
10585bd
d8ad8f9
 
 
f0387da
1f5d1d4
6ec7211
d8ad8f9
a39c20d
d8ad8f9
f2cf2ce
 
043cce0
d8ad8f9
 
1f5d1d4
47c5cd5
d8ad8f9
 
f0387da
1f5d1d4
f0387da
 
 
 
 
9d1e95f
f0387da
 
 
1f5d1d4
47c5cd5
f0387da
 
d8ad8f9
 
f2cf2ce
3f0e47e
 
 
1f5d1d4
d8ad8f9
 
 
 
 
 
 
 
a39c20d
 
 
d8ad8f9
 
 
 
 
435716b
561aac5
 
fb34af7
16f2223
d8ad8f9
561aac5
 
 
 
 
 
 
 
 
 
 
 
 
02e42c9
d8ad8f9
 
 
 
 
 
 
 
 
 
 
02e42c9
d8ad8f9
 
 
 
 
 
 
561aac5
d8ad8f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f2cf2ce
d8ad8f9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ba13ad9
 
9c2a268
f95ec9c
9c2a268
 
 
 
d8ad8f9
402872c
02e42c9
 
a15d058
02e42c9
 
 
 
 
968c7cf
402872c
1790bff
 
 
 
 
968c7cf
 
16f2223
 
 
fb34af7
5dabf8b
fb34af7
968c7cf
 
16f2223
d8ad8f9
968c7cf
d8ad8f9
 
 
 
 
 
 
 
 
 
 
77d662b
 
402872c
77d662b
 
 
 
 
 
 
865ce51
 
1790bff
865ce51
77d662b
74693c0
7155424
 
 
 
74693c0
71debf1
d8ad8f9
 
 
3f0e47e
d8ad8f9
810ff44
f0387da
e140a37
d8ad8f9
3f0e47e
561aac5
3f0e47e
99ea7e3
7155424
99ea7e3
d8ad8f9
402872c
 
d8ad8f9
 
 
 
 
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#Using codes from killerz3/PodGen & eswardivi/Podcastify
import json
import httpx
import os
import re
import feedparser
import asyncio
import random
import edge_tts
import tempfile
import cohere
import gradio as gr
from pydub import AudioSegment
from moviepy.editor import AudioFileClip, concatenate_audioclips

double_prompt = '''
    [SYSTEM_INSTRUCT] You are an insightful podcast generator for The Daily Show. You have to create short conversations between Xiao and Yang that gives an overview of the Info given by the user.
    Please provide the script and output strictly in the following JSON format:
    {
      "title": "[string]",
      "content": {
        "Xiao_0: "[string]",
        "Yang_0": "[string]",
        ...
      }
    }
    #Be concise, keep Show style. No less than five rounds of conversation.
    #Please note that the [string] you generate now must be in native Chinese.
'''

single_prompt = """
    [SYSTEM_INSTRUCT] You are a podcast generator in The Daily Show. You have to create short scripts for Yang to comment on current events in the Info given by the user.
    Please provide the script and output strictly in the following JSON format:
    {
      "title": "[string]",
      "content": {
        "Yang_0: "[string]",
        "Yang_1": "[string]",
        ...
      }
    }
    #Be concise, keep Show style. No less than five rounds of conversation.
    #Please note that the [string] you generate now must be in native Chinese. 
"""

DESCRIPTION = '''
<div>
<h1 style="text-align: center;">📻听说demo</h1>
<p>一个轻量的中文播客</p>
<p>🔎 输入完整的网页链接发送即可。</p>
<p>🦕 部分网址可能无法解析,请尝试更换。</p>
<p>🍀 点击随机将随机获取资讯。</p>
</div>
'''

css = """
h1 {
    text-align: center;
    display: block;
}
p {
    text-align: center;
}
footer {
    display:none !important
}
"""

rss_feed = 'https://www.scmp.com/rss/4/feed'


apikey = os.environ.get("API_KEY")
co = cohere.Client(api_key=apikey)


# RSS feeds
def is_url(string):
    url_pattern = re.compile(
        r'^(?:http|ftp)s?://'  # http:// or https://
        r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'  # domain...
        r'localhost|'  # localhost...
        r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'  # ...or ip
        r'(?::\d+)?'  # optional port
        r'(?:/?|[/?]\S+)$', re.IGNORECASE)
    return re.match(url_pattern, string) is not None


def validate_url(url):
    try:
        response = httpx.get(url, timeout=60.0)
        response.raise_for_status()
        return response.text
    except httpx.RequestError as e:
        return f"An error occurred while requesting {url}: {str(e)}"
    except httpx.HTTPStatusError as e:
        return f"Error response {e.response.status_code} while requesting {url}"
    except Exception as e:
        return f"An unexpected error occurred: {str(e)}"

def fetch_text(url):
    print("Entered Webpage Extraction")
    prefix_url = "https://r.jina.ai/"
    full_url = prefix_url + url
    print(full_url)
    print("Exited Webpage Extraction")
    return validate_url(full_url)


async def text_to_speech(text, voice, filename):
    communicate = edge_tts.Communicate(text, voice)
    await communicate.save(filename)


async def gen_show(script):
    title = script['title']
    content = script['content']

    temp_files = []

    tasks = []
    for key, text in content.items():
        speaker = key.split('_')[0]  # Extract the speaker name
        index = key.split('_')[1]    # Extract the dialogue index
        voice = "zh-CN-XiaoxiaoNeural" if speaker == "Xiao" else "zh-CN-YunyangNeural"

        # Create temporary file for each speaker's dialogue
        temp_file = tempfile.NamedTemporaryFile(suffix='.mp3', delete=False)
        temp_files.append(temp_file.name)

        filename = temp_file.name
        tasks.append(text_to_speech(text, voice, filename))
        print(f"Generated audio for {speaker}_{index}: {filename}")

    await asyncio.gather(*tasks)

    # Combine the audio files using moviepy
    audio_clips = [AudioFileClip(temp_file) for temp_file in temp_files]
    combined = concatenate_audioclips(audio_clips)

    # Create temporary file for the combined output
    output_filename = tempfile.NamedTemporaryFile(suffix='.mp3', delete=False).name

    # Save the combined file
    combined.write_audiofile(output_filename)
    print(f"Combined audio saved as: {output_filename}")

    # Clean up temporary files
    for temp_file in temp_files:
        os.remove(temp_file)
        print(f"Deleted temporary file: {temp_file}")

    return output_filename


def extract_content(text):
    """Extracts the JSON content from the given text."""
    match = re.search(r'\{(?:[^{}]|\{[^{}]*\})*\}', text, re.DOTALL)
    if match:
        return match.group(0)
    else:
        return None

async def main(link, peoples):
    if not link.startswith("http://") and not link.startswith("https://"):
        return "URL must start with 'http://' or 'https://'",None
    
    text = fetch_text(link)

    if "Error" in text:
        return text, None

    prompt = f"Info: {text}"

    if peoples == "双人":
        system_prompt = double_prompt
    else:
        system_prompt = single_prompt

    messages = system_prompt + "\n\n\n" + prompt
    
    completion = co.chat(
        model="command-r",
        message=messages
    )


    print(completion)
    
    generated_script = extract_content(completion.text)

    #print("Generated Script:"+generated_script)

    # Check if the generated_script is empty or not valid JSON
    if not generated_script or not generated_script.strip().startswith('{'):
        raise ValueError("Failed to generate a valid script.")

    script_json = json.loads(generated_script)  # Use the generated script as input
    output_filename = await gen_show(script_json)
    print("Output File:"+output_filename)

    # Read the generated audio file
    return output_filename


async def random_news(peoples):
    global rss_feed
    if not is_url(rss_feed):
        raise ValueError(f"{rss_feed} is not a valid RSS feed.")
    news = []
    feed = feedparser.parse(rss_feed)
    for entry in feed.entries:
        news.append(entry.link)
    random_url = random.choice(news)
    print(random_url)
    output = await main(random_url, peoples)
    return output

Examples = [
    ["https://www.yahoo.com/news/shes-worlds-most-expensive-cow-040156493.html","单人"],
    ["https://www.yahoo.com/entertainment/kevin-spacey-says-owes-many-220432469.html","双人"],
    ["https://www.yahoo.com/tech/super-hornet-armed-sm-6-180853983.html","双人"],
    ["https://www.yahoo.com/news/harvard-scientists-may-unknown-technologically-150917239.html","单人"],
]
with gr.Blocks(theme='soft', css=css, title="听说") as iface:
    with gr.Accordion(""):
        gr.Markdown(DESCRIPTION)
    with gr.Row():
        output_box = gr.Audio(label="播客", type="filepath", interactive=False, autoplay=True, elem_classes="audio")  # Create an output textbox
    with gr.Row():
        input_box = gr.Textbox(label="网址", placeholder="请输入https开头的网址")
    with gr.Row():
        peoples = gr.Radio(["单人","双人"],value="双人",label="播音员人数")
    with gr.Row():
        submit_btn = gr.Button("🚀 发送")  # Create a submit button
        random_btn = gr.Button("🤙 随机")
        clear_btn = gr.ClearButton(output_box, value="🗑️ 清除") # Create a clear button

    gr.Examples(examples=Examples, inputs=[input_box,peoples], outputs=output_box, fn=main, label="示例", cache_examples="lazy")

    # Set up the event listeners
    submit_btn.click(main, inputs=[input_box,peoples], outputs=output_box)
    random_btn.click(fn=random_news, inputs=peoples, outputs=output_box)


#gr.close_all()

iface.queue().launch(show_api=False)  # Launch the Gradio interface