はじめに
えーっと、なにがしたいか
- OSXのiTunesで現在再生している楽曲情報を取得したい
- 出来ればD言語からそれを呼び出したい(ABI使うだけだし、たぶん他の言語でも可能)
ここで検索してみた結果、AppleScript
を使う方法は簡単に得ることが出来ましたが、僕はObjective-C
で結果を得たかったのです(extern "C"
することでD言語
から呼ぶことが出来るので。)
ちなみに、僕はObjective-C
プログラマーではないので、いかに記載するコードはObjective-C
っぽくないかもしれないです。
それで、色々と調べた結果、Scripting Bridge
(?)というのを使うことで実現できました(AppleのDeveloper向けのページにヒントが書いてあった)。
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の修飾がいらないみたいだが(それでコンパイル通った)あえて書いたりした)。
それでは。
#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
実行してみると
できました!!!
再生中の楽曲情報を得ることに成功しました。
【おまけ】 Dで簡単なNowPlayingを書いてみる
とりあえず、Dから呼んでみる
ここで、D言語から呼んでみましょう。
と言っても非常に簡単です。
D言語側でiTunes.mm
にてextern "C"したMusic
構造体とgetCurrentiTunesPlay
関数をextern(C)
のなかでプロトタイプ宣言し、リンクするだけです。
とりあえず、以下にDのコードを貼り付けます。
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
これで実行してみると
出来ました!!
## NowPlayingを書く
さて、ここまで来たらNowPlaying
をツイートしたくなりますよね?
それで、拙作のD言語製Twitterライブラリ Twitter4D を用いて書いてみましょう。
D言語にはDUB
というビルドツール兼パッケージマネージャーが存在しますが、ここでは簡単のため、それを用いずに Twitter4D
をcloneしそのなかのtwitter4d.d
を作業ディレクトリにコピーすることにします(DUB
について書かないといけなくなるので、こうすることにしました)。
とりあえず、iTunes.mm
のmain関数
を削除したものとして話を進めます。
早速コードを書くことにします。
解説については特に無いので...
あ、ConsumerKey
とAccessToken
等は各自で用意して以下のnowPlaying.d
の中に書き込んでください。。。
ConsumerKey
は持っているが、AccessToken
を持っていない場合は拙作のツールを使うといいかもしれません: accessTokenGetter
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
これでコンパイル&リンクされます
これを実行することで
となり、同時にTwitterには
Now Playing: "千本桜" from "VOCALO CLASSIC" (石川綾子) #NowPlaying
— α改 (@alpha_kai_NET) February 23, 2016
と投稿されます!!
お疲れ様でした。
僕は全然Objective-C
やOS XのiTunes
について詳しくないのでわかりませんが、何かしらの方法で次の曲が再生されたイベントをフック出来るのであれば完璧にNowPlaying
を実装することが出来ますね。
では、以上です。