├── .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 | }
--------------------------------------------------------------------------------