sopi works great except when it doesn't, in which case he just vomits an error message like this:
laussy@covid:~/Dropbox/Fabrice/Pictures/2023$ ./sopi .
Hi there! Sopi working with 6364 files in /home/laussy/Dropbox/Fabrice/Pictures/2023
Started 2024-01-05T01:26:57.484
ERROR: LoadError: BoundsError: attempt to access 0-element Array{Int64,1} at index [1]
Stacktrace:
[1] getindex(::Array{Int64,1}, ::Int64) at ./array.jl:809
[2] datemy(::String) at /home/laussy/Dropbox/Fabrice/Pictures/2023/mysopi:46
[3] iterate at ./generator.jl:47 [inlined]
[4] collect_to!(::Array{Array{SubString{String},1},1}, ::Base.Generator{Array{String,1},typeof(datemy)}, ::Int64, ::Int64) at ./array.jl:732
[5] collect_to_with_first!(::Array{Array{SubString{String},1},1}, ::Array{SubString{String},1}, ::Base.Generator{Array{String,1},typeof(datemy)}, ::Int64) at ./array.jl:710
[6] collect(::Base.Generator{Array{String,1},typeof(datemy)}) at ./array.jl:691
[7] top-level scope at /home/laussy/Dropbox/Fabrice/Pictures/2023/mysopi:51
[8] include(::Function, ::Module, ::String) at ./Base.jl:380
[9] include(::Module, ::String) at ./Base.jl:368
[10] exec_options(::Base.JLOptions) at ./client.jl:296
[11] _start() at ./client.jl:506
in expression starting at /home/laussy/Dropbox/Fabrice/Pictures/2023/mysopi:51
Something is causing an exception, returns an unexpected empty list... I don't know what this is, and of course I forgot everything about the code. So I have to debug it as if it's been written by someone else!
The code works on extracting information from exiftool. Some file is not returning a valid output.
The first thing to do is reduce the bug to its offending material, supposedly a particular file. I am dealing with thousands of files which takes several minutes, so the first thing is by trial an error, find a subset that breaks the code in a few seconds. I do this by running on subsets until one breaks the code. I arrive to this one:
Even without digging into the code, it is clear that one is different from the others:
Sure enough 2023-06-18 12.12.04.jpg is not a picture but an image downloaded by Elena which contaminated the collection, although it gets there with a timestamp:
The problem is that such images do not have the tag "Date/Time Original"
laussy@covid:~/Dropbox/Fabrice/Pictures/2023/subset/subset/subset/subset4/subset$ exiftool working.jpg | grep "Date/Time Original" Date/Time Original : 2023:06:17 15:40:57 Date/Time Original : 2023:06:17 15:40:57.200+01:00 laussy@covid:~/Dropbox/Fabrice/Pictures/2023/subset/subset/subset/subset4/subset$ exiftool broken.jpg | grep "Date/Time Original" laussy@covid:~/Dropbox/Fabrice/Pictures/2023/subset/subset/subset/subset4/subset$
so the information is not extracted, making a 0-index table, which access crashes the code. Now the bug is found.
To solve it, I tag the offending case with a ["0","0"] entry as opposed to date and time strings. And then I skip over this case. This makes v0°3:
#!/bin/sh
# ____ _
# / ___| ___ _ __ (_)
# \___ \ / _ \| '_ \| |
# ___) | (_) | |_) | |
# |____/ \___/| .__/|_|
# v°0.3 |_|
# Fri 5 Jan 2024
# F.P. Laussy - fabrice.laussy@gmail.com
#
# Sopi sorts jpg files in a directory tree named after the dates
# at which the pictures have been taken (after their exif data)
# (`sopi' stands for Sort Pictures)
#
#=
exec julia -O3 "$0" -- $@
=#
using Dates;
# Goes to given directory if one is given as argument
# If not argument given, exit (for safety)
if length(ARGS)!=0
cd(ARGS[1])
else
println("The working directory must be given as argument")
println("To process files in the current directory, use:")
println(" sopi .")
exit()
end
# This list the filenames of JPG files to process (in current path)
lfn=filter(x->occursin("jpg",lowercase(x)), readdir());
# Starting
println("Hi there! Sopi working with "*string(length(lfn))*" files in "*pwd())
print("Started ");
print(now());
print("\n")
# This returns a vector with date and time from the exif data
function datemy(fn)
mdata = read(`exiftool $fn`,String)
sdata=split(mdata,"\n")[findall( x -> occursin("Date/Time Original", x) , split(mdata,"\n"))]
if isempty(sdata)
# if there is no "Date/Time Original" in exif, we tag for exclusion
["0","0"]
else
# otherwise we extract date and time
[split(sdata[1]," ")[end-1],split(sdata[1]," ")[end]]
end
end
# This collects all the dates and times to process and transform into a matrix
alltimes=permutedims(reduce(hcat,[datemy(i) for i in lfn]))
# Initialize the files to exclude to none
excludefile = Int[];
# Find the entries tagged for exclusion from exif
for i=1:length(lfn)
if alltimes[i] == "0"
append!(excludefile, i)
end
end
# Keep unique days
uniquetimes=(x->replace(x, ":"=>"/")).(unique(alltimes[:,1]))
# This creates the directory tree
lmonths=["/01/" "/01-January/"; "/02/" "/02-February/"; "/03/" "/03-March/"; "/04/" "/04-April/"; "/05/" "/05-May/"; "/06/" "/06-June/"; "/07/" "/07-July/"; "/08/" "/08-August/"; "/09/" "/09-September/"; "/10/" "/10-October/"; "/11/" "/11-November/"; "/12/" "/12-December/"]
for i=1:12
global uniquetimes=(x->replace(x,lmonths[i,1]=>lmonths[i,2])).(uniquetimes[:,1])
end
# Remove the case of exif exclusion (would create a "0" year)
filter!(e->e!="0",uniquetimes)
print("Working with "*string(length(uniquetimes))*" directories...\n")
mkpath.(uniquetimes)
print("Moving files!\n")
# This puts the files in place
for i=1:length(lfn)
# skip file if no exif
(i in excludefile) && continue
dest=split(alltimes[i,1],":")
# to rename after date
# mv(lfn[i],dest[1]*lmonths[:,2][parse(Int64,dest[2])]*dest[3]*"/"*replace(replace(lfn[i],".JPG"=>"-"),".jpg"=>"-")*alltimes[i,2]*".jpg")
# to NOT rename after date
mv(lfn[i],dest[1]*lmonths[:,2][parse(Int64,dest[2])]*dest[3]*"/"*lfn[i])
end
print("Finished ")
print(now())
print("\n")