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?

Zanna
  • 72,312
Dave
  • 145

2 Answers2

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).

jfs
  • 4,078
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.

emk2203
  • 4,393
  • 1
  • 26
  • 52