LoginSignup
10
11

More than 5 years have passed since last update.

OSXでObjective-Cを用いてiTunesが現在再生している楽曲情報を取得する方法(& D言語でNow Playingをツイートする方法)

Last updated at Posted at 2016-02-23

はじめに

えーっと、なにがしたいか

  • OSXのiTunesで現在再生している楽曲情報を取得したい
  • 出来ればD言語からそれを呼び出したい(ABI使うだけだし、たぶん他の言語でも可能)

ここで検索してみた結果、AppleScriptを使う方法は簡単に得ることが出来ましたが、僕はObjective-Cで結果を得たかったのです(extern "C"することでD言語から呼ぶことが出来るので。)
ちなみに、僕はObjective-Cプログラマーではないので、いかに記載するコードはObjective-Cっぽくないかもしれないです。
それで、色々と調べた結果、Scripting Bridge(?)というのを使うことで実現できました(AppleのDeveloper向けのページにヒントが書いてあった)。

Using Scripting Bridge

iTunes.hをつくる。

iTunesに関する関数のプロトタイプ宣言等が記述されたiTunes.hを生成します。
この辺に関してはよくわかってないので詳しく書くことは出来ませんが、上記のページを参考にして作成します。
次のコマンドで生成することが出来ます。

 $ sdef /Applications/iTunes.app | sdp -fh --basename iTunes

Objective-Cのコードを書く

次のようなコードを書くことで実現できました。
ここでは、D言語から簡単に扱うためにMusicという構造体を定義しています。
また、D言語で書いたプログラムとリンクするためにextern "C"しています。
それから、うえで述べたように僕はObjective-Cプログラマーではなく、今回はじめてObjective-Cを書いただけなので以下のコードがObjective-C的であるかは分からないです(たとえば、どうやらObjective-Cではstructについて、変数の型として書くときにstructの修飾がいらないみたいだが(それでコンパイル通った)あえて書いたりした)。
それでは。

iTunes.mm
#include <stdlib.h>
#include <stdbool.h>
#import "iTunes.h"

// For ABI
extern "C" {
  struct Music {
    const char* name;
    const char* album;
    const char* artist;
  };

  bool checkiTunesIsRunning() {
    return [[SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"] isRunning];
  }

  struct Music* getCurrentiTunesPlay() {
    iTunesApplication* iTunes;
    iTunesTrack* current;
    struct Music* music = NULL;

    if (!checkiTunesIsRunning()) {
      return music;
    }

    music = (Music*)malloc(sizeof(Music));

    iTunes  = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];
    current = [iTunes currentTrack];

    music->name   = [[current name] UTF8String];
    music->album  = [[current album] UTF8String];
    music->artist = [[current artist] UTF8String];

    return music;
  }
}

// Test
int main() {
  struct Music* music;

  music = getCurrentiTunesPlay();

  if (music == NULL) {
    printf("[Fatal Error] - iTunes is not running\n");
    return -1;
  }

  printf("name:   %s\n", music->name);
  printf("album:  %s\n", music->album);
  printf("artist: %s\n", music->artist);

  free(music);
}

それではコンパイルしてみましょう。
コンパイルは次のコマンドで可能です。

$ clang -o iTunes iTunes.mm -framework Foundation -framework iTunesLibrary -framework ScriptingBridge

実行してみると

iTunes now playing in objc

できました!!!
再生中の楽曲情報を得ることに成功しました。

【おまけ】 Dで簡単なNowPlayingを書いてみる

とりあえず、Dから呼んでみる

ここで、D言語から呼んでみましょう。
と言っても非常に簡単です。
D言語側でiTunes.mmにてextern "C"したMusic構造体とgetCurrentiTunesPlay関数をextern(C)のなかでプロトタイプ宣言し、リンクするだけです。

とりあえず、以下にDのコードを貼り付けます。

iTunesByD
import std.stdio,
       std.string;

extern(C) {
  struct Music {
    const char* name;
    const char* album;
    const char* artist;
  }

  Music* getCurrentiTunesPlay();
}

void main() {
  Music* music;

  music = getCurrentiTunesPlay();

  if (music == null) {
    throw new Error("[Fatal Error] - iTunes is not running.");
  }<img width="1046" alt="スクリーンショット 2016-02-23 11.54.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/18198/3a33578d-2849-94f8-1bd8-8c9c822551cf.png">


  writefln("name:   %s", fromStringz(music.name));
  writefln("album:  %s", fromStringz(music.album));
  writefln("artist: %s", fromStringz(music.artist));
}

先ほどのiTunes.mmからmain関数を削除してから D言語とリンクします
コンパイル&リンクは以下のコマンドで出来ます

$ clang -c iTunes.mm
$ dmd iTunesByD.d iTunes.o -L-framework -LFoundation -L-framework -LiTunesLibrary -L-framework -LScriptingBridge

これで実行してみると

iTunes now playing in D

出来ました!!

 NowPlayingを書く

さて、ここまで来たらNowPlayingをツイートしたくなりますよね?
それで、拙作のD言語製Twitterライブラリ Twitter4D を用いて書いてみましょう。
D言語にはDUBというビルドツール兼パッケージマネージャーが存在しますが、ここでは簡単のため、それを用いずに Twitter4D をcloneしそのなかのtwitter4d.dを作業ディレクトリにコピーすることにします(DUBについて書かないといけなくなるので、こうすることにしました)。

とりあえず、iTunes.mmmain関数を削除したものとして話を進めます。
早速コードを書くことにします。
解説については特に無いので...

あ、ConsumerKeyAccessToken等は各自で用意して以下のnowPlaying.dの中に書き込んでください。。。
ConsumerKeyは持っているが、AccessTokenを持っていない場合は拙作のツールを使うといいかもしれません: accessTokenGetter

nowPlaying.d

import std.stdio,
       std.string;
import twitter4d;

extern(C) {
  struct Music {
    const char* name;
    const char* album;
    const char* artist;
  }

  Music* getCurrentiTunesPlay();
}


void main() {
  Twitter4D t4d;
  Music* music;

  string[string] keys = [
    // Please Replace Here
    "consumerKey"       : "",
    "consumerSecret"    : "",
    "accessToken"       : "",
    "accessTokenSecret" : ""
  ];

  music = getCurrentiTunesPlay();

  if (music == null) {
    throw new Error("[Fatal Error] - iTunes is not running.");
  }

  t4d = new Twitter4D(keys);
  string name   = cast(string)fromStringz(music.name),
         album  = cast(string)fromStringz(music.album),
         artist = cast(string)fromStringz(music.artist);

  string nowPlayingString = "Now Playing: \"" ~ name ~ "\" from \"" ~ album ~ "\" (" ~ artist ~ ") #NowPlaying";

  writeln("NowPlaying:");
  writefln("name:   %s", name);
  writefln("album:  %s", album);
  writefln("artist: %s", artist);
  writeln;

  writeln("[Tweet] - ", nowPlayingString);
  t4d.request("POST", "statuses/update.json", ["status" : nowPlayingString]);
}

うえのコードをnowPlaying.dとして保存してください。(以下に記述するコマンドで作成するnowPlayingDでぃれくとりに配置してください)

$ mkdir nowPlayingD
$ cd nowPlayingD
$ mv ../iTunes.mm ../iTunes.h .
$ git clone https://github.com/alphaKAI/Twitter4D
$ cp Twitter4D/source/twitter4d.d .
$ clang -c iTunes.mm
$ dmd nowplaying.d twitter4d.d itunes.o -L-framework -LFoundation -L-framework -LiTunesLibrary -L-framework -LScriptingBridge

これでコンパイル&リンクされます

これを実行することで

スクリーンショット 2016-02-23 12.18.50.png

となり、同時にTwitterには

と投稿されます!!

お疲れ様でした。

僕は全然Objective-COS XのiTunesについて詳しくないのでわかりませんが、何かしらの方法で次の曲が再生されたイベントをフック出来るのであれば完璧にNowPlayingを実装することが出来ますね。

では、以上です。

10
11
1

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
10
11