diff --git a/CHANGELOG.md b/CHANGELOG.md index eb29145..3510c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,8 @@ Unreleased - `archive.contents_sha256` reads members in stored order - Removed `resources/kindlookup.json`; use `content_kinds.KINDLIST` - Added `kolibri-constants/ContentKinds` + - Removed `resources/presetlookup.json`; use `format_presets.PRESETLIST` + - Added `kolibri-constants/FormatPresets` 0.2.17 Apr 13, 2026 diff --git a/README.md b/README.md index cb0d831..592ba28 100644 --- a/README.md +++ b/README.md @@ -150,9 +150,10 @@ Every `ContentNode` is associated with one or more `File` objects and nature of this association is represented though the `format_preset` attribute of the file. The `format_preset` is the role the file is playing in the content node, e.g., thumbnail, high resolution video, or low resolution video. -Note that format presets are represented redundantly as python string in -[constants/format_presets.py](https://github.com/learningequality/le-utils/blob/master/le_utils/constants/format_presets.py) -and as json [resources/presetlookup.json](https://github.com/learningequality/le-utils/blob/master/le_utils/resources/presetlookup.json). +Format presets are defined in +[spec/constants-format_presets.json](https://github.com/learningequality/le-utils/blob/main/spec/constants-format_presets.json), +from which [constants/format_presets.py](https://github.com/learningequality/le-utils/blob/main/le_utils/constants/format_presets.py) +and [js/FormatPresets.js](https://github.com/learningequality/le-utils/blob/main/js/FormatPresets.js) are generated. You can think of the different format presets on a content node as different "slots" to be filled in by files, with certain slots being required while other optional. diff --git a/js/FormatPresets.js b/js/FormatPresets.js new file mode 100644 index 0000000..7d771ed --- /dev/null +++ b/js/FormatPresets.js @@ -0,0 +1,107 @@ +// -*- coding: utf-8 -*- +// Generated by scripts/generate_from_specs.py +// FormatPresets + +export default { + VIDEO_HIGH_RES: "high_res_video", + VIDEO_HIGH_RES_READABLE: "High Resolution", + VIDEO_LOW_RES: "low_res_video", + VIDEO_LOW_RES_READABLE: "Low Resolution", + VIDEO_DEPENDENCY: "video_dependency", + VIDEO_DEPENDENCY_READABLE: "Video (dependency)", + VIDEO_THUMBNAIL: "video_thumbnail", + VIDEO_THUMBNAIL_READABLE: "Thumbnail", + VIDEO_SUBTITLE: "video_subtitle", + VIDEO_SUBTITLE_READABLE: "Subtitle", + AUDIO: "audio", + AUDIO_READABLE: "Audio", + AUDIO_DEPENDENCY: "audio_dependency", + AUDIO_DEPENDENCY_READABLE: "audio (dependency)", + AUDIO_THUMBNAIL: "audio_thumbnail", + AUDIO_THUMBNAIL_READABLE: "Thumbnail", + EPUB: "epub", + EPUB_READABLE: "ePub Document", + DOCUMENT: "document", + DOCUMENT_READABLE: "Document", + DOCUMENT_THUMBNAIL: "document_thumbnail", + DOCUMENT_THUMBNAIL_READABLE: "Thumbnail", + EXERCISE: "exercise", + EXERCISE_READABLE: "Exercise", + EXERCISE_THUMBNAIL: "exercise_thumbnail", + EXERCISE_THUMBNAIL_READABLE: "Thumbnail", + EXERCISE_IMAGE: "exercise_image", + EXERCISE_IMAGE_READABLE: "Exercise Image", + EXERCISE_GRAPHIE: "exercise_graphie", + EXERCISE_GRAPHIE_READABLE: "Exercise Graphie", + CHANNEL_THUMBNAIL: "channel_thumbnail", + CHANNEL_THUMBNAIL_READABLE: "Channel Thumbnail", + TOPIC_THUMBNAIL: "topic_thumbnail", + TOPIC_THUMBNAIL_READABLE: "Thumbnail", + HTML5_ZIP: "html5_zip", + HTML5_ZIP_READABLE: "HTML5 Zip", + HTML5_DEPENDENCY_ZIP: "html5_dependency", + HTML5_DEPENDENCY_ZIP_READABLE: "HTML5 Dependency (Zip format)", + HTML5_THUMBNAIL: "html5_thumbnail", + HTML5_THUMBNAIL_READABLE: "HTML5 Thumbnail", + IMSCP_ZIP: "imscp_zip", + IMSCP_ZIP_READABLE: "IMSCP Zip", + H5P_ZIP: "h5p", + H5P_ZIP_READABLE: "H5P Zip", + H5P_THUMBNAIL: "h5p_thumbnail", + H5P_THUMBNAIL_READABLE: "H5P Thumbnail", + ZIM: "zim", + ZIM_READABLE: "Zim", + ZIM_THUMBNAIL: "zim_thumbnail", + ZIM_THUMBNAIL_READABLE: "Zim Thumbnail", + QTI_ZIP: "qti", + QTI_ZIP_READABLE: "QTI Zip", + QTI_THUMBNAIL: "qti_thumbnail", + QTI_THUMBNAIL_READABLE: "QTI Thumbnail", + SLIDESHOW_IMAGE: "slideshow_image", + SLIDESHOW_IMAGE_READABLE: "Slideshow Image", + SLIDESHOW_THUMBNAIL: "slideshow_thumbnail", + SLIDESHOW_THUMBNAIL_READABLE: "Slideshow Thumbnail", + SLIDESHOW_MANIFEST: "slideshow_manifest", + SLIDESHOW_MANIFEST_READABLE: "Slideshow Manifest", + BLOOMPUB: "bloompub", + BLOOMPUB_READABLE: "Bloom Document", + KPUB_ZIP: "kpub", + KPUB_ZIP_READABLE: "Kolibri HTML5 Article", + VIDEO_VECTOR: "vector_video", + VIDEO_VECTOR_READABLE: "Vectorized", +}; + +export const PRESETLIST = [ + { id: "high_res_video", readable_name: "High Resolution", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: true, order: 1, kind: "video", allowed_formats: ["mp4", "webm"], convertible_formats: ["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"] }, + { id: "low_res_video", readable_name: "Low Resolution", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: true, order: 2, kind: "video", allowed_formats: ["mp4", "webm"], convertible_formats: ["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"] }, + { id: "video_dependency", readable_name: "Video Dependency", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: false, order: 3, kind: "video", allowed_formats: ["mp4", "webm"], convertible_formats: ["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"] }, + { id: "video_thumbnail", readable_name: "Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: true, order: 3, kind: "video", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "video_subtitle", readable_name: "Subtitle", multi_language: true, supplementary: true, thumbnail: false, subtitle: true, display: true, order: 4, kind: "video", allowed_formats: ["vtt"], convertible_formats: ["srt", "ttml", "sami", "scc", "dfxp"] }, + { id: "audio", readable_name: "Audio", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: true, order: 1, kind: "audio", allowed_formats: ["mp3"], convertible_formats: ["wav", "m4a", "aac", "ogg"] }, + { id: "audio_dependency", readable_name: "Audio", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: false, order: 3, kind: "audio", allowed_formats: ["mp3"], convertible_formats: ["wav", "m4a", "aac", "ogg"] }, + { id: "audio_thumbnail", readable_name: "Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: true, order: 2, kind: "audio", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "epub", readable_name: "ePub Document", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: true, order: 1, kind: "document", allowed_formats: ["epub"], convertible_formats: [] }, + { id: "document", readable_name: "Document", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: true, order: 2, kind: "document", allowed_formats: ["pdf"], convertible_formats: [] }, + { id: "document_thumbnail", readable_name: "Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: true, order: 4, kind: "document", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "exercise", readable_name: "Exercise", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: false, order: 1, kind: "exercise", allowed_formats: ["perseus"], convertible_formats: [] }, + { id: "exercise_thumbnail", readable_name: "Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: true, order: 2, kind: "exercise", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "exercise_image", readable_name: "Exercise Image", multi_language: false, supplementary: true, thumbnail: false, subtitle: false, display: false, order: 3, kind: "exercise", allowed_formats: ["png", "jpg", "jpeg", "gif", "svg"], convertible_formats: [] }, + { id: "exercise_graphie", readable_name: "Exercise Graphie", multi_language: false, supplementary: true, thumbnail: false, subtitle: false, display: false, order: 4, kind: "exercise", allowed_formats: ["svg", "json", "graphie"], convertible_formats: [] }, + { id: "channel_thumbnail", readable_name: "Channel Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: true, order: 0, kind: null, allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "topic_thumbnail", readable_name: "Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: true, order: 1, kind: "topic", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "html5_zip", readable_name: "HTML5 Zip", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: true, order: 1, kind: "html5", allowed_formats: ["zip"], convertible_formats: [] }, + { id: "html5_dependency", readable_name: "HTML5 Zip Dependency", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: false, order: 3, kind: "html5", allowed_formats: ["zip"], convertible_formats: [] }, + { id: "html5_thumbnail", readable_name: "HTML5 Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: true, order: 2, kind: "html5", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "imscp_zip", readable_name: "IMSCP Zip", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: true, order: 1, kind: "html5", allowed_formats: ["zip"], convertible_formats: [] }, + { id: "h5p", readable_name: "H5P", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: true, order: 1, kind: "h5p", allowed_formats: ["h5p"], convertible_formats: [] }, + { id: "h5p_thumbnail", readable_name: "H5P Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: true, order: 2, kind: "h5p", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "zim", readable_name: "Zim", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: false, order: 1, kind: "zim", allowed_formats: ["zim"], convertible_formats: [] }, + { id: "zim_thumbnail", readable_name: "Zim Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: true, order: 2, kind: "zim", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "qti", readable_name: "QTI", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: false, order: 0, kind: "exercise", allowed_formats: ["zip"], convertible_formats: [] }, + { id: "qti_thumbnail", readable_name: "QTI Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: true, order: 2, kind: "exercise", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "slideshow_image", readable_name: "Slideshow Slide Image", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: false, order: 3, kind: "slideshow", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "slideshow_thumbnail", readable_name: "Slideshow Thumbnail", multi_language: false, supplementary: true, thumbnail: true, subtitle: false, display: false, order: 2, kind: "slideshow", allowed_formats: ["png", "jpg", "jpeg"], convertible_formats: [] }, + { id: "slideshow_manifest", readable_name: "Slideshow Manifest", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: false, order: 1, kind: "slideshow", allowed_formats: ["json"], convertible_formats: [] }, + { id: "bloompub", readable_name: "Bloom Document", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: true, order: 3, kind: "document", allowed_formats: ["bloompub", "bloomd"], convertible_formats: [] }, + { id: "kpub", readable_name: "Kolibri HTML5 Article", multi_language: false, supplementary: false, thumbnail: false, subtitle: false, display: true, order: 1, kind: "document", allowed_formats: ["kpub"], convertible_formats: [] }, +]; diff --git a/le_utils/constants/format_presets.py b/le_utils/constants/format_presets.py index bf4a7b3..1845672 100644 --- a/le_utils/constants/format_presets.py +++ b/le_utils/constants/format_presets.py @@ -1,40 +1,33 @@ -import json -import pkgutil -from collections import namedtuple +# -*- coding: utf-8 -*- +# Generated by scripts/generate_from_specs.py +from __future__ import unicode_literals -""" Format Preset Constants""" +from collections import namedtuple -# IMPORTANT: Keep these constants in sync with presetlookup.json +# FormatPresets VIDEO_HIGH_RES = "high_res_video" VIDEO_HIGH_RES_READABLE = "High Resolution" VIDEO_LOW_RES = "low_res_video" VIDEO_LOW_RES_READABLE = "Low Resolution" -VIDEO_VECTOR = "vector_video" -VIDEO_VECTOR_READABLE = "Vectorized" +VIDEO_DEPENDENCY = "video_dependency" +VIDEO_DEPENDENCY_READABLE = "Video (dependency)" VIDEO_THUMBNAIL = "video_thumbnail" VIDEO_THUMBNAIL_READABLE = "Thumbnail" VIDEO_SUBTITLE = "video_subtitle" VIDEO_SUBTITLE_READABLE = "Subtitle" -VIDEO_DEPENDENCY = "video_dependency" -VIDEO_DEPENDENCY_READABLE = "Video (dependency)" - AUDIO = "audio" AUDIO_READABLE = "Audio" -AUDIO_THUMBNAIL = "audio_thumbnail" -AUDIO_THUMBNAIL_READABLE = "Thumbnail" AUDIO_DEPENDENCY = "audio_dependency" AUDIO_DEPENDENCY_READABLE = "audio (dependency)" - -DOCUMENT = "document" -DOCUMENT_READABLE = "Document" # TODO(ivan): Change to "PDF Document" str translations? +AUDIO_THUMBNAIL = "audio_thumbnail" +AUDIO_THUMBNAIL_READABLE = "Thumbnail" EPUB = "epub" EPUB_READABLE = "ePub Document" +DOCUMENT = "document" +DOCUMENT_READABLE = "Document" DOCUMENT_THUMBNAIL = "document_thumbnail" DOCUMENT_THUMBNAIL_READABLE = "Thumbnail" -BLOOMPUB = "bloompub" -BLOOMPUB_READABLE = "Bloom Document" - EXERCISE = "exercise" EXERCISE_READABLE = "Exercise" EXERCISE_THUMBNAIL = "exercise_thumbnail" @@ -43,86 +36,97 @@ EXERCISE_IMAGE_READABLE = "Exercise Image" EXERCISE_GRAPHIE = "exercise_graphie" EXERCISE_GRAPHIE_READABLE = "Exercise Graphie" - CHANNEL_THUMBNAIL = "channel_thumbnail" CHANNEL_THUMBNAIL_READABLE = "Channel Thumbnail" TOPIC_THUMBNAIL = "topic_thumbnail" TOPIC_THUMBNAIL_READABLE = "Thumbnail" - HTML5_ZIP = "html5_zip" HTML5_ZIP_READABLE = "HTML5 Zip" -HTML5_THUMBNAIL = "html5_thumbnail" -HTML5_THUMBNAIL_READABLE = "HTML5 Thumbnail" - HTML5_DEPENDENCY_ZIP = "html5_dependency" HTML5_DEPENDENCY_ZIP_READABLE = "HTML5 Dependency (Zip format)" - +HTML5_THUMBNAIL = "html5_thumbnail" +HTML5_THUMBNAIL_READABLE = "HTML5 Thumbnail" +IMSCP_ZIP = "imscp_zip" +IMSCP_ZIP_READABLE = "IMSCP Zip" H5P_ZIP = "h5p" H5P_ZIP_READABLE = "H5P Zip" H5P_THUMBNAIL = "h5p_thumbnail" H5P_THUMBNAIL_READABLE = "H5P Thumbnail" - -KPUB_ZIP = "kpub" -KPUB_ZIP_READABLE = "Kolibri HTML5 Article" - ZIM = "zim" ZIM_READABLE = "Zim" ZIM_THUMBNAIL = "zim_thumbnail" ZIM_THUMBNAIL_READABLE = "Zim Thumbnail" - QTI_ZIP = "qti" QTI_ZIP_READABLE = "QTI Zip" QTI_THUMBNAIL = "qti_thumbnail" QTI_THUMBNAIL_READABLE = "QTI Thumbnail" - -IMSCP_ZIP = "imscp_zip" -IMSCP_ZIP_READABLE = "IMSCP Zip" - - SLIDESHOW_IMAGE = "slideshow_image" SLIDESHOW_IMAGE_READABLE = "Slideshow Image" - SLIDESHOW_THUMBNAIL = "slideshow_thumbnail" SLIDESHOW_THUMBNAIL_READABLE = "Slideshow Thumbnail" - SLIDESHOW_MANIFEST = "slideshow_manifest" SLIDESHOW_MANIFEST_READABLE = "Slideshow Manifest" +BLOOMPUB = "bloompub" +BLOOMPUB_READABLE = "Bloom Document" +KPUB_ZIP = "kpub" +KPUB_ZIP_READABLE = "Kolibri HTML5 Article" +VIDEO_VECTOR = "vector_video" +VIDEO_VECTOR_READABLE = "Vectorized" +RENDERABLE_PRESETS_ORDER = [ + VIDEO_HIGH_RES, + VIDEO_LOW_RES, + VIDEO_DEPENDENCY, + AUDIO, + AUDIO_DEPENDENCY, + EPUB, + DOCUMENT, + EXERCISE, + HTML5_ZIP, + HTML5_DEPENDENCY_ZIP, + IMSCP_ZIP, + H5P_ZIP, + ZIM, + QTI_ZIP, + SLIDESHOW_IMAGE, + SLIDESHOW_MANIFEST, + BLOOMPUB, + KPUB_ZIP, +] choices = ( - (VIDEO_HIGH_RES, VIDEO_HIGH_RES_READABLE), - (VIDEO_LOW_RES, VIDEO_LOW_RES_READABLE), - # (VIDEO_VECTOR, VIDEO_VECTOR_READABLE), # doesn't exist yet so took out - (VIDEO_THUMBNAIL, VIDEO_THUMBNAIL_READABLE), - (VIDEO_SUBTITLE, VIDEO_SUBTITLE_READABLE), - (VIDEO_DEPENDENCY, VIDEO_DEPENDENCY_READABLE), - (AUDIO, AUDIO_READABLE), - (AUDIO_THUMBNAIL, AUDIO_THUMBNAIL_READABLE), - (AUDIO_DEPENDENCY, AUDIO_DEPENDENCY_READABLE), - (DOCUMENT, DOCUMENT_READABLE), - (EPUB, EPUB_READABLE), - (DOCUMENT_THUMBNAIL, DOCUMENT_THUMBNAIL_READABLE), - (EXERCISE, EXERCISE_READABLE), - (EXERCISE_THUMBNAIL, EXERCISE_THUMBNAIL_READABLE), - (EXERCISE_IMAGE, EXERCISE_IMAGE_READABLE), - (EXERCISE_GRAPHIE, EXERCISE_GRAPHIE_READABLE), - (CHANNEL_THUMBNAIL, CHANNEL_THUMBNAIL_READABLE), - (TOPIC_THUMBNAIL, TOPIC_THUMBNAIL_READABLE), - (HTML5_ZIP, HTML5_ZIP_READABLE), - (HTML5_DEPENDENCY_ZIP, HTML5_DEPENDENCY_ZIP_READABLE), - (HTML5_THUMBNAIL, HTML5_THUMBNAIL_READABLE), - (H5P_ZIP, H5P_ZIP_READABLE), - (H5P_THUMBNAIL, H5P_THUMBNAIL_READABLE), - (ZIM, ZIM_READABLE), - (ZIM_THUMBNAIL, ZIM_THUMBNAIL_READABLE), - (QTI_ZIP, QTI_ZIP_READABLE), - (QTI_THUMBNAIL, QTI_THUMBNAIL_READABLE), - (SLIDESHOW_IMAGE, SLIDESHOW_IMAGE_READABLE), - (SLIDESHOW_THUMBNAIL, SLIDESHOW_THUMBNAIL_READABLE), - (SLIDESHOW_MANIFEST, SLIDESHOW_MANIFEST_READABLE), - (IMSCP_ZIP, IMSCP_ZIP_READABLE), - (BLOOMPUB, BLOOMPUB_READABLE), - (KPUB_ZIP, KPUB_ZIP_READABLE), + (VIDEO_HIGH_RES, "High Resolution"), + (VIDEO_LOW_RES, "Low Resolution"), + (VIDEO_THUMBNAIL, "Thumbnail"), + (VIDEO_SUBTITLE, "Subtitle"), + (VIDEO_DEPENDENCY, "Video (dependency)"), + (AUDIO, "Audio"), + (AUDIO_THUMBNAIL, "Thumbnail"), + (AUDIO_DEPENDENCY, "audio (dependency)"), + (DOCUMENT, "Document"), + (EPUB, "ePub Document"), + (DOCUMENT_THUMBNAIL, "Thumbnail"), + (EXERCISE, "Exercise"), + (EXERCISE_THUMBNAIL, "Thumbnail"), + (EXERCISE_IMAGE, "Exercise Image"), + (EXERCISE_GRAPHIE, "Exercise Graphie"), + (CHANNEL_THUMBNAIL, "Channel Thumbnail"), + (TOPIC_THUMBNAIL, "Thumbnail"), + (HTML5_ZIP, "HTML5 Zip"), + (HTML5_DEPENDENCY_ZIP, "HTML5 Dependency (Zip format)"), + (HTML5_THUMBNAIL, "HTML5 Thumbnail"), + (H5P_ZIP, "H5P Zip"), + (H5P_THUMBNAIL, "H5P Thumbnail"), + (ZIM, "Zim"), + (ZIM_THUMBNAIL, "Zim Thumbnail"), + (QTI_ZIP, "QTI Zip"), + (QTI_THUMBNAIL, "QTI Thumbnail"), + (SLIDESHOW_IMAGE, "Slideshow Image"), + (SLIDESHOW_THUMBNAIL, "Slideshow Thumbnail"), + (SLIDESHOW_MANIFEST, "Slideshow Manifest"), + (IMSCP_ZIP, "IMSCP Zip"), + (BLOOMPUB, "Bloom Document"), + (KPUB_ZIP, "Kolibri HTML5 Article"), ) @@ -148,21 +152,425 @@ class Preset( def generate_list(constantlist): - for id, preset in constantlist.items(): - yield Preset(id=id, **preset) - - -def _initialize_preset_list(): - constantlist = json.loads(pkgutil.get_data("le_utils", "resources/presetlookup.json").decode("utf-8")) - - return generate_list(constantlist) - + for id, record in constantlist.items(): + yield Preset(id=id, **record) -PRESETLIST = list(_initialize_preset_list()) -# Bit order for the File.included_presets renderable bitmask. -# Consumers compute the bit value as 2 ** RENDERABLE_PRESETS_ORDER.index(preset_id). -# APPEND-ONLY: never reorder or remove an existing entry — that would -# silently change the bit value of every preset after it and corrupt -# already-persisted bitmasks. Only append new preset ids at the end. -RENDERABLE_PRESETS_ORDER = [preset.id for preset in PRESETLIST if not preset.supplementary] +PRESETLIST = [ + Preset( + id="high_res_video", + readable_name="High Resolution", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=True, + order=1, + kind="video", + allowed_formats=["mp4", "webm"], + convertible_formats=["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"], + ), + Preset( + id="low_res_video", + readable_name="Low Resolution", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=True, + order=2, + kind="video", + allowed_formats=["mp4", "webm"], + convertible_formats=["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"], + ), + Preset( + id="video_dependency", + readable_name="Video Dependency", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=False, + order=3, + kind="video", + allowed_formats=["mp4", "webm"], + convertible_formats=["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"], + ), + Preset( + id="video_thumbnail", + readable_name="Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=True, + order=3, + kind="video", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="video_subtitle", + readable_name="Subtitle", + multi_language=True, + supplementary=True, + thumbnail=False, + subtitle=True, + display=True, + order=4, + kind="video", + allowed_formats=["vtt"], + convertible_formats=["srt", "ttml", "sami", "scc", "dfxp"], + ), + Preset( + id="audio", + readable_name="Audio", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=True, + order=1, + kind="audio", + allowed_formats=["mp3"], + convertible_formats=["wav", "m4a", "aac", "ogg"], + ), + Preset( + id="audio_dependency", + readable_name="Audio", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=False, + order=3, + kind="audio", + allowed_formats=["mp3"], + convertible_formats=["wav", "m4a", "aac", "ogg"], + ), + Preset( + id="audio_thumbnail", + readable_name="Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=True, + order=2, + kind="audio", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="epub", + readable_name="ePub Document", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=True, + order=1, + kind="document", + allowed_formats=["epub"], + convertible_formats=[], + ), + Preset( + id="document", + readable_name="Document", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=True, + order=2, + kind="document", + allowed_formats=["pdf"], + convertible_formats=[], + ), + Preset( + id="document_thumbnail", + readable_name="Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=True, + order=4, + kind="document", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="exercise", + readable_name="Exercise", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=False, + order=1, + kind="exercise", + allowed_formats=["perseus"], + convertible_formats=[], + ), + Preset( + id="exercise_thumbnail", + readable_name="Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=True, + order=2, + kind="exercise", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="exercise_image", + readable_name="Exercise Image", + multi_language=False, + supplementary=True, + thumbnail=False, + subtitle=False, + display=False, + order=3, + kind="exercise", + allowed_formats=["png", "jpg", "jpeg", "gif", "svg"], + convertible_formats=[], + ), + Preset( + id="exercise_graphie", + readable_name="Exercise Graphie", + multi_language=False, + supplementary=True, + thumbnail=False, + subtitle=False, + display=False, + order=4, + kind="exercise", + allowed_formats=["svg", "json", "graphie"], + convertible_formats=[], + ), + Preset( + id="channel_thumbnail", + readable_name="Channel Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=True, + order=0, + kind=None, + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="topic_thumbnail", + readable_name="Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=True, + order=1, + kind="topic", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="html5_zip", + readable_name="HTML5 Zip", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=True, + order=1, + kind="html5", + allowed_formats=["zip"], + convertible_formats=[], + ), + Preset( + id="html5_dependency", + readable_name="HTML5 Zip Dependency", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=False, + order=3, + kind="html5", + allowed_formats=["zip"], + convertible_formats=[], + ), + Preset( + id="html5_thumbnail", + readable_name="HTML5 Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=True, + order=2, + kind="html5", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="imscp_zip", + readable_name="IMSCP Zip", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=True, + order=1, + kind="html5", + allowed_formats=["zip"], + convertible_formats=[], + ), + Preset( + id="h5p", + readable_name="H5P", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=True, + order=1, + kind="h5p", + allowed_formats=["h5p"], + convertible_formats=[], + ), + Preset( + id="h5p_thumbnail", + readable_name="H5P Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=True, + order=2, + kind="h5p", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="zim", + readable_name="Zim", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=False, + order=1, + kind="zim", + allowed_formats=["zim"], + convertible_formats=[], + ), + Preset( + id="zim_thumbnail", + readable_name="Zim Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=True, + order=2, + kind="zim", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="qti", + readable_name="QTI", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=False, + order=0, + kind="exercise", + allowed_formats=["zip"], + convertible_formats=[], + ), + Preset( + id="qti_thumbnail", + readable_name="QTI Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=True, + order=2, + kind="exercise", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="slideshow_image", + readable_name="Slideshow Slide Image", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=False, + order=3, + kind="slideshow", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="slideshow_thumbnail", + readable_name="Slideshow Thumbnail", + multi_language=False, + supplementary=True, + thumbnail=True, + subtitle=False, + display=False, + order=2, + kind="slideshow", + allowed_formats=["png", "jpg", "jpeg"], + convertible_formats=[], + ), + Preset( + id="slideshow_manifest", + readable_name="Slideshow Manifest", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=False, + order=1, + kind="slideshow", + allowed_formats=["json"], + convertible_formats=[], + ), + Preset( + id="bloompub", + readable_name="Bloom Document", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=True, + order=3, + kind="document", + allowed_formats=["bloompub", "bloomd"], + convertible_formats=[], + ), + Preset( + id="kpub", + readable_name="Kolibri HTML5 Article", + multi_language=False, + supplementary=False, + thumbnail=False, + subtitle=False, + display=True, + order=1, + kind="document", + allowed_formats=["kpub"], + convertible_formats=[], + ), +] diff --git a/le_utils/resources/presetlookup.json b/le_utils/resources/presetlookup.json deleted file mode 100644 index 1f9f45e..0000000 --- a/le_utils/resources/presetlookup.json +++ /dev/null @@ -1,386 +0,0 @@ -{ - "high_res_video": { - "readable_name": "High Resolution", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": true, - "order": 1, - "kind": "video", - "allowed_formats": ["mp4", "webm"], - "convertible_formats": ["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"] - }, - "low_res_video": { - "readable_name": "Low Resolution", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": true, - "order": 2, - "kind": "video", - "allowed_formats": ["mp4", "webm"], - "convertible_formats": ["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"] - }, - "video_dependency": { - "readable_name": "Video Dependency", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": false, - "order": 3, - "kind": "video", - "allowed_formats": ["mp4", "webm"], - "convertible_formats": ["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"] - }, - "video_thumbnail": { - "readable_name": "Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": true, - "order": 3, - "kind": "video", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "video_subtitle": { - "readable_name": "Subtitle", - "multi_language": true, - "supplementary": true, - "thumbnail": false, - "subtitle": true, - "display": true, - "order": 4, - "kind": "video", - "allowed_formats": ["vtt"], - "convertible_formats": ["srt", "ttml", "sami", "scc", "dfxp"] - }, - "audio": { - "readable_name": "Audio", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": true, - "order": 1, - "kind": "audio", - "allowed_formats": ["mp3"], - "convertible_formats": ["wav", "m4a", "aac", "ogg"] - }, - "audio_dependency": { - "readable_name": "Audio", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": false, - "order": 3, - "kind": "audio", - "allowed_formats": ["mp3"], - "convertible_formats": ["wav", "m4a", "aac", "ogg"] - }, - "audio_thumbnail": { - "readable_name": "Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": true, - "order": 2, - "kind": "audio", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "epub": { - "readable_name": "ePub Document", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": true, - "order": 1, - "kind": "document", - "allowed_formats": ["epub"], - "convertible_formats": [] - }, - "document": { - "readable_name": "Document", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": true, - "order": 2, - "kind": "document", - "allowed_formats": ["pdf"], - "convertible_formats": [] - }, - "document_thumbnail": { - "readable_name": "Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": true, - "order": 4, - "kind": "document", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "exercise": { - "readable_name": "Exercise", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": false, - "order": 1, - "kind": "exercise", - "allowed_formats": ["perseus"], - "convertible_formats": [] - }, - "exercise_thumbnail": { - "readable_name": "Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": true, - "order": 2, - "kind": "exercise", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "exercise_image": { - "readable_name": "Exercise Image", - "multi_language": false, - "supplementary": true, - "thumbnail": false, - "subtitle": false, - "display": false, - "order": 3, - "kind": "exercise", - "allowed_formats": ["png", "jpg", "jpeg", "gif", "svg"], - "convertible_formats": [] - }, - "exercise_graphie": { - "readable_name": "Exercise Graphie", - "multi_language": false, - "supplementary": true, - "thumbnail": false, - "subtitle": false, - "display": false, - "order": 4, - "kind": "exercise", - "allowed_formats": ["svg", "json", "graphie"], - "convertible_formats": [] - }, - "channel_thumbnail": { - "readable_name": "Channel Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": true, - "order": 0, - "kind": null, - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "topic_thumbnail": { - "readable_name": "Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": true, - "order": 1, - "kind": "topic", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "html5_zip": { - "readable_name": "HTML5 Zip", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": true, - "order": 1, - "kind": "html5", - "allowed_formats": ["zip"], - "convertible_formats": [] - }, - "html5_dependency": { - "readable_name": "HTML5 Zip Dependency", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": false, - "order": 3, - "kind": "html5", - "allowed_formats": ["zip"], - "convertible_formats": [] - }, - "html5_thumbnail": { - "readable_name": "HTML5 Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": true, - "order": 2, - "kind": "html5", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "imscp_zip": { - "readable_name": "IMSCP Zip", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": true, - "order": 1, - "kind": "html5", - "allowed_formats": ["zip"], - "convertible_formats": [] - }, - "h5p": { - "readable_name": "H5P", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": true, - "order": 1, - "kind": "h5p", - "allowed_formats": ["h5p"], - "convertible_formats": [] - }, - "h5p_thumbnail": { - "readable_name": "H5P Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": true, - "order": 2, - "kind": "h5p", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "zim": { - "readable_name": "Zim", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": false, - "order": 1, - "kind": "zim", - "allowed_formats": ["zim"], - "convertible_formats": [] - }, - "zim_thumbnail": { - "readable_name": "Zim Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": true, - "order": 2, - "kind": "zim", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "qti": { - "readable_name": "QTI", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": false, - "order": 0, - "kind": "exercise", - "allowed_formats": ["zip"], - "convertible_formats": [] - }, - "qti_thumbnail": { - "readable_name": "QTI Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": true, - "order": 2, - "kind": "exercise", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "slideshow_image": { - "readable_name": "Slideshow Slide Image", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": false, - "order": 3, - "kind": "slideshow", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "slideshow_thumbnail": { - "readable_name": "Slideshow Thumbnail", - "multi_language": false, - "supplementary": true, - "thumbnail": true, - "subtitle": false, - "display": false, - "order": 2, - "kind": "slideshow", - "allowed_formats": ["png", "jpg", "jpeg"], - "convertible_formats": [] - }, - "slideshow_manifest": { - "readable_name": "Slideshow Manifest", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": false, - "order": 1, - "kind": "slideshow", - "allowed_formats": ["json"], - "convertible_formats": [] - }, - "bloompub": { - "readable_name": "Bloom Document", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": true, - "order": 3, - "kind": "document", - "allowed_formats": ["bloompub", "bloomd"], - "convertible_formats": [] - }, - "kpub": { - "readable_name": "Kolibri HTML5 Article", - "multi_language": false, - "supplementary": false, - "thumbnail": false, - "subtitle": false, - "display": true, - "order": 1, - "kind": "document", - "allowed_formats": ["kpub"], - "convertible_formats": [] - } -} diff --git a/scripts/generate_from_specs.py b/scripts/generate_from_specs.py index 89e2337..d0dd8cc 100644 --- a/scripts/generate_from_specs.py +++ b/scripts/generate_from_specs.py @@ -237,18 +237,23 @@ def define_constant(constants, constant, value): constants[constant] = value +def check_record_fields(record, fields): + for field in fields: + if field not in record: + raise ValueError("Record {} has no field {}".format(record["constant"], field)) + + def namedtuple_constants(spec): constants = OrderedDict() ids = set() values = set() fields = spec["namedtuple"]["fields"] constant_field = spec["namedtuple"].get("constant_field", "id") + label_constant_suffix = spec["namedtuple"].get("label_constant_suffix") if constant_field not in fields: raise ValueError("constant_field {} is not a field".format(constant_field)) for record in spec["records"]: - for field in fields: - if field not in record: - raise ValueError("Record {} has no field {}".format(record["constant"], field)) + check_record_fields(record, fields) if record["id"] in ids: raise ValueError("Record id {} is defined twice".format(record["id"])) ids.add(record["id"]) @@ -256,6 +261,9 @@ def namedtuple_constants(spec): raise ValueError("Record {} {} is defined twice".format(constant_field, record[constant_field])) values.add(record[constant_field]) define_constant(constants, record["constant"], record[constant_field]) + if label_constant_suffix: + check_record_fields(record, ["label"]) + define_constant(constants, "{}_{}".format(record["constant"], label_constant_suffix), record["label"]) for field in record.get("field_constants", []): define_constant(constants, "{}_{}".format(record["constant"], field.upper()), record[field]) for constant, value in spec.get("constants", {}).items(): @@ -263,24 +271,39 @@ def namedtuple_constants(spec): return constants -def namedtuple_tuples(spec, constants): - tuples = spec.get("tuples", {}) - for tuple_name, members in tuples.items(): - if len(set(members)) != len(members): - raise ValueError("Tuple {} repeats a constant".format(tuple_name)) - for member in members: - if member not in constants: - raise ValueError("Tuple {} references undefined constant {}".format(tuple_name, member)) - return tuples +SEQUENCE_TEMPLATES = OrderedDict((("tuples", "{} = ({},)\n"), ("lists", "{} = [{}]\n"))) -def namedtuple_mappings(spec, constants): - mappings = OrderedDict() +def module_names(spec, constants): name = spec["namedtuple"]["name"] - defined_names = set(constants) | set(spec.get("tuples", {})) - defined_names |= {name, "{}LIST".format(name.upper()), "choices", "default", "generate_list", "namedtuple", "unicode_literals"} + names = set(constants) + names |= {name, "{}LIST".format(name.upper()), "choices", "default", "generate_list", "namedtuple", "unicode_literals"} if "getter" in spec["namedtuple"]: - defined_names |= {spec["namedtuple"]["getter"], "_{}LOOKUP".format(name.upper())} + names |= {spec["namedtuple"]["getter"], "_{}LOOKUP".format(name.upper())} + return names + + +def namedtuple_sequences(spec, constants): + sequences = [] + defined_names = module_names(spec, constants) + for key in SEQUENCE_TEMPLATES: + for sequence_name, members in spec.get(key, {}).items(): + if sequence_name in defined_names: + raise ValueError("{} {} is already defined".format(key, sequence_name)) + defined_names.add(sequence_name) + if not members: + raise ValueError("{} {} is empty".format(key, sequence_name)) + if len(set(members)) != len(members): + raise ValueError("{} {} repeats a constant".format(key, sequence_name)) + for member in members: + if member not in constants: + raise ValueError("{} {} references undefined constant {}".format(key, sequence_name, member)) + sequences.append((key, sequence_name, members)) + return sequences, defined_names + + +def namedtuple_mappings(spec, constants, defined_names): + mappings = OrderedDict() for mapping_name, entries in spec.get("mappings", {}).items(): if mapping_name in defined_names: raise ValueError("Mapping {} is already defined".format(mapping_name)) @@ -359,8 +382,8 @@ def write_python_getter(f, spec): def write_namedtuple_python_file(output_file, name, spec): constants = namedtuple_constants(spec) - tuples = namedtuple_tuples(spec, constants) - mappings = namedtuple_mappings(spec, constants) + sequences, defined_names = namedtuple_sequences(spec, constants) + mappings = namedtuple_mappings(spec, constants, defined_names) choices = namedtuple_choices(spec) with open(output_file, "w") as f: write_python_header(f) @@ -370,9 +393,9 @@ def write_namedtuple_python_file(output_file, name, spec): f.write("\n") for constant, value in constants.items(): f.write("{} = {!r}\n".format(constant, value)) - for tuple_name, members in tuples.items(): + for key, sequence_name, members in sequences: f.write("\n") - f.write("{} = ({},)\n".format(tuple_name, ", ".join(members))) + f.write(SEQUENCE_TEMPLATES[key].format(sequence_name, ", ".join(members))) write_python_mappings(f, mappings) f.write("\n") if choices is not None: @@ -400,7 +423,8 @@ def write_js_mappings(f, mappings, constants): def write_namedtuple_js_file(output_file, name, spec): fields = spec["namedtuple"]["fields"] constants = namedtuple_constants(spec) - mappings = namedtuple_mappings(spec, constants) + _, defined_names = namedtuple_sequences(spec, constants) + mappings = namedtuple_mappings(spec, constants, defined_names) with open(output_file, "w") as f: write_js_header(f) f.write("// {}\n".format(name)) diff --git a/spec/constants-format_presets.json b/spec/constants-format_presets.json new file mode 100644 index 0000000..617a2ee --- /dev/null +++ b/spec/constants-format_presets.json @@ -0,0 +1,549 @@ +{ + "namedtuple": { + "name": "Preset", + "fields": ["id", "readable_name", "multi_language", "supplementary", "thumbnail", "subtitle", "display", "order", "kind", "allowed_formats", "convertible_formats"], + "label_constant_suffix": "READABLE" + }, + "records": [ + { + "constant": "VIDEO_HIGH_RES", + "label": "High Resolution", + "id": "high_res_video", + "readable_name": "High Resolution", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": true, + "order": 1, + "kind": "video", + "allowed_formats": ["mp4", "webm"], + "convertible_formats": ["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"] + }, + { + "constant": "VIDEO_LOW_RES", + "label": "Low Resolution", + "id": "low_res_video", + "readable_name": "Low Resolution", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": true, + "order": 2, + "kind": "video", + "allowed_formats": ["mp4", "webm"], + "convertible_formats": ["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"] + }, + { + "constant": "VIDEO_DEPENDENCY", + "label": "Video (dependency)", + "id": "video_dependency", + "readable_name": "Video Dependency", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": false, + "order": 3, + "kind": "video", + "allowed_formats": ["mp4", "webm"], + "convertible_formats": ["avi", "mov", "mpg", "wmv", "mkv", "flv", "ogv", "m4v"] + }, + { + "constant": "VIDEO_THUMBNAIL", + "label": "Thumbnail", + "id": "video_thumbnail", + "readable_name": "Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": true, + "order": 3, + "kind": "video", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "VIDEO_SUBTITLE", + "label": "Subtitle", + "id": "video_subtitle", + "readable_name": "Subtitle", + "multi_language": true, + "supplementary": true, + "thumbnail": false, + "subtitle": true, + "display": true, + "order": 4, + "kind": "video", + "allowed_formats": ["vtt"], + "convertible_formats": ["srt", "ttml", "sami", "scc", "dfxp"] + }, + { + "constant": "AUDIO", + "label": "Audio", + "id": "audio", + "readable_name": "Audio", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": true, + "order": 1, + "kind": "audio", + "allowed_formats": ["mp3"], + "convertible_formats": ["wav", "m4a", "aac", "ogg"] + }, + { + "constant": "AUDIO_DEPENDENCY", + "label": "audio (dependency)", + "id": "audio_dependency", + "readable_name": "Audio", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": false, + "order": 3, + "kind": "audio", + "allowed_formats": ["mp3"], + "convertible_formats": ["wav", "m4a", "aac", "ogg"] + }, + { + "constant": "AUDIO_THUMBNAIL", + "label": "Thumbnail", + "id": "audio_thumbnail", + "readable_name": "Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": true, + "order": 2, + "kind": "audio", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "EPUB", + "label": "ePub Document", + "id": "epub", + "readable_name": "ePub Document", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": true, + "order": 1, + "kind": "document", + "allowed_formats": ["epub"], + "convertible_formats": [] + }, + { + "constant": "DOCUMENT", + "label": "Document", + "id": "document", + "readable_name": "Document", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": true, + "order": 2, + "kind": "document", + "allowed_formats": ["pdf"], + "convertible_formats": [] + }, + { + "constant": "DOCUMENT_THUMBNAIL", + "label": "Thumbnail", + "id": "document_thumbnail", + "readable_name": "Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": true, + "order": 4, + "kind": "document", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "EXERCISE", + "label": "Exercise", + "id": "exercise", + "readable_name": "Exercise", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": false, + "order": 1, + "kind": "exercise", + "allowed_formats": ["perseus"], + "convertible_formats": [] + }, + { + "constant": "EXERCISE_THUMBNAIL", + "label": "Thumbnail", + "id": "exercise_thumbnail", + "readable_name": "Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": true, + "order": 2, + "kind": "exercise", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "EXERCISE_IMAGE", + "label": "Exercise Image", + "id": "exercise_image", + "readable_name": "Exercise Image", + "multi_language": false, + "supplementary": true, + "thumbnail": false, + "subtitle": false, + "display": false, + "order": 3, + "kind": "exercise", + "allowed_formats": ["png", "jpg", "jpeg", "gif", "svg"], + "convertible_formats": [] + }, + { + "constant": "EXERCISE_GRAPHIE", + "label": "Exercise Graphie", + "id": "exercise_graphie", + "readable_name": "Exercise Graphie", + "multi_language": false, + "supplementary": true, + "thumbnail": false, + "subtitle": false, + "display": false, + "order": 4, + "kind": "exercise", + "allowed_formats": ["svg", "json", "graphie"], + "convertible_formats": [] + }, + { + "constant": "CHANNEL_THUMBNAIL", + "label": "Channel Thumbnail", + "id": "channel_thumbnail", + "readable_name": "Channel Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": true, + "order": 0, + "kind": null, + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "TOPIC_THUMBNAIL", + "label": "Thumbnail", + "id": "topic_thumbnail", + "readable_name": "Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": true, + "order": 1, + "kind": "topic", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "HTML5_ZIP", + "label": "HTML5 Zip", + "id": "html5_zip", + "readable_name": "HTML5 Zip", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": true, + "order": 1, + "kind": "html5", + "allowed_formats": ["zip"], + "convertible_formats": [] + }, + { + "constant": "HTML5_DEPENDENCY_ZIP", + "label": "HTML5 Dependency (Zip format)", + "id": "html5_dependency", + "readable_name": "HTML5 Zip Dependency", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": false, + "order": 3, + "kind": "html5", + "allowed_formats": ["zip"], + "convertible_formats": [] + }, + { + "constant": "HTML5_THUMBNAIL", + "label": "HTML5 Thumbnail", + "id": "html5_thumbnail", + "readable_name": "HTML5 Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": true, + "order": 2, + "kind": "html5", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "IMSCP_ZIP", + "label": "IMSCP Zip", + "id": "imscp_zip", + "readable_name": "IMSCP Zip", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": true, + "order": 1, + "kind": "html5", + "allowed_formats": ["zip"], + "convertible_formats": [] + }, + { + "constant": "H5P_ZIP", + "label": "H5P Zip", + "id": "h5p", + "readable_name": "H5P", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": true, + "order": 1, + "kind": "h5p", + "allowed_formats": ["h5p"], + "convertible_formats": [] + }, + { + "constant": "H5P_THUMBNAIL", + "label": "H5P Thumbnail", + "id": "h5p_thumbnail", + "readable_name": "H5P Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": true, + "order": 2, + "kind": "h5p", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "ZIM", + "label": "Zim", + "id": "zim", + "readable_name": "Zim", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": false, + "order": 1, + "kind": "zim", + "allowed_formats": ["zim"], + "convertible_formats": [] + }, + { + "constant": "ZIM_THUMBNAIL", + "label": "Zim Thumbnail", + "id": "zim_thumbnail", + "readable_name": "Zim Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": true, + "order": 2, + "kind": "zim", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "QTI_ZIP", + "label": "QTI Zip", + "id": "qti", + "readable_name": "QTI", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": false, + "order": 0, + "kind": "exercise", + "allowed_formats": ["zip"], + "convertible_formats": [] + }, + { + "constant": "QTI_THUMBNAIL", + "label": "QTI Thumbnail", + "id": "qti_thumbnail", + "readable_name": "QTI Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": true, + "order": 2, + "kind": "exercise", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "SLIDESHOW_IMAGE", + "label": "Slideshow Image", + "id": "slideshow_image", + "readable_name": "Slideshow Slide Image", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": false, + "order": 3, + "kind": "slideshow", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "SLIDESHOW_THUMBNAIL", + "label": "Slideshow Thumbnail", + "id": "slideshow_thumbnail", + "readable_name": "Slideshow Thumbnail", + "multi_language": false, + "supplementary": true, + "thumbnail": true, + "subtitle": false, + "display": false, + "order": 2, + "kind": "slideshow", + "allowed_formats": ["png", "jpg", "jpeg"], + "convertible_formats": [] + }, + { + "constant": "SLIDESHOW_MANIFEST", + "label": "Slideshow Manifest", + "id": "slideshow_manifest", + "readable_name": "Slideshow Manifest", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": false, + "order": 1, + "kind": "slideshow", + "allowed_formats": ["json"], + "convertible_formats": [] + }, + { + "constant": "BLOOMPUB", + "label": "Bloom Document", + "id": "bloompub", + "readable_name": "Bloom Document", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": true, + "order": 3, + "kind": "document", + "allowed_formats": ["bloompub", "bloomd"], + "convertible_formats": [] + }, + { + "constant": "KPUB_ZIP", + "label": "Kolibri HTML5 Article", + "id": "kpub", + "readable_name": "Kolibri HTML5 Article", + "multi_language": false, + "supplementary": false, + "thumbnail": false, + "subtitle": false, + "display": true, + "order": 1, + "kind": "document", + "allowed_formats": ["kpub"], + "convertible_formats": [] + } + ], + "constants": { + "VIDEO_VECTOR": "vector_video", + "VIDEO_VECTOR_READABLE": "Vectorized" + }, + "lists": { + "RENDERABLE_PRESETS_ORDER": [ + "VIDEO_HIGH_RES", + "VIDEO_LOW_RES", + "VIDEO_DEPENDENCY", + "AUDIO", + "AUDIO_DEPENDENCY", + "EPUB", + "DOCUMENT", + "EXERCISE", + "HTML5_ZIP", + "HTML5_DEPENDENCY_ZIP", + "IMSCP_ZIP", + "H5P_ZIP", + "ZIM", + "QTI_ZIP", + "SLIDESHOW_IMAGE", + "SLIDESHOW_MANIFEST", + "BLOOMPUB", + "KPUB_ZIP" + ] + }, + "choices_order": [ + "VIDEO_HIGH_RES", + "VIDEO_LOW_RES", + "VIDEO_THUMBNAIL", + "VIDEO_SUBTITLE", + "VIDEO_DEPENDENCY", + "AUDIO", + "AUDIO_THUMBNAIL", + "AUDIO_DEPENDENCY", + "DOCUMENT", + "EPUB", + "DOCUMENT_THUMBNAIL", + "EXERCISE", + "EXERCISE_THUMBNAIL", + "EXERCISE_IMAGE", + "EXERCISE_GRAPHIE", + "CHANNEL_THUMBNAIL", + "TOPIC_THUMBNAIL", + "HTML5_ZIP", + "HTML5_DEPENDENCY_ZIP", + "HTML5_THUMBNAIL", + "H5P_ZIP", + "H5P_THUMBNAIL", + "ZIM", + "ZIM_THUMBNAIL", + "QTI_ZIP", + "QTI_THUMBNAIL", + "SLIDESHOW_IMAGE", + "SLIDESHOW_THUMBNAIL", + "SLIDESHOW_MANIFEST", + "IMSCP_ZIP", + "BLOOMPUB", + "KPUB_ZIP" + ] +} diff --git a/tests/test_generate_from_specs.py b/tests/test_generate_from_specs.py index 950f990..35a570b 100644 --- a/tests/test_generate_from_specs.py +++ b/tests/test_generate_from_specs.py @@ -82,12 +82,19 @@ def test_choices_order_repeating_record_raises(tmp_path): assert_spec_raises(tmp_path, spec(choices_order=["MP4", "MP4"])) -def test_tuple_with_undefined_constant_raises(tmp_path): - assert_spec_raises(tmp_path, spec(tuples={"VIDEO": ["MP4", "AVI"]})) +@pytest.mark.parametrize("key", ["tuples", "lists"]) +def test_sequence_with_undefined_constant_raises(tmp_path, key): + assert_spec_raises(tmp_path, spec(**{key: {"VIDEO": ["MP4", "AVI"]}})) -def test_tuple_repeating_constant_raises(tmp_path): - assert_spec_raises(tmp_path, spec(tuples={"VIDEO": ["MP4", "MP4"]})) +@pytest.mark.parametrize("key", ["tuples", "lists"]) +def test_sequence_repeating_constant_raises(tmp_path, key): + assert_spec_raises(tmp_path, spec(**{key: {"VIDEO": ["MP4", "MP4"]}})) + + +@pytest.mark.parametrize("key", ["tuples", "lists"]) +def test_empty_sequence_raises(tmp_path, key): + assert_spec_raises(tmp_path, spec(**{key: {"VIDEO": []}})) def test_mapping_with_undefined_constant_raises(tmp_path): @@ -101,9 +108,10 @@ def test_mapping_key_under_two_constants_raises(tmp_path): def test_mapping_name_already_defined_raises(tmp_path): (tmp_path / "valid").mkdir() - names = set(write_and_exec(tmp_path / "valid", spec(tuples={"VIDEO": ["MP4"]}))) - {"__builtins__"} + sequences = {"tuples": {"VIDEO": ["MP4"]}, "lists": {"ORDER": ["MP4"]}} + names = set(write_and_exec(tmp_path / "valid", spec(**sequences))) - {"__builtins__"} for mapping_name in names | {"default"}: - assert_spec_raises(tmp_path, spec(tuples={"VIDEO": ["MP4"]}, mappings={mapping_name: {"MP4": ["a"]}})) + assert_spec_raises(tmp_path, spec(mappings={mapping_name: {"MP4": ["a"]}}, **sequences)) def test_mapping_keys_not_a_list_raises(tmp_path): @@ -132,6 +140,31 @@ def test_constant_field_repeated_value_raises(tmp_path): ) +def test_label_constant_suffix_without_label_raises(tmp_path): + record = mp4_record() + del record["label"] + namedtuple = dict(spec()["namedtuple"], label_constant_suffix="READABLE") + assert_spec_raises(tmp_path, spec(namedtuple=namedtuple, records=[record], choices_order=[])) + + +def test_record_constant_shadowing_label_constant_raises(tmp_path): + namedtuple = dict(spec()["namedtuple"], label_constant_suffix="READABLE") + records = [mp4_record(constant="MP4_READABLE", id="a", field_constants=[]), mp4_record()] + assert_spec_raises(tmp_path, spec(namedtuple=namedtuple, records=records, choices_order=["MP4_READABLE", "MP4"])) + + +def test_sequence_name_already_defined_raises(tmp_path): + (tmp_path / "valid").mkdir() + names = set(write_and_exec(tmp_path / "valid", spec())) - {"__builtins__"} + for sequence_name in names | {"default"}: + for key in ("tuples", "lists"): + assert_spec_raises(tmp_path, spec(**{key: {sequence_name: ["MP4"]}})) + + +def test_tuple_and_list_with_same_name_raises(tmp_path): + assert_spec_raises(tmp_path, spec(tuples={"VIDEO": ["MP4"]}, lists={"VIDEO": ["MP4"]})) + + def write_and_exec(tmp_path, valid_spec): output_file = str(tmp_path / "formats.py") generate_from_specs.write_namedtuple_python_file(output_file, "FileFormats", valid_spec) @@ -146,6 +179,11 @@ def test_tuple_with_defined_constants_is_written(tmp_path): assert namespace["VIDEO"] == ("mp4", "avi") +def test_list_with_defined_constants_is_written(tmp_path): + namespace = write_and_exec(tmp_path, spec(constants={"AVI": "avi"}, lists={"ORDER": ["MP4", "AVI"]})) + assert namespace["ORDER"] == ["mp4", "avi"] + + def test_mapping_maps_keys_to_constant_values(tmp_path): namespace = write_and_exec(tmp_path, spec(constants={"AVI": "avi"}, mappings={"MAPPING": {"MP4": ["a", "b"], "AVI": ["c"]}})) assert namespace["MAPPING"] == {"a": "mp4", "b": "mp4", "c": "avi"} @@ -177,6 +215,11 @@ def test_constant_field_sets_record_constant_value(tmp_path): assert namespace["MP4"] == "video/mp4" +def test_label_constant_suffix_sets_label_constant(tmp_path): + namespace = write_and_exec(tmp_path, spec(namedtuple=dict(spec()["namedtuple"], label_constant_suffix="READABLE"))) + assert namespace["MP4_READABLE"] == "MP4 Video" + + def import_js(tmp_path, valid_spec, imports, value): output_file = tmp_path / "Formats.mjs" generate_from_specs.write_namedtuple_js_file(str(output_file), "FileFormats", valid_spec) diff --git a/tests/test_presets.py b/tests/test_presets.py index edcf30d..de09686 100644 --- a/tests/test_presets.py +++ b/tests/test_presets.py @@ -1,21 +1,14 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -import json -import pkgutil - +from le_utils.constants import file_formats from le_utils.constants import format_presets -def test_format_presets_are_synced(): - presetlookup = json.loads(pkgutil.get_data("le_utils", "resources/presetlookup.json").decode("utf-8")) - presets_json = set(dict(presetlookup).keys()) - presets_python = set(dict(format_presets.choices).keys()) - assert presets_json == presets_python - - -def test_PRESETLIST_exists(): - assert format_presets.PRESETLIST, "PRESETLIST did not genereate properly" +def test_PRESETLIST_allowed_formats_are_file_format_ids(): + format_ids = {file_format.id for file_format in file_formats.FORMATLIST} + for preset in format_presets.PRESETLIST: + assert set(preset.allowed_formats) <= format_ids, preset.id def test_RENDERABLE_PRESETS_ORDER_matches_PRESETLIST_ids():