rch850 の上澄み

技術的な話題とか、雑談とか。タイトルを上澄みに変えました @ 2020/09/02

pomemo がようやく実用レベルになった - ふくもく会その128

fukumoku.connpass.com

直前の告知にも関わらず、8枠埋まってびっくり。

コツコツ進めてたポッドキャストプレイヤーの開発を進めてました。

開発は Kiro に任せてて、コードを読むことはあっても、直接書き換えたことは多分ないです。

この日やってたのは、Vercel にデプロイするとうまく動いてないところの修正。最初は状況から推測して指示を出していて、なかなか動くようになってくれなかったんですが、ビルドログをコピペしたらすぐ直りました。最初からそうしてればよかった。

ようやくちゃんとスマホ実機で動くようになったので、ここからが差別化ポイント。聞きながらワンタッチで再生地点をブックマークして、そこをあとから聞き返せる機能の追加。技術系のポッドキャストで、知らない単語や、後で深堀りしたい話題が出てきたときに、常々やりたいと思ってたことです。

ざっくり「こんなことがやりたいですー」って言うと、結構あれこれ盛った requirements を作ってくれるけど、今回はまず動かして体験したかったから、かなり削ってもらった。

# 要件定義書

## 概要

ポッドキャスト視聴中のブックマーク機能は、ユーザーが気になる話題や重要な瞬間をワンタッチで記録できる機能です。この機能により、ユーザーは後で特定の時点に簡単に戻ることができ、より効率的にポッドキャストを活用できます。

## 要件

### 要件 1

**ユーザーストーリー:** ポッドキャストリスナーとして、視聴中に気になる話題が出てきたときに、その時点をワンタッチでブックマークしたい。そうすることで、後でその瞬間に簡単に戻ることができる。

#### 受け入れ基準

1. ユーザーが再生中にブックマークボタンを押したとき、システムは現在の再生時間をブックマークとして保存する
2. ユーザーがブックマークを作成するとき、システムは3秒以内にブックマークが保存されたことを視覚的に確認できる
3. ユーザーがキーボードショートカット(B キー)を使用したとき、システムは同様にブックマークを作成する

### 要件 2

**ユーザーストーリー:** ポッドキャストリスナーとして、作成したブックマークを一覧で確認したい。そうすることで、保存した重要な瞬間を整理して管理できる。

#### 受け入れ基準

1. ユーザーがブックマーク一覧を表示するとき、システムはエピソード別に整理されたブックマークリストを表示する
2. ユーザーがブックマークリストを閲覧するとき、システムは各ブックマークの時間と作成日時を表示する
3. ユーザーがブックマークを削除するとき、システムは確認ダイアログを表示してから削除を実行する

### 要件 3

**ユーザーストーリー:** ポッドキャストリスナーとして、ブックマークした時点から再生を再開したい。そうすることで、気になった話題を再度聞くことができる。

#### 受け入れ基準

1. ユーザーがブックマークをクリックしたとき、システムはそのエピソードのブックマーク時点から再生を開始する
2. ユーザーがブックマークから再生を開始するとき、システムは現在の再生を停止して新しいエピソードに切り替える
3. ユーザーがブックマーク再生中にプレイヤーを操作するとき、システムは通常の再生コントロールを提供する

これで作ってもらって、こんな感じになった。

しばらく使ってみて、どんな操作をよく使うか見極めてみます。

pomemo はメモが取れるポッドキャストってことでつけた名前なんだけど、メモは不要かもなー。

仕事で悩んだときに気をつけていること

悩むより考える。という話がある。

ひとつはある万バズツイート

  • 「悩む」=物事を複雑にする事
  • 「考える」=物事をシンプルにする事

もうひとつは『イシューからはじめよ』の序盤

「悩む」=「答えが出ない」という前提のもとに、「考えるフリ」をすること 「考える」=「答えが出る」という前提のもとに、建設的に考えを組み立てること

イシューからはじめよ――知的生産の「シンプルな本質」

自分が「悩んでいるな」と感じたときに、これらの考え方がしっくりくる。

「悩んでいるな」と感じるときは、あえて選択肢を広げたり、深堀りしたりしている感じがある。

居酒屋のメニューを見ている時間はそんな感じ。なんなら悩んでいる時間のほうが楽しい。お願いだからまだオーダーを聞きに来ないで。

で、仕事だとそうも言ってられないよねという話。

「悩む」と「考える」の定義が云々という話は置いといて、なんだか仕事の進みが悪い感じがするときは、この考え方を思い出すようにしている。

表現は違うけど、言いたいことは散々擦られている "Done is better than perfect" に近いと感じている。

最高の答えを出すために四苦八苦する時間は楽しいものだけど、それが目的になってしまわないように気をつけている。

読むスピードにムラがある

本、特に専門書を読むスピードにムラがある。

まず基本的に読むのが遅い。だいたい1冊1ヶ月から2ヶ月ぐらいかかる。

あまり本を読まない方なので、それでも頑張ってる方だなと自分では感じている。

稀にスピードが出ることがあって、1週間ぐらいで読み終わる。

そういうときは、貪るように読んでいる。

なぜ貪るようなスピードが出ているかというと、実際に貪っているからだと思う。

なにか答えを探しているとき、そしてその本に答えがありそうという期待があるときに、答えを貪るように読んでいて、スピードが出ている。

読み進めて、答えから遠ざかっていくと、スピードが落ちてしまうことがあって、1週間、2週間と本から離れてしまうことがある。

だいぶムラがあるけど、それでいいと思っている。本を読む以外にもやりたいことはあるし、読みたいと思ったときに読めればそれでいいと思う。

続 mise 生活

rch850.hatenablog.com

その後の話。

ツールのアップデート

ふと思い出したタイミングで mise ls する。

❯ mise ls
Tool  Version             Config Source              Requested
deno  1.46.3 (outdated)   ~/.config/mise/config.toml latest
go    1.23.1 (outdated)   ~/.config/mise/config.toml latest
java  24.0.0-loom+7-60    ~/.config/mise/config.toml latest
node  20.17.0 (outdated)  ~/.config/mise/config.toml 20
ruby  3.3.5               ~/.config/mise/config.toml latest
rust  1.81.0 (outdated)   ~/.config/mise/config.toml latest
uv    0.4.17 (outdated)   ~/.config/mise/config.toml latest

なるほど古くなってるな。 mise up する。

❯ mise up
mise uv@0.4.26                                                                                                                                                                                info: downloading installer
...
mise rust@1.82.0 ✓ installed
mise go@1.23.2 ✓ installed
mise node@20.18.0 ✓ installed
mise deno@2.0.3 ✓ installed
mise Uninstalling node@20.17.0 ✓ removing ~/.local/share/mise/installs/node/20.17.0
mise Uninstalling go@1.23.1 ✓ removing ~/.local/share/mise/installs/go/1.23.1
mise Uninstalling rust@1.81.0 ✓ removing ~/Library/Caches/mise/rust/1.81.0
mise Uninstalling uv@0.4.17 ✓ removing ~/Library/Caches/mise/uv/0.4.17
mise Uninstalling deno@1.46.3 ✓ removing ~/Library/Caches/mise/deno/1.46.3

アップデート完了。かんたん快適。

この手のアップデートは brew upgrade 担当だったけど、それが mise up に変わった感じ。もちろん brew update もそれはそれでやるけど。

退路をどんどん断つ

  • brew uninstall deno → そもそも入れてなかった
  • brew uninstall go → できた
  • brew uninstall openjdk → clojure と kotlin が依存 → 以下を経て削除
    • brew uninstall clojure → できた。なんで使ってたんだろう
    • mise use -g kotlin → できた
    • brew uninstall kotlin → できた
    • brew uninstall openjdk → できた。めでたし
  • brew uninstall node → 依存されすぎててだめ
  • brew uninstall ruby → そもそも入れてなかった
  • brew uninstall rust → できた
  • brew uninstall python → 依存されすぎててだめ

だいぶスッキリ

開発環境構築を mise に引っ越した

node の新しいバージョンを入れようとしたときに、なんとなく乗り換えようと思って、mise を使い始めることにしました。

これまでは asdf-vm を使ってました。なにか強い不満があったわけではありませんが、コマンドがなかなか覚えられなかったり、asdf を打ち間違えたりということはありました。

インストール編

インストールには Homebrew を使いました。

$ brew install mise

このあと、以下のコマンドで activate するコードを .zshrc に追加したのですが、どうも自分の環境では ~/.local/bin/mise が見つからず、エラーになってしまいました。

$ echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc

代わりに .zshrceval "$(mise activate zsh)" を書いて事なきを得ました。

乗り換える意思を見せる

asdf list の出力や ~/.tool-versions の内容から引っ越しが必要なツールを一覧し、それらをすべて mise で入れ直しました。

$ mise use --global node@20
$ mise use --global golang
$ mise use --global java
$ mise use --global ruby
$ mise use --global rust

そしてバッサリと ~/.tool-versions を削除。mise にするにあたって、設定ファイルが ~/.tool-versions よりも ~/.config/mise/config.toml を優先するようになるのがちょっと不安だったのですが、郷に入っては郷に従えです。

ここから先は、引っ越しで見つけた細かい Tips です。

[Tips] .zshrc の中で mise 環境に入りたいときは mise x (mise exec)

割とレアなニーズかもですが、 .zshrc の中で mise でインストールしたツールを使いたくても、うまく使えません。

例えば .zshrc に次のように書いても、表示される node のパスやバージョンはグローバルにインストールされたものです。activate スクリプトの下に書いてもです。

which node
node -v

ですが、mise xmise execエイリアス)を使うと、mise でインストールしたツールの情報が表示されます。

mise x -- which node
mise x -- node -v

活躍するシーンは稀かもですが、稀に活躍するかもしれない tips です。

[Tips] mise list したときの outdated 表示がいいかも

mise に引っ越したのが先週ぐらいで、ついさっき mise list したときに、次のような表示が出ました。

$ mise list
...
uv    0.4.15 (outdated)  ~/.config/mise/config.toml latest

outdated 表示のお陰でアップデートが必要なことに気付けるのはいいかもです。asdf-vm のときどうだったかは覚えていません。outdated に対するカウンターは mise upgrade で良さそうです。

$ mise upgrade
mise uv@0.4.17 ✓ installed
mise uv@0.4.15 ✓ removing ~/Library/Caches/mise/uv/0.4.15

ふくもく会その113、Feedlyのポッドキャスト化途中まで

tl;dr

Feedly の記事を Zapier で Google スプレッドシートに列挙し、OpenAI (ChatGPT) で要約して音声ファイルとして出力しました。

きっかけ

車で通勤中に、ポッドキャストを消化することはできても Feedly の記事を消化することはできない。じゃぁ Feedlyポッドキャストにできないか?

ってことで検索して出てきたこちらの記事に出会ったのが2週間ほど前。

note.com

これは作ってみたい!ということで、ふくもく会の機会を使ってやってみました。

fukumoku.connpass.com

Feedly の記事を Zapier で Google スプレッドシートに送る

元ネタの記事では Feedly API を使っていましたが、この API は現時点では Enterprise プランでしか使えないことが分かりました。

これはお試しにしてはハードルが高いので、Pro プランにある Zapier 連携を試すことにしました。

Zapier で feedly を使いたいんだけど〜的に検索したら、こんなのが提案されました。

この Zap を調整して、次の形にしました。Feedly については他にも in Board や in Feed という選択肢がありましたが、in Folder の使い勝手が一番良さそうでした。

  1. New Article in Folder in Feedly
  2. Create Spreadsheet Row in Google Sheets

Feedly 側のステップの説明は省略して、Google Sheets に入れるところの説明。言葉で説明するより、見たほうが早いと思うので、スクショをどうぞ。

  1. Feedly Articles from Zapier というスプレッドシートGoogle スプレッドシートで作っておく。
  2. シートの名前を Inbox にしておく。アルファベットのほうがハマりが少なさそうなので。
  3. 1行目をヘッダーとして、date、title、url、summary、content という列を用意する。
  4. Zapier で画像のように連携する。

ざっとこんなところですね。これで Feedly のフォルダーに記事が追加されるたびにシートに行が追加されるようになります。

Google スプレッドシートの内容から音声ファイルを作る

記事さえ確保できれば、あとは元記事様とほぼ同じ進め方で行けます。

GAS でシートの内容を取得し、それを OpenAI の API に要約させて、音声化する流れです。

PoC レベルですが、こんな感じです。ここで呼び出している関数は元記事のものを使っています。

function myFunction() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Inbox');
  const numRows = sheet.getLastRow() - 1;
  const values = sheet.getSheetValues(2, 1, numRows, 4);

  let message = 'Feedly の記事をまとめました。\n\n';
  for (let i = 0; i < values.length; i++) {
    const title = values[i][1];
    const article = values[i][3];
    console.log(`Trying to summarize #${i} ${title}`);
    let obj = gptSummarize(title, article);
    message += `${i + 1}件目の記事です。\n\n${obj.title}\n\n${obj.summary}\n\n`;
  }
  message += '以上、Feedly の記事まとめでした。';

  let audio = getSpeechAudioData(message);
  createAudioFile(audio);
}

これで生成された音声ファイルはこれです。ここでは1件だけにしてます。

これから

  • Spotify でも YouTube Music でもいいからポッドキャスト化したい
  • 記事の内容を RSS から取得しているので、いい感じに本文を取ってくるようにしたい
  • 他の声を試してみたい
  • 記事の強弱をつける

こんなところです。それでは👋

ISUCON 13 で1万点ぐらいでした #isucon

去年は出られませんでしたが、今年は ISUCON 13 参加しました。チームは moudameda です。いつもと名前もメンバーも違います。

最終ベンチマークでは1万点をちょっと超えたぐらいでした。おそらく。failed でなければ。最終スコア、公開されるかな?

16時までほぼ初期スコアだったので、現場のテンションとしては1万点でも十分満足な感じでした。

自分がやっていたことはというと、環境整備をして一息ついた12時頃から16時すぎまで、ほとんどの時間を GET /api/livestream/search の高速化に費やしていた気がします。実装力が足りなくてハマるハマる。

  • sqlx での JOIN が分からず、あれやこれや。カラム名"users.id" とかにすればよかったんですね。
  • JOIN するところで struct のフィールドには db:"users" などと書けばよかったんですね。
  • IN 句を使うときは sqlx.In で準備するんですね。
    • そこに []int64 は渡せないから []int にする必要があるんですね。

あとはこの APILEFT JOIN livestream_tags などして1クエリで書けるだろうと思いこんでしまったのも反省点。LIMIT 50 することを考えると、無理でしたね。

これが落ち着いてから、つまり16時過ぎにようやくインデックスを追加して、順番が逆だったなと思いました。

statistics 系や DNS 周りは他メンバーに頼んでました。あと終盤に moderate をなんとかしようとしたけど、道筋が立てられなくて時間切れ。DELETE 文の呼び出し回数がベンチーマーク走行中4万回近くて相当な負荷になってそうに思えたので、なんとかしたかったです。一方で icon は影響小さそうだなと思ってほぼ放置。

ISUCON 10 の予選突破の成功体験から、終盤まで各自のサーバーで色々試行錯誤して、17時過ぎに負荷分散を始める。というのが身についてしまって、今回はそうじゃないほうが良かったんだろうなと思いました。DNS サーバー、MySQL サーバーを早めに別々にしていたらどうなっていたか。

などと考えていたんですが matsuu さんの「mysqlを2台目に」が17時10分なので、あんまり関係なさそう。

とても楽しかったので、また来年も参加したいです。