{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://openmusiccollection.org/schema/core-1.0.schema.json",
  "title": "Open Music Collection Core 1.0: one record (one line of a collection file)",
  "description": "Validates one line of an .omc.ndjson file. Unknown members are allowed everywhere: the core requires hosts to keep them. Optional fields accept null, because the core says an absent field and a null field mean the same.",
  "type": "object",
  "required": ["type"],
  "properties": { "type": { "type": "string" } },
  "allOf": [
    {
      "if": { "properties": { "type": { "const": "omc" } } },
      "then": { "$ref": "#/$defs/header" }
    },
    {
      "if": { "properties": { "type": { "const": "source" } } },
      "then": { "$ref": "#/$defs/source" }
    },
    {
      "if": { "properties": { "type": { "const": "item" } } },
      "then": { "$ref": "#/$defs/item" }
    },
    {
      "if": { "properties": { "type": { "const": "list" } } },
      "then": { "$ref": "#/$defs/list" }
    },
    {
      "if": { "properties": { "type": { "not": { "enum": ["omc", "source", "item", "list"] } } } },
      "then": { "$ref": "#/$defs/moduleRecord" }
    }
  ],
  "$defs": {
    "id": { "type": "string", "minLength": 1, "maxLength": 256 },
    "optId": { "type": ["string", "null"], "minLength": 1, "maxLength": 256 },
    "timestamp": { "type": "string", "format": "date-time", "pattern": "([Zz]|\\+00:00)$", "description": "RFC 3339, in UTC" },
    "optTimestamp": { "type": ["string", "null"], "format": "date-time", "pattern": "([Zz]|\\+00:00)$" },
    "optPartialDate": { "type": ["string", "null"], "pattern": "^[0-9]{4}(-(0[1-9]|1[0-2])(-(0[1-9]|[12][0-9]|3[01]))?)?$" },
    "optWebUrl": { "type": ["string", "null"], "format": "uri", "pattern": "^https?://" },
    "optString": { "type": ["string", "null"] },
    "namespace": { "type": "string", "pattern": "^[a-z0-9-]+(\\.[a-z0-9-]+)+$" },
    "optExt": { "type": ["object", "null"], "description": "Extensions, keyed by reverse-domain namespace.", "propertyNames": { "$ref": "#/$defs/namespace" } },
    "optStrings": { "type": ["array", "null"], "items": { "type": "string", "minLength": 1 } },
    "optHash": { "type": ["object", "null"], "required": ["algorithm", "value"], "properties": { "algorithm": { "type": "string", "minLength": 1 }, "value": { "type": "string", "minLength": 1 } } },
    "optApp": { "type": ["object", "null"], "required": ["name"], "properties": { "name": { "type": "string", "minLength": 1 }, "url": { "$ref": "#/$defs/optWebUrl" } } },
    "header": {
      "type": "object",
      "required": ["type", "version", "generated_at"],
      "properties": {
        "type": { "const": "omc" },
        "version": { "type": "string", "pattern": "^1\\.[0-9]+$" },
        "generated_at": { "$ref": "#/$defs/timestamp" },
        "name": { "$ref": "#/$defs/optString" },
        "generator": { "$ref": "#/$defs/optApp" },
        "counts": {
          "type": ["object", "null"],
          "additionalProperties": { "type": ["integer", "null"], "minimum": 0 },
          "description": "sources, items, recordings, releases, lists: a hint for showing progress"
        },
        "modules": {
          "type": ["object", "null"],
          "propertyNames": { "$ref": "#/$defs/namespace" },
          "additionalProperties": { "type": ["string", "null"] }
        },
        "ext": { "$ref": "#/$defs/optExt" }
      }
    },
    "source": {
      "type": "object",
      "required": ["type", "id", "platform", "name"],
      "properties": {
        "type": { "const": "source" },
        "id": { "$ref": "#/$defs/id" },
        "platform": { "type": "string", "pattern": "^[a-z0-9_]+$|^[a-z0-9-]+(\\.[a-z0-9-]+)+$" },
        "name": { "type": "string", "minLength": 1 },
        "account": { "$ref": "#/$defs/optString", "description": "The platform's stable id for the person's account" },
        "account_name": { "$ref": "#/$defs/optString" },
        "descriptor": { "type": ["string", "null"], "format": "uri", "pattern": "^https://" },
        "ext": { "$ref": "#/$defs/optExt" }
      },
      "if": { "required": ["platform"], "properties": { "platform": { "type": "string", "pattern": "\\." } } },
      "then": {
        "description": "A host-name platform is not a registered name, so it needs a descriptor.",
        "required": ["descriptor"],
        "properties": { "descriptor": { "type": "string" } }
      }
    },
    "item": {
      "type": "object",
      "required": ["type", "id", "kind", "title", "artists", "copies"],
      "properties": {
        "type": { "const": "item" },
        "id": { "$ref": "#/$defs/id" },
        "kind": { "type": "string", "minLength": 1, "description": "recording or release; unknown kinds are displayed as recording" },
        "title": { "type": "string", "description": "May be empty when the source has none; consumers show a fallback" },
        "artists": { "type": "array", "items": { "type": "string", "minLength": 1 } },
        "album": { "$ref": "#/$defs/optString" },
        "duration_ms": { "type": ["integer", "null"], "minimum": 0 },
        "release_date": { "$ref": "#/$defs/optPartialDate" },
        "label": { "$ref": "#/$defs/optString" },
        "composers": { "$ref": "#/$defs/optStrings", "description": "For a recording: who wrote it" },
        "album_artists": { "$ref": "#/$defs/optStrings", "description": "For a recording: the release's artists, as display text next to album" },
        "release_type": { "type": ["string", "null"], "minLength": 1, "description": "For a release: album, single, ep, compilation, or another value (open list)" },
        "catalog_number": { "$ref": "#/$defs/optString" },
        "country": { "type": ["string", "null"], "pattern": "^[A-Z]{2}$", "description": "ISO 3166-1 alpha-2, or XW for worldwide" },
        "genres": { "type": ["array", "null"], "items": { "type": "string" } },
        "bpm": { "type": ["number", "null"], "exclusiveMinimum": 0 },
        "key": { "type": ["string", "null"], "pattern": "^(?:[A-G][b#]?m?|o)$", "description": "ID3v2.4 TKEY: a note A-G, then b or #, then m for minor (Am, F#, Dbm), or o for off-key" },
        "artwork_url": { "$ref": "#/$defs/optWebUrl" },
        "ids": {
          "type": ["object", "null"],
          "properties": {
            "isrc": { "type": ["string", "null"], "pattern": "^[A-Z]{2}[A-Z0-9]{3}[0-9]{7}$" },
            "upc": { "type": ["string", "null"], "pattern": "^[0-9]{12,14}$" },
            "musicbrainz": { "type": ["string", "null"], "format": "uuid" },
            "iswc": { "type": ["string", "null"], "pattern": "^T[0-9]{10}$" }
          },
          "additionalProperties": { "type": ["string", "null"] }
        },
        "rating": { "type": ["integer", "null"], "minimum": 1, "maximum": 5 },
        "note": { "$ref": "#/$defs/optString", "description": "The person's own text about the item" },
        "added_at": { "$ref": "#/$defs/optTimestamp" },
        "play_count": { "type": ["integer", "null"], "minimum": 0, "description": "Derived by the home from listens; read-only" },
        "last_played_at": { "$ref": "#/$defs/optTimestamp", "description": "Derived by the home from listens; read-only" },
        "copies": { "type": "array", "items": { "$ref": "#/$defs/copy" } },
        "provenance": { "$ref": "#/$defs/provenance" },
        "via": { "$ref": "#/$defs/optApp" },
        "ext": { "$ref": "#/$defs/optExt" }
      }
    },
    "copy": {
      "type": "object",
      "required": ["source", "ref"],
      "properties": {
        "source": { "$ref": "#/$defs/id" },
        "ref": { "type": "string", "minLength": 1 },
        "url": { "$ref": "#/$defs/optWebUrl" },
        "ownership": {
          "type": ["string", "null"],
          "minLength": 1,
          "description": "none (default), digital, physical or file, or a namespaced value of an app's own. An open list: unknown values are kept and shown as none."
        },
        "release": { "type": ["string", "null"], "minLength": 1, "description": "The id of the release item this copy comes from" },
        "track_number": { "type": ["integer", "null"], "minimum": 1 },
        "disc_number": { "type": ["integer", "null"], "minimum": 1 },
        "position": { "type": ["string", "null"], "minLength": 1, "description": "The position as printed on the release: A1, 2-03" },
        "file": {
          "type": ["object", "null"],
          "properties": {
            "mime": { "$ref": "#/$defs/optString" },
            "size_bytes": { "type": ["integer", "null"], "minimum": 0 },
            "sample_rate_hz": { "type": ["integer", "null"], "minimum": 1 },
            "bit_depth": { "type": ["integer", "null"], "minimum": 1 },
            "bitrate_kbps": { "type": ["number", "null"], "exclusiveMinimum": 0 },
            "audio_hash": { "$ref": "#/$defs/optHash", "description": "A hash of the audio data only, without tags" },
            "fingerprint": { "$ref": "#/$defs/optHash", "description": "An acoustic fingerprint, such as chromaprint" }
          }
        },
        "format": { "type": ["string", "null"], "minLength": 1, "description": "vinyl, cd, cassette, minidisc, dvd, reel, or another value (open list)" },
        "format_details": { "$ref": "#/$defs/optString" },
        "condition": { "type": ["object", "null"], "properties": { "media": { "$ref": "#/$defs/optString" }, "sleeve": { "$ref": "#/$defs/optString" } } },
        "location": { "$ref": "#/$defs/optString" },
        "added_at": { "$ref": "#/$defs/optTimestamp" },
        "provenance": { "$ref": "#/$defs/provenance" },
        "ext": { "$ref": "#/$defs/optExt" }
      }
    },
    "list": {
      "type": "object",
      "required": ["type", "id", "name", "ordered", "entries"],
      "properties": {
        "type": { "const": "list" },
        "id": { "$ref": "#/$defs/id" },
        "name": { "type": "string", "minLength": 1 },
        "description": { "$ref": "#/$defs/optString" },
        "ordered": { "type": "boolean" },
        "role": { "type": ["string", "null"], "description": "library, saved, wanted, curated or history; unknown roles (including namespaced ones) are treated as curated" },
        "source": { "$ref": "#/$defs/optId" },
        "ref": { "type": ["string", "null"], "minLength": 1, "description": "The source's own id for the list" },
        "folder": { "type": ["boolean", "null"], "description": "A folder: holds lists, no entries" },
        "parent": { "$ref": "#/$defs/optId", "description": "The id of the folder this list is in" },
        "position": { "type": ["number", "null"], "description": "Where the list sorts among the lists in its folder" },
        "color": { "type": ["string", "null"], "pattern": "^#[0-9A-Fa-f]{6}$" },
        "filter": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/filter" }] },
        "entries": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["item"],
            "properties": {
              "item": { "$ref": "#/$defs/id" },
              "ref": { "type": ["string", "null"], "minLength": 1, "description": "On a list that lives on a source: the ref of the item's copy this entry is there" },
              "added_at": { "$ref": "#/$defs/optTimestamp" },
              "note": { "$ref": "#/$defs/optString", "description": "The person's text about the item in this list" },
              "provenance": { "$ref": "#/$defs/provenance" },
              "ext": { "$ref": "#/$defs/optExt" }
            }
          }
        },
        "created_at": { "$ref": "#/$defs/optTimestamp" },
        "ext": { "$ref": "#/$defs/optExt" }
      },
      "if": { "required": ["folder"], "properties": { "folder": { "const": true } } },
      "then": {
        "description": "A folder holds lists, not items.",
        "properties": { "entries": { "maxItems": 0 }, "ordered": { "const": false } }
      }
    },
    "filter": {
      "description": "A smart list's rule: a group of rules, with optional sort and limit.",
      "allOf": [{ "$ref": "#/$defs/filterGroup" }],
      "properties": {
        "sort": { "type": ["array", "null"], "items": { "type": "object", "required": ["field"], "properties": { "field": { "type": "string", "minLength": 1 }, "order": { "enum": ["asc", "desc", null] } } } },
        "limit": { "type": ["integer", "null"], "minimum": 1 }
      }
    },
    "filterGroup": {
      "type": "object",
      "required": ["match", "rules"],
      "properties": {
        "match": { "enum": ["all", "any"] },
        "rules": { "type": "array", "items": { "anyOf": [{ "$ref": "#/$defs/filterGroup" }, { "$ref": "#/$defs/filterRule" }] } }
      }
    },
    "filterRule": {
      "type": "object",
      "required": ["field", "op"],
      "properties": { "field": { "type": "string", "minLength": 1 }, "op": { "type": "string", "minLength": 1 }, "value": {} }
    },
    "moduleRecord": { "type": "object", "required": ["type"], "properties": { "type": { "type": "string", "pattern": "^[a-z0-9-]+(\\.[a-z0-9-]+)+/[a-z0-9_-]+$" } } },
    "provenance": {
      "type": ["object", "null"],
      "description": "Where values came from, as a map from field name to origin.",
      "additionalProperties": {
        "type": ["object", "null"],
        "required": ["origin"],
        "properties": {
          "origin": {
            "type": "string",
            "minLength": 1,
            "description": "embedded, platform, analysis, catalog, user, estimated or inferred; unknown origins (including namespaced ones) are kept"
          },
          "by": { "$ref": "#/$defs/optString" }
        }
      }
    }
  }
}
