Skip to content

Instantly share code, notes, and snippets.

@devstopfix
Created April 22, 2020 15:39
Show Gist options
  • Save devstopfix/5d3904097190f527410e5539a687fa65 to your computer and use it in GitHub Desktop.
Save devstopfix/5d3904097190f527410e5539a687fa65 to your computer and use it in GitHub Desktop.
Partition image files into folders based on timestamp.
#!/usr/bin/env plk
(ns chunk.core
(:require [goog.date.UtcDateTime]
[planck.core :refer [*in* line-seq]]
[planck.io :as io]))
;
; Partition image files into folders based on timestamp.
;
; This script takes a list of images produced by a webcamera with motion detection
; trigger and copies them into folders of images that were taken in sucession. These
; folders can then be converted into animations or movies.
;
; The only parameter to this script is the number of seconds between images for them
; to be considered part of the same movie. The image filename must start with timestamp.
;
; The list of files to be processed should be piped to the script. Inspect the output of:
;
; ls /tmp/webcam/*jpg | ./chunk_files.cljs 60
;
; Once you are satisfied, you can execute in BASH (4.0 or later on OS X) with:
;
; source <(ls /tmp/webcam/*jpg | ./chunk_files.cljs 60)
;
; If single images are detected, they are placed in a folder called photos.
;
; Download this script, set it to executable, and run with https://planck-repl.org/
;
(def orphans-folder "photos/")
(def sort-by-path (partial sort-by :path))
; https://stackoverflow.com/a/23221442/3366
(defn partition-between [pred? coll]
"Partition a seq into a list of lists, partitioning
when the pred returns true for adjacent items."
(let [switch (reductions not= true (map pred? coll (rest coll)))]
(map (partial map first) (partition-by second (map list coll switch)))))
(defn parse-timestamp [f]
"Extract timestamp from filename in format yyyymmddhhnnss...jpg"
(let [[y mm d h mn s] (->> f
(re-matches #"(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2}).*")
(rest)
(map js/parseInt))]
(goog.date.UtcDateTime. y mm d h mn s)))
(defn get-time [f] (-> f io/file-name (parse-timestamp) .getTime))
(defn nigh-pred [interval]
"Return a predicate that returns true if two dates
are outside interval seconds of each other"
(let [i-ms (* interval 1000)]
(fn [d1 d2]
(let [t1 (get-time d1)
t2 (get-time d2)]
(> (- t2 t1) i-ms)))))
(defn chunk-files [interval files]
(let [nigh? (nigh-pred interval)]
(->> files
(map io/file)
(filter io/regular-file?)
(sort-by-path)
(partition-between nigh?))))
(defn sstr [& xs] (clojure.string/join " " (cons "" xs)))
(def mkdir (partial sstr "mkdir" "-p"))
(defn q [s] (str "\"" s "\""))
(defn path [f] "Path of file without its name"
(->> f io/path-elements butlast (apply io/file) (io/file "")))
(defn replace-ext [f]
(-> (:path f)
(clojure.string/replace #"\.\w+$" ".frames")
(clojure.string/replace #" " "_")))
(defn output [lfs]
(flatten
(for [fs lfs]
(if (second fs)
(let [target (replace-ext (first fs))]
(cons
(mkdir target)
(for [f fs] (sstr "cp" (q f) target))))
(let [f (first fs)
target (io/file (path f) orphans-folder)]
[(mkdir target) (sstr "cp" (q f) target)])))))
(defn -main [arg]
(let [interval (js/parseInt arg)]
(->>
(chunk-files interval (line-seq *in*))
(output)
(distinct)
(map println)
(doall))))
(set! *main-cli-fn* -main)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment