TwitterIrcGatewayをmono 6で動かす

twitterTLS 1.2限定になって無事TwitterIrcGatewayが死亡したわけですが、ここはソースいじってLinux(debian 9)環境のmono(6.0.0.319)で動かそうという話。ソースはopentigリポジトリから、編集はVisual Studio 2017 Community版で行いました。なおそれまでdebian 7で動かしていたものをTLS 1.2対応が必要になって環境ごとバージョンアップしたら動かなくなったのであれこれやりましたが、先に結論から言うとmono 6で動かないのはTLS 1.2関係なかったです。

この記事はやったことを羅列していくのであまり原因について調べていませんというかそんな余裕ないです。

蛇足

ちなみに本稿と関係ないですが、WindowsTLS 1.2の対応はターゲットフレームワークを.NET 4.5にしてMainの頭に

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

と書いておけばいいです。monoではなくても動きました。理由はよくわかってません。レジストリをいじるハックが出回ってるのでソースいじりたくない人はそちらでもいいと思います。

本題

本題に入ります。まず、debian公式のmonoは古いのでまず公式のmonoをインストールします。何度も言うように動かない原因は別にあったので今となっては必要あるかはわかりませんが!

# apt install apt-transport-https dirmngr gnupg ca-certificates
# apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF
# echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | tee /etc/apt/sources.list.d/mono-official-stable.list
# apt-get update
# apt-get install mono-devel ca-certificates-mono

次にローカル環境にソースコードをcloneしてきて、Visual Studioで開き、すべてのプロジェクトのターゲットフレームワークをv4.5にします。これも必要あるかはわかりません!がこの記事はやったことをただ書いていきます。

git clone https://github.com/opentig/TwitterIrcGateway.git

ここから本題です。monoでTwitterIrcGatewayが動かないのは、TwitterIrcGatewayCore内AddInManager.cs 150行目_sessionがnullになっておりここで止まるからです。ということでnullにならないようにしましょう。同ファイル63,64行目に問題がります。

            _server = ServerProxy.GetTransparentProxy() as Server;
            _session = SessionProxy.GetTransparentProxy() as Session;

これを

            _server = (Server)ServerProxy.GetTransparentProxy();
            _session = (Session)SessionProxy.GetTransparentProxy();

こうします。なんでこれで動くのかは例によってわかっていません。これでRelease設定でビルドしてBin/Releaseディレクトリ内ファイルをアップロードすれば終わりです。

お疲れ様でした。

2Mbyte以下のwebmファイルを作りたい

webmファイルを2Mに抑えたい時ってありますよね。何の話かわからない?まぁ某所にはそういう需要があるんですよ。今回は2M以下のwebmファイルのビットレート試行錯誤をわりと効率よく作れる感じになったのでメモ書き。

1. aviutilで動画を編集する

そのまんまです。aviutilの編集の使い方は省略。

2. おもむろにエンコする

ffmpeg/arvconv出力を使ってwebm出力します。

-c:a libopus -b:a {音声のビットレート} 
-c:v libvpx-vp9 -b:v {動画のビットレート} -cpu-used 0 -speed 0 -threads 1 -quality best

※threadsは動画サイズによって適切な値が異なります。エンコ速度に関わるので詳しくはぐぐってください。

動画コーデックはVP9、音声コーデックはopusを使います。サイズ当たりの画質を少しでも上げたいので2パスでエンコしましょう。mp4ではなくてwebmを使うのはVP9が使えるからです。mp4のx265はブラウザで再生できないので今回の用途には使えません。

出力されたwebmが2M以下なら完成です。お疲れさまでした。

3. ここから今回書きたいこと

画質はいい感じだけど2Mを少し!少しだけ超えてる!画質落とすより音質削りたい!
このパターンが結構あって困ります。画質は割とぎりぎりを狙っているのに2M以下になってくれない。そういう時は仕方がないので音質を落としていきましょう。
再エンコは動画部分のエンコに時間が無駄なのでffmpegを直にたたいて調整していきます。
今回エンコしたwebmをa.webmとします。

aviutilのファイル→WAV出力でソースとなるWAVファイルを出力します。(a.wavとします)

まず、a.webmから音声ファイルを削除しb.webmを出力します。

> ffmpeg.exe -i a.webm -an -vcodec copy b.webm 

この時b.webmのサイズが動画だけのサイズなので次に作成するb.opusのサイズは(2M - a.webmのサイズ - 1k)くらいとなるようにすればいいことがわかります。1kくらい余分を持たせているのは詳しく調べていませんので不明ですがヘッダかな何かで単純に動画+音声とはなりません。余裕を持たせます。

それでは出力したa.wavファイルをopusに変換します。目標になるまで{音声のビットレート}を調整してコマンドを実行します。

> ffmpeg.exe -i a.wav -acodec libopus -b:a {音声のビットレート} b.opus

b.webmとb.opusのサイズを見ていい感じだと思ったら結合します。

> ffmpeg.exe" -i b.webm -i b.opus -vcodec copy -acodec copy c.webm

4. サムネイルをきれいにする

サムネに来るのは動画の0フレーム目なので0フレームに目立つ画像を配置しましょう。
編集→現在のフレームの画像をコピーして0フレーム目に編集→現在のフレームに画像を張り付けするといいでしょう…
とか書いてたんですが、長い動画だとすごく汚くなります。すごく。

そこで、サムネにしたい画像1フレームをaviutilで切り出し、d.webmにエンコします。(このとき無音の音声も追加してください)(3.)で確定したVP9とopusの設定でエンコするとトラブルがないと思います。

1フレームの動画ができたら、ffmpegで結合します。(ffmpegのconcatはバージョンによって記載法方法が違うらしいのでお使いのバージョンに合わせて読み替えてください)

> (echo file d.webm && echo file c.webm) | ffmpeg.exe -protocol_whitelist file,pipe  -f concat -i pipe:0 -c copy e.webm

あとはe.webmを投稿すれば終わり。お疲れ様でした。

Windows 10 October 2018 Update(1809)同士のリモートデスクトップができない

あまり情報がなくて再現パターンがはっきりとわからないのですがウチの環境は100%再現しているので書く。

事象としては、Windows 10(1809)からWindows 10 Pro(1809)にRDPすると画面が黒くなる、ログオンできてもフリーズするなどして操作できません。接続の状態は良好なので回線速度の問題ではないと思います。ホストと同じネットワークにいるWindows 8 Proには問題なく接続できますしandroid端末からは問題なくWindows 10 Pro(1809)にRDPできています。

私の環境を書いておくと、
・外部からVPNを用いてWindows 10(1809)からWindows 10 Pro(1809)にアクセス
・リモートマシンの指定はIPアドレス
・ログインアカウントはMSアカウントに紐づけ
これくらいでいいかな?

VPNがないとどうだとか、マシン名で指定するとどうなるかとか条件ははいろいろと確かめたいのだけど、しばらく試せないので保留。

ぐぐってみた感じ1809のmstsc.exeのバグのようで古いmstsc.exeを持ってくることで解決しました。参考までに試したmstsc.exe取得もとはWindows 8 Pro, Windows 10 Pro(1511)。Windows\System32配下から下記のファイルを取得し、適当なフォルダに格納します。

> tree /F
│  mstsc.exe
│  mstscax.dll
│
└─ja-JP
        mstsc.exe.mui
        mstscax.dll.mui

Windows Helloに対応していると(?)Windows Helloのログオン画面が出るのですが、ホストが対応していないのか設定が足りないのか接続できません。キャンセルしてパスワード認証することでログオンできます。1809のmstsc.exeでは出ないのでたぶんこの辺り改修があったのでしょう。

UWPウインドウからパッケージ名を取りたい

UWPアプリはApplicationFrameWindowクラスウインドウの子供にWindows.UI.Core.CoreWindowクラスウインドウが別プロセスでいるのでそこからプロセスIDを取得してよしなに。って感じの説明がぐぐるとヒットしますが、UWPウインドウがアクティブの場合、Windows.UI.Core.CoreWindowクラスウインドウが何故か存在しません。そこでApplicationFrameWindowクラスウインドウから情報を取得ってことになりますなるなった。

SHGetPropertyStoreForWindowを呼び出してIID_IPropertyStoreからPKEY_AppUserModel_IDを取得することでパッケージを取得できます。

IPropertyStore store;
Guid IID_IPropertyStore = new Guid("886d8eeb-8cf2-4446-8d02-cdba1dbdcf99");
SHGetPropertyStoreForWindow(window.Handle, ref IID_IPropertyStore, out store);
PropertyKey PKEY_AppUserModel_ID = new PropertyKey(
    new Guid("9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3"),
    5);
store.GetValue(ref PKEY_AppUserModel_ID, out PROPVARIANT v);
appName = Marshal.PtrToStringUni(v.value);

twitter公式で9E2F88E3.Twitter_wgeqdkkx372wm!x554f661dyd360y462cy8743yf8a99b7d41dbxが取れたら成功。

Zabbix v3.4でmirakurunの監視効率化を図る

更新しないといったなあれはry

yarukizero.hatenablog.jp

はい前回の続きです。
というか前回の記事の推敲中に気づいたのですが、Zabbix v3.4から追加された依存アイテムでmirakurunへのアクセス数を1回にできることが分かったので早速対応したという。

まずZabbixのmirakurun.confは前回と比べ驚きの1行に。

UserParameter=mirakurun.status,curl -Ss http://localhost:40772/api/status

でもって定義したmirakurun.statusを受け取るアイテムを作成します。そこから各種ステータス値を取るアイテムを追加していくわけですがアイテムのタイプをZabbixエージェントから依存アイテムに変更。マスターアイテムに先ほど追加したJSONを指定します。
f:id:azumyar:20180331230951p:plain

任意のキー名を決めたら保存処理タブに移って保存前の処理ステップにJSON Pathを追加、キーのパスを記述します。pidが取りたい場合$process.pidで。必要なアイテムを作り終わったら再度グラフを作っていきます。グラフに関しては特に迷うところないと思いますので割愛。

f:id:azumyar:20180331231831p:plain


お疲れ様でした。

mirakurunをZabbixで監視したい

mirakurunとchinachuγは導入済みの前提で話が進みます。当方の環境はdebianでZabbixは3.4。

mirakurun公式がもっているmuninプラグインみたいことをZabbixでもやりたい!という導入から。で方針としてmirakurunのstatus APIを叩いてJSONをパースすればよいという方向。まずはコマンドからJSONをよしなにパースしてくれるjqを導入する。

apt-get install jq

これでcurlと組み合わせて

curl http://localhost:40772/api/status | jq -r .process.pid

こうするとプロセスIDが取れる。IPアドレスとポートは設定の環境に合わせて読み替えてほしい。このままだと進捗が表示されていまい困ったことになるので-sオプションで進捗は非表示に。でもエラーの場合はなんかほしいので-Sもつけちゃう。

方針が決まったところで/etc/zabbix/zabbix_agentd.d に設定ファイルmirakurun.confを置いてstatus APIを叩いてユーザパラメータに格納していく。zabbixエージェントが古い場合はこのディレクトリ存在しないのでzabbix_agentd.confのinclude設定を有効化すればいい。

UserParameter=mirakurun.memory[*],curl -Ss http://localhost:40772/api/status | jq -r .process.memoryUsage.$1
UserParameter=mirakurun.stream[*],curl -Ss http://localhost:40772/api/status | jq -r .streamCount.$1
UserParameter=mirakurun.error[*],curl -Ss http://localhost:40772/api/status | jq -r .errorCount.$1
UserParameter=mirakurun.timer[*],curl -Ss http://localhost:40772/api/status | jq -r .timerAccuracy.$1.avg
UserParameter=mirakurun.events,curl -Ss http://localhost:40772/api/status | jq -r .epg.storedEvents 

ユーザパラメータを定義したらzabbix-agentを再起動。


続いてZabbix Serverの設定。
ここからは文章で書くのめどいのでスクリーンショットを貼っていく。

こんな感じでアイテムを作って
f:id:azumyar:20180331063951p:plain

f:id:azumyar:20180331064450p:plain

こんな感じでグラフにする

f:id:azumyar:20180331065020p:plain

f:id:azumyar:20180331065137p:plain



今回特に触れないけど録画のストレージ容量はchinachuのほうのAPIから同じ感じで取得できるしグラフもかける。

30秒毎にTCPセッションがはられるのは無駄な気しかしないのでキャッシュするなにがしを噛ましたほうがいい気がするがおいおい。まだ構築したばかりで運用はこれからなので気づいたことがあればアップデートされるかもしれないしされないと思う。

Xamarin.AndroidでISerializableする話

結論=>無意味。JSONあたりにシリアライズして文字列としてやり取りしよう。

以下結論に至るまでことの顛末を記す。
Androidな開発でクラスをシリアライズすることは多々ある。Activityの遷移とか保存とかでBundkeにシリアライズして受け渡したりとか。そこでクラスを作る。Javaのシリアライザを使いたいのでJava.Lang.Objectから派生しよう。

class JavaSerializable : Java.Lang.Object, Java.IO.ISerializable { }

ところがこれだと復元時に例外が発生し、復元されない。

E/AndroidRuntime(15922): FATAL EXCEPTION: main
E/AndroidRuntime(15922): Process: App1.App1, PID: 15922
E/AndroidRuntime(15922): android.runtime.JavaProxyThrowable: System.NotSupportedException: Unable to activate instance of type App1.Moc from native handle 0x1d (key_handle 0x2bc79163). ---> System.MissingMethodException: No constructor found for App1.Moc::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership) ---> Java.Interop.JavaLocationException: Exception of type 'Java.Interop.JavaLocationException' was thrown.

原因はXamarinがインスタンスの復旧処理を行う場合(IntPtr, Android.Runtime.JniHandleOwnership)なコンストラクタを介して行うからだそうだ。つまり下記で回避できる。

class JavaSerializable : Java.Lang.Object, Java.IO.ISerializable {
    public JavaSerializable() { }
    public JavaSerializable(
        IntPtr handle,
        Android.Runtime.JniHandleOwnership transfer
    ) : base(handle, transfer) { }
}


しかしこれでエラーは出なくなるもののよくよく見るとメンバが規定値で復元されていない。調べてみるとJavaのISerializableはコンパイラがreadObject, writeObjectメソッドを生成して処理するけどXamarinはそんなことしてくれないので自分で作って保存復元処理を書く必要がある。それを踏まえてついでにコードの再利用を鑑みると下の感じに。

class JavaSerializable : Java.Lang.Object, Java.IO.ISerializable {
    public JavaSerializable() { }
    public JavaSerializable(
        IntPtr handle,
        Android.Runtime.JniHandleOwnership transfer
    ) : base(handle, transfer) { }

    [Export("readObject", Throws = new[] {
        typeof (Java.IO.IOException),
        typeof (Java.Lang.ClassNotFoundException)})]
    private void __ReadObject(Java.IO.ObjectInputStream source) {
        var byteArray = new byte[source.Available()];
        source.Read(byteArray);
        using (var memStream = new MemoryStream()) {
            var binForm = new BinaryFormatter();
            memStream.Write(byteArray, 0, byteArray.Length);
            memStream.Seek(0, SeekOrigin.Begin);
            var obj = binForm.Deserialize(memStream);
            this.Obj = obj;
        }
    }

    [Export("writeObject", Throws = new[] {
        typeof (Java.IO.IOException),
        typeof (Java.Lang.ClassNotFoundException)})]
    private void __WriteObject(Java.IO.ObjectOutputStream destination) {
        var bf = new BinaryFormatter();
        using (var ms = new MemoryStream()) {
            bf.Serialize(ms, this.Obj);

            destination.Write(ms.ToArray());
        }
    }

    public object Obj { get; set; }
}


C#だと*thisの差し替えなんてことはできないので折衷案としてプロパティObjに保存したいクラスを格納して~といった感じになるなった。しかしObjがobject型なのはキモい。ジェネリックを使ってタイプセーフにしたいところである。が、ISerializableなクラスをジェネリッククラスにすると復元時にジェネリックパラメータが不明なので復元できない。困った。

さて途中でお気づきだと思うが自分でbyteにシリアライズ、デシリアライズするならクラス化の必要なくbyteでBndleにputすればよい気がするし、今時ならJSONシリアライズしてやりとりしたほうが絶対再利用性が高い。

という感じで結論に至る。