SwiftでQRコードを生成する

Core Imageを使うと簡単に作れるんですね〜
Swiftで実装するとこんな感じです。

func makeQRCodeImage(code:String) -> UIImage? {    
    guard let ciFilter = CIFilter(name: "CIQRCodeGenerator") else {
        return nil
    }
    ciFilter.setDefaults()
    
    // QRコードを設定
    ciFilter.setValue(code.data(using: String.Encoding.utf8), forKey: "inputMessage")
    
    // 誤り訂正レベルを設定
    // "L" "M" "H" が設定できるみたい
    ciFilter.setValue("M", forKey: "inputCorrectionLevel")
    
    if let outputImage = ciFilter.outputImage {        
    	// 作成されたQRコードのイメージが小さいので拡大する
        let sizeTransform = CGAffineTransform(scaleX: 10, y: 10)
        let zoomedCiImage = outputImage.applying(sizeTransform)
        return UIImage(ciImage: zoomedCiImage, scale: 1.0, orientation: .up)
    }
    
    return nil
}

SharedPreferencesでputStringSetを使う時の注意点

こんな感じでgetStringSetで得た結果に直接要素を追加して、
putStringSetしてもダメっぽい。

SharedPreferences pref 
  = context.getSharedPreferences("name",Context.MODE_PRIVATE);

String key = "keyA";

// Setを取得する
Set<String> set1 = pref.getStringSet(key, new HashSet<String>());

// Setに文字列を追加
set1.add("追加する文字列");

// そのまま保存
SharedPreferences.Editor editor = pref.edit();
editor.putStringSet(key, set1);
editor.apply();

 

この状態でgetStringSetするとちゃんと設定されてて正常に動いているように見えるんだけど、
プロセスが再起動されたあとに消えてます。(2件目以降)

対応としては下記のように別のSetを作ってコピーしてあげるとちゃんと動いてくれた。

// Setをクローンする
HashSet<String> set2 = new HashSet<>();
set2.addAll(set1);

SharedPreferences.Editor editor = pref.edit();
editor.putStringSet(key, set2);
editor.apply();

モヤモヤしますな。

ルビ付きのNSAttributedStringを使うとiOS8でBAD_ACCESSが発生する

原因としてはルビに空文字を設定してるのが悪いっぽい。

たとえばこんな感じ

// 空文字をルビとして設定する
let ruby = ""
var textRef: [Unmanaged<CFString>?] = [Unmanaged<CFString>.passRetained(ruby as CFString) as Unmanaged<CFString>, .none, .none, .none]
let annotation = CTRubyAnnotationCreate(.auto, .auto, 0.5, &textRef[0]!)
                
let attributedString = NSAttributedString(
                    string: "漢字",
                    attributes: [kCTRubyAnnotationAttributeName as String: annotation]

// ↓ ここでEXC_BAD_ACCESSが発生する
let line = CTLineCreateWithAttributedString(attributedString)

 

ほかにも

let framesetter = CTFramesetterCreateWithAttributedString( attributedText)

// ↓ ここでEXC_BAD_ACCESSが発生する
let frame = CTFramesetterCreateFrame(framesetter, CFRange(), path, nil)

ちなみにiOS10じゃ発生しないよ。

Javaで青空文庫のルビをパースする

Swiftで作ったサンプルのJava版です。
仕様などはSwift版と同様なので→Swift版

こちらもJavaで正規表現を利用するサンプルにもなってます。
 

使い方

String sampleText = "てめえらの|血《ち》は|何色《なにいろ》だーっ!!";
AozoraRubyParser parser = new AozoraRubyParser(sampleText);
List<AozoraRubyParser.Result> tokens = parser.parse();
for (AozoraRubyParser.Result r : tokens){
  Log.d("", r.text +  (r.ruby != null ? "[" + r.ruby + "]" : "") );
}

 

パーサーのコード

public class AozoraRubyParser {

  static public class Result {
    public String text;
    public String ruby;

    public Result(String t, String r) {
      this.text = t;
      this.ruby = r;
    }
  }


  private String text;

  public AozoraRubyParser(String text) {
    this.text = text;
  }

  public List<Result> parse() {

    ArrayList<Result> result = new ArrayList<>();

    String patternStr = "|(.+?)《(.+?)》";
    Pattern ptn = Pattern.compile(patternStr);

    int position = 0;

    Matcher matcher = ptn.matcher(text);
    while (matcher.find()) {
      if (position < matcher.start()) {
        String subText = this.text.substring(position, matcher.start());
        result.add(new Result(subText, null));
      }
      position = matcher.end();

      // 文字と読み仮名を抽出
      result.add(new Result(matcher.group(1), matcher.group(2)));
    }

    // 文末の残りを追加
    if (this.text.length() > position) {
      String subText = this.text.substring(position, this.text.length());
      result.add(new Result(subText, null));
    }

    return result;
  }
}

Swiftで青空文庫のルビをパースする

青空文庫のルビ表記をパースするサンプルを作ってみました。

青空文庫のルビはこんな感じで定義されてます。
http://www.aozora.gr.jp/KOSAKU/MANUAL_2.html

本来はリンクの通り、いろいろと細かな仕様があるのですが、今回はプログラムで「ルビ付き文字列を定義する」ということだけを狙ってシンプルな仕様で実装してみます。
具体的には↓のフォーマットだけを対象とします。

|漢字《よみかた》

例えばこんな感じですね。

「てめえらの|血《ち》は|何色《なにいろ》だーっ!!」

先に使い方ですが

let text = "てめえらの|血《ち》は|何色《なにいろ》だーっ!!"
let parser = AozoraRubyParser(text: text)
if let result = parser.parse() {
	for r in result {
		print("\(r.text) \( (r.ruby != nil) ? "[" + r.ruby! + "]" : "" )")
	}
}

こんな感じで出力されます
> てめえらの
> 血 [ち]
> は
> 何色 [なにいろ]
> だーっ!!

class AozoraRubyParser {
  
  class Result {
    let text:String
    let ruby:String?
    init(text:String,ruby:String?) {
      self.text = text
      self.ruby = ruby
    }
  }
  
  
  let text:String;  
  
  init(text:String) {
    self.text = text;
  }
    
  func parse() -> [Result]?{    
    
    var result:[Result] = []
    do {
  
      var position = 0;
      let nsText = text as NSString
      let totalLength = nsText.length
      
      let re = try NSRegularExpression(pattern: "|(.+?)《(.+?)》", options: [])
  
      let matches = re.matches(in: text, options: [], range: NSRange(location: 0, length: totalLength))
      for match in matches {
        let matchRange = match.range;
        
        // 現在の位置からrangeの先頭が離れていたら、その間の文字を抽出する
        if position < matchRange.location {
          let subRange = NSRange(location: position, length: matchRange.location - position)
          result.append(Result(text: nsText.substring(with:subRange), ruby: nil))
        }
        position = matchRange.location + matchRange.length;
	
        // 文字と読み仮名を抽出
        let str = nsText.substring(with: match.rangeAt(1))
        let ruby = nsText.substring(with: match.rangeAt(2))
        result.append(Result(text: str, ruby: ruby))
        
      }
      
      // 文末の残りを追加
      if totalLength > position {
        let subRange = NSRange(location: position, length: totalLength - position)
        result.append(Result(text: nsText.substring(with:subRange), ruby: nil))
      }
      
      
    }catch let err  {      
      log.error("\(err)")
      return nil;
    }
  
    return result
  }

}

Swiftで正規表現を使うサンプルとしてもどうぞ。

「Server aborted the SSL handshake」とか怒られる件

なんですかね。
gitも


$ git push
fatal: unable to access 'https://****.***/': Server aborted the SSL handshake

って言われるし、curlも


$ curl https://****.***/
curl: (35) Server aborted the SSL handshake

pipも


$ pip install -U pip
Could not fetch URL https://pypi.python.org/simple/pip/: There was a problem confirming the ssl certificate: EOF occurred in violation of protocol (_ssl.c:661) - skipping

とか言われて実行できない。
困った。
2日ほど困った。

これまでは動いてたのに・・

ふと思いついた。
こんな時、大概悪いのはセキュリティソフト – Avast – だ!!!!

で、AvastのWebシールドを無効にしたらオールオッケー!!!

Avastのおかげで無駄な時間を使わされました!くそっ!くそっ!

Macメインになってセキュリティソフトから受けるストレスはかなり(体感97%ほど)減りましたけど、たまにこんなのあるんですよねぇ。

※ちなみに環境は↓
– OSX El Capitan (10.11.6)
– Avast Mac セキュリティ 2016
 プログラムバージョン12.6
 ウイルス定義(17042000)

サーバをEC2に引っ越しました。

まぁ、仮住まいですが。

これまではこのブログはネットオウルのミニバードってサービス使ってました。

このミニバード、安い割にMySQLのデータベースを複数作成できたりサブドメイン・マルチドメインに対応していたり、
何かと融通が利いて気に入っていたのですけど、最近ちょっと遅いかなーと感じてきて。。
特に混んでいる時間帯はWordpressの管理画面を表示するのをためらう事がありました。

うーん、どこかに引っ越そう。。
ちょうどawsのLightsailがサービス開始されたのでそこに移ろうと思ったのですが、
Tokyoリージョンではまだリリースされていないみたい。

そこで仕事でよく使っているEC2に引っ越しです。
1年間は無料ですし。(1年内にきっとLightsailが東京に来るでしょう??)

サーバー構築は1年以内に再度引っ越す事に備えansibleで行いました。
多少の修正があるにしても次の引っ越し時、大いに役立ってくれることでしょう。

混んでる時間帯でもサクッと表示されるのは気持ちいいですね。

AndroidでQRコードをリーダーを作る(超シンプル版)

GooglePlayサービス 7.8以降では「Mobile Vision API」ってのが追加されていて、これを使うとQRコードの認識が行えるようです。
ググってみると色々とサンプルはあるのですが、いかんせん複雑になっており「QRコードを認識する!」って箇所がわかり辛く思いました。
そこで超シンプルなサンプルを作成しましたよ。

簡単にQRコードが認識できると思いますよ。

まずは準備から。

build.gradleへ依存ライブラリを追記します。

dependencies {
  compile 'com.google.android.gms:play-services-vision:10.2.0'
}

次にManifestへ権限などを追記します。
下記の uses-feature、uses-permission、meta-dataを追記してください。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.eyewhale.sample">
  
  <!-- ↓ この2行を追記  -->
  <uses-feature android:name="android.hardware.camera" android:required="true"/>
  <uses-permission android:name="android.permission.CAMERA" />
  
  <application ・・ >
    
      <!-- ↓ このmeta-dataを追記  -->
      <meta-data
          android:name="com.google.android.gms.version"
          android:value="@integer/google_play_services_version" />
      <meta-data
          android:name="com.google.android.gms.vision.DEPENDENCIES"
          android:value="barcode" />

  </application>
</manifest>

これは説明がいるか不明ですが、、超シンプルなレイアウトファイル。
SurfaceViewにカメラから撮影された画像が表示されます。
(今回のサンプルではFragmentにしてみてます。よくあるサンプルがActivityだったので)

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/camera_preview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

ようやく実装です。
実際のコードは最後に記述しておきますが、
大きな流れはこんなかんじです。

1. SurfaceViewにコールバックを設定
2. BarcodeDetectorを作ってProcessorを設定する
3. CameraSourceを作成してBarcodeDetectorを設定
4. SurfaceViewの準備ができたらCameraSourceを開始
  →バーコード認識も開始される
5. バーコードが認識されたらProcessorのreceiveDetectionsが呼ばれるので適当に処理

あと、Pause/Resumeでカメラの停止/再開を行っています。

import com.google.android.gms.vision.CameraSource;
import com.google.android.gms.vision.Detector;
import com.google.android.gms.vision.barcode.Barcode;
import com.google.android.gms.vision.barcode.BarcodeDetector;

public class BarcodeSampleFragment extends Fragment {
  private final static String TAG = "BarcodeSampleFragment";
    
  private SurfaceView _surfaceView;
  private CameraSource _cameraSource;
  private boolean _surfaceCreated = false;
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_barcode_sample, container, false);

    // SurfaceViewにコールバックを設定
    _surfaceView = (SurfaceView)view.findViewById(R.id.camera_preview);
    _surfaceView.getHolder().addCallback(_surfaceCallback);
    return view;
  }

  @Override
  public void onResume() {
    super.onResume();
      
    // CameraSourceが未作成 or 破棄されていたら作成
    if (_cameraSource == null) {
        setupCameraSource();
    }
        
    // SurfaceViewが準備できていたらキャプチャを開始
    if (_surfaceCreated){
        startCameraSource(_surfaceView.getHolder());
    }
  }

  @Override
  public void onPause() {
    super.onPause();
    // キャプチャを停止
    if (_cameraSource != null){
      _cameraSource.stop();
    }
  }
    
  private void setupCameraSource(){
    // QRコードを認識させるためのBarcodeDetectorを作成
    BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(this.getContext())
        .setBarcodeFormats(Barcode.QR_CODE)
        .build();
    // DetectorにProcessorというコールバックを設定
    barcodeDetector.setProcessor(_detactorProcessor);

    // CameraSourceを作成
    _cameraSource = new CameraSource.Builder(this.getContext(), barcodeDetector)
        .setAutoFocusEnabled(true)
        .setFacing(CameraSource.CAMERA_FACING_BACK)
        .build();
  }

  private void startCameraSource(SurfaceHolder holder){
    try {
      _cameraSource.start(holder);
    } catch (IOException e){
        Log.e(TAG,"",e);
    }
  }
  
  private Detector.Processor<Barcode> _detactorProcessor = new Detector.Processor<Barcode>(){
    @Override
    public void release() {
    }
        
    @Override
    public void receiveDetections(Detector.Detections<Barcode> detections) {
      // 認識された結果がdetectionsに入ってる
      for(int i=0;i<detections.getDetectedItems().size();i++){
        int key = detections.getDetectedItems().keyAt(i);
        Barcode barcode = detections.getDetectedItems().get(key);
        String barcodeValue = barcode.rawValue;
        Rect rect = barcode.getBoundingBox();
        // 認識結果をログへ出力
        Log.d(TAG,"detect barcode:" + barcodeValue + " " + rect.toString());
      }
    }
  };

  private SurfaceHolder.Callback _surfaceCallback = new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
      try {
          // カメラのキャプチャを開始
          _cameraSource.start(holder);
      } catch (IOException e){
          Log.e(TAG,"",e);
      }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
      _cameraSource.stop();
      _cameraSource.release();
      _cameraSource = null;
    }
  }; 
}

※ 注意!
このサンプルはシンプルにするため色々と省いてます
– カメラへアクセスするための権限チェック
– GooglePlayServicesが利用可能であるかのチェック
– BarcodeDetectorが利用可能であるかのチェック
– その他もろもろ
実際に使う時は注意してくださいね!

DLNAで再生してたら途中で中断される問題!(DMCアプリ落ち)

ネットワークオーディオ機器を使ってみたくて「Marantz M-CR611」を購入したんですよ。こないだ。(→記事)
「Marantz Hifi Remote」からNASに保存されている上原ひろみの「SPARK」を再生して「ええ音や・・(涙」と聞いてたら数曲再生しただけで止まってしまう・・萎えるー

これが「DMCアプリ落ち問題!」
→ なんらかの原因(※)でDMCアプリが停止したら次の曲以降が再生されない。
※スリープ、バッテリー切れ、ゲームなどの高負荷アプリを動かす・・など

比較的新しいiPhoneであればバックグラウンドでそれなりの時間までは再生してくれるんですけど、なにぶんちょっと古めのAndroidとかだとすぐに再生が止まってしまいます。
最初はこれ、なんでか分かりませんでしたがDLNAの機能を考えるとまぁしょうがない。
DMRは再生するリスト(以下プレイリストと呼ぶ)を保持せず、DMCからの指示で動くのでDMCが動いてくれないとどうしようも無いのだ。
(とはいえ、「Marantz Hifi Remote」はMarantz純正アプリでもあるんだから機器に専用コマンドで送信してM-CR611をDMPとして動かしてくれよ!・・とは思います。)

補足1.
DLNAを構成する機能

DMS(デジタルメディアサーバー)
 - コンテンツを保存してそれらを配信する機能を有する機器。DLNA対応のNASとかがこれにあたる。
DMP(デジタルメディアプレイヤー)
 - DMSのコンテンツを自分で検索/再生する機器。M-CR611のようなネットワークオーディオ機器は大概これ。
DMC(デジタルメディアコントローラー)
 - DMSのコンテンツを選択してそれを再生する機器(DMR)に指示するリモコンみたいな機能。スマホアプリ(Kazooとか)とか。
DMR(デジタルメディアレンダラー)
 - DMCから指令を受ける再生機器 。DMPとの大きな違いはDMCからの指令で再生すれば良いのでDMSのコンテンツを検索する機能を持たなくても良い。

補足2.
M-CR611のようなネットワークオーディオプレイヤーは大抵DMPにもDMRにも対応しています。
DMPとして動かせば(例えば本体でサーバの曲を選んで再生するとか)DMCは関係無いので、この場合はアプリが落ちようと関係無く再生されます。

で、色々と調べているとこの問題を解決する「OpenHome」という規格があるらしい。
しかもどうやら 「BubbleUPnP Server」というものを使うとOpenHome非対応の機器もOpenHome対応機器として動かすことができるらしい。
やってみよう!

BubbleUPnP Server

「BubbleUPnP Server」はMacやWindowsはもちろん、LinuxやAndroid、さらにはSynologyやQNAPなどのNASでも動作する。

用途を考えると常時起動している機器にインストールするのがベスト。
RaspberryPiへインストールする事も考えたが、今回は自宅のNAS「Synology DS215j」にインストールすることとした。

※ Kazoo Server も気になりますが、まだsynology用のパッケージは提供されていないようです。
 
 

Synology DS215j(DSM 6.0)へのインストール手順

(以下は執筆時点の手順なので公式のインストール手順を事前に確認してください)
※自分は今のところ正常に動いていますけど、自己責任でお願いしますね。大事なデータが消えたりしても自己責任で!

1.Java8のインストール
DSMの パッケージセンター → デベロッパーツール から 「Java8」 を選んでインストール。

2.Java8をOracleJDKに更新
DSMのメインメニューから「Java8」の画面を開いて

20170113_001
→ [Javaをアップグレード]

20170113_002
→ 「Java SE Download」

20170113_003
→ 「Linux ARM 32 Hard Float ABI」をダウンロード
※執筆時には「jdk-8u111-linux-arm32-vfp-hflt.tar.gz」こんなファイルがダウンロードされました。

先ほどの画面でダウンロードしたファイルを参照して[OK]

※ダウンロードするファイルは機種によって違うらしいです。↓のサイトを参照してくれとのこと。
http://minimserver.com/install-synology.html

3.事前の設定

コントロールパネル > ユーザー > 詳細
20170113_004
[ユーザーホームサービスを有効にする]にチェックをいれて[適用]

証明書のチェックを無効に
パッケージセンター > 設定
20170113_005
トラストレベルを「全ての製造元」に変更して[OK]

4.「BubbleUPnP Server」のインストール

下記URLからSynology用のパッケージをダウンロード
https://www.bubblesoftapps.com/bubbleupnpserver
※[Download Synology package]ってところ
※自分が行った時は BubbleUPnPServer.spk というファイルでした

パッケージセンター を開き[手動インストール]からダウンロードしたファイルを選びインストールします。
スクリーンショット_2017-01-13_16_57_30
※ffmpegのインストールが推奨されているけど今回はインストールせず。
 
  

BubbleUPnP Serverの設定

インストールできたら次は設定
※インストール直後に設定画面が開かれますけど、まだ起動に時間がかかっていたりすると空白ページが開かれてしまいます。
その時は、ちょっと待ってからメインメニューから[BubbleUPnP Server]を選んで再度開きましょう。
20170113_006

1.ネットワーク系
インターネットからアクセスしたくはないので、[Network and Security]というタブを開いて[Allow to access the server from the Internet]のチェックを外す。

2.DMRをOpenHome機器として公開する設定
[Media Renderers]というタブを開く
20170113_007

出力対象とするDMR (この場合はM-CR611)を選んで、[Create an OpenHome renderer]にチェックを入れる

以上でサーバ側は完了。簡単。

後はHomeHomeに対応したアプリ(※)でレンダラーを指定すればOK。
DMRとしてのM-CR611とOpenHome化された同機器がどちらも表示されているのでOpenHomeの方を選択

同じレンダラーを参照したアプリでは同じプレイリストが表示されるし、アプリを落としてもそのまま再生されます。
※ AndroidだとBubbleUPnP、iPhoneだとKazooとか

NASのCPU負荷も特に上がっていませんね。
20170113_008
これなら大丈夫そう。

JSONパーサー Unbox をデータクラスを定義せずに使用する方法

Unboxは便利ですね〜
結構便利でswiftでjson扱う時はこればっかり使っています。
WEBで見てもユーザがどんどん増えている気がしますね。

さて、そんな便利なUnboxですが決まったデータクラスが存在しない時の扱いについてサンプルが少ないように感じました。
特にマップを要素に持つjson配列の扱いにちょっと迷いましたのでここにメモっておきます。

ポイントは performCustomUnboxing*** 系のメソッドを使う事と、 UnboxableDictionary を使う事です。



/*  
jsonStringには下記のjsonが入っているとして

{
   "storename":"金沢店",
   "stocks":[
      {"item":"白菜","stock":12},
      {"item":"甘エビ","stock":439}
   ]
}
*/

if let data = jsonString.dataUsingEncoding(NSUTF8StringEncoding) {           
  do {
                
    // performCustomUnboxing*** を使うのがポイント
                
    let result = try Unboxer.performCustomUnboxingWithData(data) { (unboxer) -> String in
                    
      // ルートの"storename"を取得
      let storename = unboxer.unbox("storename") ?? "unknown store"
                    
      // マップを含むjson配列はUnboxableDictionaryの配列として取得する
      let stockDictArray:[UnboxableDictionary] = unboxer.unbox("stocks")
                    
      // 取得したjson配列を performCustomUnboxingWithArray でパース
      let stocks = try Unboxer.performCustomUnboxingWithArray(stockDictArray) { (unboxer) -> (String,Int) in
                      
          let item = unboxer.unbox("item") ?? ""
          let stock = unboxer.unbox("stock") ?? 0
                        
          // 結果をtupleで返却
          return (item,stock)
      }
                    
      // パースした結果を文字列にして返却
      var caption = "storename:\(storename) \n"
      for stock in stocks {
        caption += " item:\(stock.0) stock:\(stock.1) \n"
      }
      return caption
    }
                
    print(result)
                
  } catch let error as NSError {
    print(error)
  }
}  

/*
[output]  
         
storename:金沢店  
item:白菜 stock:12  
item:甘エビ stock:439  
*/