pomemo がようやく実用レベルになった - ふくもく会その128
直前の告知にも関わらず、8枠埋まってびっくり。
コツコツ進めてたポッドキャストプレイヤーの開発を進めてました。
開発中のポッドキャストプレーヤーがようやく実用的になってきた。
— りちゃ🏠🌈 (@rch850) 2025年9月27日
今日の #ふくもく会 で色々アイデアもらえてよかった pic.twitter.com/sOyTmBmyN4
開発は Kiro に任せてて、コードを読むことはあっても、直接書き換えたことは多分ないです。
この日やってたのは、Vercel にデプロイするとうまく動いてないところの修正。最初は状況から推測して指示を出していて、なかなか動くようになってくれなかったんですが、ビルドログをコピペしたらすぐ直りました。最初からそうしてればよかった。
ようやくちゃんとスマホ実機で動くようになったので、ここからが差別化ポイント。聞きながらワンタッチで再生地点をブックマークして、そこをあとから聞き返せる機能の追加。技術系のポッドキャストで、知らない単語や、後で深堀りしたい話題が出てきたときに、常々やりたいと思ってたことです。
ざっくり「こんなことがやりたいですー」って言うと、結構あれこれ盛った requirements を作ってくれるけど、今回はまず動かして体験したかったから、かなり削ってもらった。
# 要件定義書 ## 概要 ポッドキャスト視聴中のブックマーク機能は、ユーザーが気になる話題や重要な瞬間をワンタッチで記録できる機能です。この機能により、ユーザーは後で特定の時点に簡単に戻ることができ、より効率的にポッドキャストを活用できます。 ## 要件 ### 要件 1 **ユーザーストーリー:** ポッドキャストリスナーとして、視聴中に気になる話題が出てきたときに、その時点をワンタッチでブックマークしたい。そうすることで、後でその瞬間に簡単に戻ることができる。 #### 受け入れ基準 1. ユーザーが再生中にブックマークボタンを押したとき、システムは現在の再生時間をブックマークとして保存する 2. ユーザーがブックマークを作成するとき、システムは3秒以内にブックマークが保存されたことを視覚的に確認できる 3. ユーザーがキーボードショートカット(B キー)を使用したとき、システムは同様にブックマークを作成する ### 要件 2 **ユーザーストーリー:** ポッドキャストリスナーとして、作成したブックマークを一覧で確認したい。そうすることで、保存した重要な瞬間を整理して管理できる。 #### 受け入れ基準 1. ユーザーがブックマーク一覧を表示するとき、システムはエピソード別に整理されたブックマークリストを表示する 2. ユーザーがブックマークリストを閲覧するとき、システムは各ブックマークの時間と作成日時を表示する 3. ユーザーがブックマークを削除するとき、システムは確認ダイアログを表示してから削除を実行する ### 要件 3 **ユーザーストーリー:** ポッドキャストリスナーとして、ブックマークした時点から再生を再開したい。そうすることで、気になった話題を再度聞くことができる。 #### 受け入れ基準 1. ユーザーがブックマークをクリックしたとき、システムはそのエピソードのブックマーク時点から再生を開始する 2. ユーザーがブックマークから再生を開始するとき、システムは現在の再生を停止して新しいエピソードに切り替える 3. ユーザーがブックマーク再生中にプレイヤーを操作するとき、システムは通常の再生コントロールを提供する
これで作ってもらって、こんな感じになった。

しばらく使ってみて、どんな操作をよく使うか見極めてみます。
pomemo はメモが取れるポッドキャストってことでつけた名前なんだけど、メモは不要かもなー。
仕事で悩んだときに気をつけていること
悩むより考える。という話がある。
ひとつはある万バズツイート
昔知った言葉「悩むとは物事を複雑にする事。考えるとは物事をシンプルにする事。」
— 秋田道夫 (@kotobakatachi) 2022年7月24日
- 「悩む」=物事を複雑にする事
- 「考える」=物事をシンプルにする事
もうひとつは『イシューからはじめよ』の序盤
「悩む」=「答えが出ない」という前提のもとに、「考えるフリ」をすること 「考える」=「答えが出る」という前提のもとに、建設的に考えを組み立てること
自分が「悩んでいるな」と感じたときに、これらの考え方がしっくりくる。
「悩んでいるな」と感じるときは、あえて選択肢を広げたり、深堀りしたりしている感じがある。
居酒屋のメニューを見ている時間はそんな感じ。なんなら悩んでいる時間のほうが楽しい。お願いだからまだオーダーを聞きに来ないで。
で、仕事だとそうも言ってられないよねという話。
「悩む」と「考える」の定義が云々という話は置いといて、なんだか仕事の進みが悪い感じがするときは、この考え方を思い出すようにしている。
表現は違うけど、言いたいことは散々擦られている "Done is better than perfect" に近いと感じている。
最高の答えを出すために四苦八苦する時間は楽しいものだけど、それが目的になってしまわないように気をつけている。
読むスピードにムラがある
本、特に専門書を読むスピードにムラがある。
まず基本的に読むのが遅い。だいたい1冊1ヶ月から2ヶ月ぐらいかかる。
あまり本を読まない方なので、それでも頑張ってる方だなと自分では感じている。
稀にスピードが出ることがあって、1週間ぐらいで読み終わる。
そういうときは、貪るように読んでいる。
なぜ貪るようなスピードが出ているかというと、実際に貪っているからだと思う。
なにか答えを探しているとき、そしてその本に答えがありそうという期待があるときに、答えを貪るように読んでいて、スピードが出ている。
読み進めて、答えから遠ざかっていくと、スピードが落ちてしまうことがあって、1週間、2週間と本から離れてしまうことがある。
だいぶムラがあるけど、それでいいと思っている。本を読む以外にもやりたいことはあるし、読みたいと思ったときに読めればそれでいいと思う。
続 mise 生活
その後の話。
ツールのアップデート
ふと思い出したタイミングで 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
代わりに .zshrc に eval "$(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 x(mise 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週間ほど前。
これは作ってみたい!ということで、ふくもく会の機会を使ってやってみました。
Feedly の記事を Zapier で Google スプレッドシートに送る
元ネタの記事では Feedly API を使っていましたが、この API は現時点では Enterprise プランでしか使えないことが分かりました。

これはお試しにしてはハードルが高いので、Pro プランにある Zapier 連携を試すことにしました。
Zapier で feedly を使いたいんだけど〜的に検索したら、こんなのが提案されました。

この Zap を調整して、次の形にしました。Feedly については他にも in Board や in Feed という選択肢がありましたが、in Folder の使い勝手が一番良さそうでした。
Feedly 側のステップの説明は省略して、Google Sheets に入れるところの説明。言葉で説明するより、見たほうが早いと思うので、スクショをどうぞ。

- Feedly Articles from Zapier というスプレッドシートを Google スプレッドシートで作っておく。
- シートの名前を Inbox にしておく。アルファベットのほうがハマりが少なさそうなので。
- 1行目をヘッダーとして、date、title、url、summary、content という列を用意する。
- 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件だけにしてます。
音声化できた pic.twitter.com/V0azKiT0HX
— りちゃ🏠🌈 (@rch850) 2024年6月29日
これから
- Spotify でも YouTube Music でもいいからポッドキャスト化したい
- 記事の内容を RSS から取得しているので、いい感じに本文を取ってくるようにしたい
- 他の声を試してみたい
- rinna ?
- 記事の強弱をつける
こんなところです。それでは👋
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にする必要があるんですね。
- そこに
あとはこの API が LEFT JOIN livestream_tags などして1クエリで書けるだろうと思いこんでしまったのも反省点。LIMIT 50 することを考えると、無理でしたね。
これが落ち着いてから、つまり16時過ぎにようやくインデックスを追加して、順番が逆だったなと思いました。
statistics 系や DNS 周りは他メンバーに頼んでました。あと終盤に moderate をなんとかしようとしたけど、道筋が立てられなくて時間切れ。DELETE 文の呼び出し回数がベンチーマーク走行中4万回近くて相当な負荷になってそうに思えたので、なんとかしたかったです。一方で icon は影響小さそうだなと思ってほぼ放置。
ISUCON 10 の予選突破の成功体験から、終盤まで各自のサーバーで色々試行錯誤して、17時過ぎに負荷分散を始める。というのが身についてしまって、今回はそうじゃないほうが良かったんだろうなと思いました。DNS サーバー、MySQL サーバーを早めに別々にしていたらどうなっていたか。
などと考えていたんですが matsuu さんの「mysqlを2台目に」が17時10分なので、あんまり関係なさそう。
とても楽しかったので、また来年も参加したいです。