# Media

The media types describe images, video, audio and captions: an image's
size, a frame rate, a timecode, a subtitle cue, a film's age rating.
Each follows its own standard. As every standard type does, each is read
only in its canonical text. None is ordered, and none is personal data.

Here a video library makes thumbnails, reads a film's age rating, shows
where an edit starts and writes captions:

```jadpo
type FormText = Text {}

type NotARating = Failure {
    kind: FailureKind.invalid_value
}

/// A still's thumbnail: scaled to fit a 320-pixel square, never enlarged.
function thumbnail(video_still: ImageDimensions) -> ImageDimensions {
    var tile: ImageDimensions = ImageDimensions {
        width: 320
        height: 320
    }
    return media.fit(video_still, within: tile)
}

/// The age rating a film's form names.
function film_rating(raw: FormText) fails NotARating -> ContentRating {
    var typed_rating: ContentRating = attempt text.parse(raw, as: ContentRating) {
        malformed: NotARating
        rating_of_scheme: NotARating
    }
    return typed_rating
}

/// Where an edit starts, as the edit list shows it.
function edit_start(edit_point: Timecode) -> PresentationText {
    return text.format(edit_point, locale: Locale.en_gb)
}

/// A caption as a WebVTT file writes it.
function caption_text(opening_caption: SubtitleCue) -> FormText {
    return FormText(text.from(opening_caption))
}
```

```jadpo
test "a thumbnail fits its tile and is never enlarged" {
    var landscape: ImageDimensions = ImageDimensions {
        width: 4000
        height: 3000
    }
    var wide_still: ImageDimensions = ImageDimensions {
        width: 1000
        height: 333
    }
    var icon: ImageDimensions = ImageDimensions {
        width: 200
        height: 100
    }
    assert text.from(thumbnail(landscape)) == "320x240"
    assert text.from(thumbnail(wide_still)) == "320x106"
    assert thumbnail(icon) == icon
}

test "a rating is one of its body's categories" {
    var given_rating: ContentRating = attempt film_rating("BBFC 12A")
    assert given_rating.scheme == ContentRatingScheme.bbfc
    assert film_rating("BBFC PG-13") fails NotARating
    assert film_rating("12A") fails NotARating
}

test "a timecode is shown in SMPTE's notation, a cue as WebVTT writes it" {
    var edit_point: Timecode = Timecode {
        hours: 1
        minutes: 10
        seconds: 0
        frames: 0
        rate: FrameRate {
            numerator: 30000
            denominator: 1001
        }
        drop_frame: true
    }
    assert edit_start(edit_point) == "01:10:00;00"
    var opening_caption: SubtitleCue = SubtitleCue {
        start: 1s
        end: 4s
        text: "Mind the gap."
    }
    assert caption_text(opening_caption) == "00:00:01.000 --> 00:00:04.000\nMind the gap."
}
```

## Fitting an image into a box

| Operation | Gives | Fails | SQL form |
|---|---|---|---|
| `media.fit(dimensions, within:)` | `ImageDimensions` scaled to fit inside `within`, keeping the aspect ratio, never larger than the original, rounded down to whole pixels and never below 1 | | none |

`media.fit` works as CSS's `object-fit: scale-down` does, in whole
numbers. The side that limits the fit becomes exactly the box's size. The
other side is scaled with it and rounded down, never below 1. An image
that already fits is kept as it is, never enlarged.

## Sizes, ratios and frame rates

- An `ImageDimensions` and a `VideoResolution` are a `width` and a
  `height` in whole pixels, from 1 to 2,147,483,647. That is PNG's limit,
  the largest of the common formats'. Each is written `1920x1080`, width
  first, with a lower-case `x`. Each is sent as named fields, so nothing
  reads them the wrong way round, and stored as a column each.
- A `VideoResolution` has the same shape under its own name. It names a
  resolution by its pixels, never by a name like "4K", which means both
  3840 and 4096 pixels across.
- An `AspectRatio` is a `width` and a `height`, whole numbers from 1,
  written `16:9`. It is held as written, as a `Ratio` is, so `16:9` and
  `32:18` are two different values. It is sent as its text and stored as
  a column each. A cinema's 2.39:1 is written `239:100`. A later release
  can accept decimals without changing any value.
- A `FrameRate` is a `numerator` and a `denominator`, each from 1 to
  2,147,483,647, written `30000/1001` or `25/1`. The denominator is always
  written, and the rate is held as written. It is sent as its text and
  stored as a column each.

## Boxes, paper and orientation

- A `Rect` is an `x` and a `y`, each any `Decimal`, and a `width` and a
  `height`, never below 0. Its text is SVG's `viewBox`: `0 0 300 200`.
- An `Insets` is a `top`, `right`, `bottom` and `left`, each never below
  0, so a negative margin is refused. Its text is the CSS box model's four
  sides: `8 16 8 16`.
- In both texts each number keeps the places it was given, has no leading
  zero, and is one space from the next, and every part is always written.
  Each is sent as named fields and stored as a column per part.
- A `PaperSize` is one of ISO 216's A0 to A10 and B0 to B10, or ANSI/ASME
  Y14.1's letter, legal and tabloid, coded `A4` or `Letter`. ISO 269's
  envelope sizes and ledger (tabloid on its side) are left out.
- An `ExifOrientation` says how a stored image must be turned to show it
  upright. Its variants are named for where the stored image's first row
  and column are seen, as libtiff names TIFF 6.0's values: `top_left` to
  `left_bottom`. They are coded `1` to `8` and sent as that text, as every
  enum is sent as its code.
- An `AltText` is one line of an image's text alternative, as WCAG
  describes it: 1 to 1,000 characters, with no space at either end. An
  image with nothing to say has `none` in a field that may be empty, never
  empty text.

## Codecs

A `VideoCodec` and an `AudioCodec` are the codec strings the W3C
WebCodecs Codec Registry lists, in RFC 6381's form, such as `avc1.42001E`
or `opus`:

- video: `av01`, `avc1`, `avc3`, `hev1`, `hvc1` and `vp09`, each with its
  elements, and `vp8`;
- audio: `mp4a` with its elements, `flac`, `mp3`, `opus`, `vorbis`,
  `ulaw`, `alaw` and the five `pcm-` forms.

The elements after the first aren't checked further. Case is kept, as the
registry writes them.

## Timecodes and subtitles

A `Timecode` is a position in a video as SMPTE ST 12-1 writes it:
`hours` (0 to 23), `minutes` and `seconds` (0 to 59) and `frames` (0 to
119, room for 120 frames a second), with its `rate` and whether it counts
`drop_frame`. Drop-frame timecode skips some frame labels so that the
clock keeps time with a 29.97 frames-a-second video. A timecode has three
rules:

- `frames_within_rate`: its frames are below its rate's frames a second,
  rounded up to a whole number;
- `drop_frame_rate`: drop-frame is used only at 30000/1001 and 60000/1001
  frames a second, compared by value;
- `frame_not_dropped`: a drop-frame timecode never names a label that
  drop-frame skips, frames 0 and 1 (or 0 to 3 at the higher rate) at the
  start of each minute except every tenth.

Its notation, `01:00:00;00`, doesn't say its rate, so a timecode has no
single text form. It is sent as its parts, stored as a column per part
(its rate in two), and shown in that notation.

A `SubtitleCue` is a WebVTT cue: a `start` and an `end`, each a
`Duration` from the start of the media to the millisecond, and its
`text`, lines with no control character and no blank line. Its rules are
`start_not_negative`, `end_after_start` and `text_without_arrow`, which
refuses `-->` in the text. Its canonical text is the cue's timings line,
its hours in two digits or more, then a line feed and its text, with no
identifier or settings: `00:00:01.000 --> 00:00:04.000` and its text. It
is sent as named fields and stored as a column per part.

When you write a `Timecode` or a `SubtitleCue` out as a literal, its rules
are checked while compiling. A value read from JSON is checked as it is
read. One built in code from other values is built with `attempt`, which
fails by the rule it breaks.

## Age ratings

A `ContentRating` is a `scheme` and a `rating`, one of that body's
categories as it writes them, its rule `rating_of_scheme`. It is written
`BBFC 12A` or `PEGI 16`, the body's code read ignoring case. It is sent
as that text and stored as a column each.

The bodies are a standard enum, `ContentRatingScheme`:

- BBFC: U, PG, 12A, 12, 15, 18, R18;
- the MPA, the MPAA's name since 2019: G, PG, PG-13, R, NC-17;
- PEGI: 3, 7, 12, 16, 18;
- ESRB: E, E10+, T, M, AO, RP.

Other bodies, such as USK, the ACB and US television's, come with a
later release.
