Skip to content

Instantly share code, notes, and snippets.

@karanlyons
Created July 9, 2017 10:34
Show Gist options
  • Save karanlyons/a196433c6cf6ba4a036af77c47d980e2 to your computer and use it in GitHub Desktop.
Save karanlyons/a196433c6cf6ba4a036af77c47d980e2 to your computer and use it in GitHub Desktop.
Javascript QTFF/MP4 Parser
<html>
<body>
<script>
'use strict';
ArrayBuffer.prototype.toSource = function(hex) {
var buf = this,
bytes = Array.prototype.slice.call(new Uint8Array(buf)),
i = buf.byteLength,
blank = true;
while (i--) {
if (blank && bytes[i]) {
blank = false;
}
if (hex) {
bytes[i] = ("00" + bytes[i].toString(16)).slice(-2);
}
}
if (blank) {
return "ArrayBuffer(" + buf.byteLength + ")";
} else {
return "Uint8Array([" + bytes.join(" ") + "])";
}
};
// Constants
var QT_EPOCH = new Date('January 1 1904 00:00:00 UTC');
var ATOM_NAMES = {
'ftyp': 'File Type',
'moov': 'Movie',
'mvhd': 'Movie Header',
'iods': 'Inital Object Descriptor',
'trak': 'Track',
'tkhd': 'Track Header',
'mdia': 'Media',
'mdhd': 'Media Header',
'hdlr': 'Handler Description',
'minf': 'Media Information',
'vmhd': 'Video Media Header',
'dinf': 'Data Information',
'dref': 'Data Reference',
'stbl': 'Sample Table',
'stsd': 'Sample Descriptions',
'stts': 'Sample to Time',
'stsz': 'Sample Sizes',
'stsc': 'Sample to Chunk',
'stco': 'Chunk Offset Table',
'ctts': 'Composition Offsets',
'stss': 'Sync Samples',
'sdtp': 'Independent/Disposable Samples',
'edts': 'Edits',
'elst': 'Edit List',
'udta': 'User Data',
'meta': 'Metadata',
'mdat': 'Media Data',
};
var USER_TAG_DESCRIPTIONS = {
'AllF': "Play all frames—byte indicating that all frames of video should be played, regardless of timing",
'hinf': "Hint track information—statistical data for real-time streaming of a particular track.",
'hnti': "Hint info atom—data used for real-time streaming of a movie or a track.",
'LOOP': "Long integer indicating looping style. This atom is not present unless the movie is set to loop. Values are 0 for normal looping, 1 for palindromic looping.",
'name': "Name of object",
'ptv ': "Print to video—display movie in full screen mode. This atom contains a 16-byte structure, described in Print to Video (Full Screen Mode).",
'SelO': "Play selection only—byte indicating that only the selected area of the movie should be played",
'tagc': "Media characteristic optionally present in Track user data—specialized text that describes something of interest about the track.",
'tnam': "Localized track name optionally present in Track user data. The payload is described in Track Name.",
'WLOC': "Default window location for movie—two 16-bit values, {x,y}",
'©arg': "Name of arranger",
'©ark': "Keywords for arranger",
'©cok': "Keywords for composer",
'©com': "Name of composer",
'©cpy': "Copyright statement",
'©day': "Date the movie content was created",
'©dir': "Name of movie's director",
'©ed1': "Edit dates and descriptions",
'©ed2': "Edit dates and descriptions",
'©ed3': "Edit dates and descriptions",
'©ed4': "Edit dates and descriptions",
'©ed5': "Edit dates and descriptions",
'©ed6': "Edit dates and descriptions",
'©ed7': "Edit dates and descriptions",
'©ed8': "Edit dates and descriptions",
'©ed9': "Edit dates and descriptions",
'©fmt': "Indication of movie format (computer-generated, digitized, and so on)",
'©inf': "Information about the movie",
'©isr': "ISRC code",
'©lab': "Name of record label",
'©lal': "URL of record label",
'©mak': "Name of file creator or maker",
'©mal': "URL of file creator or maker",
'©nak': "Title keywords of the content",
'©nam': "Title of the content",
'©pdk': "Keywords for producer",
'©phg': "Recording copyright statement, normally preceded by the symbol (P)",
'©prd': "Name of producer",
'©prf': "Names of performers",
'©prk': "Keywords of main artist and performer",
'©prl': "URL of main artist and performer",
'©req': "Special hardware and software requirements",
'©snk': "Subtitle keywords of the content",
'©snm': "Subtitle of content",
'©src': "Credits for those who provided movie source content",
'©swf': "Name of songwriter",
'©swk': "Keywords for songwriter",
'©swr': "Name and version number of the software (or hardware) that generated this movie",
'©wrt': "Name of movie's writer",
};
var MACINTOSH_LANGUAGE_CODES = {
0: 'English',
1: 'French',
2: 'German',
3: 'Italian',
4: 'Dutch',
5: 'Swedish',
6: 'Spanish',
7: 'Danish',
8: 'Portuguese',
9: 'Norwegian',
10: 'Hebrew',
11: 'Japanese',
12: 'Arabic',
13: 'Finnish',
14: 'Greek',
15: 'Icelandic',
16: 'Maltese',
17: 'Turkish',
18: 'Croatian',
19: 'Traditional Chinese',
20: 'Urdu',
21: 'Hindi',
22: 'Thai',
23: 'Korean',
24: 'Lithuanian',
25: 'Polish',
26: 'Hungarian',
27: 'Estonian',
28: 'Latvian',
28: 'Lettish',
29: 'Saami',
29: 'Sami',
30: 'Faroese',
31: 'Farsi',
32: 'Russian',
33: 'Simplified Chinese',
34: 'Flemish',
35: 'Irish',
36: 'Albanian',
37: 'Romanian',
38: 'Czech',
39: 'Slovak',
40: 'Slovenian',
41: 'Yiddish',
42: 'Serbian',
43: 'Macedonian',
44: 'Bulgarian',
45: 'Ukrainian',
46: 'Belarusian',
47: 'Uzbek',
48: 'Kazakh',
49: 'Azerbaijani',
50: 'AzerbaijanAr',
51: 'Armenian',
52: 'Georgian',
53: 'Moldavian',
54: 'Kirghiz',
55: 'Tajiki',
56: 'Turkmen',
57: 'Mongolian',
58: 'MongolianCyr',
59: 'Pashto',
60: 'Kurdish',
61: 'Kashmiri',
62: 'Sindhi',
63: 'Tibetan',
64: 'Nepali',
65: 'Sanskrit',
66: 'Marathi',
67: 'Bengali',
68: 'Assamese',
69: 'Gujarati',
70: 'Punjabi',
71: 'Oriya',
72: 'Malayalam',
73: 'Kannada',
74: 'Tamil',
75: 'Telugu',
76: 'Sinhala',
77: 'Burmese',
78: 'Khmer',
79: 'Lao',
80: 'Vietnamese',
81: 'Indonesian',
82: 'Tagalog',
83: 'MalayRoman',
84: 'MalayArabic',
85: 'Amharic',
87: 'Galla',
87: 'Oromo',
88: 'Somali',
89: 'Swahili',
90: 'Kinyarwanda',
91: 'Rundi',
92: 'Nyanja',
93: 'Malagasy',
94: 'Esperanto',
128: 'Welsh',
129: 'Basque',
130: 'Catalan',
131: 'Latin',
132: 'Quechua',
133: 'Guarani',
134: 'Aymara',
135: 'Tatar',
136: 'Uighur',
137: 'Dzongkha',
138: 'JavaneseRom',
32767: 'Unspecified',
};
var MACINTOSH_CODE_POINTS = {
128: 'Ä',
129: 'Å',
130: 'Ç',
131: 'É',
132: 'Ñ',
133: 'Ö',
134: 'Ü',
135: 'á',
136: 'à',
137: 'â',
138: 'ä',
139: 'ã',
140: 'å',
141: 'ç',
142: 'é',
143: 'è',
144: 'ê',
145: 'ë',
146: 'í',
147: 'ì',
148: 'î',
149: 'ï',
150: 'ñ',
151: 'ó',
152: 'ò',
153: 'ô',
154: 'ö',
155: 'õ',
156: 'ú',
157: 'ù',
158: 'û',
159: 'ü',
160: '†',
161: '°',
162: '¢',
163: '£',
164: '§',
165: '•',
166: '¶',
167: 'ß',
168: '®',
169: '©',
170: '™',
171: '´',
172: '¨',
173: '≠',
174: 'Æ',
175: 'Ø',
176: '∞',
177: '±',
178: '≤',
179: '≥',
180: '¥',
181: 'µ',
182: '∂',
183: '∑',
184: '∏',
185: 'π',
186: '∫',
187: 'ª',
188: 'º',
189: 'Ω',
190: 'æ',
191: 'ø',
192: '¿',
193: '¡',
194: '¬',
195: '√',
196: 'ƒ',
197: '≈',
198: '∆',
199: '«',
200: '»',
201: '…',
202: ' ',
203: 'À',
204: 'Ã',
205: 'Õ',
206: 'Œ',
207: 'œ',
208: '–',
209: '—',
210: '“',
211: '”',
212: '‘',
213: '’',
214: '÷',
215: '◊',
216: 'ÿ',
217: 'Ÿ',
218: '⁄',
219: '€',
220: '‹',
221: '›',
222: 'fi',
223: 'fl',
224: '‡',
225: '·',
226: '‚',
227: '„',
228: '‰',
229: 'Â',
230: 'Ê',
231: 'Á',
232: 'Ë',
233: 'È',
234: 'Í',
235: 'Î',
236: 'Ï',
237: 'Ì',
238: 'Ó',
239: 'Ô',
240: '',
241: 'Ò',
242: 'Ú',
243: 'Û',
244: 'Ù',
245: 'ı',
246: 'ˆ',
247: '˜',
248: '¯',
249: '˘',
250: '˙',
251: '˚',
252: '¸',
253: '˝',
254: '˛',
255: 'ˇ',
};
var ISO_LANGUAGE_CODES = {
'aar': "Afar",
'abk': "Abkhazian",
'ace': "Achinese",
'ach': "Acoli",
'ada': "Adangme",
'ady': "Adyghe; Adygei",
'afa': "Afro-Asiatic languages",
'afh': "Afrihili",
'afr': "Afrikaans",
'ain': "Ainu",
'aka': "Akan",
'akk': "Akkadian",
'ale': "Aleut",
'alg': "Algonquian languages",
'alt': "Southern Altai",
'amh': "Amharic",
'ang': "English, Old (ca.450-1100)",
'anp': "Angika",
'apa': "Apache languages",
'ara': "Arabic",
'arc': "Official Aramaic (700-300 BCE); Imperial Aramaic (700-300 BCE)",
'arg': "Aragonese",
'arn': "Mapudungun; Mapuche",
'arp': "Arapaho",
'art': "Artificial languages",
'arw': "Arawak",
'asm': "Assamese",
'ast': "Asturian; Bable; Leonese; Asturleonese",
'ath': "Athapascan languages",
'aus': "Australian languages",
'ava': "Avaric",
'ave': "Avestan",
'awa': "Awadhi",
'aym': "Aymara",
'aze': "Azerbaijani",
'bad': "Banda languages",
'bai': "Bamileke languages",
'bak': "Bashkir",
'bal': "Baluchi",
'bam': "Bambara",
'ban': "Balinese",
'bas': "Basa",
'bat': "Baltic languages",
'bej': "Beja; Bedawiyet",
'bel': "Belarusian",
'bem': "Bemba",
'ben': "Bengali",
'ber': "Berber languages",
'bho': "Bhojpuri",
'bih': "Bihari languages",
'bik': "Bikol",
'bin': "Bini; Edo",
'bis': "Bislama",
'bla': "Siksika",
'bnt': "Bantu languages",
'bod': "Tibetan",
'bod': "Tibetan",
'bos': "Bosnian",
'bra': "Braj",
'bre': "Breton",
'btk': "Batak languages",
'bua': "Buriat",
'bug': "Buginese",
'bul': "Bulgarian",
'byn': "Blin; Bilin",
'cad': "Caddo",
'cai': "Central American Indian languages",
'car': "Galibi Carib",
'cat': "Catalan; Valencian",
'cau': "Caucasian languages",
'ceb': "Cebuano",
'cel': "Celtic languages",
'ces': "Czech",
'ces': "Czech",
'cha': "Chamorro",
'chb': "Chibcha",
'che': "Chechen",
'chg': "Chagatai",
'chk': "Chuukese",
'chm': "Mari",
'chn': "Chinook jargon",
'cho': "Choctaw",
'chp': "Chipewyan; Dene Suline",
'chr': "Cherokee",
'chu': "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic",
'chv': "Chuvash",
'chy': "Cheyenne",
'cmc': "Chamic languages",
'cop': "Coptic",
'cor': "Cornish",
'cos': "Corsican",
'cpe': "Creoles and pidgins, English based",
'cpf': "Creoles and pidgins, French-based",
'cpp': "Creoles and pidgins, Portuguese-based",
'cre': "Cree",
'crh': "Crimean Tatar; Crimean Turkish",
'crp': "Creoles and pidgins",
'csb': "Kashubian",
'cus': "Cushitic languages",
'cym': "Welsh",
'cym': "Welsh",
'dak': "Dakota",
'dan': "Danish",
'dar': "Dargwa",
'day': "Land Dayak languages",
'del': "Delaware",
'den': "Slave (Athapascan)",
'deu': "German",
'deu': "German",
'dgr': "Dogrib",
'din': "Dinka",
'div': "Divehi; Dhivehi; Maldivian",
'doi': "Dogri",
'dra': "Dravidian languages",
'dsb': "Lower Sorbian",
'dua': "Duala",
'dum': "Dutch, Middle (ca.1050-1350)",
'dyu': "Dyula",
'dzo': "Dzongkha",
'efi': "Efik",
'egy': "Egyptian (Ancient)",
'eka': "Ekajuk",
'ell': "Greek, Modern (1453-)",
'ell': "Greek, Modern (1453-)",
'elx': "Elamite",
'eng': "English",
'enm': "English, Middle (1100-1500)",
'epo': "Esperanto",
'est': "Estonian",
'eus': "Basque",
'eus': "Basque",
'ewe': "Ewe",
'ewo': "Ewondo",
'fan': "Fang",
'fao': "Faroese",
'fas': "Persian",
'fas': "Persian",
'fat': "Fanti",
'fij': "Fijian",
'fil': "Filipino; Pilipino",
'fin': "Finnish",
'fiu': "Finno-Ugrian languages",
'fon': "Fon",
'fra': "French",
'fra': "French",
'frm': "French, Middle (ca.1400-1600)",
'fro': "French, Old (842-ca.1400)",
'frr': "Northern Frisian",
'frs': "Eastern Frisian",
'fry': "Western Frisian",
'ful': "Fulah",
'fur': "Friulian",
'gaa': "Ga",
'gay': "Gayo",
'gba': "Gbaya",
'gem': "Germanic languages",
'gez': "Geez",
'gil': "Gilbertese",
'gla': "Gaelic; Scottish Gaelic",
'gle': "Irish",
'glg': "Galician",
'glv': "Manx",
'gmh': "German, Middle High (ca.1050-1500)",
'goh': "German, Old High (ca.750-1050)",
'gon': "Gondi",
'gor': "Gorontalo",
'got': "Gothic",
'grb': "Grebo",
'grc': "Greek, Ancient (to 1453)",
'grn': "Guarani",
'gsw': "Swiss German; Alemannic; Alsatian",
'guj': "Gujarati",
'gwi': "Gwich'in",
'hai': "Haida",
'hat': "Haitian; Haitian Creole",
'hau': "Hausa",
'haw': "Hawaiian",
'heb': "Hebrew",
'her': "Herero",
'hil': "Hiligaynon",
'him': "Himachali languages; Western Pahari languages",
'hin': "Hindi",
'hit': "Hittite",
'hmn': "Hmong; Mong",
'hmo': "Hiri Motu",
'hrv': "Croatian",
'hsb': "Upper Sorbian",
'hun': "Hungarian",
'hup': "Hupa",
'hye': "Armenian",
'hye': "Armenian",
'iba': "Iban",
'ibo': "Igbo",
'ido': "Ido",
'iii': "Sichuan Yi; Nuosu",
'ijo': "Ijo languages",
'iku': "Inuktitut",
'ile': "Interlingue; Occidental",
'ilo': "Iloko",
'ina': "Interlingua (International Auxiliary Language Association)",
'inc': "Indic languages",
'ind': "Indonesian",
'ine': "Indo-European languages",
'inh': "Ingush",
'ipk': "Inupiaq",
'ira': "Iranian languages",
'iro': "Iroquoian languages",
'isl': "Icelandic",
'isl': "Icelandic",
'ita': "Italian",
'jav': "Javanese",
'jbo': "Lojban",
'jpn': "Japanese",
'jpr': "Judeo-Persian",
'jrb': "Judeo-Arabic",
'kaa': "Kara-Kalpak",
'kab': "Kabyle",
'kac': "Kachin; Jingpho",
'kal': "Kalaallisut; Greenlandic",
'kam': "Kamba",
'kan': "Kannada",
'kar': "Karen languages",
'kas': "Kashmiri",
'kat': "Georgian",
'kat': "Georgian",
'kau': "Kanuri",
'kaw': "Kawi",
'kaz': "Kazakh",
'kbd': "Kabardian",
'kha': "Khasi",
'khi': "Khoisan languages",
'khm': "Central Khmer",
'kho': "Khotanese; Sakan",
'kik': "Kikuyu; Gikuyu",
'kin': "Kinyarwanda",
'kir': "Kirghiz; Kyrgyz",
'kmb': "Kimbundu",
'kok': "Konkani",
'kom': "Komi",
'kon': "Kongo",
'kor': "Korean",
'kos': "Kosraean",
'kpe': "Kpelle",
'krc': "Karachay-Balkar",
'krl': "Karelian",
'kro': "Kru languages",
'kru': "Kurukh",
'kua': "Kuanyama; Kwanyama",
'kum': "Kumyk",
'kur': "Kurdish",
'kut': "Kutenai",
'lad': "Ladino",
'lah': "Lahnda",
'lam': "Lamba",
'lao': "Lao",
'lat': "Latin",
'lav': "Latvian",
'lez': "Lezghian",
'lim': "Limburgan; Limburger; Limburgish",
'lin': "Lingala",
'lit': "Lithuanian",
'lol': "Mongo",
'loz': "Lozi",
'ltz': "Luxembourgish; Letzeburgesch",
'lua': "Luba-Lulua",
'lub': "Luba-Katanga",
'lug': "Ganda",
'lui': "Luiseno",
'lun': "Lunda",
'luo': "Luo (Kenya and Tanzania)",
'lus': "Lushai",
'mad': "Madurese",
'mag': "Magahi",
'mah': "Marshallese",
'mai': "Maithili",
'mak': "Makasar",
'mal': "Malayalam",
'man': "Mandingo",
'map': "Austronesian languages",
'mar': "Marathi",
'mas': "Masai",
'mdf': "Moksha",
'mdr': "Mandar",
'men': "Mende",
'mga': "Irish, Middle (900-1200)",
'mic': "Mi'kmaq; Micmac",
'min': "Minangkabau",
'mis': "Uncoded languages",
'mkd': "Macedonian",
'mkd': "Macedonian",
'mkh': "Mon-Khmer languages",
'mlg': "Malagasy",
'mlt': "Maltese",
'mnc': "Manchu",
'mni': "Manipuri",
'mno': "Manobo languages",
'moh': "Mohawk",
'mon': "Mongolian",
'mos': "Mossi",
'mri': "Maori",
'mri': "Maori",
'msa': "Malay",
'msa': "Malay",
'mul': "Multiple languages",
'mun': "Munda languages",
'mus': "Creek",
'mwl': "Mirandese",
'mwr': "Marwari",
'mya': "Burmese",
'mya': "Burmese",
'myn': "Mayan languages",
'myv': "Erzya",
'nah': "Nahuatl languages",
'nai': "North American Indian languages",
'nap': "Neapolitan",
'nau': "Nauru",
'nav': "Navajo; Navaho",
'nbl': "Ndebele, South; South Ndebele",
'nde': "Ndebele, North; North Ndebele",
'ndo': "Ndonga",
'nds': "Low German; Low Saxon; German, Low; Saxon, Low",
'nep': "Nepali",
'new': "Nepal Bhasa; Newari",
'nia': "Nias",
'nic': "Niger-Kordofanian languages",
'niu': "Niuean",
'nld': "Dutch; Flemish",
'nld': "Dutch; Flemish",
'nno': "Norwegian Nynorsk; Nynorsk, Norwegian",
'nob': "Bokmål, Norwegian; Norwegian Bokmål",
'nog': "Nogai",
'non': "Norse, Old",
'nor': "Norwegian",
'nqo': "N'Ko",
'nso': "Pedi; Sepedi; Northern Sotho",
'nub': "Nubian languages",
'nwc': "Classical Newari; Old Newari; Classical Nepal Bhasa",
'nya': "Chichewa; Chewa; Nyanja",
'nym': "Nyamwezi",
'nyn': "Nyankole",
'nyo': "Nyoro",
'nzi': "Nzima",
'oci': "Occitan (post 1500)",
'oji': "Ojibwa",
'ori': "Oriya",
'orm': "Oromo",
'osa': "Osage",
'oss': "Ossetian; Ossetic",
'ota': "Turkish, Ottoman (1500-1928)",
'oto': "Otomian languages",
'paa': "Papuan languages",
'pag': "Pangasinan",
'pal': "Pahlavi",
'pam': "Pampanga; Kapampangan",
'pan': "Panjabi; Punjabi",
'pap': "Papiamento",
'pau': "Palauan",
'peo': "Persian, Old (ca.600-400 B.C.)",
'phi': "Philippine languages",
'phn': "Phoenician",
'pli': "Pali",
'pol': "Polish",
'pon': "Pohnpeian",
'por': "Portuguese",
'pra': "Prakrit languages",
'pro': "Provençal, Old (to 1500);Occitan, Old (to 1500)",
'pus': "Pushto; Pashto",
'que': "Quechua",
'raj': "Rajasthani",
'rap': "Rapanui",
'rar': "Rarotongan; Cook Islands Maori",
'roa': "Romance languages",
'roh': "Romansh",
'rom': "Romany",
'ron': "Romanian; Moldavian; Moldovan",
'ron': "Romanian; Moldavian; Moldovan",
'run': "Rundi",
'rup': "Aromanian; Arumanian; Macedo-Romanian",
'rus': "Russian",
'sad': "Sandawe",
'sag': "Sango",
'sah': "Yakut",
'sai': "South American Indian languages",
'sal': "Salishan languages",
'sam': "Samaritan Aramaic",
'san': "Sanskrit",
'sas': "Sasak",
'sat': "Santali",
'scn': "Sicilian",
'sco': "Scots",
'sel': "Selkup",
'sem': "Semitic languages",
'sga': "Irish, Old (to 900)",
'sgn': "Sign Languages",
'shn': "Shan",
'sid': "Sidamo",
'sin': "Sinhala; Sinhalese",
'sio': "Siouan languages",
'sit': "Sino-Tibetan languages",
'sla': "Slavic languages",
'slk': "Slovak",
'slk': "Slovak",
'slv': "Slovenian",
'sma': "Southern Sami",
'sme': "Northern Sami",
'smi': "Sami languages",
'smj': "Lule Sami",
'smn': "Inari Sami",
'smo': "Samoan",
'sms': "Skolt Sami",
'sna': "Shona",
'snd': "Sindhi",
'snk': "Soninke",
'sog': "Sogdian",
'som': "Somali",
'son': "Songhai languages",
'sot': "Sotho, Southern",
'spa': "Spanish; Castilian",
'sqi': "Albanian",
'sqi': "Albanian",
'srd': "Sardinian",
'srn': "Sranan Tongo",
'srp': "Serbian",
'srr': "Serer",
'ssa': "Nilo-Saharan languages",
'ssw': "Swati",
'suk': "Sukuma",
'sun': "Sundanese",
'sus': "Susu",
'sux': "Sumerian",
'swa': "Swahili",
'swe': "Swedish",
'syc': "Classical Syriac",
'syr': "Syriac",
'tah': "Tahitian",
'tai': "Tai languages",
'tam': "Tamil",
'tat': "Tatar",
'tel': "Telugu",
'tem': "Timne",
'ter': "Tereno",
'tet': "Tetum",
'tgk': "Tajik",
'tgl': "Tagalog",
'tha': "Thai",
'tig': "Tigre",
'tir': "Tigrinya",
'tiv': "Tiv",
'tkl': "Tokelau",
'tlh': "Klingon; tlhIngan-Hol",
'tli': "Tlingit",
'tmh': "Tamashek",
'tog': "Tonga (Nyasa)",
'ton': "Tonga (Tonga Islands)",
'tpi': "Tok Pisin",
'tsi': "Tsimshian",
'tsn': "Tswana",
'tso': "Tsonga",
'tuk': "Turkmen",
'tum': "Tumbuka",
'tup': "Tupi languages",
'tur': "Turkish",
'tut': "Altaic languages",
'tvl': "Tuvalu",
'twi': "Twi",
'tyv': "Tuvinian",
'udm': "Udmurt",
'uga': "Ugaritic",
'uig': "Uighur; Uyghur",
'ukr': "Ukrainian",
'umb': "Umbundu",
'und': "Undetermined",
'urd': "Urdu",
'uzb': "Uzbek",
'vai': "Vai",
'ven': "Venda",
'vie': "Vietnamese",
'vol': "Volapük",
'vot': "Votic",
'wak': "Wakashan languages",
'wal': "Wolaitta; Wolaytta",
'war': "Waray",
'was': "Washo",
'wen': "Sorbian languages",
'wln': "Walloon",
'wol': "Wolof",
'xal': "Kalmyk; Oirat",
'xho': "Xhosa",
'yao': "Yao",
'yap': "Yapese",
'yid': "Yiddish",
'yor': "Yoruba",
'ypk': "Yupik languages",
'zap': "Zapotec",
'zbl': "Blissymbols; Blissymbolics; Bliss",
'zen': "Zenaga",
'zgh': "Standard Moroccan Tamazight",
'zha': "Zhuang; Chuang",
'zho': "Chinese",
'zho': "Chinese",
'znd': "Zande languages",
'zul': "Zulu",
'zun': "Zuni",
'zxx': "No linguistic content; Not applicable",
'zza': "Zaza; Dimili; Dimli; Kirdki; Kirmanjki; Zazaki",
};
var PACKED_TO_ISO_CODE = {}
for (var code in ISO_LANGUAGE_CODES) {
PACKED_TO_ISO_CODE[(
((code.charCodeAt(0) - 0x60) * 0x400) +
((code.charCodeAt(1) - 0x60) * 0x20) +
((code.charCodeAt(2) - 0x60))
)] = code;
}
// Utility
var sliceDataView = function (view, offset, length) {
length = length || (view.byteLength - offset);
if ((offset + length) > view.byteLength) {
throw 'Length is out of view bounds.';
} else if (length === 0) {
return undefined;
} else {
return new DataView(view.buffer, view.byteOffset + offset, length);
}
}
var bytesToString = function (view, offset, length) {
var string = '',
i;
offset = offset || 0;
length = length || (view.byteLength - offset);
for (i=0; i < length; i++) {
string += String.fromCharCode(view.getUint8(offset + i));
}
return string;
}
// Parsers
var parseMatrix = function (view) {
return [
[view.getInt32(0) / Math.pow(2, 16), view.getInt32(4) / Math.pow(2, 16), view.getInt32(8) / Math.pow(2, 30)],
[view.getInt32(12) / Math.pow(2, 16), view.getInt32(16) / Math.pow(2, 16), view.getInt32(20) / Math.pow(2, 30)],
[view.getInt32(24) / Math.pow(2, 16), view.getInt32(28) / Math.pow(2, 16), view.getInt32(32) / Math.pow(2, 30)],
]
}
var parseColors = function (view) {
var i,
colors = [];
for (i = 0; i < view.byteLength; i += 8) {
colors.push([
view.getUint16(i),
view.getUint16(i + 2),
view.getUint16(i + 4),
view.getUint16(i + 6),
]);
}
return colors;
}
var parseRaw = function (atom) {
atom.raw_data = new DataView(atom.stream.buffer, atom.stream.byteOffset, atom.stream.byteLength);
return atom;
}
var parseChildren = function (atom) {
var last_child;
atom.children = parseQuickTime(atom.stream, undefined, undefined, undefined, atom.level + 1).parsed_atoms;
last_child = atom.children.length? atom.children[atom.children.length - 1] : undefined;
if (last_child) {
atom.stream = sliceDataView(atom.stream, last_child.offset + last_child.length);
} else {
atom.children = undefined;
}
return atom;
}
var parseAtomContainer = function (atom) {
atom.reserved = sliceDataView(atom.stream, 8, 18);
atom.lock_count = atom.stream.getUint16(18);
atom.stream = sliceDataView(atom.stream, 20);
return atom;
}
var parseFTYP = function (atom) {
var i;
atom.major_brand = bytesToString(atom.stream, 0, 4);
atom.minor_version = atom.stream.getUint32(4);
atom.compatible_brands = [];
for (i=8; i<atom.stream.byteLength; i+=4) {
atom.compatible_brands.push(bytesToString(atom.stream, i, 4));
}
if (i == atom.stream.byteLength) {
atom.stream = undefined;
} else {
atom.stream = sliceDataView(atom.stream, i);
}
return atom;
}
var parseMVHD = function (atom) {
atom.version = atom.stream.getInt8(0);
atom.flags = sliceDataView(atom.stream, 1, 3);
atom.creation_time = new Date(QT_EPOCH); atom.creation_time.setSeconds(atom.stream.getUint32(4));
atom.modification_time = new Date(QT_EPOCH); atom.modification_time.setSeconds(atom.stream.getUint32(8));
atom.time_scale = atom.stream.getUint32(12);
atom.duration = atom.stream.getUint32(16);
atom.preferred_rate = atom.stream.getUint32(20) / Math.pow(2, 16);
atom.preferred_volume = atom.stream.getUint16(24) / Math.pow(2, 8);
atom.reserved = sliceDataView(atom.stream, 26, 10);
atom.matrix_structure = parseMatrix(sliceDataView(atom.stream, 36, 36));
atom.preview_time = atom.stream.getUint32(72);
atom.preview_duration = atom.stream.getUint32(76);
atom.poster_time = atom.stream.getUint32(80);
atom.selection_time = atom.stream.getUint32(84);
atom.selection_duration = atom.stream.getUint32(88);
atom.current_time = atom.stream.getUint32(92);
atom.next_track_id = atom.stream.getUint32(96);
atom.stream = sliceDataView(atom.stream, 100);
return atom;
}
var parseCTAB = function (atom) {
atom.color_table_seed = atom.stream.getUint32(0);
atom.color_table_flags = atom.stream.getUint16(4);
atom.color_table_size = atom.stream.getUint16(6);
atom.color_array = parseColors(sliceDataView(atom.stream, 8, (atom.color_table_size + 1) * 8));
atom.stream = sliceDataView(8 + (atom.color_array.length * 8));
return atom;
}
var parseUserTag = function (atom) {
var value,
sub_atom,
attr,
i,
j;
if (USER_TAG_DESCRIPTIONS[atom.type] !== undefined) {
atom.description = USER_TAG_DESCRIPTIONS[atom.type];
if (atom.type[0] !== '©') {
atom.value = bytesToString(atom.stream);
atom.stream = sliceDataView(atom.stream, atom.value.length);
}
else {
atom.children = [];
i = 0;
while (i < atom.stream.byteLength) {
sub_atom = {
'language_code': atom.stream.getUint16(i + 2),
'language': undefined,
'offset': i,
'length': atom.stream.getUint16(i),
'raw_data': sliceDataView(atom.stream, i + 4, atom.stream.getUint16(i) - 4),
'value': undefined,
}
if (sub_atom_language_code in MACINTOSH_LANGUAGE_CODES) {
sub_atom.language = MACINTOSH_LANGUAGE_CODES[sub_atom_language_code];
sub_atom.value = ''
for (j=0; j < sub_atom_data.byteLength; j++) {
sub_atom.value += MACINTOSH_CODE_POINTS[sub_atom_data.getUint32(j)] || String.fromCharCode(j);
}
}
else if (sub_atom_language_code in PACKED_TO_ISO_CODE) {
sub_atom.unpacked_language_code = PACKED_TO_ISO_CODE[sub_atom.language_code];
sub_atom.language = ISO_LANGUAGE_CODES[sub_atom.unpacked_language_code];
if (sub_atom_data.getUint32(0) == 0xfeff) {
value = String.fromCharCode.apply(null, new Uint16Array(sub_atom_data.buffer, sub_atom_data.byteOffset, sub_atom_data.byteLength));
} else {
value = String.fromCharCode.apply(null, new Uint8Array(sub_atom_data.buffer, sub_atom_data.byteOffset, sub_atom_data.byteLength));
}
}
for (attr in sub_atom) {
if (sub_atom[attr] === undefined) {
delete sub_atom[attr];
}
}
atom.children.push(sub_atom);
i += sub_atom_length;
}
atom.stream = sliceDataView(atom.stream, i);
}
}
return atom;
}
var parseTAGC = function (atom) {
atom.value = bytesToString(atom.stream);
atom.stream = sliceDataView(atom.stream, atom.value.length);
return atom;
}
var parseTNAM = function (atom) {
atom.reserved = atom.stream.getUint32(0);
atom.language_code = atom.stream.getUint16(4);
atom.unpacked_language_code = PACKED_TO_ISO_CODE[atom.language_code];
if (sub_atom_data.getUint32(0) == 0xfeff) {
atom.name = String.fromCharCode.apply(null, new Uint16Array(atom.stream.buffer, atom.stream.byteOffset + 6, atom.stream.byteLength - 6));
} else {
atom.name = String.fromCharCode.apply(null, new Uint8Array(atom.stream.buffer, atom.stream.byteOffset + 6, atom.stream.byteLength - 6));
}
atom.stream = undefined;
return atom;
}
var parsePTV = function (atom) {
atom.display_size = ['normal', 'double', 'half', 'fill', 'current'][atom.stream.getUint16(0, true)];
atom.reserved_1 = atom.stream.getUint16(2);
atom.reserved_2 = atom.stream.getUint16(4);
atom.slide_show = atom.stream.getUint8(6) === 1;
atom.play_on_open = atom.stream.getUint8(8) === 1;
atom.stream = sliceDataView(atom.stream, 10);
return atom;
}
var parsePRFL = function (atom) {
var i,
feature_entry;
atom.version = atom.stream.getUint32(0);
atom.flags = sliceDataView(atom.stream, 1, 3);
atom.number_of_feature_entries = atom.stream.getUint32(4);
if (atom.number_of_feature_entries) {
atom.feature_entries = [];
for (i = 0; i < atom.number_of_feature_entries; i += 16) {
feature_entry = {
'offset': i,
'length': 16,
'reserved': atom.stream.getUint32(4 + i),
'part_id': bytesToString(sliceDataView(atom.stream, 4 + i + 4, 4)),
'feature_code': bytesToString(sliceDataView(atom.stream, 4 + i + 8, 4)),
'feature_value': atom.stream.getUint32(4 + i + 12),
'raw_data': sliceDataView(atom.stream, i, 16),
}
atom.feature_entries.push(feature_entry);
}
}
atom.stream = sliceDataView(atom.stream, 4 + (atom.number_of_feature_entries * 16));
return atom;
}
var parseTKHD = function (atom) {
var flags;
atom.version = atom.stream.getUint8(0);
atom.flags = {
'enabled': false,
'in_movie': false,
'in_preview': false,
'in_poster': false,
'raw_data': sliceDataView(atom.stream, 1, 3),
}
flags = atom.stream.getUint16(2);
if (flags >= 0x0008) { atom.flags.in_poster = true; flags -= 0x0008; }
if (flags >= 0x0004) { atom.flags.in_preview = true; flags -= 0x0004; }
if (flags >= 0x0002) { atom.flags.in_movie = true; flags -= 0x0002; }
if (flags >= 0x0001) { atom.flags.enabled = true; flags -= 0x0001; }
atom.creation_time = new Date(QT_EPOCH); atom.creation_time.setSeconds(atom.stream.getUint32(4));
atom.modification_time = new Date(QT_EPOCH); atom.modification_time.setSeconds(atom.stream.getUint32(8));
atom.track_id = atom.stream.getUint32(12);
atom.reserved = atom.stream.getUint32(16);
atom.duration = atom.stream.getUint32(20);
atom.reserved_1 = sliceDataView(atom.stream, 24, 8);
atom.layer = atom.stream.getUint16(32);
atom.alternate_group = atom.stream.getUint16(34);
atom.volume = atom.stream.getUint16(36) / Math.pow(2, 8);
atom.reserved_2 = atom.stream.getUint16(38);
atom.matrix_structure = parseMatrix(sliceDataView(atom.stream, 40, 36));
atom.track_width = atom.stream.getUint32(76) / Math.pow(2, 16);
atom.track_height = atom.stream.getUint32(80) / Math.pow(2, 16);
atom.stream = sliceDataView(atom.stream, 84);
return atom;
}
var parseTAPTTypes = function (atom) {
atom.version = atom.stream.getUint8(0);
atom.flags = sliceDataView(atom.stream, 1, 3);
atom.width = atom.stream.getUint32(4) / Math.pow(2, 16);
atom.height = atom.stream.getUint32(8) / Math.pow(2, 16);
atom.stream = sliceDataView(atom.stream, 8);
return atom;
}
var parseCRGN = function (atom) {
region_size = atom.stream.getUint16(0);
region_boundary_box = sliceDataView(atom.stream, 2, 8);
clipping_region_data = sliceDataView(atom.stream, 10);
atom.stream = undefined;
return atom;
}
var parseKMAT = function (atom) {
atom.version = atom.stream.getUint8(0);
atom.flags = sliceDataView(atom.stream, 1, 3);
// There are two more fields:
// - Matte image description structure
// - Matte data
// Both are of variable length, and neither are elaborated upon
// in the spec. So...
atom.stream = sliceDataView(atom.stream, 4);
return atom;
}
var parseELST = function (atom) {
var i;
atom.version = atom.stream.getUint8(0);
atom.flags = sliceDataView(atom.stream, 1, 3);
atom.number_of_entries = atom.stream.getUint32(4);
atom.edit_list_table = [];
for (i = 0; i < (atom.number_of_entries * 12); i += 12) {
atom.edit_list_table.push({
'offset': i,
'length': 12,
'track_duration': atom.stream.getUint32(8 + i),
'media_time': atom.stream.getInt32(8 + i + 4),
'media_rate': atom.stream.getUint32(8 + i + 8) / Math.pow(2, 16),
'raw_data': sliceDataView(atom.stream, i, 12),
});
}
atom.stream = sliceDataView(atom.stream, 8 + (atom.number_of_entries * 12));
return atom;
}
var parseLOAD = function (atom) {
var flags;
atom.preload_start_time = atom.stream.getUint32(0);
atom.preload_duration = atom.stream.getInt32(4);
atom.preload_flags = ['never', 'regardless', 'if_enabled'][atom.stream.getUint32(8)];
atom.default_hints = {
'double_buffer': false,
'high_quality': false,
};
flags = atom.stream.getUint32(12);
if (flags >= 0x0100) { atom.default_hints.high_quality = true; flags -= 0x0100; }
if (flags >= 0x0020) { atom.default_hints.double_buffer = true; flags -= 0x0020; }
return atom;
}
var ignore = function (atom) {
atom.stream = undefined;
return atom;
}
// Parser Flows
var atom_processors_for_type = {
undefined: [parseRaw],
'ftyp': [parseRaw, parseFTYP],
'mvhd': [parseRaw, parseMVHD],
'ctab': [parseRaw, parseCTAB],
'moov': [parseRaw, parseChildren],
'udta': [parseRaw, parseChildren],
'tagc': [parseRaw, parseTAGC],
'tnam': [parseRaw, parseTNAM],
'ptv ': [parseRaw, parsePTV],
'trak': [parseRaw, parseChildren],
'tkhd': [parseRaw, parseTKHD],
'txas': [parseRaw],
'tapt': [parseRaw, parseChildren],
'clef': [parseRaw, parseTAPTTypes],
'prof': [parseRaw, parseTAPTTypes],
'enof': [parseRaw, parseTAPTTypes],
'clip': [parseRaw, parseChildren],
'crgn': [parseRaw, parseCRGN],
'matt': [parseRaw, parseChildren],
'edts': [parseRaw, parseChildren],
'elst': [parseRaw, parseELST],
'load': [parseRaw, parseLOAD],
// To Finish.
'prfl': [parseRaw, parsePRFL],
'kmat': [parseRaw, parseKMAT],
// To Do.
'mdia': [parseRaw, parseChildren],
'minf': [parseRaw, parseChildren],
'dinf': [parseRaw, parseChildren],
'stbl': [parseRaw, parseChildren],
'edts': [parseRaw, parseChildren],
'tref': [parseRaw],
// Ignore
'mdat': [parseRaw, ignore],
};
for (var type in USER_TAG_DESCRIPTIONS) {
if (atom_processors_for_type[type] === undefined) {
atom_processors_for_type[type] = [parseRaw, parseUserTag];
}
}
// Main Loop
var parseQuickTime = function (view, offset, length, state, level) {
var state,
view,
i,
stream_offset,
possible_length,
atom_processors,
attr,
state;
offset = offset || 0;
length = length || view.byteLength;
level = level || 0;
state = state || {
'parsed_atoms': [],
'current_atom': {
'level': level,
'type': undefined,
'name': undefined,
'offset': undefined,
'length': undefined,
'length_type': undefined,
},
'needed_bytes': 0,
};
i = 0;
while (i < 50 && length >= state.needed_bytes) {
if (state.current_atom.length !== undefined && (state.current_atom.offset + state.current_atom.length) <= view.byteLength) {
state.current_atom.type = bytesToString(view, state.current_atom.offset + 4, 4);
state.current_atom.name = ATOM_NAMES[state.current_atom.type];
stream_offset = state.current_atom.length_type === 'extended'? 16 : 8;
state.current_atom.stream = new DataView(
view.buffer,
view.byteOffset + state.current_atom.offset + stream_offset,
state.current_atom.length - stream_offset
);
atom_processors = [state.current_atom].concat(atom_processors_for_type[state.current_atom.type] || atom_processors_for_type[undefined]);
state.current_atom = atom_processors.reduce(function (atom, processor) { return processor(atom); });
for (attr in state.current_atom) {
if (state.current_atom[attr] === undefined) {
delete state.current_atom[attr];
}
}
state.parsed_atoms.push(state.current_atom);
state.current_atom = {
'level': level,
'type': undefined,
'offset': state.current_atom.offset + state.current_atom.length,
'length': undefined,
'length_type': undefined,
}
state.needed_bytes = state.current_atom.offset + 4;
}
else if (state.current_atom.offset !== undefined && (state.current_atom.offset + 4) <= view.byteLength) {
if (state.current_atom.length_type === 'extended') {
state.current_atom.length = view.getUint64(state.current_atom.offset + 8);
}
else {
possible_length = view.getUint32(state.current_atom.offset);
if (possible_length === 0) {
// Runs to end of file.
state.current_atom.length_type = 'end';
state.current_atom.length = (length - state.current_atom.offset);
}
else if (possible_length === 1) {
// Extended size: True size is 8 bytes after the type.
state.current_atom.length_type = 'extended';
state.needed_bytes = state.current_atom.offset + 8;
}
else {
// Standard size.
state.current_atom.length_type = 'standard';
state.current_atom.length = view.getUint32(state.current_atom.offset);
}
}
if (state.current_atom.length) {
state.needed_bytes = state.current_atom.offset + state.current_atom.length;
}
}
else {
state.current_atom.offset = (state.current_atom.offset || offset);
// 4 bytes for the length.
state.needed_bytes = state.current_atom.offset + 4;
}
i++;
}
return state;
}
// Example
var SOURCE_URL = 'https://raw.githubusercontent.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4';
var xhr = new XMLHttpRequest();
xhr.open('GET', SOURCE_URL);
xhr.responseType = 'arraybuffer';
xhr.addEventListener('load', function () {
var state = parseQuickTime(new DataView(xhr.response));
print(state.parsed_atoms);
})
var print = function (parsed_atoms) {
for (var atom in parsed_atoms) {
for (var attr in parsed_atoms[atom]) {
if (attr === 'stream') {
console.log(
"\t\t\t\t\t\t\t\t\t\t\t\t".slice(0, parsed_atoms[atom].level) + attr,
parsed_atoms[atom].stream.buffer.slice(parsed_atoms[atom].stream.byteOffset, parsed_atoms[atom].stream.byteLength).toSource(true)
);
}
else {
console.log(
"\t\t\t\t\t\t\t\t\t\t\t\t".slice(0, parsed_atoms[atom].level) + attr,
parsed_atoms[atom][attr],
);
}
}
console.log('');
if (parsed_atoms[atom].children !== undefined) {
print(parsed_atoms[atom].children);
}
}
};
xhr.send();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment