当EXIF日期相同时,在照片名称的末尾添加间隔

  • 本文关键字:添加 照片 日期 EXIF bash
  • 更新时间 :
  • 英文 :


我正在编写一个脚本来自动化我的照片管理,但我被困在了工作流程的最后一部分。脚本当前执行以下操作。。。

  1. 查询给定的文件夹,以使用其文件扩展名查找任何图像
  2. 一旦找到一个文件,它就会查询EXIF DateTime字段
  3. 使用DateTime字段,将在另一个位置使用以下语法创建文件夹结构/2016年10月10日/2016年10月31日/
  4. 然后将照片移动到与其DateTime匹配的新文件夹中/2016年10月10日至2016年3月31日/random_photo793958.jpg
  5. 该照片随后被重命名为/2016/10年10月10日.2016/10.1.2016_01.jpg

如果任何一天只存在一张照片,这很好,但它只是覆盖当前状态下的现有照片。我不知道如何构建一个循环来检查文件是否存在,并将_01更改为_02等等

我试过使用While循环,但总是会陷入无限循环。使用我目前的结构,做这件事的最佳循环是什么?

dateTime=`identify -verbose "$2" | grep "exif:DateTime:" | awk -F' ' '{print $2" "$3}'`
eDate=`echo $dateTime | awk -F' ' '{print $1}'`
year=`echo $eDate | awk -F ":" '{print $1}'`
monthNum=`echo $eDate | awk -F ":" '{print $2}'`
monthString=`echo $eDate | awk -F ":" '{print $2}' | sed -e 's/01/01.janurary/' | sed -e 's/02/02.feburary/' | sed -e 's/03/03.march/' | sed -e 's/04/04.april/' | sed -e 's/05/05.may/' | sed -e 's/06/06.june/' | sed -e 's/07/07.july/' | sed -e 's/08/08.august/' | sed -e 's/09/09.september/' | sed -e 's/10/10.october/' | sed -e 's/11/11.november/' | sed -e 's/12/12.december/'`
day=`echo $eDate | awk -F ":" '{print $3}'`
oldPhotoName=$(echo $2 | awk -F"/" '{print $NF}') #strip off last portion of $2 to get the photo name into a variable
fileExt=$(echo $2 | awk -F"." '{print $NF}' | sed 's/./L&/g') #create variable with the file extension and convert it to lowercase
mkdir -p "$outputDir/$year/$monthString/$monthNum.$day.$year" && mv -f "$2" "$outputDir/$year/$monthString/$monthNum.$day.$year/"
n=01
mv -f "$outputDir/$year/$monthString/$monthNum.$day.$year/$oldPhotoName" ""$outputDir/$year/$monthString/$monthNum.$day.$year/$monthNum.$day.$year"_$n.$fileExt"

您已经标记了您的问题bash。虽然您可以使用grepawkcut等实用程序来解析所需的文本,但请注意,对实用程序的每次调用都会在新的子shell中产生自己的进程。如果你在数千个文件上循环,每个迭代产生10个子shell,那么这可能会相加。

不需要进行大部分调用,因为bash提供了自己的文本操作例程(例如,参数扩展,带有子字符串删除替换字符串索引等),可以大大加快速度。您在POSIX shell中有相同的参数扩展,但没有字符串索引

下面是一个如何处理bash内建内容的示例,并向您展示了在发生文件冲突时如何处理递增的_01, _02, ..。命名还考虑了那些暗示你倾向于逻辑排序的命名约定的评论。exif标签提供了一种易于解析的YYYYYMMDD_HHMMSS日期/时间格式(下面我将向您展示如何分解为各个组件)。你们可以用任何你们喜欢的方式(这是你们的),但一系列的评论会导致如下结果:

按年份列出的目录,包含每月的子目录01-12,然后是带有完整日期戳的单个文件名,例如

YYYY/
+- 01/
|   +- YYYYMMDD_HHMMSS.jpg    # duplicate timestamped files 
|   +- YYYYMMDD_HHMMSS_01.jpg
|   +- YYYYMMDD_HHMMSS_02.jpg
|
+- 02/
|
...
|
+- 12/

文件名示例为:

/home/david/tmp/2016/10/20161010_163345.jpg

如果遇到在同一秒拍摄的照片,则它们将被移动到与相同的位置

20161010_163345_01.jpg
20161010_163345_02.jpg
...

使用参数扩展子字符串移除是直接的。substring必须从字符串的末尾到字符串内的某个点匹配(如下图所示,从左或右)。允许使用通配符、球符:

${string#substring}     # remove 1st occurence of substring from left
${string%substring}     # remove 1st occurence of substring from right
${string##substring}    # remove up to last occurence starting from left
${string%%substring}    # remove up to last occurence starting from right

字符串索引也是直接的

${string:position:len}  # extract 'len' number of chars beginning at position

(您可以通过使position为负数来设置字符串末尾的位置,,但您必须(1)在:和数字之间留一个空格(例如${foo: -2:1})或在括号中加一个负数position(如${foo:(-2):1}))

考虑到这一点,您可以相对容易地使日期/时间戳的每个组件可用于命名约定,并处理pathextension的拆分。我已经努力评论下面的代码,以帮助您跟随:

#!/bin/bash
fullfn="$1"   # the full filename including: /path/to/your/image.jpg
# exif datetime (original), 
# remove ' Value: ' label, 
# remove all :, translate ' ' to _
datetime=$(exif -t 0x9003 "$fullfn" | 
sed -n 's/^.*Value:[ ]//p' | 
tr -d ':' | tr ' ' _)
# validate $datetime is YYYYMMDD_HHMMSS
[[ $datetime =~ [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9] ]] || {
printf "error: invalid date/time from '%s'n" "$fullfn"
exit 1
}
dtdate=${datetime%_*}   # isolate YYYYMMDD
dttime=${datetime#*_}   # isolate HHMMSS
dtyear=${dtdate:0:4}    # split YYYYMMDD into YYYY
dtmon=${dtdate:4:2}     # into MM
dtday=${dtdate:6:2}     # into DD
path="${fullfn%/*}"                     # isolate path from /path/fname.ext
fn="${fullfn#"$path"/}"                 # isolate fn from path
test "$fullfn" = "$path" && path='.'    # if no path, set to '.' (or $PWD)
ext="${fn##*.}"                         # isolate ext from fn.ext
test "$ext" = "$fn" && ext=             # unset if no extension present
if test -n "$ext"
then
lcext=".${ext,,}"    # I like my extensions lowercase
else
lcext=".jpg"         # if no extension, then its 'jpg' (or leave blank)
fi
## create your directories as desired here
# e.g.
newpath=~/tmp/$dtyear/$dtmon    ## SUBSTITUTE YOUR LOCATION FOR tmp HERE
mkdir -p "$newpath"
# test no duplicate before moving to new directory
declare -i n=1
newfn="${datetime}$lcext"           # YYYYMMDD_HHMMSS.jpg
newname="$newfn"                    # copy of $newfn to update if req'd
while test -f "$newpath/$newname"   # increment 'n' until no conflict
do
printf -v newname "%s_%02d%s" "${newfn%$lcext}" "$n" "$lcext"
((n++))
done
newfn="$newname"
printf "mv %snto %sn" "$fullfn" "$newpath/$newfn"
mv "$fullfn" "newpath/$newfn"
exit 0

注意:这只是一个使用单个文件名并根据需要对其进行操作的示例。您需要在循环中包含类似的内容,以处理您想要排序和移动的所有文件。还要注意,exif采用tag选择-t。上面的-t 0x9003对应于图像的Date and Time (Original)。可以使用exif -l imagename.jpg检查图像的所有可用标记。

示例使用/输出

$ ./splitexif.sh ~/tmp/100_4423.JPG
mv /home/david/tmp/100_4423.JPG
to /home/david/tmp/2016/10/20161010_163345.jpg
$ cp ~/tmp/100_4423.JPG foo
$ ./splitexif.sh ~/tmp/foo
mv /home/david/tmp/foo
to /home/david/tmp/2016/10/20161010_163345_01.jpg

如果再打电话,你会得到:

$ ./splitexif.sh ~/tmp/foo
mv /home/david/tmp/foo
to /home/david/tmp/2016/10/20161010_163345_02.jpg

仔细看看,如果你有任何问题,请告诉我。

您可以使用一个属性(或属性组合)来使文件唯一(或者至少在给定目标的情况下足够可能是唯一的),例如文件大小(例如字节)或校验和,并将其附加在目标目录中的文件名末尾。

这样做的好处是,如果您实际复制同一个文件两次,最终不会得到两个副本,而是用另一个完全相同的副本覆盖旧文件。

例如:

#(assuming variable `file` contains the full path of the file)
#(assuming variable `dir` contains the target directory to move file to)
name="$(basename "$file")"
md5="$(md5sum "$file" | cut -f1 -d" ")"
target_name="${md5}_$name")
mv "$file" "$dir/$target_name"

我简化了扩展处理,将MD5校验和放在该示例的开头,您可能更喜欢其他方式。

请注意,这避免了对循环的任何需要,并确保对于任何一组文件,无论文件的处理顺序如何,最终结果都是相同的。

最新更新