見出し画像

Feedly + ChatGPTで、毎朝 自分専用のポッドキャストを自動生成する仕組みを作った

昨年こんな記事を書きました。

毎朝、デザイン系の英語記事を10件ほどおすすめしてくれるSlackボットです。このボットは現在も問題なく稼働し続けていますが、毎朝のニュースは文字で読むよりも音声として聞いたほうが負担が少なく続けられそうです。

そこで、このSlackボットを拡張して、毎朝デザインニュースのポッドキャストを自動生成する仕組みを作ることにしました。


成果物

はじめに成果物について。完成したポッドキャストは毎朝SpotifyとApple Podcastで配信しています。

おおまかな処理の流れ

開発前に想定した処理の流れは以下の通りです。

毎朝ポッドキャストが自動で配信される理想的なフロー

自分はコンテンツ制作者ではなく、あくまでリスナーというスタンスを取りたいため、ワークフローに自分が介在しない完全自動化が理想です。
ところが、Sound Cloud APIの利用に必要なアプリケーション登録の受付が現在停止していることが判明しました。

https://docs.google.com/forms/d/e/1FAIpQLSfNxc82RJuzC0DnISat7n4H-G7IsPQIdaMpe202iiHZEoso9w/closedform

他に良い手段がないか調べてみましたが、現在のところ手軽で有望な手段が見つかっていません。そのため一部に人間(自分)が介在する以下のフローで妥協することにしました。

一部に人間が介在する妥協したフロー

まとまった時間をとれたら、自前で音声ファイルをホスティングしてRSS配信する処理を追加し、完全自動化を目指したいと思います。

ここからは各工程の手順を詳しく見ていきます。

Feedly APIからおすすめ記事の一覧を取得〜記事本文の取得

このポッドキャストでは、毎朝10件程度の記事を紹介します。元となる記事は自分のFeedlyアカウントのデザインカテゴリから取得します。Mixes APIを使用すると、Feedlyのロジック上でおすすめされる記事を取得できます。

また、Feed内に記事本文を含んでいない場合は、URLから本文を取得します。

これらの手順は以下の記事の「2. Feedlyからの記事の取得」と「3. 記事本文の取得」をご確認ください。

※ この手順で使用しているMixes APIは現在公式ドキュメントから削除されています。予告なく停止する可能性があるのでご注意ください。

ChatGPT で記事本文を要約

Slackボットの際は記事の内容を3行の箇条書きで要約するようにしましたが、今回は音声で読み上げるためナレーション用の原稿をつくってもらいます。

OpenAI APIのChat completionsにmessagesを送信する処理を書きます。

const gptApiKey = '○○○'

function gptRequestCompletion(messages) {
  const apiUrl = 'https://api.openai.com/v1/chat/completions';

  let headers = {
    // 1. リクエストヘッダのAuthorizationにAPI KEYを設定
    'Authorization':'Bearer '+ gptApiKey,
    'Content-type': 'application/json',
    'X-Slack-No-Retry': 1
  };

  let options = {
    'muteHttpExceptions' : true,
    'headers': headers, 
    'method': 'POST',
    'payload': JSON.stringify({
      // 2. POSTするpayloadにモデルとして gpt-3.5-turbo-110 を指定
      'model': 'gpt-3.5-turbo-1106',
      // 3. レスポンスのフォーマットとしてJSONを指定
      'response_format' : { 'type': 'json_object' },
      'messages': messages})
  };

  const response = JSON.parse(UrlFetchApp.fetch(apiUrl, options).getContentText());

  try {
    let text = response.choices[0].message.content;
    let obj = JSON.parse(text);
    return obj;
  }catch(error){
    Logger.log(error);
    return null
  }

}
  1. リクエストヘッダのAuthorizationにAPI KEYを設定します。

  2. POSTするpayloadにモデルとして gpt-3.5-turbo-1106 を指定します。

  3. レスポンスのフォーマットとしてJSONを指定します。

レスポンスでJSONフォーマットを指定する機能は、昨年11月のOpenAI DevDayで発表されました。

次に送信メッセージとしてsystemとuserのプロンプトを作成します。
記事のタイトルと本文を元に指定したフォーマットで要約してもらいます。

function gptSummarize(title, article){
  // 1. systemにレスポンスのフォーマットを指定
  let system = 
`与えられた文章の要約を400文字以内のですます調の日本語で出力してください。
レスポンスは必ず以下のJSONフォーマットで返します。
{'title':'記事のタイトルの日本語訳', 'summary':'記事の要約'}`
  // 2. userに記事タイトルと本文(8000文字以内)を指定。 
  let user = 'title: ' + title + '\nbody: ' + article.substring(0,8000)

  return gptRequestCompletion([
    {"role": "system", "content": system},
    {"role": "user", "content": user}
  ])
}
  1. systemにテキストフォーマットを指定します。記事本文の内容を400文字以内のですます調の文章で要約し、記事のタイトルの日本語訳と合わせてJSON形式で出力してもらいます。

  2. userにタイトルと本文を指定します。APIに送信できるTokenに上限があるため、本文は8000文字以内になるように削ります。

GASで実行してみます。

let title = 'How To Avoid Burnout as a Designer'
let article = 'Asa seasoned designer, I have had my fair share of burnouts over the years. I know firsthand how exhausting and demotivating it can be to constantly push yourself to meet deadlines and exceed client expectations. Burnout is a very real problem in the creative industry and it can happen to anyone, regardless of experience level.  In this article, I will share my personal experiences with burnout and offer some advice on how to avoid it.  Firstly, it’s important to understand what burnout is and how it can manifest. Burnout is a state of emotional, physical, and mental exhaustion caused by prolonged periods of stress. As a designer, you may experience burnout when you have to deal with tight deadlines, demanding clients, and repetitive tasks for a prolonged period. The warning signs of burnout can vary from person to person, but some of the most common ones include:  Feeling emotionally drained and unable to cope with stress. Lack of motivation and interest in your work. Difficulty sleeping or staying focused. Chronic fatigue or physical exhaustion. Physical and emotional detachment from colleagues and clients. Increased irritability and short temper. Decreased productivity and creativity. If you notice any of these symptoms, it’s important to take action and address them before they worsen.  Here are some tips on how to avoid burnout as a designer: 1. Set realistic goals and manage expectations One of the main causes of burnout is the pressure to meet unrealistic goals and expectations. To avoid this, make sure you set achievable goals and communicate clearly with your clients and colleagues. If you are feeling overwhelmed, it’s important to speak up and ask for help. This can mean delegating tasks, pushing back on unrealistic deadlines, or re-negotiating the scope of a project.  Something I found extremely useful in the past is to buddy up with somebody at work. Have weekly or bi-weekly check-ins with them. Talk about how you feel. Ideally, this would be someone who does not work directly with you or within the same project. Since it’s very useful to have an external point of view. The buddy should listen to the situation, and give advice. Knowing that sometimes having a rant is also all the other person would like to do. Externalising your emotions may be good enough to save you from stress.  2. Take breaks and prioritize self-care Design work can be intense and demanding, but it’s important to take regular breaks and prioritize self-care. This means taking time to rest, exercise, and engage in activities that you enjoy outside of work. Try to establish a healthy work-life balance that allows you to recharge your batteries and prevent burnout. '
let summary = gptSummarize(title, article)
console.log(summary)
GASの実行結果

タイトルの日本語訳と要約文がJSON形式で出力されました。

Text to speechで原稿を読み上げ

次にテキストの音声読み上げについて。音声読み上げにはOpenAIのText to speech を利用します。

テキストを与えると、音声データを返してくれる処理を書きます。

const gptApiKey = '○○○'

function getSpeechAudioData(text) {

  const apiUrl = 'https://api.openai.com/v1/audio/speech'
  let headers = {
    'Authorization':'Bearer '+ gptApiKey,
    'Content-type': 'application/json',
    'X-Slack-No-Retry': 1
  }

  let options = {
    'muteHttpExceptions' : true,
    'headers': headers, 
    'method': 'POST',
    'payload': JSON.stringify({
      // 1. 送信パラメータの指定 
      'model': 'tts-1-hd',
      'voice': 'alloy',
      'input': text,
      'response_format' : 'mp3',
      'speed' : 1.2
      })
  };

  try {
    // 2. 取得した音声をBlobデータとして返す
    const blob = UrlFetchApp.fetch(apiUrl, options).getBlob()
    return blob
  }catch(error){
    Logger.log(error)
    return null
  }

}
  1. Text to speechへの送信パラメータを指定します。
    inputにテキストを与え、response_formatにmp3を指定します。voiceはspeedは何度か試しつつ調整するのが良いと思います。

  2. APIのレスポンスで受け取った音声データをBlobオブジェクトとして返します。

次に音声データをGoogle Driveに保存する処理を書きます。folder IdにはGoogle DriveのフォルダIDを指定します。

const folderId = '◯◯◯'

function createAudioFile(blob){
  const folder = DriveApp.getFolderById(folderId)
  const file = folder.createFile(blob)
  if(file){
    let date = new Date()
    file.setName(Utilities.formatDate( date, 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss') + '.mp3')
  }
  return file
}

これをGASで実行してみます。

  let text = 'デザイナーとしての燃え尽きを避ける方法\n\nデザイナーとして経験を積んできた筆者は、燃え尽きを経験してきた。燃え尽きは創造産業で起こりうる現実の問題であり、誰にでも起こりうる。燃え尽きの症状には、ストレスに対処できないこと、仕事への興味の欠如、睡眠障害、体力・精神的な疲労などがある。このような症状が現れたら、リアルな目標を設定し、期待を管理すること、休憩を取ることやセルフケアを優先することが重要である。'
  let audio = getSpeechAudioData(text)
  createAudioFile(audio)

指定したフォルダにmp3ファイルが格納されます。

Google Driveにmp3ファイルが格納される

以下が作成された音声データです。一部英語が混じりますが、概ねいい感じで日本語で発話してくれます。

Google Apps Scriptでの定期実行

以上で必要な関数が一通り完成しました。毎朝定期実行する処理を書きます。


const feedlyAccessToken = '◯◯◯'
const feedlyStreamId = "user/◯◯◯/category/◯◯◯"
const gptApiKey = '◯◯◯'
const folderId = '◯◯◯'


function task(){

  //フィードの一覧を取得
  let feedList = getNewFeedList()
  let messageCount = 0
  let message = '今日のデザインニュースです。この音声では、話題になったデザイン関連ニュースを要約し、AIによる自動音声で紹介します。各記事のURLはショーノートからご確認ください。\n\n'


  feedList.items.forEach(function(item){
    
    let url = item.canonicalUrl || item.originId
    let article

    // 記事の本文を取得
    if(item.content){
      article = item.content.content
    }else{
      article = getArticle(url)
    }
    if(!article){
      return
    }

    // 記事の本文の要約文を取得
    let obj = gptSummarize(item.title, article)

    // 原稿を作成
    if(obj){
      messageCount += 1
      message += messageCount + '件目の記事です。\n\n' + obj.title + '\n\n' + obj.summary + '\n\n'
    }
  })
  message += '今日のデザインニュースは以上です。よい一日を。'
  metaText += '</ol>'

  // 原稿を元に音声ファイルを作成
  let audio = getSpeechAudioData(message)

  // ファイルを格納
  createAudioFile(audio)
}

毎朝指定時刻に1回実行されるように時間主導型のトリガーを設定します。

時間主導型のトリガーを設定

以上で完成です。毎朝指定した時間帯におすすめニュースを紹介してくれる音声ファイルがGoogle Drive上に生成されます。

これをSpotify for Podcastersから配信します。

完成したPodcastはSpotifyとApple Podcastからお聞きいただけます。

API使用料

最後にこの仕組みの運用に必要なAPI使用料について。
Feedly APIの利用にはFeedly Proの契約が必要で月額$6です。

ただし、2024年1月現在の料金表を見ると、Feedly ProのプランではAPIの利用ができず、ZapierとIFTTTからのみ利用可能に変更されています。現状はProプランで発行したAccessTokenでAPIを利用できていますが、今後利用できなくなる可能性があります。

次にOpenAIのAPI利用料です。
テキストの要約には gpt-3.5-turbo-1106 を使用していて

  • Input : $0.001 / 1K tokens

  • Output : $0.0020 / 1K tokens

です。日本語の場合は概算で 1文字 = 1token になります。要約文の作成では5000文字の記事を与え、400文字の要約文を作成したと仮定して、

$0.001 * 5 + $0.002 * 0.4 = $0.0058

です。1日10記事要約されると仮定すると、1日あたり $0.058 になり、1ヶ月で $1.74 です。

Feedly APIとOpenAI APIの利用料をあわせると、月額 $7.74 になります。


Twitterで主にUIデザインについて発信しています。感想やコメントなどいただけると嬉しいです!

Twitter (@shingo2000)

いいなと思ったら応援しよう!