ChatGPTとVOICEVOXでNHKニュースを聞く

 ChatGPTとVOICEVOXでNHKのニュース記事をラジオみたいな感覚で聞けるプログラムを作ってみました。環境構築はよくわからないので説明全然してません。頑張ってください。WSLでやってます。予期しない動作でお金がいっぱいとられても自己責任でお願いします。

環境

 まずはVOICEVOXのAPIをなんとかして使えるようにします。以下の記事を参考にしました。

 この記事通りエンジンを起動するところまでやってください。GPUは使わなくていいと思います。
 ChatGPTの方は無限に紹介記事あるのでなんでもいいでしょう。SeleniumのためのChrome driverとかも必要です。

コード

 いんぽーと

from selenium import webdriver
from bs4 import BeautifulSoup
import requests
import chromedriver_binary

import time
import json

import wave
from playsound import playsound

import openai

 まずはChatGPTの問い合わせコードです。ほぼこの記事からのコピペですが、使用トークン数が分かるようにしています。あと使ってないけどてんぷらちゃあも。

openai.api_key = "あぴきー"
def completion(new_message_text:str, settings_text:str = '', past_messages:list = [], temperature=1):
    if len(past_messages) == 0 and len(settings_text) != 0:
        system = {"role": "system", "content": settings_text}
        past_messages.append(system)
    new_message = {"role": "user", "content": new_message_text}
    past_messages.append(new_message)

    result = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=past_messages,
        temperature=temperature
    )
    response_message = {"role": "assistant", "content": result.choices[0].message.content}
    past_messages.append(response_message)
    response_message_text = result.choices[0].message.content
    return response_message_text, past_messages, result["usage"]["total_tokens"]

 最新ニュース記事を取得します。Seleniumを使うのはブラウザで読み込まないと記事いっぱいとってこれないからです。

driver = webdriver.Chrome()   
driver.get("https://www3.nhk.or.jp/news/catnew.html#!/10/")
time.sleep(1)
html = driver.page_source
driver.close()

soup = BeautifulSoup(html, 'html.parser')
links = [url.get('href') for url in soup.find_all('a')]
links = ["https://www3.nhk.or.jp" + news for news in links if "/html/" in news]
links = list(set(links))

 ChatGPTへのプロンプト。プロンプトはよくわからないので適当です。

system_settings = """記事の内容を入力します。
あなたは入力された記事を要約して少女風のかわいらしい話し言葉に変えてください。
例:
あのね、
すごいでしょ?
できたんだって!
びっくりだよね~
"""

 メインのコード

start_time = False
for link in links:
    res = requests.get(link)
    res.encoding = res.apparent_encoding
    soup = BeautifulSoup(res.text, 'html.parser')
    
    try:
        #タイトル、本文
        question = soup.find(class_="content--detail-body").get_text()
        title = soup.find(class_="content--title").get_text().replace("\n","")
        new_message, _, tokens = completion(question, system_settings, [], temperature=1)
    except:
        continue

    print(title, f":{tokens}トークン")
    print("(" + link + ")")
    print(new_message)
    print()

    #音声合成
    res1 = requests.post('http://127.0.0.1:50021/audio_query',params = {'text': title + " " + new_message, 'speaker':8})
    res2 = requests.post('http://127.0.0.1:50021/synthesis',params = {'speaker': 8},data=json.dumps(res1.json()))
    data = res2.content
    
    #前の音声が終わるまで待つ
    end_time = time.perf_counter()
    if start_time and end_time - start_time < play_time:
        time.sleep(start_time - end_time + play_time)
    time.sleep(1)
    
    with open('output.wav', mode='wb') as f:
        f.write(data)
    with wave.open('output.wav', mode='r') as f:
        play_time =  f.getnframes() / f.getframerate()
    
    #再生
    start_time = time.perf_counter()
    playsound("output.wav", False)

 playsoundで非同期に再生して、記事間で隙間ができないようにしています。音声処理に関しては知識がないのでこんな感じでいいのかよく分かりません。記事の一部で形式が違ってうまく本文が取り込めないものや、ChatGPTのトークン数制限に引っかかるものがあるので、適当に例外処理しています。

生成例:

カーディナルス ヌートバー 左手親指を痛め 開幕2戦目欠場 :635トークン (https://www3.nhk.or.jp/news/html/20230402/k10014026831000.html)
わあ、あのね、WBCっていうすごい大会で活躍したカーディナルスのラーズ・ヌートバー選手が、開幕戦の後に左手の親指を痛めて2戦目に出場できなかったの。でも、けっこう元気な感じで「ほとんど腫れていないし大したことはない。バットも振れるよ!」って言ってたよ!

 こんな感じです。ヌートバーまじか;;

うまくいってないもの。

大阪桐蔭 連覇ならず“王者の敗因は中だるみ”【解説】 :1030トークン (https://www3.nhk.or.jp/news/html/20230331/k10014025981000.html)
わたしたちの大好きな大阪桐蔭高校野球部が、センバツでの2回目の連覇をあきらめることになってしまったよ。キャプテンの前田悠伍投手は、「中だるみの試合になってしまいました。自分たちの弱さがそこにあります」と、とても悔しそうに話したの。でも、彼らは夏に向けてまた頑張るって言ってたよ!私たちも一緒に応援しようね♪

 「わたしたちの大好きな」、とか「あきらめることになってしまった」、とかちょっとおかしいですね。

おわりに

 英語圏のニュースサイトでやった方が有意義じゃね?