├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── check.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── bmff ├── base_types.odin ├── bmff.odin ├── box_types.odin ├── build.bat ├── example │ ├── bmff_example.odin │ └── build.bat ├── helpers.odin └── print.odin ├── build.bat ├── common ├── build.bat ├── common.odin └── types.odin ├── ebml ├── base_types.odin ├── build.bat ├── ebml.odin ├── example │ ├── build.bat │ └── ebml_example.odin ├── helpers.odin └── print.odin └── tests ├── assets ├── bmff │ └── test_metadata.mp4 └── ebml │ ├── README.md │ ├── damaged.mkv │ └── subtitles.mkv ├── bmff ├── build.bat └── test_iso_bmff.odin ├── build.bat └── ebml ├── build.bat └── test_ebml.odin /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Checklist before submitting: 4 | - [ ] This patch compiles cleanly with flags `-vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do` 5 | - [ ] This patch follows the `core` naming convention: https://github.com/odin-lang/Odin/wiki/Naming-Convention. 6 | - [ ] By submitting, I understand that this patch is made available under this license: [Public Domain](https://unlicense.org). Only for third-party dependencies are other licenses allowed. 7 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check everything 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: 8 | - cron: 0 20 * * * 9 | 10 | env: 11 | FORCE_COLOR: "1" 12 | 13 | jobs: 14 | checks: 15 | runs-on: macos-14 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Install dependencies 20 | run: | 21 | brew install llvm@18 22 | echo "/opt/homebrew/opt/llvm@18/bin" >> "$GITHUB_PATH" 23 | 24 | - name: Build Odin 25 | run: | 26 | git clone https://github.com/odin-lang/Odin.git --depth 1 --single-branch --branch=master 27 | cd Odin 28 | make 29 | echo "$(pwd)" >> "$GITHUB_PATH" 30 | ./odin report 31 | 32 | - name: Check everything 33 | run: | 34 | FLAGS="-vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do" 35 | 36 | pushd tests/bmff 37 | odin test . $FLAGS 38 | popd 39 | 40 | pushd tests/ebml 41 | odin test . $FLAGS 42 | popd 43 | 44 | - name: BMFF example 45 | run: | 46 | FLAGS="-vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:BMFF_DEBUG=false -o:speed" 47 | pushd bmff/example 48 | odin run . $FLAGS -- "../../tests/assets/bmff/test_metadata.mp4" 49 | popd 50 | 51 | - name: EBML example 52 | run: | 53 | FLAGS="-vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -define:EBML_DEBUG=false -o:speed" 54 | pushd ebml/example 55 | odin run . $FLAGS -- "../../tests/assets/ebml/subtitles.mkv" 56 | popd -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.obj 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # File Formats 2 | 3 | [Odin](https://github.com/odin-lang/Odin) implementations of various file formats (WIP). 4 | 5 | ## ISO Base Media File Format (BMFF) 6 | 7 | The base container format used for `MP4`, `HEIF`, `JPEG 2000`, and other formats. 8 | 9 | Implemented from [ISO/IEC Standard 14496, Part 12](https://www.iso.org/standard/68960.html), fifth edition 2015-12-15 specification. 10 | 11 | See also: [Library of Congress archivist's information about the format](https://www.loc.gov/preservation/digital/formats/fdd/fdd000079.shtml). 12 | 13 | ### Status 14 | * `open` opens a file and returns a handle. 15 | * `close` closes the file and cleans up anything allocated on behalf of the user. 16 | * `parse` parses the opened file. 17 | * `print` prints the parse tree. 18 | * Various convenience functions to convert things to Odin-native types. 19 | * Initial test harness. 20 | 21 | ### TODO 22 | * Add parse options, e.g. parse / don't parse the `mdat` box, etc. 23 | * Add handlers for more box types. 24 | * Add more box constraints, e.g. type `foo_` may appear only in `bar_`, zero or more times. 25 | * Add a writer. 26 | 27 | ## Extensible Binary Meta Language (EBML) 28 | 29 | The base container format used for `Matroska` and `WebM`. 30 | 31 | Implemented from [RFC 8794](https://datatracker.ietf.org/doc/rfc8794/) and the [Matroska](https://www.ietf.org/archive/id/draft-ietf-cellar-matroska-08.html) specification. 32 | 33 | ### Status 34 | * `open` opens a file and returns a handle. 35 | * `close` closes the file and cleans up anything allocated on behalf of the user. 36 | * `parse` parses the opened file. 37 | * `print` prints the parse tree. 38 | * Various convenience functions to convert things to Odin-native types. 39 | * Initial test harness. 40 | 41 | ### TODO 42 | * Add parse options, e.g. parse / skip clusters, etc. 43 | * Add more element constraints, e.g. type `foo_` may appear only in `bar_`, zero or more times. 44 | * Add a writer. 45 | * Add tables and enums for [tags](https://datatracker.ietf.org/doc/html/draft-ietf-cellar-tags-06) and [codecs](https://datatracker.ietf.org/doc/html/draft-ietf-cellar-codec-06). 46 | 47 | ## Other file formats 48 | 49 | TBD. 50 | 51 | ## Revisions 52 | ### 2025-05-16 53 | Jeroen van Rijn : 54 | - BMFF + EBML: Compile with all the -vet vlags 55 | - BMFF + EBML: Remove placeholder test framework 56 | 57 | ### 2021-11 58 | Jeroen van Rijn : 59 | - BMFF + EBML: Initial implementations -------------------------------------------------------------------------------- /bmff/base_types.odin: -------------------------------------------------------------------------------- 1 | package iso_bmff 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | A from-scratch implementation of ISO base media file format (ISOM), 30 | as specified in ISO/IEC 14496-12, Fifth edition 2015-12-15. 31 | The identical text is available as ISO/IEC 15444-12 (JPEG 2000, Part 12). 32 | 33 | See: https://www.iso.org/standard/68960.html and https://www.loc.gov/preservation/digital/formats/fdd/fdd000079.shtml 34 | 35 | This file contains base type definitions. 36 | */ 37 | 38 | import "../common" 39 | 40 | Error :: union #shared_nil { 41 | BMFF_Error, 42 | common.Error, 43 | } 44 | 45 | BMFF_Error :: enum { 46 | File_Not_Found, 47 | File_Not_Opened, 48 | File_Empty, 49 | File_Ended_Early, 50 | Read_Error, 51 | 52 | Wrong_File_Format, 53 | Error_Parsing_iTunes_Metadata, 54 | 55 | CHPL_Unknown_Version, 56 | CHPL_Invalid_Size, 57 | 58 | ELST_Unknown_Version, 59 | ELST_Invalid_Size, 60 | 61 | FTYP_Duplicated, 62 | FTYP_Invalid_Size, 63 | 64 | HDLR_Unexpected_Parent, 65 | HDLR_Invalid_Size, 66 | 67 | MDHD_Unknown_Version, 68 | MDHD_Invalid_Size, 69 | 70 | MVHD_Unknown_Version, 71 | MVHD_Invalid_Size, 72 | 73 | TKHD_Unknown_Version, 74 | TKHD_Invalid_Size, 75 | } 76 | 77 | BCD4 :: distinct [4]u8 78 | 79 | FourCC :: enum common.FourCC { 80 | /* 81 | File root dummy Box. 82 | */ 83 | Root = 0, 84 | 85 | /* 86 | File type 87 | */ 88 | File_Type = 'f' << 24 | 't' << 16 | 'y' << 8 | 'p', // 0x66747970 89 | 90 | /* 91 | Box types 92 | */ 93 | Data = 'd' << 24 | 'a' << 16 | 't' << 8 | 'a', // 0x64617461 94 | 95 | Movie = 'm' << 24 | 'o' << 16 | 'o' << 8 | 'v', // 0x6d6f6f76 96 | Movie_Header = 'm' << 24 | 'v' << 16 | 'h' << 8 | 'd', // 0x6d766864 97 | 98 | Track = 't' << 24 | 'r' << 16 | 'a' << 8 | 'k', // 0x7472616b 99 | Edit = 'e' << 24 | 'd' << 16 | 't' << 8 | 's', // 0x65647473 100 | Edit_List = 'e' << 24 | 'l' << 16 | 's' << 8 | 't', // 0x656c7374 101 | Media = 'm' << 24 | 'd' << 16 | 'i' << 8 | 'a', // 0x6d646961 102 | Handler_Reference = 'h' << 24 | 'd' << 16 | 'l' << 8 | 'r', // 0x68646c72 103 | Media_Header = 'm' << 24 | 'd' << 16 | 'h' << 8 | 'd', // 0x6d646864 104 | Media_Information = 'm' << 24 | 'i' << 16 | 'n' << 8 | 'f', // 0x6d696e66 105 | Track_Header = 't' << 24 | 'k' << 16 | 'h' << 8 | 'd', // 0x746b6864 106 | 107 | User_Data = 'u' << 24 | 'd' << 16 | 't' << 8 | 'a', // 0x75647461 108 | Meta = 'm' << 24 | 'e' << 16 | 't' << 8 | 'a', // 0x6d657461 109 | /* 110 | Apple Metadata. 111 | Not part of ISO 14496-12-2015. 112 | */ 113 | iTunes_Metadata = 'i' << 24 | 'l' << 16 | 's' << 8 | 't', // 0x696c7374 114 | 115 | iTunes_Album = '©' << 24 | 'a' << 16 | 'l' << 8 | 'b', // 0xa9616c62 116 | iTunes_Album_Artist = 'a' << 24 | 'A' << 16 | 'R' << 8 | 'T', // 0x61415254 117 | iTunes_Author = '©' << 24 | 'A' << 16 | 'R' << 8 | 'T', // 0xa9415254 118 | ìTunes_Category = 'c' << 24 | 'a' << 16 | 't' << 8 | 'g', // 0x63617467 119 | iTunes_Comment = '©' << 24 | 'c' << 16 | 'm' << 8 | 't', // 0xa9636d74 120 | iTunes_Composer = '©' << 24 | 'w' << 16 | 'r' << 8 | 't', // 0xa9777274 121 | iTunes_Copyright = 'c' << 24 | 'p' << 16 | 'r' << 8 | 't', // 0x63707274 122 | iTunes_Copyright_Alt = '©' << 24 | 'c' << 16 | 'p' << 8 | 'y', // 0xa9637079 123 | iTunes_Cover = 'c' << 24 | 'o' << 16 | 'v' << 8 | 'r', // 0x636f7672 124 | iTunes_Description = 'd' << 24 | 'e' << 16 | 's' << 8 | 'c', // 0x64657363 125 | iTunes_Disk = 'd' << 24 | 'i' << 16 | 's' << 8 | 'k', // 0x6469736b 126 | iTunes_Encoder = '©' << 24 | 't' << 16 | 'o' << 8 | 'o', // 0xa9746f6f 127 | ìTunes_Episode_GUID = 'e' << 24 | 'g' << 16 | 'i' << 8 | 'd', // 0x65676964 128 | iTunes_Episode_Name = 't' << 24 | 'v' << 16 | 'e' << 8 | 'n', // 0x7476656e 129 | ìTunes_Gapless_Playback = 'p' << 24 | 'g' << 16 | 'a' << 8 | 'p', // 0x70676170 130 | iTunes_Genre = '©' << 24 | 'g' << 16 | 'e' << 8 | 'n', // 0xa967656e 131 | iTunes_Grouping = '©' << 24 | 'g' << 16 | 'r' << 8 | 'p', // 0xa9677270 132 | ìTunes_Keywords = 'k' << 24 | 'e' << 16 | 'y' << 8 | 'w', // 0x6b657977 133 | ìTunes_Lyricist = '©' << 24 | 's' << 16 | 'w' << 8 | 'f', // 0xa9737766 134 | iTunes_Lyrics = '©' << 24 | 'l' << 16 | 'y' << 8 | 'r', // 0xa96c7972 135 | ìTunes_Media_Type = 's' << 24 | 't' << 16 | 'i' << 8 | 'k', // 0x7374696b 136 | iTunes_Network = 't' << 24 | 'v' << 16 | 'n' << 8 | 'n', // 0x74766e6e 137 | ìTunes_Performers = '©' << 24 | 'p' << 16 | 'r' << 8 | 'f', // 0xa9707266 138 | ìTunes_Podcast = 'p' << 24 | 'c' << 16 | 's' << 8 | 't', // 0x70637374 139 | ìTunes_Podcast_URL = 'p' << 24 | 'u' << 16 | 'r' << 8 | 'l', // 0x7075726c 140 | ìTunes_Predefined_Genre = 'g' << 24 | 'n' << 16 | 'r' << 8 | 'e', // 0x676e7265 141 | ìTunes_Producer = '©' << 24 | 'p' << 16 | 'r' << 8 | 'd', // 0xa9707264 142 | ìTunes_Purchase_Date = 'p' << 24 | 'u' << 16 | 'r' << 8 | 'd', // 0x70757264 143 | ìTunes_Rating = 'r' << 24 | 't' << 16 | 'n' << 8 | 'g', // 0x72746e67 144 | ìTunes_Record_Label = '©' << 24 | 'l' << 16 | 'a' << 8 | 'b', // 0xa96c6162 145 | iTunes_Role = 'r' << 24 | 'o' << 16 | 'l' << 8 | 'e', // 0x726f6c65 146 | iTunes_Show = 't' << 24 | 'v' << 16 | 's' << 8 | 'h', // 0x74767368 147 | iTunes_Synopsis = 'l' << 24 | 'd' << 16 | 'e' << 8 | 's', // 0x6c646573 148 | iTunes_Tempo = 't' << 24 | 'm' << 16 | 'p' << 8 | 'o', // 0x746d706f 149 | iTunes_Title = '©' << 24 | 'n' << 16 | 'a' << 8 | 'm', // 0xa96e616d 150 | iTunes_Track = 't' << 24 | 'r' << 16 | 'k' << 8 | 'n', // 0x74726b6e 151 | iTunes_Year = '©' << 24 | 'd' << 16 | 'a' << 8 | 'y', // 0xa9646179 152 | ìTunes_TV_Episode = 't' << 24 | 'v' << 16 | 'e' << 8 | 's', // 0x74766573 153 | ìTunes_TV_Season = 't' << 24 | 'v' << 16 | 's' << 8 | 'n', // 0x7476736e 154 | 155 | /* 156 | Special. Found at the end of M4A audio book metadata, for example. 157 | `----` has no data sub-node. 158 | */ 159 | iTunes_Extended = '-' << 24 | '-' << 16 | '-' << 8 | '-', // 0x2d2d2d2d 160 | iTunes_Mean = 'm' << 24 | 'e' << 16 | 'a' << 8 | 'n', // 161 | 162 | Movie_Fragment = 'm' << 24 | 'o' << 16 | 'o' << 8 | 'f', // 0x6d6f6f66 163 | Track_Fragment = 't' << 24 | 'r' << 16 | 'a' << 8 | 'f', // 0x74726166 164 | Additional_Metadata_Container = 'm' << 24 | 'e' << 16 | 'c' << 8 | 'o', // 0x6d65636f 165 | 166 | Media_Data = 'm' << 24 | 'd' << 16 | 'a' << 8 | 't', // 0x6d646174 167 | 168 | Chapter_List = 'c' << 24 | 'h' << 16 | 'p' << 8 | 'l', // 0x6368706c 169 | Name = 'n' << 24 | 'a' << 16 | 'm' << 8 | 'e', // 0x6e616d65 170 | Padding = 'f' << 24 | 'r' << 16 | 'e' << 8 | 'e', // 0x66726565 171 | UUID = 'u' << 24 | 'u' << 16 | 'i' << 8 | 'd', // 0x75756964 172 | 173 | /* 174 | Brands 175 | */ 176 | isom = 'i' << 24 | 's' << 16 | 'o' << 8 | 'm', // 0x69736f6d 177 | iso2 = 'i' << 24 | 's' << 16 | 'o' << 8 | '2', // 0x69736f32 178 | iso3 = 'i' << 24 | 's' << 16 | 'o' << 8 | '3', // 0x69736f33 179 | iso4 = 'i' << 24 | 's' << 16 | 'o' << 8 | '4', // 0x69736f34 180 | iso5 = 'i' << 24 | 's' << 16 | 'o' << 8 | '5', // 0x69736f35 181 | iso6 = 'i' << 24 | 's' << 16 | 'o' << 8 | '6', // 0x69736f36 182 | iso7 = 'i' << 24 | 's' << 16 | 'o' << 8 | '7', // 0x69736f37 183 | iso8 = 'i' << 24 | 's' << 16 | 'o' << 8 | '8', // 0x69736f38 184 | iso9 = 'i' << 24 | 's' << 16 | 'o' << 8 | '9', // 0x69736f39 185 | 186 | avc1 = 'a' << 24 | 'v' << 16 | 'c' << 8 | '1', // 0x61766331 187 | mp41 = 'm' << 24 | 'p' << 16 | '4' << 8 | '1', // 0x6d703431 188 | mp42 = 'm' << 24 | 'p' << 16 | '4' << 8 | '2', // 0x6d703432 189 | mp71 = 'm' << 24 | 'p' << 16 | '7' << 8 | '1', // 0x6d703731 190 | 191 | m4a_ = 'm' << 24 | '4' << 16 | 'a' << 8 | ' ', // 0x4d344120 192 | 193 | /* 194 | Handler types 195 | */ 196 | Video = 'v' << 24 | 'i' << 16 | 'd' << 8 | 'e', // 0x76696465 197 | Sound = 's' << 24 | 'o' << 16 | 'u' << 8 | 'n', // 0x736f756e 198 | Text = 't' << 24 | 'e' << 16 | 'x' << 8 | 't', // 0x74657874 199 | } 200 | 201 | /* 202 | UUIDs are compliant with RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace 203 | (https://www.rfc-editor.org/rfc/rfc4122.html) 204 | */ 205 | UUID :: common.UUID_RFC_4122 206 | 207 | Fixed_16_16 :: distinct u32be 208 | Fixed_2_30 :: distinct u32be 209 | Fixed_8_8 :: distinct u16be 210 | Rational_16_16 :: distinct [2]u16be 211 | ISO_639_2 :: distinct u16be 212 | 213 | View_Matrix :: struct #packed { 214 | a: Fixed_16_16, b: Fixed_16_16, u: Fixed_2_30, 215 | c: Fixed_16_16, d: Fixed_16_16, v: Fixed_2_30, 216 | x: Fixed_16_16, y: Fixed_16_16, w: Fixed_2_30, 217 | } 218 | #assert(size_of(View_Matrix) == 9 * size_of(u32be)) 219 | 220 | MVHD_Predefined :: struct { 221 | foo: [6]u32be, 222 | } -------------------------------------------------------------------------------- /bmff/bmff.odin: -------------------------------------------------------------------------------- 1 | package iso_bmff 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | A from-scratch implementation of ISO base media file format (ISOM), 30 | as specified in ISO/IEC 14496-12, Fifth edition 2015-12-15. 31 | The identical text is available as ISO/IEC 15444-12 (JPEG 2000, Part 12). 32 | 33 | See: https://www.iso.org/standard/68960.html and https://www.loc.gov/preservation/digital/formats/fdd/fdd000079.shtml 34 | 35 | This file contains the base media format parser. 36 | */ 37 | 38 | import os "core:os/os2" 39 | @(require) import "core:fmt" 40 | import "../common" 41 | 42 | DEBUG :: #config(BMFF_DEBUG, false) 43 | DEBUG_VERBOSE :: DEBUG && #config(BMFF_DEBUG_VERBOSE, false) 44 | 45 | intern_payload :: proc(box: ^BMFF_Box, payload: $T, loc := #caller_location) { 46 | when T == []u8 { 47 | box.payload = [dynamic]u8{} 48 | append(&box.payload.([dynamic]u8), ..payload) 49 | 50 | } else { 51 | fmt.panicf("Unhandled: intern_payload(%v), called from %v\n", typeid_of(T), loc) 52 | } 53 | } 54 | 55 | free_atom :: proc(atom: ^BMFF_Box, allocator := context.allocator) { 56 | atom := atom 57 | 58 | for atom != nil { 59 | when DEBUG_VERBOSE { 60 | fmt.printfln("Freeing '%v' (0x%08x).", _string(atom.type), int(atom.type)) 61 | } 62 | 63 | if atom.payload != nil { 64 | switch v in atom.payload { 65 | 66 | case [dynamic]u8: delete(v) 67 | case ELST_V0: delete(v.entries) 68 | case ELST_V1: delete(v.entries) 69 | case FTYP: delete(v.compatible) 70 | case HDLR: delete(v.name) 71 | 72 | // iTunes metadata types 73 | case iTunes_Metadata: 74 | switch w in v.data { 75 | case cstring: delete(w) 76 | case [dynamic]u8: delete(w) 77 | case iTunes_Track, iTunes_Disk: // Nothing to free. 78 | } 79 | 80 | case Chapter_List: 81 | for chapter in v.chapters { 82 | delete(chapter.title) 83 | } 84 | delete(v.chapters) 85 | 86 | // These are just structs with no allocated items to free. 87 | case MDHD_V0, MDHD_V1: 88 | case MVHD_V0, MVHD_V1: 89 | case TKHD_V0, TKHD_V1: 90 | case iTunes_Track, iTunes_Disk: 91 | case: fmt.panicf("free_atom: Unhandled payload type: %v\n", v) 92 | } 93 | } 94 | 95 | if atom.first_child != nil { 96 | free_atom(atom.first_child) 97 | } 98 | 99 | ptr_to_free := atom 100 | atom = atom.next 101 | free(ptr_to_free) 102 | } 103 | } 104 | 105 | parse_itunes_metadata :: proc(f: ^BMFF_File) -> (err: Error) { 106 | context.allocator = f.allocator 107 | 108 | assert(f.itunes_metadata != nil) 109 | when DEBUG { 110 | fmt.println("\nCalling specialized iTunes metadata parser...") 111 | defer fmt.println("Back from specialized iTunes metadata parser...\n") 112 | } 113 | 114 | fd := f.handle 115 | 116 | h: BMFF_Box_Header 117 | box: ^BMFF_Box 118 | prev: ^BMFF_Box = f.itunes_metadata 119 | parent: ^BMFF_Box = f.itunes_metadata 120 | 121 | loop: for { 122 | // Peek at header and check if this would put us past the end of the iTunes metadata. 123 | h = read_box_header(fd=fd, read=false) or_return 124 | if h.offset >= f.root.end || h.offset > f.itunes_metadata.end { 125 | // Done. 126 | return 127 | } 128 | 129 | // Now read it for real. 130 | h = read_box_header(fd=fd, read=true) or_return 131 | 132 | // Create box and set type, size, parent, etc. 133 | #partial switch h.type { 134 | case .Data: 135 | if parent == f.itunes_metadata { 136 | // Fold data into parent. 137 | metadata := iTunes_Metadata{} 138 | 139 | metadata._ilst_data = common.read_data(fd, _ILST_DATA) or_return 140 | 141 | payload := common.read_slice(fd, h.payload_size - size_of(_ILST_DATA), f.allocator) or_return 142 | 143 | #partial switch metadata.type { 144 | case .Text: 145 | metadata.data = cstring(raw_data(payload)) 146 | 147 | case: // Binary, JPEG, PNG, ... 148 | #partial switch prev.type { 149 | case .iTunes_Track: 150 | if len(payload) != size_of(iTunes_Track) { return .Wrong_File_Format } 151 | data := (^iTunes_Track)(raw_data(payload))^ 152 | metadata.data = data 153 | delete(payload) 154 | 155 | case .iTunes_Disk: 156 | if len(payload) != size_of(iTunes_Disk) { return .Wrong_File_Format } 157 | data := (^iTunes_Disk)(raw_data(payload))^ 158 | metadata.data = data 159 | delete(payload) 160 | 161 | case: 162 | metadata.data = [dynamic]u8{} 163 | append(&metadata.data.([dynamic]u8), ..payload) 164 | delete(payload) 165 | } 166 | } 167 | 168 | box.payload = metadata 169 | 170 | prev = box 171 | continue loop 172 | } 173 | 174 | parent = prev 175 | 176 | case .iTunes_Mean: 177 | parent = prev 178 | case: 179 | parent = f.itunes_metadata 180 | } 181 | 182 | box = new(BMFF_Box) 183 | box.header = h 184 | box.parent = parent 185 | 186 | // Chain it. 187 | if parent.first_child == nil { 188 | // We're either our parent's first child... 189 | parent.first_child = box 190 | } else { 191 | // Or we walk our siblings until its next pointer is nil. 192 | sibling: ^BMFF_Box 193 | for sibling = parent.first_child; sibling.next != nil; sibling = sibling.next {} 194 | sibling.next = box 195 | } 196 | 197 | when DEBUG { 198 | level := 1 if box.parent == f.itunes_metadata else 2 199 | print_box_header(box, level) 200 | } 201 | 202 | #partial switch box.type { 203 | case .Data: 204 | /* 205 | Apple iTunes mdir metadata tag. 206 | Found as a child of the various tags under: `moov.udta.meta.ilst` 207 | */ 208 | skip := true 209 | if f.itunes_metadata != nil { 210 | // We parse if we've previously located the property bag. 211 | if parent.parent == f.itunes_metadata { 212 | skip = false 213 | payload := common.read_slice(fd, box.payload_size) or_return 214 | intern_payload(box, payload) 215 | } 216 | } 217 | 218 | if skip { 219 | skip_box(fd, box) or_return 220 | } 221 | 222 | case .iTunes_Extended: 223 | payload := common.read_slice(fd, box.payload_size) or_return 224 | intern_payload(box, payload) 225 | } 226 | 227 | prev = box 228 | } 229 | return 230 | } 231 | 232 | parse :: proc(f: ^BMFF_File, parse_metadata := true) -> (err: Error) { 233 | context.allocator = f.allocator 234 | 235 | when DEBUG { 236 | fmt.println("\nParsing...") 237 | defer fmt.println("\nBack from parsing...") 238 | } 239 | 240 | fd := f.handle 241 | 242 | h: BMFF_Box_Header 243 | box: ^BMFF_Box 244 | prev: ^BMFF_Box = f.root 245 | parent: ^BMFF_Box = f.root 246 | 247 | // Most files start with an 'ftyp' atom. 248 | h = read_box_header(fd=fd, read=false) or_return 249 | if h.type != .File_Type { 250 | // NOTE(Jeroen): Files with no file‐type box should be read as if they contained an FTYP box with 251 | // Major_brand='mp41', minor_version=0, and the single compatible brand `mp41`. 252 | box = new(BMFF_Box) 253 | box.size = 0 254 | box.type = .File_Type 255 | box.parent = f.root 256 | f.root.first_child = box 257 | 258 | ftyp := FTYP{ 259 | header = { .mp41, 0, }, 260 | } 261 | append(&ftyp.compatible, FourCC.mp41) 262 | intern_payload(box, ftyp) 263 | } 264 | 265 | loop: for { 266 | h = read_box_header(fd=fd, read=true) or_return 267 | if h.offset >= f.root.size { 268 | // Done 269 | return 270 | } 271 | 272 | // Find the parent by what byte range of the file we're at. 273 | parent = prev 274 | for { 275 | if h.offset >= parent.end { 276 | // Parent can't contain this box. Let's look at its parent. 277 | when DEBUG_VERBOSE { 278 | fmt.printf("\t[%v] ends past ", _string(h.type)) 279 | fmt.printf("[%v] end, checking if ", _string(parent.type)) 280 | fmt.printf("[%v] is our parent.\n", _string(parent.parent.type)) 281 | } 282 | parent = parent.parent 283 | } else { 284 | // Box fits within this parent. 285 | break 286 | } 287 | } 288 | 289 | // Create box and set type, size, parent, etc. 290 | box = new(BMFF_Box) 291 | box.header = h 292 | box.parent = parent 293 | 294 | // Chain it. 295 | if parent.first_child == nil { 296 | // We're either our parent's first child... 297 | parent.first_child = box 298 | } else { 299 | // Or we walk our siblings until its next pointer is `nil`. 300 | sibling: ^BMFF_Box 301 | for sibling = parent.first_child; sibling.next != nil; sibling = sibling.next {} 302 | sibling.next = box 303 | } 304 | 305 | if box.end > f.root.size { 306 | when DEBUG { 307 | fmt.printf("\t[%v] ended early, expected to end at %v.\n", _string(h.type), box.end) 308 | } 309 | return .File_Ended_Early 310 | } 311 | 312 | when DEBUG { 313 | level := 0 314 | for p := box.parent; p != f.root; p = p.parent { level += 1 } 315 | print_box_header(box, level) 316 | } 317 | 318 | #partial switch h.type { 319 | case .File_Type: 320 | // `ftyp` must always be the first child and we can't have two nodes of this type. 321 | if f.root.first_child != box { 322 | return .FTYP_Duplicated 323 | } 324 | f.ftyp = box 325 | 326 | if box.payload_size % size_of(FourCC) != 0 || box.payload_size < size_of(_FTYP) { 327 | /* 328 | Remaining: 329 | - Major Brand: FourCC 330 | - Minor Brand: BCD4 331 | - ..FourCC 332 | 333 | All have the same size, so the remaining length of this box should cleanly divide by `size_of(FourCC)`. 334 | */ 335 | return .FTYP_Invalid_Size 336 | } 337 | 338 | _ftyp := common.read_data(fd, _FTYP) or_return 339 | ftyp := FTYP{ header = _ftyp, } 340 | 341 | compat_count := (box.payload_size - size_of(_FTYP)) / size_of(FourCC) 342 | for _ in 0.. 1 { return .MVHD_Unknown_Version } 356 | 357 | switch version { 358 | case 0: 359 | if box.payload_size != size_of(MVHD_V0) { return .MVHD_Invalid_Size } 360 | box.payload = common.read_data(fd, MVHD_V0) or_return 361 | f.time_scale = box.payload.(MVHD_V0).time_scale 362 | case 1: 363 | if box.payload_size != size_of(MVHD_V1) { return .MVHD_Invalid_Size } 364 | box.payload = common.read_data(fd, MVHD_V1) or_return 365 | f.time_scale = box.payload.(MVHD_V1).time_scale 366 | case: 367 | unreachable() 368 | } 369 | 370 | case .Track: 371 | 372 | case .Track_Header: 373 | version := common.peek_u8(fd) or_return 374 | if version > 1 { return .TKHD_Unknown_Version } 375 | 376 | switch version { 377 | case 0: 378 | if box.payload_size != size_of(TKHD_V0) { return .TKHD_Invalid_Size } 379 | box.payload = common.read_data(fd, TKHD_V0) or_return 380 | case 1: 381 | if box.payload_size != size_of(TKHD_V1) { return .TKHD_Invalid_Size } 382 | box.payload = common.read_data(fd, TKHD_V1) or_return 383 | case: 384 | unreachable() 385 | } 386 | 387 | case .Edit: 388 | 389 | case .Edit_List: 390 | version := common.peek_u8(fd) or_return 391 | if version > 1 { return .ELST_Unknown_Version } 392 | 393 | elst_hdr := common.read_data(fd, _ELST) or_return 394 | 395 | switch version { 396 | case 0: 397 | if box.payload_size != i64(size_of(_ELST)) + i64(elst_hdr.entry_count) * size_of(ELST_Entry_V0) { return .ELST_Invalid_Size } 398 | 399 | elst := ELST_V0{ header = elst_hdr } 400 | for _ in 0.. 1 { return .MDHD_Unknown_Version } 423 | 424 | switch version { 425 | case 0: 426 | if box.payload_size != size_of(MDHD_V0) { return .MDHD_Invalid_Size } 427 | box.payload = common.read_data(fd, MDHD_V0) or_return 428 | case 1: 429 | if box.payload_size != size_of(MDHD_V1) { return .MDHD_Invalid_Size } 430 | box.payload = common.read_data(fd, MDHD_V1) or_return 431 | case: 432 | unreachable() 433 | } 434 | 435 | case .Handler_Reference: 436 | /* 437 | ISO 14496-12-2015, section 8.4.3.1: 438 | `hdlr` may be contained in a `mdia` or `meta` box. 439 | */ 440 | if !(box.parent.type == .Media || box.parent.type == .Meta) { 441 | return .HDLR_Unexpected_Parent 442 | } 443 | if box.payload_size < size_of(_HDLR) { return .HDLR_Invalid_Size } 444 | 445 | _hdlr := common.read_data(fd, _HDLR) or_return 446 | hdlr := HDLR { _hdlr = _hdlr } 447 | 448 | name_bytes := common.read_slice(fd, box.payload_size - size_of(_HDLR), f.allocator) or_return 449 | hdlr.name = cstring(raw_data(name_bytes)) 450 | box.payload = hdlr 451 | 452 | case .User_Data: 453 | if !(box.parent.type == .Movie || box.parent.type == .Movie_Fragment || box.parent.type == .Track || box.parent.type == .Track_Fragment) { 454 | return .Wrong_File_Format 455 | } 456 | 457 | case .Meta: 458 | payload := common.read_slice(fd, size_of(META)) or_return 459 | intern_payload(box, payload) 460 | 461 | case .iTunes_Metadata: 462 | f.itunes_metadata = box 463 | 464 | if parse_metadata { 465 | // Apple Metadata. Not part of the ISO standard, but we'll handle it anyway. 466 | parse_itunes_metadata(f) or_return 467 | } else { 468 | skip_box(fd, box) or_return 469 | } 470 | case .Name: 471 | if parent.type == .User_Data { 472 | payload := common.read_slice(fd, box.payload_size) or_return 473 | intern_payload(box, payload) 474 | 475 | } else { 476 | skip_box(fd, box) or_return 477 | } 478 | 479 | case .Chapter_List: 480 | if parent.type == .User_Data { 481 | vf := common.read_data(fd, Version_and_Flags) or_return 482 | if vf.version > 1 { return .CHPL_Unknown_Version } 483 | 484 | entry_count: u32be 485 | 486 | if vf.version == 0 { 487 | short_count := common.read_u8(fd) or_return 488 | entry_count = u32be(short_count) 489 | } else { 490 | common.read_u8(fd) or_return // Skip reserved field 491 | entry_count = common.read_data(fd, u32be) or_return 492 | } 493 | 494 | chapter_list := Chapter_List{} 495 | for i := u32be(0); i < entry_count; i += 1 { 496 | _entry := common.read_data(fd, _Chapter_Entry) or_return 497 | title_bytes := common.read_slice(fd, _entry.title_size, f.allocator) or_return 498 | entry := Chapter_Entry{ 499 | timestamp = _entry.timestamp, 500 | title = string(title_bytes), 501 | } 502 | append(&chapter_list.chapters, entry) 503 | } 504 | box.payload = chapter_list 505 | 506 | // Read cursor should be one past the end of the expected box end. 507 | cur_pos := common.get_pos(fd) or_return 508 | if !(cur_pos == box.end + 1) { return .CHPL_Invalid_Size } 509 | } else { 510 | skip_box(fd, box) or_return 511 | } 512 | 513 | case .Media_Data: 514 | f.mdat = box 515 | skip_box(fd, box) or_return 516 | 517 | // Boxes we don't (want to or can yet) parse, we skip. 518 | case: 519 | if box.end >= i64(f.root.size) { break loop } 520 | skip_box(fd, box) or_return 521 | when DEBUG_VERBOSE { 522 | fmt.printf("[SKIP]", box) 523 | } 524 | } 525 | 526 | prev = box 527 | } 528 | return 529 | } 530 | 531 | skip_box :: proc(fd: ^os.File, box: ^BMFF_Box) -> (err: Error) { 532 | assert(box != nil) 533 | common.set_pos(fd, box.end + 1) or_return 534 | return 535 | } 536 | 537 | read_box_header :: #force_inline proc(fd: ^os.File, read := true) -> (header: BMFF_Box_Header, err: Error) { 538 | h: _BMFF_Box_Header 539 | 540 | header.offset = common.get_pos(fd) or_return 541 | header.payload_offset = header.offset 542 | 543 | /* 544 | Read the basic box header. 545 | */ 546 | h = common.read_data(fd, _BMFF_Box_Header) or_return 547 | 548 | header.size = i64(h.size) 549 | header.type = h.type 550 | header.payload_offset += size_of(_BMFF_Box_Header) 551 | 552 | if header.size == 1 { 553 | // This atom has a 64-bit size. 554 | hsize := common.read_data(fd, u64be) or_return 555 | header.payload_offset += size_of(u64be) 556 | header.size = i64(hsize) 557 | 558 | } else if header.size == 0 { 559 | // This atom runs until the end of the file. 560 | file_size := os.file_size(fd) or_return 561 | header.size = file_size - header.offset 562 | } 563 | 564 | header.end = header.offset + header.size - 1 565 | 566 | if header.type == .UUID { 567 | // Read extended type. 568 | header.uuid = common.read_data(fd, UUID) or_return 569 | header.payload_offset += size_of(UUID) 570 | } 571 | 572 | header.payload_size = header.end - header.payload_offset + 1 573 | 574 | when DEBUG_VERBOSE { 575 | verb := "read_box_header" if read else "peek_box_header" 576 | if header.type == .uuid { 577 | fmt.printf("[%v] 'uuid' (%v) Size: %v\n", verb, _string(header.uuid), header.size) 578 | } else { 579 | fmt.printf("[%v] '%v' (0x%08x) Size: %v\n", verb, _string(FourCC(header.type)), int(header.type), header.size) 580 | } 581 | } 582 | 583 | // Rewind if peeking. 584 | if !read { 585 | common.set_pos(fd, header.offset) or_return 586 | } 587 | 588 | return 589 | } 590 | 591 | open_from_filename :: proc(filename: string, allocator := context.allocator) -> (file: ^BMFF_File, err: Error) { 592 | context.allocator = allocator 593 | 594 | fd, os_err := os.open(filename, os.O_RDONLY, 0) 595 | if os_err == nil { 596 | return open_from_handle(fd, allocator) 597 | } 598 | return {}, .File_Not_Found 599 | } 600 | 601 | open_from_handle :: proc(handle: ^os.File, allocator := context.allocator) -> (file: ^BMFF_File, err: Error) { 602 | context.allocator = allocator 603 | 604 | file = new(BMFF_File, allocator) 605 | file.allocator = allocator 606 | file.handle = handle 607 | file.file_info = os.fstat(handle, allocator) or_return 608 | 609 | if file.file_info.size == 0 { 610 | close(file) 611 | return file, .File_Empty 612 | } 613 | 614 | file.root = new(BMFF_Box, allocator) 615 | file.root.offset = 0 616 | file.root.size = file.file_info.size 617 | file.root.end = file.file_info.size - 1 618 | file.root.type = .Root 619 | file.root.payload_offset = 0 620 | file.root.payload_size = file.file_info.size 621 | 622 | return 623 | } 624 | 625 | open :: proc { open_from_filename, open_from_handle, } 626 | 627 | close :: proc(file: ^BMFF_File) { 628 | if file == nil { 629 | return 630 | } 631 | 632 | context.allocator = file.allocator 633 | 634 | when DEBUG_VERBOSE { 635 | fmt.println("\n-=-=-=-=-=-=- CLEANING UP -=-=-=-=-=-=-") 636 | } 637 | 638 | os.file_info_delete(file.file_info, file.allocator) 639 | if file.handle != nil { 640 | os.close(file.handle) 641 | } 642 | if file.root != nil { 643 | free_atom(file.root, file.allocator) 644 | } 645 | free(file) 646 | 647 | when DEBUG_VERBOSE { 648 | fmt.println("-=-=-=-=-=-=- CLEANED UP -=-=-=-=-=-=-") 649 | } 650 | } -------------------------------------------------------------------------------- /bmff/box_types.odin: -------------------------------------------------------------------------------- 1 | package iso_bmff 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | A from-scratch implementation of ISO base media file format (ISOM), 30 | as specified in ISO/IEC 14496-12, Fifth edition 2015-12-15. 31 | The identical text is available as ISO/IEC 15444-12 (JPEG 2000, Part 12). 32 | 33 | See: https://www.iso.org/standard/68960.html and https://www.loc.gov/preservation/digital/formats/fdd/fdd000079.shtml 34 | 35 | This file contains box type definitions. 36 | */ 37 | import os "core:os/os2" 38 | import "core:mem" 39 | 40 | /* 41 | On-disk structures are prefixed with a `_`. 42 | The parsed versions lack this prefix. If they're the same, there's no prefixed version. 43 | */ 44 | 45 | _BMFF_Box_Header :: struct { 46 | size: u32be, 47 | type: FourCC, 48 | } 49 | #assert(size_of(_BMFF_Box_Header) == 8) 50 | 51 | BMFF_Box_Header :: struct { 52 | // Box file offset, and size including header. 53 | offset: i64, 54 | size: i64, 55 | end: i64, 56 | 57 | payload_offset: i64, 58 | payload_size: i64, 59 | 60 | type: FourCC, 61 | uuid: UUID, 62 | } 63 | 64 | Payload_Types :: union { 65 | [dynamic]u8, 66 | ELST_V0, ELST_V1, 67 | FTYP, 68 | HDLR, 69 | MVHD_V0, MVHD_V1, 70 | MDHD_V0, MDHD_V1, 71 | TKHD_V0, TKHD_V1, 72 | 73 | Chapter_List, 74 | iTunes_Metadata, 75 | iTunes_Track, 76 | iTunes_Disk, 77 | } 78 | 79 | BMFF_Box :: struct { 80 | using header: BMFF_Box_Header, 81 | 82 | parent: ^BMFF_Box, 83 | next: ^BMFF_Box, 84 | first_child: ^BMFF_Box, 85 | 86 | // Payload can be empty 87 | payload: Payload_Types, 88 | } 89 | 90 | BMFF_File :: struct { 91 | // Root atom 92 | root: ^BMFF_Box, 93 | 94 | // Important atoms 95 | ftyp: ^BMFF_Box, 96 | moov: ^BMFF_Box, 97 | mvhd: ^BMFF_Box, 98 | mdat: ^BMFF_Box, 99 | 100 | /* 101 | Apple Metadata is not specified in ISO 14496-12-2015. 102 | Nevertheless, we add support for it. 103 | 104 | If `moov.udta.meta.hdlr` == `mdir/appl`, then `itunes_metadata` is set to `moov.udta.meta.ilst` 105 | */ 106 | itunes_metadata: ^BMFF_Box, 107 | 108 | // Useful file members 109 | time_scale: u32be, 110 | 111 | // Implementation 112 | file_info: os.File_Info, 113 | handle: ^os.File, 114 | allocator: mem.Allocator, 115 | } 116 | 117 | // Files that don't start with 'ftyp' have this synthetic one. 118 | _FTYP :: struct { 119 | brand: FourCC, 120 | version: BCD4, 121 | } 122 | 123 | FTYP :: struct { 124 | using header: _FTYP, 125 | compatible: [dynamic]FourCC, 126 | } 127 | 128 | Header_Flag :: enum u8 { 129 | track_enabled = 0, 130 | track_in_movie = 1, 131 | track_size_is_aspect_ratio = 2, 132 | } 133 | Header_Flags :: bit_set[Header_Flag; u8] 134 | 135 | Version_and_Flags :: struct #packed { 136 | version: u8, 137 | flag: [2]u8, 138 | flags: Header_Flags, 139 | } 140 | 141 | Times :: struct($T: typeid) #packed { 142 | creation_time: T, 143 | modification_time: T, 144 | } 145 | 146 | MVHD :: struct($T: typeid) #packed { 147 | using _vf: Version_and_Flags, 148 | using _times: Times(T), 149 | time_scale: u32be, 150 | duration: T, 151 | preferred_rate: Fixed_16_16, 152 | preferred_volume: Fixed_8_8, 153 | _reserved: [10]u8 `fmt:"-"`, 154 | view_matrix: View_Matrix, 155 | predefined: MVHD_Predefined, 156 | next_track_id: u32be, 157 | } 158 | MVHD_V0 :: distinct MVHD(u32be) 159 | MVHD_V1 :: distinct MVHD(u64be) 160 | #assert(size_of(MVHD_V0) == 100 && size_of(MVHD_V1) == 112) 161 | 162 | TKHD :: struct($T: typeid) #packed { 163 | using _vf: Version_and_Flags, 164 | using _times: Times(T), 165 | track_id: u32be, 166 | _reserved_1: u32be, 167 | duration: T, 168 | 169 | _reserved_2: [2]u32be `fmt:"-"`, 170 | layer: i16be, 171 | alternate_group: i16be, 172 | volume: Fixed_8_8, 173 | reserved_3: u16be, 174 | view_matrix: View_Matrix, 175 | width: Fixed_16_16, 176 | height: Fixed_16_16, 177 | } 178 | TKHD_V0 :: distinct TKHD(u32be) 179 | TKHD_V1 :: distinct TKHD(u64be) 180 | #assert(size_of(TKHD_V0) == 84 && size_of(TKHD_V1) == 96) 181 | 182 | _ELST :: struct #packed { 183 | using _vf: Version_and_Flags, 184 | entry_count: u32be, 185 | } 186 | #assert(size_of(_ELST) == 8) 187 | 188 | ELST_Entry :: struct($T: typeid) #packed { 189 | segment_duration: T, 190 | media_time: i32be, 191 | media_rate: Rational_16_16, 192 | } 193 | ELST_Entry_V0 :: distinct ELST_Entry(u32be) 194 | ELST_Entry_V1 :: distinct ELST_Entry(u64be) 195 | #assert(size_of(ELST_Entry_V0) == 12 && size_of(ELST_Entry_V1) == 16) 196 | 197 | ELST :: struct($T: typeid) #packed { 198 | using header: _ELST, 199 | entries: [dynamic]ELST_Entry(T), 200 | } 201 | ELST_V0 :: distinct ELST(u32be) 202 | ELST_V1 :: distinct ELST(u64be) 203 | 204 | MDHD :: struct($T: typeid) #packed { 205 | using _vf: Version_and_Flags, 206 | using _times: Times(T), 207 | time_scale: u32be, 208 | duration: T, 209 | language: ISO_639_2, // ISO-639-2/T language code 210 | quality: u16be, 211 | } 212 | MDHD_V0 :: distinct MDHD(u32be) 213 | MDHD_V1 :: distinct MDHD(u64be) 214 | #assert(size_of(MDHD_V0) == 24 && size_of(MDHD_V1) == 36) 215 | 216 | _HDLR :: struct #packed { 217 | using _vf: Version_and_Flags, 218 | component_type: FourCC, 219 | component_subtype: FourCC, 220 | component_manufacturer: FourCC, 221 | component_flags: u32be, 222 | reserved: u32be, 223 | } 224 | #assert(size_of(_HDLR) == 24) 225 | 226 | HDLR :: struct #packed { 227 | using _hdlr: _HDLR, 228 | name: cstring, 229 | } 230 | 231 | META :: struct #packed { 232 | using vf: Version_and_Flags, 233 | } 234 | #assert(size_of(META) == 4) 235 | 236 | ILST_DATA_Type :: enum u32be { 237 | Binary = 0, // e.g. moov.udta.meta.ilst.trkn.data 238 | Text = 1, 239 | 240 | JPEG = 13, // moov.udta.meta.ilst.covr.data 241 | PNG = 14, // moov.udta.meta.ilst.covr.data 242 | } 243 | 244 | /* 245 | Apple iTunes mdir metadata tag. 246 | Found as a child of the various tags under: 247 | `moov.udta.meta.ilst` 248 | */ 249 | _ILST_DATA :: struct { 250 | type: ILST_DATA_Type, 251 | subtype: u32be, 252 | } 253 | #assert(size_of(_ILST_DATA) == 8) 254 | 255 | iTunes_Metadata :: struct { 256 | using _ilst_data: _ILST_DATA, 257 | 258 | data: union { 259 | [dynamic]u8, // Binary, JPEG, PNG and unknown sub-types 260 | cstring, // Text 261 | iTunes_Track, 262 | iTunes_Disk, 263 | }, 264 | } 265 | 266 | iTunes_Track :: struct #packed { 267 | _reserved: u16be, 268 | current: u16be, 269 | disk_total: u16be, 270 | set_total: u16be, 271 | } 272 | 273 | iTunes_Disk :: struct #packed { 274 | _reserved: u16be, 275 | current: u16be, 276 | total: u16be, 277 | } 278 | 279 | /* 280 | Chapter list, orinally from the F4V format. 281 | Not part of original ISO spec. 282 | */ 283 | _Chapter_Entry :: struct #packed { 284 | timestamp: u64be, 285 | title_size: u8, 286 | } 287 | 288 | Chapter_Entry :: struct #packed { 289 | timestamp: u64be, 290 | title: string, 291 | } 292 | 293 | Chapter_List :: struct { 294 | using _vf: Version_and_Flags, 295 | _reserved: u8, 296 | chapters: [dynamic]Chapter_Entry, 297 | } -------------------------------------------------------------------------------- /bmff/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd example 3 | call build.bat 4 | popd -------------------------------------------------------------------------------- /bmff/example/bmff_example.odin: -------------------------------------------------------------------------------- 1 | package iso_bmff_example 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | This file is an example that parses an ISO base media file (mp4/m4a/...) and prints the parse tree. 30 | */ 31 | 32 | import "core:fmt" 33 | import "core:mem" 34 | import "core:os" 35 | import "core:time" 36 | import bmff ".." 37 | 38 | parse_metadata := true 39 | 40 | _main :: proc() { 41 | EXE_NAME := os.args[0] 42 | 43 | if len(os.args) == 1 { 44 | fmt.println("ISO Base Media File Format parser example") 45 | fmt.printf("Usage: %v [bmff filename]\n\n", EXE_NAME) 46 | os.exit(1) 47 | } 48 | 49 | file := os.args[1] 50 | f, err := bmff.open(file) 51 | defer bmff.close(f) 52 | 53 | if err != nil { 54 | fmt.printf("Couldn't open '%v'. Err: %v\n", file, err) 55 | return 56 | } 57 | 58 | fmt.printf("\nOpened '%v'\n", file) 59 | fmt.printf("\tFile size: %v\n", f.file_info.size) 60 | fmt.printf("\tCreated: %v\n", f.file_info.creation_time) 61 | fmt.printf("\tModified: %v\n", f.file_info.modification_time) 62 | 63 | fmt.println("\n-=-=-=-=-=-=- PARSED FILE -=-=-=-=-=-=-") 64 | 65 | parse_start := time.now() 66 | e := bmff.parse(f, parse_metadata) 67 | parse_end := time.now() 68 | parse_diff := time.diff(parse_start, parse_end) 69 | 70 | print_start := time.now() 71 | bmff.print(f) 72 | print_end := time.now() 73 | print_diff := time.diff(print_start, print_end) 74 | 75 | fmt.println("\n-=-=-=-=-=-=- PARSED FILE -=-=-=-=-=-=-") 76 | fmt.printf("Parse Error: %v\n\n", e) 77 | 78 | parse_speed := f64(time.Second) / f64(parse_diff) * f64(f.file_info.size) / f64(1024 * 1024) 79 | fmt.printfln("Parse: %.3f ms (%f MiB/s).", 1_000 * time.duration_seconds(parse_diff), parse_speed) 80 | fmt.printfln("Print: %.3f ms.", 1_000 * time.duration_seconds(print_diff)) 81 | 82 | d, _ := bmff.duration(f) 83 | fmt.printfln("\nFile duration: %v", d) 84 | } 85 | 86 | main :: proc() { 87 | track: mem.Tracking_Allocator 88 | mem.tracking_allocator_init(&track, context.allocator) 89 | context.allocator = mem.tracking_allocator(&track) 90 | 91 | _main() 92 | 93 | if len(track.allocation_map) > 0 { 94 | fmt.println() 95 | for _, v in track.allocation_map { 96 | fmt.printf("Leaked %v bytes @ loc %v\n", v.size, v.location) 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /bmff/example/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | odin build . -out:example.exe -vet -define:BMFF_DEBUG=true 3 | : -opt:1 -debug 4 | if %errorlevel% neq 0 goto end_of_build 5 | example 6 | rem example "../../tests/assets/bmff/test_metadata.mp4" 7 | :end_of_build -------------------------------------------------------------------------------- /bmff/helpers.odin: -------------------------------------------------------------------------------- 1 | package iso_bmff 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | A from-scratch implementation of ISO base media file format (ISOM), 30 | as specified in ISO/IEC 14496-12, Fifth edition 2015-12-15. 31 | The identical text is available as ISO/IEC 15444-12 (JPEG 2000, Part 12). 32 | 33 | See: https://www.iso.org/standard/68960.html and https://www.loc.gov/preservation/digital/formats/fdd/fdd000079.shtml 34 | 35 | This file contains type conversion helpers. 36 | */ 37 | 38 | import "core:time" 39 | 40 | // Returns the movie's duration 41 | duration :: proc(f: ^BMFF_File) -> (res: time.Duration, ok: bool) { 42 | if f == nil || f.mvhd == nil { 43 | return {}, false 44 | } 45 | 46 | #partial switch kind in f.mvhd.payload { 47 | case MVHD_V0: return time.Duration(kind.duration) * time.Second / time.Duration(kind.time_scale), true 48 | case MVHD_V1: return time.Duration(kind.duration) * time.Second / time.Duration(kind.time_scale), true 49 | } 50 | return 51 | } -------------------------------------------------------------------------------- /bmff/print.odin: -------------------------------------------------------------------------------- 1 | package iso_bmff 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | A from-scratch implementation of ISO base media file format (ISOM), 30 | as specified in ISO/IEC 14496-12, Fifth edition 2015-12-15. 31 | The identical text is available as ISO/IEC 15444-12 (JPEG 2000, Part 12). 32 | 33 | See: https://www.iso.org/standard/68960.html and https://www.loc.gov/preservation/digital/formats/fdd/fdd000079.shtml 34 | 35 | This file is a debug helper that prints the parse tree. 36 | */ 37 | 38 | import "base:runtime" 39 | import "core:fmt" 40 | import "core:reflect" 41 | import "core:time" 42 | import "../common" 43 | 44 | print_box :: proc(f: ^BMFF_File, box: ^BMFF_Box, level := int(0), print_siblings := false, recurse := false) { 45 | box := box 46 | 47 | for box != nil { 48 | box_type := fmt.tprintf("UUID: %v", to_string(box.uuid)) if box.type == .UUID else to_string(box.type) 49 | printf(level, "[%v] Pos: %d, Size: %d\n", box_type, box.offset, box.payload_size) 50 | 51 | #partial switch v in box.payload { 52 | case FTYP: print_ftyp( v, level + 1) 53 | case HDLR: print_hdlr( v, level + 1) 54 | case MDHD_V0: print_mdhd( v, level + 1) 55 | case MDHD_V1: print_mdhd( v, level + 1) 56 | case MVHD_V0: print_mvhd( v, level + 1) 57 | case MVHD_V1: print_mvhd( v, level + 1) 58 | case TKHD_V0: print_tkhd(f, v, level + 1) 59 | case TKHD_V1: print_tkhd(f, v, level + 1) 60 | case ELST_V0: print_elst( v, level + 1) 61 | case ELST_V1: print_elst( v, level + 1) 62 | case Chapter_List: print_chpl(f, v, level + 1) 63 | case: 64 | #partial switch box.type { 65 | case .Name: 66 | payload := box.payload.([dynamic]u8)[:] 67 | if len(payload) == 0 { return } 68 | 69 | if box.parent.type == .User_Data { 70 | printf(level + 1, "Name: %v\n", string(payload)) 71 | } 72 | } 73 | 74 | if box.parent == f.itunes_metadata { 75 | if box.type != .iTunes_Extended { 76 | #partial switch kind in box.payload { 77 | case iTunes_Metadata: print_itunes_metadata(kind, level + 1) 78 | } 79 | 80 | } 81 | } 82 | } 83 | 84 | if recurse && box.first_child != nil { 85 | print_box(f, box.first_child, level + 1, print_siblings, recurse) 86 | } 87 | 88 | box = box.next if print_siblings else nil 89 | } 90 | } 91 | 92 | print :: proc(f: ^BMFF_File, box: ^BMFF_Box = nil, print_siblings := false, recurse := false) { 93 | if box != nil { 94 | print_box(f, box, 0, print_siblings, recurse) 95 | } else { 96 | print_box(f, f.root, 0, true, true) 97 | } 98 | } 99 | 100 | @(private="package") 101 | print_itunes_metadata :: proc(tag: iTunes_Metadata, level := int(0)) { 102 | #partial switch tag.type { 103 | case .Text: println(level, string(tag.data.(cstring))) 104 | case .JPEG: printf(level, "Thumbnail Type: JPEG\n") 105 | case .PNG: printf(level, "Thumbnail Type: PNG\n") 106 | case: 107 | switch v in tag.data { 108 | case [dynamic]u8: 109 | if common.is_printable(v[:]) { 110 | println(level, string(v[:])) 111 | } else { 112 | println(level, "Bytes:", v) 113 | } 114 | case iTunes_Track: 115 | printf(level, "Track: %v/%v\n", v.current, v.disk_total) 116 | 117 | case iTunes_Disk: 118 | printf(level, "Disk: %v/%v\n", v.current, v.total) 119 | 120 | case cstring: 121 | // Already handled above. 122 | } 123 | 124 | } 125 | } 126 | 127 | @(private="package") 128 | print_mdhd :: proc(mdhd: $T, level := int(0)) { 129 | #assert(T == MDHD_V0 || T == MDHD_V1) 130 | 131 | seconds := f64(mdhd.duration) / f64(mdhd.time_scale) 132 | printf(level, "duration: %v seconds\n", seconds) 133 | printf(level, "created: %v (%v)\n", to_time(mdhd.creation_time), mdhd.creation_time) 134 | printf(level, "modified: %v (%v)\n", to_time(mdhd.modification_time), mdhd.modification_time) 135 | printf(level, "language: %v\n", to_string(mdhd.language)) 136 | printf(level, "quality: %v\n", mdhd.quality) 137 | } 138 | 139 | @(private="package") 140 | print_elst :: proc(elst: $T, level := int(0)) { 141 | #assert(T == ELST_V0 || T == ELST_V1) 142 | for e, i in elst.entries { 143 | printf(level, "edit: %v\n", i) 144 | printf(level + 1, "segment_duration: %v\n", e.segment_duration) 145 | printf(level + 1, "media_time: %v\n", e.media_time) 146 | printf(level + 1, "media_rate: %v/%v\n", e.media_rate.x, e.media_rate.y) 147 | } 148 | } 149 | 150 | @(private="package") 151 | print_tkhd :: proc(f: ^BMFF_File, tkhd: $T, level := int(0)) { 152 | #assert(T == TKHD_V0 || T == TKHD_V1) 153 | 154 | seconds := f64(tkhd.duration) / f64(f.time_scale) 155 | 156 | printf(level, "track: %v\n", tkhd.track_id) 157 | printf(level, "flags: %v\n", tkhd.flags) 158 | printf(level, "duration: %v seconds\n", seconds) 159 | printf(level, "created: %v (%v)\n", to_time(tkhd.creation_time), tkhd.creation_time) 160 | printf(level, "modified: %v (%v)\n", to_time(tkhd.modification_time), tkhd.modification_time) 161 | 162 | if tkhd.volume == 0 { 163 | printf(level, "width: %v\n", to_f64(tkhd.width)) 164 | printf(level, "height: %v\n", to_f64(tkhd.height)) 165 | } else { 166 | printf(level, "volume: %v\n", to_f64(tkhd.volume)) 167 | } 168 | printf(level, "matrix: %v\n", to_matrix(tkhd.view_matrix)) 169 | } 170 | 171 | @(private="package") 172 | print_mvhd :: proc(mvhd: $T, level := int(0)) { 173 | #assert(T == MVHD_V0 || T == MVHD_V1) 174 | 175 | seconds := f64(mvhd.duration) / f64(mvhd.time_scale) 176 | 177 | printf(level, "preferred_rate: %v\n", to_f64(mvhd.preferred_rate)) 178 | printf(level, "preferred_volume: %v\n", to_f64(mvhd.preferred_volume)) 179 | printf(level, "duration: %v seconds\n", seconds) 180 | printf(level, "created: %v (%v)\n", to_time(mvhd.creation_time), mvhd.creation_time) 181 | printf(level, "modified: %v (%v)\n", to_time(mvhd.modification_time), mvhd.modification_time) 182 | printf(level, "matrix: %v\n", to_matrix(mvhd.view_matrix)) 183 | printf(level, "next track id: %v\n", mvhd.next_track_id) 184 | } 185 | 186 | @(private="package") 187 | print_ftyp :: proc(ftyp: FTYP, level := int(0)) { 188 | printf(level, "Major Brand: %v (0x%08x)\n", to_string(ftyp.brand), int(ftyp.brand)) 189 | printf(level, "Minor Version: %v.%v.%v.%v\n", ftyp.version.x, ftyp.version.y, ftyp.version.z, ftyp.version.w) 190 | 191 | println(level, "Compat:") 192 | for compat in ftyp.compatible { 193 | println(level + 1, to_string(compat)) 194 | } 195 | } 196 | 197 | @(private="package") 198 | print_hdlr :: proc(hdlr: $T, level := int(0)) { 199 | #assert(T == HDLR) 200 | 201 | if hdlr.component_type != nil { 202 | printf(level, "Type: %v\n", to_string(hdlr.component_type)) 203 | } 204 | 205 | if hdlr.component_subtype != nil { 206 | printf(level, "Sub-Type: %v\n", to_string(hdlr.component_subtype)) 207 | } 208 | 209 | if hdlr.component_manufacturer != nil { 210 | printf(level, "Manufacturer: %v\n", to_string(hdlr.component_manufacturer)) 211 | } 212 | 213 | if len(hdlr.name) > 0 { 214 | printf(level, "Name: %v\n", hdlr.name) 215 | } 216 | } 217 | 218 | @(private="package") 219 | print_chpl :: proc(f: ^BMFF_File, chpl: Chapter_List, level := int(0)) { 220 | time_scale := f.time_scale if chpl.version == 0 else 10_000_000 221 | 222 | for chapter, i in chpl.chapters { 223 | start := f64(chapter.timestamp) / f64(time_scale) 224 | 225 | printf(level, "Chapter #: %v\n", i + 1) 226 | printf(level + 1, "Title: %v\n", chapter.title) 227 | printf(level + 1, "Start: %.2f seconds\n", start) 228 | if i + 1 < len(chpl.chapters) { 229 | println(0) 230 | } 231 | } 232 | } 233 | 234 | @(private="package") 235 | printf :: proc(level: int, format: string, args: ..any) { 236 | indent(level) 237 | fmt.printf(format, ..args) 238 | } 239 | 240 | @(private="package") 241 | println :: proc(level: int, args: ..any) { 242 | indent(level) 243 | fmt.println(..args) 244 | } 245 | 246 | @(private="package") 247 | indent :: proc(level: int) { 248 | TABS := []u8{ 249 | '\t', '\t', '\t', '\t', '\t', 250 | '\t', '\t', '\t', '\t', '\t', 251 | '\t', '\t', '\t', '\t', '\t', 252 | '\t', '\t', '\t', '\t', '\t', 253 | } 254 | fmt.printf(string(TABS[:level])) 255 | } 256 | 257 | // Internal type conversion helpers 258 | @(private="package") 259 | to_string :: proc(type: $T) -> (res: string) { 260 | when T == ISO_639_2 { 261 | buffer := PRINT_BUFFER[:] 262 | 263 | l := int(type) 264 | buffer[0] = u8(96 + (l >> 10) ) 265 | buffer[1] = u8(96 + (l >> 5) & 31) 266 | buffer[2] = u8(96 + (l ) & 31) 267 | 268 | return string(buffer[:3]) 269 | 270 | } else when T == FourCC { 271 | has_prefix :: proc(s, prefix: string) -> bool { 272 | return len(s) >= len(prefix) && s[0:len(prefix)] == prefix 273 | } 274 | 275 | if type == .Root { 276 | return "" 277 | } 278 | id := runtime.typeid_base(typeid_of(FourCC)) 279 | type_info := type_info_of(id) 280 | 281 | buffer := PRINT_BUFFER[:] 282 | name: string 283 | 284 | #partial switch e in type_info.variant { 285 | case runtime.Type_Info_Enum: 286 | Enum_Value :: runtime.Type_Info_Enum_Value 287 | 288 | ev_, _ := reflect.as_i64(type) 289 | ev := Enum_Value(ev_) 290 | 291 | for val, idx in e.values { 292 | if val == ev { 293 | name = fmt.bprintf(buffer[:], "%v", e.names[idx]) 294 | if has_prefix(name, "iTunes") { 295 | buffer[6] = ':' 296 | } 297 | for v, i in name { 298 | if v == '_' { 299 | buffer[i] = ' ' 300 | } 301 | } 302 | return name 303 | 304 | } 305 | } 306 | } 307 | 308 | temp := transmute([4]u8)type 309 | /* 310 | We could do `string(t[:4])`, but this also handles e.g. `©too`. 311 | */ 312 | if common.is_printable(temp[:]) { 313 | return fmt.bprintf(buffer[:], "%c%c%c%c 0x%08x", temp[0], temp[1], temp[2], temp[3], i64(type)) 314 | } else { 315 | return fmt.bprintf(buffer[:], "0x%08x", i64(type)) 316 | } 317 | 318 | } else when T == UUID { 319 | return common._string(type) 320 | } else { 321 | #panic("to_string: Unsupported type.") 322 | } 323 | } 324 | 325 | @(private="package") 326 | to_f64 :: proc(fixed: $T) -> (res: f64) { 327 | when T == Fixed_16_16 { 328 | FRACT :: 16 329 | f := u32(fixed) 330 | 331 | res = f64(f >> FRACT) 332 | res += f64(f & (1 << FRACT - 1)) / f64(1 << FRACT) 333 | } else when T == Fixed_2_30 { 334 | FRACT :: 30 335 | f := u32(fixed) 336 | 337 | res = f64(f >> FRACT) 338 | res += f64(f & (1 << FRACT - 1)) / f64(1 << FRACT) 339 | } else when T == Fixed_8_8 { 340 | FRACT :: 8 341 | f := u16(fixed) 342 | 343 | res = f64(f >> FRACT) 344 | res += f64(f & (1 << FRACT - 1)) / f64(1 << FRACT) 345 | } else { 346 | #panic("to_f64: Unsupported type.") 347 | } 348 | return 349 | } 350 | 351 | @(private="package") 352 | to_time :: proc(seconds: $T) -> time.Time { 353 | // MPEG4 (ISO/IEC 14496) dates are in seconds since midnight, Jan. 1, 1904, in UTC time 354 | MPEG_YEAR :: -66 355 | MPEG_TO_INTERNAL :: i64((MPEG_YEAR*365 + MPEG_YEAR/4 - MPEG_YEAR/100 + MPEG_YEAR/400 - 1) * time.SECONDS_PER_DAY) 356 | 357 | return time.Time{(i64(seconds) + MPEG_TO_INTERNAL) * 1e9} 358 | } 359 | 360 | @(private="package") 361 | to_matrix :: proc(mat: View_Matrix) -> (m: matrix[3, 3]f64) { 362 | m = matrix[3, 3]f64{ 363 | to_f64(mat.a), to_f64(mat.b), to_f64(mat.u), 364 | to_f64(mat.c), to_f64(mat.d), to_f64(mat.v), 365 | to_f64(mat.x), to_f64(mat.y), to_f64(mat.w), 366 | } 367 | return 368 | } 369 | @thread_local PRINT_BUFFER: [512]u8 -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if "%1" == "bmff" ( 3 | pushd bmff 4 | call build.bat 5 | popd 6 | goto end 7 | ) 8 | 9 | if "%1" == "ebml" ( 10 | pushd ebml 11 | call build.bat 12 | popd 13 | goto end 14 | ) 15 | 16 | if "%1" == "test" ( 17 | pushd tests 18 | call build.bat 19 | popd 20 | goto end 21 | ) 22 | 23 | echo Run: 24 | echo `build.bat test` for bmff + ebml tests. 25 | echo `build.bat bmff` for bmff (mp4/m4a) example. 26 | echo `build.bat ebml` for ebml (mkv) example. 27 | :end -------------------------------------------------------------------------------- /common/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd ..\bmff 3 | call build.bat 4 | popd -------------------------------------------------------------------------------- /common/common.odin: -------------------------------------------------------------------------------- 1 | package file_format_common 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | File format parser helpers. 30 | */ 31 | 32 | import "base:intrinsics" 33 | import os "core:os/os2" 34 | import "core:io" 35 | 36 | Error :: os.Error 37 | 38 | SEEK_SET :: 0 39 | SEEK_CUR :: 1 40 | SEEK_END :: 2 41 | 42 | get_pos :: proc(f: ^os.File) -> (pos: i64, err: Error) { 43 | return io.seek(f.stream, 0, .Current) 44 | } 45 | 46 | set_pos :: proc(f: ^os.File, pos: i64) -> (err: Error) { 47 | io.seek(f.stream, pos, .Start) or_return 48 | return 49 | } 50 | 51 | @(optimization_mode="favor_size") 52 | read_slice :: #force_inline proc(f: ^os.File, size: $S, allocator := context.temp_allocator, loc := #caller_location) -> (res: []u8, err: Error) where intrinsics.type_is_integer(S) { 53 | res = make([]u8, int(size), allocator, loc=loc) or_return 54 | if _, err = io.read(f.stream, res); err != nil && err != .EOF { 55 | delete(res) 56 | return nil, .Unexpected_EOF 57 | } 58 | return res, nil 59 | } 60 | 61 | @(optimization_mode="favor_size") 62 | read_data :: #force_inline proc(f: ^os.File, $T: typeid, allocator := context.temp_allocator, loc := #caller_location) -> (res: T, err: Error) { 63 | b := read_slice(f, size_of(T), loc=loc) or_return 64 | return intrinsics.unaligned_load((^T)(raw_data(b))), nil 65 | } 66 | 67 | @(optimization_mode="favor_size") 68 | read_u8 :: #force_inline proc(f: ^os.File, loc := #caller_location) -> (res: u8, err: Error) { 69 | return io.read_byte(f.stream) 70 | } 71 | 72 | @(optimization_mode="favor_size") 73 | peek_data :: #force_inline proc(f: ^os.File, $T: typeid, allocator := context.temp_allocator, loc := #caller_location) -> (res: T, err: Error) { 74 | b := make([]u8, size_of(T), allocator, loc=loc) or_return 75 | io.read_at(f.stream, b, 0) or_return 76 | return intrinsics.unaligned_load((^T)(raw_data(b))), nil 77 | } 78 | 79 | @(optimization_mode="favor_size") 80 | peek_u8 :: #force_inline proc(f: ^os.File, allocator := context.temp_allocator, loc := #caller_location) -> (res: u8, err: Error) { 81 | buf: [1]byte 82 | io.read_at(f.stream, buf[:], 0) or_return 83 | res = buf[0] 84 | return 85 | } -------------------------------------------------------------------------------- /common/types.odin: -------------------------------------------------------------------------------- 1 | package file_format_common 2 | 3 | import "core:fmt" 4 | 5 | @thread_local PRINT_BUFFER: [512]u8 6 | 7 | bprintf :: fmt.bprintf 8 | 9 | /* 10 | UUIDs are compliant with RFC 4122: A Universally Unique IDentifier (UUID) URN Namespace 11 | (https://www.rfc-editor.org/rfc/rfc4122.html) 12 | */ 13 | UUID_RFC_4122 :: struct { 14 | time_low: u32be, 15 | time_mid: u16be, 16 | time_hi_and_version: u16be, 17 | clk_seq_hi_and_reserved: u8, 18 | clk_seq_low: u8, 19 | node: [6]u8, 20 | } 21 | #assert(size_of(UUID_RFC_4122) == 16) 22 | 23 | FourCC :: distinct u32be 24 | 25 | _string :: proc(type: $T) -> (res: string) { 26 | /* 6ba7b810-9dad-11d1-80b4-00c04fd430c8 */ 27 | 28 | when T == UUID_RFC_4122 { 29 | buffer := PRINT_BUFFER[:] 30 | return bprintf( 31 | buffer[:], "%06x-%02x-%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 32 | type.time_low, 33 | type.time_mid, 34 | type.time_hi_and_version, 35 | type.clk_seq_hi_and_reserved, 36 | type.clk_seq_low, 37 | type.node[0], type.node[1], type.node[2], type.node[3], type.node[4], type.node[5], 38 | ) 39 | } else when T == FourCC { 40 | buffer := PRINT_BUFFER[:] 41 | 42 | temp := transmute([4]u8)type 43 | /* 44 | We could do `string(t[:4])`, but this also handles e.g. `©too`. 45 | */ 46 | if is_printable(temp[:]) { 47 | return fmt.bprintf(buffer[:], "%c%c%c%c", temp[0], temp[1], temp[2], temp[3]) 48 | } else { 49 | return fmt.bprintf(buffer[:], "0x%02x%02x%02x%02x", temp[0], temp[1], temp[2], temp[3]) 50 | } 51 | 52 | } else { 53 | #panic("to_string: Unsupported type.") 54 | } 55 | } 56 | 57 | is_printable :: proc(buf: []u8) -> (printable: bool) { 58 | printable = true 59 | for r in buf { 60 | switch r { 61 | case '\r', '\n', '\t': 62 | continue 63 | case 0x00..=0x19: 64 | return false 65 | case 0x20..=0x7e: 66 | continue 67 | case 0x7f..=0xa0: 68 | return false 69 | case 0xa1..=0xff: // ¡ through ÿ except for the soft hyphen 70 | if r == 0xad { 71 | return false 72 | } 73 | } 74 | } 75 | return 76 | } -------------------------------------------------------------------------------- /ebml/base_types.odin: -------------------------------------------------------------------------------- 1 | package ebml 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | A from-scratch implementation of the Extensible Binary Meta Language (EBML), 30 | as specified in [IETF RFC 8794](https://www.rfc-editor.org/rfc/rfc8794). 31 | 32 | The EBML format is the base format upon which Matroska (MKV) and WebM are based. 33 | 34 | This file contains the base EBML types. 35 | */ 36 | 37 | import os "core:os/os2" 38 | import "core:mem" 39 | import "core:time" 40 | import "../common" 41 | 42 | Matroska_UUID :: common.UUID_RFC_4122 43 | Matroska_Time :: time.Time 44 | 45 | /* 46 | Version of the EBML specification we support. As of the time of writing, only EBML v1 exists. 47 | */ 48 | EBML_VERSION :: 1 49 | MAX_DOCTYPE_LENGTH :: 1_024 50 | 51 | Error :: union #shared_nil { 52 | EBML_Error, 53 | common.Error, 54 | } 55 | 56 | EBML_Error :: enum { 57 | File_Not_Found, 58 | File_Not_Opened, 59 | File_Empty, 60 | File_Ended_Early, 61 | Read_Error, 62 | 63 | EBML_Header_Missing_or_Corrupt, 64 | EBML_Header_Unexpected_Field_Length, 65 | EBML_Header_Duplicated, 66 | 67 | Element_out_of_Range, 68 | Unsupported_EBML_Version, 69 | DocType_Empty, 70 | DocType_Too_Long, 71 | DocTypeVersion_Invalid, 72 | DocTypeReadVersion_Invalid, 73 | Max_ID_Length_Invalid, 74 | Max_Size_Invalid, 75 | Invalid_CRC_Size, 76 | Invalid_CRC, 77 | Validation_Failed, 78 | Unsigned_Invalid_Length, 79 | Signed_Invalid_Length, 80 | Float_Invalid_Length, 81 | Matroska_Track_Type_Invalid_Length, 82 | 83 | VINT_All_Zeroes, 84 | VINT_All_Ones, 85 | VINT_Out_of_Range, 86 | 87 | Unprintable_String, 88 | Wrong_File_Format, 89 | 90 | Matroska_Body_Root_Wrong_ID, 91 | Matroska_Broken_SeekPosition, 92 | Matroska_SegmentUID_Invalid_Length, 93 | } 94 | 95 | /* 96 | RFC 8794 - An EBML Stream is a file that consists of one or more EBML Documents that are concatenated 97 | together. An occurrence of an EBML Header at the Root Level marks the beginning of an EBML Document. 98 | */ 99 | EBML_Document :: struct { 100 | header: ^EBML_Element, 101 | body: ^EBML_Element, 102 | 103 | /* 104 | Useful document variables. 105 | */ 106 | version: u8, // EBMLVersion 107 | read_version: u8, // EBMLReadVersion 108 | doctype: String, // DocType 109 | doctype_version: u8, // EBMLDocTypeVersion 110 | doctype_read_version: u8, // EBMLDocTypeReadVersion 111 | max_id_length: u8, // EBMLMaxIDLength 112 | max_size_length: u8, // EBMLMaxSizeLength 113 | } 114 | 115 | EBML_File :: struct { 116 | documents: [dynamic]^EBML_Document, 117 | 118 | // Implementation 119 | file_info: os.File_Info, 120 | handle: ^os.File, 121 | allocator: mem.Allocator, 122 | } 123 | 124 | VINT_MAX :: (1 << 56) - 1 // 72,057,594,037,927,934 125 | String :: distinct string // Printable string only, RFC 0020 126 | Date :: distinct i64 // Default value: 2001-01-01T00:00:00.000000000 UTC, RFC 3339. 127 | 128 | Matroska_Track_Type :: enum u8 { 129 | Video = 1, 130 | Audio = 2, 131 | Complex = 3, 132 | Logo = 16, 133 | Subtitle = 17, 134 | Buttons = 18, 135 | Control = 32, 136 | Metadata = 33, 137 | } 138 | 139 | Payload_Types :: union { 140 | i64, 141 | u64, 142 | f64, 143 | String, 144 | string, 145 | Date, 146 | [dynamic]u8, 147 | 148 | Matroska_UUID, 149 | Matroska_Time, 150 | Matroska_Track_Type, 151 | } 152 | 153 | EBML_Element :: struct { 154 | /* 155 | Element file offset, and size including header. 156 | */ 157 | offset: i64, 158 | size: i64, 159 | end: i64, 160 | 161 | payload_offset: i64, 162 | payload_size: i64, 163 | 164 | parent: ^EBML_Element, 165 | next: ^EBML_Element, 166 | first_child: ^EBML_Element, 167 | 168 | id: EBML_ID, 169 | type: EBML_Type, 170 | level: int, 171 | 172 | /* 173 | Payload can be empty 174 | */ 175 | payload: Payload_Types, 176 | } 177 | 178 | EBML_Type :: enum { 179 | Unhandled, // Not (yet) handled. 180 | 181 | Signed, // Section 7.1 of RFC 8794 182 | Unsigned, // Section 7.2 of RFC 8794 183 | Float, // Section 7.3 of RFC 8794 184 | String, // Section 7.4 of RFC 8794 185 | UTF_8, // Section 7.5 of RFC 8794 186 | Date, // Section 7.6 of RFC 8794 187 | Master, // Section 7.7 of RFC 8794 188 | Binary, // Section 7.8 of RFC 8794 189 | 190 | /* 191 | Custom types for Matroska 192 | */ 193 | Matroska_UUID, 194 | Matroska_Time, 195 | Matroska_Track_Type, 196 | } 197 | 198 | EBML_ID :: enum u64be { 199 | /* 200 | =================== =================== EBML IDs =================== =================== 201 | As specified in IETF RFC 8794. See: https://datatracker.ietf.org/doc/rfc8794/ 202 | =================== =================== EBML IDs =================== =================== 203 | */ 204 | 205 | /* 206 | Every EBML Document has to start with this ID. 207 | Described in Section 11.2.1 of RFC 8794. 208 | */ 209 | EBML = 0x1A_45_DF_A3, 210 | 211 | /* 212 | Version of the EBML specifications used to create the EBML document. 213 | Described in Section 11.2.2 of RFC 8794. 214 | */ 215 | EBMLVersion = 0x4286, 216 | 217 | /* 218 | The minimum EBML version a reader has to support to read this document. 219 | Described in Section 11.2.3 of RFC 8794. 220 | */ 221 | EBMLReadVersion = 0x42F7, 222 | 223 | /* 224 | The EBMLMaxIDLength Element stores the maximum permitted length in octets of the Element 225 | IDs to be found within the EBML Body. 226 | Described in Section 11.2.4 of RFC 8794. 227 | */ 228 | EBMLMaxIDLength = 0x42F2, 229 | 230 | /* 231 | The EBMLMaxSizeLength Element stores the maximum permitted length in octets of the 232 | expressions of all Element Data Sizes to be found within the EBML Body. 233 | Described in Section 11.2.5 of RFC 8794. 234 | */ 235 | EBMLMaxSizeLength = 0x42F3, 236 | 237 | /* 238 | A string that describes and identifies the content of the EBML Body that follows this EBML Header. 239 | Described in Section 11.2.6 of RFC 8794. 240 | */ 241 | DocType = 0x4282, 242 | 243 | /* 244 | The version of DocType interpreter used to create the EBML Document. 245 | Described in Section 11.2.7 of RFC 8794. 246 | */ 247 | DocTypeVersion = 0x4287, 248 | 249 | /* 250 | The minimum DocType version an EBML Reader has to support to read this EBML Document. 251 | Described in Section 11.2.8 of RFC 8794. 252 | */ 253 | DocTypeReadVersion = 0x4285, 254 | 255 | /* 256 | A DocTypeExtension adds extra Elements to the main DocType+DocTypeVersion tuple it's attached to. 257 | Described in Section 11.2.9 of RFC 8794. 258 | */ 259 | DocTypeExtension = 0x4281, 260 | 261 | /* 262 | The name of the DocTypeExtension to differentiate it from other DocTypeExtensions 263 | of the same DocType+DocTypeVersion tuple. 264 | Described in Section 11.2.10 of RFC 8794. 265 | */ 266 | DocTypeExtensionName = 0x4283, 267 | 268 | /* 269 | The version of the DocTypeExtension. Different DocTypeExtensionVersion values of the same 270 | DocType + DocTypeVersion + DocTypeExtensionName tuple contain completely different sets of extra Elements. 271 | Described in Section 11.2.11 of RFC 8794. 272 | */ 273 | DocTypeExtensionVersion = 0x4284, 274 | 275 | /* 276 | The CRC-32 Element contains a 32-bit Cyclic Redundancy Check value of all the 277 | Element Data of the Parent Element as stored except for the CRC-32 Element itself. 278 | Described in Section 11.3.1 of RFC 8794. 279 | */ 280 | CRC_32 = 0xBF, 281 | 282 | /* 283 | Used to void data or to avoid unexpected behaviors when using damaged data. The 284 | content is discarded. Also used to reserve space in a subelement for later use. 285 | Described in Section 11.3.2 of RFC 8794. 286 | */ 287 | Void = 0xEC, 288 | 289 | /* 290 | =================== =================== MATROSKA IDs =================== =================== 291 | As specified in IETF draft draft-ietf-cellar-matroska-08 292 | See: https://www.ietf.org/archive/id/draft-ietf-cellar-matroska-08.html#name-segment-element 293 | =================== =================== MATROSKA IDs =================== =================== 294 | */ 295 | 296 | /* 297 | The Root Element that contains all other Top-Level Elements (Elements defined only at Level 1). 298 | A Matroska file is composed of 1 Segment. 299 | Described in Section 8.1 of IETF draft-ietf-cellar-matroska-08 300 | */ 301 | Matroska_Segment = 0x18538067, 302 | 303 | /* 304 | Contains the Segment Position of other Top-Level Elements. 305 | Described in Section 8.1.1 of IETF draft-ietf-cellar-matroska-08 306 | */ 307 | Matroska_SeekHead = 0x114D9B74, 308 | 309 | /* 310 | Contains a single seek entry to an EBML Element. 311 | Described in Section 8.1.1.1 of IETF draft-ietf-cellar-matroska-08 312 | */ 313 | Matroska_Seek = 0x4DBB, 314 | 315 | /* 316 | The binary ID corresponding to the Element name. 317 | Described in Section 8.1.1.1.1 of IETF draft-ietf-cellar-matroska-08 318 | */ 319 | Matroska_SeekID = 0x53AB, 320 | 321 | /* 322 | The Segment Position of the Element. 323 | Described in Section 8.1.1.1.2 of IETF draft-ietf-cellar-matroska-08 324 | */ 325 | Matroska_SeekPosition = 0x53AC, 326 | 327 | /* 328 | Contains general information about the Segment. 329 | Described in Section 8.1.2 of IETF draft-ietf-cellar-matroska-08 330 | */ 331 | Matroska_Info = 0x1549A966, 332 | 333 | /* 334 | A randomly generated unique ID to identify the Segment amongst many others (128 bits). 335 | Described in Section 8.1.2.1 of IETF draft-ietf-cellar-matroska-08 336 | */ 337 | Matroska_SegmentUID = 0x73A4, 338 | 339 | /* 340 | A filename corresponding to this Segment. 341 | Described in Section 8.1.2.2 of IETF draft-ietf-cellar-matroska-08 342 | */ 343 | Matroska_SegmentFilename = 0x7384, 344 | 345 | /* 346 | A unique ID to identify the previous Segment of a Linked Segment (128 bits). 347 | Described in Section 8.1.2.3 of IETF draft-ietf-cellar-matroska-08 348 | */ 349 | Matroska_PrevUID = 0x3CB923, 350 | 351 | /* 352 | A filename corresponding to the file of the previous Linked Segment. 353 | Described in Section 8.1.2.4 of IETF draft-ietf-cellar-matroska-08 354 | */ 355 | Matroska_PrevFilename = 0x3C83AB, 356 | 357 | /* 358 | A unique ID to identify the previous Segment of a Linked Segment (128 bits). 359 | Described in Section 8.1.2.5 of IETF draft-ietf-cellar-matroska-08 360 | */ 361 | Matroska_NextUID = 0x3EB923, 362 | 363 | /* 364 | A filename corresponding to the file of the previous Linked Segment. 365 | Described in Section 8.1.2.6 of IETF draft-ietf-cellar-matroska-08 366 | */ 367 | Matroska_NextFilename = 0x3E83BB, 368 | 369 | /* 370 | A randomly generated unique ID that all Segments of a Linked Segment MUST share (128 bits). 371 | Described in Section 8.1.2.7 of IETF draft-ietf-cellar-matroska-08 372 | */ 373 | Matroska_SegmentFamily = 0x4444, 374 | 375 | /* 376 | A tuple of corresponding ID used by chapter codecs to represent this Segment. 377 | Described in Section 8.1.2.8 of IETF draft-ietf-cellar-matroska-08 378 | */ 379 | Matroska_ChapterTranslate = 0x6924, 380 | 381 | /* 382 | Specify an edition UID on which this correspondence applies. When not specified, 383 | it means for all editions found in the Segment. 384 | Described in Section 8.1.2.8.1 of IETF draft-ietf-cellar-matroska-08 385 | */ 386 | Matroska_ChapterTranslateEditionUID = 0x69FC, 387 | 388 | /* 389 | The chapter codec; see Section 8.1.7.1.4.15. 390 | Described in Section 8.1.2.8.2 of IETF draft-ietf-cellar-matroska-08 391 | */ 392 | Matroska_ChapterTranslateCodec = 0x69BF, 393 | 394 | /* 395 | The binary value used to represent this Segment in the chapter codec data. 396 | The format depends on the ChapProcessCodecID used; see Section 8.1.7.1.4.15. 397 | 398 | Described in Section 8.1.2.8.3 of IETF draft-ietf-cellar-matroska-08 399 | */ 400 | Matroska_ChapterTranslateID = 0x69A5, 401 | 402 | /* 403 | Timestamp scale in nanoseconds (1_000_000 means all timestamps in the Segment are expressed in milliseconds). 404 | 405 | Described in Section 8.1.2.9 of IETF draft-ietf-cellar-matroska-08 406 | */ 407 | Matroska_TimestampScale = 0x2AD7B1, 408 | 409 | /* 410 | Duration of the Segment in nanoseconds based on TimestampScale. 411 | 412 | Described in Section 8.1.2.10 of IETF draft-ietf-cellar-matroska-08 413 | */ 414 | Matroska_Duration = 0x4489, 415 | 416 | /* 417 | The date and time that the Segment was created by the muxing application or library. 418 | 419 | Described in Section 8.1.2.11 of IETF draft-ietf-cellar-matroska-08 420 | */ 421 | Matroska_DateUTC = 0x4461, 422 | 423 | /* 424 | General name of the Segment. 425 | 426 | Described in Section 8.1.2.12 of IETF draft-ietf-cellar-matroska-08 427 | */ 428 | Matroska_Title = 0x7BA9, 429 | 430 | /* 431 | Muxing application or library (example: "libmatroska-0.4.3"). 432 | 433 | Described in Section 8.1.2.13 of IETF draft-ietf-cellar-matroska-08 434 | */ 435 | Matroska_MuxingApp = 0x4D80, 436 | 437 | /* 438 | Writing application (example: "mkvmerge-0.3.3"). 439 | 440 | Described in Section 8.1.2.14 of IETF draft-ietf-cellar-matroska-08 441 | */ 442 | Matroska_WritingApp = 0x5741, 443 | 444 | /* 445 | The Top-Level Element containing the (monolithic) Block structure. 446 | 447 | Described in Section 8.1.3 of IETF draft-ietf-cellar-matroska-08 448 | */ 449 | Matroska_Cluster = 0x1F43B675, 450 | 451 | /* 452 | Absolute timestamp of the cluster (based on TimestampScale). 453 | 454 | Described in Section 8.1.3.1 of IETF draft-ietf-cellar-matroska-08 455 | */ 456 | Matroska_Timestamp = 0xE7, 457 | 458 | /* 459 | The Segment Position of the Cluster in the Segment (0 in live streams). 460 | It might help to resynchronise offset on damaged streams. 461 | 462 | Described in Section 8.1.3.2 of IETF draft-ietf-cellar-matroska-08 463 | */ 464 | Matroska_Position = 0xA7, 465 | 466 | /* 467 | Size of the previous Cluster, in octets. Can be useful for backward playing. 468 | 469 | Described in Section 8.1.3.3 of IETF draft-ietf-cellar-matroska-08 470 | */ 471 | Matroska_PrevSize = 0xAB, 472 | 473 | /* 474 | Similar to Block, see Section 12, but without all the extra information, 475 | mostly used to reduce overhead when no extra feature is needed; 476 | see Section 12.4 on SimpleBlock Structure. 477 | 478 | Described in Section 8.1.3.4 of IETF draft-ietf-cellar-matroska-08 479 | */ 480 | Matroska_SimpleBlock = 0xA3, 481 | 482 | /* 483 | Basic container of information containing a single Block and information specific to that Block. 484 | 485 | Described in Section 8.1.3.5 of IETF draft-ietf-cellar-matroska-08 486 | */ 487 | Matroska_BlockGroup = 0xA0, 488 | 489 | /* 490 | Block containing the actual data to be rendered and a timestamp relative to the 491 | Cluster Timestamp; see Section 12 on Block Structure. 492 | 493 | Described in Section 8.1.3.5.1 of IETF draft-ietf-cellar-matroska-08 494 | */ 495 | Matroska_Block = 0xA1, 496 | 497 | /* 498 | Contain additional blocks to complete the main one. An EBML parser that has no knowledge 499 | of the Block structure could still see and use/skip these data. 500 | 501 | Described in Section 8.1.3.5.2 of IETF draft-ietf-cellar-matroska-08 502 | */ 503 | Matroska_BlockAdditions = 0x75A1, 504 | 505 | /* 506 | Contain the BlockAdditional and some parameters. 507 | 508 | Described in Section 8.1.3.5.2.1 of IETF draft-ietf-cellar-matroska-08 509 | */ 510 | Matroska_BlockMore = 0xA6, 511 | 512 | /* 513 | An ID to identify the BlockAdditional level. If BlockAddIDType of the corresponding block is 0, 514 | this value is also the value of BlockAddIDType for the meaning of the content of BlockAdditional. 515 | 516 | Described in Section 8.1.3.5.2.2 of IETF draft-ietf-cellar-matroska-08 517 | */ 518 | Matroska_BlockAddID = 0xEE, 519 | 520 | /* 521 | Interpreted by the codec as it wishes (using the BlockAddID). 522 | 523 | Described in Section 8.1.3.5.2.3 of IETF draft-ietf-cellar-matroska-08 524 | */ 525 | Matroska_BlockAdditional = 0xA5, 526 | 527 | /* 528 | The duration of the Block (based on TimestampScale). The BlockDuration Element can be useful at the end 529 | of a Track to define the duration of the last frame (as there is no subsequent Block available), 530 | or when there is a break in a track like for subtitle tracks. 531 | 532 | Described in Section 8.1.3.5.3 of IETF draft-ietf-cellar-matroska-08 533 | */ 534 | Matroska_BlockDuration = 0x9B, 535 | 536 | /* 537 | This frame is referenced and has the specified cache priority. In cache only a frame of the 538 | same or higher priority can replace this frame. A value of 0 means the frame is not referenced. 539 | 540 | Described in Section 8.1.3.5.4 of IETF draft-ietf-cellar-matroska-08 541 | */ 542 | Matroska_ReferencePriority = 0xFA, 543 | 544 | /* 545 | Timestamp of another frame used as a reference (ie: B or P frame). The timestamp is relative 546 | to the block it's attached to. 547 | 548 | Described in Section 8.1.3.5.5 of IETF draft-ietf-cellar-matroska-08 549 | */ 550 | Matroska_ReferenceBlock = 0xFB, 551 | 552 | /* 553 | The new codec state to use. Data interpretation is private to the codec. 554 | This information SHOULD always be referenced by a seek entry. 555 | 556 | Described in Section 8.1.3.5.6 of IETF draft-ietf-cellar-matroska-08 557 | */ 558 | Matroska_CodecState = 0xA4, 559 | 560 | /* 561 | Duration in nanoseconds of the silent data added to the Block (padding at the end of the Block 562 | for positive value, at the beginning of the Block for negative value). The duration of 563 | DiscardPadding is not calculated in the duration of the TrackEntry and SHOULD be discarded 564 | during playback. 565 | 566 | Described in Section 8.1.3.5.7 of IETF draft-ietf-cellar-matroska-08 567 | */ 568 | Matroska_DiscardPadding = 0x75A2, 569 | 570 | /* 571 | Contains slices description. 572 | 573 | Described in Section 8.1.3.5.8 of IETF draft-ietf-cellar-matroska-08 574 | */ 575 | Matroska_Slices = 0x8E, 576 | 577 | /* 578 | Contains extra time information about the data contained in the Block. 579 | Being able to interpret this Element is not REQUIRED for playback. 580 | 581 | Described in Section 8.1.3.5.8.1 of IETF draft-ietf-cellar-matroska-08 582 | */ 583 | Matroska_TimeSlice = 0xE8, 584 | 585 | /* 586 | The reverse number of the frame in the lace (0 is the last frame, 1 is the next to last, etc). 587 | Being able to interpret this Element is not REQUIRED for playback. 588 | 589 | Described in Section 8.1.3.5.8.2 of IETF draft-ietf-cellar-matroska-08 590 | */ 591 | Matroska_LaceNumber = 0xCC, 592 | 593 | /* 594 | A Top-Level Element of information with many tracks described. 595 | 596 | Described in Section 8.1.4 of IETF draft-ietf-cellar-matroska-08 597 | */ 598 | Matroska_Tracks = 0x1654AE6B, 599 | 600 | /* 601 | Describes a track with all Elements. 602 | 603 | Described in Section 8.1.4.1 of IETF draft-ietf-cellar-matroska-08 604 | */ 605 | Matroska_TrackEntry = 0xAE, 606 | 607 | /* 608 | The track number as used in the Block Header (using more than 127 tracks is not encouraged, 609 | though the design allows an unlimited number). 610 | 611 | Described in Section 8.1.4.1.1 of IETF draft-ietf-cellar-matroska-08 612 | */ 613 | Matroska_TrackNumber = 0xD7, 614 | 615 | /* 616 | The value of this Element SHOULD be kept the same when making a direct stream copy to another file. 617 | 618 | Described in Section 8.1.4.1.2 of IETF draft-ietf-cellar-matroska-08 619 | */ 620 | Matroska_TrackUID = 0x73C5, 621 | 622 | /* 623 | A set of track types coded on 8 bits. 624 | 625 | Described in Section 8.1.4.1.3 of IETF draft-ietf-cellar-matroska-08 626 | */ 627 | Matroska_TrackType = 0x83, 628 | 629 | /* 630 | Set to 1 if the track is usable. It is possible to turn a not usable track into a 631 | usable track using chapter codecs or control tracks. 632 | 633 | Described in Section 8.1.4.1.4 of IETF draft-ietf-cellar-matroska-08 634 | */ 635 | Matroska_FlagEnabled = 0xB8, 636 | 637 | /* 638 | Set if that track (audio, video or subs) SHOULD be eligible for automatic selection by the player; 639 | see Section 21 for more details. 640 | 641 | Described in Section 8.1.4.1.5 of IETF draft-ietf-cellar-matroska-08 642 | */ 643 | Matroska_FlagDefault = 0x88, 644 | 645 | /* 646 | Applies only to subtitles. Set if that track SHOULD be eligible for automatic selection by the player 647 | if it matches the user's language preference, even if the user's preferences would normally not 648 | enable subtitles with the selected audio track; this can be used for tracks containing only 649 | translations of foreign-language audio or onscreen text. See Section 21 for more details. 650 | 651 | Described in Section 8.1.4.1.6 of IETF draft-ietf-cellar-matroska-08 652 | */ 653 | Matroska_FlagForced = 0x55AA, 654 | 655 | /* 656 | Set to 1 if that track is suitable for users with hearing impairments, 657 | set to 0 if it is unsuitable for users with hearing impairments. 658 | 659 | Described in Section 8.1.4.1.7 of IETF draft-ietf-cellar-matroska-08 660 | */ 661 | Matroska_FlagHearingImpaired = 0x55AB, 662 | 663 | /* 664 | Set to 1 if that track is suitable for users with visual impairments, 665 | set to 0 if it is unsuitable for users with visual impairments. 666 | 667 | Described in Section 8.1.4.1.8 of IETF draft-ietf-cellar-matroska-08 668 | */ 669 | Matroska_FlagVisualImpaired = 0x55AC, 670 | 671 | /* 672 | Set to 1 if that track contains textual descriptions of video content, 673 | set to 0 if that track does not contain textual descriptions of video content. 674 | 675 | Described in Section 8.1.4.1.9 of IETF draft-ietf-cellar-matroska-08 676 | */ 677 | Matroska_FlagTextDescriptions = 0x55AD, 678 | 679 | /* 680 | Set to 1 if that track is in the content's original language, 681 | set to 0 if it is a translation. 682 | 683 | Described in Section 8.1.4.1.10 of IETF draft-ietf-cellar-matroska-08 684 | */ 685 | Matroska_FlagOriginal = 0x55AE, 686 | 687 | /* 688 | Set to 1 if that track contains commentary, 689 | set to 0 if it does not contain commentary. 690 | 691 | Described in Section 8.1.4.1.11 of IETF draft-ietf-cellar-matroska-08 692 | */ 693 | Matroska_FlagCommentary = 0x55AF, 694 | 695 | /* 696 | Set to 1 if the track MAY contain blocks using lacing. 697 | 698 | Described in Section 8.1.4.1.12 of IETF draft-ietf-cellar-matroska-08 699 | */ 700 | Matroska_FlagLacing = 0x9C, 701 | 702 | /* 703 | The minimum number of frames a player SHOULD be able to cache during playback. 704 | If set to 0, the reference pseudo-cache system is not used. 705 | 706 | Described in Section 8.1.4.1.13 of IETF draft-ietf-cellar-matroska-08 707 | */ 708 | Matroska_MinCache = 0x6DE7, 709 | 710 | /* 711 | The maximum cache size necessary to store referenced frames in and the current frame. 712 | 0 means no cache is needed. 713 | 714 | Described in Section 8.1.4.1.14 of IETF draft-ietf-cellar-matroska-08 715 | */ 716 | Matroska_MaxCache = 0x6DF8, 717 | 718 | /* 719 | Number of nanoseconds (not scaled via TimestampScale) per frame 720 | (frame in the Matroska sense -- one Element put into a (Simple)Block). 721 | 722 | Described in Section 8.1.4.1.15 of IETF draft-ietf-cellar-matroska-08 723 | */ 724 | Matroska_DefaultDuration = 0x23E383, 725 | 726 | /* 727 | The period in nanoseconds (not scaled by TimestampScale) between two successive fields at 728 | the output of the decoding process, see Section 11 for more information 729 | 730 | Described in Section 8.1.4.1.16 of IETF draft-ietf-cellar-matroska-08 731 | */ 732 | Matroska_DefaultDecodedFieldDuration = 0x234E7A, 733 | 734 | /* 735 | DEPRECATED, DO NOT USE. The scale to apply on this track to work at normal speed in relation with other tracks 736 | (mostly used to adjust video speed when the audio length differs). 737 | 738 | Described in Section 8.1.4.1.17 of IETF draft-ietf-cellar-matroska-08 739 | */ 740 | Matroska_TrackTimestampScale = 0x23314F, 741 | 742 | /* 743 | The maximum value of BlockAddID (Section 8.1.3.5.2.2). 744 | A value 0 means there is no BlockAdditions (Section 8.1.3.5.2) for this track. 745 | 746 | Described in Section 8.1.4.1.18 of IETF draft-ietf-cellar-matroska-08 747 | */ 748 | Matroska_MaxBlockAdditionID = 0x55EE, 749 | 750 | /* 751 | Contains elements that extend the track format, by adding content either to each frame, 752 | with BlockAddID (Section 8.1.3.5.2.2), or to the track as a whole with BlockAddIDExtraData. 753 | 754 | Described in Section 8.1.4.1.19 of IETF draft-ietf-cellar-matroska-08 755 | */ 756 | Matroska_BlockAdditionMapping = 0x41E4, 757 | 758 | /* 759 | If the track format extension needs content beside frames, the value refers to the BlockAddID 760 | (Section 8.1.3.5.2.2), value being described. To keep MaxBlockAdditionID as low as possible, 761 | small values SHOULD be used. 762 | 763 | Described in Section 8.1.4.1.19.1 of IETF draft-ietf-cellar-matroska-08 764 | */ 765 | Matroska_BlockAddIDValue = 0x41F0, 766 | 767 | /* 768 | A human-friendly name describing the type of BlockAdditional data, 769 | as defined by the associated Block Additional Mapping. 770 | 771 | Described in Section 8.1.4.1.19.2 of IETF draft-ietf-cellar-matroska-08 772 | */ 773 | Matroska_BlockAddIDName = 0x41A4, 774 | 775 | /* 776 | Stores the registered identifier of the Block Additional Mapping to define how the 777 | BlockAdditional data should be handled. 778 | 779 | Described in Section 8.1.4.1.19.3 of IETF draft-ietf-cellar-matroska-08 780 | */ 781 | Matroska_BlockAddIDType = 0x41E7, 782 | 783 | /* 784 | Extra binary data that the BlockAddIDType can use to interpret the BlockAdditional data. 785 | The interpretation of the binary data depends on the BlockAddIDType value and the corresponding 786 | Block Additional Mapping. 787 | 788 | Described in Section 8.1.4.1.19.4 of IETF draft-ietf-cellar-matroska-08 789 | */ 790 | Matroska_BlockAddIDExtraData = 0x41ED, 791 | 792 | /* 793 | A human-readable track name. 794 | 795 | Described in Section 8.1.4.1.20 of IETF draft-ietf-cellar-matroska-08 796 | */ 797 | Matroska_Track_Name = 0x536E, 798 | 799 | /* 800 | Specifies the language of the track in the Matroska languages form; see Section 6 on language codes. 801 | This Element MUST be ignored if the LanguageIETF Element is used in the same TrackEntry. 802 | 803 | Described in Section 8.1.4.1.21 of IETF draft-ietf-cellar-matroska-08 804 | */ 805 | Matroska_Language = 0x22B59C, 806 | 807 | /* 808 | Specifies the language of the track according to [BCP47] and using the IANA Language Subtag Registry 809 | [IANALangRegistry]. If this Element is used, then any Language Elements used in the same TrackEntry 810 | MUST be ignored. 811 | 812 | Described in Section 8.1.4.1.22 of IETF draft-ietf-cellar-matroska-08 813 | */ 814 | Matroska_Language_IETF = 0x22B59D, 815 | 816 | /* 817 | An ID corresponding to the codec, see [MatroskaCodec] for more info. 818 | 819 | Described in Section 8.1.4.1.23 of IETF draft-ietf-cellar-matroska-08 820 | */ 821 | Matroska_CodecID = 0x86, 822 | 823 | /* 824 | Private data only known to the codec. 825 | 826 | Described in Section 8.1.4.1.24 of IETF draft-ietf-cellar-matroska-08 827 | */ 828 | Matroska_CodecPrivate = 0x63A2, 829 | 830 | /* 831 | A human-readable string specifying the codec. 832 | 833 | Described in Section 8.1.4.1.25 of IETF draft-ietf-cellar-matroska-08 834 | */ 835 | Matroska_CodecName = 0x258688, 836 | 837 | /* 838 | The UID of an attachment that is used by this codec. 839 | 840 | Described in Section 8.1.4.1.26 of IETF draft-ietf-cellar-matroska-08 841 | */ 842 | Matroska_AttachmentLink = 0x7446, 843 | 844 | /* 845 | Specify that this track is an overlay track for the Track specified (in the u-integer). 846 | That means when this track has a gap, see Section 26.3.1 on SilentTracks, the overlay track 847 | SHOULD be used instead. 848 | 849 | Described in Section 8.1.4.1.27 of IETF draft-ietf-cellar-matroska-08 850 | */ 851 | Matroska_TrackOverlay = 0x6FAB, 852 | 853 | /* 854 | CodecDelay is The codec-built-in delay in nanoseconds. This value MUST be subtracted from each block 855 | timestamp in order to get the actual timestamp. The value SHOULD be small so the muxing of tracks with 856 | the same actual timestamp are in the same Cluster. 857 | 858 | Described in Section 8.1.4.1.28 of IETF draft-ietf-cellar-matroska-08 859 | */ 860 | Matroska_CodecDelay = 0x56AA, 861 | 862 | /* 863 | After a discontinuity, SeekPreRoll is the duration in nanoseconds of the data the decoder 864 | MUST decode before the decoded data is valid. 865 | 866 | Described in Section 8.1.4.1.29 of IETF draft-ietf-cellar-matroska-08 867 | */ 868 | Matroska_SeekPreRoll = 0x56BB, 869 | 870 | /* 871 | The track identification for the given Chapter Codec. 872 | 873 | Described in Section 8.1.4.1.30 of IETF draft-ietf-cellar-matroska-08 874 | */ 875 | Matroska_TrackTranslate = 0x6624, 876 | 877 | /* 878 | Specify an edition UID on which this translation applies. 879 | When not specified, it means for all editions found in the Segment. 880 | 881 | Described in Section 8.1.4.1.30.1 of IETF draft-ietf-cellar-matroska-08 882 | */ 883 | Matroska_TrackTranslateEditionUID = 0x66FC, 884 | 885 | /* 886 | The chapter codec; see Section 8.1.7.1.4.15. 887 | 888 | Described in Section 8.1.4.1.30.2 of IETF draft-ietf-cellar-matroska-08 889 | */ 890 | Matroska_TrackTranslateCodec = 0x66BF, 891 | 892 | /* 893 | The binary value used to represent this track in the chapter codec data. 894 | The format depends on the ChapProcessCodecID used; see Section 8.1.7.1.4.15. 895 | 896 | Described in Section 8.1.4.1.30.3 of IETF draft-ietf-cellar-matroska-08 897 | */ 898 | Matroska_TrackTranslateTrackID = 0x66A5, 899 | 900 | /* 901 | Video settings. 902 | 903 | Described in Section 8.1.4.1.31 of IETF draft-ietf-cellar-matroska-08 904 | */ 905 | Matroska_Video = 0xE0, 906 | 907 | /* 908 | Specify whether the video frames in this track are interlaced or not. 909 | 910 | Described in Section 8.1.4.1.31.1 of IETF draft-ietf-cellar-matroska-08 911 | */ 912 | Matroska_FlagInterlaced = 0x9A, 913 | 914 | /* 915 | Specify the field ordering of video frames in this track. 916 | 917 | Described in Section 8.1.4.1.31.2 of IETF draft-ietf-cellar-matroska-08 918 | */ 919 | Matroska_FieldOrder = 0x9D, 920 | 921 | /* 922 | Stereo-3D video mode. There are some more details in Section 20.10. 923 | 924 | Described in Section 8.1.4.1.31.3 of IETF draft-ietf-cellar-matroska-08 925 | */ 926 | Matroska_StereoMode = 0x53B8, 927 | 928 | /* 929 | Alpha Video Mode. Presence of this Element indicates that the BlockAdditional Element could contain Alpha data. 930 | 931 | Described in Section 8.1.4.1.31.4 of IETF draft-ietf-cellar-matroska-08 932 | */ 933 | Matroska_AlphaMode = 0x53C0, 934 | 935 | /* 936 | Width, Height of the encoded video frames in pixels. 937 | 938 | Described in Section 8.1.4.1.31.5 .. 6 of IETF draft-ietf-cellar-matroska-08 939 | */ 940 | Matroska_PixelWidth = 0xB0, 941 | Matroska_PixelHeight = 0xBA, 942 | 943 | /* 944 | The number of video pixels to remove at the bottom, top, left, right of the image. 945 | 946 | Described in Section 8.1.4.1.31.7 .. 10 of IETF draft-ietf-cellar-matroska-08 947 | */ 948 | Matroska_PixelCropBottom = 0x54AA, 949 | Matroska_PixelCropTop = 0x54BB, 950 | Matroska_PixelCropLeft = 0x54CC, 951 | Matroska_PixelCropRight = 0x54DD, 952 | 953 | /* 954 | Display Width + Height, and how DisplayWidth & DisplayHeight are interpreted. 955 | 956 | Described in Section 8.1.4.1.31.11 .. 13 of IETF draft-ietf-cellar-matroska-08 957 | */ 958 | Matroska_DisplayWidth = 0x54B0, 959 | Matroska_DisplayHeight = 0x54BA, 960 | Matroska_DisplayUnit = 0x54B2, 961 | 962 | /* 963 | Specify the pixel format used for the Track's data as a FourCC. 964 | This value is similar in scope to the biCompression value of AVI's BITMAPINFOHEADER. 965 | 966 | Described in Section 8.1.4.1.31.14 of IETF draft-ietf-cellar-matroska-08 967 | */ 968 | Matroska_ColourSpace = 0x2EB524, 969 | 970 | /* 971 | Settings describing the colour format. 972 | 973 | Described in Section 8.1.4.1.31.15 of IETF draft-ietf-cellar-matroska-08 974 | */ 975 | Matroska_Colour = 0x55B0, 976 | 977 | /* 978 | The Matrix Coefficients of the video used to derive luma and chroma values from 979 | red, green, and blue color primaries. For clarity, the value and meanings for 980 | MatrixCoefficients are adopted from Table 4 of ISO/IEC 23001-8:2016 or ITU-T H.273. 981 | 982 | Described in Section 8.1.4.1.31.16 of IETF draft-ietf-cellar-matroska-08 983 | */ 984 | Matroska_MatrixCoefficients = 0x55B1, 985 | 986 | /* 987 | Number of decoded bits per channel. A value of 0 indicates that the BitsPerChannel is unspecified. 988 | 989 | Described in Section 8.1.4.1.31.17 of IETF draft-ietf-cellar-matroska-08 990 | */ 991 | Matroska_BitsPerChannel = 0x55B2, 992 | 993 | /* 994 | The amount of pixels to remove in the Cr and Cb channels for every pixel not removed horizontally. 995 | Example: For video with 4:2:0 chroma subsampling, the ChromaSubsamplingHorz SHOULD be set to 1. 996 | 997 | The amount of pixels to remove in the Cr and Cb channels for every pixel not removed vertically. 998 | Example: For video with 4:2:0 chroma subsampling, the ChromaSubsamplingVert SHOULD be set to 1. 999 | 1000 | Described in Section 8.1.4.1.31.18 .. 23 of IETF draft-ietf-cellar-matroska-08 1001 | */ 1002 | Matroska_ChromaSubsamplingHorz = 0x55B3, 1003 | Matroska_ChromaSubsamplingVert = 0x55B4, 1004 | Matroska_CbSubsamplingHorz = 0x55B5, 1005 | Matroska_CbSubsamplingVert = 0x55B6, 1006 | Matroska_ChromaSitingHorz = 0x55B7, 1007 | Matroska_ChromaSitingVert = 0x55B8, 1008 | 1009 | /* 1010 | Clipping of the color ranges. 1011 | 1012 | Described in Section 8.1.4.1.31.24 of IETF draft-ietf-cellar-matroska-08 1013 | */ 1014 | Matroska_Range = 0x55B9, 1015 | 1016 | /* 1017 | The transfer characteristics of the video. For clarity, the value and meanings for 1018 | TransferCharacteristics are adopted from Table 3 of ISO/IEC 23091-4 or ITU-T H.273. 1019 | 1020 | The colour primaries of the video. For clarity, the value and meanings for Primaries 1021 | are adopted from Table 2 of ISO/IEC 23091-4 or ITU-T H.273. 1022 | 1023 | Described in Section 8.1.4.1.31.25 .. 26 of IETF draft-ietf-cellar-matroska-08 1024 | */ 1025 | Matroska_TransferCharacteristics = 0x55BA, 1026 | Matroska_Primaries = 0x55BB, 1027 | 1028 | 1029 | /* 1030 | Maximum brightness of a single pixel (Maximum Content Light Level) in candelas per square meter (cd/m2). 1031 | Maximum brightness of a single full frame (Maximum Frame-Average Light Level) in candelas per square meter (cd/m2). 1032 | 1033 | Described in Section 8.1.4.1.31.27 .. 28 of IETF draft-ietf-cellar-matroska-08 1034 | */ 1035 | Matroska_MaxCLL = 0x55BC, 1036 | Matroska_MaxFALL = 0x55BD, 1037 | 1038 | /* 1039 | SMPTE 2086 mastering data. 1040 | 1041 | Described in Section 8.1.4.1.31.29 of IETF draft-ietf-cellar-matroska-08 1042 | */ 1043 | Matroska_MasteringMetadata = 0x55D0, 1044 | 1045 | /* 1046 | RGB-W chromaticity coordinates, as defined by CIE 1931. 1047 | 1048 | Described in Section 8.1.4.1.31.30 .. 37 of IETF draft-ietf-cellar-matroska-08 1049 | */ 1050 | Matroska_PrimaryRChromaticityX = 0x55D1, 1051 | Matroska_PrimaryRChromaticityY = 0x55D2, 1052 | Matroska_PrimaryGChromaticityX = 0x55D3, 1053 | Matroska_PrimaryGChromaticityY = 0x55D4, 1054 | Matroska_PrimaryBChromaticityX = 0x55D5, 1055 | Matroska_PrimaryBChromaticityY = 0x55D6, 1056 | Matroska_WhitePointChromaticityX = 0x55D7, 1057 | Matroska_WhitePointChromaticityY = 0x55D8, 1058 | 1059 | /* 1060 | Maximum/Minimum luminance. Represented in candelas per square meter (cd/m2). 1061 | 1062 | Described in Section 8.1.4.1.31.38 .. 39 of IETF draft-ietf-cellar-matroska-08 1063 | */ 1064 | Matroska_LuminanceMax = 0x55D9, 1065 | Matroska_LuminanceMin = 0x55DA, 1066 | 1067 | /* 1068 | * Describes the video projection details. Used to render spherical and VR videos. 1069 | * Describes the projection used for this video track. 1070 | * Private data that only applies to a specific projection. 1071 | 1072 | Described in Section 8.1.4.1.31.40 .. 42 of IETF draft-ietf-cellar-matroska-08 1073 | */ 1074 | Matroska_Projection = 0x7670, 1075 | Matroska_ProjectionType = 0x7671, 1076 | Matroska_ProjectionPrivate = 0x7672, 1077 | 1078 | /* 1079 | Projection vector rotation. 1080 | 1081 | Described in Section 8.1.4.1.31.43 .. 45 of IETF draft-ietf-cellar-matroska-08 1082 | */ 1083 | Matroska_ProjectionPoseYaw = 0x7673, 1084 | Matroska_ProjectionPosePitch = 0x7674, 1085 | Matroska_ProjectionPoseRoll = 0x7675, 1086 | 1087 | /* 1088 | Audio settings. 1089 | 1090 | Described in Section 8.1.4.1.32 of IETF draft-ietf-cellar-matroska-08 1091 | */ 1092 | Matroska_Audio = 0xE1, 1093 | Matroska_SamplingFrequency = 0xB5, 1094 | Matroska_OutputSamplingFrequency = 0x78B5, 1095 | Matroska_Channels = 0x9F, 1096 | Matroska_BitDepth = 0x6264, 1097 | 1098 | /* 1099 | Operation that needs to be applied on tracks to create this virtual track. 1100 | For more details look at Section 20.8. 1101 | 1102 | Contains the list of all video plane tracks that need to be combined to create this 3D track. 1103 | 1104 | Contains a video plane track that need to be combined to create this 3D track. 1105 | 1106 | Described in Section 8.1.4.1.33 .. 33.2 of IETF draft-ietf-cellar-matroska-08 1107 | */ 1108 | Matroska_TrackOperation = 0xE2, 1109 | Matroska_TrackCombinePlanes = 0xE3, 1110 | Matroska_TrackPlane = 0xE4, 1111 | 1112 | /* 1113 | The trackUID number of the track representing the plane. 1114 | The kind of plane this track corresponds to. 1115 | 1116 | Described in Section 8.1.4.1.33.3 .. 4 of IETF draft-ietf-cellar-matroska-08 1117 | */ 1118 | Matroska_TrackPlaneUID = 0xE5, 1119 | Matroska_TrackPlaneType = 0xE6, 1120 | 1121 | /* 1122 | Contains the list of all tracks whose Blocks need to be combined to create this virtual track. 1123 | 1124 | Described in Section 8.1.4.1.33.5 of IETF draft-ietf-cellar-matroska-08 1125 | */ 1126 | Matroska_TrackJoinBlocks = 0xE9, 1127 | 1128 | /* 1129 | The trackUID number of a track whose blocks are used to create this virtual track. 1130 | 1131 | Described in Section 8.1.4.1.33.6 of IETF draft-ietf-cellar-matroska-08 1132 | */ 1133 | Matroska_TrackJoinUID = 0xED, 1134 | 1135 | /* 1136 | Settings for several content encoding mechanisms like compression or encryption. 1137 | 1138 | Described in Section 8.1.4.1.34 of IETF draft-ietf-cellar-matroska-08 1139 | */ 1140 | Matroska_ContentEncodings = 0x6D80, 1141 | 1142 | /* 1143 | Settings for one content encoding like compression or encryption. 1144 | 1145 | Described in Section 8.1.4.1.34.1 of IETF draft-ietf-cellar-matroska-08 1146 | */ 1147 | Matroska_ContentEncoding = 0x6240, 1148 | 1149 | /* 1150 | Tells when this modification was used during encoding/muxing starting with 0 and counting upwards. 1151 | The decoder/demuxer has to start with the highest order number it finds and work its way down. 1152 | This value has to be unique over all ContentEncodingOrder Elements in the TrackEntry that 1153 | contains this ContentEncodingOrder element. 1154 | 1155 | Described in Section 8.1.4.1.34.2 of IETF draft-ietf-cellar-matroska-08 1156 | */ 1157 | Matroska_ContentEncodingOrder = 0x5031, 1158 | 1159 | /* 1160 | A bit field that describes which Elements have been modified in this way. 1161 | Values (big-endian) can be OR'ed. 1162 | 1163 | Described in Section 8.1.4.1.34.3 of IETF draft-ietf-cellar-matroska-08 1164 | */ 1165 | Matroska_ContentEncodingScope = 0x5032, 1166 | 1167 | /* 1168 | A value describing what kind of transformation is applied. 1169 | 1170 | Described in Section 8.1.4.1.34.4 of IETF draft-ietf-cellar-matroska-08 1171 | */ 1172 | Matroska_ContentEncodingType = 0x5033, 1173 | 1174 | /* 1175 | Settings describing the compression used. This Element MUST be present if the value of 1176 | ContentEncodingType is 0 and absent otherwise. Each block MUST be decompressable even if no 1177 | previous block is available in order not to prevent seeking. 1178 | 1179 | Described in Section 8.1.4.1.34.5 of IETF draft-ietf-cellar-matroska-08 1180 | */ 1181 | Matroska_ContentCompression = 0x5034, 1182 | 1183 | /* 1184 | The compression algorithm used. 1185 | 1186 | Described in Section 8.1.4.1.34.6 of IETF draft-ietf-cellar-matroska-08 1187 | */ 1188 | Matroska_ContentCompAlgo = 0x4254, 1189 | 1190 | /* 1191 | Settings that might be needed by the decompressor. For Header Stripping (ContentCompAlgo=3), 1192 | the bytes that were removed from the beginning of each frames of the track. 1193 | 1194 | Described in Section 8.1.4.1.34.7 of IETF draft-ietf-cellar-matroska-08 1195 | */ 1196 | Matroska_ContentCompSettings = 0x4255, 1197 | 1198 | /* 1199 | Settings describing the encryption used. This Element MUST be present if the value of 1200 | ContentEncodingType is 1 (encryption) and MUST be ignored otherwise. 1201 | 1202 | Described in Section 8.1.4.1.34.8 of IETF draft-ietf-cellar-matroska-08 1203 | */ 1204 | Matroska_ContentEncryption = 0x5035, 1205 | 1206 | /* 1207 | The encryption algorithm used. The value "0" means that the contents have not been encrypted. 1208 | 1209 | Described in Section 8.1.4.1.34.9 of IETF draft-ietf-cellar-matroska-08 1210 | */ 1211 | Matroska_ContentEncAlgo = 0x47E1, 1212 | 1213 | /* 1214 | For public key algorithms this is the ID of the public key the the data was encrypted with. 1215 | 1216 | Described in Section 8.1.4.1.34.10 of IETF draft-ietf-cellar-matroska-08 1217 | */ 1218 | Matroska_ContentEncKeyID = 0x47E2, 1219 | 1220 | /* 1221 | Settings describing the encryption algorithm used. If ContentEncAlgo != 5 this MUST be ignored. 1222 | 1223 | Described in Section 8.1.4.1.34.11 of IETF draft-ietf-cellar-matroska-08 1224 | */ 1225 | Matroska_ContentEncAESSettings = 0x47E7, 1226 | 1227 | /* 1228 | The AES cipher mode used in the encryption. 1229 | 1230 | Described in Section 8.1.4.1.34.12 of IETF draft-ietf-cellar-matroska-08 1231 | */ 1232 | Matroska_AESSettingsCipherMode = 0x47E8, 1233 | 1234 | /* 1235 | A Top-Level Element to speed seeking access. All entries are local to the Segment. 1236 | 1237 | Described in Section 8.1.5 of IETF draft-ietf-cellar-matroska-08 1238 | */ 1239 | Matroska_Cues = 0x1C53BB6B, 1240 | 1241 | /* 1242 | Contains all information relative to a seek point in the Segment. 1243 | 1244 | Described in Section 8.1.5.1 of IETF draft-ietf-cellar-matroska-08 1245 | */ 1246 | Matroska_CuePoint = 0xBB, 1247 | 1248 | /* 1249 | Absolute timestamp according to the Segment time base. 1250 | 1251 | Described in Section 8.1.5.1.1 of IETF draft-ietf-cellar-matroska-08 1252 | */ 1253 | Matroska_CueTime = 0xB3, 1254 | 1255 | /* 1256 | Contains all information relative to a seek point in the Segment. 1257 | 1258 | Described in Section 8.1.5.1.2 of IETF draft-ietf-cellar-matroska-08 1259 | */ 1260 | Matroska_CueTrackPositions = 0xB7, 1261 | 1262 | /* 1263 | The track for which a position is given. 1264 | 1265 | Described in Section 8.1.5.1.2.1 of IETF draft-ietf-cellar-matroska-08 1266 | */ 1267 | Matroska_CueTrack = 0xF7, 1268 | 1269 | /* 1270 | The Segment Position of the Cluster containing the associated Block. 1271 | 1272 | Described in Section 8.1.5.1.2.2 of IETF draft-ietf-cellar-matroska-08 1273 | */ 1274 | Matroska_CueClusterPosition = 0xF1, 1275 | 1276 | /* 1277 | The relative position inside the Cluster of the referenced SimpleBlock or BlockGroup with 0 1278 | being the first possible position for an Element inside that Cluster. 1279 | 1280 | Described in Section 8.1.5.1.2.3 of IETF draft-ietf-cellar-matroska-08 1281 | */ 1282 | Matroska_CueRelativePosition = 0xF0, 1283 | 1284 | /* 1285 | The duration of the block according to the Segment time base. If missing the track's 1286 | DefaultDuration does not apply and no duration information is available in terms of the cues. 1287 | 1288 | Described in Section 8.1.5.1.2.4 of IETF draft-ietf-cellar-matroska-08 1289 | */ 1290 | Matroska_CueDuration = 0xB2, 1291 | 1292 | /* 1293 | Number of the Block in the specified Cluster. 1294 | 1295 | Described in Section 8.1.5.1.2.5 of IETF draft-ietf-cellar-matroska-08 1296 | */ 1297 | Matroska_CueBlockNumber = 0x5378, 1298 | 1299 | /* 1300 | The Segment Position of the Codec State corresponding to this Cue Element. 1301 | 0 means that the data is taken from the initial Track Entry. 1302 | 1303 | Described in Section 8.1.5.1.2.6 of IETF draft-ietf-cellar-matroska-08 1304 | */ 1305 | Matroska_CueCodecState = 0xEA, 1306 | 1307 | /* 1308 | The Clusters containing the referenced Blocks. 1309 | 1310 | Described in Section 8.1.5.1.2.7 of IETF draft-ietf-cellar-matroska-08 1311 | */ 1312 | Matroska_CueReference = 0xDB, 1313 | 1314 | /* 1315 | Timestamp of the referenced Block. 1316 | 1317 | Described in Section 8.1.5.1.2.8 of IETF draft-ietf-cellar-matroska-08 1318 | */ 1319 | Matroska_CueRefTime = 0x96, 1320 | 1321 | 1322 | /* 1323 | Contain attached files. 1324 | 1325 | Described in Section 8.1.6 of IETF draft-ietf-cellar-matroska-08 1326 | */ 1327 | Matroska_Segment_Attachment = 0x1941A469, 1328 | 1329 | /* 1330 | An attached file. 1331 | 1332 | Described in Section 8.1.6.1 of IETF draft-ietf-cellar-matroska-08 1333 | */ 1334 | Matroska_AttachedFile = 0x61A7, 1335 | 1336 | /* 1337 | A human-friendly name for the attached file. 1338 | 1339 | Described in Section 8.1.6.1.1 of IETF draft-ietf-cellar-matroska-08 1340 | */ 1341 | Matroska_FileDescription = 0x467E, 1342 | 1343 | /* 1344 | Filename of the attached file. 1345 | 1346 | Described in Section 8.1.6.1.2 of IETF draft-ietf-cellar-matroska-08 1347 | */ 1348 | Matroska_FileName = 0x466E, 1349 | 1350 | /* 1351 | MIME type of the file. 1352 | 1353 | Described in Section 8.1.6.1.3 of IETF draft-ietf-cellar-matroska-08 1354 | */ 1355 | Matroska_FileMimeType = 0x4660, 1356 | 1357 | /* 1358 | The data of the file. 1359 | 1360 | Described in Section 8.1.6.1.4 of IETF draft-ietf-cellar-matroska-08 1361 | */ 1362 | Matroska_FileData = 0x465C, 1363 | 1364 | /* 1365 | Unique ID representing the file, as random as possible. 1366 | 1367 | Described in Section 8.1.6.1.5 of IETF draft-ietf-cellar-matroska-08 1368 | */ 1369 | Matroska_FileUID = 0x46AE, 1370 | 1371 | /* 1372 | A system to define basic menus and partition data. For more detailed information, 1373 | look at the Chapters explanation in Section 22. 1374 | 1375 | Described in Section 8.1.7 of IETF draft-ietf-cellar-matroska-08 1376 | */ 1377 | Matroska_Chapters = 0x1043A770, 1378 | 1379 | /* 1380 | Contains all information about a Segment edition. 1381 | 1382 | Described in Section 8.1.7.1 of IETF draft-ietf-cellar-matroska-08 1383 | */ 1384 | Matroska_EditionEntry = 0x45B9, 1385 | 1386 | /* 1387 | A unique ID to identify the edition. It's useful for tagging an edition. 1388 | 1389 | Described in Section 8.1.7.1.1 of IETF draft-ietf-cellar-matroska-08 1390 | */ 1391 | Matroska_EditionUID = 0x45BC, 1392 | 1393 | /* 1394 | Set to 1 if an edition is hidden. Hidden editions **SHOULD NOT** be available to the user interface. 1395 | 1396 | Described in https://www.matroska.org/technical/elements.html 1397 | */ 1398 | Matroska_EditionFlagHidden = 0x45BD, 1399 | 1400 | /* 1401 | Set to 1 if the edition SHOULD be used as the default one. 1402 | 1403 | Described in Section 8.1.7.1.2 of IETF draft-ietf-cellar-matroska-08 1404 | */ 1405 | Matroska_EditionFlagDefault = 0x45DB, 1406 | 1407 | /* 1408 | Set to 1 if the chapters can be defined multiple times and the order to play them is enforced; see Section 22.1.3. 1409 | 1410 | Described in Section 8.1.7.1.3 of IETF draft-ietf-cellar-matroska-08 1411 | */ 1412 | Matroska_EditionFlagOrdered = 0x45DD, 1413 | 1414 | /* 1415 | Contains the atom information to use as the chapter atom (apply to all tracks). 1416 | 1417 | Described in Section 8.1.7.1.4 of IETF draft-ietf-cellar-matroska-08 1418 | */ 1419 | Matroska_ChapterAtom = 0xB6, 1420 | 1421 | /* 1422 | A unique ID to identify the Chapter. 1423 | 1424 | Described in Section 8.1.7.1.4.1 of IETF draft-ietf-cellar-matroska-08 1425 | */ 1426 | Matroska_ChapterUID = 0x73C4, 1427 | 1428 | /* 1429 | A unique string ID to identify the Chapter. Use for WebVTT cue identifier storage [WebVTT]. 1430 | 1431 | Described in Section 8.1.7.1.4.2 of IETF draft-ietf-cellar-matroska-08 1432 | */ 1433 | Matroska_ChapterStringUID = 0x5654, 1434 | 1435 | /* 1436 | Timestamp of the start of Chapter (not scaled). 1437 | 1438 | Described in Section 8.1.7.1.4.3 of IETF draft-ietf-cellar-matroska-08 1439 | */ 1440 | Matroska_ChapterTimeStart = 0x91, 1441 | 1442 | /* 1443 | Timestamp of the end of Chapter (timestamp excluded, not scaled). 1444 | The value MUST be strictly greater than the ChapterTimeStart of the same ChapterAtom. 1445 | 1446 | Described in Section 8.1.7.1.4.4 of IETF draft-ietf-cellar-matroska-08 1447 | */ 1448 | Matroska_ChapterTimeEnd = 0x92, 1449 | 1450 | /* 1451 | Set to 1 if a chapter is hidden. Hidden chapters it SHOULD NOT be available to the user interface 1452 | (but still to Control Tracks; see Section 22.2.3 on Chapter flags). 1453 | 1454 | Described in Section 8.1.7.1.4.5 of IETF draft-ietf-cellar-matroska-08 1455 | */ 1456 | Matroska_ChapterFlagHidden = 0x98, 1457 | 1458 | /* 1459 | Set to 1 if the chapter is enabled. It can be enabled/disabled by a Control Track. 1460 | When disabled, the movie **SHOULD** skip all the content between the TimeStart and TimeEnd of this chapter; 1461 | see notes on Chapter flags. 1462 | 1463 | Described in https://www.matroska.org/technical/elements.html 1464 | */ 1465 | Matroska_ChapterFlagEnabled = 0x4598, 1466 | 1467 | /* 1468 | The SegmentUID of another Segment to play during this chapter. 1469 | 1470 | Described in Section 8.1.7.1.4.6 of IETF draft-ietf-cellar-matroska-08 1471 | */ 1472 | Matroska_ChapterSegmentUID = 0x6E67, 1473 | 1474 | /* 1475 | The EditionUID to play from the Segment linked in ChapterSegmentUID. 1476 | If ChapterSegmentEditionUID is undeclared, then no Edition of the linked Segment is used; 1477 | see Section 19.2 on medium-linking Segments. 1478 | 1479 | Described in Section 8.1.7.1.4.7 of IETF draft-ietf-cellar-matroska-08 1480 | */ 1481 | Matroska_ChapterSegmentEditionUID = 0x6EBC, 1482 | 1483 | /* 1484 | Specify the physical equivalent of this ChapterAtom like "DVD" (60) or "SIDE" (50); 1485 | see Section 22.4 for a complete list of values. 1486 | 1487 | Described in Section 8.1.7.1.4.8 of IETF draft-ietf-cellar-matroska-08 1488 | */ 1489 | Matroska_ChapterPhysicalEquiv = 0x63C3, 1490 | 1491 | /* 1492 | Contains all possible strings to use for the chapter display. 1493 | 1494 | Described in Section 8.1.7.1.4.9 of IETF draft-ietf-cellar-matroska-08 1495 | */ 1496 | Matroska_ChapterDisplay = 0x80, 1497 | 1498 | /* 1499 | Contains the string to use as the chapter atom. 1500 | 1501 | Described in Section 8.1.7.1.4.10 of IETF draft-ietf-cellar-matroska-08 1502 | */ 1503 | Matroska_ChapString = 0x85, 1504 | 1505 | /* 1506 | A language corresponding to the string, in the bibliographic ISO-639-2 form [ISO639-2]. 1507 | This Element MUST be ignored if a ChapLanguageIETF Element is used within the same ChapterDisplay Element. 1508 | 1509 | Described in Section 8.1.7.1.4.11 of IETF draft-ietf-cellar-matroska-08 1510 | */ 1511 | Matroska_ChapLanguage = 0x437C, 1512 | 1513 | /* 1514 | Specifies a language corresponding to the ChapString in the format defined in [BCP47] 1515 | and using the IANA Language Subtag Registry [IANALangRegistry]. 1516 | If a ChapLanguageIETF Element is used, then any ChapLanguage and ChapCountry Elements used in the same 1517 | ChapterDisplay MUST be ignored. 1518 | 1519 | Described in Section 8.1.7.1.4.12 of IETF draft-ietf-cellar-matroska-08 1520 | */ 1521 | Matroska_ChapLanguageIETF = 0x437D, 1522 | 1523 | /* 1524 | A country corresponding to the string, using the same 2 octets country-codes as in Internet domains 1525 | [IANADomains] based on [ISO3166-1] alpha-2 codes. This Element MUST be ignored if a ChapLanguageIETF 1526 | Element is used within the same ChapterDisplay Element. 1527 | 1528 | Described in Section 8.1.7.1.4.13 of IETF draft-ietf-cellar-matroska-08 1529 | */ 1530 | Matroska_ChapCountry = 0x437E, 1531 | 1532 | /* 1533 | Contains all the commands associated to the Atom. 1534 | 1535 | Described in Section 8.1.7.1.4.14 of IETF draft-ietf-cellar-matroska-08 1536 | */ 1537 | Matroska_ChapProcess = 0x6944, 1538 | 1539 | /* 1540 | Contains the type of the codec used for the processing. A value of 0 means native Matroska processing 1541 | (to be defined), a value of 1 means the DVD command set is used; see Section 22.3 on DVD menus. 1542 | More codec IDs can be added later. 1543 | 1544 | Described in Section 8.1.7.1.4.15 of IETF draft-ietf-cellar-matroska-08 1545 | */ 1546 | Matroska_ChapProcessCodecID = 0x6955, 1547 | 1548 | /* 1549 | Some optional data attached to the ChapProcessCodecID information. For ChapProcessCodecID = 1, 1550 | it is the "DVD level" equivalent; see Section 22.3 on DVD menus. 1551 | 1552 | Described in Section 8.1.7.1.4.16 of IETF draft-ietf-cellar-matroska-08 1553 | */ 1554 | Matroska_ChapProcessPrivate = 0x450D, 1555 | 1556 | /* 1557 | Contains all the commands associated to the Atom. 1558 | 1559 | Described in Section 8.1.7.1.4.17 of IETF draft-ietf-cellar-matroska-08 1560 | */ 1561 | Matroska_ChapProcessCommand = 0x6911, 1562 | 1563 | /* 1564 | Defines when the process command SHOULD be handled. 1565 | 1566 | Described in Section 8.1.7.1.4.18 of IETF draft-ietf-cellar-matroska-08 1567 | */ 1568 | Matroska_ChapProcessTime = 0x6922, 1569 | 1570 | /* 1571 | Contains the command information. The data SHOULD be interpreted depending on the ChapProcessCodecID value. 1572 | For ChapProcessCodecID = 1, the data correspond to the binary DVD cell pre/post commands; 1573 | see Section 22.3 on DVD menus. 1574 | 1575 | Described in Section 8.1.7.1.4.19 of IETF draft-ietf-cellar-matroska-08 1576 | */ 1577 | Matroska_ChapProcessData = 0x6933, 1578 | 1579 | /* 1580 | Element containing metadata describing Tracks, Editions, Chapters, Attachments, or the Segment as a whole. 1581 | A list of valid tags can be found in [MatroskaTags]. 1582 | 1583 | Described in Section 8.1.8 of IETF draft-ietf-cellar-matroska-08 1584 | */ 1585 | Matroska_Tags = 0x1254C367, 1586 | 1587 | /* 1588 | A single metadata descriptor. 1589 | 1590 | Described in Section 8.1.8.1 of IETF draft-ietf-cellar-matroska-08 1591 | */ 1592 | Matroska_Tag = 0x7373, 1593 | 1594 | /* 1595 | Specifies which other elements the metadata represented by the Tag applies to. 1596 | If empty or not present, then the Tag describes everything in the Segment. 1597 | 1598 | Described in Section 8.1.8.1.1 of IETF draft-ietf-cellar-matroska-08 1599 | */ 1600 | Matroska_Targets = 0x63C0, 1601 | 1602 | /* 1603 | A number to indicate the logical level of the target. 1604 | 1605 | Described in Section 8.1.8.1.1.1 of IETF draft-ietf-cellar-matroska-08 1606 | */ 1607 | Matroska_TargetTypeValue = 0x68CA, 1608 | 1609 | /* 1610 | An informational string that can be used to display the logical level of the target like 1611 | "ALBUM", "TRACK", "MOVIE", "CHAPTER", etc ; see Section 6.4 of [MatroskaTags]. 1612 | 1613 | Described in Section 8.1.8.1.1.2 of IETF draft-ietf-cellar-matroska-08 1614 | */ 1615 | Matroska_TargetType = 0x63CA, 1616 | 1617 | /* 1618 | A unique ID to identify the Track(s) the tags belong to. 1619 | 1620 | Described in Section 8.1.8.1.1.3 of IETF draft-ietf-cellar-matroska-08 1621 | */ 1622 | Matroska_TagTrackUID = 0x63C5, 1623 | 1624 | /* 1625 | A unique ID to identify the EditionEntry(s) the tags belong to. 1626 | 1627 | Described in Section 8.1.8.1.1.4 of IETF draft-ietf-cellar-matroska-08 1628 | */ 1629 | Matroska_TagEditionUID = 0x63C9, 1630 | 1631 | /* 1632 | A unique ID to identify the Chapter(s) the tags belong to. 1633 | 1634 | Described in Section 8.1.8.1.1.5 of IETF draft-ietf-cellar-matroska-08 1635 | */ 1636 | Matroska_TagChapterUID = 0x63C4, 1637 | 1638 | /* 1639 | A unique ID to identify the Attachment(s) the tags belong to. 1640 | 1641 | Described in Section 8.1.8.1.1.6 of IETF draft-ietf-cellar-matroska-08 1642 | */ 1643 | Matroska_TagAttachmentUID = 0x63C6, 1644 | 1645 | /* 1646 | Contains general information about the target. 1647 | 1648 | Described in Section 8.1.8.1.2 of IETF draft-ietf-cellar-matroska-08 1649 | */ 1650 | Matroska_SimpleTag = 0x67C8, 1651 | 1652 | /* 1653 | The name of the Tag that is going to be stored. 1654 | 1655 | Described in Section 8.1.8.1.2.1 of IETF draft-ietf-cellar-matroska-08 1656 | */ 1657 | Matroska_TagName = 0x45A3, 1658 | 1659 | /* 1660 | Specifies the language of the tag specified, in the Matroska languages form; see Section 6 on language codes. 1661 | This Element MUST be ignored if the TagLanguageIETF Element is used within the same SimpleTag Element. 1662 | 1663 | Described in Section 8.1.8.1.2.2 of IETF draft-ietf-cellar-matroska-08 1664 | */ 1665 | Matroska_TagLanguage = 0x447A, 1666 | 1667 | /* 1668 | Specifies the language used in the TagString according to [BCP47] and using the IANA Language Subtag Registry 1669 | [IANALangRegistry]. If this Element is used, then any TagLanguage Elements used in the same SimpleTag MUST be ignored. 1670 | 1671 | Described in Section 8.1.8.1.2.3 of IETF draft-ietf-cellar-matroska-08 1672 | */ 1673 | Matroska_TagLanguageIETF = 0x447B, 1674 | 1675 | /* 1676 | A boolean value to indicate if this is the default/original language to use for the given tag. 1677 | 1678 | Described in Section 8.1.8.1.2.4 of IETF draft-ietf-cellar-matroska-08 1679 | */ 1680 | Matroska_TagDefault = 0x4484, 1681 | 1682 | /* 1683 | The value of the Tag. 1684 | 1685 | Described in Section 8.1.8.1.2.5 of IETF draft-ietf-cellar-matroska-08 1686 | */ 1687 | Matroska_TagString = 0x4487, 1688 | 1689 | /* 1690 | The values of the Tag, if it is binary. Note that this cannot be used in the same SimpleTag as TagString. 1691 | 1692 | Described in Section 8.1.8.1.2.6 of IETF draft-ietf-cellar-matroska-08 1693 | */ 1694 | Matroska_TagBinary = 0x4485, 1695 | 1696 | 1697 | 1698 | } 1699 | 1700 | /* 1701 | TODO(Jeroen): Think about replacing the parsers with table-driven ones. 1702 | where an array indexed by the id decides whether to intern (and as what type), skip, or handle in a special manner. 1703 | */ 1704 | 1705 | EBML_ID_Operation :: enum { 1706 | /* 1707 | Intern by type 1708 | */ 1709 | Intern, 1710 | /* 1711 | Handle the ID specially 1712 | */ 1713 | Special, 1714 | /* 1715 | Just save offsets, etc, and skip to the next element. 1716 | */ 1717 | Skip, 1718 | } 1719 | 1720 | EBML_Type_Info :: struct { 1721 | type: EBML_Type, 1722 | operation: EBML_ID_Operation, 1723 | name: string, 1724 | } 1725 | 1726 | Matroska_Schema :: #partial #sparse[EBML_ID]EBML_Type_Info { 1727 | .Matroska_Segment = { 1728 | .Master, .Intern, "Segment", 1729 | }, 1730 | } -------------------------------------------------------------------------------- /ebml/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd example 3 | call build.bat 4 | popd -------------------------------------------------------------------------------- /ebml/example/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | odin build . -out:example.exe -vet -define:EBML_DEBUG=true 3 | : -opt:1 -debug 4 | if %errorlevel% neq 0 goto end_of_build 5 | example 6 | 7 | 8 | :end_of_build -------------------------------------------------------------------------------- /ebml/example/ebml_example.odin: -------------------------------------------------------------------------------- 1 | package ebml_example 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | This file is an example that parses an EBML file (mkv/webm/...) and prints the parse tree. 30 | */ 31 | 32 | import "core:mem" 33 | import "core:os" 34 | import "core:fmt" 35 | import "core:time" 36 | import ebml ".." 37 | import "../../common" 38 | 39 | parse_metadata := true 40 | skip_clusters := false 41 | return_after_cluster := false 42 | 43 | _main :: proc() { 44 | EXE_NAME := os.args[0] 45 | 46 | if len(os.args) == 1 { 47 | fmt.println("EBML File Format parser example") 48 | fmt.printf("Usage: %v [ebml filename]\n\n", EXE_NAME) 49 | os.exit(1) 50 | } 51 | 52 | file := os.args[1] 53 | f, err := ebml.open(file) 54 | defer ebml.close(f) 55 | 56 | if err != nil { 57 | fmt.printf("Couldn't open '%v'. Err: %v\n", file, err) 58 | return 59 | } 60 | 61 | fmt.printf("\nOpened '%v'\n", file) 62 | fmt.printf("\tFile size: %v\n", f.file_info.size) 63 | fmt.printf("\tCreated: %v\n", f.file_info.creation_time) 64 | fmt.printf("\tModified: %v\n", f.file_info.modification_time) 65 | 66 | fmt.println("\n-=-=-=-=-=-=- PARSING FILE -=-=-=-=-=-=-") 67 | 68 | parse_start := time.now() 69 | e := ebml.parse(f, parse_metadata, skip_clusters, return_after_cluster) 70 | parse_end := time.now() 71 | parse_diff := time.diff(parse_start, parse_end) 72 | 73 | fmt.println("\n-=-=-=-=-=-=- PARSED FILE -=-=-=-=-=-=-") 74 | fmt.printf("Parse Error: %v\n\n", e) 75 | 76 | print_start := time.now() 77 | ebml.print(f) 78 | print_end := time.now() 79 | print_diff := time.diff(print_start, print_end) 80 | 81 | size: i64 82 | if return_after_cluster { 83 | if pos, err := common.get_pos(f.handle); err != nil { 84 | return 85 | } else { 86 | size = pos 87 | } 88 | } else { 89 | size = f.file_info.size 90 | } 91 | 92 | parse_speed := f64(time.Second) / f64(parse_diff) * f64(f.file_info.size) / f64(1024 * 1024) 93 | fmt.printfln("Parse: %.3f ms (%f MiB/s).", 1_000 * time.duration_seconds(parse_diff), parse_speed) 94 | fmt.printfln("Print: %.3f ms.", 1_000 * time.duration_seconds(print_diff)) 95 | } 96 | 97 | main :: proc() { 98 | track: mem.Tracking_Allocator 99 | mem.tracking_allocator_init(&track, context.allocator) 100 | context.allocator = mem.tracking_allocator(&track) 101 | 102 | _main() 103 | 104 | if len(track.allocation_map) > 0 { 105 | fmt.println() 106 | for _, v in track.allocation_map { 107 | fmt.printf("%v Leaked %v bytes: %#v\n", v.location, v.size, (^ebml.EBML_Element)(v.memory)^) 108 | } 109 | } 110 | } -------------------------------------------------------------------------------- /ebml/helpers.odin: -------------------------------------------------------------------------------- 1 | package ebml 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | A from-scratch implementation of the Extensible Binary Meta Language (EBML), 30 | as specified in [IETF RFC 8794](https://www.rfc-editor.org/rfc/rfc8794). 31 | 32 | The EBML format is the base format upon which Matroska (MKV) and WebM are based. 33 | 34 | This file contains the EBML type helpers. 35 | */ 36 | 37 | import "base:intrinsics" 38 | import "base:runtime" 39 | import "core:reflect" 40 | import "core:time" 41 | import "core:hash" 42 | 43 | import "core:fmt" 44 | import "../common" 45 | 46 | CRC_BLOCK_SIZE :: 4096 47 | 48 | clz :: intrinsics.count_leading_zeros 49 | 50 | @thread_local PRINT_BUFFER: [512]u8 51 | 52 | read_variable_id :: proc(f: ^EBML_File) -> (res: EBML_ID, length: u8, err: Error) { 53 | assert(f != nil) 54 | 55 | b0 := common.read_u8(f.handle) or_return 56 | length = clz(b0) + 1 57 | val := u64be(b0) 58 | 59 | if length == 1 { return EBML_ID(val), 1, nil } 60 | data := common.read_slice(f.handle, length - 1) or_return 61 | 62 | for v in data { 63 | val <<= 8 64 | val |= u64be(v) 65 | } 66 | return EBML_ID(val), length, nil 67 | } 68 | 69 | read_variable_int :: proc(f: ^EBML_File) -> (res: u64, length: u8, err: Error) { 70 | assert(f != nil) 71 | 72 | b0 := common.read_u8(f.handle) or_return 73 | 74 | length = clz(b0) + 1 75 | res = u64(b0) 76 | 77 | if length == 1 { return res & 0x7f, 1, nil } 78 | 79 | data := common.read_slice(f.handle, length - 1) or_return 80 | 81 | for v in data { 82 | res <<= 8 83 | res |= u64(v) 84 | } 85 | return res & ((1 << (length * 7) - 1)), length, nil 86 | } 87 | 88 | _read_uint :: proc(f: ^EBML_File, length: u64) -> (res: u64, err: Error) { 89 | assert(f != nil) 90 | 91 | switch length { 92 | case 0: 93 | return 0, nil 94 | 95 | case 1: 96 | b0 := common.read_u8(f.handle) or_return 97 | return u64(b0), nil 98 | 99 | case 2..=8: 100 | data := common.read_slice(f.handle, length) or_return 101 | 102 | for v in data { 103 | res <<= 8 104 | res |= u64(v) 105 | } 106 | return res, nil 107 | 108 | case: 109 | return 0, .Unsigned_Invalid_Length 110 | } 111 | } 112 | 113 | intern_uint :: proc(f: ^EBML_File, length: u64, this: ^EBML_Element) -> (err: Error) { 114 | assert(f != nil && this != nil) 115 | 116 | this.type = .Unsigned 117 | this.payload = _read_uint(f, length) or_return 118 | 119 | return 120 | } 121 | 122 | _read_sint :: proc(f: ^EBML_File, length: u64) -> (res: i64, err: Error) { 123 | assert(f != nil) 124 | 125 | switch length { 126 | case 0: 127 | return 0, nil 128 | 129 | case 1..=8: 130 | data := common.read_slice(f.handle, length) or_return 131 | 132 | res = 0 133 | if data[0] & 0x80 == 0x80 { 134 | res = -1 135 | } 136 | 137 | for v in data { 138 | res <<= 8 139 | res |= i64(v) 140 | } 141 | return res, nil 142 | 143 | case: 144 | return 0, .Signed_Invalid_Length 145 | } 146 | } 147 | 148 | intern_sint :: proc(f: ^EBML_File, length: u64, this: ^EBML_Element) -> (err: Error) { 149 | assert(f != nil && this != nil) 150 | 151 | this.type = .Signed 152 | this.payload = _read_sint(f, length) or_return 153 | 154 | return 155 | } 156 | 157 | _read_float :: proc(f: ^EBML_File, length: u64) -> (res: f64, err: Error) { 158 | assert(f != nil) 159 | 160 | switch length { 161 | case 0: 162 | return 0.0, nil 163 | 164 | case 4: 165 | fl := common.read_data(f.handle, f32be) or_return 166 | return f64(fl), nil 167 | 168 | case 8: 169 | fl := common.read_data(f.handle, f64be) or_return 170 | return f64(fl), nil 171 | 172 | case: 173 | return 0.0, .Float_Invalid_Length 174 | } 175 | } 176 | 177 | intern_float :: proc(f: ^EBML_File, length: u64, this: ^EBML_Element) -> (err: Error) { 178 | assert(f != nil && this != nil) 179 | 180 | this.type = .Float 181 | this.payload = _read_float(f, length) or_return 182 | 183 | return 184 | } 185 | 186 | read_string :: proc(f: ^EBML_File, length: u64, utf8 := false) -> (res: String, err: Error) { 187 | assert(f != nil) 188 | 189 | data := common.read_slice(f.handle, length, f.allocator) or_return 190 | 191 | for ch, i in data { 192 | printable, terminator := is_printable(ch) 193 | 194 | if terminator { 195 | data = data[:i] 196 | break 197 | } 198 | 199 | if !printable && !utf8 { 200 | delete(data) 201 | return "", .Unprintable_String 202 | } 203 | } 204 | return String(data), nil 205 | } 206 | 207 | intern_string :: proc(f: ^EBML_File, length: u64, this: ^EBML_Element) -> (err: Error) { 208 | assert(f != nil && this != nil) 209 | 210 | this.type = .String 211 | this.payload = read_string(f, length) or_return 212 | 213 | return 214 | } 215 | 216 | read_utf8 :: proc(f: ^EBML_File, length: u64) -> (res: string, err: Error) { 217 | s, e := read_string(f, length, true) 218 | 219 | return string(s), e 220 | } 221 | 222 | intern_utf8 :: proc(f: ^EBML_File, length: u64, this: ^EBML_Element) -> (err: Error) { 223 | assert(f != nil && this != nil) 224 | 225 | this.type = .UTF_8 226 | this.payload = read_utf8(f, length) or_return 227 | 228 | return 229 | } 230 | 231 | skip_binary :: proc(f: ^EBML_File, length: u64, this: ^EBML_Element) -> (err: Error) { 232 | this.type = .Binary 233 | return common.set_pos(f.handle, this.end + 1) 234 | } 235 | 236 | intern_binary :: proc(f: ^EBML_File, length: u64, this: ^EBML_Element) -> (err: Error) { 237 | this.type = .Binary 238 | this.payload = [dynamic]u8{} 239 | 240 | payload := common.read_slice(f.handle, length) or_return 241 | append(&this.payload.([dynamic]u8), ..payload) 242 | 243 | return 244 | } 245 | 246 | 247 | verify_crc32 :: proc(f: ^EBML_File, element: ^EBML_Element) -> (err: Error) { 248 | if f == nil || element == nil { 249 | // Return false if given a bogus element. 250 | return .Invalid_CRC 251 | } 252 | 253 | if element.first_child == nil || element.first_child.id != .CRC_32 { 254 | // This element doesn't have a CRC-32 check, so consider it verified. 255 | return 256 | } 257 | 258 | if checksum, checksum_ok := element.first_child.payload.(u64); !checksum_ok { 259 | // A CRC-32 payload always has to be the first element, per the spec. 260 | return .Invalid_CRC 261 | } else { 262 | cur_pos := common.get_pos(f.handle) or_return 263 | 264 | start := element.first_child.end + 1 265 | end := element.end + 1 266 | 267 | common.set_pos(f.handle, start) or_return 268 | 269 | size := end - start 270 | computed_crc: u32 271 | 272 | for size > 0 { 273 | block_size := min(size, CRC_BLOCK_SIZE) 274 | data := common.read_slice(f.handle, block_size) or_return 275 | computed_crc = hash.crc32(data, computed_crc) 276 | cur_pos = common.get_pos(f.handle) or_return 277 | 278 | size -= block_size 279 | } 280 | 281 | when DEBUG { 282 | printf(0, "CRC_32 Expected: %08x, Computed: %08x.\n", checksum, computed_crc) 283 | } 284 | 285 | if u64(computed_crc) != checksum { return .Invalid_CRC } 286 | 287 | // Restore original seek head. 288 | common.set_pos(f.handle, cur_pos) or_return 289 | 290 | // CRC32 matched. 291 | return 292 | } 293 | return .Invalid_CRC 294 | } 295 | 296 | MATROSKA_TO_INTERNAL :: 978_307_200_000_000_000 297 | 298 | nanoseconds_to_time :: proc(nanoseconds: i64) -> (res: time.Time) { 299 | return time.Time{ MATROSKA_TO_INTERNAL + nanoseconds } 300 | } 301 | 302 | is_printable :: #force_inline proc(ch: u8) -> (printable: bool, terminator: bool) { 303 | switch ch { 304 | case 0x20..=0x7E: // 0x20..0x7E: 305 | return true, false 306 | case 0x00: 307 | // Terminator 308 | return true, true 309 | case: 310 | // Unprintable 311 | return false, false 312 | } 313 | } 314 | 315 | _string :: proc(type: $T) -> (res: string) { 316 | when T == EBML_ID { 317 | id := runtime.typeid_base(typeid_of(EBML_ID)) 318 | type_info := type_info_of(id) 319 | 320 | buffer := PRINT_BUFFER[:] 321 | name: string 322 | 323 | #partial switch e in type_info.variant { 324 | case runtime.Type_Info_Enum: 325 | Enum_Value :: runtime.Type_Info_Enum_Value 326 | 327 | ev_, _ := reflect.as_i64(type) 328 | ev := Enum_Value(ev_) 329 | 330 | for val, idx in e.values { 331 | if val == ev { 332 | name = fmt.bprintf(buffer[:], "%v", e.names[idx]) 333 | for v, i in name { 334 | if v == '_' { 335 | buffer[i] = ' ' 336 | } 337 | } 338 | return name 339 | 340 | } 341 | } 342 | } 343 | 344 | // We could do `string(t[:4])`, but this also handles e.g. `©too`. 345 | temp := transmute([8]u8)type 346 | if common.is_printable(temp[:]) { 347 | return fmt.bprintf(buffer[:], "%c%c%c%c 0x%08x", temp[0], temp[1], temp[2], temp[3], i64(type)) 348 | } else { 349 | return fmt.bprintf(buffer[:], "0x%08x", i64(type)) 350 | } 351 | 352 | } else when T == time.Time { 353 | buffer := PRINT_BUFFER[:] 354 | return fmt.bprintf(buffer[:], "%v", type) 355 | 356 | } else { 357 | #panic("to_string: Unsupported type.") 358 | } 359 | } 360 | 361 | 362 | _find_element_by_type :: proc(element: ^EBML_Element, type: EBML_ID, elements: ^[dynamic]^EBML_Element) { 363 | element := element 364 | 365 | for element != nil { 366 | if element.first_child != nil { 367 | _find_element_by_type(element.first_child, type, elements) 368 | } 369 | 370 | if element.id == type { append(elements, element) } 371 | element = element.next 372 | } 373 | } 374 | 375 | find_element_by_type :: proc(f: ^EBML_File, type: EBML_ID, elements: ^[dynamic]^EBML_Element, document_index := 0) { 376 | if f == nil { return } 377 | if document_index + 1 < len(f.documents) { return } 378 | 379 | document := f.documents[document_index] 380 | _find_element_by_type(document.body, type, elements) 381 | } -------------------------------------------------------------------------------- /ebml/print.odin: -------------------------------------------------------------------------------- 1 | package ebml 2 | /* 3 | This is free and unencumbered software released into the public domain. 4 | 5 | Anyone is free to copy, modify, publish, use, compile, sell, or 6 | distribute this software, either in source code form or as a compiled 7 | binary, for any purpose, commercial or non-commercial, and by any 8 | means. 9 | 10 | In jurisdictions that recognize copyright laws, the author or authors 11 | of this software dedicate any and all copyright interest in the 12 | software to the public domain. We make this dedication for the benefit 13 | of the public at large and to the detriment of our heirs and 14 | successors. We intend this dedication to be an overt act of 15 | relinquishment in perpetuity of all present and future rights to this 16 | software under copyright law. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | 26 | For more information, please refer to 27 | 28 | 29 | A from-scratch implementation of the Extensible Binary Meta Language (EBML), 30 | as specified in [IETF RFC 8794](https://www.rfc-editor.org/rfc/rfc8794). 31 | 32 | The EBML format is the base format upon which Matroska (MKV) and WebM are based. 33 | 34 | This file contains print helpers. 35 | */ 36 | 37 | import "core:io" 38 | import "core:fmt" 39 | import "core:strings" 40 | import "../common" 41 | 42 | _string_common :: common._string 43 | 44 | @(thread_local) 45 | string_builder: strings.Builder 46 | 47 | @(thread_local) 48 | global_writer: io.Writer 49 | 50 | @(init) 51 | _init_string_builder :: proc() { 52 | strings.builder_init(&string_builder) 53 | global_writer = strings.to_writer(&string_builder) 54 | } 55 | 56 | _get_string :: proc() -> string { 57 | return strings.to_string(string_builder) 58 | } 59 | 60 | flush_builder_to_output :: proc() { 61 | fmt.println(_get_string()) 62 | strings.builder_reset(&string_builder) 63 | } 64 | 65 | printf :: proc(level: int, format: string, args: ..any) { 66 | indent(level) 67 | fmt.wprintf(global_writer, format, ..args) 68 | } 69 | 70 | println :: proc(level: int, args: ..any) { 71 | indent(level) 72 | fmt.wprintln(global_writer, ..args) 73 | } 74 | 75 | indent :: proc(level: int) { 76 | TABS := []u8{ 77 | '\t', '\t', '\t', '\t', '\t', 78 | '\t', '\t', '\t', '\t', '\t', 79 | '\t', '\t', '\t', '\t', '\t', 80 | '\t', '\t', '\t', '\t', '\t', 81 | } 82 | fmt.wprintf(global_writer, "%v", string(TABS[:level])) 83 | } 84 | 85 | print_element_header :: proc(element: ^EBML_Element, level := int(0)) { 86 | printf(level, "[%v] Pos: %d, Size: %d\n", _string(element.id), element.offset, element.payload_size) 87 | } 88 | 89 | print_element :: proc(f: ^EBML_File, element: ^EBML_Element, level := int(0), print_siblings := false, recurse := false) { 90 | element := element 91 | 92 | for element != nil { 93 | print_element_header(element, level) 94 | 95 | #partial switch element.type { 96 | case .Unsigned: 97 | if val, ok := element.payload.(u64); ok { 98 | printf(level + 1, "Value: %v\n", val) 99 | } 100 | 101 | case .Signed: 102 | if val, ok := element.payload.(i64); ok { 103 | printf(level + 1, "Value: %v\n", val) 104 | } 105 | 106 | case .Float: 107 | if val, ok := element.payload.(f64); ok { 108 | printf(level + 1, "Value: %.2f\n", val) 109 | } 110 | 111 | case .String: 112 | if val, ok := element.payload.(String); ok { 113 | printf(level + 1, "Value: %v\n", val) 114 | } 115 | 116 | case .UTF_8: 117 | if val, ok := element.payload.(string); ok { 118 | printf(level + 1, "Value: %v\n", val) 119 | } 120 | 121 | case .Binary: 122 | if val, ok := element.payload.([dynamic]u8); ok { 123 | #partial switch element.id { 124 | case .Matroska_SeekID: 125 | seek_id := u64be(0) 126 | for v in val { 127 | seek_id <<= 8 128 | seek_id += u64be(v) 129 | } 130 | printf(level + 1, "Value: %v\n", _string(EBML_ID(seek_id))) 131 | 132 | case: 133 | if len(val) > 20 { 134 | printf(level + 1, "Value: %X...\n", val[:20]) 135 | } else { 136 | printf(level + 1, "Value: %X\n", val) 137 | } 138 | } 139 | } 140 | 141 | case .Matroska_UUID: 142 | if uuid, ok := element.payload.(Matroska_UUID); ok { 143 | printf(level + 1, "UUID: %v\n", _string_common(uuid)) 144 | } 145 | 146 | case .Matroska_Time: 147 | if datetime, ok := element.payload.(Matroska_Time); ok { 148 | printf(level + 1, "Time: %v\n", datetime) 149 | } 150 | 151 | case .Matroska_Track_Type: 152 | if val, ok := element.payload.(Matroska_Track_Type); ok { 153 | printf(level + 1, "Value: %v\n", val) 154 | } 155 | 156 | } 157 | 158 | /* 159 | TODO: Make this some sort of queue so we don't blow the stack. 160 | */ 161 | if recurse && element.first_child != nil { 162 | print_element(f, element.first_child, level + 1, print_siblings, recurse) 163 | } 164 | 165 | if print_siblings { 166 | element = element.next 167 | } 168 | } 169 | } 170 | 171 | print :: proc(f: ^EBML_File, element: ^EBML_Element = nil, print_siblings := false, recurse := false) { 172 | if element != nil { 173 | print_element(f, element, 0, print_siblings, recurse) 174 | } else { 175 | for document, doc_idx in f.documents { 176 | println(0, "") 177 | printf(0, "Document: #%v\n", doc_idx) 178 | print_element(f, document.header, 0, true, true) 179 | flush_builder_to_output() 180 | 181 | print_element(f, document.body, 0, true, true) 182 | flush_builder_to_output() 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /tests/assets/bmff/test_metadata.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kelimion/odin-file-formats/27d484fe119e456ab5532afa09511b1cad278a27/tests/assets/bmff/test_metadata.mp4 -------------------------------------------------------------------------------- /tests/assets/ebml/README.md: -------------------------------------------------------------------------------- 1 | # Matroska Test Files 2 | 3 | These test files are a subsset of the [official Matroska test files](https://github.com/ietf-wg-cellar/matroska-test-files). 4 | 5 | # Multiple audio/subtitles 6 | 7 | This has a main audio track in english and a secondary audio track in 8 | english. It also has subtitles in English, French, German, Hungarian, 9 | Spanish, Italian and Japanese. The player should provide the possibility 10 | to switch between these streams. 11 | 12 | The sample contains H264 (1024x576 pixels), and stereo AAC and 13 | commentary in AAC+ (using SBR). The source material is taken from the 14 | [Elephant Dreams](http://orange.blender.org/download) video project 15 | 16 | # Junk elements & damaged 17 | 18 | This file contains junk elements (elements not defined in the specs) 19 | either at the beggining or the end of Clusters. These elements should be 20 | skipped. There is also an invalid element at 451417 that should be 21 | skipped until the next valid Cluster is found. 22 | 23 | The sample contains H264 (1024x576 pixels), and stereo AAC. The source 24 | material is taken from the [Elephant 25 | Dreams](http://orange.blender.org/download) video project -------------------------------------------------------------------------------- /tests/assets/ebml/damaged.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kelimion/odin-file-formats/27d484fe119e456ab5532afa09511b1cad278a27/tests/assets/ebml/damaged.mkv -------------------------------------------------------------------------------- /tests/assets/ebml/subtitles.mkv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kelimion/odin-file-formats/27d484fe119e456ab5532afa09511b1cad278a27/tests/assets/ebml/subtitles.mkv -------------------------------------------------------------------------------- /tests/bmff/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | odin test . -show-timings -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -------------------------------------------------------------------------------- /tests/bmff/test_iso_bmff.odin: -------------------------------------------------------------------------------- 1 | /* 2 | This is free and unencumbered software released into the public domain. 3 | 4 | Anyone is free to copy, modify, publish, use, compile, sell, or 5 | distribute this software, either in source code form or as a compiled 6 | binary, for any purpose, commercial or non-commercial, and by any 7 | means. 8 | 9 | In jurisdictions that recognize copyright laws, the author or authors 10 | of this software dedicate any and all copyright interest in the 11 | software to the public domain. We make this dedication for the benefit 12 | of the public at large and to the detriment of our heirs and 13 | successors. We intend this dedication to be an overt act of 14 | relinquishment in perpetuity of all present and future rights to this 15 | software under copyright law. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | For more information, please refer to 26 | 27 | 28 | A test suite for the ISO Base Media File Format package 29 | */ 30 | package test_bmff 31 | import "core:testing" 32 | 33 | import "../../bmff" 34 | 35 | ISOM_Test :: string 36 | 37 | ISOM_Tests :: []ISOM_Test{ 38 | "../assets/bmff/test_metadata.mp4", 39 | } 40 | 41 | @test 42 | isom_test :: proc(t: ^testing.T) { 43 | for test in ISOM_Tests { 44 | err := isom_test_file(t, test) 45 | testing.expectf(t, err == nil, "isom_test_file(%v) returned %v", test, err) 46 | } 47 | } 48 | 49 | isom_test_file :: proc(t: ^testing.T, test: ISOM_Test) -> (err: bmff.Error) { 50 | f := bmff.open(test) or_return 51 | defer bmff.close(f) 52 | 53 | bmff.parse(f) or_return 54 | 55 | // TODO(Jeroen): Write node type helpers and helpers to find a given node by path. 56 | return 57 | } -------------------------------------------------------------------------------- /tests/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | echo --- 3 | echo Running ISO Base Media File Format tests 4 | echo --- 5 | pushd bmff 6 | call build.bat 7 | popd 8 | echo --- 9 | echo Running EBML (Matroska) Format tests 10 | echo --- 11 | pushd ebml 12 | call build.bat 13 | popd -------------------------------------------------------------------------------- /tests/ebml/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | odin test . -show-timings -vet -vet-tabs -strict-style -vet-style -warnings-as-errors -disallow-do -------------------------------------------------------------------------------- /tests/ebml/test_ebml.odin: -------------------------------------------------------------------------------- 1 | /* 2 | This is free and unencumbered software released into the public domain. 3 | 4 | Anyone is free to copy, modify, publish, use, compile, sell, or 5 | distribute this software, either in source code form or as a compiled 6 | binary, for any purpose, commercial or non-commercial, and by any 7 | means. 8 | 9 | In jurisdictions that recognize copyright laws, the author or authors 10 | of this software dedicate any and all copyright interest in the 11 | software to the public domain. We make this dedication for the benefit 12 | of the public at large and to the detriment of our heirs and 13 | successors. We intend this dedication to be an overt act of 14 | relinquishment in perpetuity of all present and future rights to this 15 | software under copyright law. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 21 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 23 | OTHER DEALINGS IN THE SOFTWARE. 24 | 25 | For more information, please refer to 26 | 27 | 28 | A test suite for the EBML/Matroska/WebM package 29 | */ 30 | package test_ebml 31 | 32 | import "core:testing" 33 | import "../../ebml" 34 | 35 | EBML_Test :: struct { 36 | filename: string, 37 | subtitles: []string, 38 | 39 | } 40 | 41 | EBML_Tests :: []EBML_Test{ 42 | { 43 | filename = "../assets/ebml/subtitles.mkv", 44 | subtitles = { "hun", "ger", "fre", "spa", "ita", "jpn"}, 45 | }, 46 | { 47 | filename = "../assets/ebml/damaged.mkv", 48 | }, 49 | } 50 | 51 | @test 52 | ebml_test :: proc(t: ^testing.T) { 53 | for test in EBML_Tests { 54 | err := ebml_test_file(t, test) 55 | testing.expectf(t, err == nil, "ebml_test_file(%v) returned %v", test.filename, err) 56 | } 57 | } 58 | 59 | ebml_test_file :: proc(t: ^testing.T, test: EBML_Test) -> (err: ebml.Error) { 60 | f := ebml.open(test.filename) or_return 61 | defer ebml.close(f) 62 | 63 | /* 64 | We opened the file, let's parse it. 65 | */ 66 | ebml.parse(f) or_return 67 | 68 | /* 69 | TODO(Jeroen): Write node type helpers and helpers to find a given node by path, 70 | and then test specific fields. 71 | */ 72 | 73 | elements: [dynamic]^ebml.EBML_Element 74 | ebml.find_element_by_type(f, .Matroska_CodecID, &elements) 75 | defer delete(elements) 76 | 77 | for lang in test.subtitles { 78 | subtitle_found := false 79 | 80 | for element in elements { 81 | codec_type, codec_type_ok := element.payload.(ebml.String) 82 | testing.expect(t, codec_type_ok, "Unexpected payload type.") 83 | 84 | if codec_type == "S_TEXT/UTF8" { 85 | /* 86 | Next element should be the language tag (nil for default language) 87 | */ 88 | if element.next != nil && element.next.id == .Matroska_Language { 89 | language, language_ok := element.next.payload.(string) 90 | testing.expect(t, language_ok, "Unexpected payload type.") 91 | 92 | if language == lang { 93 | subtitle_found = true 94 | } 95 | } 96 | } 97 | } 98 | testing.expectf(t, subtitle_found, "Subtitle %v not found.", lang) 99 | } 100 | return 101 | } --------------------------------------------------------------------------------