Swift第3弾 Fiio X3 2ndのファームウェアをMacだけでいじくる

広告

Swift第3弾として、今回はMacでFiio X3 2ndのテーマをいじっちゃおうというテーマでやってみたいと思います。
 

最初は色変更だけのつもり


最初は、アイコンの色変更を簡単にできないかなと思っていました。
以前に私のように絵心が無いけどテーマに合わせて色変更を行うには、GIMPのグラデーションマップを使うのが簡単ですよ。というのを書きました。
しかし、アイコンの数が多いので、一つ一つグラデーションマップで色変更をしていては時間がかかります。
そこで、GIMPのバッチ機能を使って自動化しちゃおうと思いました。
しかし、色を決めるのも簡単にしたかったので、Swiftで作った画面で色を選択させて、それをGIMPのバッチモードで回せば良いかなと思い、その部分を作りました。

アイコンの色選択

しかし、これだけだとなぁということで、色はグループごとに変えられるようにしました。
さらに、壁紙をよく見えるように透過も自動化できるようにしました。

アイコンを色々変更

MacからGIMPのバッチモードを叩くのに苦労しましたが、目的は果たしました。

変更後

こんな感じ。

バッチモードをSwiftから使う場合には
NSTaskを使う。そして引数に"-c"を使うと、後は普通にターミナルで使う文で使用できる。
self.task = NSTask()
self.task.launchPath = "/bin/sh"
self.task.arguments = ["-c", "コマンド"]
self.task.launch()
self.task.waitUntilExit()

※dispatch_asyncで並列処理をすると思うので、self付き。
 

壁紙も変更


壁紙自体は作るとして、ファイル名を適切なものに変更して、ディレクトリに格納するのも面倒だなと思い、ファイルを選んだら適切な場所へ適切な名前で格納されるようにしました。

Swiftでは、ファイルの上書きは実装されていないので、存在チェック→ファイル削除→ファイルコピーが必要。
 
func copyFile(source: String, destination:String, filename:String, overWrite:Bool) -> Bool {
        
        var error: NSError? = nil
        let fileManager = NSFileManager.defaultManager()
        
        //Make directory if not exist
        if !File.exists(destination) {
            fileManager.createDirectoryAtPath(destination, withIntermediateDirectories: true, attributes: nil, error: &error)
        }
        
        //Copy file
        var flg = overWrite
        if (!flg) {
            if !File.exists(destination + "/" + filename) {
                flg = true
            }
        }
        if (flg) {
            if File.exists(destination + "/" + filename) {
                fileManager.removeItemAtPath(destination + "/" + filename, error: &error)
            }
            
            fileManager.copyItemAtPath(source, toPath: destination + "/" + filename, error: &error)
        }
        return true
    }

※全部trueで返しちゃだめじゃん。今思った。
errorの内容で最後にエラーハンドリング入れましょう。
 

文字色・サイズの変更も


こうなったら、文字色もサイズも変えちゃおう。
ファイルで全部指定していて、中身はxmlタグなので、ファイルを読んで、NSDictionaryに突っ込めば良いでしょう。
ただ、このファイルはValidateなXMLでは無いので、そのままXMLReaderに入れるとエラーとなるので一工夫。
頭にrootタグを付けてあげればOK。
そして中国語のコメントもついているファイルなのでそこは読み飛ばす。また中国語なので、UTF8EncodeではなくUTF16Encodeを使う。という処理を入れて、ValidateなXMLにする。
保存する場合は、元のファイルに忠実にするために、再度1行ずつ読んで、タグ部分とNSDictionaryのkeyが一致した場合に書き換える。

ここはこのファイル特有でトリッキーなやり方なので割愛。

xmlタグ解析
この部分がそう。
 

プレビューできたらいいよね


最後まで悩んだのがプレビュー。
本来の動きが分からないし検証するほどでもないので、アイコンサイズはデフォルトの状態で、見た感じおかしくないことというのが前提。
アイコンサイズを変更するようになったら、検証してなんとなく合うように調整することにしよう。
ここはCALayerを使って表示する。
基本的には、アイコンサイズと、場所、最大高さ等から真ん中に来るように配置してみる。
CALayerの配置は左下が0、私の頭は左上が0。
よって、配置に困る。そこで左上0の考えを左下0に変換するロジックを導入。
 
func convertY(maxHeight:Int, org:CGFloat, height:CGFloat) -> CGFloat {
        return CGFloat(maxHeight) - org - height
}

※maxHeightは配置する場所の最大の高さ。ど真ん中なら画面の高さ
※orgは左上0からの位置
※heightはアイコンならそのアイコンの高さ
※maxHeightはCGFloatで受けても良かったけど、基本的に画面サイズの高さとなり、固定値に近いのでIntを引数にした。
 

フォントを何とかしたい


フォントは場所指定だとCALayerでフォント指定ができないため、Macにインストールされているフォントを使用することにした。
X3IIはTrueTypeFontなら使えそうだったので、MacにインストールされているTrueTypeフォントを選択できるようにして、それをプレビューとファイル転送でファームの中に格納する。

フォントの取得はこんな感じ
NSFontManagerから、利用できるフォントを取得。余計なフォント(今回は.で始まるものとTrueType以外)を排除して、その名前とパスを取得して、配列に格納する。
 
func getAllFont() -> Array {
        var ret = [Font]()
        for family in NSFontManager.sharedFontManager().availableFonts
        {
            if !family.hasPrefix(".") {
                let fontRef:CTFontDescriptorRef = CTFontDescriptorCreateWithNameAndSize(family as! String, 14.0)
                let url:AnyObject? = CTFontDescriptorCopyAttribute (fontRef, kCTFontURLAttribute)
                let path:String = (url as! NSURL).path!
                if (path.hasSuffix(".ttf")) {
                    var font = Font()
                    font.name = family as! String
                    font.path = path
                    ret.append(font)
                }
            }
        }
        return ret
}
 

プレビュー画像を保存したい


CALayerで重ねた画像をpngなどで保存できたら良いかもなということで、やってみた。
これが結構面倒くさい。
NSImageViewのLayer部分を取得して、それをNSBitmapImageRepに変換して、保存する。
このNSBitmapImageRepに変換するところが大変でStackOverFlowなどを参考にやってみる。
func bitmapImageForLayer (layer:CALayer) -> NSBitmapImageRep?
{
        let pixelsHigh:Int = Int(layer.bounds.size.height)
        let pixelsWide:Int = Int(layer.bounds.size.width)
    
        let bitmapBytesPerRow:Int = pixelsWide * 4
        let bitmapByteCount:Int = bitmapBytesPerRow * pixelsHigh
    
        let colorSpace:CGColorSpaceRef = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
    
        let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue)
        let context = CGBitmapContextCreate(nil, pixelsWide, pixelsHigh, 8, 0, colorSpace, bitmapInfo)
        
        if (context == nil) {
            return nil
        }
    
        layer.renderInContext(context)
        let image:CGImageRef = CGBitmapContextCreateImage(context)
        let bitmap = NSBitmapImageRep.init(CGImage: image)
        return bitmap
}

※let bitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue)←この部分が、最近のSwiftで書き方が変わったらしい。
※このあと、NSBitmapImageRepをTIFFRepresentation.writeToFile(path, atomically: true)で書き出せばOK。

 

WindowsのexeをMacから使いたい


windows用のexeでファームウェアはpackしたりunpackしたりする。そのためにWindowsを立ち上げるのも面倒なので、何とかMacでできないかと考えた結果、Wineを使うことにした。
Swiftからは、GIMPを読み出したのと一緒で、NSTaskを使って起動する。
これで、Macだけで、unpack、packが可能になる。

 

最後に転送


USBで繋いでおけば、ルート直下に転送も可能にしている。
これは、曲転送&プレイリスト作成アプリのTransferでやっているのと一緒なので簡単。
Volumes配下を検索して、X3を選ばせてファイルをコピーするだけ。
ファイルの上書きはさっき書いた壁紙のコピーと同じ。

 

最後に


storyboardに詰め込みすぎて重たい…
一画面でできるのは楽だけど、分割した方が良かったなとちょっと後悔。
起動終了画面のアニメーションも見られるようにしようかなとも思うけど、あまり変更しないところだから、まぁその時に考えようかな。
あとは、自動変換で気に入らないアイコンは個別で変更することで対応。

できた画面はこちら

最終画面

Swiftにもだいぶ慣れてきました。
今回は、Swift + GIMP + Wineで頑張ってみました。
連携することで何でもかんでも自前でやる必要もなく便利になりますね。

気になったことは、Wine起動して少しするとデバッグが落ちることがあることですかね。
毎回でもなく、起動すぐ、処理後すぐでもなく少しするとなので原因わからず。

実際Archiveしたものは問題ないので、何なのでしょうね。

で、最後にどういった動作になるかをちょっとだけ動画で。

 

画面の動画にQuicktimeを使ってるけど、どうやったらきれいに撮れるのかがわからん。

こんな感じでファームウェアをいじって好きな壁紙にしたりアイコンにしたりできて、コスパが良くて音も良い、Fiio X3 2nd。
おすすめです。