1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code から Mac の Music.app を AppleScript で操る /dj スキルを自作した

1
Last updated at Posted at 2026-05-24

この記事の概要: 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 選曲機能の mooddiscover はスキル定義側で Claude が直接判断するパターンになっており、シェル側のコマンドではない。

嗜好モデルの仕組み

/dj discover/dj mood の選曲精度を支えているのが、~/.config/dj/model.json に永続化された嗜好モデルだ。3 つの入力からアーティスト別・ジャンル別のスコアを計算する。

  1. ライブラリの再生回数(Music.app の played count
  2. DJ Queue 経由で再生した曲のフィードバック/dj like / /dj skip
  3. 小さなランダムノイズ(毎回同じ曲にならないようにする揺らぎ)

スコア計算式

実装は 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.shplay-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 を操るスキルも作れる気がしている。


関連リンク


この記事は はてなブログ からのクロスポストです。


はてなブログ版: https://saitoko.hatenablog.com/entry/2026/05/22/235707

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?