0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIによるラジオを作ってみた

Last updated at Posted at 2024-11-18

AIによるラジオ作ってみた

airadio-image.jpg

AIってたまに突飛な発想しますよね。聞いてるのはそういうことじゃないんだよなあって感覚と、そのズレがおもしろく思えたりもします。

もしAI同士で会話させたら、どれほどの会話の内容になるのか気になったため、今回はAI同士で会話をさせるラジオを作成してみました!

その名も「田中と中田のAIラジオ」

「この番組では、AIパーソナリティ田中と中田が、リスナーの皆さんから寄せられたお便りやお題に全力でお応えします。ふつおた、大喜利、AIに言わせたいことなど、内容はすべてリスナーとAIが共同で作り上げるインタラクティブなラジオ体験です。」

この紹介文もAIに書いてもらいました。

プログラム

ChatGPTのAPI、Python、VOICEVOX、Premire Pro、fireflyで作成しました。

まずメールの内容をスプレッドシートに書き出し微調整したのち、Pythonでスプレッドシートを読み込みました。NGワードをひたすら書き出していって、変なものを読み込まないようにしました。作っていておもしろかったです。

書き出すシステムはこちらを参考にさせていただきました。

https://note.com/omame727/n/neeea81533906

スクリーンショット 2024-11-18 15.17.32.png
(↑読み込むスプレッドシート)

PythonではChatGPTのAPIを会話のターンごとに呼び出して、台本を作成しました。
こうすることでAIの発言に対して、AIが考えて発言する仕組みを実現しました。
ただしオープニングや、エンディング、コーナーの頭の方は伝えないといけない情報があるため、文章をある程度作成しましたが、その後のやり取りは完全にAIによって作成されました。

台本ができたら、ずんだもんで有名なVOICEVOXで音声を作成しました。
音声ファイルを扱うのに、pydubのモジュールを使いました。

最終的に以下の二つのファイルを出力するようにしています。
・音声ファイル(output.wav)
・本文にタイトルと要約をつけたテキストファイル(content.txt)

Pythonのコードはこちら。

import requests
import os
import random
from openai import OpenAI
#import sounddevice as sd
#import soundfile as sf
import gspread
from pydub import AudioSegment
from pydub.playback import play

def get_sheet():
    """ 
    スプレッドシートを取得する関数 
    
    """
    # 認証してGoogleスプレッドシートにアクセス
    gc = gspread.service_account(filename="./.config/airadio.json")
    # スプレッドシートを開く (スプレッドシートのURLの最後にあるIDを使用)
    sh = gc.open_by_key(os.getenv("SPREADSHEET_URL"))
    #シートを取得
    ws = sh.worksheet("data")
    return ws

def make_ngwords_list():
    """ 
    NGリストを作成 
    
    """
    ng_words = ['事故','死亡','骨折','重傷','殺害','傷害','暴力','被害者','放送事故',\
            'ポルノ','アダルト','セックス','バイブレーター','マスターベーション','オナニー','スケベ','羞恥','セクロス',\
            'エッチ','SEX','風俗','童貞','ペニス','巨乳','ロリ','触手','羞恥','ノーブラ',\
            '大麻','麻薬',\
            '基地外','','死ね','殺す','しね','ころす',\
            'shit','piss','fuck','cunt','cocksucker','motherfucker','tits',\
            'R18','R-18','例のアレ','真夏の夜の淫夢','アヘ顔','亀頭','へんたい','ヘンタイ','変態','パンチラ',\
            '','アッー','アナル','アヘ顔','イマラ','','運営のお気に入り','エッチ','MKT','おっぱい','オッパイ','オナシャス','ガチムチ',\
            '','元祖羞恥心','亀頭','KMR','','クルルァ','グロ','ゲイ','ケツ','','シコ','自分を売る',\
            '18禁','春画','処女','ショタ','パイパン','フェラ','ふたなり','ペニス','へんたい','ヘンタイ','変態','ホモ',\
            'マラ','まんこ','マンコ','野獣','幼女','ょぅ','レイプ','レズ','ろり','ロリ','セックス','せっくす',\
            'レスリングシリーズ','来いよアグネス','紳士','運営仕事しろ','例のプール',\
        ]
    return ng_words

def check_ngwords(words_list, ng_words):
    """
    リストからNGリストに載ってる単語を取り除く。

    """
    filtered_array = [
    row for row in words_list if not any(
        any(ng_word in item for item in row) for ng_word in ng_words
    )
]
    return filtered_array

def pick_up_mail(ws,pick_up_num,words_list):
    """ 
    リストからランダムにpick_up_um個要素を抽出し、
    何を抽出したかスプレッドシートに記録する。
    
    """
    not_used_list=[x for x in words_list if str(x[4])=="0"]

    if len(not_used_list) >= pick_up_num:
        choices = random.sample(not_used_list,pick_up_num)
    else:
        return []

    #スプレッドシート更新
    for c in choices:
        row_num = int(c[0])+1
        ws.update_acell(f'E{row_num}',"1")

    return choices

def get_reply(client,content):
    """
    contentの中身をChatGPTに質問する。

    """
    res= client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{
            "role":"user",
            "content":(content)
        }]
    )
    reply = res.choices[0].message.content
    return reply

def input_to_male(content):
    """
    田中に投げかける質問文を生成する。

    """
    content = str(content)
    text = "あなたは田中という男の芸人だ。\
        友達の中田と、簡単な会話をしている。\
        次の中田の言葉に対して、文脈に即した上で、短文でできる限り馬鹿馬鹿しい返事をして。また冒頭に挨拶をする必要はありません。\n"
    return text + content

def input_to_female(content):
    """
    中田に投げかける質問文を生成する。
    
    """
    content = str(content)
    text = "あなたは中村という、常に冷静な女性だ。\
        友達の田村と、簡単な会話をしています。\
        次の田村の言葉に対して、文脈に即した上で、短文でできる限り現実的な返事をして。また冒頭に挨拶をする必要はありません。\n"
    return text + content

def man_talks(client,content):
    """
    田中に質問文を投げかけて、回答を生成する。
    
    """
    txt = input_to_male(content)
    reply = get_reply(client,txt)
    return reply

def female_talks(client,content):
    """
    中田に質問文を投げかけて、回答を生成する。
    
    """
    txt = input_to_female(content)
    reply = get_reply(client,txt)
    return reply


def talk_with_each_other(client,first_content,repeat_time=2):
    """
    田中と中田の会話のやりとりをrepeat_time分、生成する。
    
    """
    conv_list = []
    content = first_content
    i = 0
    while i < repeat_time:
        content = man_talks(client,content)
        conv_list.append(content)
        content = female_talks(client,content)
        conv_list.append(content)
        i += 1
    return conv_list

def is_charactor_num(num):
    """
    voicevoxに渡すキャラクター番号を、入力に応じて返す。
    """
    if num%2: #もし2で割り切れるなら、女性の声を当てる。
        return 9
    else: #もし2で割り切れないなら、男性の声を当てる。
        return 39



    
def get_audio_filepath(reply, charactor_num):
    """
    textをvoicevoxに読み上げてもらう。音声ファイルをwavで保存。
    
    """
    params = {"text":reply, "speaker":charactor_num}
    res = requests.post(
        f'http://localhost:50021/audio_query',
        params=params
    )
    res = requests.post(
        f'http://localhost:50021/synthesis',
        params=params,
        json = res.json()
    )
    voice = res.content
    file_path = f'audio/airadio-voice.wav'
    with open(file_path, "wb") as f:
        f.write(voice)
    return file_path

    #音声ファイルを再生
    #data,fs = sf.read(file_path)
    #sd.play(data,fs)
    #sd.wait()

def get_audio_file(file_path):
    """
    wavファイルを呼び出して、AudioSegmentに変換。
    
    """
    #音声ファイルをAudioSegmentで出力
    audio_file = AudioSegment.from_file(file_path)
    return audio_file

def get_audio_reply(reply, charactor_num):
    """
    voicevoxに読み上げてもらう音声をAudioSegmentに変換。
    
    """
    filepath = get_audio_filepath(reply, charactor_num)
    audio_reply = get_audio_file(filepath)
    return audio_reply

def make_them_speak(client,conv_list):
    """
    二人の会話のやりとりを交互に読み上げてもらう。
    
    """
    combined_audio_file = AudioSegment.empty()
    for i, conv in enumerate(conv_list):
        combined_audio_file += get_audio_reply(conv,is_charactor_num(i))
    return combined_audio_file

def get_mail_content(content_list):   
    """
    メールの内容を取得する。
    
    """
    radio_names = list(map(lambda x:x[5], content_list))
    for i in range(len(radio_names)):
        if radio_names[i].strip(" ") == "":
            radio_names[i] = "なし"
        else:
            radio_names[i] += "さん"
    odais = list(map(lambda x:x[6], content_list))
    return radio_names,odais
    
def oogiri(client,ws,oogiri_list):
    """
    大喜利のコーナーの文章を生成する。
    
    """
    pick_up_list = pick_up_mail(ws,2,oogiri_list)

    if len(pick_up_list) < 2:
        return []

    radio_names, odais = get_mail_content(pick_up_list)
    answers = []
    for odai in odais:
        answer = get_reply(client,f'あなたは芸人です。次のお題に対して大喜利してください。\n{odai}')
        answers.append(answer) 

    contents1 = ["大喜利ー!","えーと、このコーナーは、リスナーから募集したお題で大喜利をしていくコーナーです。",\
                "今週のお題はこちら!",\
                f'ラジオネーム{radio_names[0]}からいただきました。{odais[0]}', f'{answers[0]}']
    reply10 = female_talks(client,answer[0])
    reply11 = talk_with_each_other(client,reply10,repeat_time=2)
    contents2 = ["",f'えー続いて、ラジオネーム{radio_names[1]}からいただきました。{odais[1]}',\
                f'{answers[1]}']
    reply20 = female_talks(client,answer[1])
    reply21 = talk_with_each_other(client,reply20,repeat_time=2)

    return contents1 + [reply10] + reply11 + contents2 + [reply20] + reply21 


def iwasetaikoto(client,ws,iwasetaikoto_list):
    """
    AIに言わせたいことのコーナーの文章を生成する。
    
    """
    pick_up_list = pick_up_mail(ws,2,iwasetaikoto_list)

    if len(pick_up_list) < 2:
        return []
    
    radio_names, odais = get_mail_content(pick_up_list)

    contents1 = ["AIに言わせたいこと!","えーと、このコーナーは、我々AIパーソナリティーに読み上げてほしい文章を募集するコーナーです。",\
                "今週のお題はこちら!",\
                f'ラジオネーム{radio_names[0]}からいただきました。', f'{odais[0]}']
    reply10 = female_talks(client,odais[0])
    reply11 = talk_with_each_other(client,reply10,repeat_time=2)
    contents2 = ["",f'えーと、続いて、ラジオネーム{radio_names[1]}からいただきました。',\
                f'{odais[1]}']
    reply20 = female_talks(client,odais[1])
    reply21 = talk_with_each_other(client,reply20,repeat_time=1)

    return contents1 + [reply10] + reply11 + contents2 + [reply20] + reply21


def hutsuota(ws,hutsuota_list):
    """
    ふつおたのコーナーの文章を生成する。
    
    """
    pick_up_list = pick_up_mail(ws,1,hutsuota_list)
    if len(pick_up_list) == 0:
        return ""
    radio_name, odai = get_mail_content(pick_up_list)
    content = f'ふつおたが届いています。ラジオネーム、{radio_name[0]}からいただきました。\n{odai[0]}'
    return content

def get_title(client,content):
    """
    タイトルと要約を取得。
    
    """
    text = f'## 依頼\n次の文章を要約して、人を惹きつけるタイトルをつけてください。\
    \n## アウトプットのフォーマット\nタイトル:\n要約:\
    \n## 文章\n{content}'
    return get_reply(client,text)

def main():
    print("処理を開始します。")
    client = OpenAI()
    ws = get_sheet()
    mail_data = ws.get_all_values()
    ng_words = make_ngwords_list()

    #ふつおたがあれば最初に読む。最初のトークの内容を生成。
    hutsuota_list=[x for x in mail_data if "ふつおた" in x[2]]
    hutsuota_list = check_ngwords(hutsuota_list, ng_words)
    first_content = ""

    if len(hutsuota_list) != 0:
        first_content = hutsuota(ws,hutsuota_list)

    if first_content == "":
        topic_list = ["何か面白い話してよ。","最近どう?",
                      "最近見た夢あれば教えて。","最近ハマってるものある?","最近どこか行った?",
                      "最近何か食べた?","最近欲しいものある?"]
        first_content = random.sample(topic_list,1)
        
    print("ふつおたの処理完了")

    #大喜利コーナー
    oogiri_list=[x for x in mail_data if "大喜利" in x[2]]
    oogiri_list = check_ngwords(oogiri_list,ng_words)
    oogiri_conv = []
    if len(oogiri_list) >= 2: #大喜利のメールが二件以上あれば大喜利のコーナー実施。
        oogiri_conv = oogiri(client,ws,oogiri_list)
    print("大喜利の処理完了")

    #AIに言わせたいことのコーナー
    iwasetaikoto_list=[x for x in mail_data if ("言わせたい" in x[2])or("いわせたい" in x[2])]
    iwasetaikoto_list = check_ngwords(iwasetaikoto_list, ng_words)
    iwasetaikoto_conv = []
    if len(iwasetaikoto_list) >= 2: #AIに言わせたいことのメールが二件以上あれば大喜利のコーナー実施。
        iwasetaikoto_conv = iwasetaikoto(client,ws,iwasetaikoto_list)
    print("言わせたいことの処理完了")

    #オープニングのコーナーを生成。
    opening = ["こんにちは!AIの田中です。","こんにちはAIの中田です。",\
                "この番組はAIパーソナリティの田中と中田でお送りいたします。",\
                "内容はすべてリスナーの皆様とAIによって作られています。",\
                "よろしくお願いします。オープニング行きます、AIラジオ"]
    
    #エンディングのコーナーを生成。
    ending  =  ["AIラジオ、そろそろお別れのお時間です。","大喜利のお題やAIに言わせたいこと、ふつおたのメールお待ちしております。",\
               "件名にコーナー名を書いて送ってください。","宛先はTANAKATA、ドット、AIRADIO、アット、GMAIL、ドット、COM",
               "たなかた、ドット、AIラジオアットジーメールコムとお覚えください。","感想は、ハッシュタグ田中と中田のAIラジオをつけてポストしてください。"\
               "お願いしますー。","以上、ここまでのお相手は中田と","田中でした!今週も聞いてくれてありがとう!","来週もお楽しみに!","さよならー!"]
    
    #フリートーク(ふつおた)のコーナーを生成。
    conv_list = talk_with_each_other(client,first_content,repeat_time=6) 
    #conv_list = ['「うん、ウサギの耳を持つスパゲッティマシーンが欲しいんだ!」', '「それはユニークなアイデアね。どんな用途を考えているの?」', '「うん、カメラの代わりにスイカを使って、スイカの中から風景を撮ろうと思ってるんだ!」', '「面白いアイデアね、その発想でどんな風景が撮れるか楽しみ。」']
    print("フリートークの内容生成完了")

    #要約文・タイトルを生成。
    subtitle = opening + [first_content] + conv_list + oogiri_conv + iwasetaikoto_conv + ending 
    title = get_title(client,subtitle)
    text = title + "\n-----以下本文-----\n" + str(subtitle)
    print("----------------------")
    print(text)
    print("----------------------")
    print("全テキストの生成完了")

    #BGMの収録
    audio1 = get_audio_file("airadio-bgm.mp3").fade_out(1000)
    audio2 = get_audio_file("airadio-corner-bgm.mp3").fade_out(1000) - 10
    print("BGMの処理完了")


    #オープニングの収録
    opening_audio = make_them_speak(client,opening) + audio1
    print("オープニングの収録終了")

    #フリートークの収録
    free_talk_audio1 = get_audio_reply(first_content, 9)
    free_talk_audio2 = make_them_speak(client,conv_list)
    free_talk_audio = free_talk_audio1 + free_talk_audio2
    play(free_talk_audio)
    print("フリートークの収録終了")

    #大喜利の収録
    if len(oogiri_conv) >= 2:
        oogiri_audio = audio2 + make_them_speak(client,oogiri_conv).fade_out(1000)
    else:
        oogiri_audio = AudioSegment.empty()
    print("大喜利の収録終了")

    #我々に言わせたいことの収録
    if len(iwasetaikoto_conv) >= 2:
        iwasetaikoto_audio = audio2 + make_them_speak(client,iwasetaikoto_conv).fade_out(1000)
    else:
        iwasetaikoto_audio = AudioSegment.empty()
    print("言わせたいことの収録終了")

    #エンディングの収録
    ending_audio = audio1 + make_them_speak(client,ending)
    print("エンディングの収録終了")

    #音声ファイル処理
    file_title = title.split("\n")[0]
    file_title = file_title[5:]
    body = opening_audio + free_talk_audio + oogiri_audio + iwasetaikoto_audio + ending_audio
    play(body)
    body.export(f'output/{file_title}.wav', format="wav")

    #テキストファイルの処理
    with open(f'output/{file_title}.txt', "wb") as f:
        f.write(text.encode("utf-8"))
    print(f'全処理の完了。{file_title}.wav, {file_title}.txtを確認してください。')
   
if __name__=="__main__":
    main()

また、Githubにも載せてあります。
https://github.com/KANEI/AIradio

音声ファイルとテキストファイルを元に、fireflyでイメージ画像を再生して、Premire Proを使って動画を作成しました。

配信はこちら

Podcast

Youtube

感想

2回ほど作成してみましたが、内容がぶっとんでいてやはりおもしろかったです。
chatGPTのAPIを使用するいい練習になった気がします。
気になった方、おもしろいのでぜひ聴いてみてください!!

参照

・AITuberを作ってみよう!サプーが引退の危機?!〜 Pythonを使って自動で喋るVTuber 〜
 https://youtu.be/WVpvExDPgOE?si=FhazmJR_3fAp7crc

・【GAS】Gmailの内容をスプレッドシートに自動転記する方法
 https://note.com/omame727/n/neeea81533906

・NGワードを考えるのが面倒な人向けにサンプル集を作りました
 https://qiita.com/hose/items/774ac58f0155ef0a9973

・Python pydubで音声ファイルを操作しよう: 基本的な読み込みと保存
 https://qiita.com/Tadataka_Takahashi/items/1dd564802932d4a81b04

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?