Productive Photo Culling
An AutoHotkey workflow for faster photo culling in XnView MP and FastStone Image Viewer.
Culling comprehensive photo libraries is a tedious and time-consuming activity. So each speeding up, is highly welcomed.
For me these functionalities are very important:
- Rating of photos should be easily possible with a simple key click without holding special keys (
Alt,Ctrl) - After rating a photo, automatic navigation to the next photo should happen
Unfortunately most of the standard applications don’t support it, but with AutoHotKey this functionality can be added. Personally I’m addicted to XnView MP and FastStone Image Viewer
First attempt and familiarization
Simple mapping of NumPad keys to rating shortcuts
#Requires AutoHotkey v2.0
#SingleInstance Force
; Nur aktiv, wenn XnView MP im Vordergrund ist
#HotIf WinActive("XnView MP")
Numpad1::Send("^5") ; Taste Num 1 sendet Ctrl+5
Numpad0::Send("^0") ; Taste Num 1 sendet Ctrl+0
#HotIf
; Nur aktiv, wenn FastStone Image Viewer ausgeführt wird
#HotIf WinExist("FastStone Image Viewer 8.3")
Numpad1::Send("!5") ; Taste Num 1 sendet Alt+5
Numpad2::Send("!4")
Numpad3::Send("!3")
Numpad4::Send("!2")
Numpad5::Send("!1")
Numpad0::Send("!0")
Numpad7::Send("^!l") ; Taste Num 7 sendet Ctrl+Alt+L = rotate CCW
Numpad9::Send("^!r") ; Taste Num 9 sendet Ctrl+Alt+R = rotate CW
#HotIf
This script is very basic and offered for
XnView MP
- only 5 star rating
- remove rating
FastStone Image Viewer
all ratings (1..5)
remove rating
rotation (CW & CCW)
📝 NOTE: Because the lower digits are easier to type,
1is mapped to 5 stars. In case of of FastStone Image Viewer I only used 5 stars to classify “keep photo” and no stars for candidated to delete.
Improvement I
In the next evolution mainly an automated navigation to the next photo after rating was implemented. Therefore in the first block a bunch of variables was set (rate0 := "^0" till prevKey := "{Left}") for later re-use.
For easier rating and navigation now NumPad 1 and 3 are used for backward/forward navigation and 2 to set the lowest rating (1 star); again, the idea is to have the most common actions directly under the fingers, like known from shooters. Due to bracketing (three shot exposure sequence), my shootings contain three photos per subject. Hence, I have to delete 2 out of these three, which makes 1 star rating (=delete later) the main rating. So it must be NumPad 2. So my 3 navigation fingers lay on 3 keys in a line and can to very fast their navigation and selection work. To decide on the survivor of the three my middle finger climbs up to 5 which sets a 3 star rating (avery good photo). And if the photo is a top one, the middle finger climbs another line up and clicks 8 for a 5 start rating. Accordingly, I don’t use 2 or 4 star ratings.
#Requires AutoHotkey v2.0
#SingleInstance Force
rate0 := "^0"
rate1 := "^1"
rate2 := "^2"
rate3 := "^3"
rate4 := "^4"
rate5 := "^5"
nextKey := "{Right}"
prevKey := "{Left}"
; or "{PgDn}" or "{Space}" depending on your setup
; Nur aktiv, wenn XnView MP im Vordergrund ist
#HotIf WinActive("XnView MP")
Numpad1:: {
Send(prevKey)
}
Numpad2:: {
Send(rate1)
Sleep(30)
Send(nextKey)
}
Numpad3:: {
Send(nextKey)
}
Numpad4:: {
Send(rate2)
Sleep(30)
Send(nextKey)
}
Numpad5:: {
Send(rate3)
Sleep(30)
Send(nextKey)
}
Numpad6:: {
Send(rate4)
Sleep(30)
Send(nextKey)
}
Numpad7:: {
Send(prevKey)
}
Numpad8:: {
Send(rate5)
Sleep(30)
Send(nextKey)
}
Numpad9:: {
Send(nextKey)
}
Numpad0:: {
Send(rate0)
Sleep(30)
Send(nextKey)
}
#HotIf
; Nur aktiv, wenn FastStone Image Viewer ausgeführt wird
#HotIf WinExist("FastStone Image Viewer 8.3")
Numpad1::Send("!5") ; Taste Num 1 sendet Alt+5
Numpad2::Send("!4")
Numpad3::Send("!3")
Numpad4::Send("!2")
Numpad5::Send("!1")
Numpad0::Send("!0")
Numpad7::Send("^!l") ; Taste Num 7 sendet Ctrl+Alt+L = rotate CCW
Numpad9::Send("^!r") ; Taste Num 9 sendet Ctrl+Alt+R = rotate CW
#HotIf
Improvement II (Keyboard w/o NumPad)
Typically I use an external Bluetooth keyboard with full layout. But sometimes I have to work with my laptop only, which does not provide a NumPad block. So I had to map the NumPad shortcuts from above somehow to the small keyboard.
I decided use these keys to map to the NumPad keys.
7 8 9
U I O
J K L
M
To not interfer with existing shortcuts, additionally the CapsLock must be activated to accept these key clicks.
Another small improvement was the incoporation of accoustical feedback. My three main ratings now have very individual sounds. That helps to know that the right rating has set, becuase visually this cannot be verifyed anymore dur to automatic navigation to the next photo.
#Requires AutoHotkey v2.0
#SingleInstance Force
; ============================================================
; Common settings
; ============================================================
rate0 := "^0"
rate1 := "^1"
rate2 := "^2"
rate3 := "^3"
rate4 := "^4"
rate5 := "^5"
nextKey := "{Right}"
prevKey := "{Left}"
SoundDelete() {
SoundBeep 300, 100
}
SoundAverage() {
SoundBeep 800, 80
}
SoundFavorite() {
SoundBeep 1400, 70
Sleep 25
SoundBeep 1900, 100
}
; ============================================================
; XnView MP
;
; Full keyboard:
; Use the real NumPad.
;
; Laptop without NumPad:
; Turn CapsLock ON to activate this virtual NumPad:
;
; 7 8 9
; U I O = 4 5 6
; J K L = 1 2 3
; M = 0
;
; Turn CapsLock OFF to return the keys to normal.
; ============================================================
#HotIf WinActive("ahk_exe xnviewmp.exe")
; ------------------------------------------------------------
; Physical NumPad
; ------------------------------------------------------------
Numpad1::XnView_Numpad1()
Numpad2::XnView_Numpad2()
Numpad3::XnView_Numpad3()
Numpad4::XnView_Numpad4()
Numpad5::XnView_Numpad5()
Numpad6::XnView_Numpad6()
Numpad7::XnView_Numpad7()
Numpad8::XnView_Numpad8()
Numpad9::XnView_Numpad9()
Numpad0::XnView_Numpad0()
; ------------------------------------------------------------
; Laptop virtual NumPad
; Active only while CapsLock is ON
; ------------------------------------------------------------
#HotIf WinActive("ahk_exe xnviewmp.exe")
&& GetKeyState("CapsLock", "T")
j::XnView_Numpad1()
k::XnView_Numpad2()
l::XnView_Numpad3()
u::XnView_Numpad4()
i::XnView_Numpad5()
o::XnView_Numpad6()
7::XnView_Numpad7()
8::XnView_Numpad8()
9::XnView_Numpad9()
m::XnView_Numpad0()
#HotIf
; ============================================================
; XnView MP actions
; ============================================================
XnView_Numpad1() {
global prevKey
Send(prevKey)
}
XnView_Numpad2() {
global rate1, nextKey
Send(rate1)
SoundDelete()
Sleep(30)
Send(nextKey)
}
XnView_Numpad3() {
global nextKey
Send(nextKey)
}
XnView_Numpad4() {
global rate2, nextKey
Send(rate2)
Sleep(30)
Send(nextKey)
}
XnView_Numpad5() {
global rate3, nextKey
Send(rate3)
SoundAverage()
Sleep(30)
Send(nextKey)
}
XnView_Numpad6() {
global rate4, nextKey
Send(rate4)
Sleep(30)
Send(nextKey)
}
XnView_Numpad7() {
global prevKey
Send(prevKey)
}
XnView_Numpad8() {
global rate5, nextKey
Send(rate5)
SoundFavorite()
Sleep(30)
Send(nextKey)
}
XnView_Numpad9() {
global nextKey
Send(nextKey)
}
XnView_Numpad0() {
global rate0, nextKey
Send(rate0)
Sleep(30)
Send(nextKey)
}
; ============================================================
; FastStone Image Viewer
;
; Same behavior:
; - Physical NumPad always works.
; - Laptop virtual NumPad works while CapsLock is ON.
; ============================================================
#HotIf WinActive("ahk_exe FSViewer.exe")
; ------------------------------------------------------------
; Physical NumPad
; ------------------------------------------------------------
Numpad1::FastStone_Numpad1()
Numpad2::FastStone_Numpad2()
Numpad3::FastStone_Numpad3()
Numpad4::FastStone_Numpad4()
Numpad5::FastStone_Numpad5()
Numpad7::FastStone_Numpad7()
Numpad9::FastStone_Numpad9()
Numpad0::FastStone_Numpad0()
; ------------------------------------------------------------
; Laptop virtual NumPad
; Active only while CapsLock is ON
; ------------------------------------------------------------
#HotIf WinActive("ahk_exe FSViewer.exe")
&& GetKeyState("CapsLock", "T")
j::FastStone_Numpad1()
k::FastStone_Numpad2()
l::FastStone_Numpad3()
u::FastStone_Numpad4()
i::FastStone_Numpad5()
7::FastStone_Numpad7()
9::FastStone_Numpad9()
m::FastStone_Numpad0()
#HotIf
; ============================================================
; FastStone actions
; ============================================================
FastStone_Numpad1() {
Send("!5")
}
FastStone_Numpad2() {
Send("!4")
}
FastStone_Numpad3() {
Send("!3")
}
FastStone_Numpad4() {
Send("!2")
}
FastStone_Numpad5() {
Send("!1")
}
FastStone_Numpad0() {
Send("!0")
}
FastStone_Numpad7() {
Send("^!l")
}
FastStone_Numpad9() {
Send("^!r")
}