uokadaの見逃し三振は嫌いです

ここで述べられていることは私の個人的な意見に基づくものであり、私が所属する組織には一切の関係はありません。

自分の写真整理するためのGoスクリプトを書いた。

みなさん大掃除は済みましたか? 自分は今パソコンの中を大掃除中です。

今年たくさん写真とったおかげでMACのディスクをかなり圧迫していてちょうどそれの整理をしています。

自分のカメラは1枚の写真に対してORFとJPGファイルを2つ生成するような設定となっています。 ただ、ORFファイルがあればJPGは要らないな〜と思ったので今回まとめて整理出来るスクリプトを書きました。

ディレクトリを引数にとってそのディレクトリ以下を探索して、
JPGとORFファイルを重複して持っているものは削除するというものです。

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "regexp"
    //    "os"
    "flag"
    "log"
    "os/exec"
)

const (
    DEBUG     = false
    EXTENSION = ".JPG"
    WALK_PATH = "/Users/hogehoge/Dropbox/Pictures/写真 Library.photoslibrary/"
    TRASH_BOX = "~/.Trash"
)

const raw = `
on run argv
  tell application "Finder"
    repeat with f in argv
      move (f as POSIX file) to trash
    end repeat
  end tell
end run
`

var trash = filepath.Join(os.Getenv("HOME"), ".Trash")

func go2Trash(f string) error {
    bin, err := exec.LookPath("osascript")
    if err != nil {
        err = fmt.Errorf("not yet supported")
        return err
    }

    if _, err = os.Stat(trash); err != nil {
        err = fmt.Errorf("not yet supported")
        return err
    }

    path, err := filepath.Abs(f)
    if err != nil {
        return err
    }

    params := []string{"-e", raw, path}
    cmd := exec.Command(bin, params...)
    if err = cmd.Run(); err != nil {
        return err
    }
    return nil
}

func WalkFunc(path string, info os.FileInfo, err error) error {
    if err != nil {
        fmt.Println(err)
        return nil
    }
    if info.IsDir() {
        return nil
    }

    if ext := filepath.Ext(path); ext == EXTENSION {
        pat, err := regexp.Compile(EXTENSION + "$")
        if err != nil {
            fmt.Println(err)
            return nil
        }
        orfpath := pat.ReplaceAllString(path, ".ORF")

        if _, berr := os.Stat(orfpath); berr == nil {
            fmt.Println("ORF File Exist: ", path)
            if dryrun {
                // if dry-run mode, file isn't deleted.
                log.Println("next file will be deleted: ", path)
                return nil
            }
            // File Remove Process
            if rename {
                derr := go2Trash(path)
                if derr != nil {
                    log.Println(err)
                    return err
                }
            } else {
                derr := os.Remove(path)
                if derr != nil {
                    log.Println(err)
                    return err
                }
            }
        }
    }
    return nil
}

var path string
var rename, dryrun bool

func init() {
    flag.StringVar(&path, "path", WALK_PATH, "Pictures filepath.")
    flag.BoolVar(&dryrun, "n", false, "dry run flag")
    flag.BoolVar(&rename, "trash", true, "go to trashbox")
    flag.Parse()
    debugp(fmt.Sprintf("dryrun: %s", dryrun))
    debugp(fmt.Sprintf("trash: %s", rename))

    if path != WALK_PATH {
        tmp_p, err := filepath.Abs(path)
        if err != nil {
            log.Fatalln(err)
        }
        path = tmp_p
    }
    debugp(path)
}

func debugp(debugObj interface{}) {
    if DEBUG {
        fmt.Println(debugObj)
    }
}

func main() {
    f := WalkFunc
    err := filepath.Walk(path, f)
    if err != nil {
        fmt.Println(err)
    }
}

機能は以下のとおり。

  • ドライラン
  • ゴミ箱に入れずに直接削除モード

あとはこのスクリプトをビルドしてドライランで削除されるファイルを確認。 問題がなければドライランオプションを外して削除という流れです。

$ go build -o walker
$ ./walker -path=~/Dropbox/Pictures/写真\ Library.photoslibrary -n
<中略>
$ ./walker -path=~/Dropbox/Pictures/写真\ Library.photoslibrary
〜〜〜〜〜〜
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200882.JPG
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200883.JPG
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200886.JPG
ORF File Exist:  /path/to/2015/09/20/20150920-181504/P9200889.JPG
〜〜〜〜〜〜

こんなスクリプトでもうまく動きましたが実行してみてると1つ課題が見つかりました。 コマンド実行でゴミ箱に送ってる部分が遅いのです。
ファイル1000個ぐらいで15分程度なので耐えられない遅さではないとは思いますが、 go で並列化出来るといいんだろうなと感じました。


余談ですが、ゴミ箱に入れる方法が分からなかったのでその部分は gomi を参考にさせてもらいました。
(AppleScript使って実現してるんですね。)

Link