📜 ⬆️ ⬇️

The command line of a Linux photographer is retired!

I take a great interest in a photo from the Change-8M. Then there were long expectations of Friday or Saturday (printing usually went on the night of the weekend), and before that there were very long waiting times for photographic film, chemicals, photographic paper (for scarcity). Now I grew up, became big and lazy. My soap box is almost always with me: either in a backpack, or somewhere in my pocket. I photograph everything that aroused interest. In this case, there can be one photo per day (I went from work), and maybe a lot at once (purposefully went out for a walk). And if with a purposeful case I’ve most likely to pick up the photos with salt and sort them out, then in rare cases I’ll forget and then it turns out that I need to sort the photos taken in a dozen different days. Recently, getting purposefully getting less and less, so the number of single photographs grew. And one of these days, inspired by last year’s article , I decided to simplify my hobby. Since Linux is on a computer (openSUSE 12.1), there should be no unsolvable problems - I thought. And I wanted it to copy itself and not need to poke anywhere. Well, since I’m not a real Linux user (the first and last script was in the third year of 0x0C years ago), I’ll say at once that not everything worked out.

I keep photos in one place, a separate catalog with a date for a separate event, even if there is one photo: “2009.05.20 Night Peter”, “2011.08.20 Lavna Falls”, “2012.07.24 Dusya is sleeping”. Thoughts are already coming that need at least one more level - a year, but for the time being I suffer. My or not my photos (in the case of collective campaigns) - it does not matter to me, everything will be in the same catalog for the event. I’ll find my photos if necessary.
For automatic sorting, you need to track the moment of connecting the desired memory card and run the sorting script. On Linux, the udev daemon is responsible for hardware. Therefore, to begin to learn how to handle it.

udev
udev monitors the hardware and creates a node for each device in the / dev directory. This is convenient, but there is a small nuance: devices of one class will be named sequentially depending on the order of their connection. Therefore, the initial option is to press a button on the script that would copy everything where necessary — not suitable (you never know what other disks will be connected, but to track a specific disk you will have to complicate the script, and you didn’t want to press somewhere after connecting the card) . But it can be configured so that a particular disk is mounted at the desired point - this is already good, but not enough. Its biggest plus: the launch of an arbitrary script for some events suitable for the filter. To begin with, let's look at what attributes and events we can attach to uniquely determine the fact of inserting a memory card into the card reader. The ultimate goal of this section is the udev card matching rules file.
To view the characteristics of devices, you can use the program udevadm. But she needs a device name. Therefore, you must first determine the name of the disk in the card reader. We use the easiest way. First, we look at what drives we already have in the system:
>ls -1 /dev/sd* /dev/sda /dev/sda1 /dev/sdb /dev/sdb1 /dev/sdc /dev/sdc1 /dev/sdd /dev/sdd1 /dev/sde /dev/sde1 /dev/sdf /dev/sdg /dev/sdh /dev/sdi 

Insert the card into the reader and repeat the command:
 >ls -1 /dev/sd* /dev/sda /dev/sda1 /dev/sdb /dev/sdb1 /dev/sdc /dev/sdc1 /dev/sdd /dev/sdd1 /dev/sde /dev/sde1 /dev/sdf /dev/sdg /dev/sdh /dev/sdh1 /dev/sdi 

It can be seen that the inserted card is hidden under the name / dev / sdh1. I will say secretly that the last 4 disks (sdf, sdg, sdh, sdi) are all card readers, and you could determine its disks by running the same command before and after connecting the reader to the computer, even without a memory card (before me it is a little later it came when he had already determined the name of the volume in the manner described).

Now we look at the characteristics of this card reader and card. You need to find something for which it will be possible to cling to for more or less unambiguous determination of the fact of the appearance of the card in the reader. Here we will need any of his disk names. This command will display a list of attributes of all devices, starting with the one specified by the name and up to the root, in a udev-like format:
Udevadm output
 >udevadm info -a -n /dev/sdh1 Udevadm info starts with the device specified by the devpath and then walks up the chain of parent devices. It prints for every device found, all possible attributes in the udev rules key format. A rule to match, can be composed by the attributes of the device and the attributes from one single parent device. looking at device '/devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host10/target10:0:0/10:0:0:2/block/sdh/sdh1': KERNEL=="sdh1" SUBSYSTEM=="block" DRIVER=="" ATTR{partition}=="1" ATTR{start}=="2048" ATTR{size}=="153600" ATTR{ro}=="0" ATTR{alignment_offset}=="0" ATTR{discard_alignment}=="0" ATTR{stat}==" 146 4 738 319 0 0 0 0 0 319 319" ATTR{inflight}==" 0 0" looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host10/target10:0:0/10:0:0:2/block/sdh': KERNELS=="sdh" SUBSYSTEMS=="block" DRIVERS=="" ATTRS{range}=="16" ATTRS{ext_range}=="256" ATTRS{removable}=="1" ATTRS{ro}=="0" ATTRS{size}=="7745536" ATTRS{alignment_offset}=="0" ATTRS{discard_alignment}=="0" ATTRS{capability}=="51" ATTRS{stat}==" 1352 1239 73856 8882 4 18 22 735 0 3608 9615" ATTRS{inflight}==" 0 0" ATTRS{events}=="media_change" ATTRS{events_async}=="" ATTRS{events_poll_msecs}=="-1" looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host14/target14:0:0/14:0:0:2': KERNELS=="14:0:0:2" SUBSYSTEMS=="scsi" DRIVERS=="sd" ATTRS{device_blocked}=="0" ATTRS{type}=="0" ATTRS{scsi_level}=="0" ATTRS{vendor}=="Generic-" ATTRS{model}=="SD/MMC " ATTRS{rev}=="1.00" ATTRS{state}=="running" ATTRS{timeout}=="30" ATTRS{iocounterbits}=="32" ATTRS{iorequest_cnt}=="0x220" ATTRS{iodone_cnt}=="0x220" ATTRS{ioerr_cnt}=="0x21f" ATTRS{evt_media_change}=="0" ATTRS{queue_depth}=="1" ATTRS{queue_type}=="none" ATTRS{max_sectors}=="240" looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host14/target14:0:0': KERNELS=="target14:0:0" SUBSYSTEMS=="scsi" DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host14': KERNELS=="host14" SUBSYSTEMS=="scsi" DRIVERS=="" looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0': KERNELS=="1-1:1.0" SUBSYSTEMS=="usb" DRIVERS=="usb-storage" ATTRS{bInterfaceNumber}=="00" ATTRS{bAlternateSetting}==" 0" ATTRS{bNumEndpoints}=="02" ATTRS{bInterfaceClass}=="08" ATTRS{bInterfaceSubClass}=="06" ATTRS{bInterfaceProtocol}=="50" ATTRS{supports_autosuspend}=="1" ATTRS{interface}=="Bulk-In, Bulk-Out, Interface" looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1/1-1': KERNELS=="1-1" SUBSYSTEMS=="usb" DRIVERS=="usb" ATTRS{configuration}=="CARD READER" ATTRS{bNumInterfaces}==" 1" ATTRS{bConfigurationValue}=="1" ATTRS{bmAttributes}=="80" ATTRS{bMaxPower}=="500mA" ATTRS{urbnum}=="10885" ATTRS{idVendor}=="0bda" ATTRS{idProduct}=="0151" ATTRS{bcdDevice}=="5195" ATTRS{bDeviceClass}=="00" ATTRS{bDeviceSubClass}=="00" ATTRS{bDeviceProtocol}=="00" ATTRS{bNumConfigurations}=="1" ATTRS{bMaxPacketSize0}=="64" ATTRS{speed}=="480" ATTRS{busnum}=="1" ATTRS{devnum}=="15" ATTRS{devpath}=="1" ATTRS{version}==" 2.00" ATTRS{maxchild}=="0" ATTRS{quirks}=="0x0" ATTRS{avoid_reset_quirk}=="0" ATTRS{authorized}=="1" ATTRS{manufacturer}=="Generic" ATTRS{product}=="USB2.0-CRW" ATTRS{serial}=="20060413092100000" looking at parent device '/devices/pci0000:00/0000:00:02.1/usb1': KERNELS=="usb1" SUBSYSTEMS=="usb" DRIVERS=="usb" ATTRS{configuration}=="" ATTRS{bNumInterfaces}==" 1" ATTRS{bConfigurationValue}=="1" ATTRS{bmAttributes}=="e0" ATTRS{bMaxPower}==" 0mA" ATTRS{urbnum}=="222" ATTRS{idVendor}=="1d6b" ATTRS{idProduct}=="0002" ATTRS{bcdDevice}=="0301" ATTRS{bDeviceClass}=="09" ATTRS{bDeviceSubClass}=="00" ATTRS{bDeviceProtocol}=="00" ATTRS{bNumConfigurations}=="1" ATTRS{bMaxPacketSize0}=="64" ATTRS{speed}=="480" ATTRS{busnum}=="1" ATTRS{devnum}=="1" ATTRS{devpath}=="0" ATTRS{version}==" 2.00" ATTRS{maxchild}=="6" ATTRS{quirks}=="0x0" ATTRS{avoid_reset_quirk}=="0" ATTRS{authorized}=="1" ATTRS{manufacturer}=="Linux 3.1.10-1.16-desktop ehci_hcd" ATTRS{product}=="EHCI Host Controller" ATTRS{serial}=="0000:00:02.1" ATTRS{authorized_default}=="1" looking at parent device '/devices/pci0000:00/0000:00:02.1': KERNELS=="0000:00:02.1" SUBSYSTEMS=="pci" DRIVERS=="ehci_hcd" ATTRS{vendor}=="0x10de" ATTRS{device}=="0x077c" ATTRS{subsystem_vendor}=="0x1043" ATTRS{subsystem_device}=="0x82e7" ATTRS{class}=="0x0c0320" ATTRS{irq}=="22" ATTRS{local_cpus}=="00000000,00000000,00000000,0000000f" ATTRS{local_cpulist}=="0-3" ATTRS{numa_node}=="0" ATTRS{dma_mask_bits}=="32" ATTRS{consistent_dma_mask_bits}=="32" ATTRS{enable}=="1" ATTRS{broken_parity_status}=="0" ATTRS{msi_bus}=="" ATTRS{companion}=="" ATTRS{uframe_periodic_max}=="100" looking at parent device '/devices/pci0000:00': KERNELS=="pci0000:00" SUBSYSTEMS=="" DRIVERS=="" 

As written in a note, you can use the properties of the device itself and the properties of one of the parent devices to write the rules.
')
We will write the rules for connecting the first section of the memory card (in order to immediately cut off the cards without partitions, although I did not try to do such, and the camera writes exclusively in the first section). The volume sdh1 itself has few unique properties. Here, except that the device name KERNEL == "sdh1" and the subsystem of this device SUBSYSTEM == "block". But such properties will have any flash drive. And, as I said earlier, it’s not a fact that the next time the system calls our volume sdh1, and suddenly I’ll buy myself another camera with xD-Picture or CompactFlash in general (which would be sdf, sdg, sdi) - I wouldn’t like to for this rewrite the rules. Luckily, udev supports jokers and therefore in this part for the rule we take the expression KERNEL == "sd? 1" (or even "sd *"), which will mean the first volume of the mapped drive, and under what specific letter it will be - we don’t important, the script will still get the name completely without the joker.

Let's go to the next device. /devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host14/target14 Sample track / 14 , only ATTRS {events} == "media_change", but again this attribute will be on any flash drive.

The following device /devices/pci0000:00/0000:00:02.1/usb1/1-1/1-1:1.0/host14/target14 Sampling track / 14 and the track is more specific variant of ATTRS { model} == "SD / MMC", but tied to this attribute, we will only respond to SD-cards and ignore other possible options of memory cards. In the next three devices, we again find nothing interesting. The following device /devices/pci0000:00/0000:00:02.1/usb1/1-1 is more interesting:
 SUBSYSTEMS=="usb" DRIVERS=="usb" ATTRS{configuration}=="CARD READER" ATTRS{idVendor}=="0bda" ATTRS{idProduct}=="0151" ATTRS{product}=="USB2.0-CRW" ATTRS{serial}=="20060413092100000" 

Judging by the attributes, this is the card reader itself and that’s what we need (the remaining devices already have a USB connection). Total in the rules of correspondence, we will rely on the attributes of the card reader and the first volume of the memory card:
 ACTION=="add", KERNEL=="sd?1", SUBSYSTEMS=="usb", ATTRS{configuration}=="CARD READER", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="0151" 

And they inserted another event (ACTION == "add") of adding the device, since copying after the card is removed from the reader is impossible purely physically. I thought here: there is still the attribute ATTR {ro} == "0" - it can also be added to the rules, otherwise, if the disk is write-protected, then nothing can be moved (but it can be copied, although it can be slightly scattered extra directories depending on the file type names of the camera), but I will not do this for myself.

As you probably already noticed, the ATTR / KERNEL / SUBSYSTEM / DRIVER keys are used for the device itself, and the ATTRS / KERNELS / SUBSYSTEMS / DRIVERS keys are already used for its parents with the letter S on the end.

I have another reader for microSD cards, so in its configuration there is an empty line and you can only be attached via idVendor and idProduct. In one of the previous versions of the udev rules file (I started writing the script somewhere in the middle of August and did not update the system until October, and somewhere in that period this attribute was added, and indeed many attributes for this reader changed ) the vendor and device attributes were used, which (having changed slightly since they disappeared) I left, although there is no special need for them now. In this version of the filter, it will act only for this model of the card reader and connecting some other model will not cause any effect.

We defined the filters, and actions have not yet been assigned. Since we need to execute a specific script, then we add an action to the filter:
 RUN+="/root/bin/PhotoSort.sh %k" 

% k in the script parameters - volume name generated by the system (what is displayed in the KERNEL key of the disk is sdh1); /root/bin/PhotoSort.sh is the name of our future script.

The final udev rule file looks like this:
 >cat /etc/udev/rules.d/99-lumix.rules #      ACTION=="add", KERNEL=="sd?1", SUBSYSTEMS=="usb", ATTRS{configuration}=="CARD READER", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="0151", RUN+="/root/bin/PhotoSort.sh %k" #      #ACTION=="add", KERNEL=="sd?1", SUBSYSTEMS=="pci", ATTR{events}=="media_change", ATTRS{vendor}=="0x10de", ATTRS{device}=="0x077e", RUN+="/root/bin/PhotoSort.sh %k" 

Now we save this text in the /etc/udev/rules.d/ directory (we need root rights) under an arbitrary name (I have 99-lumix.rules). The udev daemon processes files from this directory in alphabetical order until it finds a filter that matches the conditions. After finding the appropriate rule, no further file processing is performed. Therefore, the number for our rule can be set small — it will be processed earlier and will not be covered with any other rule.

If you want to keep track of all the connected flash drives and cards, then you can rely on the level below after the reader itself and cling to SUBSYSTEMS == "usb", DRIVERS == "usb-storage" - they definitely should be on the cards and flash drives. But for the time being I am only watching the reader - there was no need for the rest.

bash
And now we write a script to sort photos from the connected card. The original version supported the sorting of the entire disk only. In the course of operation, I also wanted to process the already existing catalogs (unsorted sorts), therefore I had to change and add a little. Here we will consider the second version, which can sort media files (media file extensions are specified in the script itself in the parameters of the find command) from the disk and from the directory.

First, briefly about the algorithm of the script. Determine what we will handle - a volume or directory. If the volume is, then it should be mounted at the beginning (and if the mount point is already occupied, and it is used only by this script, then the script will complete its work), and at the end do not forget to unmount it. After mounting, the presence of a hidden .PhotoSort file is checked in which the settings for this card are stored. His absence serves as a signal that some ordinary card has been inserted and nothing needs to be done with it. In this file, you can specify the name of the disk (required only for logging - explained at the end) and the directory for saving files if the standard does not fit (I thought to make a separate storage for the mobile phone). The export directory can also be specified by the second parameter to the script in the command line or in the udev rules file.

Then all the media files from the specified and nested directories (because cameras save photos in different directories that we don’t know in advance) need to be sorted by the time they were created. Here lies the catch: in Linux there is no such thing as a file creation time, but there is only the time of the last access to the file and it is updated when manipulating pictures, so you cannot rely on it. The file name also cannot be: DSC08655.JPG made on 02.05 should go after MOV08554.MPG from 29.04, which in turn should be processed after P1170007.JPG from 19.04. This problem is especially acute if we have several cameras from different manufacturers who took pictures of the same event (aka "party"). EXIF comes to the rescue - among other things, it has the attribute DateTimeOriginal and CreateDate (there is some difference between them: at least the first one has no video files and if it is not read for some reason, the second one will be read) and these attributes in ordinary situations do not change. But I don’t know how to count files from all subdirectories starting from a given one, sorted by these attributes, so all files are renamed in a loop (exiftool can do this, but I left a loop for the log), adding the time for creating a snapshot or video to the name (so that it can be sorted in chronological order by file name).

The second cycle distributes files to directories: a new directory is created when the break between the current file and the previous one becomes more than five hours (5 * 60 * 60) - it is for this that we sorted the files in chronological order. Five hours - a figure from the ceiling. I have not yet had pictures from one event so that the break between frames was more than that time. There were tours, with long journeys between POIs, but the road took less than five hours, and if more, then it was a different city. There were weddings and holidays passing into another day, and even though the date changed, but the event remained the same. So, while the break is small, we save it in the same directory as the previous file. The catalog name is set as YYYYMMDD_HH - the clock (taken from the first file of the new event) is added to the catalog name because of two events of the same day, otherwise, when going to the second day, we get the same catalog name, and at least its creation will fail, but later saves files will be carried to the same directory.

Everything that happens is written to the log (this is how I checked the functioning of the script), as well as commands to return the files back to disk (it was useful for debugging - I performed a piece of the file and the test media files were returned back to the card with their old names). Additional processing (rotation, reduction, signing) - I don’t use it, anyway then I’ll watch, delete and do it all (and I don’t put a signature on the photos). The place where this can be done is indicated by a comment in the text of the script.
The script itself:
 >cat /root/bin/PhotoSort.sh #!/bin/bash #/root/bin/PhotoSort.sh #requires: bash,coreutils,findutils,exiftool,sed,util-linux #cat /etc/udev/rules/99-lumix.rules ##      #ACTION=="add", KERNEL=="sd?1", SUBSYSTEMS=="usb", ATTRS{configuration}=="CARD READER", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="0151", RUN+="/root/bin/PhotoSort.sh %k" ##      ##ACTION=="add", KERNEL=="sd?1", SUBSYSTEMS=="pci", ATTR{events}=="media_change", ATTRS{vendor}=="0x10de", ATTRS{device}=="0x077e", RUN+="/root/bin/PhotoSort.sh %k" #      udev #udevadm info -a -p $(udeadm info -q path -n /dev/sd*)  udevadm info -a -n /dev/sd* #http://www.arccomm.ru/OpenSource/Dev/udev.html if [[ -z "$1" ]] then echo             , echo      echo $(basename "$0") Source [DestDir] echo Source -    ,    echo DestDir -  ,     echo   ,    echo   ,      .PhotoCopy echo             echo    -   \( \) echo : echo $(basename "$0") sdd1 -    /dev/sdd1. echo         sdd1     .PhotoCopy echo        echo $(basename "$0") . ~/Photo -     \(.\)       echo    ~/Photo/  exit 1 #    fi if [[ ${1:0:1} == "/" || ${1:0:1} == "." || ${1:0:1} == "~" ]] then #   if [[ -d "$1" ]] then disk="$1" else echo    exit 2 #     fi else #   #   dev="/dev/$1" if [[ ! -e "$dev" ]] then #   ,     (      ,   ) exit 0 fi #    disk="/mnt/photo" fi #    if [[ ! -z "$2" ]] then #     photo="$2" else photo="/mnt/temp/Photo" #  ,  fi sphoto="" #    #         photodir="" # -:         -  log="/var/log/photosort.log" #      lastfiletime=0 #     curfiletime=0 #   -  #export XAUTHORITY="/home/%username%/.Xauthority" #export DISPLAY=:0.0 #notify-send Photoes "FlashCard found" #     - ?  - ,         if [[ -n "$dev" ]] then grep -q "$disk" /etc/mtab if [[ $? -eq 0 ]] #     , grep  0 then #  -   echo "#=- $(date -u +%Y.%m.%d\ %T)    -=#" exit 10 #   ,    -   fi #  if [[ ! -d "$disk" ]] then #     - mkdir "$disk" &>>"$log" echo "#"    "$disk" >> "$log" fi mount -t vfat -o noatime,rw,noexec,users,iocharset=utf8 "$dev" "$disk" &>> "$log" if [[ ! -e "$disk"/.PhotoCopy ]] then #       ,    umount "$disk" &>> "$log" exit 0 #   ,       ,       fi # :      $2,         #  -   ( /dev)    (  root) sphoto=$(head -n1 "$disk"/.PhotoCopy) #           ,        if [[ ${sphoto:0:1} == "/" && -d "$sphoto" ]] then #    ,    ,   photo="$sphoto" fi sphoto=$(tail -n1 "$disk"/.PhotoCopy) #        echo $(date -u +%Y.%m.%d\ %T)    "$sphoto" >> "$log" else if [[ ! -z "$2" ]] then photo="$2" #    fi log="./PhotoSort.log" #      echo "#" $(date -u +%Y.%m.%d\ %T)    "$photo" >> "$log" echo cd $(pwd) >> "$log" #     ,            fi #       # NB!:     - ,     #   ,       , #    (     ) #    (     -  ) for file in $(find "$disk" -type f \( -name '*.JPG' -o -name '*.MOV' -o -name '*.MPG' -o -name '*.THM' -o -name '*.MP4' -o -name '*.AVI' \) -and -not -name '*_*' -and -not -name '* *') do # exiftool   : # Date/Time Original : 2011:07:30 15:35:52 #     20110730153552 curfiletime=$(exiftool -DateTimeOriginal "$file" | cut -d: -f2- | sed 's/[:\ ]//g') #    if [[ $curfiletime == "" ]] then curfiletime=$(exiftool -CreateDate "$file" | cut -d: -f2- | sed 's/[:\ ]//g') #    fi mv "$file" $(dirname "$file")/"$curfiletime"_$(basename "$file") &>> "$log" #  echo mv $(dirname "$file")/"$curfiletime"_$(basename "$file") "$file" >> "$log" #     done #      # (      ,       -  -     ,  ,  ) for file in $(find "$disk" -type f -name '*.JPG' -o -name '*.MOV' -o -name '*.MPG' -o -name '*.THM' -o -name '*.MP4' -o -name '*.AVI' | sort) do # exiftool  ,               ,     curfiletime=$(exiftool -DateTimeOriginal "$file" | sed -r 's/^.+: ([0-9]+):([0-9]+):([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/\1-\2-\3 \4:\5:\6/g') if [[ $curfiletime == "" ]] then curfiletime=$(exiftool -CreateDate "$file" | sed -r 's/^.+: ([0-9]+):([0-9]+):([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/\1-\2-\3 \4:\5:\6/g') fi curfiletime=$(date -d "$curfiletime" +%s) #       if (( $curfiletime - $lastfiletime > 5*60*60 )) #        : 5*60*60 ,           then photodir=$(date -d @$curfiletime +%Y.%m.%d_%H) #       if [[ ! -d "$photo"/"$photodir" ]] #    - then mkdir "$photo"/"$photodir" &>> "$log" chown nobody:users "$photo"/"$photodir" &>> "$log" chmod 0777 "$photo"/"$photodir" &>> "$log" echo "#" $(date -u +%Y.%m.%d\ %T)    "$photodir" >> "$log" fi lastfiletime="$curfiletime" fi echo "#" $(date -u +%Y.%m.%d\ %T)   "$file"  "$photo"/"$photodir"/$(basename "$file") >> "$log" echo copy "$photo"/"$photodir"/$(basename "$file") "$file" >> "$log" #        -  (/ , gps   ) mv "$file" "$photo"/"$photodir"/ &>> "$log" chown nobody:users "$photo"/"$photodir"/$(basename "$file") &>> "$log" chmod 0666 "$photo"/"$photodir"/$(basename "$file") &>> "$log" done if [[ -n "$dev" ]] then echo $(date -u +%Y.%m.%d\ %T)   >> "$log" #    umount "$disk" &>> "$log" fi exit 0 

Explanation of some points: exiftool returns strings like "Date / Time Original: 2011: 07: 30 15:35:52" this line needs to be converted to "20110730153552" (note: the Internet is full of examples where the separator is not a colon, but a vertical trait - see what you have):

 curfiletime=$(exiftool -DateTimeOriginal "$file" | cut -d: -f2- | sed 's/[:\ ]//g') 

cut will cut everything after the first colon, and sed will remove all spaces and colons from the resulting string. I am sure that you can do only sed'om, but with RegExpami not very friendly.

 curfiletime=$(exiftool -DateTimeOriginal "$file" | sed -r 's/^.+: ([0-9]+):([0-9]+):([0-9]+) ([0-9]+):([0-9]+):([0-9]+)/\1-\2-\3 \4:\5:\6/g') 

The same line converts to "2011-07-30 15:35:52". Again, perhaps there is a more elegant solution. This is so that the date could be treated as a date (in any format, the string cannot be dropped into it).

Using
The script processes the specified directory and all attached to it (for a memory card, this will be its root and everything deeper) as one.

 ./PhotoSort.sh sdh1 
so the script is called from udev to sort the sdh1 disk. In this case, the presence of a file with settings in its root will be checked. The disk was transferred to us or the directory is determined by the first character: if it will be ~ / or. - means a directory, otherwise - a disk.

 ./PhotoSort.sh ~/AllFromParty 
as well, you can sort all the photo and video files from all the directories nested in ~ / AllFromParty (including himself). Everything will be saved to the default directory specified in the script (photo = "/ mnt / temp / Photo"). If there were files from several cameras in the source directory, then there may be some dissynchronization in the file names due to the fact that the time between the cameras was not synchronized, or even never exhibited. What can cause the distribution of files from different cameras in different directories. Then, before performing the sorting, you need to adjust the time in the EXIF ​​tags: exiftool "-DateTimeOriginal + = 00.00.0000 02:37:30" * .JPG - will shift the time in the DateTimeoriginal tag by 2:37:30 to the future for all JPG files from the current directory.

 ./PhotoSort.sh ~/AllFromParty /media/backup/Photoes 
the same as above, but everything will be saved in the / media / backup / Photoes directory


  1. : , KDE , , . , , ( ).
  2. , . ini-.
  3. . . RUN+= , - . udev' , KDE , — . , .
  4. .PhotoSort udev' . , root - . - . (, ) .
  5. , dosfslabel . udev - . - .
  6. : . Caanoo — .PhotoSort .
  7. - , RO .
  8. . , - , ?

References
Bash-
HOWTO: udev
udev. ( )

Source: https://habr.com/ru/post/158371/


All Articles