Programming」カテゴリーアーカイブ

CRLF

何を今頃だけど JavaScript の replace について。

String.prototype.replace() – JavaScript | MDN

第一引数を正規表現にすればすべてのマッチした箇所を置換できるのか。
最初に一致した箇所だけだとずっと勘違いしていた筆者であった。

JXA: doShellScript Line feed code | Paepoi Blog

これを試しに書き換えしてみよう。

#!/usr/bin/osascript

let app = Application.currentApplication();
app.includeStandardAdditions = true;

res = app.doShellScript('ls -l');
console.log(res.replace(/\r/g, '\n'));

いけた。

ついでに。
よく考えたらコマンドの中で tr を使って変換すりゃいいじゃん。
と思ってやってみたら上手くいかなかった。

#!/usr/bin/osascript

let app = Application.currentApplication();
app.includeStandardAdditions = true;

// no...
//res = app.doShellScript('ls -l | tr "\r" "\n"');

// test
res = app.doShellScript('ls -l | tr "\n" "|"');
console.log(res);

となる。

つまりコマンド実行の時点では改行コードは LF のまま。
doShellScript が値を戻す時に CR へ変換しているようです。
LF のまま戻すオプションって無いのかな?

lf

#!/usr/bin/osascript

let app = Application.currentApplication();
app.includeStandardAdditions = true;

res = app.doShellScript('ls -l', {alteringLineEndings:false});
console.log(res);

あった、今後はコレで。

ついでに、doShellScript は bash の POSIX 互換モードです。
zsh やフル状態の bash ではないので注意、macOS って本当に色々面倒臭い。
プログラミングするならやっぱり Linux だよ、某サル専用を除く。

関係ないけど Macbook Air 2020 が出たね、かなり良さげ。
でも筆者はこんな使い方ばかりだから 2018 モデルで何の不満も無いのよね。
ペチペチキーボードにもすっかり慣れてしまったし、うるさいけど。
さて 10.15.4 にアップデートするか。

echo hyphen

ポータブルな echo 代替、reverb コマンド – 拡張 POSIX シェルスクリプト Advent Calendar 2013 – ダメ出し Blog

そうか、echo はオプションとまったく同じ文字列を出力できなかったのか。
でもそんなの — を使えばいいんでね?

先頭にハイフンが付くファイルを削除できない – ITmedia エンタープライズ

echo

だめジャン、これ echo には通用しないのか。
先頭にハイフンがあるだけなら普通に表示できるし関係なかったか。
でも何か手段が他にあるはず、と思って探してみた。

Bashで文字列をエスケープをする – Qiita

printf %q “$value”
こんな手段があるとは。

printf

イケた、オプションと同じ文字列は case 文で振り分けすれば使えそう。
って zsh だと \n を入れなくても強制改行してしまうんだね。
そんなことより gnome-terminal で zsh を使うと Home/End キーでカーソル移動ができないことのほうが気になるぞ!
こんな所でも bash と zsh の違いがあるのがなんとも。

macOS Get UTI

Apple 関連で開発をしていると UTI を調べる必要がある場合が多々ある。

Uniform Type Identifier – Wikipedia

Uniform Type Identifier Concepts

検索をしていたら素敵なページを見つけた。

Mac や iOS でファイルの種類を表す識別子 Uniform Type Identifiers を拡張子から調べる(Swiftで1行で出来る) – niwatakoのはてなブログ

てゆーか JXA でも PyObjC でもできる。
しかし困ったことに JXA では CFString が NSString にキャストできない。

objective c – JXA: Accessing CFString constants from CoreServices – Stack Overflow

上記を見つけてようやく解決。
console.log って C 言語の char[] を出力できる、初めて知った。
UTF16LE に変換は不要、CJK 文字列でも問題ないようです。

#!/usr/bin/osascript -l JavaScript

let jp = $('スズキ GSX250R').UTF8String;
console.log(jp);
//=> スズキ GSX250R

// ex: ft=js.jxa

ということで JXA にて簡単に調べるコマンドを作ってみる。

#!/usr/bin/osascript -l JavaScript

ObjC.import('CoreServices');

function run(argv) {
    for (let ext of argv) {
        let uti = $.UTTypeCreatePreferredIdentifierForTag(
            $.kUTTagClassFilenameExtension, $(ext), null);
        let s = $.CFStringGetCStringPtr(uti, 0);
        console.log(`${ext}: ${s}`);
    }
}

// ex: ft=js.jxa

getuti.js

せっかくなので基底タイプも調べたいぞ。
JXA で得る方法が解らなかったので PyObjC で書いてみる。
PyObjC は CFDictionary や CFString も Python の型と等価なので超簡単。
他の言語を使うのが馬鹿馬鹿しくなってしまうので注意が必要。

#!/usr/bin/env python3

import sys, CoreServices

for ext in sys.argv[1:]:
    uti = CoreServices.UTTypeCreatePreferredIdentifierForTag(
        CoreServices.kUTTagClassFilenameExtension, ext, None)
    arr = CoreServices.UTTypeCopyDeclaration(uti)['UTTypeConformsTo']
    con = ','.join(arr)
    print(f'{ext}: {uti} [{con}]')

# ex: ft=py

@PyObjC

getuti という拡張子の無い名前で +x のパーミッションを付けパスの通った場所へ。

getuti

JXA でやりたかったけどまだまだ修行が足りない。

注意: macOS 13 Ventura からは上記が使えません。
macOS: Get UTI (Ventura) | Paepoi Blog

GExiv2 and subprocess

前回の GExiv2 の件。
comipoli に実装しようとしてハマったので覚書。

GSubprocess から GUnixInputStream を得て GExiv2 に読ませる。
その GUnixInputStream からは GdkPixbuf を作ることができない。
順番を逆にしても駄目、Stream の再利用はできないみたい。

他の手段で GExiv2 を使おうと思ったけど上手くいかない。
他の手段で GdkPixbuf を得ることは無理。
exif 取得と画像取得で別にアクセスするしかないようだ。
遅くなるだろうけど気にならないレベルならいいかなって。

しかし GSubprocess をもう一つ使をうとするもエラー。
原因は解らない、GSubprocess の情報なんて皆無だ。

失敗をズラズラ書いても無意味なので結論。
GSubprocess と subprocess を両方使う強行手段でなんとかなった。

class ComipoliArchive:
    def __init__(self):
    	# etc...

    def _zip_escape(self, filename):
        ESCAPE = '[]*?!^-\\'
        res = ''
        for s in filename:
            if s in ESCAPE:
                res += '\\'
            res += s
        return res

    def _on_wait(self, proc, res):
        proc.wait_check_finish(res)

    def __getitem__(self, num):
        if num == self.max:
            raise IndexError
        try:
            args = ['unzip', '-pj', self.path, self._zip_escape(self.namelist[num])]
            # tag
            ori = 0
            pr = subprocess.Popen(args, encoding='UTF-8', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            fd = pr.stdout.fileno()
            with open(fd, 'rb') as f:
                metadata = GExiv2.Metadata()
                metadata.open_buf(f.read())
                ori = metadata.get_orientation()
            pr.wait()
            # pixbuf
            sp = Gio.Subprocess.new(args, Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_MERGE)
            sp.wait_check_async(None, self._on_wait)
            stream = sp.get_stdout_pipe()
            p = GdkPixbuf.Pixbuf.new_from_stream(stream)
            if ori == GExiv2.Orientation.HFLIP:
                p = p.flip(True)
            elif ori == GExiv2.Orientation.ROT_180:
                p = p.rotate_simple(GdkPixbuf.PixbufRotation.UPSIDEDOWN)
            elif ori == GExiv2.Orientation.VFLIP:
                p = p.flip(False)
            elif ori == GExiv2.Orientation.ROT_90_HFLIP:
                p = p.rotate_simple(GdkPixbuf.PixbufRotation.CLOCKWISE).flip(True)
            elif ori == GExiv2.Orientation.ROT_90:
                p = p.rotate_simple(GdkPixbuf.PixbufRotation.CLOCKWISE)
            elif ori == GExiv2.Orientation.ROT_90_VFLIP:
                p = p.rotate_simple(GdkPixbuf.PixbufRotation.CLOCKWISE).flip(False)
            elif ori == GExiv2.Orientation.ROT_270:
                p = p.rotate_simple(GdkPixbuf.PixbufRotation.COUNTERCLOCKWISE)
            stream.close()
            sp.force_exit()
            return p
        except Exception as e:
            raise e

前回の cbz を開いてみる。

orientation

よし回転されているな。
注意点は ROT_90 は 90 度傾いているから 270 度回転させるということ。
戻す度数という意味ではない、flip は手持ちサンプルが無いのでこれで正しいか未確認。
後 Popen は wait を忘れずに。

Scaling: GDK-PixBuf Reference Manual

展開速度も別に気にならない、てか何か変わったか?のレベル。
多分 GExiv2 が凄いだけなんだろうけど。

GExiv2

Nautilus のサムネイルはスマホの縦画像は縦に表示される。
けれど cbz にアーカイブすると横向きになってしまうようだ。

orientation1

iPhoneからアップロードしたJPEG写真が横向きになる問題(EXIF, Orientation) – Qiita

つまりスマホで撮影した写真は実は全部横向き。
EXIF 情報によりアプリ側が縦表示等に回転しているというだけの話。
アプリ側がそういう処理をしていないとそのまま表示される。

回転情報を得るにはこの EXIF 情報を得る必要がある。
GNOME は Exiv2 というライブラリがあり GExiv2 が GIR で提供されている。

Exiv2 – Image metadata library and tools

ghiro/gexiv.py at master ? Ghirensics/ghiro ? GitHub

GExiv2.Metadata – Classes – GExiv2 0.10

コレを参考に cbz アーカイブ内の画像からも取得してみる。

#!/usr/bin/env python3

import os, gi
gi.require_version('GExiv2', '0.10')
from gi.repository import GExiv2, Gio

FILENAME1 = os.path.expanduser('~/pic/exiv/IMG_0182.JPG')
FILENAME2 = os.path.expanduser('~/pic/exiv/cbz.cbz')

# filename
metadata = GExiv2.Metadata()
metadata.open_path(FILENAME1)
n = metadata.get_orientation()
print(n)

# cbz
cmd_array = ['unzip', '-pj', FILENAME2, 'IMG_0182.JPG']
sp = Gio.Subprocess.new(cmd_array, Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_MERGE)
stream = sp.get_stdout_pipe() # GUnixInputStream
fd = stream.get_fd()
with open(fd, 'rb') as f:
    metadata2 = GExiv2.Metadata()
    metadata2.open_buf(f.read())
    n = metadata2.get_orientation()
    print(n)

orientation2

GIR でアーカイブ内のバイナリ指定は迷ったけど上記でイケた。
Python の open ってファイルディスクリプタ指定でもイケるんだ。
GSubprocess を使ったけど subprocess モジュールでも同じだと思う。
で、とにかく値を得たら

gdk_pixbuf_rotate_simple
gdk_pixbuf_flip

で回転やフリップすればスマホ写真には対応できる。
ただ、この処理が cbz ビューアに必要かどうかは微妙。