3. Tutorial: Peek and Pipe

In the previous example, we had a look at the contents of a folder with ls and find.

3.1. Select

Let’s pick out one specific file and have a closer look at the second photo we found:

> E = find("pics_tree")
...
> F = getfiles(E)         # these first two steps can be combined to 'F = findfiles("pics_tree")'
...
> f = F[2]
FileEntry("/foo/exifuse/pics/pics_tree/bar/u.png", -rw-r--r--, 83761 bytes)

(Note that Julia, unlike Python, indexes its vectors from 1, not 0.)

We can also use functions to access a specific file, as in (we’ll use that seemingly ponderous approach below):

> f = first(F)
...
> f = second(F)
...

3.2. Exify Your First Photo

By default, the REPL shows a basic summary description of this file. We want to know more:

> ex = exify(f)
:SourceFile                = /foo/exifuse/pics/pics_tree/bar/u.png
:ExifTool__ExifToolVersion = 11.88
:Other__FileName           = u.png
:Other__Directory          = /foo/exifuse/pics/pics_tree/bar
:Other__FileSize           = 82 kB
:Other__FilePermissions    = rw-r--r--
...
:Gamma                     = 2.2
:PixelsPerUnitX            = 3779
:PixelsPerUnitY            = 3779
:PixelUnits                = meters
:ImageSize                 = 356x151
:Megapixels                = 0.054

Whoa! That’s a massive info dump right here!

What happened here? The exify function calls out to our trusted exiftool friend and converts the resulting metadata into a nice Julia data structure; that data is stored in our variable ex; and the REPL than immediately displays it.

We can get specific fields of this large EXIF structure in several different ways:

  1. Via the full tag name:

    > s = ex.Time__FileModifyDate
    "2023:04:28 12:53:26+02:00"
    
  2. Via the short tag name:

    > s = ex.FileModifyDate
    "2023:04:28 12:53:26+02:00"
    

Quite convenient, but still quite keyboard-intensive a task—were it not for..

3.3. Tab Completion

Remembering, let alone typing EXIF names is tedious. Luckily the Julia REPL support tab-completion. Try this:

> ex.File[press <Tab> twice]
FileAccessDate       FileInodeChangeDate  FileModifyDate       FileName             FilePermissions
FileSize             FileType             FileTypeExtension

> ex.FileM[press <Tab> twice]

Voilà—tag names at your fingertips!

As an exercise, try to select and exify a different file from our list (we used f[2] above to select the second one), and to display some of its EXIF fields. Hint: maybe you found this already, but using <Cursor-up/-down> let’s us go back in the REPL history and avoid even more typing.

3.4. More Than Tags

Now, we can work with the EXIF values directly, as shown above, but this also can soon become tricky, especially if you work on very large numbers of files with different formats. Consider for example the “date” of a file:

  1. there are several EXIF tags to choose from;

  2. different photo formats might use different tag names;

  3. we also usually want some sort of fallback—if one specific date value is not available, fall back to the next, etc., until we end up with the OS creation date;

  4. we also might want different date formats for different tasks.

Exifuse provides a few additional convenience functions as a shortcut in such cases. One is (try it out on an EXIF structure):

> exif2shortdate(ex)

(For now) this function first tries to read the Time__CreateDate; if that does no exist or if it starts with 0000, it tries to get Time__FileModifyDate. If valid, it returns a short string representation in the format YYYYMMDD_HHMMSS.

There are many such helper functions, and we will also soon learn how you can simply define your own. To find them, try:

> exif[press <Tab> twice]

Tab completion should list all currently available helpers.


Later on, we will encounter more ways to retrieve—raw or derived—EXIF fields, learn about the __ used in the long tab name variant, tweak the output to show only the tags we’re interested in, etc. But will will conclude this example with something else entirely, a brief first glimpse at a very powerful Julia feature..

3.5. Get Into the Flow and Pipe It

..that will soon become out dear friend, after probably hurting our heads good:

Above we looked for file entries in a whole hierarchy, weeded out the files, selected one of them, transformed it into an EXIF data structure, and had a look at its date. We did something like this (we will here just use the first file instead of the second):

> E = find("pics_tree")
> F = getfiles(E)
> f = first(F)
> ex = exify(f)
> s = exif2shortdate(ex)

In each step, we call a function and pass it on to a subsequent one. We can write this all in one line:

> s = exif2shortdate(exify(first(getfiles(find("pics_tree")))))

Having fun yet? Not really, I suppose. This is less typing, but at the expense of transparency and more brackets. In addition, the natural flow of things is upended—we read this line from left to right, but the actual order of what is happening is from right to left.

Julia provides a very neat way of writing this differently. Using the so-called pipe operator |>, we can create a pipeline the neatly expresses the flow of data transformations:

> find("pics_tree") |> getfiles |> first |> exify |> exif2shortdate

This allows for a quite natural workflow. We first just type:

> find("pics_tree")

If we are happy with what we see, we use <Cursor-Up> to get back the last command, and just append the next step:

> find("pics_tree") |> getfiles

We look at the plausibility of the output, go <Cursor-Up>, and.. rinse and repeat.


What is actually happening here? the pipe operator is essentially just another way of writing a function call: x |> f is simply equivalent to calling f with x which is just f(x). So you can always just rewrite some foo |> bar as bar(foo) and should get the same result. Try it:

> sin(3.14)
> 3.14 |> sin
...
> length([1,2,3])
> [1,2,3] |> length
...
> files |> length
> f |> filesize
...

We will go into pipes in detail later. For now just notice how here we don’t use round brackets within the pipeline, as we pass in function names and not their evaluation. (We will use brackets again in pipes, but that’s for another day..)