File: datauri.go 1 /* 2 The MIT License (MIT) 3 4 Copyright © 2020-2025 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the “Software”), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 /* 26 Single-file source-code for datauri, with the unit-tests omitted. 27 28 To compile a smaller-sized command-line app, you can use the `go` command as 29 follows: 30 31 go build -ldflags "-s -w" -trimpath datauri.go 32 */ 33 34 package main 35 36 import ( 37 "bufio" 38 "bytes" 39 "encoding/base64" 40 "errors" 41 "io" 42 "os" 43 "strings" 44 ) 45 46 const info = ` 47 datauri [options...] [filenames...] 48 49 50 Encode bytes as data-URIs, auto-detecting the file/data type using the first 51 few bytes from each data/file stream. When given multiple inputs, the output 52 will be multiple lines, one for each file given. 53 54 Empty files/inputs result in empty lines. A simple dash (-) stands for the 55 standard-input, which is also used automatically when not given any files. 56 57 Data-URIs are base64-encoded text representations of arbitrary data, which 58 include their payload's MIME-type, and which are directly useable/shareable 59 in web-browsers as links, despite not looking like normal links/URIs. 60 61 Some web-browsers limit the size of handled data-URIs to tens of kilobytes. 62 63 64 Options 65 66 -h, -help, --h, --help show this help message 67 ` 68 69 const errorStyle = "\x1b[31m" 70 71 // errNoMoreOutput is a dummy error, whose message is ignored, and which 72 // causes the app to quit immediately and successfully 73 var errNoMoreOutput = errors.New(`no more output`) 74 75 func main() { 76 if len(os.Args) > 1 { 77 switch os.Args[1] { 78 case `-h`, `--h`, `-help`, `--help`: 79 os.Stderr.WriteString(info[1:]) 80 return 81 } 82 } 83 84 if err := run(os.Stdout, os.Args[1:]); isActualError(err) { 85 os.Stderr.WriteString(errorStyle) 86 os.Stderr.WriteString(err.Error()) 87 os.Stderr.WriteString("\x1b[0m\n") 88 os.Exit(1) 89 } 90 } 91 92 func run(w io.Writer, args []string) error { 93 bw := bufio.NewWriter(w) 94 defer bw.Flush() 95 96 if len(args) == 0 { 97 return dataURI(bw, os.Stdin, `<stdin>`) 98 } 99 100 for _, name := range args { 101 if err := handleFile(bw, name); err != nil { 102 return err 103 } 104 } 105 return nil 106 } 107 108 func handleFile(w *bufio.Writer, name string) error { 109 if name == `` || name == `-` { 110 return dataURI(w, os.Stdin, `<stdin>`) 111 } 112 113 f, err := os.Open(name) 114 if err != nil { 115 return errors.New(`can't read from file named "` + name + `"`) 116 } 117 defer f.Close() 118 119 return dataURI(w, f, name) 120 } 121 122 // isActualError is to figure out whether not to ignore an error, and thus 123 // show it as an error message 124 func isActualError(err error) bool { 125 return err != nil && err != io.EOF && err != errNoMoreOutput 126 } 127 128 func dataURI(w *bufio.Writer, r io.Reader, name string) error { 129 var buf [64]byte 130 n, err := r.Read(buf[:]) 131 if err != nil && err != io.EOF { 132 return err 133 } 134 start := buf[:n] 135 136 // handle regular data, trying to auto-detect its MIME type using 137 // its first few bytes 138 mime, ok := detectMIME(start) 139 if !ok { 140 return errors.New(name + `: unknown file type`) 141 } 142 143 w.WriteString(`data:`) 144 w.WriteString(mime) 145 w.WriteString(`;base64,`) 146 r = io.MultiReader(bytes.NewReader(start), r) 147 enc := base64.NewEncoder(base64.StdEncoding, w) 148 if _, err := io.Copy(enc, r); err != nil { 149 return err 150 } 151 enc.Close() 152 153 w.WriteByte('\n') 154 if err := w.Flush(); err != nil { 155 return errNoMoreOutput 156 } 157 return nil 158 } 159 160 // makeDotless is similar to filepath.Ext, except its results never start 161 // with a dot 162 func makeDotless(s string) string { 163 i := strings.LastIndexByte(s, '.') 164 if i >= 0 { 165 return s[(i + 1):] 166 } 167 return s 168 } 169 170 // hasPrefixByte is a simpler, single-byte version of bytes.HasPrefix 171 func hasPrefixByte(b []byte, prefix byte) bool { 172 return len(b) > 0 && b[0] == prefix 173 } 174 175 // hasPrefixFold is a case-insensitive bytes.HasPrefix 176 func hasPrefixFold(s []byte, prefix []byte) bool { 177 n := len(prefix) 178 return len(s) >= n && bytes.EqualFold(s[:n], prefix) 179 } 180 181 // trimLeadingWhitespace ignores leading space-like symbols: this is useful 182 // to handle text-based data formats more flexibly 183 func trimLeadingWhitespace(b []byte) []byte { 184 for len(b) > 0 { 185 switch b[0] { 186 case ' ', '\t', '\n', '\r': 187 b = b[1:] 188 default: 189 return b 190 } 191 } 192 193 // an empty slice is all that's left, at this point 194 return nil 195 } 196 197 // nameToMIME tries to match a MIME type to a filename, dotted file extension, 198 // or a dot-less filetype/extension given 199 func nameToMIME(fname string) (mimeType string, ok bool) { 200 // handle dotless file types and filenames alike 201 kind, ok := type2mime[makeDotless(fname)] 202 return kind, ok 203 } 204 205 // detectMIME guesses the first appropriate MIME type from the first few 206 // data bytes given: 24 bytes are enough to detect all supported types 207 func detectMIME(b []byte) (mimeType string, ok bool) { 208 t, ok := detectType(b) 209 if ok { 210 return t, true 211 } 212 return ``, false 213 } 214 215 // detectType guesses the first appropriate file type for the data given: 216 // here the type is a a filename extension without the leading dot 217 func detectType(b []byte) (dotlessExt string, ok bool) { 218 // empty data, so there's no way to detect anything 219 if len(b) == 0 { 220 return ``, false 221 } 222 223 // check for plain-text web-document formats case-insensitively 224 kind, ok := checkDoc(b) 225 if ok { 226 return kind, true 227 } 228 229 // check data formats which allow any byte at the start 230 kind, ok = checkSpecial(b) 231 if ok { 232 return kind, true 233 } 234 235 // check all other supported data formats 236 headers := hdrDispatch[b[0]] 237 for _, t := range headers { 238 if hasPrefixPattern(b[1:], t.Header[1:], cba) { 239 return t.Type, true 240 } 241 } 242 243 // unrecognized data format 244 return ``, false 245 } 246 247 // checkDoc tries to guess if the bytes given are the start of HTML, SVG, 248 // XML, or JSON data 249 func checkDoc(b []byte) (kind string, ok bool) { 250 // ignore leading whitespaces 251 b = trimLeadingWhitespace(b) 252 253 // can't detect anything with empty data 254 if len(b) == 0 { 255 return ``, false 256 } 257 258 // handle XHTML documents which don't start with a doctype declaration 259 if bytes.Contains(b, doctypeHTML) { 260 return html, true 261 } 262 263 // handle HTML/SVG/XML documents 264 if hasPrefixByte(b, '<') { 265 if hasPrefixFold(b, []byte{'<', '?', 'x', 'm', 'l'}) { 266 if bytes.Contains(b, []byte{'<', 's', 'v', 'g'}) { 267 return svg, true 268 } 269 return xml, true 270 } 271 272 headers := hdrDispatch['<'] 273 for _, v := range headers { 274 if hasPrefixFold(b, v.Header) { 275 return v.Type, true 276 } 277 } 278 return ``, false 279 } 280 281 // handle JSON with top-level arrays 282 if hasPrefixByte(b, '[') { 283 // match [", or [[, or [{, ignoring spaces between 284 b = trimLeadingWhitespace(b[1:]) 285 if len(b) > 0 { 286 switch b[0] { 287 case '"', '[', '{': 288 return json, true 289 } 290 } 291 return ``, false 292 } 293 294 // handle JSON with top-level objects 295 if hasPrefixByte(b, '{') { 296 // match {", ignoring spaces between: after {, the only valid syntax 297 // which can follow is the opening quote for the expected object-key 298 b = trimLeadingWhitespace(b[1:]) 299 if hasPrefixByte(b, '"') { 300 return json, true 301 } 302 return ``, false 303 } 304 305 // checking for a quoted string, any of the JSON keywords, or even a 306 // number seems too ambiguous to declare the data valid JSON 307 308 // no web-document format detected 309 return ``, false 310 } 311 312 // checkSpecial handles special file-format headers, which should be checked 313 // before the normal file-type headers, since the first-byte dispatch algo 314 // doesn't work for these 315 func checkSpecial(b []byte) (kind string, ok bool) { 316 if len(b) >= 8 && bytes.Index(b, []byte{'f', 't', 'y', 'p'}) == 4 { 317 for _, t := range specialHeaders { 318 if hasPrefixPattern(b[4:], t.Header[4:], cba) { 319 return t.Type, true 320 } 321 } 322 } 323 return ``, false 324 } 325 326 // hasPrefixPattern works like bytes.HasPrefix, except it allows for a special 327 // value to signal any byte is allowed on specific spots 328 func hasPrefixPattern(what []byte, pat []byte, wildcard byte) bool { 329 // if the data are shorter than the pattern to match, there's no match 330 if len(what) < len(pat) { 331 return false 332 } 333 334 // use a slice which ensures the pattern length is never exceeded 335 what = what[:len(pat)] 336 337 for i, x := range what { 338 y := pat[i] 339 if x != y && y != wildcard { 340 return false 341 } 342 } 343 return true 344 } 345 346 // all the MIME types used/recognized in this package 347 const ( 348 aiff = `audio/aiff` 349 au = `audio/basic` 350 avi = `video/avi` 351 avif = `image/avif` 352 bmp = `image/x-bmp` 353 caf = `audio/x-caf` 354 cur = `image/vnd.microsoft.icon` 355 css = `text/css` 356 csv = `text/csv` 357 djvu = `image/x-djvu` 358 elf = `application/x-elf` 359 exe = `application/vnd.microsoft.portable-executable` 360 flac = `audio/x-flac` 361 gif = `image/gif` 362 gz = `application/gzip` 363 heic = `image/heic` 364 htm = `text/html` 365 html = `text/html` 366 ico = `image/x-icon` 367 iso = `application/octet-stream` 368 jpg = `image/jpeg` 369 jpeg = `image/jpeg` 370 js = `application/javascript` 371 json = `application/json` 372 m4a = `audio/aac` 373 m4v = `video/x-m4v` 374 mid = `audio/midi` 375 mov = `video/quicktime` 376 mp4 = `video/mp4` 377 mp3 = `audio/mpeg` 378 mpg = `video/mpeg` 379 ogg = `audio/ogg` 380 opus = `audio/opus` 381 pdf = `application/pdf` 382 png = `image/png` 383 ps = `application/postscript` 384 psd = `image/vnd.adobe.photoshop` 385 rtf = `application/rtf` 386 sqlite3 = `application/x-sqlite3` 387 svg = `image/svg+xml` 388 text = `text/plain` 389 tiff = `image/tiff` 390 tsv = `text/tsv` 391 wasm = `application/wasm` 392 wav = `audio/x-wav` 393 webp = `image/webp` 394 webm = `video/webm` 395 xml = `application/xml` 396 zip = `application/zip` 397 zst = `application/zstd` 398 ) 399 400 // type2mime turns dotless format-names into MIME types 401 var type2mime = map[string]string{ 402 `aiff`: aiff, 403 `wav`: wav, 404 `avi`: avi, 405 `jpg`: jpg, 406 `jpeg`: jpeg, 407 `m4a`: m4a, 408 `mp4`: mp4, 409 `m4v`: m4v, 410 `mov`: mov, 411 `png`: png, 412 `avif`: avif, 413 `webp`: webp, 414 `gif`: gif, 415 `tiff`: tiff, 416 `psd`: psd, 417 `flac`: flac, 418 `webm`: webm, 419 `mpg`: mpg, 420 `zip`: zip, 421 `gz`: gz, 422 `zst`: zst, 423 `mp3`: mp3, 424 `opus`: opus, 425 `bmp`: bmp, 426 `mid`: mid, 427 `ogg`: ogg, 428 `html`: html, 429 `htm`: htm, 430 `svg`: svg, 431 `xml`: xml, 432 `rtf`: rtf, 433 `pdf`: pdf, 434 `ps`: ps, 435 `au`: au, 436 `ico`: ico, 437 `cur`: cur, 438 `caf`: caf, 439 `heic`: heic, 440 `sqlite3`: sqlite3, 441 `elf`: elf, 442 `exe`: exe, 443 `wasm`: wasm, 444 `iso`: iso, 445 `txt`: text, 446 `css`: css, 447 `csv`: csv, 448 `tsv`: tsv, 449 `js`: js, 450 `json`: json, 451 `geojson`: json, 452 } 453 454 // formatDescriptor ties a file-header pattern to its data-format type 455 type formatDescriptor struct { 456 Header []byte 457 Type string 458 } 459 460 // can be anything: ensure this value differs from all other literal bytes 461 // in the generic-headers table: failing that, its value could cause subtle 462 // type-misdetection bugs 463 const cba = 0xFD // 253, which is > 127, the highest-valued ascii symbol 464 465 // dash-streamed m4a format 466 var m4aDash = []byte{ 467 cba, cba, cba, cba, 'f', 't', 'y', 'p', 'd', 'a', 's', 'h', 468 000, 000, 000, 000, 'i', 's', 'o', '6', 'm', 'p', '4', '1', 469 } 470 471 // format markers with leading wildcards, which should be checked before the 472 // normal ones: this is to prevent mismatches with the latter types, even 473 // though you can make probabilistic arguments which suggest these mismatches 474 // should be very unlikely in practice 475 var specialHeaders = []formatDescriptor{ 476 {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'M', '4', 'A', ' '}, m4a}, 477 {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'M', '4', 'A', 000}, m4a}, 478 {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'M', 'S', 'N', 'V'}, mp4}, 479 {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'i', 's', 'o', 'm'}, mp4}, 480 {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'm', 'p', '4', '2'}, m4v}, 481 {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'q', 't', ' ', ' '}, mov}, 482 {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'h', 'e', 'i', 'c'}, heic}, 483 {[]byte{cba, cba, cba, cba, 'f', 't', 'y', 'p', 'a', 'v', 'i', 'f'}, avif}, 484 {m4aDash, m4a}, 485 } 486 487 // sqlite3 database format 488 var sqlite3db = []byte{ 489 'S', 'Q', 'L', 'i', 't', 'e', ' ', 490 'f', 'o', 'r', 'm', 'a', 't', ' ', '3', 491 000, 492 } 493 494 // windows-variant bitmap file-header, which is followed by a byte-counter for 495 // the 40-byte infoheader which follows that 496 var winbmp = []byte{ 497 'B', 'M', cba, cba, cba, cba, cba, cba, cba, cba, cba, cba, cba, cba, 40, 498 } 499 500 // deja-vu document format 501 var djv = []byte{ 502 'A', 'T', '&', 'T', 'F', 'O', 'R', 'M', cba, cba, cba, cba, 'D', 'J', 'V', 503 } 504 505 var doctypeHTML = []byte{ 506 '<', '!', 'D', 'O', 'C', 'T', 'Y', 'P', 'E', ' ', 'h', 't', 'm', 'l', 507 } 508 509 // hdrDispatch groups format-description-groups by their first byte, thus 510 // shortening total lookups for some data header: notice how the `ftyp` data 511 // formats aren't handled here, since these can start with any byte, instead 512 // of the literal value of the any-byte markers they use 513 var hdrDispatch = [256][]formatDescriptor{ 514 { 515 {[]byte{000, 000, 001, 0xBA}, mpg}, 516 {[]byte{000, 000, 001, 0xB3}, mpg}, 517 {[]byte{000, 000, 001, 000}, ico}, 518 {[]byte{000, 000, 002, 000}, cur}, 519 {[]byte{000, 'a', 's', 'm'}, wasm}, 520 }, // 0 521 nil, // 1 522 nil, // 2 523 nil, // 3 524 nil, // 4 525 nil, // 5 526 nil, // 6 527 nil, // 7 528 nil, // 8 529 nil, // 9 530 nil, // 10 531 nil, // 11 532 nil, // 12 533 nil, // 13 534 nil, // 14 535 nil, // 15 536 nil, // 16 537 nil, // 17 538 nil, // 18 539 nil, // 19 540 nil, // 20 541 nil, // 21 542 nil, // 22 543 nil, // 23 544 nil, // 24 545 nil, // 25 546 { 547 {[]byte{0x1A, 0x45, 0xDF, 0xA3}, webm}, 548 }, // 26 549 nil, // 27 550 nil, // 28 551 nil, // 29 552 nil, // 30 553 { 554 // {[]byte{0x1F, 0x8B, 0x08, 0x08}, gz}, 555 {[]byte{0x1F, 0x8B, 0x08}, gz}, 556 }, // 31 557 nil, // 32 558 nil, // 33 ! 559 nil, // 34 " 560 { 561 {[]byte{'#', '!', ' '}, text}, 562 {[]byte{'#', '!', '/'}, text}, 563 }, // 35 # 564 nil, // 36 $ 565 { 566 {[]byte{'%', 'P', 'D', 'F'}, pdf}, 567 {[]byte{'%', '!', 'P', 'S'}, ps}, 568 }, // 37 % 569 nil, // 38 & 570 nil, // 39 ' 571 { 572 {[]byte{0x28, 0xB5, 0x2F, 0xFD}, zst}, 573 }, // 40 ( 574 nil, // 41 ) 575 nil, // 42 * 576 nil, // 43 + 577 nil, // 44 , 578 nil, // 45 - 579 { 580 {[]byte{'.', 's', 'n', 'd'}, au}, 581 }, // 46 . 582 nil, // 47 / 583 nil, // 48 0 584 nil, // 49 1 585 nil, // 50 2 586 nil, // 51 3 587 nil, // 52 4 588 nil, // 53 5 589 nil, // 54 6 590 nil, // 55 7 591 { 592 {[]byte{'8', 'B', 'P', 'S'}, psd}, 593 }, // 56 8 594 nil, // 57 9 595 nil, // 58 : 596 nil, // 59 ; 597 { 598 // func checkDoc is better for these, since it's case-insensitive 599 {doctypeHTML, html}, 600 {[]byte{'<', 's', 'v', 'g'}, svg}, 601 {[]byte{'<', 'h', 't', 'm', 'l', '>'}, html}, 602 {[]byte{'<', 'h', 'e', 'a', 'd', '>'}, html}, 603 {[]byte{'<', 'b', 'o', 'd', 'y', '>'}, html}, 604 {[]byte{'<', '?', 'x', 'm', 'l'}, xml}, 605 }, // 60 < 606 nil, // 61 = 607 nil, // 62 > 608 nil, // 63 ? 609 nil, // 64 @ 610 { 611 {djv, djvu}, 612 }, // 65 A 613 { 614 {winbmp, bmp}, 615 }, // 66 B 616 nil, // 67 C 617 nil, // 68 D 618 nil, // 69 E 619 { 620 {[]byte{'F', 'O', 'R', 'M', cba, cba, cba, cba, 'A', 'I', 'F', 'F'}, aiff}, 621 {[]byte{'F', 'O', 'R', 'M', cba, cba, cba, cba, 'A', 'I', 'F', 'C'}, aiff}, 622 }, // 70 F 623 { 624 {[]byte{'G', 'I', 'F', '8', '7', 'a'}, gif}, 625 {[]byte{'G', 'I', 'F', '8', '9', 'a'}, gif}, 626 }, // 71 G 627 nil, // 72 H 628 { 629 {[]byte{'I', 'D', '3', 2}, mp3}, // ID3-format metadata 630 {[]byte{'I', 'D', '3', 3}, mp3}, // ID3-format metadata 631 {[]byte{'I', 'D', '3', 4}, mp3}, // ID3-format metadata 632 {[]byte{'I', 'I', '*', 000}, tiff}, 633 }, // 73 I 634 nil, // 74 J 635 nil, // 75 K 636 nil, // 76 L 637 { 638 {[]byte{'M', 'M', 000, '*'}, tiff}, 639 {[]byte{'M', 'T', 'h', 'd'}, mid}, 640 {[]byte{'M', 'Z', cba, 000, cba, 000}, exe}, 641 // {[]byte{'M', 'Z', 0x90, 000, 003, 000}, exe}, 642 // {[]byte{'M', 'Z', 0x78, 000, 001, 000}, exe}, 643 // {[]byte{'M', 'Z', 'P', 000, 002, 000}, exe}, 644 }, // 77 M 645 nil, // 78 N 646 { 647 {[]byte{'O', 'g', 'g', 'S'}, ogg}, 648 }, // 79 O 649 { 650 {[]byte{'P', 'K', 003, 004}, zip}, 651 }, // 80 P 652 nil, // 81 Q 653 { 654 {[]byte{'R', 'I', 'F', 'F', cba, cba, cba, cba, 'W', 'E', 'B', 'P'}, webp}, 655 {[]byte{'R', 'I', 'F', 'F', cba, cba, cba, cba, 'W', 'A', 'V', 'E'}, wav}, 656 {[]byte{'R', 'I', 'F', 'F', cba, cba, cba, cba, 'A', 'V', 'I', ' '}, avi}, 657 }, // 82 R 658 { 659 {sqlite3db, sqlite3}, 660 }, // 83 S 661 nil, // 84 T 662 nil, // 85 U 663 nil, // 86 V 664 nil, // 87 W 665 nil, // 88 X 666 nil, // 89 Y 667 nil, // 90 Z 668 nil, // 91 [ 669 nil, // 92 \ 670 nil, // 93 ] 671 nil, // 94 ^ 672 nil, // 95 _ 673 nil, // 96 ` 674 nil, // 97 a 675 nil, // 98 b 676 { 677 {[]byte{'c', 'a', 'f', 'f', 000, 001, 000, 000}, caf}, 678 }, // 99 c 679 nil, // 100 d 680 nil, // 101 e 681 { 682 {[]byte{'f', 'L', 'a', 'C'}, flac}, 683 }, // 102 f 684 nil, // 103 g 685 nil, // 104 h 686 nil, // 105 i 687 nil, // 106 j 688 nil, // 107 k 689 nil, // 108 l 690 nil, // 109 m 691 nil, // 110 n 692 nil, // 111 o 693 nil, // 112 p 694 nil, // 113 q 695 nil, // 114 r 696 nil, // 115 s 697 nil, // 116 t 698 nil, // 117 u 699 nil, // 118 v 700 nil, // 119 w 701 nil, // 120 x 702 nil, // 121 y 703 nil, // 122 z 704 { 705 {[]byte{'{', '\\', 'r', 't', 'f'}, rtf}, 706 }, // 123 { 707 nil, // 124 | 708 nil, // 125 } 709 nil, // 126 710 { 711 {[]byte{127, 'E', 'L', 'F'}, elf}, 712 }, // 127 713 nil, // 128 714 nil, // 129 715 nil, // 130 716 nil, // 131 717 nil, // 132 718 nil, // 133 719 nil, // 134 720 nil, // 135 721 nil, // 136 722 { 723 {[]byte{0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A}, png}, 724 }, // 137 725 nil, // 138 726 nil, // 139 727 nil, // 140 728 nil, // 141 729 nil, // 142 730 nil, // 143 731 nil, // 144 732 nil, // 145 733 nil, // 146 734 nil, // 147 735 nil, // 148 736 nil, // 149 737 nil, // 150 738 nil, // 151 739 nil, // 152 740 nil, // 153 741 nil, // 154 742 nil, // 155 743 nil, // 156 744 nil, // 157 745 nil, // 158 746 nil, // 159 747 nil, // 160 748 nil, // 161 749 nil, // 162 750 nil, // 163 751 nil, // 164 752 nil, // 165 753 nil, // 166 754 nil, // 167 755 nil, // 168 756 nil, // 169 757 nil, // 170 758 nil, // 171 759 nil, // 172 760 nil, // 173 761 nil, // 174 762 nil, // 175 763 nil, // 176 764 nil, // 177 765 nil, // 178 766 nil, // 179 767 nil, // 180 768 nil, // 181 769 nil, // 182 770 nil, // 183 771 nil, // 184 772 nil, // 185 773 nil, // 186 774 nil, // 187 775 nil, // 188 776 nil, // 189 777 nil, // 190 778 nil, // 191 779 nil, // 192 780 nil, // 193 781 nil, // 194 782 nil, // 195 783 nil, // 196 784 nil, // 197 785 nil, // 198 786 nil, // 199 787 nil, // 200 788 nil, // 201 789 nil, // 202 790 nil, // 203 791 nil, // 204 792 nil, // 205 793 nil, // 206 794 nil, // 207 795 nil, // 208 796 nil, // 209 797 nil, // 210 798 nil, // 211 799 nil, // 212 800 nil, // 213 801 nil, // 214 802 nil, // 215 803 nil, // 216 804 nil, // 217 805 nil, // 218 806 nil, // 219 807 nil, // 220 808 nil, // 221 809 nil, // 222 810 nil, // 223 811 nil, // 224 812 nil, // 225 813 nil, // 226 814 nil, // 227 815 nil, // 228 816 nil, // 229 817 nil, // 230 818 nil, // 231 819 nil, // 232 820 nil, // 233 821 nil, // 234 822 nil, // 235 823 nil, // 236 824 nil, // 237 825 nil, // 238 826 nil, // 239 827 nil, // 240 828 nil, // 241 829 nil, // 242 830 nil, // 243 831 nil, // 244 832 nil, // 245 833 nil, // 246 834 nil, // 247 835 nil, // 248 836 nil, // 249 837 nil, // 250 838 nil, // 251 839 nil, // 252 840 nil, // 253 841 nil, // 254 842 { 843 {[]byte{0xFF, 0xD8, 0xFF}, jpg}, 844 {[]byte{0xFF, 0xF3, 0x48, 0xC4, 0x00}, mp3}, 845 {[]byte{0xFF, 0xFB}, mp3}, 846 }, // 255 847 }