きみはねこみたいなにゃんにゃんなまほう

ねこもスクリプトをかくなり

α7R4AのSDカードを選ぶ

機種は先日購入したα7R4A。初めてのフルサイズ。耳慣れなかった技術規格の用語だらけで周辺グッズを買い揃えるのもワクワクする。7年前のCanon Kiss 7からの機材更新なので隔世の感とも言う。

lightbulbcat.hatenablog.com

現在使っているのは5年前くらいに数千円で購入した SanDisk Ultra SDXC 64GB Read 48MB/s。今だったらもっとコストパフォーマンスの高いものが買えるはず。高解像度機を購入したので書き込み速度にも多少こだわりたい。撮影の旅に長々と書き込みインジケータがちらつくのは嫌だからだ。

α7R4Aのスペック

https://www.sony.jp/ichigan/products/ILCE-7RM4A/spec.html を見る。

項目 スペック
記録画素数 35mmフルサイズ時 Lサイズ: 9504 x 6336 (60M)
記録媒体 SDメモリーカードSDHCメモリーカード(UHS-I /II対応)、SDXCメモリーカード(UHS-I /II対応)、microSD メモリーカード、microSDHC メモリーカード、microSDXC メモリーカード
スロット SLOT1:SD(UHS-I、UHS-II対応)カード用スロット、SLOT2:SD(UHS-I、UHS-II対応)カード用スロット
メモリーカード間記録モード 同時記録(静止画のみ)、同時記録(動画のみ)、同時記録(静止画動画)、振り分け(JPEG/RAW)、振り分け(静止画/動画)、記録メディア自動切替(入/切)、コピー

振り分け(JPEG/RAW)は興味があるけど、データの管理が大変になりそうなのでとりあえず保留。

用語の整理

SDカードについては規格策定団体である SD Association にとてもわかりやすくまとまっている。

SDカードの要求スペックを決める

容量

実際に撮影したデータを見たところRAWが60MB、JPGが30-40MB程度だった。なので両方保存すると100MBになる。1度の充電で500枚撮影できるらしいので、データをこまめに吸い出したとして50GB。吸い出しをサボるか、長旅だとしても128GBあれば十分そうだ。流石にスナップ的な撮影であれば解像度は落とすと思うので長旅でも64GBあればなんとかなるかもしれない。

ダブルスロットの使用感も見られるように、64GBを2枚にする体制でもいいかもしれない。その場合はとりあえずの選択肢で 64GBを1枚 買っておけばいい。

ただ動画を収めるとなると64GBでは心もとないのでやっぱり128GBは欲しい。

書き込み速度

1枚当たりの保存が0.5秒くらいで終わると「まぁ早いな」という感じで使えそう。30MBを0.5秒であれば 書き込み速度は60MB/s あればよいので V60 になる。RAW+JPGをするとなると100MBで、これが1秒で保存できれば「まぁ早いな」となりそう。100MB/s あればなお良い、くらいか。そうなると V90 であれば書き込みの最低保証速度が90MB/sになり安心となる。

SDカードを選ぶ

以上を踏まえて64-128GB, UHS-II, U3, V60で探す。

SonyのSDカード

まずはSony公式のhttps://www.sony.jp/rec-media/lineup/sd.htmlからSDカードを見てみよう。

  • 64GB 7,700円
    • U3 V30, 最大読み出し速度270MB/s, 64GBのみ最大書き込み速度70MB/s
  • 128GB 13,200円
    • U3 V60, 最大読み出し速度270MB/s, 最大書き込み速度120MB/s

  • 64GB 9,900円 / 128GB 17,600円
    • U3 V60, Read 277MB/s, Write 150MB/s

そこそこしっかり高いが、Amazonと比較しても飛び抜けて高いほどではない。 容量と速度と価格を見れば、この中ではタフ64GBが自分にとって良い選択肢に見える。 公式のものという以外にも、名前の通り耐環境性能が高いというプラスアルファの魅力もある。

SDカードメーカー

では次にAmazonで探してみよう。 まず有名どころをSanDiskしか知らないのでメーカーを調べてみる。 中国ブランドが出てこないのは意外だった。中国か東南亜製ではあるんだろうけど。

各メーカーの製品

それぞれ64-128GBのV60-90で候補を挙げてみる。

  • 64GB, 14,218
  • スピードクラス:V90
  • 最大300MB/秒の読み取り、最大260MB/秒の書込スピード

  • 128GB 5,945円
  • スピードクラス:U3, V60
  • 読み取り転送速度を最大250MB / s, 書き込みは不明(V60なので最低でも60MB/s)

  • 128GB 7,127円 / 256GB 13,067円
  • スピードクラス:V60, U3, C10
  • 最大転送速度:読込250MB/s 書込130MB/s

  • 64GB 7,180円 / 128GB 12,970円
  • スピードクラス:V90
  • 転送速度 : 読み込み最大300MB/s 書き込む最大260MB/s

書き込み速度はメーカーのカードリーダを利用したときのみの値になる場合もあるらしいので、説明欄は注意深く読まなければならない。 …と思ったけど違うな。多分手元の環境が高速なインタフェースに対応していないのに気づいていない人が怒っているケースが大半な気がする。

結論: ProGrade SDXC 256GB V60

それで結局これにした。

  • 256GB 13,067円
  • スピードクラス:V60, U3, C10
  • 最大転送速度:読込250MB/s 書込130MB/s

レビューにα7R4で使っている人もいたし。 あと室内でパシャパシャ撮影していたらすぐに数十GB使っており、128GBでも不安だったので256GBにした。 速度に不満が出れば128GBの高速なものを買って、こちらはスロット2で大容量の予備として使用する。 2スロットあるのは便利なのである。

iMac 2015 lateのスペック

メインPCであるiMacに250MB/sの速度を活かせるインタフェースが生えているかSystem Informationで確認してみる。

  • Built in SD Card Reader: 2.5GT/s=250-312MB/s
    • UHS-Iのみ対応らしい
  • USB 3.0 Bus - USB 3.1 Hub (up to 5Gbps=625MB/s) - USB 3 Port

なのでリーダーを買ってUSB経由で読めば、Read 250GB/s程度の速度は引き出せるんじゃないかと予想。

使用感

特に問題なく使えている。保存速度は速くなったが通常の撮影では数値ほどの差は感じなかった。

  • 1枚当たりの保存時間
    • RAW: 1.8秒
    • JPEG(エクストラファイン): 2.4秒
  • ピクセルシフトマルチ撮影(16枚)
    • 撮影時間: 8.8秒
    • 16枚撮影終了後に保存待ちが6枚あり、その保存に10.5秒

ピクセルシフトマルチ撮影はとても速くなった。上の数値は相当適当なものなので、そのうち動画でも撮りながら検証したい。

ReadについてはiMacのBuilt in SD Card Readerから読み込んでみたが50MB/s程度しか出なかったので、追加でリーダーを購入した。

これでRead 200GB/sは出た。とりあえずこのあたりで概ね満足している。

α7R4(ILCE-7RM4A)を買った

この辺の話。結局α7R4(ILCE-7RM4A)をソニーストアの実店舗で購入した。 長い間買おうか迷っていたので、今はとても晴れやかな気分。開放感がすごい。 そしてこんなもの買ってしまっていいのだろうかという気持ちがすごい。 決済してから躁鬱みたいな状態が続いている。

購入したもの

価格

合計520,946円(税込)になった。

型番 価格(税込) 備考
ILCE-7RM4A 439,000円 ワイド保証5年(44,000円)含む
SEL24F28G 80,000円 ワイド保証5年(8,000円)含む
VF-49MPAM 5,010円 -

の対象なので50,000円がキャッシュバックされる。キャッシュバックって単純な値引きとの違いは何かあるんだろうか。名目上の売上額を上げることができるとか…などと気になり調べてみると、小売価格を維持しつつ実質の値引きをする意味があるらしい(参考)。

ソニーストアで購入した感想

店員さんはとても親身というか、カメラやレンズに対するこだわりを持って応対してくれた。初めに購入を検討していたレンズは SEL2860 だったのだが「α7R4の解像度をこのレンズは活かしきれない、購入する前に他のレンズをお勧めさせて欲しい、その上で納得してこのレンズにするなら全く問題ないです」という対応をしてくれた。

それで結局上述の単焦点レンズを購入した。最終的には、他のレンズを買った時に腐りにくそうなのが決め手になった。「このレンズを買ったら後でどんなレンズが欲しくなるか、その時このレンズは使わなくなったりするか」みたいな質問を試着したレンズが変わるごとに何度もしたと思う。

迷ったレンズと何を迷ったかを、備忘録がてらにまとめてみよう。

  • SEL2860
    • 7RCのキットレンズ、軽さに惚れて当初はこれを購入するつもりだった
  • SEL2470Z
    • キャッシュバック対象のズームレンズその1、結構重い
    • 高解像度なのを考えると105mmまでの望遠が欲しくなるかは人によりそうとのこと
  • SEL24105G
    • キャッシュバック対象のズームレンズその2、結構軽い
    • 最終的にこれか単焦点かで悩んだ
    • ズームレンズはなんだかんだで、楽だし楽しい
    • ただずっと使い続けたいレンズになるかは微妙なところだった
    • α7R4につけた時の見た目がシンプルすぎたからかもしれない
  • SEL24F28G
    • 単焦点なので高解像度を活かせそうだし、他のレンズを買った時に腐りにくそう
    • 星を取りたいので明るいのもポイント
    • ただズームしたいなという気持ちもあり…

結果を見れば当初の予定よりも高いレンズを買わされたとも取れるが、色々レンズを取っ替え引っ替えしたり4Kディスプレイに繋いで「ほら、この白の階調がこのレンズだと出ているんですよ」とか色々伝えようとしてくれていたので、素直に良い選択肢を与えてくれているんだと信じることにした。私に後悔をさせまいとする気持ちに裏は無いと感じられたからだ。

と書きつつ、手元にはカメラが届くのは数日後だし、本当にあの選択でよかったのだろうかという不安な気持ちはどうにも否定できない。自分は乗せられてしまったのか、正しい選択だったのか、どこで何を買おうと店員と客との間にはついて回る問題なのだろうけれど、まぁなかなか、この待っている時間は、心の根っこがぐらぐらするのを感じるのである。

Node.jsのREPL出力を実行可能なJavaScriptとしてコピペする

Node.jsの

% node
>
>
> f = () => {
...
... return 'hello'
... }
[Function: f]
>
>

のような出力を、スクリプト行の > を外し、評価行をコメントアウトして、JavaScriptとして実行可能な形式にする。 記事をまとめるときに使いたくて作った。

cat <<'EOF' | sed -Ee 's%^%// %g' -e 's%// (>|\.\.\.)( |$)%%g' | grep -E .

これを以下のように使う。

bash-3.2$ cat <<'EOF' | sed -Ee 's%^%// %g' -e 's%// (>|\.\.\.)( |$)%%g' | grep -E .
> >
> >
> >
> > f = () => {
> ...
> ... return 'hello'
> ... }
> [Function: f]
> >
> >
> EOF
f = () => {
return 'hello'
}
// [Function: f]
bash-3.2$

無事JavaScriptとして変換された。

f = () => {
return 'hello'
}
// [Function: f]

コンテクストメニューから実行する

コピーから変換までを自動で行うためにmacOSのShortcutsアプリに登録する。 Copy to Clipboard でやろうとしたらクリップボードにはコピーできるけれどそこからペーストできない現象に当たったので pbcopy を使った。

sed -Ee 's%^%// %g' -e 's%// (>|\.\.\.)( |$)%%g' | grep -E . | pbcopy

これを以下のように登録する。

f:id:lightbulbcat:20220102185952p:plain

これで変換しながらクリップボードに登録できるようになった。

バリエーション

他言語の置換ワンライナーも作っていく。

pbpaste | sed ...

のようにすると動作確認が楽にできる。

JavaScript

sed -Ee 's%^%// %g' -e 's%// (>|\.\.\.)( |$)%%g' | grep -E .

Python

sed -Ee 's/^/# /g' -e 's/^# (.*@.*%|>)( |$)//g' | grep -E .

Zsh

変数 PS1 の設定によってカスタムする必要がある。

sed -Ee 's/^/# /g' -e 's/^# (.*@.*%|>|pipe heredoc>)( |$)//g' | grep -E .

これが

asataka@tailmoon ~ % cd tmp
asataka@tailmoon tmp % cat <<EOF | cat -
pipe heredoc> I im a
pipe heredoc> cat
pipe heredoc> EOF
I im a
cat
asataka@tailmoon tmp % ls \
> -al
total 8
drwxr-xr-x   3 asataka  staff    96 Jan  3 19:57 .
drwxr-x---+ 44 asataka  staff  1408 Jan 10 15:21 ..
-rw-r--r--   1 asataka  staff   318 Jan  3 20:56 prototype-chain.js
asataka@tailmoon tmp %

こうなる。

cd tmp
cat <<EOF | cat -
I im a
cat
EOF
# I im a
# cat
ls \
-al
# total 8
# drwxr-xr-x   3 asataka  staff    96 Jan  3 19:57 .
# drwxr-x---+ 44 asataka  staff  1408 Jan 10 15:21 ..
# -rw-r--r--   1 asataka  staff   318 Jan  3 20:56 prototype-chain.js

他にやりたいこと

まとめたい

変換Shortcutの数が増えてくるとメニューがいっぱいになってくるので、一つにまとめられたら良いが何かいい方法はないだろうか。

f:id:lightbulbcat:20220111010145p:plain

ExtendScriptでプログレスバーを表示する

lightbulbcat.hatenablog.com

前回の続きです。

f:id:lightbulbcat:20211226224228p:plain

の各アートボードのテキストを取得する処理中に、どのアートボードを現在処理中であるかを示す

f:id:lightbulbcat:20211227003349p:plain

のようなプログレスバーを表示します。 今回は ProgressWindow というクラスを定義して利用しました。 この辺はお好みですね。

var CONFIG = {
    outFile: '~/Desktop/descs.txt',
}

function map(items, fn) {
    var res = []
    for (var i = 0; i < items.length; i++) res[i] = fn(items[i], i)
    return res
}

function filter(items, fn) {
    var res = []
    for (var i = 0; i < items.length; i++) {
        if (fn(items[i], i)) res.push(items[i])
    }
    return res
}

function log(input) {
    var now = new Date();
    var output = now.toTimeString() + ": " + input;
    $.writeln(output)
}

function ProgressWindow(opt) {
    opt = opt || {}
    var w = this.w = new Window('palette', opt.title || 'Progress')
    var t = this.t = w.add('statictext')
    t.preferredSize = [450, -1]

    var p = this.p = w.add('progressbar', undefined, 0, 100)
    p.value = 0
    p.preferredSize = [450, -1]

    w.show()
}

ProgressWindow.prototype.set = function (progressRatio, message) {
    this.p.value = progressRatio * 100
    this.t.text = message
    this.w.update()
}

ProgressWindow.prototype.close = function () {
    this.w.close()    
}

try {
    log('Start script')

    var allTexts = []
    var pw = new ProgressWindow({ title: 'copy-textbox' })
    var doc = app.activeDocument

    for (var i = 0; i < doc.artboards.length; i++) {
        doc.selection = null
        doc.artboards.setActiveArtboardIndex(i)
        doc.selectObjectsOnActiveArtboard()

        var textFrames = filter(doc.selection, function(item) {
            return item instanceof TextFrame
        })

        var texts = map(textFrames, function(tf) {
            log('Artboard' + i + ': ' + tf.contents)
            return tf.contents
        })

        pw.set(i / doc.artboards.length, 'Artboard ' + i + ': ' + texts.join(',').slice(0, 100))
        allTexts = allTexts.concat(texts)
    }
    log('Detect ' + allTexts.length + ' textFrames total')

    allTexts = map(allTexts, function(text) {
        return text.replace(/\r\n|\r|\n/g, '\\n')
    })

    var f = File(CONFIG.outFile)
    f.encoding = 'UTF-8'
    f.lineFeed = 'Unix'
    f.open('w')
    f.write(allTexts.join('\n'))
    f.close()
    pw.close()
} catch (e) {
    alert('Error: ' + e)
    $.writeln(e)
}

無事動きました。

ループをキャンセルするボタンを設置できない

GUIを表示させられ、欲が出たので処理のキャンセル機能もつけようとキャンセルボタンの設置を試みたのですが、できませんでした。 正確にはボタンの設置はできるけれど、そのボタンにループを停止させる機能を持たせるのが無理そう、ということがわかりました。

色々探して、これかなと思った方法は全てCEPの機能を使うもので、例えば

Solved: Need an ExtendScript timer - Adobe Support Community - 6117453

で説明されている方法も同様にCEPを使うものです。他にも探したのですがExtendScriptのみで行う方法は見つかりませんでした。

JavaScript に存在するような setTimeout のように非同期実行処理のキューにタスクを積むような処理ができれば、 ループを、コールバックを積む処理の連続で置き換えられるのでユーザの処理を待てるかと思いますが、 調べたところそのような処理はExtendScriptにはなさそうなので、諦めてCEPのスクリプトで行うしかなさそうです。

Adobe ExtendScript Debuggerを使ってみる

アートボードを100枚以上含むファイルがあり、各アートボードのテキストボックスの内容をまとめてテキストファイルとして取得したくなったので、久しぶりにAdobe ExtendScriptスクリプトを書きました。やりたいことの要件は以下の通りです。

  • 各アートボードはそれぞれ1つのテキストボックスを持つ
    • もしくは対象の1つ以外のテキストボックスはレイヤーのロックなどで選択できないようにしておく
  • 各テキストボックスの内容が1行ごとに書かれたファイルを出力する

本物のデータは載せられないので、簡略化した例で示すと

f:id:lightbulbcat:20211226224228p:plain

のようなIllustratorのファイルに対して

テキストA
テキストB
テキストC
...

というデータが書き込まれたファイルを出力してくれるスクリプトが欲しい、ということになります。

開発環境はVS Codeを使い、途中でデバッグが辛くなったのでAdobe謹製のExtendScript Debuggerという拡張機能を入れました。 最終的に完成したスクリプトは以下のものです。

var CONFIG = {
    outFile: '~/Desktop/descs.txt',
}

function map(items, fn) {
    var res = []
    for (var i = 0; i < items.length; i++) res[i] = fn(items[i], i)
    return res
}

function filter(items, fn) {
    var res = []
    for (var i = 0; i < items.length; i++) {
        if (fn(items[i], i)) res.push(items[i])
    }
    return res
}

function log(input) {
    var now = new Date();
    var output = now.toTimeString() + ": " + input;
    $.writeln(output)
}

try {
    var texts = []
    log('Start script')
    for (var i = 0; i < app.activeDocument.artboards.length; i++) {
        app.activeDocument.selection = null;
        app.activeDocument.artboards.setActiveArtboardIndex(i);
        app.activeDocument.selectObjectsOnActiveArtboard();

        var textFrames = filter(app.activeDocument.selection, function(item) {
            return item instanceof TextFrame
        })

        texts.concat(map(textFrames, function(tf) {
            log('Artboard' + i + ': ' + tf.contents)
            return tf.contents
        }))
    }
    log('Detect ' + texts.length + ' textFrames total')

    texts = map(texts, function(text) {
        return text.replace(/\r\n|\r|\n/g, '\\n')
    })

    var output = texts.join('\n')
    var f = new File(CONFIG.outFile)
    f.encoding = 'UTF-8'
    f.lineFeed = 'Unix'
    f.open('w')
    f.write(output)
    f.close()

} catch (e) {
    alert('Error: ' + e)
}

始めはこのスクリプトデバッグを、ログメッセージをファイル出力して行なっていたのですが、 ファイルに何も書き込まれないという事象(原因は後述)に陥ったため、ExtendScript Debuggerの導入に踏み切りました。 ファイル出力を行う処理のデバッグ用のログをファイル出力で行なったため、結果として混乱が増大しました。 デバッグ専用の情報出力チャネルの存在は大切ですね。

ExtendScript Debuggerの使い方

使い方と言っても基本的には公式のドキュメントに従えば問題なく使えます。

ExtendScript Debugger - Visual Studio Marketplace

VS Codeデバッグ機能を使ったことがないと分かりづらいのは、デバッグ対象のプログラム (今回の場合はスクリプトを動作させるプログラムを指すのでAdobe Illustrator) を指定してやらないと Can’t start a session without an active target and engine. Select an active target and engine before trying again. と表示されて失敗するところです。意図するところは、どこでデバッグ対象のスクリプトを動かすか指定する必要があるということです。

f:id:lightbulbcat:20211226020254p:plain

これを解決するにはデバッグを実行する前に、下のステータスバーで対象プログラムを指定します。

f:id:lightbulbcat:20211226020229p:plain

Select the target application と表示されているところを選択すると選択モードになるので Adobe Illustrator を選択します。

f:id:lightbulbcat:20211226020234p:plain

デバッグの実行に成功すると $.write() で指定したメッセージがコンソールに流れます。

f:id:lightbulbcat:20211226020249p:plain

その他

毎回デバッグ対象のプログラムを指定したくない

${command:AskForScriptName} を固定文字列にすれば毎回ダイアログで入力せずにすみます。

{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "extendscript-debug",
            "request": "launch",
            "name": "Ask for script name",
            // "program": "${workspaceFolder}/${command:AskForScriptName}",
            "program": "${workspaceFolder}/copy-textbox.jsx",
            "stopOnEntry": false
        }
    ]
}

それにしてもこの program っていう表現、デバッグ対象のスクリプトなのか、ランタイムを指すのか、何を指しているか曖昧で分かりづらいですね。

ファイル出力ではまったところ

はまったところは2点。

  • join('\n') で結合した文字列の改行文字が fileObj.write() 時に \r になる
    • f.lineFeed = 'Unix' で期待通りになった
  • 日本語が出力されない
    • f.encoding = 'UTF-8' で改善された。

ちなみに File クラスのリファレンスは Illustrator_Scripting_Guide_cc.pdf ではなく JavaScriptToolsGuide_CS5.pdf にあります。 共にAdobeサイト内の検索に引っかからないのもあり非常に探しづらい。

AdobeはもっとExtendScriptのリファレンスを整備してほしい。なんでPDFでしか提供されていないんだよ。 というか公式リファレンスくらいちゃんとアクセスできるところに置いてほしい。AdobeExtendScriptを捨てたいんだろうか。

LinuxからAirPort Time Capsuleをマウントする

AirPort Time Capsule からデータをLinuxにコピーしたくて調べた。初めは多少速度は落ちるだろうけれどmacOS経由で移動すればいいやと考えていたが、何度やっても途中でコピーが止まるしキャンセルもできない状態になるので直接移行先であるLinuxからTime Capsuleの共有ディレクトリをマウントする方法を探した。

f:id:lightbulbcat:20211219145854p:plain
途中でコピーが止まる

手元のAirPort Time Capsule10.0.1.1 で動作しており Data という名前で共有ディレクトリを提供している。

sudo mkdir -p /mnt/tc
sudo mount.cifs //10.0.1.1/Data /mnt/tc -o user="USERNAME",password="PASSWORD",sec=ntlm,vers=1.0
ls /mnt/tc # Data の中身が表示される

で行けた。dmesg でエラーを表示しながら試行錯誤した。

# vers=1.0 を指定しない
sudo mount.cifs //10.0.1.1/Data /mnt/tc -o user="USERNAME",password="PASSWORD"
# No dialect specified on mount. Default has changed to a more secure dialect,
# SMB2.1 or later (e.g. SMB3), from CIFS (SMB1).
# To use the less secure SMB1 dialect to access old servers which do not support SMB3 (or SMB2.1) specify vers=1.0 on mount.

# sec=ntlm を指定しない
sudo mount.cifs //10.0.1.1/Data /mnt/tc -o user="USERNAME",password="PASSWORD",vers=1.0
# Status code returned 0xc000006d NT_STATUS_LOGON_FAILURE

参考