QtのGUIからiTunesの情報を間接的に取ってきて色々しました

この投稿は、[Qt Advent Calendar 2016](http://qiita.com/advent-calendar/2016/qt)の13日目の投稿です。

#お約束

昨日は”はりきらない”こと@helicalgearさんの[「Windows 10 MobileでQtQuickアプリを動かしてみる。Part-II」](http://qiita.com/helicalgear/items/78ec6348dd1de431b52f)でした。さすがの変態端末クラスタですね。日本人の９割９分９厘が持っていないWindowsMobileをターゲットにされると、すぐに試せないもどかしさが有ります( ﾟдﾟ )!! 個人的にはWebSocketのバイナリ送信に対応してる5.8betaの方に興味を惹かれました。

#tl;dr

昔作っていた以下の機能を持つQtアプリを形にしました。 [^1] [^2]

+ iTunesを操作＋再生中のトラック名を取ってきて表示する（**QAxScriptManager**）
+ iTunesからアートワーク（ジャケット）を取ってきて表示する（**QPaintEvent**）
+ iTunesの再生中トラックを自分のアカウントにTweetする（**twitter4qml**）

順序立てて書くと↑の様になりますが、一言で書くと「@kimitakeさんが [twitterでやっている#NowPlaying](https://twitter.com/search?q=kimitake%20nowplaying&src=typd) ってやつを再生中のiTunesの曲でやりたかった」が本当の所です。



対象の環境は下記の通りです。

| Qt          |5.7                               |
|:-----------:|:---------------------------------:|
| OS          |Windows10 (たぶんwindows7とかでも可)|
| iTunes      |12.3.5.17                         |
| その他      |twitter4qml, VBScript             |


プラットフォーム非依存のQtですが、Windows10でiTuneにコマンドを送ったりするのに**VBScript**を使っていたり、そもそもiTuneを間接的に利用しているのでLinux環境では100%無用の長物です。MacOSではVBScript部分をJavaScriptに直す等すれば多少は使えるかも知れませんが確認はしていません。


#iTunesを操作＋再生中のトラック名を取ってきて表示する（**QAxScriptManager**）
本当の意味で難しいiTunesの操作自体はVBScriptでやっていて、Qtはそのスクリプトを内部に持って実行するだけになっています。実はこの**QAxScriptManager**クラスはJavaScriptとか外部スクリプトを実行できる賢い子だったはずですが、当時試した限りVBScript以外では期待通りの動作をしなかったので今回は使えていません。JavaScriptの方が汎用性はあるはずなので何とかしたい気はします。

```cpp:artwork.cpp
Artwork::Artwork(QWidget *parent) : QGraphicsView(parent)
{
～～（略）～～
    m_manager = new QAxScriptManager(this);
    bool isRegistered = m_manager->registerEngine("VBScript", "vbs");
    if(isRegistered){
        qDebug() << "OK to register VBScript";
        m_script = m_manager->load("://test.vbs", "HelloWorld");
    }
    else
        qDebug() << "NG to register VBScript";

    connect(m_script, static_cast<void(QAxScript::*)()>(&QAxScript::entered),
        [=](){ enterScript(); });
    connect(m_script, static_cast<void(QAxScript::*)()>(&QAxScript::finished),
        [=](){ finScript(); });
}
```

でqrcに入れたtest.vbsファイルをメモリ上にロードすると

```cpp:artwork.cpp
QString Artwork::getArtworkPath()
{
    QString rStr = "";

    if (m_script){
        QVariant var = m_script->call("getArtworkAndPath()");
        rStr = var.toString();
    }
    else
        qDebug() << "didn't load";

    return rStr;
}
```

`call("hogehoge")`と言う感じで、VBScript側の関数を文字通り呼び出すことができます。ちなみにこのgetArtworkAndPath()の中身はこんな感じ。[^3]

```vbnet:test.vbs
Function getArtworkAndPath()

        Dim objShell
        Dim tmp
        Dim iTunesApp
        Dim current
        Dim artworks
        Dim artwork
        Dim format
        Dim c_str

        Set objShell = CreateObject("WScript.Shell")
        tmp = objShell.ExpandEnvironmentStrings("%TMP%")

        Set iTunesApp = CreateObject("iTunes.Application")
        Set current = iTunesApp.CurrentTrack
        Set artworks = current.Artwork
        Set artwork = artworks.Item(1)
        format = artwork.Format

        Select Case format
        Case 1
                c_str = "jpg"
        Case 2
                c_str = "jpg"
        Case 3
                c_str = "jpg"
        End Select

        artwork.SaveArtworkToFile(tmp + "\\z." + c_str)
        getArtworkAndPath = tmp + "\\z."+ c_str

End Function
```

Qtと直接関係ないので詳細は省きます・・・。

#iTunesからアートワーク（ジャケット）を取ってきて表示する（**QPaintEvent**）
地味に面倒くさかったのがこっち。.NetFrameworkを使う場合だと、Imageクラスのloadメソッド呼び出しだけで矩形更新して画面に画像データが反映されるのでそう言うものだと思ってやってみようにもそう言うサンプルが無くて、実装はまず**QGraphicsView**を継承したクラスを作って**paintEvent**をオーバーライドして該当イベントの中でゴニョゴニョやらなきゃいけないという不親切な感じ。なので、先程出てきた**Artwork**クラスは**QGraphicsView**を継承していて

```cpp:artwork.cpp
void Artwork::paintEvent(QPaintEvent *event)
{
    QString path;

    qDebug() << __FUNCTION__ << " called";

    path = getArtworkPath();
    path = path.replace("\\\\", "\\");

    if(path != "")
        updateImage(event, path);
    else
        setNoImage(event, "No Image.");
}
```

**paintEvent**のコールバック内でiTunesのアートワークを取ってきて画面にロードするという割りと面倒なことをやっています。画像サイズの縱橫がおかしかったり、そのせいかエッジにバリが出たりしてるのでこれも気になるところ。そもそももっと綺麗に書く方法があるような気がする。

#iTunesの再生中トラックを自分のアカウントにTweetする（**twitter4qml**）

本稿のオリジナル記事を読んでいた貴特な方がいらっしゃったら申し訳ないのですが
> 知る限り一番の近道は@task_jpさんのTwitter4QMLを使うことだと思われます
>> [はりきらないひと: 【一夜漬け】Twitter4QMLを使ってみようか。](http://helicalgear.blogspot.jp/2015/12/twitter4qml.html)
>> [艦メモのビルド方法(Windows編) - 理ろぐ](http://relog.xii.jp/mt5r/2013/08/windows-4.html)

>使ってみようとpriをプロジェクトに追加してブログ記事とコードをにらめっこしておりましたが実力不足でついていけないのでTwitterにポストするだけの低機能クラスを作ることにしました。

と、書いていたのにtwitterのpostには**twitter4qml**を使わせて頂いております。そもそも自前でOAuth解決してPostするまでゴリゴリQtで書くほうが遥かにレベルが高かったです。。。orz

Windows向けの導入部分は@ioriayaneさんの記事を、実際に動かす部分は@helicalgearさんの記事を参考というのもおこがましい位にコードをまるまる拝借した感じで動かしています。

```cpp:tw.cpp
bool Tw::init()
{
    bool r_inf = false;

    // FIXME: it's so tentative implementaion...
    this->oauth.setConsumerKey(consumer_key);
    this->oauth.setConsumerSecret(consumer_secret);
    this->oauth.setToken(token);
    this->oauth.setTokenSecret(token_secret);
    this->oauth.user_id(user_id);
    this->oauth.screen_name(screen_name);
    //this->status.statusesUpdate();

    if(oauth.state() == 0) {
        QString pin = "";
        oauth.request_token(pin);
        oauth.access_token(pin);
    }

    if(oauth.state() == 2) {
        oauth.authorize();
    }

    if(oauth.state() == 5){
        qDebug() << "success token authorize";
        r_inf = true;
    }

    return r_inf;
}

bool Tw::postTweet(QString title)
{
    bool r_inf = false;
    QVariantMap map;
    QString sTweet = "#NowPlaying " + title;
    map.insert("status", sTweet);

    if(oauth.state() == 5)
    {
        status.statusesUpdate(map);
        r_inf = true;
    }
    return r_inf;
}
```

取りあえずタイトルをポストするだけならコレだけで出来ました。@task_jpさんはやはりスゴかった。。。


#動かしてみると

こんな感じ。

![c.png](https://qiita-image-store.s3.amazonaws.com/0/58573/f9496b79-e3d9-9e23-6240-5e6ec8087cf6.png)　![b.png](https://qiita-image-store.s3.amazonaws.com/0/58573/f0eed620-ed6e-31c7-3aff-1a73b44f1bb1.png)

アートワークがある楽曲はオリジナルデータを展開して表示します。無い（iTunes以外のファイル等）ものは無いので”No Image”って出してるんですが悲しいことになってます。何とかしたい。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/NowPlaying?src=hash">#NowPlaying</a> リフレクティア</p>&mdash; Yuya Adachi [bot] (@moguriso) <a href="https://twitter.com/moguriso/status/808359755974483968">2016年12月12日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

tweet ボタンを押すと上記の感じで問答無用でポストします。これも、ポスト内容を事前確認させたりとか、ユーザーを切り替えたりとか、アートワークも（著作権的にOKか微妙だけど）ポストしたりとか機能追加したい気はします。やるかどうかは判りません。。。

#まとめ
職業SIerだとcmd.exeやらscriptで所々の作業を自動化することは結構あると思うので（すが、**CLI使ってくれない**人も多々いるので）**QAxScriptManager**は上手に使えばトテモ便利なんじゃないかなと思いました。




明日は@tetsuromさんの”Widgetの階層構造を可視化するツールを作った話を書きます”です。期待して待ちましょう。



[^1]: 途中過程は → http://blog.tizen.moe/entry/2016/06/16/000000
[^2]: ソースコードはこちら（ https://github.com/moguriso/gawa_sample ）
[^3]: このあたりの仕様はApple公式で（一般には？）公開されなくなったようなのでココでは http://www.joshkunz.com/iTunesControl/interfaceIiTunes.html#z1_4 を参考にしていました