diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..4d7a7f2 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,49 @@ +name: Deploy to GitHub Pages + +on: + push: + pull_request: + branches: [main] + +permissions: + pages: write + id-token: write + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: pip install check-jsonschema + + - name: Validate examples + run: check-jsonschema --schemafile schema.json examples/*/data.json + + deploy: + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + needs: validate + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: pip install json-schema-for-humans + + - name: Build site + run: | + mkdir build + generate-schema-doc schema.json build/index.html + cp schema.json build/ + cp -r examples/*/ build/ + + - uses: actions/upload-pages-artifact@v3 + with: + path: build/ + + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 1a1fdf7..4b21047 100644 --- a/README.md +++ b/README.md @@ -1,161 +1,16 @@ # SmartCompanion Data Format -This repository holds a specification of the data format for SmartCompanion apps. In addition examples are provided to demonstrate app features. +This repository holds a specification of the data format for SmartCompanion apps. In addition examples are provided to demonstrate app features. The specification is defined within the provided `schema.json` file as a JSON Schema. ## Examples - - AI-generated multilanguage tour of animal stations - - German language audiotour through the town "Bruck an der Großglocknerstraße" © by Leon Schwaiger + - [AI-generated multilanguage tour of animal stations](https://smartcompanion-app.github.io/data-format/animals/data.json) + - [German language audiotour through the town "Bruck an der Großglocknerstraße"](https://smartcompanion-app.github.io/data-format/leon/data.json) © by Leon Schwaiger ## Specification -### Station +A human-readable documentation of the JSON Schema can be found [here](https://smartcompanion-app.github.io/data-format). -A station represents a specific point of interest within a tour or guide. +## Inspiration -| Name | Datatype | Description | Mandatory | -|---|---|---|---| -| `id` | `string` | A unique identifier for a station, this is the same for all translations | ✅ | -| `language` | `string` | The language code of the station entry | ✅ | -| `title` | `string` | The title of the station | ✅ | -| `subtitle` | `string` | The subtitle of the station | | -| `number` | `string` | The station number, usable for identifaction of a station by humans | ✅ | -| `description` | `string` | A textual description of the station | | -| `latitude` | `number` | The latitude of the station to display on a map | | -| `longitude` | `number` | The longitude of the station to display on a map | | -| `images` | `Array` | An array of asset identifiers to reference images of the station | ✅ | -| `audios` | `Array` | An array of asset identifiers to reference audio files corresponding to the station | | - -#### Example - -```json -{ - "id": "s1", - "language": "de", - "number": "1", - "title": "Bär", - "subtitle": "", - "audios": [ - "a1de" - ], - "images": [ - "i11", - "i12" - ] -} -``` - -### Asset - -An asset represents a piece of media or content used within the application, such as an audio file, video, or other downloadable resource. - -| Name | Datatype | Description | Mandatory | -|---|---|---|---| -| `id` | `string` | A unique identifier of an asset, this is the same for all translations | ✅ | -| `language` | `string` | The language code of the asset, if translations are available | | -| `title` | `string` | The title of an asset, e.g. the title of an audio | | -| `duration` | `number` | The duration in seconds of an audio or video | | -| `filename` | `string` | The filename of the asset, without a path | ✅ | -| `externalUrl` | `string` | The URL of the asset file for download | ✅ | - -#### Example - -```json -{ - "id": "a3en", - "filename": "a3en.mp3", - "externalUrl": "https://smartcompanion-app.github.io/sample-data/animals/assets/a3en.mp3", - "language": "en" -} -``` - -### Text - -All texts in the app can be translated into any of the supported languages. Within the app’s code, only keys are used as identifiers for text snippets. These keys are replaced with the appropriate text based on the user’s selected language. - -| Name | Datatype | Description | Mandatory | -|---|---|---|---| -| `language` | `string` | The language code of the translation | ✅ | -| `kev` | `string` | The key of the text, as used inside the apps | ✅ | -| `value` | `number` | The human readable text, as identified by language and key | ✅ | - -#### Example - -```json -{ - "language": "en", - "key": "menu-language", - "value": "Change language" -} -``` - -### Language - -Specifies the languages in which the guide is available. Users can select their preferred language. - -| Name | Datatype | Description | Mandatory | -|---|---|---|---| -| `title` | `string` | The title of the language, e.g., English, Deutsch, Italiano, ... | ✅ | -| `language` | `string` | The language code, e.g., `en`, `de`, `it`, ... | ✅ | - -#### Example - -```json -{ - "title": "English", - "language": "en" -} -``` - -### Pin - -A string array containing four-digit PIN codes. This provides a simple mechanism that allows the operator to require visitors to enter a PIN— for example, a PIN that is issued only upon payment of a fee. - -#### Example - -```json -["1234", "5577"] -``` - -### Tour - -By including tours with designated stations in the audio guide, visitors would receive clearer guidance and enjoy a more structured, informative experience. - -| Name | Datatype | Description | Mandatory | -|---|---|---|---| -| `id` | `string` | A unique identifier of the tour, this is the same for all translations | ✅ | -| `title` | `string` | The title of the tour | ✅ | -| `language` | `string` | The language code, e.g., `en`, `de`, `it`, ... | ✅ | -| `default` | `boolean` | One tour is defined as the default tour | ✅ | -| `number` | `string` | A tour could have a number as an identifier for humans | | -| `description` | `string` | A tour can have a description | | -| `stations` | `Array` | An array of ordered station numbers to list all stations | ✅ | -| `images` | `Array` | An array of asset identifiers to relate images to the tour | ✅ | - -#### Example - -```json -{ - "language": "en", - "id": "tour1", - "number": "1", - "title": "Historic city walk", - "description": "Discover the city in a scenic walk with explanations", - "duration": "60 minutes", - "stations": [ - "1", - "2", - "3", - "4", - "5" - ], - "images": [ - "image123" - ], - "default": true -} -``` - -### Server - -An array of strings of IP addresses to identify servers to download data within a internal Wifi instead of the internet. This would enhance the download speed of visitors and reduce the outbound internet traffic. + - [XMLGuide](https://dl.acm.org/doi/abs/10.1145/1967486.1967549) diff --git a/animals/assets/a1de.mp3 b/examples/animals/assets/a1de.mp3 similarity index 100% rename from animals/assets/a1de.mp3 rename to examples/animals/assets/a1de.mp3 diff --git a/animals/assets/a1en.mp3 b/examples/animals/assets/a1en.mp3 similarity index 100% rename from animals/assets/a1en.mp3 rename to examples/animals/assets/a1en.mp3 diff --git a/animals/assets/a1es.mp3 b/examples/animals/assets/a1es.mp3 similarity index 100% rename from animals/assets/a1es.mp3 rename to examples/animals/assets/a1es.mp3 diff --git a/animals/assets/a2de.mp3 b/examples/animals/assets/a2de.mp3 similarity index 100% rename from animals/assets/a2de.mp3 rename to examples/animals/assets/a2de.mp3 diff --git a/animals/assets/a2en.mp3 b/examples/animals/assets/a2en.mp3 similarity index 100% rename from animals/assets/a2en.mp3 rename to examples/animals/assets/a2en.mp3 diff --git a/animals/assets/a2es.mp3 b/examples/animals/assets/a2es.mp3 similarity index 100% rename from animals/assets/a2es.mp3 rename to examples/animals/assets/a2es.mp3 diff --git a/animals/assets/a3de.mp3 b/examples/animals/assets/a3de.mp3 similarity index 100% rename from animals/assets/a3de.mp3 rename to examples/animals/assets/a3de.mp3 diff --git a/animals/assets/a3en.mp3 b/examples/animals/assets/a3en.mp3 similarity index 100% rename from animals/assets/a3en.mp3 rename to examples/animals/assets/a3en.mp3 diff --git a/animals/assets/a3es.mp3 b/examples/animals/assets/a3es.mp3 similarity index 100% rename from animals/assets/a3es.mp3 rename to examples/animals/assets/a3es.mp3 diff --git a/animals/assets/i11.png b/examples/animals/assets/i11.png similarity index 100% rename from animals/assets/i11.png rename to examples/animals/assets/i11.png diff --git a/animals/assets/i12.png b/examples/animals/assets/i12.png similarity index 100% rename from animals/assets/i12.png rename to examples/animals/assets/i12.png diff --git a/animals/assets/i21.png b/examples/animals/assets/i21.png similarity index 100% rename from animals/assets/i21.png rename to examples/animals/assets/i21.png diff --git a/animals/assets/i22.png b/examples/animals/assets/i22.png similarity index 100% rename from animals/assets/i22.png rename to examples/animals/assets/i22.png diff --git a/animals/assets/i31.png b/examples/animals/assets/i31.png similarity index 100% rename from animals/assets/i31.png rename to examples/animals/assets/i31.png diff --git a/animals/assets/i32.png b/examples/animals/assets/i32.png similarity index 100% rename from animals/assets/i32.png rename to examples/animals/assets/i32.png diff --git a/animals/data.json b/examples/animals/data.json similarity index 100% rename from animals/data.json rename to examples/animals/data.json diff --git a/leon/assets/a10de.mp3 b/examples/leon/assets/a10de.mp3 similarity index 100% rename from leon/assets/a10de.mp3 rename to examples/leon/assets/a10de.mp3 diff --git a/leon/assets/a1de.mp3 b/examples/leon/assets/a1de.mp3 similarity index 100% rename from leon/assets/a1de.mp3 rename to examples/leon/assets/a1de.mp3 diff --git a/leon/assets/a2de.mp3 b/examples/leon/assets/a2de.mp3 similarity index 100% rename from leon/assets/a2de.mp3 rename to examples/leon/assets/a2de.mp3 diff --git a/leon/assets/a3de.mp3 b/examples/leon/assets/a3de.mp3 similarity index 100% rename from leon/assets/a3de.mp3 rename to examples/leon/assets/a3de.mp3 diff --git a/leon/assets/a4de.mp3 b/examples/leon/assets/a4de.mp3 similarity index 100% rename from leon/assets/a4de.mp3 rename to examples/leon/assets/a4de.mp3 diff --git a/leon/assets/a5de.mp3 b/examples/leon/assets/a5de.mp3 similarity index 100% rename from leon/assets/a5de.mp3 rename to examples/leon/assets/a5de.mp3 diff --git a/leon/assets/a6de.mp3 b/examples/leon/assets/a6de.mp3 similarity index 100% rename from leon/assets/a6de.mp3 rename to examples/leon/assets/a6de.mp3 diff --git a/leon/assets/a7de.mp3 b/examples/leon/assets/a7de.mp3 similarity index 100% rename from leon/assets/a7de.mp3 rename to examples/leon/assets/a7de.mp3 diff --git a/leon/assets/a8de.mp3 b/examples/leon/assets/a8de.mp3 similarity index 100% rename from leon/assets/a8de.mp3 rename to examples/leon/assets/a8de.mp3 diff --git a/leon/assets/a9de.mp3 b/examples/leon/assets/a9de.mp3 similarity index 100% rename from leon/assets/a9de.mp3 rename to examples/leon/assets/a9de.mp3 diff --git a/leon/assets/i101.png b/examples/leon/assets/i101.png similarity index 100% rename from leon/assets/i101.png rename to examples/leon/assets/i101.png diff --git a/leon/assets/i102.png b/examples/leon/assets/i102.png similarity index 100% rename from leon/assets/i102.png rename to examples/leon/assets/i102.png diff --git a/leon/assets/i11.png b/examples/leon/assets/i11.png similarity index 100% rename from leon/assets/i11.png rename to examples/leon/assets/i11.png diff --git a/leon/assets/i12.png b/examples/leon/assets/i12.png similarity index 100% rename from leon/assets/i12.png rename to examples/leon/assets/i12.png diff --git a/leon/assets/i21.png b/examples/leon/assets/i21.png similarity index 100% rename from leon/assets/i21.png rename to examples/leon/assets/i21.png diff --git a/leon/assets/i22.png b/examples/leon/assets/i22.png similarity index 100% rename from leon/assets/i22.png rename to examples/leon/assets/i22.png diff --git a/leon/assets/i31.png b/examples/leon/assets/i31.png similarity index 100% rename from leon/assets/i31.png rename to examples/leon/assets/i31.png diff --git a/leon/assets/i32.png b/examples/leon/assets/i32.png similarity index 100% rename from leon/assets/i32.png rename to examples/leon/assets/i32.png diff --git a/leon/assets/i41.png b/examples/leon/assets/i41.png similarity index 100% rename from leon/assets/i41.png rename to examples/leon/assets/i41.png diff --git a/leon/assets/i42.png b/examples/leon/assets/i42.png similarity index 100% rename from leon/assets/i42.png rename to examples/leon/assets/i42.png diff --git a/leon/assets/i51.png b/examples/leon/assets/i51.png similarity index 100% rename from leon/assets/i51.png rename to examples/leon/assets/i51.png diff --git a/leon/assets/i52.png b/examples/leon/assets/i52.png similarity index 100% rename from leon/assets/i52.png rename to examples/leon/assets/i52.png diff --git a/leon/assets/i61.png b/examples/leon/assets/i61.png similarity index 100% rename from leon/assets/i61.png rename to examples/leon/assets/i61.png diff --git a/leon/assets/i62.png b/examples/leon/assets/i62.png similarity index 100% rename from leon/assets/i62.png rename to examples/leon/assets/i62.png diff --git a/leon/assets/i71.png b/examples/leon/assets/i71.png similarity index 100% rename from leon/assets/i71.png rename to examples/leon/assets/i71.png diff --git a/leon/assets/i72.png b/examples/leon/assets/i72.png similarity index 100% rename from leon/assets/i72.png rename to examples/leon/assets/i72.png diff --git a/leon/assets/i81.png b/examples/leon/assets/i81.png similarity index 100% rename from leon/assets/i81.png rename to examples/leon/assets/i81.png diff --git a/leon/assets/i82.png b/examples/leon/assets/i82.png similarity index 100% rename from leon/assets/i82.png rename to examples/leon/assets/i82.png diff --git a/leon/assets/i91.png b/examples/leon/assets/i91.png similarity index 100% rename from leon/assets/i91.png rename to examples/leon/assets/i91.png diff --git a/leon/assets/i92.png b/examples/leon/assets/i92.png similarity index 100% rename from leon/assets/i92.png rename to examples/leon/assets/i92.png diff --git a/leon/data.json b/examples/leon/data.json similarity index 100% rename from leon/data.json rename to examples/leon/data.json diff --git a/schema.json b/schema.json new file mode 100644 index 0000000..2ef83c4 --- /dev/null +++ b/schema.json @@ -0,0 +1,254 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://smartcompanion-app.github.io/data-format/data.schema.json", + "title": "SmartCompanion Audioguide Data", + "description": "Schema for the data.json file used by the SmartCompanion Audioguide App. This file defines all content for an audioguide: assets (images and audio files), stations (points of interest), languages (available translations), texts (UI translations), and optionally tours, pins, and server configuration.", + "type": "object", + "required": ["checksum", "assets", "stations", "languages", "texts"], + "properties": { + "checksum": { + "type": "string", + "description": "An arbitrary checksum string used to detect data changes and trigger cache invalidation. Update this value whenever the data changes." + }, + "assets": { + "type": "array", + "description": "List of all media assets (images, audio files, videos) referenced by stations and tours. Each asset must have a unique id.", + "items": { + "$ref": "#/$defs/Asset" + }, + "minItems": 1 + }, + "stations": { + "type": "array", + "description": "List of audioguide stations (points of interest). For multilingual projects, each station is repeated per language — sharing the same 'id' and 'number' but with different 'language', 'title', 'subtitle', 'description', and 'audios' values. Images are typically shared across languages.", + "items": { + "$ref": "#/$defs/Station" + }, + "minItems": 1 + }, + "languages": { + "type": "array", + "description": "List of available languages for the audioguide. The first language in the list is used as the default.", + "items": { + "$ref": "#/$defs/Language" + }, + "minItems": 1 + }, + "texts": { + "type": "array", + "description": "UI translation strings for the app interface. Each text entry is a key-value pair scoped to a specific language. All keys should be provided for every language listed in the 'languages' array.", + "items": { + "$ref": "#/$defs/Text" + }, + "minItems": 1 + }, + "tours": { + "type": "array", + "description": "Optional list of guided tours. Tours group stations into a specific order to give visitors a structured, guided experience. For multilingual projects, each tour is repeated per language — sharing the same 'id' but with different 'language', 'title', and 'description' values.", + "items": { + "$ref": "#/$defs/Tour" + } + }, + "pins": { + "type": "array", + "description": "Optional array of four-digit PIN codes. Provides a simple access control mechanism — e.g. a PIN issued upon payment. If present, visitors must enter a valid PIN to access the audioguide.", + "items": { + "type": "string", + "pattern": "^[0-9]{4}$", + "description": "A four-digit PIN code." + } + }, + "server": { + "type": "array", + "description": "Optional array of IP addresses identifying local servers for downloading data within an internal WiFi network. Enhances download speed for visitors and reduces outbound internet traffic.", + "items": { + "type": "string", + "description": "An IP address of a local server." + } + } + }, + "additionalProperties": false, + "$defs": { + "Asset": { + "type": "object", + "description": "A media asset (image, audio file, video, or other downloadable resource). Image assets are typically language-independent. Audio/video assets include a 'language' field to associate them with a specific language.", + "required": ["id", "filename", "externalUrl"], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this asset. Referenced by stations in their 'images' and 'audios' arrays, and by tours in their 'images' array." + }, + "language": { + "type": "string", + "description": "Language code (e.g. 'de', 'en', 'es'). Used for audio/video assets to associate them with a language. Omitted for image assets since images are typically shared across languages.", + "pattern": "^[a-z]{2,3}$" + }, + "title": { + "type": "string", + "description": "Optional title of the asset, e.g. the title of an audio track." + }, + "duration": { + "type": "number", + "description": "Optional duration in seconds of an audio or video asset.", + "minimum": 0 + }, + "filename": { + "type": "string", + "description": "The filename of the asset including extension, without a path. Images are typically .png or .jpg, audio files are typically .mp3." + }, + "externalUrl": { + "type": "string", + "format": "uri", + "description": "The full URL where this asset is hosted and can be downloaded from." + } + }, + "additionalProperties": false + }, + "Station": { + "type": "object", + "description": "An audioguide station representing a specific point of interest within a tour or guide. In multilingual projects, each logical station appears multiple times — once per language — sharing the same 'id' and 'number' but with language-specific content. Images are typically shared across all language variants of a station.", + "required": ["id", "language", "number", "title", "images"], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this station, shared across language variants of the same station (e.g. 's1' for station 1 in all languages)." + }, + "language": { + "type": "string", + "description": "Language code (e.g. 'de', 'en', 'es'). Indicates which language this station variant is in.", + "pattern": "^[a-z]{2,3}$" + }, + "title": { + "type": "string", + "description": "The title/name of the station in the specified language." + }, + "subtitle": { + "type": "string", + "description": "An optional subtitle for the station in the specified language." + }, + "number": { + "type": "string", + "description": "Display number for the station (e.g. '1', '2', '3'). Used for identification of a station by visitors." + }, + "description": { + "type": "string", + "description": "An optional textual description of the station in the specified language." + }, + "latitude": { + "type": "number", + "description": "Optional latitude coordinate of the station, used to display the station on a map.", + "minimum": -90, + "maximum": 90 + }, + "longitude": { + "type": "number", + "description": "Optional longitude coordinate of the station, used to display the station on a map.", + "minimum": -180, + "maximum": 180 + }, + "images": { + "type": "array", + "description": "Array of asset IDs referencing image files for this station. IDs must match entries in the top-level 'assets' array. Images are typically shared across language variants.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "audios": { + "type": "array", + "description": "Optional array of asset IDs referencing audio files for this station. IDs must match entries in the top-level 'assets' array.", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "Language": { + "type": "object", + "description": "A language available in the audioguide. Displayed in the language selection screen.", + "required": ["title", "language"], + "properties": { + "title": { + "type": "string", + "description": "The display name of the language, written in that language itself (e.g. 'Deutsch', 'English', 'Español', 'Italiano')." + }, + "language": { + "type": "string", + "description": "Language code (e.g. 'de', 'en', 'es', 'it').", + "pattern": "^[a-z]{2,3}$" + } + }, + "additionalProperties": false + }, + "Text": { + "type": "object", + "description": "A UI translation string. The app uses keys to look up interface text in the selected language. Keys are arbitrary strings, e.g. 'menu-overview', 'menu-selection', 'menu-language', 'no-internet', 'try-again', 'enter-pin', 'pin-error'.", + "required": ["language", "key", "value"], + "properties": { + "language": { + "type": "string", + "description": "Language code (e.g. 'de', 'en', 'es', 'it'). Indicates which language this translation belongs to.", + "pattern": "^[a-z]{2,3}$" + }, + "key": { + "type": "string", + "description": "The translation key identifier used inside the app." + }, + "value": { + "type": "string", + "description": "The translated text for this key in the specified language." + } + }, + "additionalProperties": false + }, + "Tour": { + "type": "object", + "description": "A guided tour that groups stations into a specific order to provide visitors with a structured, informative experience. In multilingual projects, each tour is repeated per language — sharing the same 'id' but with language-specific 'title' and 'description' values.", + "required": ["id", "title", "language", "default", "stations", "images"], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for this tour, shared across language variants (e.g. 'tour1')." + }, + "title": { + "type": "string", + "description": "The title of the tour in the specified language." + }, + "language": { + "type": "string", + "description": "Language code indicating which language this tour variant is in.", + "pattern": "^[a-z]{2,3}$" + }, + "default": { + "type": "boolean", + "description": "Whether this tour is the default tour. Exactly one tour should be marked as default." + }, + "number": { + "type": "string", + "description": "Optional tour number as an identifier for visitors." + }, + "description": { + "type": "string", + "description": "Optional description of the tour in the specified language." + }, + "stations": { + "type": "array", + "description": "An ordered array of station numbers (as strings) listing all stations in this tour in the intended visiting order.", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "images": { + "type": "array", + "description": "Array of asset IDs referencing images for this tour. IDs must match entries in the top-level 'assets' array.", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + } + } +}