この記事の概要: Claude Code のカスタムスキルとして、Mac の Music.app を AppleScript 経由で操作する /dj スキルを自作した。基本操作(再生・停止・音量)に加え、ライブラリの再生回数と like/skip フィードバックから嗜好モデルを構築し、Claude 自身が会話の雰囲気を読んで選曲する /dj mood と未聴曲を発掘する /dj discover を実装した。スキル本体は scripts/util/dj.sh(427行・19コマンド)と scripts/util/dj-model.py(250行)の構成で、AppleScript 固有の制約(ストリーミング曲の AutoPlay 化、current track 取得失敗)には DJ Queue プレイリスト経由の duplicate ハックで対処している。
/dj スキルで何ができるか: 19コマンドの全体像
コーディング中の BGM 選びをサボりたかった。Music.app は手元にライブラリがあるのにシャッフルだとノイズが多く、プレイリストを切り替えるのも面倒。せっかく Claude Code を常駐させているのだから、会話の文脈から「今の作業に合う曲」を選んでもらいたい。
そこで作ったのが /dj スキルだ。Claude Code のセッションから次のようなコマンドが叩ける。
/dj # 現在の再生状態を表示
/dj mood # 会話の雰囲気から Claude が選曲
/dj discover # 嗜好モデルから未聴曲を発掘
/dj like # 今の曲を「好き」として記録
/dj skip # 今の曲を「スキップ」として記録し次の曲へ
実装は 3 ファイルだけだ。
| ファイル | 役割 | 行数 |
|---|---|---|
scripts/util/dj.sh |
AppleScript 呼び出し・コマンド分岐 | 427 |
scripts/util/dj-model.py |
嗜好モデルの構築・スコア計算・JSON 永続化 | 250 |
.claude/commands/dj.md |
Claude Code スキル定義 | 88 |
dj.sh の case 文には 19 コマンドが入っている(status / play / pause / pp / next / prev / vol / playlists / playlist / shuffle / refresh / model / like / skip / taste / unplayed / play-track / search / help)。AI 選曲機能の mood と discover はスキル定義側で Claude が直接判断するパターンになっており、シェル側のコマンドではない。
嗜好モデルの仕組み
/dj discover や /dj mood の選曲精度を支えているのが、~/.config/dj/model.json に永続化された嗜好モデルだ。3 つの入力からアーティスト別・ジャンル別のスコアを計算する。
-
ライブラリの再生回数(Music.app の
played count) -
DJ Queue 経由で再生した曲のフィードバック(
/dj like//dj skip) - 小さなランダムノイズ(毎回同じ曲にならないようにする揺らぎ)
スコア計算式
実装は scripts/util/dj-model.py にある。アーティストとジャンルそれぞれに「ベーススコア」を計算してから、トラック単位でブレンドする。
ベーススコアは次のロジック。
def lib_score(plays):
"""Library play count → 0–5 score (log scale)."""
return math.log(plays + 1) / math.log(100) * 5 if plays > 0 else 0
def score(d):
s = lib_score(d["library_plays"]) + d["dj_plays"] * 1.0 - d["dj_skips"] * 0.5
return round(min(max(s, 0), 10), 3)
再生回数は対数スケールで 0〜5 点に圧縮し、like を +1.0 点、skip を -0.5 点で加減する。最終的に 0〜10 点にクリップする。対数にしているのは「100 回聴いた曲が 1 回聴いた曲の 100 倍好き」ではないからで、線形だと再生回数の多いアーティストが圧倒的に有利になる。
トラック単位のスコアは、そのトラックのアーティストスコアとジャンルスコアの重み付き和に揺らぎを加える。
def score_track(model, artist, genre):
a_score = model.get("artists", {}).get(artist, {}).get("score", 0)
g_score = model.get("genres", {}).get(genre, {}).get("score", 0)
raw = a_score * 0.55 + g_score * 0.45
noise = random.uniform(-0.4, 0.4)
return round(raw + noise, 3)
重みはアーティスト 0.55、ジャンル 0.45。アーティストにやや偏らせているのは、同じジャンルの中でも「このアーティストは聴くがあのアーティストは聴かない」という選好のほうが強いと判断したからだ(これは私の運用上の判断で、もっとデータが溜まれば調整する)。
like / skip の即時反映
/dj like と /dj skip を叩いた瞬間に cmd_build() が走り、~/.config/dj/model.json が再構築される。次の /dj discover の選曲には即座に反映される。学習のフィードバックループが秒単位で回るので、「この曲ちょっと違うな」と思った瞬間に skip すれば、次の選曲が変わる。
/dj mood の中身: Claude が選ぶ
/dj mood の実装は、スクリプト側にロジックを持たせず Claude 自身に判断させるスタイルにしている。.claude/commands/dj.md の該当部分はこうだ。
### `/dj mood` — 会話の雰囲気に合わせた選曲
実行手順:
1. `bash "$PROJECT_ROOT/scripts/util/dj.sh" taste` を実行し、ユーザーの音楽趣味プロファイルを取得する
2. 現在の会話の雰囲気を自分で分析する(スクリプトではなく Claude が判断する)
- トーン: 集中・リラックス・テンション高め・感傷的・クリエイティブ など
- 話題: 技術的な作業・日常会話・悩み相談・ブレスト など
3. taste の結果から「今の雰囲気に合うアーティスト・ジャンル」を推論する
4. ユーザーへ選曲理由を伝えてから `bash "$PROJECT_ROOT/scripts/util/dj.sh" play-track "曲名" "アーティスト名"` で再生する
ポイントは、雰囲気分析を Python やシェルに書かないことだ。会話の文脈は LLM が一番うまく扱える領域なので、データ取得(taste)と再生(play-track)だけをスクリプトに任せ、間の判断は Claude 本人にやらせる。スクリプトに NLP を実装する代わりに、スキル定義の Markdown に「こう考えろ」と書いておくだけで動く。
実際に使うと「コードに集中している感じなので、宇多田ヒカルの中からアンビエントより静かな曲を選びます」というふうに、選曲理由を添えて再生してくれる。理由がついてくるおかげで、外したときに「違うな」と判断しやすい。
AppleScript の制約と回避ハック
ここからが本題と言ってもいい。Music.app を AppleScript で叩くと、ドキュメントには書かれていない制約に何度もぶつかる。私が踏んだ罠と、現状の回避策をまとめる。
制約 1: ストリーミング曲を直接 play すると AutoPlay モードに入る
Apple Music で配信されている曲(shared track)に対して play t を実行すると、Music.app は「指定した 1 曲を再生」ではなく「その曲を起点に AutoPlay でずっと流し続けるモード」に入る。これだと嗜好モデルの選曲が無視され、Apple Music の推薦エンジンに乗っ取られてしまう。
回避策は「DJ Queue」という名前の一時プレイリストを作って duplicate で曲を放り込み、そのプレイリストを再生する方式だ。scripts/util/dj.sh の play-track コマンドが実装している。
-- DJ Queue 経由で再生(ストリーミング曲対応)
try
delete playlist "DJ Queue"
end try
set dj to make new user playlist with properties {name: "DJ Queue"}
duplicate t to dj
play dj
毎回プレイリストを作り直すのは無駄に見えるが、プレイリスト名を固定にしておくと「次に再生したい曲」のキューが Music.app の UI 上に常に見える状態になるので、これはこれで使い勝手がいい。
制約 2: 全曲スキャンが遅い
every track of library playlist 1 でライブラリ全体を舐めると、数千曲規模では数分かかる。これだと /dj refresh を叩いた瞬間に Claude Code が固まる。
回避策は、スキャン対象をお気に入りプレイリスト 4 本(「★4つ以上」「お気に入りの曲」「トップ 25」「最近再生した項目」)に限定することだ。フルスキャンは諦め、「ユーザーが過去に好意的に扱った曲」だけを学習データに使う。これでスキャン時間は数十秒に収まる。
制約 3: current track が AutoPlay 時に取れない
これは制約 1 とも関連する。何かの拍子に Music.app が AutoPlay モードに入っていると、current track プロパティへのアクセスがエラーになる。/dj で再生状態を表示しようとしても曲名が取れない。
回避策は DJ Queue プレイリストの 1 曲目をフォールバックとして読むこと。
set t to missing value
try
set t to current track
on error
try
set t to track 1 of playlist "DJ Queue"
end try
end try
/dj スキルで再生する曲は必ず DJ Queue に入っているので、フォールバックがほぼ確実に機能する。
制約 4: 一括プロパティ取得が shared track で失敗する
name of tracks of playlist のような「プロパティを一括で配列として取り出す」記法は、ライブラリ内のローカル曲には効くが、ストリーミング曲が混ざると失敗する。
回避策は素直にループでインデックスアクセスすることだ。/dj refresh の実装は次のようになっている。
set n1 to count of tracks of playlist "★4つ以上"
repeat with i from 1 to n1
try
set t to track i of playlist "★4つ以上"
set output to output & (name of t) & tab & (artist of t) & tab & ...
end try
end repeat
try ... end try で個別のトラック取得失敗を握りつぶしながら回すのがミソ。1 曲失敗しても全体は走り続ける。
これらの制約は Apple の公式ドキュメントには明記されておらず、実装中に試行錯誤で気付いた挙動だ。同じ罠にハマる人の参考になれば、と思って記事にしている(公式記載と実機挙動は別物として扱うべき、というのは AppleScript に限らない話だが)。
残課題: プレイリストの自動生成はまだコマンド化していない
会話の中で「Claude のプレイリスト作って」と言うと、嗜好モデルのトップアーティストから各 2〜3 曲ずつを選んで Music.app に新規プレイリストとして書き出すことはできる。しかしこれは Claude がその場でコードを書いて実行しているスタイルで、/dj make-playlist のようなコマンドにはなっていない。
理由は単純で、まだ「どういう粒度でプレイリストを作るのが心地よいか」が固まっていないからだ。曲数?ジャンル混合度?気分タグ?コマンド化する前に運用で試して、形が見えてから実装したい。Claude にその場でコードを書かせる方式は、固まる前のプロトタイピングとして優秀なので、慌ててコマンド化する必要を感じていない。
Claude Code × AppleScript で作る AI DJ の設計まとめ
- Claude Code のカスタムスキルとして、Music.app を AppleScript で操作する
/djを自作した。実装は 3 ファイル・合計 765 行 - 嗜好モデルは
lib_score(再生回数) + dj_plays - dj_skips × 0.5でアーティスト/ジャンル別スコアを作り、トラック単位ではアーティストスコア × 0.55 + ジャンルスコア × 0.45 + ノイズでランク付けする -
/dj moodは雰囲気分析を Claude 本人にやらせ、スクリプトはデータ取得と再生だけを担当する設計にした - AppleScript 固有の制約(ストリーミング曲の AutoPlay 化・
current track取得失敗・一括プロパティ取得失敗)には、DJ Queue プレイリスト経由のduplicateハックとループ+tryで対処している
Claude Code のスキル機能は「シェルを叩く + LLM が文脈で判断する」という分担がしやすく、AppleScript のような「強いがクセのある」自動化ターゲットとの相性がよかった。同じパターンで Reminder.app や Calendar.app を操るスキルも作れる気がしている。
関連リンク
- Mac で AppleScript を書き始めるなら、定番のリファレンス本が一冊あると詰まったときの調査が早い: AppleScript リファレンスを Amazon で見る
- Music.app のライブラリを充実させるには Apple Music の購読が前提になる: Apple Music ギフトカードを Amazon で見る
この記事は はてなブログ からのクロスポストです。
はてなブログ版: https://saitoko.hatenablog.com/entry/2026/05/22/235707