Skip to content

Commit b3afd45

Browse files
committed
image: write documentation
1 parent ac3d209 commit b3afd45

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

lib/data/collection.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule Memorable.Data.Collection do
55
Collections can be thought of as analogous to a photo album, or a gallery. They are the primary mechanism for
66
organizing images.
77
8-
Collections are comprised of the following fields:
8+
## Fields
99
- `id`: An [ID](`t:Memorable.Util.id/0`) representing the collection
1010
- `name`: The human-readable name of the collection
1111
- `created_datetime`: A `DateTime` representing the time at which the collection was created.

lib/data/image.ex

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
defmodule Memorable.Data.Image do
2+
@moduledoc """
3+
Functions for working with images in memorable.
4+
5+
An `Image` represents one image stored in a memorable `Collection`. Images are always associated with exactly one
6+
collection - it is not possible for one image to be a part of two different collections.
7+
8+
## Structure
9+
The `Image` struct holds information about an image relevant to storage and relations. It does not contain other
10+
metadata about the image itself, such as when the photo was taken or other data stored in the image's EXIF tags. These
11+
are instead kept in the `Memorable.Data.Image.DerivedMetadata` table.
12+
13+
## Fields
14+
- `id`: An [ID](`t:Memorable.Util.id/0`) representing the image.
15+
- `collection_id`: The [ID](`t:Memorable.Util.id/0`) of the `Memorable.Data.Collection` the image is a part of.
16+
- `path`: The path to the image file on disk, relative to the folder memorable stores all images in.
17+
- `imported_datetime`: A `DateTime` representing the time at which the image was imported into the memorable database.
18+
"""
19+
@moduledoc since: "1.0.0"
220
@derive {Inspect, except: []}
321
use Memento.Table, attributes: [:id, :collection_id, :path, :imported_datetime]
422

@@ -10,6 +28,15 @@ defmodule Memorable.Data.Image do
1028
}
1129
@type metadata :: %{String.t() => any()}
1230

31+
@doc """
32+
Reads metadata from the image.
33+
34+
To get metadata from the image, memorable calls out to `exiftool` installed on the system to obtain all EXIF tags
35+
stored on the image. See `Subprocess`, or the code in `native/subprocess` for details on how this is done.
36+
37+
Returns a map from EXIF tags to their values, for all tags in the image.
38+
"""
39+
@doc since: "1.0.0"
1340
@spec read_metadata(t()) :: {:ok, metadata()} | {:error, any()}
1441
def read_metadata(%__MODULE__{path: path}) do
1542
with {:file_read, {:ok, data}} <- {:file_read, File.read(path)},
@@ -26,6 +53,32 @@ defmodule Memorable.Data.Image do
2653
end
2754

2855
defmodule Memorable.Data.Image.DerivedMetadata do
56+
@moduledoc """
57+
Metadata associated with images.
58+
59+
Where `Memorable.Data.Image` holds information about the image's internal representation within memorable,
60+
`DerivedMetadata` contains information about the image itself. When an image is imported into memorable,
61+
`from_image/1` is called to extract metadata from EXIF tags, which gets stored in the memorable database for quick
62+
access.
63+
64+
## Fields
65+
- `image_id`: The [ID](`t:Memorable.Util.id/0`) of the `Memorable.Data.Image` this metadata is associated with.
66+
- `file_hash`: A SHA256 hash of the image file this metadata was derived from. This is used to ensure that the
67+
metadata stored in the table is up to date, and matches the image on disk.
68+
69+
The following fields are retrieved from EXIF metadata stored in the image, and may be `nil` if the associated tags are
70+
not present on the image:
71+
- `original_datetime`: A `NaiveDateTime` representing when the image was taken.
72+
- `lens_model`: The lens used to take the photo.
73+
- `body_model`: The camera body used to take the photo.
74+
- `focal_length`: The focal length the image was taken at, in millimetres.
75+
- `aperture`: The aperture the image was taken at, represented as an f-number.
76+
- `exposure_time`: The duration of time the image was exposed for, in seconds expressed as a rational (eg. "1/1250").
77+
- `iso`: The ISO sensitivity the image was taken at.
78+
"""
79+
@moduledoc since: "1.0.0"
80+
alias Memorable.Data.Image.DerivedMetadata
81+
alias Memorable.Data.Image.DerivedMetadata
2982
alias Memorable.Data.Image
3083

3184
# `image_id` is 1:1 with an Image `id`
@@ -54,6 +107,18 @@ defmodule Memorable.Data.Image.DerivedMetadata do
54107
iso: integer()
55108
}
56109

110+
@doc """
111+
Reads and parses metadata for a `Memorable.Data.Image`.
112+
113+
EXIF tags from an image file are retrieved with `Memorable.Data.Image.read_metadata/1`, and then parsed into the
114+
format described above.
115+
116+
Returns:
117+
- `{:ok, metadata}`: When metadata was successfully read and parsed from the image.
118+
- `{:error, {:read_file, error}}`: When an error occured reading the image file from disk.
119+
- `{:error, {:read_metadata, error}}`: When an error occurred while extracting EXIF tags from the image file.
120+
"""
121+
@doc since: "1.0.0"
57122
@spec from_image(Image.t()) :: {:ok, t()} | {:error, any()}
58123
def from_image(%Image{id: image_id, path: path} = image) do
59124
with {:read_file, {:ok, data}} <- {:read_file, File.read(path)},
@@ -79,13 +144,16 @@ defmodule Memorable.Data.Image.DerivedMetadata do
79144
end
80145
end
81146

147+
# Parses the `DateTimeOriginal` field in EXIF into a `NaiveDateTime`.
82148
@spec original_datetime(Image.metadata()) :: NaiveDateTime.t() | nil
83149
defp original_datetime(metadata) do
84150
with result when result != nil <- Map.get(metadata, "DateTimeOriginal") do
85151
NaiveDateTime.from_iso8601!(result)
86152
end
87153
end
88154

155+
# Parses the `FocalLength` field in EXIF into a float, discarding the unit measurement.
156+
# Currently assumes `exiftool` always returns focal length in millimetres, and fails if it doesn't.
89157
@spec focal_length(Image.metadata()) :: float() | nil
90158
defp focal_length(metadata) do
91159
with result when result != nil <- Map.get(metadata, "FocalLength") do

0 commit comments

Comments
 (0)