2

I have a huge folder of images that I've collected over the years that my students use for coursework. It started off with about twenty images so I didn't really bother with file names or any kind of structure, just tagged them and chucked them in. That same folder now has about 5000 images in it and it's getting to the point where it's hard to work with.

Ideally I'd like to be able to run a command (now and in the future as more are submitted) that would look at the images tag(s) and create a directory with the name of the tag and move that image into it, or if a previous image has already created that directory simply move the image into that directory.

I'm not sure I'm doing a very good job explaining this.

The first 5 images for example are cars - BMW, Honda, Ford, Alfa Romeo and another BMW. The file names are all unique - they're all in the same folder remember. So after the running this mythical script I'd have 4 folders with the 2 images in the BMW one.

Everything is tagged in Shotwell, it's mostly JPGs with a few PNGs thrown in just to complicate things! There could be multiple tags but 95% are just single ones. I'm not sure where the tags are stored but I'm assuming in each file since the tags show up on both my computers but were only ever tagged on the main one.

Further investigation has shown that the tags are being stored against the individual image's 'keywords' so maybe I should be asking how to create directories based on its keywords?

I'm very much learning as I go here!

If it helps, this is the output from the exiftool:

$ exiftool 010.jpg
ExifTool Version Number         : 10.10
File Name                       : 010.jpg
Directory                       : .
File Size                       : 274 kB
File Modification Date/Time     : 2016:04:23 21:03:59+01:00
File Access Date/Time           : 2016:04:23 21:03:59+01:00
File Inode Change Date/Time     : 2016:04:23 21:03:59+01:00
File Permissions                : rw-rw-r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : inches
X Resolution                    : 72
Y Resolution                    : 72
Exif Byte Order                 : Little-endian (Intel, II)
Software                        : Shotwell 0.22.0
XMP Toolkit                     : XMP Core 4.4.0-Exiv2
Subject                         : BMW, Bell_Photography
Current IPTC Digest             : 6249bf63f46e9abb15cfe27d71a87a72
Keywords                        : BMW, Bell_Photography
Originating Program             : Shotwell
Program Version                 : 0.22.0
Image Width                     : 936
Image Height                    : 1334
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:4:4 (1 1)
Image Size                      : 936x1334
Megapixels                      : 1.2

Assuming that makes sense to anyone but me, is it possible?

  • What is the format of the images (.jpg)? What program have you used to tag the images? Are the tags stored in EXIF inside each image file? Is there a single tag per image or there could be multiple tags? – jfs Apr 23 '16 at 18:57
  • Everything is tagged in Shotwell, it's mostly JPG's with a few PNG's thrown in just to complicate things! There could be multiple tags but 95% are just single ones. I'm not sure where the tags are stored but I'm assuming in each file since the tags show up on both my computers but were only ever tagged on the main one. Hope that helps :) – Dave Apr 23 '16 at 19:04
  • 1- have you look through Shotwell's export options (perhaps, it can save images to directories corresponding to tags already)? 2- have you enabled "Write tags, titles and other metadata to photo files" option? Inspect an image file using something like exiftool, to make sure the tag info is present 3- Note: if some piece of information is necessary to answer the question then edit your question and put it there (don't put it in the comments) – jfs Apr 23 '16 at 19:23
  • 1 - yes, I've had an explore through shotwell, it'll export individual and groups of photos but not with any consideration of the tags. 2 - The option you mentioned is enabled, and the tag info seems to be present. 3 - thank you for the headsup, I'd edited it. This is only my 2nd question, I'm proudly wearing my 'newb' t-shirt!! :) – Dave Apr 23 '16 at 19:31
  • Provide an example output: exiftool example.jpg or exiv2 example.jpg commands. Mention where tags in the output are. – jfs Apr 23 '16 at 19:56
4

While J. F. Sebastian's answer is probably what you are looking for, there is also a tag-based file system named tagsistant which might be worth looking into. You can treat your tags like directories in conventional file systems and browse your files by navigation. This makes it easier if you have several tags in a file and want to search and sort for them.

4

You could use exiftool, to get meta information from jpg, png files. To install exiftool, run:

$ sudo apt-get install libimage-exiftool-perl

Usage: exiftool -s example.jpg

It seems the most complete info is stored as TagsList. It even supports hierarchical tags saved by Shotwell e.g., nature/sky. The tags can be also found in Subject, Keywords, LastKeywordXMP fields. Subject, Keywords do not preserve the tag hierarchy—you can use them if you don't want to create nested directories for nested tags.

Here's python3 script that moves image files specified on the command line to the corresponding tag directories (by creating hardlinks and removing original filename) e.g., if example.jpg has the tags list: nature, nature/clouds, nature/mountain, nature/sky then the result directory tree looks like:

.
└── nature
    ├── clouds
    │   └── example.jpg
    ├── example.jpg
    ├── mountain
    │   └── example.jpg
    └── sky
        └── example.jpg

Note: always backup your data before running potentially destructive commands (bugs happen):

#!/usr/bin/env python3
"""Move image files to their tag directories by creating hardlinks.

Usage: move-to-tag-dirs <image>...
"""
import json
import os
import sys
from subprocess import Popen, PIPE

TAGNAMES = ['TagsList', 'Keywords', 'Subject']
__version__ = '0.3.1'


def move_to_dirs(filename, dirs):
    """Move *filename* to *dirs* by creating hardlinks.

    Create destination directories if they don't exist.
    """
    for dirname in dirs:
        os.makedirs(dirname, exist_ok=True)
        dest_filename = os.path.join(dirname, os.path.basename(filename))
        os.link(filename, dest_filename)  # create hardlink
    if dirs:  # created at least one new link
        os.unlink(filename)  # remove old


if len(sys.argv) < 2:
    sys.exit(__doc__)  # print usage

command = ['exiftool', '-json'] + ['-' + t for t in TAGNAMES] + sys.argv[1:]
try:
    process = Popen(command, stdout=PIPE, stderr=PIPE, universal_newlines=True)
except OSError as e:
    sys.exit("error: can't start exiftool: " + str(e))
else:
    output, errors = process.communicate()
    if process.returncode != 0:
        sys.exit("error: can't read tags info: exiftool exit code: %d: %s" % (
            process.returncode, errors))

for metadata in json.loads(output):
    # get first available tags list
    dirs = next(filter(None, map(metadata.get, TAGNAMES)), [])
    if isinstance(dirs, str):  # single tag
        dirs = [dirs]
    try:
        move_to_dirs(metadata['SourceFile'], dirs)
    except OSError as e:
        print("warning: failed to move %s to %s: %s" % (
            metadata['SourceFile'], dirs, e), file=sys.stderr)

Save it to a file named move-to-tag-dirs somewhere in $PATH. Make sure the file has executable permissions:

$ chmod +x move-to-tag-dirs

Related: How to run a .py program directly?

Then to move all images to the corresponding tag directories, run:

$ move-to-tag-dirs *.jpg *.png

If you get 'Argument list too long' error then run:

$ find . -maxdepth 1 -name \*.jpg -o -name \*.png -exec move-to-tag-dirs {} +

See Solving “mv: Argument list too long”?

If you ignore error handling, support for hierarchical tags, support for unusual characters in file names, tags, etc then the script could be simplified significantly (perhaps, a simple one-off shell script would be enough in this case).

  • That is a hundred times more than I'd hoped for, thank you @J.F. Sebastian so much!!! I've had a quick stab at putting it all together. I've checked .profile for my $PATH thing, said my user one was HOME/bin so I've created it, saved the script in there and chmod'ed it but when I come to run the command I still get 'command not found' even after a restart. Have I forgotten something painfully obvious here?! – Dave Apr 24 '16 at 11:40
  • @Dave what happens if run python3? 1- provide the exact command that you have run and the corresponding error message 2- show the result of echo $PATH and ls -l /path/to/copy-to-tag-dirs (update your question with the info or ask a new one (about "command not found" error)) – jfs Apr 24 '16 at 12:09
  • going through the process of gathering that information showed me the problem, which I've now sorted (turns out I just can't spell!). The script has run and kicked up this error I'm hoping you can help with please - copy-to-tag-dirs *.jpg Traceback (most recent call last): File "/bin/copy-to-tag-dirs", line 39, in <module> copy_to_dirs(metadata['SourceFile'], metadata[TAGNAME]) KeyError: 'TagsList' Ideally the resulting folders wouldn't be nested, they'd just be a flat tree. Am I right in thinking if I change the tagname to Keywords that'd be the result I'd get? – Dave Apr 24 '16 at 12:31
  • @Dave if there is no Tagslist in the image metadata then the hierarchical information will be lost. Are you sure you haven't removed the tick from "export meta data" option while exporting the images from Shotwell? If you don't need to preserve the hierarchy then replace 'Tagslist' with 'Keywords' in the script. – jfs Apr 24 '16 at 12:41
  • I've started over, gone into shotwell and made sure options to save the tags is clicked in. I've exported a fresh set of images and run the script again. No errors kick up in the terminal but what I end up with in the original directory is a new folder for every letter in the tag. So for the images tagged as BMW I have a folder called B and a folder called M and a folder called W with the same 2 images in each of the folders. – Dave Apr 24 '16 at 12:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.