歌詞サイトリンク機能 データ設計書
最終更新: 2026-07-19
1. 概要
Genius API を利用して楽曲の歌詞ページ URL を取得し、releases.json の Track.links に保存する。
曲詳細画面から Genius の歌詞ページへ直接リンクできるようにする。
2. Genius API 調査
2-1. search エンドポイント
| 項目 | 内容 |
|---|---|
| エンドポイント | https://api.genius.com/search?q={query} |
| 認証 | Authorization: Bearer {GENIUS_ACCESS_TOKEN} |
| クエリ構築 | {曲タイトル} {アーティスト名} を URL エンコードして渡す |
| レート制限 | 非公開(1秒間に数リクエスト程度は許容される) |
2-2. レスポンス構造(関連部分)
{
"meta": { "status": 200 },
"response": {
"hits": [
{
"type": "song",
"result": {
"id": 12345,
"title": "LOVEマシーン",
"full_title": "LOVEマシーン by モーニング娘。",
"url": "https://genius.com/Morning-musume-love-machine-lyrics",
"primary_artist": {
"id": 678,
"name": "モーニング娘。"
}
}
}
]
}
}
2-3. フィールド対応表
注意: 実装では
hitsを先頭から順に走査し、以下のフィールドでマッチング判定を行う(先頭が正しいとは限らないため、全 hits を対象に確認する)。
| Genius フィールド(n: 走査中のインデックス) | 用途 |
|---|---|
response.hits[n].result.url |
歌詞ページ URL(Track.links に保存) |
response.hits[n].result.title |
マッチング確認用(タイトル一致スコア計算) |
response.hits[n].result.primary_artist.name |
マッチング確認用(アーティスト名一致スコア計算) |
response.hits[n].type |
"song" 以外("album" 等)はスキップ |
3. マッチング精度の検討
3-1. 課題
モーニング娘。の楽曲は日本語タイトルが多く、Genius は英語圏のサービスのため以下の問題がある。
- タイトル表記が日本語原題と英語表記のいずれかで登録されている(例:
LOVEマシーン/Love Machine) - アーティスト名が
モーニング娘。/Morning Musume/Morning Musume。と複数表記あり - 複数の hits が返る場合、先頭が正しいとは限らない
3-2. マッチング戦略
- 検索クエリ:
{Track.title} {Track.artist}で検索 - タイトル一致判定: 正規化後のタイトル一致(全角/半角・大小文字統一)を確認
- アーティスト一致判定:
primary_artist.nameが期待アーティスト名と部分一致するかを確認 - スコアリング: タイトル一致かつアーティスト一致の場合のみ採用。どちらか片方の場合は保留(スキップ)
3-3. 採用基準
| タイトル一致 | アーティスト一致 | 採用 |
|---|---|---|
| ○ | ○ | ✅ 採用(マッチした hits[n].result.url を保存) |
| ○ | ✗ | ❌ スキップ(別アーティストの同名曲の可能性) |
| ✗ | ○ | ❌ スキップ(表記ゆれが大きく誤マッチの可能性) |
| ✗ | ✗ | ❌ スキップ |
| hits が空 | — | ❌ スキップ(Genius 未登録) |
補足: スキップされたトラックは
geniusリンクなしのままとする。手動で設定する仕組みは v3.6.0 スコープ外。
4. ExternalLinkType への genius 追加
types/external-link.ts の ExternalLinkTypeSchema に 'genius' を追加する(詳細実装は #746)。
変更前
export const ExternalLinkTypeSchema = z.enum([
'youtube',
'youtube-music',
'spotify',
'apple-music',
'line-music',
'recochoku',
'mora',
'other',
]);
変更後
export const ExternalLinkTypeSchema = z.enum([
'youtube',
'youtube-music',
'spotify',
'apple-music',
'line-music',
'recochoku',
'mora',
'genius', // 追加: 歌詞サイトリンク(v3.6.0)
'other',
]);
5. データ保存先
5-1. Track.links への追加
既存の Track.links 配列(ExternalLink[])に genius タイプのエントリを追加する。
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"trackNumber": 1,
"title": "LOVEマシーン",
"links": [
{ "type": "youtube", "url": "https://www.youtube.com/watch?v=..." },
{ "type": "genius", "url": "https://genius.com/Morning-musume-love-machine-lyrics" }
]
}
5-2. 更新対象ファイル
| ファイル | 更新方法 |
|---|---|
Neon releases/track_links テーブル |
sync-genius-links スクリプトによる差分更新(fetchReleasesFromDB() で読み込み → genius リンク付与 → prisma.trackLink に書き戻し) |
6. 取得対象の絞り込み条件
| 条件 | 理由 |
|---|---|
Track.links に type: 'youtube' が存在する |
YouTube リンクがある = 公式 MV または音源として確認済みの楽曲 |
Track.links に type: 'genius' が存在しない |
既取得済みスキップ(冪等性確保) |
Track.artist が設定されている |
検索クエリを構築できない曲はスキップ(手動登録リリース等で省略される場合あり) |
全曲対象としない理由: Genius への不要なリクエストを削減し、API レート制限を回避するため。YouTube リンク済みの楽曲を優先的に対象とする。
7. API キー管理方針
| 項目 | 内容 |
|---|---|
| シークレット名 | GENIUS_ACCESS_TOKEN |
| 保存場所 | GitHub Repository secrets(Environment secrets は使用しない) |
| スクリプト内参照 | process.env.GENIUS_ACCESS_TOKEN |
| 未設定時の挙動 | スクリプトを即時終了(エラーログを出力して非ゼロ終了コード) |
参照: GitHub Actions シークレット運用方針 → 開発ノート #13
8. スクリプト概要(#747 実装向け)
| 項目 | 内容 |
|---|---|
| スクリプト | scripts/sync-genius-links.ts |
| 実行タイミング | GitHub Actions workflow_dispatch(手動実行)または定期実行 |
| 処理概要 | releases.json 読み込み → 対象トラック抽出 → Genius 検索 → マッチング → Blob 書き戻し |
処理フロー概要
1. Vercel Blob から releases.json を取得
2. 全リリースの全トラックを走査
→ Track.links に youtube があり genius がなく、かつ Track.artist が設定済みのトラックを抽出
3. 各対象トラックに対して Genius API search を呼び出す
→ クエリ: "{Track.title} {Track.artist}"
→ マッチング判定(§3-2)で採用/スキップを決定
4. 採用トラックの Track.links に genius リンクを追加
5. 更新済み releases.json を Vercel Blob に書き戻す
→ put(url, JSON.stringify({ releases }), { addRandomSuffix: false, allowOverwrite: true, token: ... })
6. 実行サマリーをコンソール出力(対象数・採用数・スキップ数)
9. 環境変数
| 変数名 | 必須 | 説明 |
|---|---|---|
GENIUS_ACCESS_TOKEN |
○ | Genius API アクセストークン。Repository secret に保存 |
BLOB_READ_WRITE_TOKEN |
○ | Vercel Blob 読み書きトークン(既存) |
RELEASES_BLOB_URL |
○ | releases.json の Blob URL(既存) |
GENIUS_LINKS_STATUS_BLOB_URL |
任意 | genius-links-status.json の Blob URL。未設定時は RELEASES_BLOB_URL からファイル名部分を置き換えて導出する |
10. 同期状況の記録
Genius リンク同期の実行結果を Vercel Blob に記録し、同期状況画面(/sync-status)に表示する。画面側の設計は 画面設計書 §7 を参照。
10-1. Blob ファイル
| ファイルパス | 概要 | 更新タイミング |
|---|---|---|
genius-links-status.json |
同期実行状態(同期状況画面向け) | sync-genius-links スクリプト実行時(毎回追記) |
10-2. 型定義
型定義ファイル: types/genius-links-status.ts
type GeniusLinksStatusSnapshot = {
date: string; // スナップショット取得日(YYYY-MM-DD)
targetTracks: number; // 対象トラック総数(YouTube リンクありトラックの累計)
fetchedTracks: number; // Genius リンク取得済みトラック数(累計)
};
type GeniusLinksStatus = {
lastSyncedAt: string; // 最終同期日時(ISO 8601)
history: GeniusLinksStatusSnapshot[]; // 日別スナップショット履歴(最新30件)
};
genius-links-status.json の形式: GeniusLinksStatus
10-3. 保存処理
saveGeniusLinksStatus(targetTracks, fetchedTracks)(scripts/workflow/sync-genius-links.ts)が Neon への書き込み後に呼び出される。
- 実行日(
YYYY-MM-DD)のスナップショットを追記する。同日に複数回実行した場合は当日分を最新の実行結果で上書きする - 履歴は日付昇順でソートし、直近30件(
GENIUS_LINKS_STATUS_HISTORY_MAX)のみ保持する - Blob 書き込みは
put(..., { addRandomSuffix: false, allowOverwrite: true })で行う(既存ファイルを上書き) - 保存処理が例外を投げた場合は警告ログを出すのみで、Genius リンク同期本体の失敗としては扱わない(
syncGeniusLinksMain内で try/catch)
10-4. 読み込み処理
getGeniusLinksStatusFromBlob()(lib/blob.ts)が同期状況画面向けに読み込む。
- Blob URL は環境変数
GENIUS_LINKS_STATUS_BLOB_URLを優先し、未設定の場合はRELEASES_BLOB_URLからファイル名部分を置き換えて導出する(#890) - 取得失敗・スキーマ不一致の場合は
nullを返す(フェイルセーフ)
11. 後続 Issue との関係
| Issue | 依存内容 |
|---|---|
| #746 型定義更新: ExternalLinkType に genius を追加する | 本設計書 §4 に基づいて実装 |
| #747 Genius リンク取得スクリプト実装 + ワークフロー組み込み | 本設計書 §5〜§9 に基づいて実装 |
| #748 曲詳細ページに歌詞リンクセクションを追加する | 本設計書 §5-1 の genius リンク形式に基づいてUI実装 |
改訂履歴
| 版 | 更新日 | 変更内容 |
|---|---|---|
| 1.1 | 2026-07-19 | §7, §9: 環境変数名を実装に合わせて GENIUS_API_KEY から GENIUS_ACCESS_TOKEN に是正(#757で実装済みだった名称変更が未反映だった)。§10(新設): 同期状況の記録(Blobファイル・型定義・保存/読み込み処理)を追記し、旧§10を§11に繰り下げ(#1375) |
| 1.0 | 2026-04-29 | 初版作成(#745) |