summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA. Cody Schuffelen <schuffelen@google.com>2020-11-20 02:19:38 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-11-20 02:19:38 +0000
commit4534b4c172b259218a40c40fdc235b46843037b8 (patch)
tree773e0b92a05045d70173dcf2aa2351c1fb1a96d0
parentb0487c6a476c4e43c8f187117d454ac3aa81a5d5 (diff)
parent739708406bbb5699d11c6e9b742cf5beb3af954c (diff)
downloadlibwebm-4534b4c172b259218a40c40fdc235b46843037b8.tar.gz
Merge remote-tracking branch 'origin/upstream-master' am: 2efb1cd57a am: 739708406b
Original change: https://android-review.googlesource.com/c/platform/external/libwebm/+/1504154 Change-Id: I613fd1293bc638f7553b76ca4dc721267fc66f41
-rw-r--r--.clang-format152
-rw-r--r--.gitattributes6
-rw-r--r--.gitignore35
-rw-r--r--AUTHORS.TXT4
-rw-r--r--Android.mk17
-rw-r--r--CMakeLists.txt452
-rw-r--r--LICENSE.TXT30
-rw-r--r--Makefile.unix57
-rw-r--r--PATENTS.TXT23
-rw-r--r--README.libwebm143
-rw-r--r--build/cxx_flags.cmake73
-rw-r--r--build/msvc_runtime.cmake23
-rw-r--r--build/x86-mingw-gcc.cmake26
-rw-r--r--build/x86_64-mingw-gcc.cmake24
-rw-r--r--codereview.settings4
-rw-r--r--common/common.sh62
-rw-r--r--common/file_util.cc93
-rw-r--r--common/file_util.h44
-rw-r--r--common/hdr_util.cc220
-rw-r--r--common/hdr_util.h71
-rw-r--r--common/indent.cc29
-rw-r--r--common/indent.h48
-rw-r--r--common/libwebm_util.cc110
-rw-r--r--common/libwebm_util.h65
-rw-r--r--common/video_frame.cc45
-rw-r--r--common/video_frame.h68
-rw-r--r--common/vp9_header_parser.cc266
-rw-r--r--common/vp9_header_parser.h125
-rw-r--r--common/vp9_header_parser_tests.cc181
-rw-r--r--common/vp9_level_stats.cc269
-rw-r--r--common/vp9_level_stats.h215
-rw-r--r--common/vp9_level_stats_tests.cc191
-rw-r--r--common/webm_constants.h20
-rw-r--r--common/webm_endian.cc85
-rw-r--r--common/webm_endian.h38
-rw-r--r--common/webmids.h193
-rw-r--r--dumpvtt.cc91
-rw-r--r--hdr_util.hpp15
-rwxr-xr-xiosbuild.sh207
-rw-r--r--m2ts/tests/webm2pes_tests.cc158
-rw-r--r--m2ts/vpxpes2ts.cc217
-rw-r--r--m2ts/vpxpes2ts.h45
-rw-r--r--m2ts/vpxpes2ts_main.cc33
-rw-r--r--m2ts/vpxpes_parser.cc409
-rw-r--r--m2ts/vpxpes_parser.h177
-rw-r--r--m2ts/webm2pes.cc551
-rw-r--r--m2ts/webm2pes.h274
-rw-r--r--m2ts/webm2pes_main.cc33
-rw-r--r--mkvmuxer.hpp15
-rw-r--r--mkvmuxer/mkvmuxer.cc4221
-rw-r--r--mkvmuxer/mkvmuxer.h1924
-rw-r--r--mkvmuxer/mkvmuxertypes.h28
-rw-r--r--mkvmuxer/mkvmuxerutil.cc743
-rw-r--r--mkvmuxer/mkvmuxerutil.h115
-rw-r--r--mkvmuxer/mkvwriter.cc92
-rw-r--r--mkvmuxer/mkvwriter.h51
-rw-r--r--mkvmuxer_sample.cc802
-rw-r--r--mkvmuxertypes.hpp15
-rw-r--r--mkvmuxerutil.hpp18
-rw-r--r--mkvparser.hpp15
-rw-r--r--mkvparser/mkvparser.cc8076
-rw-r--r--mkvparser/mkvparser.h1147
-rw-r--r--mkvparser/mkvreader.cc135
-rw-r--r--mkvparser/mkvreader.h45
-rw-r--r--mkvparser_sample.cc459
-rw-r--r--mkvreader.hpp15
-rw-r--r--mkvwriter.hpp15
-rw-r--r--sample_muxer_metadata.cc391
-rw-r--r--sample_muxer_metadata.h137
-rw-r--r--testing/mkvmuxer_tests.cc1010
-rw-r--r--testing/mkvparser_tests.cc823
-rw-r--r--testing/test_util.cc215
-rw-r--r--testing/test_util.h88
-rw-r--r--testing/testdata/accurate_cluster_duration.webmbin0 -> 376 bytes
-rw-r--r--testing/testdata/accurate_cluster_duration_last_frame.webmbin0 -> 384 bytes
-rw-r--r--testing/testdata/accurate_cluster_duration_two_tracks.webmbin0 -> 533 bytes
-rw-r--r--testing/testdata/bbb_480p_vp9_opus_1second.webmbin0 -> 45863 bytes
-rw-r--r--testing/testdata/block_with_additional.webmbin0 -> 348 bytes
-rw-r--r--testing/testdata/chapters.webmbin0 -> 346 bytes
-rw-r--r--testing/testdata/colour.webmbin0 -> 416 bytes
-rw-r--r--testing/testdata/cues_before_clusters.webmbin0 -> 380 bytes
-rw-r--r--testing/testdata/discard_padding.webmbin0 -> 341 bytes
-rw-r--r--testing/testdata/estimate_duration.webmbin0 -> 365 bytes
-rw-r--r--testing/testdata/fixed_size_cluster_timecode.webmbin0 -> 368 bytes
-rw-r--r--testing/testdata/force_new_cluster.webmbin0 -> 363 bytes
-rw-r--r--testing/testdata/invalid/README.libwebm24
-rw-r--r--testing/testdata/invalid/block_ends_beyond_cluster.mkvbin0 -> 10488 bytes
-rw-r--r--testing/testdata/invalid/blockgroup_block_ends_beyond_blockgroup.mkvbin0 -> 10949 bytes
-rw-r--r--testing/testdata/invalid/chapters_truncated_chapter_string.mkvbin0 -> 6993 bytes
-rw-r--r--testing/testdata/invalid/chapters_truncated_chapter_string_2.mkvbin0 -> 7536 bytes
-rw-r--r--testing/testdata/invalid/fixed_lacing_bad_lace_size.mkvbin0 -> 47806 bytes
-rw-r--r--testing/testdata/invalid/invalid_vp9_bitstream-bug_1416.webmbin0 -> 12847 bytes
-rw-r--r--testing/testdata/invalid/invalid_vp9_bitstream-bug_1417.webmbin0 -> 11448 bytes
-rw-r--r--testing/testdata/invalid/primarychromaticity_fieldtoolarge.webm1
-rw-r--r--testing/testdata/invalid/projection_float_overflow.webmbin0 -> 172 bytes
-rw-r--r--testing/testdata/long_tag_string.webmbin0 -> 474 bytes
-rw-r--r--testing/testdata/matroska_doctype.mkvbin0 -> 307 bytes
-rw-r--r--testing/testdata/max_cluster_duration.webmbin0 -> 395 bytes
-rw-r--r--testing/testdata/max_cluster_size.webmbin0 -> 368 bytes
-rw-r--r--testing/testdata/metadata_block.webmbin0 -> 314 bytes
-rw-r--r--testing/testdata/output_cues.webmbin0 -> 394 bytes
-rw-r--r--testing/testdata/projection.webmbin0 -> 317 bytes
-rw-r--r--testing/testdata/segment_duration.webmbin0 -> 341 bytes
-rw-r--r--testing/testdata/segment_info.webmbin0 -> 284 bytes
-rw-r--r--testing/testdata/set_cues_track_number.webmbin0 -> 412 bytes
-rw-r--r--testing/testdata/set_pixelwidth_pixelheight.webmbin0 -> 302 bytes
-rw-r--r--testing/testdata/set_segment_duration.webmbin0 -> 301 bytes
-rw-r--r--testing/testdata/simple_block.webmbin0 -> 301 bytes
-rw-r--r--testing/testdata/test_stereo_left_right.webmbin0 -> 11592 bytes
-rw-r--r--testing/testdata/tracks.webmbin0 -> 363 bytes
-rw-r--r--testing/testdata/webm_doctype.webmbin0 -> 285 bytes
-rw-r--r--testing/video_frame_tests.cc75
-rw-r--r--vttdemux.cc1004
-rw-r--r--vttreader.h15
-rw-r--r--webm_info.cc1329
-rw-r--r--webm_parser/README.md325
-rw-r--r--webm_parser/demo/demo.cc1161
-rw-r--r--webm_parser/doxygen.config319
-rw-r--r--webm_parser/fuzzing/corpus/00805c2543756a5fd85652d03bfbbd2eb6192ca51
-rw-r--r--webm_parser/fuzzing/corpus/00d120eb143bb02c48d7c863e5826d2ad1a6da4b1
-rw-r--r--webm_parser/fuzzing/corpus/018dee8285e9e20ca3996bb2dc0284b5c57ba75a1
-rw-r--r--webm_parser/fuzzing/corpus/020edb59637c1e6439f19aa3a5a9d50c3377dbe91
-rw-r--r--webm_parser/fuzzing/corpus/0247ce2b1a71752a3af11e1065ca90afa0df9d301
-rw-r--r--webm_parser/fuzzing/corpus/028b19f7d79f5da7a2af13a0c1e2d13f7eaf24cd1
-rw-r--r--webm_parser/fuzzing/corpus/029ab55b16df41881f8de2351205201da334550a1
-rw-r--r--webm_parser/fuzzing/corpus/029be5e90913b19cf5890559cf3f98aa909f0a84bin0 -> 5 bytes
-rw-r--r--webm_parser/fuzzing/corpus/02fb96539b84bbd12de84ff05cbc9dc3faa96b7ebin0 -> 58 bytes
-rw-r--r--webm_parser/fuzzing/corpus/0349f5632d21faa36b85520ad0b524d561f5e13fbin0 -> 35 bytes
-rw-r--r--webm_parser/fuzzing/corpus/036fc9daf7fb1b4274dd668cfd883248ebbad9671
-rw-r--r--webm_parser/fuzzing/corpus/037a4edc18e475ec81081e47277cbf51f1316680bin0 -> 20 bytes
-rw-r--r--webm_parser/fuzzing/corpus/0481dad9a7d0e6fab0c703bba9b3268db96c6793bin0 -> 37 bytes
-rw-r--r--webm_parser/fuzzing/corpus/04dc2407e7142f5618aa5105377925b0b0ed544bbin0 -> 137 bytes
-rw-r--r--webm_parser/fuzzing/corpus/04fa2f34ff4a4406d136e5aaba5debe7d8129a06bin0 -> 41 bytes
-rw-r--r--webm_parser/fuzzing/corpus/054fd0041ad81cfad0a85e3c59195485492a4493bin0 -> 158 bytes
-rw-r--r--webm_parser/fuzzing/corpus/056b83ab2457979ea021e7118ab847eba265df15bin0 -> 36 bytes
-rw-r--r--webm_parser/fuzzing/corpus/058326151c1d4a490964d495d35adcf15178030fbin0 -> 28 bytes
-rw-r--r--webm_parser/fuzzing/corpus/05847b5be0eb200d6a6b340c939c7a654b55003ebin0 -> 33 bytes
-rw-r--r--webm_parser/fuzzing/corpus/05b600ae9a9072ac2865247e69ed0736a0c58316bin0 -> 15 bytes
-rw-r--r--webm_parser/fuzzing/corpus/05cf976698b55df1fcd03bc07446bb9283dad9d71
-rw-r--r--webm_parser/fuzzing/corpus/05d4dfda5e264fffda243f2991a86e96141f579a1
-rw-r--r--webm_parser/fuzzing/corpus/061217fc0b6af0ec3851d9728e03ed9b30c702d51
-rw-r--r--webm_parser/fuzzing/corpus/077b53a7163e51c48e8cdaf4209bb837823ffba91
-rw-r--r--webm_parser/fuzzing/corpus/07c3ade9713892bb75db2d93b48ef40b262a54e61
-rw-r--r--webm_parser/fuzzing/corpus/07eaf3c7437032f60c905f6f8e3dfc193491b6021
-rw-r--r--webm_parser/fuzzing/corpus/07f67b922b503354b2aebcdcc4ab7b6d3150b048bin0 -> 63 bytes
-rw-r--r--webm_parser/fuzzing/corpus/08af8d91bb21835c50330e997d973cac8ff67766bin0 -> 60 bytes
-rw-r--r--webm_parser/fuzzing/corpus/0a18f05bb16402756202160225aec9c5a654f1aebin0 -> 192 bytes
-rw-r--r--webm_parser/fuzzing/corpus/0b60823983971ee17a2590678f0fc0762c21bb73bin0 -> 84 bytes
-rw-r--r--webm_parser/fuzzing/corpus/0bae7f0976af0f75974047b5f2cf4c96454120661
-rw-r--r--webm_parser/fuzzing/corpus/0c1862b4065eefab2535ecc6951295e38069a82ebin0 -> 79 bytes
-rw-r--r--webm_parser/fuzzing/corpus/0c2f51d5ffc69e69680bf3d6edb91d76c353ad141
-rw-r--r--webm_parser/fuzzing/corpus/0c3af72d69f18103383c9cd41a7f2676a9d28a40bin0 -> 20 bytes
-rw-r--r--webm_parser/fuzzing/corpus/0c43df7fc9d06187249187583c3c0825207012891
-rw-r--r--webm_parser/fuzzing/corpus/0cadf5ecf58a394560a1f6db72a2e21264445d131
-rw-r--r--webm_parser/fuzzing/corpus/0cd91a4e7bec912beb3b47a685b89d5be24d00461
-rw-r--r--webm_parser/fuzzing/corpus/0d0da60f91f9489af113d8484d9b6871622523d51
-rw-r--r--webm_parser/fuzzing/corpus/0d30a4f88e53de1ce4bf1ec724016f2f4c11bc9bbin0 -> 90 bytes
-rw-r--r--webm_parser/fuzzing/corpus/0d6b3b1d024e7aa73fbc58b157ab53df048a2f2cbin0 -> 27 bytes
-rw-r--r--webm_parser/fuzzing/corpus/0d94310cfd8a9acdbc7fb82ad9c73cdf3f64f926bin0 -> 281 bytes
-rw-r--r--webm_parser/fuzzing/corpus/0f5741483be8f4f6eddcb70ea418fb0c08da9443bin0 -> 20 bytes
-rw-r--r--webm_parser/fuzzing/corpus/109e125f729f84d69c3e3a614123547a094534ad1
-rw-r--r--webm_parser/fuzzing/corpus/10d257a4a314e20644fbc469cdebafe16c7f46a81
-rw-r--r--webm_parser/fuzzing/corpus/10d951f88995a2176878501a2633b9bb4822ff961
-rw-r--r--webm_parser/fuzzing/corpus/111b8f8318a269bbe0f7d9f1cebfdc1cb43c982b1
-rw-r--r--webm_parser/fuzzing/corpus/11c122ce1f7d993f809a4eb5db17c738f279a707bin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/11e288617056809208561f2a9112fd0664d378d21
-rw-r--r--webm_parser/fuzzing/corpus/124fde9cfe7157773d8febcbb0829914bf4d17d3bin0 -> 128 bytes
-rw-r--r--webm_parser/fuzzing/corpus/1386cc740c80ede6dfea5f3bb1d4fe1501ab5e911
-rw-r--r--webm_parser/fuzzing/corpus/13dd373ccb0c534402c6f7c4a0bdd723a26bc2c11
-rw-r--r--webm_parser/fuzzing/corpus/13fc3d2b32d789c84be6349bb585c308289b4c3e1
-rw-r--r--webm_parser/fuzzing/corpus/148357130d1e5ac4059ad2bb6c63d78e2523f7d2bin0 -> 45147 bytes
-rw-r--r--webm_parser/fuzzing/corpus/15366a3aafe2590c2e3183c088dde4cc100cb9561
-rw-r--r--webm_parser/fuzzing/corpus/15feeb939fa90b25f57622a0abd9155ca88ad08bbin0 -> 88 bytes
-rw-r--r--webm_parser/fuzzing/corpus/16282b78a2018eb78544316554a92fe1003859e31
-rw-r--r--webm_parser/fuzzing/corpus/1717628a6d6ea868febec5fb196edcf4b9eb284d1
-rw-r--r--webm_parser/fuzzing/corpus/17921b1e28600e7e3faf67fc68b397ecab4e2a521
-rw-r--r--webm_parser/fuzzing/corpus/17b8276355dd2368647b7756431820f5275cc3e2bin0 -> 79 bytes
-rw-r--r--webm_parser/fuzzing/corpus/17d0aece97973ab23a467486b177ea9722e1b90b1
-rw-r--r--webm_parser/fuzzing/corpus/17e80cf8c247d17acad56c88750da34893fcb4fdbin0 -> 19 bytes
-rw-r--r--webm_parser/fuzzing/corpus/18040e106688eb1d54323927a403439ecfde66261
-rw-r--r--webm_parser/fuzzing/corpus/1813261b3141faa01431c82e06292485516d33271
-rw-r--r--webm_parser/fuzzing/corpus/184ee2343d4b9f62c69383692829fd852ad8855d1
-rw-r--r--webm_parser/fuzzing/corpus/186035a2f5c09ccd6b7d15de46f8561c98d8897d1
-rw-r--r--webm_parser/fuzzing/corpus/186292900ad9d43881b71765158c32d78d80c8f3bin0 -> 84 bytes
-rw-r--r--webm_parser/fuzzing/corpus/18fd762b91406d37b85a7b342a91434a15f17290bin0 -> 25 bytes
-rw-r--r--webm_parser/fuzzing/corpus/19334eee05eeb18c549498e7ca2e792a2a4e04f31
-rw-r--r--webm_parser/fuzzing/corpus/1a9a5df35779dd6e9e1de171b9bb0d316d2b64a51
-rw-r--r--webm_parser/fuzzing/corpus/1ab3d30f60743c2a1d3043773aae3a04f83c07c01
-rw-r--r--webm_parser/fuzzing/corpus/1ad84ed46f3fda305150bac93958a5a390e23a671
-rw-r--r--webm_parser/fuzzing/corpus/1b40a997150aa03c23ecc6efe445a2d7c3dd8368bin0 -> 17 bytes
-rw-r--r--webm_parser/fuzzing/corpus/1b6b3bab9032cd420f350b6bb252942484f6b5271
-rw-r--r--webm_parser/fuzzing/corpus/1b7765cb05c94581461ffcd38d38b7b272d8e9d61
-rw-r--r--webm_parser/fuzzing/corpus/1b89e3fe0cbd4c2291a74bf21969a9d9d851cbf11
-rw-r--r--webm_parser/fuzzing/corpus/1b95d4da08fe949a60f63cd59213d42d30dbd3811
-rw-r--r--webm_parser/fuzzing/corpus/1bac1200e05bb3269d019903241791c917a12bd0bin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/1bb8f5f81b7f6c1d58e1f7bb13fa561fe1a146bc1
-rw-r--r--webm_parser/fuzzing/corpus/1c56068c6dd17e9a824db6da78d64f933267a8c01
-rw-r--r--webm_parser/fuzzing/corpus/1c8db8b9d88dd3483b6f81e4224c4f985046e6acbin0 -> 85 bytes
-rw-r--r--webm_parser/fuzzing/corpus/1d29a77924602a79c0f546535a885f59cbbbc4051
-rw-r--r--webm_parser/fuzzing/corpus/1d75a65733da627e5c401625bff522eadf0983151
-rw-r--r--webm_parser/fuzzing/corpus/1df9507cc2a54a369646d6d34d846d3fcc172479bin0 -> 45874 bytes
-rw-r--r--webm_parser/fuzzing/corpus/1e7a571be5aa542c3dfec1223d2e089a3075ce1f1
-rw-r--r--webm_parser/fuzzing/corpus/20a6b040258fbfa09bb37c6fc07106b2e43bada71
-rw-r--r--webm_parser/fuzzing/corpus/20e8ca854d3c0c375dc943142a04ee2260f0d1fdbin0 -> 26 bytes
-rw-r--r--webm_parser/fuzzing/corpus/212ee7d21a8cb25249644cab4f959db111f3a3c0bin0 -> 106 bytes
-rw-r--r--webm_parser/fuzzing/corpus/224ce7b415f7950118880fce0594734aef489fecbin0 -> 45 bytes
-rw-r--r--webm_parser/fuzzing/corpus/226e70c1beddace83862791a6555c80475640bd0bin0 -> 88 bytes
-rw-r--r--webm_parser/fuzzing/corpus/233d53e3eb21b6ea6feebd6e59e3ef888a840f49bin0 -> 21 bytes
-rw-r--r--webm_parser/fuzzing/corpus/237f7aee90f206d3a6b138cc908b8921b544e190bin0 -> 15 bytes
-rw-r--r--webm_parser/fuzzing/corpus/23e1ac0f77f1283cf6e9fa044df3ec51bff27fd41
-rw-r--r--webm_parser/fuzzing/corpus/23fcee6c71a8f5a22447df688724c0250fb77a70bin0 -> 20 bytes
-rw-r--r--webm_parser/fuzzing/corpus/2459eb855d8c6ebac13cb74d996565d78130f4dd1
-rw-r--r--webm_parser/fuzzing/corpus/259188e5fb0c09046df96f6d565c59e0d146f1981
-rw-r--r--webm_parser/fuzzing/corpus/26f07f8e28e7521ed282fe5c3938c1ae225816b91
-rw-r--r--webm_parser/fuzzing/corpus/276425d65d58453d03a3444c9f6662d08d449af71
-rw-r--r--webm_parser/fuzzing/corpus/27d62874ec87a2552e7c842da65de113aa69f7aabin0 -> 50585 bytes
-rw-r--r--webm_parser/fuzzing/corpus/281b259280ada5d07b07a22cbe9a78c7b0fba94b1
-rw-r--r--webm_parser/fuzzing/corpus/299ed12b98673c6c4adbcf886cb70db8adde6aa11
-rw-r--r--webm_parser/fuzzing/corpus/2a40feb7480d0b31c36d5626761e948d0ae52792bin0 -> 179 bytes
-rw-r--r--webm_parser/fuzzing/corpus/2a6d7e2b829ed28307b551eda0d96f1834bff8991
-rw-r--r--webm_parser/fuzzing/corpus/2a8dda90aa286175b5c683b57fae1dc7e6ac1e7ebin0 -> 31596 bytes
-rw-r--r--webm_parser/fuzzing/corpus/2a9588e6fab82016b545462cff2ed014c03455511
-rw-r--r--webm_parser/fuzzing/corpus/2b1d57a8e8fa7164c9ba00957c9486010754560c1
-rw-r--r--webm_parser/fuzzing/corpus/2b475d1a8f2fe4fbba92e1424f0ea96d95b12fb71
-rw-r--r--webm_parser/fuzzing/corpus/2bb80c6d0e2eadd73018eb2a8cae37d41602ee79bin0 -> 18 bytes
-rw-r--r--webm_parser/fuzzing/corpus/2c1389f882e256e260e37e8a67af7e32d0f4e0fc1
-rw-r--r--webm_parser/fuzzing/corpus/2c1f94c76e4eec607cce5311323620f349912e731
-rw-r--r--webm_parser/fuzzing/corpus/2c962c7fbceaacf8247ac9b70c8eeb1f814e435b1
-rw-r--r--webm_parser/fuzzing/corpus/2c9aaabacf3a3b48dec4a85767cc5d38a1736aff1
-rw-r--r--webm_parser/fuzzing/corpus/2cf086299f983d0afc7f95c5e0c86b657448af33bin0 -> 40 bytes
-rw-r--r--webm_parser/fuzzing/corpus/2d0d96b95e9a3316d1ff0ef019ada509bbde4c9dbin0 -> 41 bytes
-rw-r--r--webm_parser/fuzzing/corpus/2d414d5dfd20055393df3208009840da9cfabca9bin0 -> 24 bytes
-rw-r--r--webm_parser/fuzzing/corpus/2df967edcc00ac5c8e00037f1a43680600af5cbabin0 -> 166 bytes
-rw-r--r--webm_parser/fuzzing/corpus/2e6b09f1eca05ce2bba920fb78f9bca2a883fda01
-rw-r--r--webm_parser/fuzzing/corpus/2e8bde3549723e13849b604f4deedd51c71dfef5bin0 -> 113 bytes
-rw-r--r--webm_parser/fuzzing/corpus/2f6e92a71918d01c16762d5ca59b328f1341d3261
-rw-r--r--webm_parser/fuzzing/corpus/306e2cf9aebe012cb0769b1b2a6ea68af2e8ed441
-rw-r--r--webm_parser/fuzzing/corpus/30f7348d35de0c47d2044736cb115972b800569dbin0 -> 173 bytes
-rw-r--r--webm_parser/fuzzing/corpus/310798b8d94a3a2fe649a4c871458b4d74fdcf321
-rw-r--r--webm_parser/fuzzing/corpus/31ea606a9859bb29d7f98162d254348021e0d9321
-rw-r--r--webm_parser/fuzzing/corpus/3230fc31ab8d408c484aa28bbe36431f71101243bin0 -> 59 bytes
-rw-r--r--webm_parser/fuzzing/corpus/328b7c92996e480d1f11efe847c3be4b5dc0f8ebbin0 -> 207 bytes
-rw-r--r--webm_parser/fuzzing/corpus/32e58bb3d00863115f33707e0c8af722036676fdbin0 -> 50 bytes
-rw-r--r--webm_parser/fuzzing/corpus/32fc8547b6145c502d98e5e0c296ebd05badfdb91
-rw-r--r--webm_parser/fuzzing/corpus/334cddba889265a1263feb77a2afdc454ca54f5e1
-rw-r--r--webm_parser/fuzzing/corpus/33c9f9990890d2baf0c30d74b34b486082782413bin0 -> 150 bytes
-rw-r--r--webm_parser/fuzzing/corpus/341f3e8689e57eeab4234fcdd6d2b8f800b9ab341
-rw-r--r--webm_parser/fuzzing/corpus/3457e356eac79bf1c30b59940532d360dba2c1a91
-rw-r--r--webm_parser/fuzzing/corpus/351185a29bdbc7bf0d584479001fb47c32c61e5b1
-rw-r--r--webm_parser/fuzzing/corpus/35ae0d43c6bfc5f4b45f16877157832fddafce77bin0 -> 145 bytes
-rw-r--r--webm_parser/fuzzing/corpus/35b2f81c573b15304cb9b13f00008b460da789421
-rw-r--r--webm_parser/fuzzing/corpus/35d9256a4d7ef3ee54384616178af3a8092d09811
-rw-r--r--webm_parser/fuzzing/corpus/36352efd680a747f0f2c93d760559d69399a41021
-rw-r--r--webm_parser/fuzzing/corpus/3685ffbcfe28b166ced0a783b012a446f9cfea461
-rw-r--r--webm_parser/fuzzing/corpus/36def8b2661197d594756f116a750822ae1d65d51
-rw-r--r--webm_parser/fuzzing/corpus/371950308853fb3b1b3a940c0598bfdfeb1d0f9c1
-rw-r--r--webm_parser/fuzzing/corpus/3765e9174971dbac9cfeb7e485ca61dc4941f7a91
-rw-r--r--webm_parser/fuzzing/corpus/37cae036ce7ff5d0671f32d3757579f248609c9c1
-rw-r--r--webm_parser/fuzzing/corpus/38460ea06ee847ff3d5733415c299b44b0866e581
-rw-r--r--webm_parser/fuzzing/corpus/3865de2fec3f353513d86b28e43bf936d4707f971
-rw-r--r--webm_parser/fuzzing/corpus/3867b18060c307e0d04e0098f195d699e4b3294f1
-rw-r--r--webm_parser/fuzzing/corpus/391920720c3609b7c1f7b51162ce3ec99329a0b4bin0 -> 148 bytes
-rw-r--r--webm_parser/fuzzing/corpus/395027454c7d5babff9544414c04a39d712eed1c1
-rw-r--r--webm_parser/fuzzing/corpus/3a258c9ed48e634ed8d495f07caf21dbc6978205bin0 -> 19 bytes
-rw-r--r--webm_parser/fuzzing/corpus/3a397485197e6b99c77158132eaf211389615fe51
-rw-r--r--webm_parser/fuzzing/corpus/3a66b34706b686fe0fa8ca8f10c829758b2ebb071
-rw-r--r--webm_parser/fuzzing/corpus/3ba666ad891fa8009dfc961aa3c215bbfd2d81d2bin0 -> 16 bytes
-rw-r--r--webm_parser/fuzzing/corpus/3ba9769c7c23cc3c80542ad123a371cf4f69e858bin0 -> 41 bytes
-rw-r--r--webm_parser/fuzzing/corpus/3beb4432302433e303a9ccaf34637dd3a910028ebin0 -> 157 bytes
-rw-r--r--webm_parser/fuzzing/corpus/3c6cdebc450d20a58150a9c71335b45a6a5ee0e51
-rw-r--r--webm_parser/fuzzing/corpus/3c867362eb6a20f573a890db87749fce3c719d14bin0 -> 44 bytes
-rw-r--r--webm_parser/fuzzing/corpus/3cd69ddcd34bd874e3abade696f95c949c17198d1
-rw-r--r--webm_parser/fuzzing/corpus/3de495ebac4b80064b79844f3a9453270caec251bin0 -> 11 bytes
-rw-r--r--webm_parser/fuzzing/corpus/3e04542bcc462ded0025b53371f709d8fcd25c21bin0 -> 17 bytes
-rw-r--r--webm_parser/fuzzing/corpus/3e94196e55a9214083bda470772d71aedc17eb5fbin0 -> 60 bytes
-rw-r--r--webm_parser/fuzzing/corpus/3ec4ac00bb967a335b5caae0ae51488f2fda6583bin0 -> 9 bytes
-rw-r--r--webm_parser/fuzzing/corpus/3eec38c0ac3a96856d3210a0357857e70b07a9cb1
-rw-r--r--webm_parser/fuzzing/corpus/3ef82ba46238fa46b89148a230d23e741ba5f1da1
-rw-r--r--webm_parser/fuzzing/corpus/3fbd43f81888ca60e5bfe49b47e23cb115d5cd7d1
-rw-r--r--webm_parser/fuzzing/corpus/3fe56e5b06653e8a37c99da190ff8d44d14bfc061
-rw-r--r--webm_parser/fuzzing/corpus/3febffec3be8b1f4bcb76a01c1d713600d8e4adb1
-rw-r--r--webm_parser/fuzzing/corpus/43380e5a9dd29ca847e9b5bfba12a0a95d94d0dabin0 -> 30 bytes
-rw-r--r--webm_parser/fuzzing/corpus/435d3ff6ddc43ac582e740ead969397fd2bfc7f5bin0 -> 24 bytes
-rw-r--r--webm_parser/fuzzing/corpus/448120a37132488f48ab6c46a146c995948958cc1
-rw-r--r--webm_parser/fuzzing/corpus/46db501d3e9a8808118333740a22bf9c808af239bin0 -> 13 bytes
-rw-r--r--webm_parser/fuzzing/corpus/47a2ca2b47aa1993cb655b77a0273ff139dd2280bin0 -> 42 bytes
-rw-r--r--webm_parser/fuzzing/corpus/490576c8f6c235e1ed921c4932bac028070c806a1
-rw-r--r--webm_parser/fuzzing/corpus/4930b86bf0dfb02836b0319d21aa4a63a8dd894d1
-rw-r--r--webm_parser/fuzzing/corpus/49667ff0b09901223117e74d2506bc37a5ae15801
-rw-r--r--webm_parser/fuzzing/corpus/49906adf7313330e29be20d51fb36e9bdf1939c3bin0 -> 41 bytes
-rw-r--r--webm_parser/fuzzing/corpus/49c0e3eed81c1827e755c70c9c6d9606e7f54b22bin0 -> 26 bytes
-rw-r--r--webm_parser/fuzzing/corpus/4a3e77f5bc061fc8121ef7ebd2b8e3ebb0bdd6091
-rw-r--r--webm_parser/fuzzing/corpus/4a454e01ef09b175f3864b654cfc1104a850e871bin0 -> 192 bytes
-rw-r--r--webm_parser/fuzzing/corpus/4acd9df9a7ee34e48c6c80777159b089f417a69abin0 -> 17 bytes
-rw-r--r--webm_parser/fuzzing/corpus/4af5564129d7b4c9c4736a3e511926c29348a4a41
-rw-r--r--webm_parser/fuzzing/corpus/4b3957bcbe5975399e34485cf0e5229169ab67b91
-rw-r--r--webm_parser/fuzzing/corpus/4c9e40aa8a439a500f073cf5a1e060a16ad12a6fbin0 -> 80 bytes
-rw-r--r--webm_parser/fuzzing/corpus/4cb5ff0e26f5bd0bb1fca846b4843db69095f18b1
-rw-r--r--webm_parser/fuzzing/corpus/4ce80549344cf5b0432e39e7b5382407f0763e8c1
-rw-r--r--webm_parser/fuzzing/corpus/4d10ae0032f04fa01148a54fae6838c44e4bb5bd1
-rw-r--r--webm_parser/fuzzing/corpus/4db2ded939a16fb1907dc2bad7acf67a64e38c0c1
-rw-r--r--webm_parser/fuzzing/corpus/4dc3a9a9477fb452ba7a4026bbc01d4cdd3d4990bin0 -> 18 bytes
-rw-r--r--webm_parser/fuzzing/corpus/4e1fb006a2268fa5fc70ca6b241bbc71faa6d7ff1
-rw-r--r--webm_parser/fuzzing/corpus/4e871bbc5089f9550533641ddf6850f8722dfc36bin0 -> 86 bytes
-rw-r--r--webm_parser/fuzzing/corpus/4eeb4507868732d3705d1ab10f6a31f70f5795561
-rw-r--r--webm_parser/fuzzing/corpus/4f21f3584c99e97453ebe513ed44c86530fbebba1
-rw-r--r--webm_parser/fuzzing/corpus/5103160695cd56d281faa45d9267013488cc7bde1
-rw-r--r--webm_parser/fuzzing/corpus/521103054e395afd0f9612b47c71cfa0e62151b9bin0 -> 163 bytes
-rw-r--r--webm_parser/fuzzing/corpus/523511e33c8788ebb1b703d7c5ad12b5da03964ebin0 -> 28 bytes
-rw-r--r--webm_parser/fuzzing/corpus/525aedb7462311393cd01939e4734d01db48535b1
-rw-r--r--webm_parser/fuzzing/corpus/53809012a8d5cfee839c3d0961b7fdc9468329a71
-rw-r--r--webm_parser/fuzzing/corpus/53c01d820d125e57b28410ca5b3a9c72c18d41021
-rw-r--r--webm_parser/fuzzing/corpus/53c27071f53df6b8d2e5c419edf95b7792b278e71
-rw-r--r--webm_parser/fuzzing/corpus/53d5307f2e7852b183839fe93c20647004d151ecbin0 -> 114 bytes
-rw-r--r--webm_parser/fuzzing/corpus/5466c59c9c0eff34800e19cc92faf07469865ea91
-rw-r--r--webm_parser/fuzzing/corpus/54bf8d6f1f846243b6f4de81605779b0acb784061
-rw-r--r--webm_parser/fuzzing/corpus/54fd1711209fb1c0781092374132c66e79e2241b1
-rw-r--r--webm_parser/fuzzing/corpus/5506797514c0b214f5faa9764a9e149cd1df9d281
-rw-r--r--webm_parser/fuzzing/corpus/5566c6d599866f5e523c54e6575d7b9e557a2539bin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/55c3d55780427eb00bde252348db1c2ede22526c1
-rw-r--r--webm_parser/fuzzing/corpus/5600abaefe804c50a353bbfdd0f9e10ffc102f811
-rw-r--r--webm_parser/fuzzing/corpus/5611ddd40488973ee6c64c45ad57d6eefe6da1001
-rw-r--r--webm_parser/fuzzing/corpus/56b0ddd4e44492b45ebb7e9438b0a5556c244bfb1
-rw-r--r--webm_parser/fuzzing/corpus/5729395b452092152b06fbb8976e9bf1fda06439bin0 -> 207 bytes
-rw-r--r--webm_parser/fuzzing/corpus/57cce6fac7c0992eb338e0e538d3e0ee3fe7f1cd1
-rw-r--r--webm_parser/fuzzing/corpus/5822672f068da89546838004ae59552e12e132f7bin0 -> 20 bytes
-rw-r--r--webm_parser/fuzzing/corpus/59ce13e9a22e88dac879841256ff66f02cd2f8cc1
-rw-r--r--webm_parser/fuzzing/corpus/5a020527fc80b0de64e279fc44635687e014a9cfbin0 -> 122 bytes
-rw-r--r--webm_parser/fuzzing/corpus/5a645b30948850f8efb7bdd11944bdf3112c7bfe1
-rw-r--r--webm_parser/fuzzing/corpus/5b2f727461cde3c02ffdfb300adb0b4a097349111
-rw-r--r--webm_parser/fuzzing/corpus/5b5531d859ab5bbb034345bbaa2852e9d8a1e1ef1
-rw-r--r--webm_parser/fuzzing/corpus/5b980cb94e35769ec92a140ca00d34977e7da5ecbin0 -> 10 bytes
-rw-r--r--webm_parser/fuzzing/corpus/5bfec2fe795176646fa0babecf97c04045bd4814bin0 -> 348 bytes
-rw-r--r--webm_parser/fuzzing/corpus/5c28d94242e9a5ba7e7324e59e5dd12f3e31e51cbin0 -> 63 bytes
-rw-r--r--webm_parser/fuzzing/corpus/5c67728ab04ba3dfe4b2636cf609f9617c4a59741
-rw-r--r--webm_parser/fuzzing/corpus/5cd36053e269c97f5359b0a075d80bf1e23b5f471
-rw-r--r--webm_parser/fuzzing/corpus/5d6f99c2afbf68a1b15a17e07945c11db8579e5fbin0 -> 88 bytes
-rw-r--r--webm_parser/fuzzing/corpus/5dd149b1b8509f456c16c4ac6dac3c7d6aa217edbin0 -> 99 bytes
-rw-r--r--webm_parser/fuzzing/corpus/5e84011c6b092e9f0d6ba60f12fc0fae95a622cabin0 -> 56 bytes
-rw-r--r--webm_parser/fuzzing/corpus/5f56766993de2b8c2db87116090af546e1add3b01
-rw-r--r--webm_parser/fuzzing/corpus/60571c8be9bee3b13428613df66ff36cf54309ec1
-rw-r--r--webm_parser/fuzzing/corpus/60ac245f262ee1ff177d351a4fe246060fb4538bbin0 -> 86 bytes
-rw-r--r--webm_parser/fuzzing/corpus/6120aafb0db1715400e47d0a61fb9f6d8665a7361
-rw-r--r--webm_parser/fuzzing/corpus/61953a8f7a9f0613020d297a6d350abeeaf6e58c1
-rw-r--r--webm_parser/fuzzing/corpus/61c0a7fef46c0c1b081b1c2b40835805e5989b541
-rw-r--r--webm_parser/fuzzing/corpus/620d85870206e88db0bc3ee978c24c0bbc1821babin0 -> 60 bytes
-rw-r--r--webm_parser/fuzzing/corpus/621131d8e28a67a6b4fb03c611f4375873d696781
-rw-r--r--webm_parser/fuzzing/corpus/624cbd42e3ebf35a3a0ba55b3b4969b99054f84dbin0 -> 348 bytes
-rw-r--r--webm_parser/fuzzing/corpus/6290103200631075988aa3566d8d8922599f8b6d1
-rw-r--r--webm_parser/fuzzing/corpus/62a329f65143f1571dc4a2fbb256800c8ce4d89fbin0 -> 52 bytes
-rw-r--r--webm_parser/fuzzing/corpus/62f8a53cfb4dd625dcb31ee2ba258db3916a3bd01
-rw-r--r--webm_parser/fuzzing/corpus/6318dc574b3ad7291fee7e01bfeaa674eb0feb481
-rw-r--r--webm_parser/fuzzing/corpus/6457a079debb9b532255e3e6ddf276b29808b49dbin0 -> 3 bytes
-rw-r--r--webm_parser/fuzzing/corpus/646061a4526506a0b3c62629f4f77b6072c8f9c31
-rw-r--r--webm_parser/fuzzing/corpus/6538d0517865c8832ee9bec87609b7bd4facb0251
-rw-r--r--webm_parser/fuzzing/corpus/653de28e2f085334e30ec1c964540b432a3cfbc61
-rw-r--r--webm_parser/fuzzing/corpus/657c9c42bf3e1c79a6dcb9071bf123367dc37734bin0 -> 40 bytes
-rw-r--r--webm_parser/fuzzing/corpus/65ebba0df08a5484686905e99631a4ead845c08b1
-rw-r--r--webm_parser/fuzzing/corpus/66dd27c22fe09842ac4c4f51fdef4bea500b6a701
-rw-r--r--webm_parser/fuzzing/corpus/66e25df2bd43c840337a49e0cb5b386e9d290d80bin0 -> 59 bytes
-rw-r--r--webm_parser/fuzzing/corpus/6714df1ac1c54d4c30add212454e9513c68807671
-rw-r--r--webm_parser/fuzzing/corpus/68a6f9c2f62e9f57667de9563b5d612ec2f2c829bin0 -> 37 bytes
-rw-r--r--webm_parser/fuzzing/corpus/69089b76cc62fe00615702bdd1f339e259194d421
-rw-r--r--webm_parser/fuzzing/corpus/69d8622d24c7315f344c424297986170f7ef22431
-rw-r--r--webm_parser/fuzzing/corpus/6a700d958cb3a7565d5a6b74540486874d52b4c71
-rw-r--r--webm_parser/fuzzing/corpus/6a852150c5c2a12d325e78056687a8ea97b25fe71
-rw-r--r--webm_parser/fuzzing/corpus/6ac8c9f754d5ff3c0cd3f3f585de99dd73944bbebin0 -> 46 bytes
-rw-r--r--webm_parser/fuzzing/corpus/6ace27a48f12990e2169cc099007aa5fa11962d21
-rw-r--r--webm_parser/fuzzing/corpus/6b10397dc3bff2ccf57be4fe3ff9d8d1acb8c76ebin0 -> 156 bytes
-rw-r--r--webm_parser/fuzzing/corpus/6c3f1d96c06b853551f685dde821142928f55acfbin0 -> 25 bytes
-rw-r--r--webm_parser/fuzzing/corpus/6c8de4d4bbb5294154c3b140d43836757b9f08ac1
-rw-r--r--webm_parser/fuzzing/corpus/6cf122ee328af5cf6f18d5f9546b0fe761951b8dbin0 -> 128 bytes
-rw-r--r--webm_parser/fuzzing/corpus/6cfbbc25caa217e22fd7ca3e92d2051c5f661bdb1
-rw-r--r--webm_parser/fuzzing/corpus/6d703bc900e74d1a576bebcccb63e3b2701ae86d1
-rw-r--r--webm_parser/fuzzing/corpus/6e1afa81dfcd6833705d84b45881f085c19b22111
-rw-r--r--webm_parser/fuzzing/corpus/6e244ec3b81e0d2f8b5810854859e1b8140422d91
-rw-r--r--webm_parser/fuzzing/corpus/6e323b4c733df90d55c754e099fb5a13bd9701cb1
-rw-r--r--webm_parser/fuzzing/corpus/6e36720f3f55dc0cd5d585c6770572964719b5b8bin0 -> 56 bytes
-rw-r--r--webm_parser/fuzzing/corpus/6e8384ab115ac92bb7787121cfb03582e2d725851
-rw-r--r--webm_parser/fuzzing/corpus/6ebf8b74dca1657f5f7ffadeccd629984473dcb4bin0 -> 19 bytes
-rw-r--r--webm_parser/fuzzing/corpus/6ec18dcca0d820fff04c03c3a1bce37dd8ada41d1
-rw-r--r--webm_parser/fuzzing/corpus/6eceebb8f46545393d217ab0111ff7dd29bc99f71
-rw-r--r--webm_parser/fuzzing/corpus/6ee071f654fd6b7b25725a8bdd880bb6836e8c81bin0 -> 80 bytes
-rw-r--r--webm_parser/fuzzing/corpus/6f1088b3d1f4cdfbc37d9b8cfc1ff4c3bde2f2051
-rw-r--r--webm_parser/fuzzing/corpus/6f79e36e880664c9b3b610140d2485fb6a6e50391
-rw-r--r--webm_parser/fuzzing/corpus/6fbea59066b2b97fa45259aa87c40c43100700191
-rw-r--r--webm_parser/fuzzing/corpus/7056b9c294f0cff7b4ace611a19363b2fc096bb31
-rw-r--r--webm_parser/fuzzing/corpus/7073dca40b911fb22f738c99aff43a10ae6c4db5bin0 -> 22 bytes
-rw-r--r--webm_parser/fuzzing/corpus/7164d8ca3f4c3d621cc5eed7b59e3495e7b017a0bin0 -> 18 bytes
-rw-r--r--webm_parser/fuzzing/corpus/71e3e01b3cfcd4228402cbf19b5282c965f042df1
-rw-r--r--webm_parser/fuzzing/corpus/720d36ea7f9616b1a1d47fa5b746e8657b5f973fbin0 -> 188 bytes
-rw-r--r--webm_parser/fuzzing/corpus/72ccbcbc53c7fa54247a81bd1d0e62dd0779494b1
-rw-r--r--webm_parser/fuzzing/corpus/736c0460f94c089c9e270857a4222a21ffd9e13fbin0 -> 5 bytes
-rw-r--r--webm_parser/fuzzing/corpus/745e32f46833c075c8e68552e0fbbcdd5ec52641bin0 -> 15 bytes
-rw-r--r--webm_parser/fuzzing/corpus/74aa7256477af2c0e2511df16376dee323f3ccb6bin0 -> 69 bytes
-rw-r--r--webm_parser/fuzzing/corpus/7528086eea404dccc3ee5b214befbc2a95676feb1
-rw-r--r--webm_parser/fuzzing/corpus/7528f9f08f80c8e85951aa1db2dc616e992c1a62bin0 -> 26 bytes
-rw-r--r--webm_parser/fuzzing/corpus/75af94f9695c375e5eb620849f0213ad41e8f205bin0 -> 39 bytes
-rw-r--r--webm_parser/fuzzing/corpus/75e1ff2e016b7b80ecb7a6a1f4c3238a2a2ed130bin0 -> 18 bytes
-rw-r--r--webm_parser/fuzzing/corpus/76129c3bdedafb3bda93f53266bd17bffdc30682bin0 -> 90 bytes
-rw-r--r--webm_parser/fuzzing/corpus/7670db89e0c3fe492c91458de219c3be34a3dcb7bin0 -> 118 bytes
-rw-r--r--webm_parser/fuzzing/corpus/7673d7fdf630637c6ae2b96694a047044c1a9cb0bin0 -> 20 bytes
-rw-r--r--webm_parser/fuzzing/corpus/76903905e654ddc59acf57380bf3dc1ea136bad91
-rw-r--r--webm_parser/fuzzing/corpus/76d36e3cf3d31e0f76af08bd7ba5571f679057a11
-rw-r--r--webm_parser/fuzzing/corpus/775ce646f7dbf50199b8e8df85c9441b8a0a5447bin0 -> 59 bytes
-rw-r--r--webm_parser/fuzzing/corpus/7770a6fd33ce821e60c78b0cb67a7e8c8bb742691
-rw-r--r--webm_parser/fuzzing/corpus/781b24e3433ba400df3ccddf56cb8fc7b1fd52ce1
-rw-r--r--webm_parser/fuzzing/corpus/784db9d87b31ffb040ad212e4018b30c3535ad18bin0 -> 121 bytes
-rw-r--r--webm_parser/fuzzing/corpus/7880e2ae55d255065ad415c4c625e1b63bfd2a931
-rw-r--r--webm_parser/fuzzing/corpus/78cb3d726e4f9e8bc89b399fa514c3b600bf8e5abin0 -> 185 bytes
-rw-r--r--webm_parser/fuzzing/corpus/79ba60931988a5974328a73fe091bdf6f5992891bin0 -> 24 bytes
-rw-r--r--webm_parser/fuzzing/corpus/79cdb8dec1ad07b389f544a511f89b347429837ebin0 -> 52 bytes
-rw-r--r--webm_parser/fuzzing/corpus/7ab8c6a65c39907f4afa4e2b294303aebb6b22d81
-rw-r--r--webm_parser/fuzzing/corpus/7b12a398a1860ac2f3930d5020e422aec061f1771
-rw-r--r--webm_parser/fuzzing/corpus/7bf1682743405f3d5b3433830eadae4ea311807d1
-rw-r--r--webm_parser/fuzzing/corpus/7c7ef0e305f37f833708b375271d59300d120d841
-rw-r--r--webm_parser/fuzzing/corpus/7d06bd4a7de57953ef09c2e18ca67aa7ff367c761
-rw-r--r--webm_parser/fuzzing/corpus/7d31bd53bb7eb7b1f354c5f85d376390760ab16fbin0 -> 32 bytes
-rw-r--r--webm_parser/fuzzing/corpus/7d67ef4d0c681655d4b4e93e848d56865630390bbin0 -> 17 bytes
-rw-r--r--webm_parser/fuzzing/corpus/7db9fe869081fdc855913dd000de0d493f5d5192bin0 -> 33 bytes
-rw-r--r--webm_parser/fuzzing/corpus/7f20c1e73451c3321c30223db91b891753ef77dd1
-rw-r--r--webm_parser/fuzzing/corpus/7fd4d999983c3fbf22763853e90ec10b03c70b171
-rw-r--r--webm_parser/fuzzing/corpus/80039ce7eff40359bee041d75e371b55117aad3a1
-rw-r--r--webm_parser/fuzzing/corpus/8104a007bc88a0a8d81ce1fd26d8b2333061e9201
-rw-r--r--webm_parser/fuzzing/corpus/81320c48ed6eea94712c5e8594c0799fbfe30d10bin0 -> 80 bytes
-rw-r--r--webm_parser/fuzzing/corpus/81425ca3d0afb88cc10d412dcc9795905eacd6d91
-rw-r--r--webm_parser/fuzzing/corpus/819b4bd08ae7d758990aea8ab9739f3cb97fd42cbin0 -> 156 bytes
-rw-r--r--webm_parser/fuzzing/corpus/8206d92b710c04ce0bd706cec25fbb72014c79bfbin0 -> 64 bytes
-rw-r--r--webm_parser/fuzzing/corpus/822926c528d16c0a9ca4c48a25651f6a0730d7971
-rw-r--r--webm_parser/fuzzing/corpus/82d80b6051f0750777bdc37319851741144b36711
-rw-r--r--webm_parser/fuzzing/corpus/830c73748baaa10c2016d04b408a7f481882cd89bin0 -> 88 bytes
-rw-r--r--webm_parser/fuzzing/corpus/8315ca46618bd0771353336407396df293e8c1821
-rw-r--r--webm_parser/fuzzing/corpus/831c152337e8b2993b8cddfde7553a8c9e64631fbin0 -> 67 bytes
-rw-r--r--webm_parser/fuzzing/corpus/832fad0e723027e5bd74726b1722a838183dada71
-rw-r--r--webm_parser/fuzzing/corpus/83b78d2ad8afd0729de45b4d9fdd765a949d80731
-rw-r--r--webm_parser/fuzzing/corpus/84a04bf3e15345f5c992bc6732a42b95857631a71
-rw-r--r--webm_parser/fuzzing/corpus/84d3a72c434074ee0d810fb2357047efd23f58f6bin0 -> 126 bytes
-rw-r--r--webm_parser/fuzzing/corpus/8659fe309298ca90f201ac7ced95b9d0de510eeb1
-rw-r--r--webm_parser/fuzzing/corpus/866cf21064b9e20eded44e2e096fd9ce6185ce86bin0 -> 8 bytes
-rw-r--r--webm_parser/fuzzing/corpus/8864c6c9319ef120d3accb7b70c32c8f5c1ebccbbin0 -> 10 bytes
-rw-r--r--webm_parser/fuzzing/corpus/88f01cffe9972bf447a21034c45f6b943ea3ec07bin0 -> 36 bytes
-rw-r--r--webm_parser/fuzzing/corpus/88f15d430469d209b72cd098405efd15a6d18b05bin0 -> 120 bytes
-rw-r--r--webm_parser/fuzzing/corpus/8936b35f29084151fa3755d81030386940523c071
-rw-r--r--webm_parser/fuzzing/corpus/89a42d72ffe0b23309a9da4e15b8854c1cf939ea1
-rw-r--r--webm_parser/fuzzing/corpus/8a0cf07d2592231bd579e4399538d9f490e3e5b91
-rw-r--r--webm_parser/fuzzing/corpus/8a5d1c05f894414aa5aaa66cf901c52dfcb119a11
-rw-r--r--webm_parser/fuzzing/corpus/8aa514b6e6fef8d045aa049fdb52fa8adcf722d4bin0 -> 25 bytes
-rw-r--r--webm_parser/fuzzing/corpus/8b0dcd4400fe19cf6a268af780990f47dba081891
-rw-r--r--webm_parser/fuzzing/corpus/8c4655e6528071edd445715ff1559f399dc9d47cbin0 -> 176 bytes
-rw-r--r--webm_parser/fuzzing/corpus/8c695cbcc43e37ce25194c4b61b3d87488b308a11
-rw-r--r--webm_parser/fuzzing/corpus/8c9dc3c5a839f90f29256ef8c8f1369bc73838171
-rw-r--r--webm_parser/fuzzing/corpus/8cdcf83eb89d7e5c87cfa1b8811c95f738747a6a1
-rw-r--r--webm_parser/fuzzing/corpus/8d0c6e6b3d74233685c3a2c1793d1a7b0ae2e34dbin0 -> 18 bytes
-rw-r--r--webm_parser/fuzzing/corpus/8d444e8b311d158f854a03ca35d2c7865cd2c46b1
-rw-r--r--webm_parser/fuzzing/corpus/8dc289730cc03ecbbed57d04cdca538a4fd080591
-rw-r--r--webm_parser/fuzzing/corpus/8dc58cfcb396a60db921901f1b0807c4f4f376191
-rw-r--r--webm_parser/fuzzing/corpus/8e05be75b7c14c463af277559dc8ea7841a733c81
-rw-r--r--webm_parser/fuzzing/corpus/8e7eef00a7a6719e77544b57d1d98ff83a37e55a1
-rw-r--r--webm_parser/fuzzing/corpus/8ed6ef9af5555ce4ff12b23139add9621e7ad930bin0 -> 92 bytes
-rw-r--r--webm_parser/fuzzing/corpus/8f39f83b71d49805a451f81a5a451f5f3d74927dbin0 -> 63 bytes
-rw-r--r--webm_parser/fuzzing/corpus/8f403efbef079f1775b63bcf1061983e4c5d21621
-rw-r--r--webm_parser/fuzzing/corpus/8f8f956eaaf8f7f5e65002be888b946218f0f7ce1
-rw-r--r--webm_parser/fuzzing/corpus/90a03241b5cc656b7c2a4ef55664b0320fa3bd431
-rw-r--r--webm_parser/fuzzing/corpus/925a91e7c25a43d9da8b63eba51ed66a52d833aabin0 -> 42 bytes
-rw-r--r--webm_parser/fuzzing/corpus/92de5c217802ec24886240188dca293325eb17c91
-rw-r--r--webm_parser/fuzzing/corpus/939da66e1fc8bb9854865066ee47b6ac4b933aa9bin0 -> 107 bytes
-rw-r--r--webm_parser/fuzzing/corpus/93a91331e7c3c060f461ae1f22e2bbf4747ffe33bin0 -> 64 bytes
-rw-r--r--webm_parser/fuzzing/corpus/94f6dfc499cbf2e84ffe1d69cb751ada9f2f2e3a1
-rw-r--r--webm_parser/fuzzing/corpus/9539fef8ffb9f7c542061f1af1f3f98e9a714cc3bin0 -> 1132 bytes
-rw-r--r--webm_parser/fuzzing/corpus/953c9a616ea56067225c88fccc6423496f2501a6bin0 -> 39 bytes
-rw-r--r--webm_parser/fuzzing/corpus/95a60268c555a77c1010d75bb44142af476354861
-rw-r--r--webm_parser/fuzzing/corpus/95e410025b965cf6ab2e8e2d3559efbe71c1a972bin0 -> 44 bytes
-rw-r--r--webm_parser/fuzzing/corpus/9671f253d1eb7cd504a5617058ce4c01a80c897ebin0 -> 15 bytes
-rw-r--r--webm_parser/fuzzing/corpus/9698d3424f1581a6bbb66c764f14c95a74b87675bin0 -> 64 bytes
-rw-r--r--webm_parser/fuzzing/corpus/96f7f38de1f8601311733984cf51cec35500dabe1
-rw-r--r--webm_parser/fuzzing/corpus/970ab6333aa5ccf8c2dc14bb56814f7bb4303b94bin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/98978113d2116dc4bdbb10265fa32bd95230bdb61
-rw-r--r--webm_parser/fuzzing/corpus/989e2872d2a34de543f23c5061db68212d8258f7bin0 -> 21 bytes
-rw-r--r--webm_parser/fuzzing/corpus/98ca9a00ad571c4454fce709d5405e5aca2a363c1
-rw-r--r--webm_parser/fuzzing/corpus/9965361765a4151902ec04fb21d9247b3a5ed10d1
-rw-r--r--webm_parser/fuzzing/corpus/99d6fe94a50faa50db9d7eb38d74bc3cc8417dc1bin0 -> 39 bytes
-rw-r--r--webm_parser/fuzzing/corpus/9a6869cec3dc84f2051bfaf0ee0d3552aa221f89bin0 -> 218 bytes
-rw-r--r--webm_parser/fuzzing/corpus/9cc1f26de1e3a7df8c7c03b95ff73ce9709c85f1bin0 -> 18 bytes
-rw-r--r--webm_parser/fuzzing/corpus/9cc5b552abbd551485135fa87eab739a0a784057bin0 -> 26 bytes
-rw-r--r--webm_parser/fuzzing/corpus/9cca361865a4fbff75abdbb79c1e91706780576c1
-rw-r--r--webm_parser/fuzzing/corpus/9d8e99f07604d6cb05ef613d41cbfb93b2aff7871
-rw-r--r--webm_parser/fuzzing/corpus/9ecd61eaf2681a882473247a603b9f30c2663d491
-rw-r--r--webm_parser/fuzzing/corpus/9f0a3b7c0814b4f80c0745161c8769f63098981bbin0 -> 19 bytes
-rw-r--r--webm_parser/fuzzing/corpus/9f711c29ccf3f54d44000d7ef6299585674be288bin0 -> 188 bytes
-rw-r--r--webm_parser/fuzzing/corpus/9fae60819c28d4fcc88a6a1b93dcf69b4e458203bin0 -> 6 bytes
-rw-r--r--webm_parser/fuzzing/corpus/a02431cf7c501a5b368c91e41283419d8fa9fb03bin0 -> 2 bytes
-rw-r--r--webm_parser/fuzzing/corpus/a0ac6c3c83817637bbbcb11a5106c57aa6654afbbin0 -> 12 bytes
-rw-r--r--webm_parser/fuzzing/corpus/a15fdfa620d19a92d9eaa9f3f13010e53f9027961
-rw-r--r--webm_parser/fuzzing/corpus/a18e76ae792a054c2f6d0d01e0e78d58678b35e31
-rw-r--r--webm_parser/fuzzing/corpus/a19d04f18f574e561d793ac0dfcffe2b38183eb11
-rw-r--r--webm_parser/fuzzing/corpus/a26fb85be3d2bb8a2360bb4d9533a1651bd12d99bin0 -> 50 bytes
-rw-r--r--webm_parser/fuzzing/corpus/a2e860fae30005a9e75b61f54f3d019c44090fcc1
-rw-r--r--webm_parser/fuzzing/corpus/a3071bfcb7b2fd3c4286ea42e1f7940754b55697bin0 -> 56 bytes
-rw-r--r--webm_parser/fuzzing/corpus/a4ac408fb9d6def070ad3a76312ca092863048e51
-rw-r--r--webm_parser/fuzzing/corpus/a4fafc117cbfde8c240deccc8997c7966d9109ba1
-rw-r--r--webm_parser/fuzzing/corpus/a59ffb5f6122e45136352585d3b53294a71346d01
-rw-r--r--webm_parser/fuzzing/corpus/a5b3a3c48727c26dfd625f247069d2cdbfa031f0bin0 -> 52 bytes
-rw-r--r--webm_parser/fuzzing/corpus/a5c829fbcd9fd760bc55bc8ab6901b8d401b65f61
-rw-r--r--webm_parser/fuzzing/corpus/a5fbbce038cad4f5e0c0f97fa69ebc3601123e5cbin0 -> 40 bytes
-rw-r--r--webm_parser/fuzzing/corpus/a6e57b33e7a219168280e51bc98a44de40f0f9ca1
-rw-r--r--webm_parser/fuzzing/corpus/a78afe9e4e9f02a10ebadac64171ad49749a69651
-rw-r--r--webm_parser/fuzzing/corpus/a823a019c0b19c97a1d35722cd843109ab016c1722
-rw-r--r--webm_parser/fuzzing/corpus/a8468104c65a6002fd3a9d4ac39f3ee34c21ce4a1
-rw-r--r--webm_parser/fuzzing/corpus/a899424027f1d69a05384355858311a6fa3940a31
-rw-r--r--webm_parser/fuzzing/corpus/a8a5de5a86a16952aacdf602120f27807294c3ec1
-rw-r--r--webm_parser/fuzzing/corpus/aa65229e62d7cb8048d2f5911226b177e8e53cab1
-rw-r--r--webm_parser/fuzzing/corpus/aaa96713f8ccb0bbd5c8e91715d4b86e7b338676bin0 -> 36 bytes
-rw-r--r--webm_parser/fuzzing/corpus/aae6354e5ba12ee3ad89fabfd72f5368a639d30fbin0 -> 43 bytes
-rw-r--r--webm_parser/fuzzing/corpus/ab6f3dc497f93f251ebacc153409b1eb8e05e2db1
-rw-r--r--webm_parser/fuzzing/corpus/abf5b18d1c1155d3e455c8b781948498f364965ebin0 -> 27 bytes
-rw-r--r--webm_parser/fuzzing/corpus/abfe5dbf594a2f22173fae7ca8de28b4e3a400991
-rw-r--r--webm_parser/fuzzing/corpus/ac842f2cc55d7f193273a35f8af3521bfe2317d0bin0 -> 63 bytes
-rw-r--r--webm_parser/fuzzing/corpus/ac923c36dd85ff5cb2a0c5b29c701999c5f2eaa0bin0 -> 40 bytes
-rw-r--r--webm_parser/fuzzing/corpus/acc6e100b519d6408a8c6d8aed19203d874a187abin0 -> 59 bytes
-rw-r--r--webm_parser/fuzzing/corpus/aceb250195867e7d9bbf6eb6e0055251e4bb5d67bin0 -> 86 bytes
-rw-r--r--webm_parser/fuzzing/corpus/acf1dd17e6a2848dfe17fb0d76cef2ce7d59ebdd1
-rw-r--r--webm_parser/fuzzing/corpus/ad94c2bda2ae4afa0f7264070fd642bf37aae596bin0 -> 91 bytes
-rw-r--r--webm_parser/fuzzing/corpus/adad2ca7ab313add6e955f704719e03d5229e4d01
-rw-r--r--webm_parser/fuzzing/corpus/adb1f8cd3b68c076d81311071aee2a1c3785c7cb1
-rw-r--r--webm_parser/fuzzing/corpus/ade30c327d9f51daf37cb3e39fdb15125b84a2571
-rw-r--r--webm_parser/fuzzing/corpus/ae783661f52d36f9d6b87c8394ee6f2d61dae6401
-rw-r--r--webm_parser/fuzzing/corpus/ae8da06c3c69076c06108219e19c8b36dba05a5c1
-rw-r--r--webm_parser/fuzzing/corpus/aec8a29238dba3c37f45f2e2e4e64b0e7fa60a741
-rw-r--r--webm_parser/fuzzing/corpus/affba8e4061bf260b1bdf8a815675cc3b4ebefa5bin0 -> 63 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b01c4e2657a230e2b600a20da6f107b0dfe2467c1
-rw-r--r--webm_parser/fuzzing/corpus/b0798ecae4d9e56eefb0c1d95b657865938e62d6bin0 -> 37 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b0ab4f92df810edf4371baad1d4bbd95d360c6071
-rw-r--r--webm_parser/fuzzing/corpus/b14e5f5f6d29b6b71e7fec03eaf7d8237c0c6b2ebin0 -> 178 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b1708df7f6dee0f2feb11f8b6330be0fcffc1f58bin0 -> 7 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b1af4f4e86890fad6fac96a23956405c550778bebin0 -> 39 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b1efd9e687d79e3f5a75eba02bd80bedb72350babin0 -> 59 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b25b0fbf46cef1d888fe900445c9ab95330f44cd1
-rw-r--r--webm_parser/fuzzing/corpus/b2786e0b9d6e7b39165eb4e87e3110362e9e66601
-rw-r--r--webm_parser/fuzzing/corpus/b332536a77d6efcd379cfc2f9828291516cc1ff41
-rw-r--r--webm_parser/fuzzing/corpus/b33962eca397f59591dbb9668e622cf99f2d7cdabin0 -> 91 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b38b6e93da1b43441f88aa53370a9f00b35c3326bin0 -> 62 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b3e01674a1e4dd78e748782fcfc3add5523f51d81
-rw-r--r--webm_parser/fuzzing/corpus/b53e7ef9aad70fcd80986696ebc586c03495b8ed1
-rw-r--r--webm_parser/fuzzing/corpus/b540412c01f960f95edd2a1bc03f1b4447f2b4f2bin0 -> 5 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b62c2c591db32b26e997aa4ece577742db84428a1
-rw-r--r--webm_parser/fuzzing/corpus/b62d99583f30c15c3c2dbed2f69c5e45075d76401
-rw-r--r--webm_parser/fuzzing/corpus/b62f98976c11d79674b019ea78a7ce4d6d78b479bin0 -> 2 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b6787dabeb5cd64ac85f1ec5a7cbeecfd48b2c5f1
-rw-r--r--webm_parser/fuzzing/corpus/b82ebcf4d09ba28d835cb9667da603e46e3438eb1
-rw-r--r--webm_parser/fuzzing/corpus/b85c6bc2473aa12e22f91db3546dfa9d85d8b3d1bin0 -> 27 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b86e98660980680890bcbf02cacdf568d530fa64bin0 -> 48 bytes
-rw-r--r--webm_parser/fuzzing/corpus/b8d9beea35762009941189674c2cfcd14f81254a1
-rw-r--r--webm_parser/fuzzing/corpus/ba517141fc9a468142a8d03d4ee395b4d4f30edc1
-rw-r--r--webm_parser/fuzzing/corpus/bb0a596017cfc2185505d28065ab3cd238d0e2ea1
-rw-r--r--webm_parser/fuzzing/corpus/bb6232815b373e441e2acfcca015f75c680eafacbin0 -> 39 bytes
-rw-r--r--webm_parser/fuzzing/corpus/bbdf4fa36ba9d645399f72c74033fd9c2631aebb1
-rw-r--r--webm_parser/fuzzing/corpus/bc53c9f93974b4f13382c1d49b1e3ec374005ee21
-rw-r--r--webm_parser/fuzzing/corpus/bcacc9bcd3b9cc7dbda9c52c6e4a06a756a9de90bin0 -> 45 bytes
-rw-r--r--webm_parser/fuzzing/corpus/bcc55f432bcf39c6ffc6d7e950612b35e9ea2ec8bin0 -> 42 bytes
-rw-r--r--webm_parser/fuzzing/corpus/bdb1cc868d6ced390f5d35c5f43da9f464fac4641
-rw-r--r--webm_parser/fuzzing/corpus/bf67bb08e0abde692748031c297bc1c7910542f4bin0 -> 34 bytes
-rw-r--r--webm_parser/fuzzing/corpus/bfc07c62ef2770d53d9188b260f531a8128a5c5ebin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/bfed121df31ff73b770ed7f27fc7f4da2fa73e0cbin0 -> 24 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c01816206d93691165e6e3a1924cf9fbc9cf39bd1
-rw-r--r--webm_parser/fuzzing/corpus/c02be525edae59ad9d0d9dcbd790629dc9aaee9bbin0 -> 130 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c094ce0c13ee9a4ca37817d9f7dddc11b2d601771
-rw-r--r--webm_parser/fuzzing/corpus/c0aab5486ad2e80bcc12fca9fb6653984aa682741
-rw-r--r--webm_parser/fuzzing/corpus/c13ca850db259c032cf24cdf6f2833c9d74529d01
-rw-r--r--webm_parser/fuzzing/corpus/c1a8da6cdc8988e6a69961413803acbd1ee935e01
-rw-r--r--webm_parser/fuzzing/corpus/c25abc82a0470129f2d098ac65fabf34c4e11188bin0 -> 61 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c2787d2cf1d95cbcd8b9bcd15d50f67f7e92ad9fbin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c2895ff545ebdc4140951c0ca956524d7a364b771
-rw-r--r--webm_parser/fuzzing/corpus/c2cc55849ff4858bf80f1a4713187618d14a496dbin0 -> 35277 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c2d63b1d75cf53ee3b955bb143036ca93ef3a256bin0 -> 105 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c372a27f78a62ebc013958fa4953a8bc792db53c1
-rw-r--r--webm_parser/fuzzing/corpus/c392963b395a7f92b3bef63fd34bc31ecd3029f3bin0 -> 108 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c3b2749ab6c4d303bfd5da9ea9c8807e9f92d259bin0 -> 25 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c4dd3c8cdd8d7c95603dd67f1cd873d5f9148b291
-rw-r--r--webm_parser/fuzzing/corpus/c52fa8d16e520980e470d76e3fb4f6a612f0f3cfbin0 -> 28 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c56fb214efcd707e6fa68b803d9e7686fc2f43361
-rw-r--r--webm_parser/fuzzing/corpus/c5902afe54998ebc5d8d59043227d379a8c2eee0bin0 -> 16 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c6a16abeea323833079e97b1830610aa6c6eba911
-rw-r--r--webm_parser/fuzzing/corpus/c6fcfd2a1f91a7f6a8c124f2637e60645be01006bin0 -> 25 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c75b02a90370df1f54de2f63a4da8db22f2cf7191
-rw-r--r--webm_parser/fuzzing/corpus/c77cb763d73db279aebfb42d2b5dca3d705d3df7bin0 -> 91 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c864ac8bce0353ecc7d5cb0ce5a1e77a5239201e1
-rw-r--r--webm_parser/fuzzing/corpus/c877c08a79a7408aed779d4a430c5db1bce263141
-rw-r--r--webm_parser/fuzzing/corpus/c8aff6e2e2dfb18be385483b871ac86ff6eac63fbin0 -> 200 bytes
-rw-r--r--webm_parser/fuzzing/corpus/c8c1c1f970bc809a75ad076bdb06275b6f72d0781
-rw-r--r--webm_parser/fuzzing/corpus/c995d52934ac188971f02d5dbdb3cdd70cb032671
-rw-r--r--webm_parser/fuzzing/corpus/c9e7fc3e0e1015a1c15992447f678a495c3ea5dc1
-rw-r--r--webm_parser/fuzzing/corpus/ca5b619ce1bbe23d519f5764d53458d2b85eb9fabin0 -> 121 bytes
-rw-r--r--webm_parser/fuzzing/corpus/cb0556c65c7381192c94324a3b1b8afb7e33feccbin0 -> 54 bytes
-rw-r--r--webm_parser/fuzzing/corpus/cb1a093e6810c7f6c002a2a54ea390cf769c9949bin0 -> 52 bytes
-rw-r--r--webm_parser/fuzzing/corpus/cc1487af64aeefd7080e7678a04870dc85a7928abin0 -> 25 bytes
-rw-r--r--webm_parser/fuzzing/corpus/cc71d2c9f5eae12acee133bd9e50d84881a1bd88bin0 -> 5 bytes
-rw-r--r--webm_parser/fuzzing/corpus/ccb8f962426683663972534c15354f70c3b34a101
-rw-r--r--webm_parser/fuzzing/corpus/cd0771c4754dfcd9c89b9b8a02df96fed974850d1
-rw-r--r--webm_parser/fuzzing/corpus/cd8899c66cbd92ee57f94e000744b32662258ba3bin0 -> 20 bytes
-rw-r--r--webm_parser/fuzzing/corpus/cdf83138f69f0f7c66c13b56028610ac39038b3c1
-rw-r--r--webm_parser/fuzzing/corpus/ce0138cd7718397b365d4c15b0b14f666c3074191
-rw-r--r--webm_parser/fuzzing/corpus/cec5c7e50ef1d865c879563d1a6d677adb86695c1
-rw-r--r--webm_parser/fuzzing/corpus/ceff1dfaf2de4e33d2e3c20aeb7ba4b98f97784c1
-rw-r--r--webm_parser/fuzzing/corpus/cf04b5d28cea1971478806979b256a030a6715411
-rw-r--r--webm_parser/fuzzing/corpus/cf1cf6ad5b3554c3ffc86a859319445158665ea71
-rw-r--r--webm_parser/fuzzing/corpus/cf2338960588d4a5f02a47f3ee96a556224aad75bin0 -> 199 bytes
-rw-r--r--webm_parser/fuzzing/corpus/cfb09018afa0eb1a829e556d9f8bceff40cb36bc1
-rw-r--r--webm_parser/fuzzing/corpus/cfbe3d66d7eb3199440e8e911a93044cc3165c6cbin0 -> 227 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d0a91f7984904976de592cb68e8832853fbec9bcbin0 -> 20 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d22f52563c16725ce4a924dc05d6ac7d64798c43bin0 -> 154 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d2709a1c9d96e72cb844eedca8bfe8440cdb0ef81
-rw-r--r--webm_parser/fuzzing/corpus/d2e385f61ad4b5d45a10a5a6cb85d72e84dea54b1
-rw-r--r--webm_parser/fuzzing/corpus/d2facb213561b30a5bcd012e4e01d0d5b0b269571
-rw-r--r--webm_parser/fuzzing/corpus/d300e5e46a825e5892fae1ba3c466c836f9e1da2bin0 -> 63 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d302c69c881e230e6433283007d318ba437025c31
-rw-r--r--webm_parser/fuzzing/corpus/d30c1b65bc141d1a15d4ed622ba182c96dacdf92bin0 -> 22 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d3adf09fe6fb1534157c1dc68eb61365b46b1c351
-rw-r--r--webm_parser/fuzzing/corpus/d3c74d3a64dba86f98a31bb726587ec97a7e45311
-rw-r--r--webm_parser/fuzzing/corpus/d3c9846ab319f12fc646c23a532c780daa9e993f1
-rw-r--r--webm_parser/fuzzing/corpus/d3f03301b52cf4a830c7dd200ed8ccbc09e6ec94bin0 -> 18 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d4800745440dace38766db3520ffe7baa0bd78f2bin0 -> 4 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d4d6271bba704ba08c2678eb8b1bc4e457144f50bin0 -> 20 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d54020f766061e80f445690c2b5694ed8af21e9d1
-rw-r--r--webm_parser/fuzzing/corpus/d588515f125ebe968fe6a81cb6df7cfc41969e681
-rw-r--r--webm_parser/fuzzing/corpus/d5ee5e4dc8fad9f1da43102d8322beb005a047c01
-rw-r--r--webm_parser/fuzzing/corpus/d62459bb217d3050bcd9e29a6327cf81f5ed68b91
-rw-r--r--webm_parser/fuzzing/corpus/d6acbf1cb46845618ed0d5a322c8bd4879d164221
-rw-r--r--webm_parser/fuzzing/corpus/d6fc8cdbb0f1517159531098e900e2dcc91edba01
-rw-r--r--webm_parser/fuzzing/corpus/d7a9bbd9875a60edf9c528b298ea72a2fad7d3d7bin0 -> 59 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d82e70046a544e95e81f6271dd2695f2599f05741
-rw-r--r--webm_parser/fuzzing/corpus/d84bc8dca7c8fcd227254c06f5b88eaaf7cd5fde1
-rw-r--r--webm_parser/fuzzing/corpus/d8bcb7dd21205e7126e700323b1d58817e9d9a6dbin0 -> 39 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d8e2628b4092b9bbab4f02041647f950503eeb48bin0 -> 33 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d8ee4c2b79863a237c432efe7d43d99c8d0afb9bbin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/d8ee9724bf16ff336387723dcf27319c3be72e011
-rw-r--r--webm_parser/fuzzing/corpus/d9379969bc956ad623e4bab8bbe47270a880a62d1
-rw-r--r--webm_parser/fuzzing/corpus/d9ccb79b0e070dcc2f5ed8e15d99bc5ef86ecff71
-rw-r--r--webm_parser/fuzzing/corpus/db83586ca6266e03067e9a9772ea728ab770ad9abin0 -> 25 bytes
-rw-r--r--webm_parser/fuzzing/corpus/dcc63c06ed2790d1380bdb9281fe8677f439c76dbin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/dd509e9a4660ec34b8f9dc23441c6df4ff97c34d1
-rw-r--r--webm_parser/fuzzing/corpus/dd72f8ff3a067dc7871438b6023e1ed0a4c5787b1
-rw-r--r--webm_parser/fuzzing/corpus/dd75880c5ad488885260f4031a763c86a8084406bin0 -> 111 bytes
-rw-r--r--webm_parser/fuzzing/corpus/dd962d74d04aa4aed270fd8e6b0ae9c4ac35fd191
-rw-r--r--webm_parser/fuzzing/corpus/dd9aa9a49dd790b2ce99c5af1933232d46a7f80d1
-rw-r--r--webm_parser/fuzzing/corpus/ddad7630818a1caa8054d2d7280a1d01bdb33ca3bin0 -> 18 bytes
-rw-r--r--webm_parser/fuzzing/corpus/ddb2eaf33960ce69d579a551a7b05733adcd52aabin0 -> 41 bytes
-rw-r--r--webm_parser/fuzzing/corpus/de0d98cb997c0a0f7be127a46d8a24d8a003931dbin0 -> 47 bytes
-rw-r--r--webm_parser/fuzzing/corpus/de0ff884898c83fb880498f1b8328f78701e65341
-rw-r--r--webm_parser/fuzzing/corpus/de9e0ed8ae29220e5b65e5b97eb3b254ccbe7e0cbin0 -> 45106 bytes
-rw-r--r--webm_parser/fuzzing/corpus/dec5e8ffb35aa707d127a11a49e47cb59954e9691
-rw-r--r--webm_parser/fuzzing/corpus/df1b205e339e7199b5094fcf0ec3b8a8ca7a692cbin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/df254daba2299f3ff2367e284efc53a3296d136b1
-rw-r--r--webm_parser/fuzzing/corpus/dfdcde31231b8b3d3fd4e0ef8ea88885f488c4dbbin0 -> 64 bytes
-rw-r--r--webm_parser/fuzzing/corpus/e01962d7dc1b94e5c4424ec7adae16a7c03b97731
-rw-r--r--webm_parser/fuzzing/corpus/e058723f2964bf1405ae043ddb99efb17d821e151
-rw-r--r--webm_parser/fuzzing/corpus/e09e306ef596dcd0e44bd3ef3c5f7019c7343f841
-rw-r--r--webm_parser/fuzzing/corpus/e154eb76d096c1e545dcad591a58a02c37cd71ff1
-rw-r--r--webm_parser/fuzzing/corpus/e198d2e4f2f3528c2ff46d769a9281ebb3cfb44a1
-rw-r--r--webm_parser/fuzzing/corpus/e2068af1e903f4a81cd6fa5f4022e62070c259ecbin0 -> 39 bytes
-rw-r--r--webm_parser/fuzzing/corpus/e2890330b5655cb277a581b8dd2eeba0d9061ba51
-rw-r--r--webm_parser/fuzzing/corpus/e2e0767d055a7042c24a7acd5d5b6b7c093ad0651
-rw-r--r--webm_parser/fuzzing/corpus/e394a8e21e2c43c42135daa034ad5aabb06acffbbin0 -> 145 bytes
-rw-r--r--webm_parser/fuzzing/corpus/e3b200e97ec226a197e91f12103aaa53d5c37b0e1
-rw-r--r--webm_parser/fuzzing/corpus/e45b077cda64c380ae1b0910bc81d010c27e1c931
-rw-r--r--webm_parser/fuzzing/corpus/e45e71ca01ebe01b01b0ca99b8f20a756c36b9671
-rw-r--r--webm_parser/fuzzing/corpus/e4ff4bd938c3737a02862fc0656a1859d384d0661
-rw-r--r--webm_parser/fuzzing/corpus/e5a1acb7e6f71bc1a2fedaa6173764dfd04c844c1
-rw-r--r--webm_parser/fuzzing/corpus/e6081eeff4ddb76e88e87ef9e4b5f199f5f11c28bin0 -> 63 bytes
-rw-r--r--webm_parser/fuzzing/corpus/e73c42dd266c4d9671da0c7af09e98c02fc052fdbin0 -> 86 bytes
-rw-r--r--webm_parser/fuzzing/corpus/e7b4559a77df21b73285243a8350b844775bb380bin0 -> 21 bytes
-rw-r--r--webm_parser/fuzzing/corpus/e7dd34a80646a8c38ae1ec3a27c1358bab13b3601
-rw-r--r--webm_parser/fuzzing/corpus/e80b344f943ff0ad9219277c4578d3b4100b71dc1
-rw-r--r--webm_parser/fuzzing/corpus/e86e26200290d9d428c5e98e191ec874eb918fb61
-rw-r--r--webm_parser/fuzzing/corpus/e873a3b8f5c3b716e6446df34279b837cf8d2c30bin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/e8fb71319db98d8e8cd131a4eb82879bfbec14d0bin0 -> 123 bytes
-rw-r--r--webm_parser/fuzzing/corpus/e93c3f14614595a2675993438b4c1bfaafdc02d01
-rw-r--r--webm_parser/fuzzing/corpus/e93faef2d77b7c467ae280ba433928d66d63ea631
-rw-r--r--webm_parser/fuzzing/corpus/e95401b11d974ba63270668d3c32c29e95ae85da1
-rw-r--r--webm_parser/fuzzing/corpus/e9723cebe688912f684bcd19b48e5bc8efafaf2a1
-rw-r--r--webm_parser/fuzzing/corpus/e9a4389895c006d4b912e8ac1169229e6b2a66dabin0 -> 56 bytes
-rw-r--r--webm_parser/fuzzing/corpus/e9dc3d10b47ea580404c8e80b844a9978fcf47471
-rw-r--r--webm_parser/fuzzing/corpus/e9e2ac24e5674e7ee424e2b270e5abe84f1a15c91
-rw-r--r--webm_parser/fuzzing/corpus/ea2b4df24b526aad253ab175334cb934b9d80b83bin0 -> 30 bytes
-rw-r--r--webm_parser/fuzzing/corpus/ea681d11486feeab2f080b06ddd2533575b7ace41
-rw-r--r--webm_parser/fuzzing/corpus/ebb747925360528a9f366f9a57730883c636b2c7bin0 -> 56 bytes
-rw-r--r--webm_parser/fuzzing/corpus/ec39abab70a8e1ff072eb082caa6ca77b1ae8087bin0 -> 138 bytes
-rw-r--r--webm_parser/fuzzing/corpus/ecdd96d6cab2dc714a0b0ada1c4fcb18c75298e41
-rw-r--r--webm_parser/fuzzing/corpus/ed008faa6c9001951f50588a1597e039315013431
-rw-r--r--webm_parser/fuzzing/corpus/ed12e272a27a2fbdbfe5a6e78e49ed722ebd3c691
-rw-r--r--webm_parser/fuzzing/corpus/ed799ba0608690ac68dd85c588004197b86e02bf1
-rw-r--r--webm_parser/fuzzing/corpus/edd982c5bd3030bde8c044760e9a678c2a9306f41
-rw-r--r--webm_parser/fuzzing/corpus/ee82c97e35ec92ec3b0bbf911904a050b3aca633bin0 -> 192 bytes
-rw-r--r--webm_parser/fuzzing/corpus/ef11f14feee00e3c198015e6bc76688e970e2d8c1
-rw-r--r--webm_parser/fuzzing/corpus/efa12e91e0d63c2353c120ca1ded7b36afbf57ebbin0 -> 59 bytes
-rw-r--r--webm_parser/fuzzing/corpus/efa14cba9bbaf749067b7bb8515a5d8a289fa3891
-rw-r--r--webm_parser/fuzzing/corpus/eff9bf13cd33ea50a8eacc5f2839cc4b5d67b2de1
-rw-r--r--webm_parser/fuzzing/corpus/f0143756917f0b2e374bd03e731cd641681800541
-rw-r--r--webm_parser/fuzzing/corpus/f0b9dcd4e1845f774bb0f42653b11040baee0765bin0 -> 118 bytes
-rw-r--r--webm_parser/fuzzing/corpus/f1a41ceb420b6b3df50b10f27108c133d4d07508bin0 -> 24 bytes
-rw-r--r--webm_parser/fuzzing/corpus/f39378fd978c6cdb4a8d08cdffc9034e2ca5b6871
-rw-r--r--webm_parser/fuzzing/corpus/f39707a104112d13d9d6bcfbef0efe8dfd98270c1
-rw-r--r--webm_parser/fuzzing/corpus/f4341645a7d466113655d63c5aa00855904166c51
-rw-r--r--webm_parser/fuzzing/corpus/f5511a42d83f94619ff8ca6c940cacc32bdc4834bin0 -> 192 bytes
-rw-r--r--webm_parser/fuzzing/corpus/f57a39fa918249e6941b4e770e15a8131ac16ea51
-rw-r--r--webm_parser/fuzzing/corpus/f5ac272a1dbf362e265210869aaf70ca410f17031
-rw-r--r--webm_parser/fuzzing/corpus/f64c5ca1de57bcf323741f56754f53c642be8ab01
-rw-r--r--webm_parser/fuzzing/corpus/f64dcdee393b4b0a3343f8e684c9db91a6eaeb6ebin0 -> 53 bytes
-rw-r--r--webm_parser/fuzzing/corpus/f69d2954da077043c6ae085e2771a702689314d5bin0 -> 82 bytes
-rw-r--r--webm_parser/fuzzing/corpus/f74e2203adb9c94ba80f7cc3214e3b3040e5675dbin0 -> 90 bytes
-rw-r--r--webm_parser/fuzzing/corpus/f7906e7dc01323b9d3d6e298e8dc2386c8d152f61
-rw-r--r--webm_parser/fuzzing/corpus/f825cac511f3dc38a5ecf3aab3691b4c49ca8f0ebin0 -> 59 bytes
-rw-r--r--webm_parser/fuzzing/corpus/f84c3f9e305172f2ae15dff0b4d955324b3a398ebin0 -> 19 bytes
-rw-r--r--webm_parser/fuzzing/corpus/f87cfad97831c33610fbbe34a04369bd93862464bin0 -> 72 bytes
-rw-r--r--webm_parser/fuzzing/corpus/f8ebd7703fd3ba1a135b243fd947dbd61907d0f41
-rw-r--r--webm_parser/fuzzing/corpus/f926f6b2337650f8b518422c2f63a8869f47c7421
-rw-r--r--webm_parser/fuzzing/corpus/f939897b9fbe865c96020927dc81de9dc255d3851
-rw-r--r--webm_parser/fuzzing/corpus/f985f995ca7d1b691e6ae3b3ef57e8b3c7aa71291
-rw-r--r--webm_parser/fuzzing/corpus/fa79e8ad34cabea4d3c434cc02ea1499069fcafcbin0 -> 47004 bytes
-rw-r--r--webm_parser/fuzzing/corpus/fa7a8dfdd46845ab0fd9b5b7004e37d0232941bf1
-rw-r--r--webm_parser/fuzzing/corpus/fcb3054fde86111e2c346aa71af2456a1705902c1
-rw-r--r--webm_parser/fuzzing/corpus/fce30dcdf2f23b14c580c282a39e065d7aacbfe81
-rw-r--r--webm_parser/fuzzing/corpus/fd15b8dd6c27bc65f90e1c988e0245b1ad7d51c51
-rw-r--r--webm_parser/fuzzing/corpus/fd608012362d161cc7f3d30e1700c51f4ccef4acbin0 -> 52 bytes
-rw-r--r--webm_parser/fuzzing/corpus/fdce5b4f3a038ce7cfeee4deb9a4644edb78189a3
-rw-r--r--webm_parser/fuzzing/corpus/fe203731ada762e02bf843b82e33daee4c2efbf51
-rw-r--r--webm_parser/fuzzing/corpus/fe46e6ef4cd5788d89a101c77123387e3fc9d206bin0 -> 80 bytes
-rw-r--r--webm_parser/fuzzing/corpus/fe7ac2ef276b817af3487bab5fe089186ca0484b1
-rw-r--r--webm_parser/fuzzing/corpus/fe7ef7c0e835873b7b1cd780f248114f18adf13abin0 -> 35 bytes
-rw-r--r--webm_parser/fuzzing/corpus/ff5274cad94d590347d6cdba7637078f82e3d44abin0 -> 107 bytes
-rw-r--r--webm_parser/fuzzing/corpus/ffff6a92363e0e55a9688d9bc025cd8dea3b50d41
-rw-r--r--webm_parser/fuzzing/webm.dict152
-rw-r--r--webm_parser/fuzzing/webm_fuzzer.cc76
-rw-r--r--webm_parser/include/webm/buffer_reader.h138
-rw-r--r--webm_parser/include/webm/callback.h363
-rw-r--r--webm_parser/include/webm/dom_types.h1781
-rw-r--r--webm_parser/include/webm/element.h208
-rw-r--r--webm_parser/include/webm/file_reader.h90
-rw-r--r--webm_parser/include/webm/id.h1085
-rw-r--r--webm_parser/include/webm/istream_reader.h99
-rw-r--r--webm_parser/include/webm/reader.h94
-rw-r--r--webm_parser/include/webm/status.h161
-rw-r--r--webm_parser/include/webm/webm_parser.h133
-rw-r--r--webm_parser/src/ancestory.cc334
-rw-r--r--webm_parser/src/ancestory.h83
-rw-r--r--webm_parser/src/audio_parser.h84
-rw-r--r--webm_parser/src/bit_utils.cc26
-rw-r--r--webm_parser/src/bit_utils.h24
-rw-r--r--webm_parser/src/block_additions_parser.h30
-rw-r--r--webm_parser/src/block_group_parser.h71
-rw-r--r--webm_parser/src/block_header_parser.cc76
-rw-r--r--webm_parser/src/block_header_parser.h65
-rw-r--r--webm_parser/src/block_more_parser.h32
-rw-r--r--webm_parser/src/block_parser.cc285
-rw-r--r--webm_parser/src/block_parser.h126
-rw-r--r--webm_parser/src/bool_parser.h95
-rw-r--r--webm_parser/src/buffer_reader.cc110
-rw-r--r--webm_parser/src/byte_parser.h144
-rw-r--r--webm_parser/src/callback.cc155
-rw-r--r--webm_parser/src/chapter_atom_parser.h42
-rw-r--r--webm_parser/src/chapter_display_parser.h34
-rw-r--r--webm_parser/src/chapters_parser.h28
-rw-r--r--webm_parser/src/cluster_parser.h48
-rw-r--r--webm_parser/src/colour_parser.h55
-rw-r--r--webm_parser/src/content_enc_aes_settings_parser.h32
-rw-r--r--webm_parser/src/content_encoding_parser.h38
-rw-r--r--webm_parser/src/content_encodings_parser.h30
-rw-r--r--webm_parser/src/content_encryption_parser.h37
-rw-r--r--webm_parser/src/cue_point_parser.h38
-rw-r--r--webm_parser/src/cue_track_positions_parser.h39
-rw-r--r--webm_parser/src/cues_parser.h27
-rw-r--r--webm_parser/src/date_parser.cc72
-rw-r--r--webm_parser/src/date_parser.h64
-rw-r--r--webm_parser/src/ebml_parser.h48
-rw-r--r--webm_parser/src/edition_entry_parser.h35
-rw-r--r--webm_parser/src/element_parser.h102
-rw-r--r--webm_parser/src/file_reader.cc122
-rw-r--r--webm_parser/src/float_parser.cc77
-rw-r--r--webm_parser/src/float_parser.h65
-rw-r--r--webm_parser/src/id_element_parser.cc49
-rw-r--r--webm_parser/src/id_element_parser.h56
-rw-r--r--webm_parser/src/id_parser.cc75
-rw-r--r--webm_parser/src/id_parser.h45
-rw-r--r--webm_parser/src/info_parser.h44
-rw-r--r--webm_parser/src/int_parser.h121
-rw-r--r--webm_parser/src/istream_reader.cc133
-rw-r--r--webm_parser/src/master_parser.cc298
-rw-r--r--webm_parser/src/master_parser.h227
-rw-r--r--webm_parser/src/master_value_parser.h533
-rw-r--r--webm_parser/src/mastering_metadata_parser.h57
-rw-r--r--webm_parser/src/parser.h34
-rw-r--r--webm_parser/src/parser_utils.cc34
-rw-r--r--webm_parser/src/parser_utils.h64
-rw-r--r--webm_parser/src/projection_parser.h40
-rw-r--r--webm_parser/src/recursive_parser.h93
-rw-r--r--webm_parser/src/seek_head_parser.h27
-rw-r--r--webm_parser/src/seek_parser.h37
-rw-r--r--webm_parser/src/segment_parser.cc86
-rw-r--r--webm_parser/src/segment_parser.h53
-rw-r--r--webm_parser/src/simple_tag_parser.h37
-rw-r--r--webm_parser/src/size_parser.cc56
-rw-r--r--webm_parser/src/size_parser.h43
-rw-r--r--webm_parser/src/skip_callback.h63
-rw-r--r--webm_parser/src/skip_parser.cc59
-rw-r--r--webm_parser/src/skip_parser.h35
-rw-r--r--webm_parser/src/slices_parser.h30
-rw-r--r--webm_parser/src/tag_parser.h37
-rw-r--r--webm_parser/src/tags_parser.h27
-rw-r--r--webm_parser/src/targets_parser.h35
-rw-r--r--webm_parser/src/time_slice_parser.h30
-rw-r--r--webm_parser/src/track_entry_parser.h65
-rw-r--r--webm_parser/src/tracks_parser.h27
-rw-r--r--webm_parser/src/unknown_parser.cc49
-rw-r--r--webm_parser/src/unknown_parser.h38
-rw-r--r--webm_parser/src/var_int_parser.cc71
-rw-r--r--webm_parser/src/var_int_parser.h56
-rw-r--r--webm_parser/src/video_parser.h122
-rw-r--r--webm_parser/src/virtual_block_parser.cc72
-rw-r--r--webm_parser/src/virtual_block_parser.h69
-rw-r--r--webm_parser/src/void_parser.cc49
-rw-r--r--webm_parser/src/void_parser.h41
-rw-r--r--webm_parser/src/webm_parser.cc282
-rw-r--r--webm_parser/test_utils/element_parser_test.h114
-rw-r--r--webm_parser/test_utils/limited_reader.cc113
-rw-r--r--webm_parser/test_utils/limited_reader.h115
-rw-r--r--webm_parser/test_utils/mock_callback.h247
-rw-r--r--webm_parser/test_utils/parser_test.h108
-rw-r--r--webm_parser/tests/audio_parser_test.cc161
-rw-r--r--webm_parser/tests/bit_utils_test.cc24
-rw-r--r--webm_parser/tests/block_additions_parser_test.cc82
-rw-r--r--webm_parser/tests/block_group_parser_test.cc234
-rw-r--r--webm_parser/tests/block_header_parser_test.cc55
-rw-r--r--webm_parser/tests/block_more_parser_test.cc79
-rw-r--r--webm_parser/tests/block_parser_test.cc865
-rw-r--r--webm_parser/tests/bool_parser_test.cc70
-rw-r--r--webm_parser/tests/buffer_reader_test.cc165
-rw-r--r--webm_parser/tests/byte_parser_test.cc117
-rw-r--r--webm_parser/tests/callback_test.cc148
-rw-r--r--webm_parser/tests/chapter_atom_parser_test.cc209
-rw-r--r--webm_parser/tests/chapter_display_parser_test.cc117
-rw-r--r--webm_parser/tests/chapters_parser_test.cc54
-rw-r--r--webm_parser/tests/cluster_parser_test.cc266
-rw-r--r--webm_parser/tests/colour_parser_test.cc285
-rw-r--r--webm_parser/tests/content_enc_aes_settings_parser_test.cc68
-rw-r--r--webm_parser/tests/content_encoding_parser_test.cc120
-rw-r--r--webm_parser/tests/content_encodings_parser_test.cc82
-rw-r--r--webm_parser/tests/content_encryption_parser_test.cc107
-rw-r--r--webm_parser/tests/cue_point_parser_test.cc87
-rw-r--r--webm_parser/tests/cue_track_positions_parser_test.cc140
-rw-r--r--webm_parser/tests/cues_parser_test.cc53
-rw-r--r--webm_parser/tests/date_parser_test.cc55
-rw-r--r--webm_parser/tests/ebml_parser_test.cc128
-rw-r--r--webm_parser/tests/edition_entry_parser_test.cc77
-rw-r--r--webm_parser/tests/element_test.cc47
-rw-r--r--webm_parser/tests/float_parser_test.cc63
-rw-r--r--webm_parser/tests/id_element_parser_test.cc51
-rw-r--r--webm_parser/tests/id_parser_test.cc56
-rw-r--r--webm_parser/tests/info_parser_test.cc105
-rw-r--r--webm_parser/tests/int_parser_test.cc113
-rw-r--r--webm_parser/tests/istream_reader_test.cc131
-rw-r--r--webm_parser/tests/limited_reader_test.cc250
-rw-r--r--webm_parser/tests/master_parser_test.cc382
-rw-r--r--webm_parser/tests/master_value_parser_test.cc366
-rw-r--r--webm_parser/tests/mastering_metadata_parser_test.cc208
-rw-r--r--webm_parser/tests/parser_utils_test.cc100
-rw-r--r--webm_parser/tests/projection_parser_test.cc144
-rw-r--r--webm_parser/tests/recursive_parser_test.cc90
-rw-r--r--webm_parser/tests/seek_head_parser_test.cc54
-rw-r--r--webm_parser/tests/seek_parser_test.cc67
-rw-r--r--webm_parser/tests/segment_parser_test.cc369
-rw-r--r--webm_parser/tests/simple_tag_parser_test.cc205
-rw-r--r--webm_parser/tests/size_parser_test.cc61
-rw-r--r--webm_parser/tests/skip_parser_test.cc42
-rw-r--r--webm_parser/tests/slices_parser_test.cc80
-rw-r--r--webm_parser/tests/tag_parser_test.cc92
-rw-r--r--webm_parser/tests/tags_parser_test.cc61
-rw-r--r--webm_parser/tests/targets_parser_test.cc102
-rw-r--r--webm_parser/tests/time_slice_parser_test.cc63
-rw-r--r--webm_parser/tests/track_entry_parser_test.cc245
-rw-r--r--webm_parser/tests/tracks_parser_test.cc55
-rw-r--r--webm_parser/tests/unknown_parser_test.cc57
-rw-r--r--webm_parser/tests/var_int_parser_test.cc53
-rw-r--r--webm_parser/tests/video_parser_test.cc446
-rw-r--r--webm_parser/tests/virtual_block_parser_test.cc74
-rw-r--r--webm_parser/tests/void_parser_test.cc56
-rw-r--r--webm_parser/tests/webm_parser_test.cc339
-rw-r--r--webm_parser/tests/webm_parser_tests.cc13
-rw-r--r--webmids.hpp23
-rw-r--r--webvtt/vttreader.cc54
-rw-r--r--webvtt/vttreader.h44
-rw-r--r--webvtt/webvttparser.cc706
-rw-r--r--webvtt/webvttparser.h158
-rw-r--r--webvttparser.h15
892 files changed, 51781 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..7ce4e92
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,152 @@
+---
+Language: Cpp
+# BasedOnStyle: Google
+# Generated with clang-format 7.0.1
+AccessModifierOffset: -1
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: true
+AlignOperands: true
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: true
+BinPackParameters: true
+BraceWrapping:
+ AfterClass: false
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: false
+ AfterNamespace: false
+ AfterObjCDeclaration: false
+ AfterStruct: false
+ AfterUnion: false
+ AfterExternBlock: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Attach
+BreakBeforeTernaryOperators: false
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeColon
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 80
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: true
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ]
+IncludeCategories:
+FixNamespaceComments: true
+ForEachMacros:
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^<ext/.*\.h>'
+ Priority: 2
+ - Regex: '^<.*\.h>'
+ Priority: 1
+ - Regex: '^<.*'
+ Priority: 2
+ - Regex: '.*'
+ Priority: 3
+IncludeIsMainRegex: '([-_](test|unittest))?$'
+IndentCaseLabels: true
+IndentPPDirectives: None
+IndentWidth: 2
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Never
+ObjCBlockIndentWidth: 2
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 2
+PenaltyBreakBeforeFirstCallParameter: 1
+PenaltyBreakComment: 300
+PenaltyBreakFirstLessLess: 120
+PenaltyBreakString: 1000
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PointerAlignment: Left
+RawStringFormats:
+ - Language: Cpp
+ Delimiters:
+ - cc
+ - CC
+ - cpp
+ - Cpp
+ - CPP
+ - 'c++'
+ - 'C++'
+ CanonicalDelimiter: ''
+ BasedOnStyle: google
+ - Language: TextProto
+ Delimiters:
+ - pb
+ - PB
+ - proto
+ - PROTO
+ EnclosingFunctions:
+ - EqualsProto
+ - EquivToProto
+ - PARSE_PARTIAL_TEXT_PROTO
+ - PARSE_TEST_PROTO
+ - PARSE_TEXT_PROTO
+ - ParseTextOrDie
+ - ParseTextProtoOrDie
+ CanonicalDelimiter: ''
+ BasedOnStyle: google
+ReflowComments: true
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 2
+SpacesInAngles: false
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Auto
+TabWidth: 8
+UseTab: Never
+...
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..2b5cda5
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,6 @@
+*.sln eol=crlf
+*.vcproj eol=crlf
+*.vsprops eol=crlf
+*.vcxproj eol=crlf
+*.mkv -text -diff
+*.webm -text -diff
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2abbaa6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+*.mkv
+*.MKV
+core
+*.a
+*.d
+*.so*
+*.o
+*~
+*.swp
+*.ncb
+*.user
+*.suo
+*.exe
+/*.webm
+Debug
+Release
+*.sdf
+*.opensdf
+ipch
+dumpvtt
+mkvmuxer_sample
+mkvparser_sample
+vttdemux
+mkvmuxer_tests
+mkvparser_tests
+webm_info
+webm2pes
+webm2pes_tests
+webm2ts
+vp9_header_parser_tests
+vp9_level_stats_tests
+Makefile
+CMakeFiles
+CMakeCache.txt
+*.cmake
diff --git a/AUTHORS.TXT b/AUTHORS.TXT
new file mode 100644
index 0000000..9686ac1
--- /dev/null
+++ b/AUTHORS.TXT
@@ -0,0 +1,4 @@
+# Names should be added to this file like so:
+# Name or Organization <email address>
+
+Google Inc.
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..b46ba10
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE:= libwebm
+LOCAL_CPPFLAGS:=-D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS
+LOCAL_CPPFLAGS+=-D__STDC_LIMIT_MACROS -std=c++11
+LOCAL_C_INCLUDES:= $(LOCAL_PATH)
+LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)
+
+LOCAL_SRC_FILES:= common/file_util.cc \
+ common/hdr_util.cc \
+ mkvparser/mkvparser.cc \
+ mkvparser/mkvreader.cc \
+ mkvmuxer/mkvmuxer.cc \
+ mkvmuxer/mkvmuxerutil.cc \
+ mkvmuxer/mkvwriter.cc
+include $(BUILD_STATIC_LIBRARY)
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..9fa5a53
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,452 @@
+## Copyright (c) 2015 The WebM project authors. All Rights Reserved.
+##
+## Use of this source code is governed by a BSD-style license
+## that can be found in the LICENSE file in the root of the source
+## tree. An additional intellectual property rights grant can be found
+## in the file PATENTS. All contributing project authors may
+## be found in the AUTHORS file in the root of the source tree.
+cmake_minimum_required(VERSION 3.2)
+project(LIBWEBM CXX)
+
+include(GNUInstallDirs)
+include("${CMAKE_CURRENT_SOURCE_DIR}/build/cxx_flags.cmake")
+
+if (NOT BUILD_SHARED_LIBS)
+ include("${CMAKE_CURRENT_SOURCE_DIR}/build/msvc_runtime.cmake")
+endif ()
+
+set(LIBWEBM_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+
+# Build/test configuration flags.
+option(ENABLE_WEBMTS "Enables WebM PES/TS support." ON)
+option(ENABLE_WEBMINFO "Enables building webm_info." ON)
+option(ENABLE_TESTS "Enables tests." OFF)
+option(ENABLE_IWYU "Enables include-what-you-use support." OFF)
+option(ENABLE_WERROR "Enable warnings as errors." OFF)
+option(ENABLE_WEBM_PARSER "Enables new parser API." OFF)
+
+if(WIN32)
+ require_cxx_flag_nomsvc("-std=gnu++11")
+else()
+ require_cxx_flag_nomsvc("-std=c++11")
+endif()
+
+add_cxx_preproc_definition("__STDC_CONSTANT_MACROS")
+add_cxx_preproc_definition("__STDC_FORMAT_MACROS")
+add_cxx_preproc_definition("__STDC_LIMIT_MACROS")
+
+# Set up compiler flags and build properties.
+include_directories("${LIBWEBM_SRC_DIR}")
+
+if (MSVC)
+ add_cxx_flag_if_supported("/W4")
+ # Disable MSVC warnings that suggest making code non-portable.
+ add_cxx_flag_if_supported("/wd4996")
+ if (ENABLE_WERROR)
+ add_cxx_flag_if_supported("/WX")
+ endif ()
+else ()
+ add_cxx_flag_if_supported("-Wall")
+ add_cxx_flag_if_supported("-Wextra")
+ add_cxx_flag_if_supported("-Wnarrowing")
+ add_cxx_flag_if_supported("-Wno-deprecated")
+ add_cxx_flag_if_supported("-Wshorten-64-to-32")
+ if (ENABLE_WERROR)
+ add_cxx_flag_if_supported("-Werror")
+ endif ()
+endif ()
+
+# Source list variables.
+set(dumpvtt_sources "${LIBWEBM_SRC_DIR}/dumpvtt.cc")
+
+set(libwebm_common_sources
+ "${LIBWEBM_SRC_DIR}/common/file_util.cc"
+ "${LIBWEBM_SRC_DIR}/common/file_util.h"
+ "${LIBWEBM_SRC_DIR}/common/hdr_util.cc"
+ "${LIBWEBM_SRC_DIR}/common/hdr_util.h"
+ "${LIBWEBM_SRC_DIR}/common/webmids.h")
+
+set(mkvmuxer_sources
+ "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvmuxer.cc"
+ "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvmuxer.h"
+ "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvmuxertypes.h"
+ "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvmuxerutil.cc"
+ "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvmuxerutil.h"
+ "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvwriter.cc"
+ "${LIBWEBM_SRC_DIR}/mkvmuxer/mkvwriter.h"
+ "${LIBWEBM_SRC_DIR}/common/webmids.h")
+
+set(mkvmuxer_sample_sources
+ "${LIBWEBM_SRC_DIR}/mkvmuxer_sample.cc"
+ "${LIBWEBM_SRC_DIR}/sample_muxer_metadata.cc"
+ "${LIBWEBM_SRC_DIR}/sample_muxer_metadata.h")
+
+set(mkvmuxer_tests_sources
+ "${LIBWEBM_SRC_DIR}/testing/mkvmuxer_tests.cc"
+ "${LIBWEBM_SRC_DIR}/testing/test_util.cc"
+ "${LIBWEBM_SRC_DIR}/testing/test_util.h")
+
+set(mkvparser_sources
+ "${LIBWEBM_SRC_DIR}/mkvparser/mkvparser.cc"
+ "${LIBWEBM_SRC_DIR}/mkvparser/mkvparser.h"
+ "${LIBWEBM_SRC_DIR}/mkvparser/mkvreader.cc"
+ "${LIBWEBM_SRC_DIR}/mkvparser/mkvreader.h"
+ "${LIBWEBM_SRC_DIR}/common/webmids.h")
+
+set(mkvparser_sample_sources "${LIBWEBM_SRC_DIR}/mkvparser_sample.cc")
+set(mkvparser_tests_sources
+ "${LIBWEBM_SRC_DIR}/testing/mkvparser_tests.cc"
+ "${LIBWEBM_SRC_DIR}/testing/test_util.cc"
+ "${LIBWEBM_SRC_DIR}/testing/test_util.h")
+
+set(vp9_header_parser_tests_sources
+ "${LIBWEBM_SRC_DIR}/common/vp9_header_parser_tests.cc"
+ "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.cc"
+ "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.h"
+ "${LIBWEBM_SRC_DIR}/testing/test_util.cc"
+ "${LIBWEBM_SRC_DIR}/testing/test_util.h")
+
+set(vp9_level_stats_tests_sources
+ "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.cc"
+ "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.h"
+ "${LIBWEBM_SRC_DIR}/common/vp9_level_stats_tests.cc"
+ "${LIBWEBM_SRC_DIR}/common/vp9_level_stats.cc"
+ "${LIBWEBM_SRC_DIR}/common/vp9_level_stats.h"
+ "${LIBWEBM_SRC_DIR}/testing/test_util.cc"
+ "${LIBWEBM_SRC_DIR}/testing/test_util.h")
+
+set(vttdemux_sources
+ "${LIBWEBM_SRC_DIR}/vttdemux.cc"
+ "${LIBWEBM_SRC_DIR}/webvtt/webvttparser.cc"
+ "${LIBWEBM_SRC_DIR}/webvtt/webvttparser.h")
+
+set(webm_parser_public_headers
+ "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/buffer_reader.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/callback.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/dom_types.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/element.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/file_reader.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/id.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/istream_reader.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/reader.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/status.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/include/webm/webm_parser.h")
+
+set(webm_parser_sources
+ ${webm_parser_public_headers}
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/ancestory.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/ancestory.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/audio_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/bit_utils.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/bit_utils.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/block_additions_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/block_group_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/block_header_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/block_header_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/block_more_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/block_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/block_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/bool_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/buffer_reader.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/byte_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/callback.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/chapter_atom_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/chapter_display_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/chapters_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/cluster_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/colour_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/content_enc_aes_settings_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/content_encoding_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/content_encodings_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/content_encryption_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/cue_point_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/cue_track_positions_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/cues_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/date_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/date_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/ebml_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/edition_entry_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/element_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/file_reader.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/float_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/float_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/id_element_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/id_element_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/id_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/id_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/info_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/int_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/istream_reader.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/master_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/master_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/master_value_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/mastering_metadata_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/parser_utils.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/parser_utils.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/projection_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/recursive_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/seek_head_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/seek_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/segment_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/segment_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/simple_tag_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/size_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/size_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/skip_callback.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/skip_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/skip_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/slices_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/tag_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/tags_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/targets_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/time_slice_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/track_entry_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/tracks_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/unknown_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/unknown_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/var_int_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/var_int_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/video_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/virtual_block_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/virtual_block_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/void_parser.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/void_parser.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/src/webm_parser.cc")
+
+set(webm_parser_demo_sources "${LIBWEBM_SRC_DIR}/webm_parser/demo/demo.cc")
+set(webm_parser_tests_sources
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/audio_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/bit_utils_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/block_additions_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/block_group_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/block_header_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/block_more_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/block_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/bool_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/buffer_reader_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/byte_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/callback_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/chapter_atom_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/chapter_display_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/chapters_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/cluster_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/colour_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/content_enc_aes_settings_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/content_encoding_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/content_encodings_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/content_encryption_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/cue_point_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/cue_track_positions_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/cues_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/date_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/ebml_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/edition_entry_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/element_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/float_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/id_element_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/id_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/info_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/int_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/istream_reader_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/limited_reader_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/master_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/master_value_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/mastering_metadata_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/parser_utils_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/projection_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/recursive_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/seek_head_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/seek_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/segment_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/simple_tag_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/size_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/skip_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/slices_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/tag_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/tags_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/targets_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/time_slice_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/track_entry_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/tracks_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/unknown_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/var_int_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/video_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/virtual_block_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/void_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/webm_parser_test.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/test_utils/element_parser_test.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/test_utils/limited_reader.cc"
+ "${LIBWEBM_SRC_DIR}/webm_parser/test_utils/limited_reader.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/test_utils/mock_callback.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/test_utils/parser_test.h"
+ "${LIBWEBM_SRC_DIR}/webm_parser/tests/webm_parser_tests.cc")
+
+set(webm_info_sources
+ "${LIBWEBM_SRC_DIR}/common/indent.cc"
+ "${LIBWEBM_SRC_DIR}/common/indent.h"
+ "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.cc"
+ "${LIBWEBM_SRC_DIR}/common/vp9_header_parser.h"
+ "${LIBWEBM_SRC_DIR}/common/vp9_level_stats.cc"
+ "${LIBWEBM_SRC_DIR}/common/vp9_level_stats.h"
+ "${LIBWEBM_SRC_DIR}/common/webm_constants.h"
+ "${LIBWEBM_SRC_DIR}/common/webm_endian.cc"
+ "${LIBWEBM_SRC_DIR}/common/webm_endian.h"
+ "${LIBWEBM_SRC_DIR}/webm_info.cc")
+
+set(webmts_sources
+ "${LIBWEBM_SRC_DIR}/common/libwebm_util.cc"
+ "${LIBWEBM_SRC_DIR}/common/libwebm_util.h"
+ "${LIBWEBM_SRC_DIR}/common/video_frame.cc"
+ "${LIBWEBM_SRC_DIR}/common/video_frame.h"
+ "${LIBWEBM_SRC_DIR}/m2ts/vpxpes2ts.cc"
+ "${LIBWEBM_SRC_DIR}/m2ts/vpxpes2ts.h"
+ "${LIBWEBM_SRC_DIR}/m2ts/vpxpes_parser.cc"
+ "${LIBWEBM_SRC_DIR}/m2ts/vpxpes_parser.h"
+ "${LIBWEBM_SRC_DIR}/m2ts/webm2pes.cc"
+ "${LIBWEBM_SRC_DIR}/m2ts/webm2pes.h")
+
+set(webm2pes_sources "${LIBWEBM_SRC_DIR}/m2ts/webm2pes_main.cc")
+set(webm2pes_tests_sources
+ "${LIBWEBM_SRC_DIR}/testing/test_util.cc"
+ "${LIBWEBM_SRC_DIR}/testing/test_util.h"
+ "${LIBWEBM_SRC_DIR}/testing/video_frame_tests.cc"
+ "${LIBWEBM_SRC_DIR}/m2ts/tests/webm2pes_tests.cc")
+set(webm2ts_sources "${LIBWEBM_SRC_DIR}/m2ts/vpxpes2ts_main.cc")
+
+set(webvtt_common_sources
+ "${LIBWEBM_SRC_DIR}/webvtt/vttreader.cc"
+ "${LIBWEBM_SRC_DIR}/webvtt/vttreader.h"
+ "${LIBWEBM_SRC_DIR}/webvtt/webvttparser.cc"
+ "${LIBWEBM_SRC_DIR}/webvtt/webvttparser.h")
+
+# Targets.
+add_library(mkvmuxer OBJECT ${mkvmuxer_sources})
+add_library(mkvparser OBJECT ${mkvparser_sources})
+add_library(webvtt_common OBJECT ${webvtt_common_sources})
+
+add_library(webm ${libwebm_common_sources}
+ $<TARGET_OBJECTS:mkvmuxer>
+ $<TARGET_OBJECTS:mkvparser>)
+
+if (WIN32)
+ # Use libwebm and libwebm.lib for project and library name on Windows (instead
+ # webm and webm.lib).
+ set_target_properties(webm PROPERTIES PROJECT_LABEL libwebm)
+ set_target_properties(webm PROPERTIES PREFIX lib)
+endif ()
+
+add_executable(mkvparser_sample ${mkvparser_sample_sources})
+target_link_libraries(mkvparser_sample LINK_PUBLIC webm)
+
+add_executable(mkvmuxer_sample ${mkvmuxer_sample_sources}
+ $<TARGET_OBJECTS:webvtt_common>)
+target_link_libraries(mkvmuxer_sample LINK_PUBLIC webm)
+
+add_executable(dumpvtt ${dumpvtt_sources} $<TARGET_OBJECTS:webvtt_common>)
+target_link_libraries(dumpvtt LINK_PUBLIC webm)
+
+add_executable(vttdemux ${vttdemux_sources})
+target_link_libraries(vttdemux LINK_PUBLIC webm)
+
+if (ENABLE_WEBMINFO)
+ add_executable(webm_info ${webm_info_sources})
+ target_link_libraries(webm_info LINK_PUBLIC webm)
+endif ()
+
+if (ENABLE_WEBM_PARSER)
+ include_directories(webm_parser webm_parser/include)
+ add_library(webm_parser OBJECT ${webm_parser_sources})
+ target_sources(webm PUBLIC $<TARGET_OBJECTS:webm_parser>)
+ set_target_properties(webm PROPERTIES PUBLIC_HEADER
+ "${webm_parser_public_headers}")
+
+ add_executable(webm_parser_demo ${webm_parser_demo_sources})
+ target_link_libraries(webm_parser_demo LINK_PUBLIC webm)
+
+ install(TARGETS webm
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/webm)
+endif ()
+
+if (ENABLE_WEBMTS)
+ add_library(webmts OBJECT ${webmts_sources})
+
+ add_executable(webm2pes ${webm2pes_sources} $<TARGET_OBJECTS:webmts>)
+ target_link_libraries(webm2pes LINK_PUBLIC webm)
+
+ add_executable(webm2ts ${webm2ts_sources} $<TARGET_OBJECTS:webmts>)
+ target_link_libraries(webm2ts LINK_PUBLIC webm)
+endif ()
+
+if (ENABLE_TESTS)
+ set(GTEST_SRC_DIR "${LIBWEBM_SRC_DIR}/../googletest" CACHE PATH
+ "Path to Googletest git repository.")
+ # This directory is where libwebm will build googletest dependencies.
+ set(GTEST_BUILD_DIR "${CMAKE_BINARY_DIR}/googletest_build")
+
+ if (LIBWEBM_DISABLE_GTEST_CMAKE)
+ add_library(gtest STATIC "${GTEST_SRC_DIR}/googletest/src/gtest-all.cc")
+ include_directories("${GTEST_SRC_DIR}/googletest")
+ else ()
+ add_subdirectory("${GTEST_SRC_DIR}" "${GTEST_BUILD_DIR}")
+ endif ()
+ include_directories("${GTEST_SRC_DIR}/googletest/include")
+
+ add_executable(mkvmuxer_tests ${mkvmuxer_tests_sources})
+ target_link_libraries(mkvmuxer_tests LINK_PUBLIC gtest webm)
+
+ add_executable(mkvparser_tests ${mkvparser_tests_sources})
+ target_link_libraries(mkvparser_tests LINK_PUBLIC gtest webm)
+
+ add_executable(vp9_header_parser_tests ${vp9_header_parser_tests_sources})
+ target_link_libraries(vp9_header_parser_tests LINK_PUBLIC gtest webm)
+
+ add_executable(vp9_level_stats_tests ${vp9_level_stats_tests_sources})
+ target_link_libraries(vp9_level_stats_tests LINK_PUBLIC gtest webm)
+
+ if (ENABLE_WEBMTS)
+ add_executable(webm2pes_tests ${webm2pes_tests_sources}
+ $<TARGET_OBJECTS:webmts>)
+ target_link_libraries(webm2pes_tests LINK_PUBLIC gtest webm)
+ endif ()
+
+ if (ENABLE_WEBM_PARSER)
+ include_directories("${GTEST_SRC_DIR}/googlemock/include")
+ add_executable(webm_parser_tests ${webm_parser_tests_sources})
+ target_link_libraries(webm_parser_tests LINK_PUBLIC gmock gtest webm)
+ endif ()
+endif ()
+
+# Include-what-you-use.
+if (ENABLE_IWYU)
+ # Make sure all the tools necessary for IWYU are present.
+ find_program(iwyu_path NAMES include-what-you-use)
+ find_program(iwyu_tool_path NAMES iwyu_tool.py)
+
+ # Some odd behavior on cmake's part: PYTHON_EXECUTABLE and PYTHON_VERSION_*
+ # are set by cmake when it does its internal python check, but
+ # PYTHONINTERP_FOUND is empty without explicitly looking for it.
+ find_package(PythonInterp)
+
+ if (iwyu_path AND iwyu_tool_path AND PYTHONINTERP_FOUND)
+ # Enable compilation command export (needed for iwyu_tool.py)
+ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+ # Add a custom target to run iwyu across all targets.
+ add_custom_target(iwyu
+ ALL
+ COMMAND "${PYTHON_EXECUTABLE}" "${iwyu_tool_path}" -p
+ "${CMAKE_BINARY_DIR}"
+ COMMENT "Running include-what-you-use..."
+ VERBATIM)
+ else ()
+ message(STATUS "Ignoring ENABLE_IWYU because reasons:")
+ message(STATUS " iwyu_path=" ${iwyu_path})
+ message(STATUS " iwyu_tool_path=" ${iwyu_tool_path})
+ message(STATUS " PYTHONINTERP_FOUND=" ${PYTHONINTERP_FOUND})
+ message(STATUS " See README.libwebm for more information.")
+ endif ()
+endif ()
diff --git a/LICENSE.TXT b/LICENSE.TXT
new file mode 100644
index 0000000..7a6f995
--- /dev/null
+++ b/LICENSE.TXT
@@ -0,0 +1,30 @@
+Copyright (c) 2010, Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ * Neither the name of Google nor the names of its contributors may
+ be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/Makefile.unix b/Makefile.unix
new file mode 100644
index 0000000..0430d17
--- /dev/null
+++ b/Makefile.unix
@@ -0,0 +1,57 @@
+CXX := g++
+DEFINES := -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS
+DEFINES += -D__STDC_LIMIT_MACROS
+INCLUDES := -I.
+CXXFLAGS := -W -Wall -g -std=c++11
+ALL_CXXFLAGS := -MMD -MP $(DEFINES) $(INCLUDES) $(CXXFLAGS)
+LIBWEBMA := libwebm.a
+LIBWEBMSO := libwebm.so
+WEBMOBJS := mkvmuxer/mkvmuxer.o mkvmuxer/mkvmuxerutil.o mkvmuxer/mkvwriter.o
+WEBMOBJS += mkvparser/mkvparser.o mkvparser/mkvreader.o
+WEBMOBJS += common/file_util.o common/hdr_util.o
+OBJSA := $(WEBMOBJS:.o=_a.o)
+OBJSSO := $(WEBMOBJS:.o=_so.o)
+VTTOBJS := webvtt/vttreader.o webvtt/webvttparser.o sample_muxer_metadata.o
+EXEOBJS := mkvmuxer_sample.o mkvparser_sample.o dumpvtt.o vttdemux.o
+EXES := mkvparser_sample mkvmuxer_sample dumpvtt vttdemux
+DEPS := $(WEBMOBJS:.o=.d) $(OBJECTS1:.o=.d) $(OBJECTS2:.o=.d)
+DEPS += $(OBJECTS3:.o=.d) $(OBJECTS4:.o=.d) $(OBJSA:.o=.d) $(OBJSSO:.o=.d)
+DEPS += $(VTTOBJS:.o=.d) $(EXEOBJS:.o=.d)
+CLEAN := $(EXEOBJS) $(VTTOBJS) $(WEBMOBJS) $(OBJSA) $(OBJSSO) $(LIBWEBMA)
+CLEAN += $(LIBWEBMSO) $(EXES) $(DEPS) $(INFOOBJS)
+
+all: $(EXES)
+
+mkvparser_sample: mkvparser_sample.o $(LIBWEBMA)
+ $(CXX) $^ -o $@
+
+mkvmuxer_sample: mkvmuxer_sample.o $(VTTOBJS) $(LIBWEBMA)
+ $(CXX) $^ -o $@
+
+dumpvtt: dumpvtt.o $(VTTOBJS) $(WEBMOBJS)
+ $(CXX) $^ -o $@
+
+vttdemux: vttdemux.o $(VTTOBJS) $(LIBWEBMA)
+ $(CXX) $^ -o $@
+
+shared: $(LIBWEBMSO)
+
+libwebm.a: $(OBJSA)
+ $(AR) rcs $@ $^
+
+libwebm.so: $(OBJSSO)
+ $(CXX) $(ALL_CXXFLAGS) -shared $(OBJSSO) -o $(LIBWEBMSO)
+
+%.o: %.cc
+ $(CXX) -c $(ALL_CXXFLAGS) $< -o $@
+%_a.o: %.cc
+ $(CXX) -c $(ALL_CXXFLAGS) $< -o $@
+%_so.o: %.cc
+ $(CXX) -c $(ALL_CXXFLAGS) -fPIC $< -o $@
+
+clean:
+ $(RM) -f $(CLEAN) Makefile.bak
+
+ifneq ($(MAKECMDGOALS), clean)
+ -include $(DEPS)
+endif
diff --git a/PATENTS.TXT b/PATENTS.TXT
new file mode 100644
index 0000000..caedf60
--- /dev/null
+++ b/PATENTS.TXT
@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+------------------------------------
+
+"These implementations" means the copyrightable works that implement the WebM
+codecs distributed by Google as part of the WebM Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge,
+royalty-free, irrevocable (except as stated in this section) patent license to
+make, have made, use, offer to sell, sell, import, transfer, and otherwise
+run, modify and propagate the contents of these implementations of WebM, where
+such license applies only to those patent claims, both currently owned by
+Google and acquired in the future, licensable by Google that are necessarily
+infringed by these implementations of WebM. This grant does not include claims
+that would be infringed only as a consequence of further modification of these
+implementations. If you or your agent or exclusive licensee institute or order
+or agree to the institution of patent litigation or any other patent
+enforcement activity against any entity (including a cross-claim or
+counterclaim in a lawsuit) alleging that any of these implementations of WebM
+or any code incorporated within any of these implementations of WebM
+constitute direct or contributory patent infringement, or inducement of
+patent infringement, then any patent rights granted to you under this License
+for these implementations of WebM shall terminate as of the date such
+litigation is filed.
diff --git a/README.libwebm b/README.libwebm
new file mode 100644
index 0000000..3406f80
--- /dev/null
+++ b/README.libwebm
@@ -0,0 +1,143 @@
+Building Libwebm
+
+To build libwebm you must first create project files. To do this run cmake
+and pass it the path to your libwebm repo.
+
+Makefile.unix can be used as a fallback on systems that cmake does not
+support.
+
+
+CMake Basics
+
+To generate project/make files for the default toolchain on your system simply
+run cmake with the path to the libwebm repo:
+
+$ cmake path/to/libwebm
+
+On Windows the above command will produce Visual Studio project files for the
+newest Visual Studio detected on the system. On Mac OS X and Linux systems, the
+above command will produce a makefile.
+
+To control what types of projects are generated the -G parameter is added to
+the cmake command line. This argument must be followed by the name of a
+generator. Running cmake with the --help argument will list the available
+generators for your system.
+
+On Mac OS X you would run the following command to generate Xcode projects:
+
+$ cmake path/to/libwebm -G Xcode
+
+On a Windows box you would run the following command to generate Visual Studio
+2013 projects:
+
+$ cmake path/to/libwebm -G "Visual Studio 12"
+
+To generate 64-bit Windows Visual Studio 2013 projects:
+
+$ cmake path/to/libwebm "Visual Studio 12 Win64"
+
+
+CMake Makefiles: Debugging and Optimization
+
+Unlike Visual Studio and Xcode projects, the build configuration for make builds
+is controlled when you run cmake. The following examples demonstrate various
+build configurations.
+
+Omitting the build type produces makefiles that use build flags containing
+neither optimization nor debug flags:
+$ cmake path/to/libwebm
+
+A makefile using release (optimized) flags is produced like this:
+$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=release
+
+A release build with debug info can be produced as well:
+$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=relwithdebinfo
+
+And your standard debug build will be produced using:
+$ cmake path/to/libwebm -DCMAKE_BUILD_TYPE=debug
+
+
+Tests
+
+To enable libwebm tests add -DENABLE_TESTS=ON CMake generation command line. For
+example:
+
+$ cmake path/to/libwebm -G Xcode -DENABLE_TESTS=ON
+
+Libwebm tests depend on googletest. By default googletest is expected to be a
+sibling directory of the Libwebm repository. To change that, update your CMake
+command to be similar to the following:
+
+$ cmake path/to/libwebm -G Xcode -DENABLE_TESTS=ON \
+ -DGTEST_SRC_DIR=/path/to/googletest
+
+The tests rely upon the LIBWEBM_TEST_DATA_PATH environment variable to locate
+test input. The following example demonstrates running the muxer tests from the
+build directory:
+
+$ LIBWEBM_TEST_DATA_PATH=path/to/libwebm/testing/testdata ./mkvmuxer_tests
+
+Note: Libwebm Googletest integration was built with googletest from
+ https://github.com/google/googletest.git at git revision
+ ddb8012eb48bc203aa93dcc2b22c1db516302b29.
+
+
+CMake Include-what-you-use integration
+
+Include-what-you-use is an analysis tool that helps ensure libwebm includes the
+C/C++ header files actually in use. To enable the integration support
+ENABLE_IWYU must be turned on at cmake run time:
+
+$ cmake path/to/libwebm -G "Unix Makefiles" -DENABLE_IWYU=ON
+
+This adds the iwyu target to the build. To run include-what-you-use:
+
+$ make iwyu
+
+The following requirements must be met for ENABLE_IWYU to enable the iwyu
+target:
+
+1. include-what-you-use and iwyu_tool.py must be in your PATH.
+2. A python interpreter must be on the system and available to CMake.
+
+The values of the following variables are used to determine if the requirements
+have been met. Values to the right of the equals sign are what a successful run
+might look like:
+ iwyu_path=/path/to/iwyu_tool.py
+ iwyu_tool_path=/path/to/include-what-you-use
+ PYTHONINTERP_FOUND=TRUE
+
+An empty PYTHONINTERP_FOUND, or iwyu_path/iwyu_tool_path suffixed with NOTFOUND
+are failures.
+
+For Include-what-you-use setup instructions, see:
+https://github.com/include-what-you-use/include-what-you-use/blob/master/docs/InstructionsForUsers.md
+
+If, when building the iwyu target, compile errors reporting failures loading
+standard include files occur, one solution can be found here:
+https://github.com/include-what-you-use/include-what-you-use/issues/100
+
+
+CMake cross compile
+To cross compile libwebm for Windows using mingw-w64 run cmake with the
+following arguments:
+
+$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \
+ path/to/libwebm
+
+Note1: As of this writing googletest will not build via mingw-w64 without
+disabling pthreads.
+googletest hash: d225acc90bc3a8c420a9bcd1f033033c1ccd7fe0
+
+To build with tests when using mingw-w64 use the following arguments when
+running CMake:
+
+$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \
+ -DENABLE_TESTS=ON -Dgtest_disable_pthreads=ON path/to/libwebm
+
+Note2: i686-w64-mingw32 is the default compiler. This can be controlled using
+the MINGW_PREFIX variable:
+
+$ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/libwebm/build/mingw-w64_toolchain.cmake \
+ -DMINGW_PREFIX=x86_64-w64-mingw32 path/to/libwebm
+
diff --git a/build/cxx_flags.cmake b/build/cxx_flags.cmake
new file mode 100644
index 0000000..9e96889
--- /dev/null
+++ b/build/cxx_flags.cmake
@@ -0,0 +1,73 @@
+## Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+##
+## Use of this source code is governed by a BSD-style license
+## that can be found in the LICENSE file in the root of the source
+## tree. An additional intellectual property rights grant can be found
+## in the file PATENTS. All contributing project authors may
+## be found in the AUTHORS file in the root of the source tree.
+cmake_minimum_required(VERSION 3.2)
+
+include(CheckCXXCompilerFlag)
+
+# String used to cache failed CXX flags.
+set(LIBWEBM_FAILED_CXX_FLAGS)
+
+# Checks C++ compiler for support of $cxx_flag. Adds $cxx_flag to
+# $CMAKE_CXX_FLAGS when the compile test passes. Caches $c_flag in
+# $LIBWEBM_FAILED_CXX_FLAGS when the test fails.
+function (add_cxx_flag_if_supported cxx_flag)
+ unset(CXX_FLAG_FOUND CACHE)
+ string(FIND "${CMAKE_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FOUND)
+ unset(CXX_FLAG_FAILED CACHE)
+ string(FIND "${LIBWEBM_FAILED_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FAILED)
+
+ if (${CXX_FLAG_FOUND} EQUAL -1 AND ${CXX_FLAG_FAILED} EQUAL -1)
+ unset(CXX_FLAG_SUPPORTED CACHE)
+ message("Checking CXX compiler flag support for: " ${cxx_flag})
+ check_cxx_compiler_flag("${cxx_flag}" CXX_FLAG_SUPPORTED)
+ if (CXX_FLAG_SUPPORTED)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${cxx_flag}" CACHE STRING ""
+ FORCE)
+ else ()
+ set(LIBWEBM_FAILED_CXX_FLAGS "${LIBWEBM_FAILED_CXX_FLAGS} ${cxx_flag}"
+ CACHE STRING "" FORCE)
+ endif ()
+ endif ()
+endfunction ()
+
+# Checks CXX compiler for support of $cxx_flag and terminates generation when
+# support is not present.
+function (require_cxx_flag cxx_flag)
+ unset(CXX_FLAG_FOUND CACHE)
+ string(FIND "${CMAKE_CXX_FLAGS}" "${cxx_flag}" CXX_FLAG_FOUND)
+
+ if (${CXX_FLAG_FOUND} EQUAL -1)
+ unset(LIBWEBM_HAVE_CXX_FLAG CACHE)
+ message("Checking CXX compiler flag support for: " ${cxx_flag})
+ check_cxx_compiler_flag("${cxx_flag}" LIBWEBM_HAVE_CXX_FLAG)
+ if (NOT LIBWEBM_HAVE_CXX_FLAG)
+ message(FATAL_ERROR
+ "${PROJECT_NAME} requires support for CXX flag: ${cxx_flag}.")
+ endif ()
+ set(CMAKE_CXX_FLAGS "${cxx_flag} ${CMAKE_CXX_FLAGS}" CACHE STRING "" FORCE)
+ endif ()
+endfunction ()
+
+# Checks only non-MSVC targets for support of $cxx_flag.
+function (require_cxx_flag_nomsvc cxx_flag)
+ if (NOT MSVC)
+ require_cxx_flag(${cxx_flag})
+ endif ()
+endfunction ()
+
+# Adds $preproc_def to CXX compiler command line (as -D$preproc_def) if not
+# already present.
+function (add_cxx_preproc_definition preproc_def)
+ unset(PREPROC_DEF_FOUND CACHE)
+ string(FIND "${CMAKE_CXX_FLAGS}" "${preproc_def}" PREPROC_DEF_FOUND)
+
+ if (${PREPROC_DEF_FOUND} EQUAL -1)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D${preproc_def}" CACHE STRING ""
+ FORCE)
+ endif ()
+endfunction ()
diff --git a/build/msvc_runtime.cmake b/build/msvc_runtime.cmake
new file mode 100644
index 0000000..d7d7add
--- /dev/null
+++ b/build/msvc_runtime.cmake
@@ -0,0 +1,23 @@
+## Copyright (c) 2015 The WebM project authors. All Rights Reserved.
+##
+## Use of this source code is governed by a BSD-style license
+## that can be found in the LICENSE file in the root of the source
+## tree. An additional intellectual property rights grant can be found
+## in the file PATENTS. All contributing project authors may
+## be found in the AUTHORS file in the root of the source tree.
+cmake_minimum_required(VERSION 2.8)
+
+if (MSVC)
+ # CMake defaults to producing code linked to the DLL MSVC runtime. In libwebm
+ # static is typically desired. Force static code generation unless the user
+ # running CMake set MSVC_RUNTIME to dll.
+ if (NOT "${MSVC_RUNTIME}" STREQUAL "dll")
+ foreach (flag_var
+ CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
+ CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
+ if (${flag_var} MATCHES "/MD")
+ string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+ endif (${flag_var} MATCHES "/MD")
+ endforeach (flag_var)
+ endif ()
+endif ()
diff --git a/build/x86-mingw-gcc.cmake b/build/x86-mingw-gcc.cmake
new file mode 100644
index 0000000..8416eaf
--- /dev/null
+++ b/build/x86-mingw-gcc.cmake
@@ -0,0 +1,26 @@
+## Copyright (c) 2017 The WebM project authors. All Rights Reserved.
+##
+## Use of this source code is governed by a BSD-style license
+## that can be found in the LICENSE file in the root of the source
+## tree. An additional intellectual property rights grant can be found
+## in the file PATENTS. All contributing project authors may
+## be found in the AUTHORS file in the root of the source tree.
+if (NOT LIBWEBM_BUILD_X86_MINGW_GCC_CMAKE_)
+set(LIBWEBM_BUILD_X86_MINGW_GCC_CMAKE_ 1)
+
+set(CMAKE_SYSTEM_PROCESSOR "x86")
+set(CMAKE_SYSTEM_NAME "Windows")
+set(CMAKE_C_COMPILER_ARG1 "-m32")
+set(CMAKE_CXX_COMPILER_ARG1 "-m32")
+
+if ("${CROSS}" STREQUAL "")
+ set(CROSS i686-w64-mingw32-)
+endif ()
+
+set(CMAKE_C_COMPILER ${CROSS}gcc)
+set(CMAKE_CXX_COMPILER ${CROSS}g++)
+
+# Disable googletest CMake usage for mingw cross compiles.
+set(LIBWEBM_DISABLE_GTEST_CMAKE 1)
+
+endif () # LIBWEBM_BUILD_X86_MINGW_GCC_CMAKE_
diff --git a/build/x86_64-mingw-gcc.cmake b/build/x86_64-mingw-gcc.cmake
new file mode 100644
index 0000000..9db28b7
--- /dev/null
+++ b/build/x86_64-mingw-gcc.cmake
@@ -0,0 +1,24 @@
+## Copyright (c) 2017 The WebM project authors. All Rights Reserved.
+##
+## Use of this source code is governed by a BSD-style license
+## that can be found in the LICENSE file in the root of the source
+## tree. An additional intellectual property rights grant can be found
+## in the file PATENTS. All contributing project authors may
+## be found in the AUTHORS file in the root of the source tree.
+if (NOT LIBWEBM_BUILD_X86_64_MINGW_GCC_CMAKE_)
+set(LIBWEBM_BUILD_X86_64_MINGW_GCC_CMAKE_ 1)
+
+set(CMAKE_SYSTEM_PROCESSOR "x86_64")
+set(CMAKE_SYSTEM_NAME "Windows")
+
+if ("${CROSS}" STREQUAL "")
+ set(CROSS x86_64-w64-mingw32-)
+endif ()
+
+set(CMAKE_C_COMPILER ${CROSS}gcc)
+set(CMAKE_CXX_COMPILER ${CROSS}g++)
+
+# Disable googletest CMake usage for mingw cross compiles.
+set(LIBWEBM_DISABLE_GTEST_CMAKE 1)
+
+endif () # LIBWEBM_BUILD_X86_64_MINGW_GCC_CMAKE_
diff --git a/codereview.settings b/codereview.settings
new file mode 100644
index 0000000..ccba2ee
--- /dev/null
+++ b/codereview.settings
@@ -0,0 +1,4 @@
+# This file is used by git cl to get repository specific information.
+GERRIT_HOST: True
+CODE_REVIEW_SERVER: chromium-review.googlesource.com
+GERRIT_SQUASH_UPLOADS: False
diff --git a/common/common.sh b/common/common.sh
new file mode 100644
index 0000000..49fec79
--- /dev/null
+++ b/common/common.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+##
+## Copyright (c) 2015 The WebM project authors. All Rights Reserved.
+##
+## Use of this source code is governed by a BSD-style license
+## that can be found in the LICENSE file in the root of the source
+## tree. An additional intellectual property rights grant can be found
+## in the file PATENTS. All contributing project authors may
+## be found in the AUTHORS file in the root of the source tree.
+##
+set -e
+devnull='> /dev/null 2>&1'
+
+readonly ORIG_PWD="$(pwd)"
+
+elog() {
+ echo "${0##*/} failed because: $@" 1>&2
+}
+
+vlog() {
+ if [ "${VERBOSE}" = "yes" ]; then
+ echo "$@"
+ fi
+}
+
+# Terminates script when name of current directory does not match $1.
+check_dir() {
+ current_dir="$(pwd)"
+ required_dir="$1"
+ if [ "${current_dir##*/}" != "${required_dir}" ]; then
+ elog "This script must be run from the ${required_dir} directory."
+ exit 1
+ fi
+}
+
+# Terminates the script when $1 is not in $PATH. Any arguments required for
+# the tool being tested to return a successful exit code can be passed as
+# additional arguments.
+check_tool() {
+ tool="$1"
+ shift
+ tool_args="$@"
+ if ! eval "${tool}" ${tool_args} > /dev/null 2>&1; then
+ elog "${tool} must be in your path."
+ exit 1
+ fi
+}
+
+# Echoes git describe output for the directory specified by $1 to stdout.
+git_describe() {
+ git_dir="$1"
+ check_git
+ echo $(git -C "${git_dir}" describe)
+}
+
+# Echoes current git revision for the directory specifed by $1 to stdout.
+git_revision() {
+ git_dir="$1"
+ check_git
+ echo $(git -C "${git_dir}" rev-parse HEAD)
+}
+
diff --git a/common/file_util.cc b/common/file_util.cc
new file mode 100644
index 0000000..6eb6428
--- /dev/null
+++ b/common/file_util.cc
@@ -0,0 +1,93 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "common/file_util.h"
+
+#include <sys/stat.h>
+#ifndef _MSC_VER
+#include <unistd.h> // close()
+#endif
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <ios>
+#include <string>
+
+namespace libwebm {
+
+std::string GetTempFileName() {
+#if !defined _MSC_VER && !defined __MINGW32__
+ std::string temp_file_name_template_str =
+ std::string(std::getenv("TEST_TMPDIR") ? std::getenv("TEST_TMPDIR")
+ : ".") +
+ "/libwebm_temp.XXXXXX";
+ char* temp_file_name_template =
+ new char[temp_file_name_template_str.length() + 1];
+ memset(temp_file_name_template, 0, temp_file_name_template_str.length() + 1);
+ temp_file_name_template_str.copy(temp_file_name_template,
+ temp_file_name_template_str.length(), 0);
+ int fd = mkstemp(temp_file_name_template);
+ std::string temp_file_name =
+ (fd != -1) ? std::string(temp_file_name_template) : std::string();
+ delete[] temp_file_name_template;
+ if (fd != -1) {
+ close(fd);
+ }
+ return temp_file_name;
+#else
+ char tmp_file_name[_MAX_PATH];
+#if defined _MSC_VER || defined MINGW_HAS_SECURE_API
+ errno_t err = tmpnam_s(tmp_file_name);
+#else
+ char* fname_pointer = tmpnam(tmp_file_name);
+ int err = (fname_pointer == &tmp_file_name[0]) ? 0 : -1;
+#endif
+ if (err == 0) {
+ return std::string(tmp_file_name);
+ }
+ return std::string();
+#endif
+}
+
+uint64_t GetFileSize(const std::string& file_name) {
+ uint64_t file_size = 0;
+#ifndef _MSC_VER
+ struct stat st;
+ st.st_size = 0;
+ if (stat(file_name.c_str(), &st) == 0) {
+#else
+ struct _stat st;
+ st.st_size = 0;
+ if (_stat(file_name.c_str(), &st) == 0) {
+#endif
+ file_size = st.st_size;
+ }
+ return file_size;
+}
+
+bool GetFileContents(const std::string& file_name, std::string* contents) {
+ std::ifstream file(file_name.c_str());
+ *contents = std::string(static_cast<size_t>(GetFileSize(file_name)), 0);
+ if (file.good() && contents->size()) {
+ file.read(&(*contents)[0], contents->size());
+ }
+ return !file.fail();
+}
+
+TempFileDeleter::TempFileDeleter() { file_name_ = GetTempFileName(); }
+
+TempFileDeleter::~TempFileDeleter() {
+ std::ifstream file(file_name_.c_str());
+ if (file.good()) {
+ file.close();
+ std::remove(file_name_.c_str());
+ }
+}
+
+} // namespace libwebm
diff --git a/common/file_util.h b/common/file_util.h
new file mode 100644
index 0000000..a873734
--- /dev/null
+++ b/common/file_util.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_COMMON_FILE_UTIL_H_
+#define LIBWEBM_COMMON_FILE_UTIL_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include "mkvmuxer/mkvmuxertypes.h" // LIBWEBM_DISALLOW_COPY_AND_ASSIGN()
+
+namespace libwebm {
+
+// Returns a temporary file name.
+std::string GetTempFileName();
+
+// Returns size of file specified by |file_name|, or 0 upon failure.
+uint64_t GetFileSize(const std::string& file_name);
+
+// Gets the contents file_name as a string. Returns false on error.
+bool GetFileContents(const std::string& file_name, std::string* contents);
+
+// Manages life of temporary file specified at time of construction. Deletes
+// file upon destruction.
+class TempFileDeleter {
+ public:
+ TempFileDeleter();
+ explicit TempFileDeleter(std::string file_name) : file_name_(file_name) {}
+ ~TempFileDeleter();
+ const std::string& name() const { return file_name_; }
+
+ private:
+ std::string file_name_;
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(TempFileDeleter);
+};
+
+} // namespace libwebm
+
+#endif // LIBWEBM_COMMON_FILE_UTIL_H_
diff --git a/common/hdr_util.cc b/common/hdr_util.cc
new file mode 100644
index 0000000..916f717
--- /dev/null
+++ b/common/hdr_util.cc
@@ -0,0 +1,220 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "hdr_util.h"
+
+#include <climits>
+#include <cstddef>
+#include <new>
+
+#include "mkvparser/mkvparser.h"
+
+namespace libwebm {
+const int Vp9CodecFeatures::kValueNotPresent = INT_MAX;
+
+bool CopyPrimaryChromaticity(const mkvparser::PrimaryChromaticity& parser_pc,
+ PrimaryChromaticityPtr* muxer_pc) {
+ muxer_pc->reset(new (std::nothrow)
+ mkvmuxer::PrimaryChromaticity(parser_pc.x, parser_pc.y));
+ if (!muxer_pc->get())
+ return false;
+ return true;
+}
+
+bool MasteringMetadataValuePresent(double value) {
+ return value != mkvparser::MasteringMetadata::kValueNotPresent;
+}
+
+bool CopyMasteringMetadata(const mkvparser::MasteringMetadata& parser_mm,
+ mkvmuxer::MasteringMetadata* muxer_mm) {
+ if (MasteringMetadataValuePresent(parser_mm.luminance_max))
+ muxer_mm->set_luminance_max(parser_mm.luminance_max);
+ if (MasteringMetadataValuePresent(parser_mm.luminance_min))
+ muxer_mm->set_luminance_min(parser_mm.luminance_min);
+
+ PrimaryChromaticityPtr r_ptr(nullptr);
+ PrimaryChromaticityPtr g_ptr(nullptr);
+ PrimaryChromaticityPtr b_ptr(nullptr);
+ PrimaryChromaticityPtr wp_ptr(nullptr);
+
+ if (parser_mm.r) {
+ if (!CopyPrimaryChromaticity(*parser_mm.r, &r_ptr))
+ return false;
+ }
+ if (parser_mm.g) {
+ if (!CopyPrimaryChromaticity(*parser_mm.g, &g_ptr))
+ return false;
+ }
+ if (parser_mm.b) {
+ if (!CopyPrimaryChromaticity(*parser_mm.b, &b_ptr))
+ return false;
+ }
+ if (parser_mm.white_point) {
+ if (!CopyPrimaryChromaticity(*parser_mm.white_point, &wp_ptr))
+ return false;
+ }
+
+ if (!muxer_mm->SetChromaticity(r_ptr.get(), g_ptr.get(), b_ptr.get(),
+ wp_ptr.get())) {
+ return false;
+ }
+
+ return true;
+}
+
+bool ColourValuePresent(long long value) {
+ return value != mkvparser::Colour::kValueNotPresent;
+}
+
+bool CopyColour(const mkvparser::Colour& parser_colour,
+ mkvmuxer::Colour* muxer_colour) {
+ if (!muxer_colour)
+ return false;
+
+ if (ColourValuePresent(parser_colour.matrix_coefficients))
+ muxer_colour->set_matrix_coefficients(parser_colour.matrix_coefficients);
+ if (ColourValuePresent(parser_colour.bits_per_channel))
+ muxer_colour->set_bits_per_channel(parser_colour.bits_per_channel);
+ if (ColourValuePresent(parser_colour.chroma_subsampling_horz)) {
+ muxer_colour->set_chroma_subsampling_horz(
+ parser_colour.chroma_subsampling_horz);
+ }
+ if (ColourValuePresent(parser_colour.chroma_subsampling_vert)) {
+ muxer_colour->set_chroma_subsampling_vert(
+ parser_colour.chroma_subsampling_vert);
+ }
+ if (ColourValuePresent(parser_colour.cb_subsampling_horz))
+ muxer_colour->set_cb_subsampling_horz(parser_colour.cb_subsampling_horz);
+ if (ColourValuePresent(parser_colour.cb_subsampling_vert))
+ muxer_colour->set_cb_subsampling_vert(parser_colour.cb_subsampling_vert);
+ if (ColourValuePresent(parser_colour.chroma_siting_horz))
+ muxer_colour->set_chroma_siting_horz(parser_colour.chroma_siting_horz);
+ if (ColourValuePresent(parser_colour.chroma_siting_vert))
+ muxer_colour->set_chroma_siting_vert(parser_colour.chroma_siting_vert);
+ if (ColourValuePresent(parser_colour.range))
+ muxer_colour->set_range(parser_colour.range);
+ if (ColourValuePresent(parser_colour.transfer_characteristics)) {
+ muxer_colour->set_transfer_characteristics(
+ parser_colour.transfer_characteristics);
+ }
+ if (ColourValuePresent(parser_colour.primaries))
+ muxer_colour->set_primaries(parser_colour.primaries);
+ if (ColourValuePresent(parser_colour.max_cll))
+ muxer_colour->set_max_cll(parser_colour.max_cll);
+ if (ColourValuePresent(parser_colour.max_fall))
+ muxer_colour->set_max_fall(parser_colour.max_fall);
+
+ if (parser_colour.mastering_metadata) {
+ mkvmuxer::MasteringMetadata muxer_mm;
+ if (!CopyMasteringMetadata(*parser_colour.mastering_metadata, &muxer_mm))
+ return false;
+ if (!muxer_colour->SetMasteringMetadata(muxer_mm))
+ return false;
+ }
+ return true;
+}
+
+// Format of VPx private data:
+//
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | ID Byte | Length | |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+// | |
+// : Bytes 1..Length of Codec Feature :
+// | |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+// ID Byte Format
+// ID byte is an unsigned byte.
+// 0 1 2 3 4 5 6 7
+// +-+-+-+-+-+-+-+-+
+// |X| ID |
+// +-+-+-+-+-+-+-+-+
+//
+// The X bit is reserved.
+//
+// See the following link for more information:
+// http://www.webmproject.org/vp9/profiles/
+bool ParseVpxCodecPrivate(const uint8_t* private_data, int32_t length,
+ Vp9CodecFeatures* features) {
+ const int kVpxCodecPrivateMinLength = 3;
+ if (!private_data || !features || length < kVpxCodecPrivateMinLength)
+ return false;
+
+ const uint8_t kVp9ProfileId = 1;
+ const uint8_t kVp9LevelId = 2;
+ const uint8_t kVp9BitDepthId = 3;
+ const uint8_t kVp9ChromaSubsamplingId = 4;
+ const int kVpxFeatureLength = 1;
+ int offset = 0;
+
+ // Set features to not set.
+ features->profile = Vp9CodecFeatures::kValueNotPresent;
+ features->level = Vp9CodecFeatures::kValueNotPresent;
+ features->bit_depth = Vp9CodecFeatures::kValueNotPresent;
+ features->chroma_subsampling = Vp9CodecFeatures::kValueNotPresent;
+ do {
+ const uint8_t id_byte = private_data[offset++];
+ const uint8_t length_byte = private_data[offset++];
+ if (length_byte != kVpxFeatureLength)
+ return false;
+ if (id_byte == kVp9ProfileId) {
+ const int priv_profile = static_cast<int>(private_data[offset++]);
+ if (priv_profile < 0 || priv_profile > 3)
+ return false;
+ if (features->profile != Vp9CodecFeatures::kValueNotPresent &&
+ features->profile != priv_profile) {
+ return false;
+ }
+ features->profile = priv_profile;
+ } else if (id_byte == kVp9LevelId) {
+ const int priv_level = static_cast<int>(private_data[offset++]);
+
+ const int kNumLevels = 14;
+ const int levels[kNumLevels] = {10, 11, 20, 21, 30, 31, 40,
+ 41, 50, 51, 52, 60, 61, 62};
+
+ for (int i = 0; i < kNumLevels; ++i) {
+ if (priv_level == levels[i]) {
+ if (features->level != Vp9CodecFeatures::kValueNotPresent &&
+ features->level != priv_level) {
+ return false;
+ }
+ features->level = priv_level;
+ break;
+ }
+ }
+ if (features->level == Vp9CodecFeatures::kValueNotPresent)
+ return false;
+ } else if (id_byte == kVp9BitDepthId) {
+ const int priv_profile = static_cast<int>(private_data[offset++]);
+ if (priv_profile != 8 && priv_profile != 10 && priv_profile != 12)
+ return false;
+ if (features->bit_depth != Vp9CodecFeatures::kValueNotPresent &&
+ features->bit_depth != priv_profile) {
+ return false;
+ }
+ features->bit_depth = priv_profile;
+ } else if (id_byte == kVp9ChromaSubsamplingId) {
+ const int priv_profile = static_cast<int>(private_data[offset++]);
+ if (priv_profile != 0 && priv_profile != 2 && priv_profile != 3)
+ return false;
+ if (features->chroma_subsampling != Vp9CodecFeatures::kValueNotPresent &&
+ features->chroma_subsampling != priv_profile) {
+ return false;
+ }
+ features->chroma_subsampling = priv_profile;
+ } else {
+ // Invalid ID.
+ return false;
+ }
+ } while (offset + kVpxCodecPrivateMinLength <= length);
+
+ return true;
+}
+} // namespace libwebm
diff --git a/common/hdr_util.h b/common/hdr_util.h
new file mode 100644
index 0000000..78e2eeb
--- /dev/null
+++ b/common/hdr_util.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_COMMON_HDR_UTIL_H_
+#define LIBWEBM_COMMON_HDR_UTIL_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "mkvmuxer/mkvmuxer.h"
+
+namespace mkvparser {
+struct Colour;
+struct MasteringMetadata;
+struct PrimaryChromaticity;
+} // namespace mkvparser
+
+namespace libwebm {
+// Utility types and functions for working with the Colour element and its
+// children. Copiers return true upon success. Presence functions return true
+// when the specified element is present.
+
+// TODO(tomfinegan): These should be moved to libwebm_utils once c++11 is
+// required by libwebm.
+
+// Features of the VP9 codec that may be set in the CodecPrivate of a VP9 video
+// stream. A value of kValueNotPresent represents that the value was not set in
+// the CodecPrivate.
+struct Vp9CodecFeatures {
+ static const int kValueNotPresent;
+
+ Vp9CodecFeatures()
+ : profile(kValueNotPresent),
+ level(kValueNotPresent),
+ bit_depth(kValueNotPresent),
+ chroma_subsampling(kValueNotPresent) {}
+ ~Vp9CodecFeatures() {}
+
+ int profile;
+ int level;
+ int bit_depth;
+ int chroma_subsampling;
+};
+
+typedef std::unique_ptr<mkvmuxer::PrimaryChromaticity> PrimaryChromaticityPtr;
+
+bool CopyPrimaryChromaticity(const mkvparser::PrimaryChromaticity& parser_pc,
+ PrimaryChromaticityPtr* muxer_pc);
+
+bool MasteringMetadataValuePresent(double value);
+
+bool CopyMasteringMetadata(const mkvparser::MasteringMetadata& parser_mm,
+ mkvmuxer::MasteringMetadata* muxer_mm);
+
+bool ColourValuePresent(long long value);
+
+bool CopyColour(const mkvparser::Colour& parser_colour,
+ mkvmuxer::Colour* muxer_colour);
+
+// Returns true if |features| is set to one or more valid values.
+bool ParseVpxCodecPrivate(const uint8_t* private_data, int32_t length,
+ Vp9CodecFeatures* features);
+
+} // namespace libwebm
+
+#endif // LIBWEBM_COMMON_HDR_UTIL_H_
diff --git a/common/indent.cc b/common/indent.cc
new file mode 100644
index 0000000..87d656c
--- /dev/null
+++ b/common/indent.cc
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "common/indent.h"
+
+#include <string>
+
+namespace libwebm {
+
+Indent::Indent(int indent) : indent_(indent), indent_str_() { Update(); }
+
+void Indent::Adjust(int indent) {
+ indent_ += indent;
+ if (indent_ < 0)
+ indent_ = 0;
+
+ Update();
+}
+
+void Indent::Update() { indent_str_ = std::string(indent_, ' '); }
+
+} // namespace libwebm
diff --git a/common/indent.h b/common/indent.h
new file mode 100644
index 0000000..22d3d26
--- /dev/null
+++ b/common/indent.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef LIBWEBM_COMMON_INDENT_H_
+#define LIBWEBM_COMMON_INDENT_H_
+
+#include <string>
+
+#include "mkvmuxer/mkvmuxertypes.h"
+
+namespace libwebm {
+
+const int kIncreaseIndent = 2;
+const int kDecreaseIndent = -2;
+
+// Used for formatting output so objects only have to keep track of spacing
+// within their scope.
+class Indent {
+ public:
+ explicit Indent(int indent);
+
+ // Changes the number of spaces output. The value adjusted is relative to
+ // |indent_|.
+ void Adjust(int indent);
+
+ std::string indent_str() const { return indent_str_; }
+
+ private:
+ // Called after |indent_| is changed. This will set |indent_str_| to the
+ // proper number of spaces.
+ void Update();
+
+ int indent_;
+ std::string indent_str_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Indent);
+};
+
+} // namespace libwebm
+
+#endif // LIBWEBM_COMMON_INDENT_H_
diff --git a/common/libwebm_util.cc b/common/libwebm_util.cc
new file mode 100644
index 0000000..d158250
--- /dev/null
+++ b/common/libwebm_util.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2015 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "common/libwebm_util.h"
+
+#include <cassert>
+#include <cstdint>
+#include <cstdio>
+#include <limits>
+
+namespace libwebm {
+
+std::int64_t NanosecondsTo90KhzTicks(std::int64_t nanoseconds) {
+ const double pts_seconds = nanoseconds / kNanosecondsPerSecond;
+ return static_cast<std::int64_t>(pts_seconds * 90000);
+}
+
+std::int64_t Khz90TicksToNanoseconds(std::int64_t ticks) {
+ const double seconds = ticks / 90000.0;
+ return static_cast<std::int64_t>(seconds * kNanosecondsPerSecond);
+}
+
+bool ParseVP9SuperFrameIndex(const std::uint8_t* frame,
+ std::size_t frame_length, Ranges* frame_ranges,
+ bool* error) {
+ if (frame == nullptr || frame_length == 0 || frame_ranges == nullptr ||
+ error == nullptr) {
+ return false;
+ }
+
+ bool parse_ok = false;
+ const std::uint8_t marker = frame[frame_length - 1];
+ const std::uint32_t kHasSuperFrameIndexMask = 0xe0;
+ const std::uint32_t kSuperFrameMarker = 0xc0;
+ const std::uint32_t kLengthFieldSizeMask = 0x3;
+
+ if ((marker & kHasSuperFrameIndexMask) == kSuperFrameMarker) {
+ const std::uint32_t kFrameCountMask = 0x7;
+ const int num_frames = (marker & kFrameCountMask) + 1;
+ const int length_field_size = ((marker >> 3) & kLengthFieldSizeMask) + 1;
+ const std::size_t index_length = 2 + length_field_size * num_frames;
+
+ if (frame_length < index_length) {
+ std::fprintf(stderr,
+ "VP9ParseSuperFrameIndex: Invalid superframe index size.\n");
+ *error = true;
+ return false;
+ }
+
+ // Consume the super frame index. Note: it's at the end of the super frame.
+ const std::size_t length = frame_length - index_length;
+
+ if (length >= index_length &&
+ frame[frame_length - index_length] == marker) {
+ // Found a valid superframe index.
+ const std::uint8_t* byte = frame + length + 1;
+
+ std::size_t frame_offset = 0;
+
+ for (int i = 0; i < num_frames; ++i) {
+ std::uint32_t child_frame_length = 0;
+
+ for (int j = 0; j < length_field_size; ++j) {
+ child_frame_length |= (*byte++) << (j * 8);
+ }
+
+ if (length - frame_offset < child_frame_length) {
+ std::fprintf(stderr,
+ "ParseVP9SuperFrameIndex: Invalid superframe, sub frame "
+ "larger than entire frame.\n");
+ *error = true;
+ return false;
+ }
+
+ frame_ranges->push_back(Range(frame_offset, child_frame_length));
+ frame_offset += child_frame_length;
+ }
+
+ if (static_cast<int>(frame_ranges->size()) != num_frames) {
+ std::fprintf(stderr, "VP9Parse: superframe index parse failed.\n");
+ *error = true;
+ return false;
+ }
+
+ parse_ok = true;
+ } else {
+ std::fprintf(stderr, "VP9Parse: Invalid superframe index.\n");
+ *error = true;
+ }
+ }
+ return parse_ok;
+}
+
+bool WriteUint8(std::uint8_t val, std::FILE* fileptr) {
+ if (fileptr == nullptr)
+ return false;
+ return (std::fputc(val, fileptr) == val);
+}
+
+std::uint16_t ReadUint16(const std::uint8_t* buf) {
+ if (buf == nullptr)
+ return 0;
+ return ((buf[0] << 8) | buf[1]);
+}
+
+} // namespace libwebm
diff --git a/common/libwebm_util.h b/common/libwebm_util.h
new file mode 100644
index 0000000..71d805f
--- /dev/null
+++ b/common/libwebm_util.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2015 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_COMMON_LIBWEBM_UTIL_H_
+#define LIBWEBM_COMMON_LIBWEBM_UTIL_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <memory>
+#include <vector>
+
+namespace libwebm {
+
+const double kNanosecondsPerSecond = 1000000000.0;
+
+// fclose functor for wrapping FILE in std::unique_ptr.
+// TODO(tomfinegan): Move this to file_util once c++11 restrictions are
+// relaxed.
+struct FILEDeleter {
+ int operator()(std::FILE* f) {
+ if (f != nullptr)
+ return fclose(f);
+ return 0;
+ }
+};
+typedef std::unique_ptr<std::FILE, FILEDeleter> FilePtr;
+
+struct Range {
+ Range(std::size_t off, std::size_t len) : offset(off), length(len) {}
+ Range() = delete;
+ Range(const Range&) = default;
+ Range(Range&&) = default;
+ ~Range() = default;
+ const std::size_t offset;
+ const std::size_t length;
+};
+typedef std::vector<Range> Ranges;
+
+// Converts |nanoseconds| to 90000 Hz clock ticks and vice versa. Each return
+// the converted value.
+std::int64_t NanosecondsTo90KhzTicks(std::int64_t nanoseconds);
+std::int64_t Khz90TicksToNanoseconds(std::int64_t khz90_ticks);
+
+// Returns true and stores frame offsets and lengths in |frame_ranges| when
+// |frame| has a valid VP9 super frame index. Sets |error| to true when parsing
+// fails for any reason.
+bool ParseVP9SuperFrameIndex(const std::uint8_t* frame,
+ std::size_t frame_length, Ranges* frame_ranges,
+ bool* error);
+
+// Writes |val| to |fileptr| and returns true upon success.
+bool WriteUint8(std::uint8_t val, std::FILE* fileptr);
+
+// Reads 2 bytes from |buf| and returns them as a uint16_t. Returns 0 when |buf|
+// is a nullptr.
+std::uint16_t ReadUint16(const std::uint8_t* buf);
+
+} // namespace libwebm
+
+#endif // LIBWEBM_COMMON_LIBWEBM_UTIL_H_
diff --git a/common/video_frame.cc b/common/video_frame.cc
new file mode 100644
index 0000000..00aa8da
--- /dev/null
+++ b/common/video_frame.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "common/video_frame.h"
+
+#include <cstdio>
+
+namespace libwebm {
+
+bool VideoFrame::Buffer::Init(std::size_t new_length) {
+ capacity = 0;
+ length = 0;
+ data.reset(new std::uint8_t[new_length]);
+
+ if (data.get() == nullptr) {
+ fprintf(stderr, "VideoFrame: Out of memory.");
+ return false;
+ }
+
+ capacity = new_length;
+ length = 0;
+ return true;
+}
+
+bool VideoFrame::Init(std::size_t length) { return buffer_.Init(length); }
+
+bool VideoFrame::Init(std::size_t length, std::int64_t nano_pts, Codec codec) {
+ nanosecond_pts_ = nano_pts;
+ codec_ = codec;
+ return Init(length);
+}
+
+bool VideoFrame::SetBufferLength(std::size_t length) {
+ if (length > buffer_.capacity || buffer_.data.get() == nullptr)
+ return false;
+
+ buffer_.length = length;
+ return true;
+}
+
+} // namespace libwebm
diff --git a/common/video_frame.h b/common/video_frame.h
new file mode 100644
index 0000000..3031ef2
--- /dev/null
+++ b/common/video_frame.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_COMMON_VIDEO_FRAME_H_
+#define LIBWEBM_COMMON_VIDEO_FRAME_H_
+
+#include <cstdint>
+#include <memory>
+
+namespace libwebm {
+
+// VideoFrame is a storage class for compressed video frames.
+class VideoFrame {
+ public:
+ enum Codec { kVP8, kVP9 };
+ struct Buffer {
+ Buffer() = default;
+ ~Buffer() = default;
+
+ // Resets |data| to be of size |new_length| bytes, sets |capacity| to
+ // |new_length|, sets |length| to 0 (aka empty). Returns true for success.
+ bool Init(std::size_t new_length);
+
+ std::unique_ptr<std::uint8_t[]> data;
+ std::size_t length = 0;
+ std::size_t capacity = 0;
+ };
+
+ VideoFrame() = default;
+ ~VideoFrame() = default;
+ VideoFrame(std::int64_t pts_in_nanoseconds, Codec vpx_codec)
+ : nanosecond_pts_(pts_in_nanoseconds), codec_(vpx_codec) {}
+ VideoFrame(bool keyframe, std::int64_t pts_in_nanoseconds, Codec vpx_codec)
+ : keyframe_(keyframe),
+ nanosecond_pts_(pts_in_nanoseconds),
+ codec_(vpx_codec) {}
+ bool Init(std::size_t length);
+ bool Init(std::size_t length, std::int64_t nano_pts, Codec codec);
+
+ // Updates actual length of data stored in |buffer_.data| when it's been
+ // written via the raw pointer returned from buffer_.data.get().
+ // Returns false when buffer_.data.get() return nullptr and/or when
+ // |length| > |buffer_.length|. Returns true otherwise.
+ bool SetBufferLength(std::size_t length);
+
+ // Accessors.
+ const Buffer& buffer() const { return buffer_; }
+ bool keyframe() const { return keyframe_; }
+ std::int64_t nanosecond_pts() const { return nanosecond_pts_; }
+ Codec codec() const { return codec_; }
+
+ // Mutators.
+ void set_nanosecond_pts(std::int64_t nano_pts) { nanosecond_pts_ = nano_pts; }
+
+ private:
+ Buffer buffer_;
+ bool keyframe_ = false;
+ std::int64_t nanosecond_pts_ = 0;
+ Codec codec_ = kVP9;
+};
+
+} // namespace libwebm
+
+#endif // LIBWEBM_COMMON_VIDEO_FRAME_H_ \ No newline at end of file
diff --git a/common/vp9_header_parser.cc b/common/vp9_header_parser.cc
new file mode 100644
index 0000000..604a050
--- /dev/null
+++ b/common/vp9_header_parser.cc
@@ -0,0 +1,266 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "common/vp9_header_parser.h"
+
+#include <stdio.h>
+
+namespace vp9_parser {
+
+bool Vp9HeaderParser::SetFrame(const uint8_t* frame, size_t length) {
+ if (!frame || length == 0)
+ return false;
+
+ frame_ = frame;
+ frame_size_ = length;
+ bit_offset_ = 0;
+ profile_ = -1;
+ show_existing_frame_ = 0;
+ key_ = 0;
+ altref_ = 0;
+ error_resilient_mode_ = 0;
+ intra_only_ = 0;
+ reset_frame_context_ = 0;
+ color_space_ = 0;
+ color_range_ = 0;
+ subsampling_x_ = 0;
+ subsampling_y_ = 0;
+ refresh_frame_flags_ = 0;
+ return true;
+}
+
+bool Vp9HeaderParser::ParseUncompressedHeader(const uint8_t* frame,
+ size_t length) {
+ if (!SetFrame(frame, length))
+ return false;
+ const int frame_marker = VpxReadLiteral(2);
+ if (frame_marker != kVp9FrameMarker) {
+ fprintf(stderr, "Invalid VP9 frame_marker:%d\n", frame_marker);
+ return false;
+ }
+
+ profile_ = ReadBit();
+ profile_ |= ReadBit() << 1;
+ if (profile_ > 2)
+ profile_ += ReadBit();
+
+ // TODO(fgalligan): Decide how to handle show existing frames.
+ show_existing_frame_ = ReadBit();
+ if (show_existing_frame_)
+ return true;
+
+ key_ = !ReadBit();
+ altref_ = !ReadBit();
+ error_resilient_mode_ = ReadBit();
+ if (key_) {
+ if (!ValidateVp9SyncCode()) {
+ fprintf(stderr, "Invalid Sync code!\n");
+ return false;
+ }
+
+ ParseColorSpace();
+ ParseFrameResolution();
+ ParseFrameParallelMode();
+ ParseTileInfo();
+ } else {
+ intra_only_ = altref_ ? ReadBit() : 0;
+ reset_frame_context_ = error_resilient_mode_ ? 0 : VpxReadLiteral(2);
+ if (intra_only_) {
+ if (!ValidateVp9SyncCode()) {
+ fprintf(stderr, "Invalid Sync code!\n");
+ return false;
+ }
+
+ if (profile_ > 0) {
+ ParseColorSpace();
+ } else {
+ // NOTE: The intra-only frame header does not include the specification
+ // of either the color format or color sub-sampling in profile 0. VP9
+ // specifies that the default color format should be YUV 4:2:0 in this
+ // case (normative).
+ color_space_ = kVpxCsBt601;
+ color_range_ = kVpxCrStudioRange;
+ subsampling_y_ = subsampling_x_ = 1;
+ bit_depth_ = 8;
+ }
+
+ refresh_frame_flags_ = VpxReadLiteral(kRefFrames);
+ ParseFrameResolution();
+ } else {
+ refresh_frame_flags_ = VpxReadLiteral(kRefFrames);
+ for (int i = 0; i < kRefsPerFrame; ++i) {
+ VpxReadLiteral(kRefFrames_LOG2); // Consume ref.
+ ReadBit(); // Consume ref sign bias.
+ }
+
+ bool found = false;
+ for (int i = 0; i < kRefsPerFrame; ++i) {
+ if (ReadBit()) {
+ // Found previous reference, width and height did not change since
+ // last frame.
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ ParseFrameResolution();
+ }
+ }
+ return true;
+}
+
+int Vp9HeaderParser::ReadBit() {
+ const size_t off = bit_offset_;
+ const size_t byte_offset = off >> 3;
+ const int bit_shift = 7 - static_cast<int>(off & 0x7);
+ if (byte_offset < frame_size_) {
+ const int bit = (frame_[byte_offset] >> bit_shift) & 1;
+ bit_offset_++;
+ return bit;
+ } else {
+ return 0;
+ }
+}
+
+int Vp9HeaderParser::VpxReadLiteral(int bits) {
+ int value = 0;
+ for (int bit = bits - 1; bit >= 0; --bit)
+ value |= ReadBit() << bit;
+ return value;
+}
+
+bool Vp9HeaderParser::ValidateVp9SyncCode() {
+ const int sync_code_0 = VpxReadLiteral(8);
+ const int sync_code_1 = VpxReadLiteral(8);
+ const int sync_code_2 = VpxReadLiteral(8);
+ return (sync_code_0 == 0x49 && sync_code_1 == 0x83 && sync_code_2 == 0x42);
+}
+
+void Vp9HeaderParser::ParseColorSpace() {
+ bit_depth_ = 0;
+ if (profile_ >= 2)
+ bit_depth_ = ReadBit() ? 12 : 10;
+ else
+ bit_depth_ = 8;
+ color_space_ = VpxReadLiteral(3);
+ if (color_space_ != kVpxCsSrgb) {
+ color_range_ = ReadBit();
+ if (profile_ == 1 || profile_ == 3) {
+ subsampling_x_ = ReadBit();
+ subsampling_y_ = ReadBit();
+ ReadBit();
+ } else {
+ subsampling_y_ = subsampling_x_ = 1;
+ }
+ } else {
+ color_range_ = kVpxCrFullRange;
+ if (profile_ == 1 || profile_ == 3) {
+ subsampling_y_ = subsampling_x_ = 0;
+ ReadBit();
+ }
+ }
+}
+
+void Vp9HeaderParser::ParseFrameResolution() {
+ width_ = VpxReadLiteral(16) + 1;
+ height_ = VpxReadLiteral(16) + 1;
+}
+
+void Vp9HeaderParser::ParseFrameParallelMode() {
+ if (ReadBit()) {
+ VpxReadLiteral(16); // display width
+ VpxReadLiteral(16); // display height
+ }
+ if (!error_resilient_mode_) {
+ ReadBit(); // Consume refresh frame context
+ frame_parallel_mode_ = ReadBit();
+ } else {
+ frame_parallel_mode_ = 1;
+ }
+}
+
+void Vp9HeaderParser::ParseTileInfo() {
+ VpxReadLiteral(2); // Consume frame context index
+
+ // loopfilter
+ VpxReadLiteral(6); // Consume filter level
+ VpxReadLiteral(3); // Consume sharpness level
+
+ const bool mode_ref_delta_enabled = ReadBit();
+ if (mode_ref_delta_enabled) {
+ const bool mode_ref_delta_update = ReadBit();
+ if (mode_ref_delta_update) {
+ const int kMaxRefLFDeltas = 4;
+ for (int i = 0; i < kMaxRefLFDeltas; ++i) {
+ if (ReadBit())
+ VpxReadLiteral(7); // Consume ref_deltas + sign
+ }
+
+ const int kMaxModeDeltas = 2;
+ for (int i = 0; i < kMaxModeDeltas; ++i) {
+ if (ReadBit())
+ VpxReadLiteral(7); // Consume mode_delta + sign
+ }
+ }
+ }
+
+ // quantization
+ VpxReadLiteral(8); // Consume base_q
+ SkipDeltaQ(); // y dc
+ SkipDeltaQ(); // uv ac
+ SkipDeltaQ(); // uv dc
+
+ // segmentation
+ const bool segmentation_enabled = ReadBit();
+ if (!segmentation_enabled) {
+ const int aligned_width = AlignPowerOfTwo(width_, kMiSizeLog2);
+ const int mi_cols = aligned_width >> kMiSizeLog2;
+ const int aligned_mi_cols = AlignPowerOfTwo(mi_cols, kMiSizeLog2);
+ const int sb_cols = aligned_mi_cols >> 3; // to_sbs(mi_cols);
+ int min_log2_n_tiles, max_log2_n_tiles;
+
+ for (max_log2_n_tiles = 0;
+ (sb_cols >> max_log2_n_tiles) >= kMinTileWidthB64;
+ max_log2_n_tiles++) {
+ }
+ max_log2_n_tiles--;
+ if (max_log2_n_tiles < 0)
+ max_log2_n_tiles = 0;
+
+ for (min_log2_n_tiles = 0; (kMaxTileWidthB64 << min_log2_n_tiles) < sb_cols;
+ min_log2_n_tiles++) {
+ }
+
+ // columns
+ const int max_log2_tile_cols = max_log2_n_tiles;
+ const int min_log2_tile_cols = min_log2_n_tiles;
+ int max_ones = max_log2_tile_cols - min_log2_tile_cols;
+ int log2_tile_cols = min_log2_tile_cols;
+ while (max_ones-- && ReadBit())
+ log2_tile_cols++;
+
+ // rows
+ int log2_tile_rows = ReadBit();
+ if (log2_tile_rows)
+ log2_tile_rows += ReadBit();
+
+ row_tiles_ = 1 << log2_tile_rows;
+ column_tiles_ = 1 << log2_tile_cols;
+ }
+}
+
+void Vp9HeaderParser::SkipDeltaQ() {
+ if (ReadBit())
+ VpxReadLiteral(4);
+}
+
+int Vp9HeaderParser::AlignPowerOfTwo(int value, int n) {
+ return (((value) + ((1 << (n)) - 1)) & ~((1 << (n)) - 1));
+}
+
+} // namespace vp9_parser
diff --git a/common/vp9_header_parser.h b/common/vp9_header_parser.h
new file mode 100644
index 0000000..06bd656
--- /dev/null
+++ b/common/vp9_header_parser.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_COMMON_VP9_HEADER_PARSER_H_
+#define LIBWEBM_COMMON_VP9_HEADER_PARSER_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace vp9_parser {
+
+const int kVp9FrameMarker = 2;
+const int kMinTileWidthB64 = 4;
+const int kMaxTileWidthB64 = 64;
+const int kRefFrames = 8;
+const int kRefsPerFrame = 3;
+const int kRefFrames_LOG2 = 3;
+const int kVpxCsBt601 = 1;
+const int kVpxCsSrgb = 7;
+const int kVpxCrStudioRange = 0;
+const int kVpxCrFullRange = 1;
+const int kMiSizeLog2 = 3;
+
+// Class to parse the header of a VP9 frame.
+class Vp9HeaderParser {
+ public:
+ Vp9HeaderParser()
+ : frame_(NULL),
+ frame_size_(0),
+ bit_offset_(0),
+ profile_(-1),
+ show_existing_frame_(0),
+ key_(0),
+ altref_(0),
+ error_resilient_mode_(0),
+ intra_only_(0),
+ reset_frame_context_(0),
+ bit_depth_(0),
+ color_space_(0),
+ color_range_(0),
+ subsampling_x_(0),
+ subsampling_y_(0),
+ refresh_frame_flags_(0),
+ width_(0),
+ height_(0),
+ row_tiles_(0),
+ column_tiles_(0),
+ frame_parallel_mode_(0) {}
+
+ // Parse the VP9 uncompressed header. The return values of the remaining
+ // functions are only valid on success.
+ bool ParseUncompressedHeader(const uint8_t* frame, size_t length);
+
+ size_t frame_size() const { return frame_size_; }
+ int profile() const { return profile_; }
+ int key() const { return key_; }
+ int altref() const { return altref_; }
+ int error_resilient_mode() const { return error_resilient_mode_; }
+ int bit_depth() const { return bit_depth_; }
+ int color_space() const { return color_space_; }
+ int width() const { return width_; }
+ int height() const { return height_; }
+ int refresh_frame_flags() const { return refresh_frame_flags_; }
+ int row_tiles() const { return row_tiles_; }
+ int column_tiles() const { return column_tiles_; }
+ int frame_parallel_mode() const { return frame_parallel_mode_; }
+
+ private:
+ // Set the compressed VP9 frame.
+ bool SetFrame(const uint8_t* frame, size_t length);
+
+ // Returns the next bit of the frame.
+ int ReadBit();
+
+ // Returns the next |bits| of the frame.
+ int VpxReadLiteral(int bits);
+
+ // Returns true if the vp9 sync code is valid.
+ bool ValidateVp9SyncCode();
+
+ // Parses bit_depth_, color_space_, subsampling_x_, subsampling_y_, and
+ // color_range_.
+ void ParseColorSpace();
+
+ // Parses width and height of the frame.
+ void ParseFrameResolution();
+
+ // Parses frame_parallel_mode_. This function skips over some features.
+ void ParseFrameParallelMode();
+
+ // Parses row and column tiles. This function skips over some features.
+ void ParseTileInfo();
+ void SkipDeltaQ();
+ int AlignPowerOfTwo(int value, int n);
+
+ const uint8_t* frame_;
+ size_t frame_size_;
+ size_t bit_offset_;
+ int profile_;
+ int show_existing_frame_;
+ int key_;
+ int altref_;
+ int error_resilient_mode_;
+ int intra_only_;
+ int reset_frame_context_;
+ int bit_depth_;
+ int color_space_;
+ int color_range_;
+ int subsampling_x_;
+ int subsampling_y_;
+ int refresh_frame_flags_;
+ int width_;
+ int height_;
+ int row_tiles_;
+ int column_tiles_;
+ int frame_parallel_mode_;
+};
+
+} // namespace vp9_parser
+
+#endif // LIBWEBM_COMMON_VP9_HEADER_PARSER_H_
diff --git a/common/vp9_header_parser_tests.cc b/common/vp9_header_parser_tests.cc
new file mode 100644
index 0000000..d13bff2
--- /dev/null
+++ b/common/vp9_header_parser_tests.cc
@@ -0,0 +1,181 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "common/vp9_header_parser.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "common/hdr_util.h"
+#include "mkvparser/mkvparser.h"
+#include "mkvparser/mkvreader.h"
+#include "testing/test_util.h"
+
+namespace {
+
+class Vp9HeaderParserTests : public ::testing::Test {
+ public:
+ Vp9HeaderParserTests() : is_reader_open_(false), segment_(NULL) {}
+
+ ~Vp9HeaderParserTests() override {
+ CloseReader();
+ if (segment_ != NULL) {
+ delete segment_;
+ segment_ = NULL;
+ }
+ }
+
+ void CloseReader() {
+ if (is_reader_open_) {
+ reader_.Close();
+ }
+ is_reader_open_ = false;
+ }
+
+ void CreateAndLoadSegment(const std::string& filename,
+ int expected_doc_type_ver) {
+ filename_ = test::GetTestFilePath(filename);
+ ASSERT_EQ(0, reader_.Open(filename_.c_str()));
+ is_reader_open_ = true;
+ pos_ = 0;
+ mkvparser::EBMLHeader ebml_header;
+ ebml_header.Parse(&reader_, pos_);
+ ASSERT_EQ(1, ebml_header.m_version);
+ ASSERT_EQ(1, ebml_header.m_readVersion);
+ ASSERT_STREQ("webm", ebml_header.m_docType);
+ ASSERT_EQ(expected_doc_type_ver, ebml_header.m_docTypeVersion);
+ ASSERT_EQ(2, ebml_header.m_docTypeReadVersion);
+ ASSERT_EQ(0, mkvparser::Segment::CreateInstance(&reader_, pos_, segment_));
+ ASSERT_FALSE(HasFailure());
+ ASSERT_GE(0, segment_->Load());
+ }
+
+ void CreateAndLoadSegment(const std::string& filename) {
+ CreateAndLoadSegment(filename, 4);
+ }
+
+ // Load a corrupted segment with no expectation of correctness.
+ void CreateAndLoadInvalidSegment(const std::string& filename) {
+ filename_ = test::GetTestFilePath(filename);
+ ASSERT_EQ(0, reader_.Open(filename_.c_str()));
+ is_reader_open_ = true;
+ pos_ = 0;
+ mkvparser::EBMLHeader ebml_header;
+ ebml_header.Parse(&reader_, pos_);
+ ASSERT_EQ(0, mkvparser::Segment::CreateInstance(&reader_, pos_, segment_));
+ ASSERT_GE(0, segment_->Load());
+ }
+
+ void ProcessTheFrames(bool invalid_bitstream) {
+ unsigned char* data = NULL;
+ size_t data_len = 0;
+ const mkvparser::Tracks* const parser_tracks = segment_->GetTracks();
+ ASSERT_TRUE(parser_tracks != NULL);
+ const mkvparser::Cluster* cluster = segment_->GetFirst();
+ ASSERT_TRUE(cluster != NULL);
+
+ while ((cluster != NULL) && !cluster->EOS()) {
+ const mkvparser::BlockEntry* block_entry;
+ long status = cluster->GetFirst(block_entry); // NOLINT
+ ASSERT_EQ(0, status);
+
+ while ((block_entry != NULL) && !block_entry->EOS()) {
+ const mkvparser::Block* const block = block_entry->GetBlock();
+ ASSERT_TRUE(block != NULL);
+ const long long trackNum = block->GetTrackNumber(); // NOLINT
+ const mkvparser::Track* const parser_track =
+ parser_tracks->GetTrackByNumber(
+ static_cast<unsigned long>(trackNum)); // NOLINT
+ ASSERT_TRUE(parser_track != NULL);
+ const long long track_type = parser_track->GetType(); // NOLINT
+
+ if (track_type == mkvparser::Track::kVideo) {
+ const int frame_count = block->GetFrameCount();
+
+ for (int i = 0; i < frame_count; ++i) {
+ const mkvparser::Block::Frame& frame = block->GetFrame(i);
+
+ if (static_cast<size_t>(frame.len) > data_len) {
+ delete[] data;
+ data = new unsigned char[frame.len];
+ ASSERT_TRUE(data != NULL);
+ data_len = static_cast<size_t>(frame.len);
+ }
+ ASSERT_FALSE(frame.Read(&reader_, data));
+ ASSERT_EQ(parser_.ParseUncompressedHeader(data, data_len),
+ !invalid_bitstream);
+ }
+ }
+
+ status = cluster->GetNext(block_entry, block_entry);
+ ASSERT_EQ(0, status);
+ }
+
+ cluster = segment_->GetNext(cluster);
+ }
+ delete[] data;
+ }
+
+ protected:
+ mkvparser::MkvReader reader_;
+ bool is_reader_open_;
+ mkvparser::Segment* segment_;
+ std::string filename_;
+ long long pos_; // NOLINT
+ vp9_parser::Vp9HeaderParser parser_;
+};
+
+TEST_F(Vp9HeaderParserTests, VideoOnlyFile) {
+ ASSERT_NO_FATAL_FAILURE(CreateAndLoadSegment("test_stereo_left_right.webm"));
+ ProcessTheFrames(false);
+ EXPECT_EQ(256, parser_.width());
+ EXPECT_EQ(144, parser_.height());
+ EXPECT_EQ(1, parser_.column_tiles());
+ EXPECT_EQ(0, parser_.frame_parallel_mode());
+}
+
+TEST_F(Vp9HeaderParserTests, Muxed) {
+ ASSERT_NO_FATAL_FAILURE(
+ CreateAndLoadSegment("bbb_480p_vp9_opus_1second.webm", 4));
+ ProcessTheFrames(false);
+ EXPECT_EQ(854, parser_.width());
+ EXPECT_EQ(480, parser_.height());
+ EXPECT_EQ(2, parser_.column_tiles());
+ EXPECT_EQ(1, parser_.frame_parallel_mode());
+}
+
+TEST_F(Vp9HeaderParserTests, Invalid) {
+ const char* files[] = {
+ "invalid/invalid_vp9_bitstream-bug_1416.webm",
+ "invalid/invalid_vp9_bitstream-bug_1417.webm",
+ };
+
+ for (int i = 0; i < static_cast<int>(sizeof(files) / sizeof(files[0])); ++i) {
+ SCOPED_TRACE(files[i]);
+ ASSERT_NO_FATAL_FAILURE(CreateAndLoadInvalidSegment(files[i]));
+ ProcessTheFrames(true);
+ CloseReader();
+ delete segment_;
+ segment_ = NULL;
+ }
+}
+
+TEST_F(Vp9HeaderParserTests, API) {
+ vp9_parser::Vp9HeaderParser parser;
+ uint8_t data;
+ EXPECT_FALSE(parser.ParseUncompressedHeader(NULL, 0));
+ EXPECT_FALSE(parser.ParseUncompressedHeader(NULL, 10));
+ EXPECT_FALSE(parser.ParseUncompressedHeader(&data, 0));
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/common/vp9_level_stats.cc b/common/vp9_level_stats.cc
new file mode 100644
index 0000000..76891e6
--- /dev/null
+++ b/common/vp9_level_stats.cc
@@ -0,0 +1,269 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "common/vp9_level_stats.h"
+
+#include <inttypes.h>
+
+#include <limits>
+#include <utility>
+
+#include "common/webm_constants.h"
+
+namespace vp9_parser {
+
+const Vp9LevelRow Vp9LevelStats::Vp9LevelTable[kNumVp9Levels] = {
+ {LEVEL_1, 829440, 36864, 200, 400, 2, 1, 4, 8, 512},
+ {LEVEL_1_1, 2764800, 73728, 800, 1000, 2, 1, 4, 8, 768},
+ {LEVEL_2, 4608000, 122880, 1800, 1500, 2, 1, 4, 8, 960},
+ {LEVEL_2_1, 9216000, 245760, 3600, 2800, 2, 2, 4, 8, 1344},
+ {LEVEL_3, 20736000, 552960, 7200, 6000, 2, 4, 4, 8, 2048},
+ {LEVEL_3_1, 36864000, 983040, 12000, 10000, 2, 4, 4, 8, 2752},
+ {LEVEL_4, 83558400, 2228224, 18000, 16000, 4, 4, 4, 8, 4160},
+ {LEVEL_4_1, 160432128, 2228224, 30000, 18000, 4, 4, 5, 6, 4160},
+ {LEVEL_5, 311951360, 8912896, 60000, 36000, 6, 8, 6, 4, 8384},
+ {LEVEL_5_1, 588251136, 8912896, 120000, 46000, 8, 8, 10, 4, 8384},
+ // CPB Size = 0 for levels 5_2 to 6_2
+ {LEVEL_5_2, 1176502272, 8912896, 180000, 0, 8, 8, 10, 4, 8384},
+ {LEVEL_6, 1176502272, 35651584, 180000, 0, 8, 16, 10, 4, 16832},
+ {LEVEL_6_1, 2353004544, 35651584, 240000, 0, 8, 16, 10, 4, 16832},
+ {LEVEL_6_2, 4706009088, 35651584, 480000, 0, 8, 16, 10, 4, 16832}};
+
+void Vp9LevelStats::AddFrame(const Vp9HeaderParser& parser, int64_t time_ns) {
+ ++frames;
+ if (start_ns_ == -1)
+ start_ns_ = time_ns;
+ end_ns_ = time_ns;
+
+ const int width = parser.width();
+ const int height = parser.height();
+ const int64_t luma_picture_size = width * height;
+ const int64_t luma_picture_breadth = (width > height) ? width : height;
+ if (luma_picture_size > max_luma_picture_size_)
+ max_luma_picture_size_ = luma_picture_size;
+ if (luma_picture_breadth > max_luma_picture_breadth_)
+ max_luma_picture_breadth_ = luma_picture_breadth;
+
+ total_compressed_size_ += parser.frame_size();
+
+ while (!luma_window_.empty() &&
+ luma_window_.front().first <
+ (time_ns - (libwebm::kNanosecondsPerSecondi - 1))) {
+ current_luma_size_ -= luma_window_.front().second;
+ luma_window_.pop();
+ }
+ current_luma_size_ += luma_picture_size;
+ luma_window_.push(std::make_pair(time_ns, luma_picture_size));
+ if (current_luma_size_ > max_luma_size_) {
+ max_luma_size_ = current_luma_size_;
+ max_luma_end_ns_ = luma_window_.back().first;
+ }
+
+ // Record CPB stats.
+ // Remove all frames that are less than window size.
+ while (cpb_window_.size() > 3) {
+ current_cpb_size_ -= cpb_window_.front().second;
+ cpb_window_.pop();
+ }
+ cpb_window_.push(std::make_pair(time_ns, parser.frame_size()));
+
+ current_cpb_size_ += parser.frame_size();
+ if (current_cpb_size_ > max_cpb_size_) {
+ max_cpb_size_ = current_cpb_size_;
+ max_cpb_start_ns_ = cpb_window_.front().first;
+ max_cpb_end_ns_ = cpb_window_.back().first;
+ }
+
+ if (max_cpb_window_size_ < static_cast<int64_t>(cpb_window_.size())) {
+ max_cpb_window_size_ = cpb_window_.size();
+ max_cpb_window_end_ns_ = time_ns;
+ }
+
+ // Record altref stats.
+ if (parser.altref()) {
+ const int delta_altref = frames_since_last_altref;
+ if (first_altref) {
+ first_altref = false;
+ } else if (delta_altref < minimum_altref_distance) {
+ minimum_altref_distance = delta_altref;
+ min_altref_end_ns = time_ns;
+ }
+ frames_since_last_altref = 0;
+ } else {
+ ++frames_since_last_altref;
+ ++displayed_frames;
+ // TODO(fgalligan): Add support for other color formats. Currently assuming
+ // 420.
+ total_uncompressed_bits_ +=
+ (luma_picture_size * parser.bit_depth() * 3) / 2;
+ }
+
+ // Count max reference frames.
+ if (parser.key() == 1) {
+ frames_refreshed_ = 0;
+ } else {
+ frames_refreshed_ |= parser.refresh_frame_flags();
+
+ int ref_frame_count = frames_refreshed_ & 1;
+ for (int i = 1; i < kMaxVp9RefFrames; ++i) {
+ ref_frame_count += (frames_refreshed_ >> i) & 1;
+ }
+
+ if (ref_frame_count > max_frames_refreshed_)
+ max_frames_refreshed_ = ref_frame_count;
+ }
+
+ // Count max tiles.
+ const int tiles = parser.column_tiles();
+ if (tiles > max_column_tiles_)
+ max_column_tiles_ = tiles;
+}
+
+Vp9Level Vp9LevelStats::GetLevel() const {
+ const int64_t max_luma_sample_rate = GetMaxLumaSampleRate();
+ const int64_t max_luma_picture_size = GetMaxLumaPictureSize();
+ const int64_t max_luma_picture_breadth = GetMaxLumaPictureBreadth();
+ const double average_bitrate = GetAverageBitRate();
+ const double max_cpb_size = GetMaxCpbSize();
+ const double compresion_ratio = GetCompressionRatio();
+ const int max_column_tiles = GetMaxColumnTiles();
+ const int min_altref_distance = GetMinimumAltrefDistance();
+ const int max_ref_frames = GetMaxReferenceFrames();
+
+ int level_index = 0;
+ Vp9Level max_level = LEVEL_UNKNOWN;
+ const double grace_multiplier =
+ max_luma_sample_rate_grace_percent_ / 100.0 + 1.0;
+ for (int i = 0; i < kNumVp9Levels; ++i) {
+ if (max_luma_sample_rate <=
+ Vp9LevelTable[i].max_luma_sample_rate * grace_multiplier) {
+ if (max_level < Vp9LevelTable[i].level) {
+ max_level = Vp9LevelTable[i].level;
+ level_index = i;
+ }
+ break;
+ }
+ }
+ for (int i = 0; i < kNumVp9Levels; ++i) {
+ if (max_luma_picture_size <= Vp9LevelTable[i].max_luma_picture_size) {
+ if (max_level < Vp9LevelTable[i].level) {
+ max_level = Vp9LevelTable[i].level;
+ level_index = i;
+ }
+ break;
+ }
+ }
+ for (int i = 0; i < kNumVp9Levels; ++i) {
+ if (max_luma_picture_breadth <= Vp9LevelTable[i].max_luma_picture_breadth) {
+ if (max_level < Vp9LevelTable[i].level) {
+ max_level = Vp9LevelTable[i].level;
+ level_index = i;
+ }
+ break;
+ }
+ }
+ for (int i = 0; i < kNumVp9Levels; ++i) {
+ if (average_bitrate <= Vp9LevelTable[i].average_bitrate) {
+ if (max_level < Vp9LevelTable[i].level) {
+ max_level = Vp9LevelTable[i].level;
+ level_index = i;
+ }
+ break;
+ }
+ }
+ for (int i = 0; i < kNumVp9Levels; ++i) {
+ // Only check CPB size for levels that are defined.
+ if (Vp9LevelTable[i].max_cpb_size > 0 &&
+ max_cpb_size <= Vp9LevelTable[i].max_cpb_size) {
+ if (max_level < Vp9LevelTable[i].level) {
+ max_level = Vp9LevelTable[i].level;
+ level_index = i;
+ }
+ break;
+ }
+ }
+ for (int i = 0; i < kNumVp9Levels; ++i) {
+ if (max_column_tiles <= Vp9LevelTable[i].max_tiles) {
+ if (max_level < Vp9LevelTable[i].level) {
+ max_level = Vp9LevelTable[i].level;
+ level_index = i;
+ }
+ break;
+ }
+ }
+
+ for (int i = 0; i < kNumVp9Levels; ++i) {
+ if (max_ref_frames <= Vp9LevelTable[i].max_ref_frames) {
+ if (max_level < Vp9LevelTable[i].level) {
+ max_level = Vp9LevelTable[i].level;
+ level_index = i;
+ }
+ break;
+ }
+ }
+
+ // Check if the current level meets the minimum altref distance requirement.
+ // If not, set to unknown level as we can't move up a level as the minimum
+ // altref distance get farther apart and we can't move down a level as we are
+ // already at the minimum level for all the other requirements.
+ if (min_altref_distance < Vp9LevelTable[level_index].min_altref_distance)
+ max_level = LEVEL_UNKNOWN;
+
+ // The minimum compression ratio has the same behavior as minimum altref
+ // distance.
+ if (compresion_ratio < Vp9LevelTable[level_index].compresion_ratio)
+ max_level = LEVEL_UNKNOWN;
+ return max_level;
+}
+
+int64_t Vp9LevelStats::GetMaxLumaSampleRate() const { return max_luma_size_; }
+
+int64_t Vp9LevelStats::GetMaxLumaPictureSize() const {
+ return max_luma_picture_size_;
+}
+
+int64_t Vp9LevelStats::GetMaxLumaPictureBreadth() const {
+ return max_luma_picture_breadth_;
+}
+
+double Vp9LevelStats::GetAverageBitRate() const {
+ const int64_t frame_duration_ns = end_ns_ - start_ns_;
+ double duration_seconds =
+ ((duration_ns_ == -1) ? frame_duration_ns : duration_ns_) /
+ libwebm::kNanosecondsPerSecond;
+ if (estimate_last_frame_duration_ &&
+ (duration_ns_ == -1 || duration_ns_ <= frame_duration_ns)) {
+ const double sec_per_frame = frame_duration_ns /
+ libwebm::kNanosecondsPerSecond /
+ (displayed_frames - 1);
+ duration_seconds += sec_per_frame;
+ }
+
+ return total_compressed_size_ / duration_seconds / 125.0;
+}
+
+double Vp9LevelStats::GetMaxCpbSize() const { return max_cpb_size_ / 125.0; }
+
+double Vp9LevelStats::GetCompressionRatio() const {
+ return total_uncompressed_bits_ /
+ static_cast<double>(total_compressed_size_ * 8);
+}
+
+int Vp9LevelStats::GetMaxColumnTiles() const { return max_column_tiles_; }
+
+int Vp9LevelStats::GetMinimumAltrefDistance() const {
+ if (minimum_altref_distance != std::numeric_limits<int>::max())
+ return minimum_altref_distance;
+ else
+ return -1;
+}
+
+int Vp9LevelStats::GetMaxReferenceFrames() const {
+ return max_frames_refreshed_;
+}
+
+} // namespace vp9_parser
diff --git a/common/vp9_level_stats.h b/common/vp9_level_stats.h
new file mode 100644
index 0000000..45d6f5c
--- /dev/null
+++ b/common/vp9_level_stats.h
@@ -0,0 +1,215 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_COMMON_VP9_LEVEL_STATS_H_
+#define LIBWEBM_COMMON_VP9_LEVEL_STATS_H_
+
+#include <limits>
+#include <queue>
+#include <utility>
+
+#include "common/vp9_header_parser.h"
+
+namespace vp9_parser {
+
+const int kMaxVp9RefFrames = 8;
+
+// Defined VP9 levels. See http://www.webmproject.org/vp9/profiles/ for
+// detailed information on VP9 levels.
+const int kNumVp9Levels = 14;
+enum Vp9Level {
+ LEVEL_UNKNOWN = 0,
+ LEVEL_1 = 10,
+ LEVEL_1_1 = 11,
+ LEVEL_2 = 20,
+ LEVEL_2_1 = 21,
+ LEVEL_3 = 30,
+ LEVEL_3_1 = 31,
+ LEVEL_4 = 40,
+ LEVEL_4_1 = 41,
+ LEVEL_5 = 50,
+ LEVEL_5_1 = 51,
+ LEVEL_5_2 = 52,
+ LEVEL_6 = 60,
+ LEVEL_6_1 = 61,
+ LEVEL_6_2 = 62
+};
+
+struct Vp9LevelRow {
+ Vp9LevelRow() = default;
+ ~Vp9LevelRow() = default;
+ Vp9LevelRow(Vp9LevelRow&& other) = default;
+ Vp9LevelRow(const Vp9LevelRow& other) = default;
+ Vp9LevelRow& operator=(Vp9LevelRow&& other) = delete;
+ Vp9LevelRow& operator=(const Vp9LevelRow& other) = delete;
+
+ Vp9Level level;
+ int64_t max_luma_sample_rate;
+ int64_t max_luma_picture_size;
+ int64_t max_luma_picture_breadth;
+ double average_bitrate;
+ double max_cpb_size;
+ double compresion_ratio;
+ int max_tiles;
+ int min_altref_distance;
+ int max_ref_frames;
+};
+
+// Class to determine the VP9 level of a VP9 bitstream.
+class Vp9LevelStats {
+ public:
+ static const Vp9LevelRow Vp9LevelTable[kNumVp9Levels];
+
+ Vp9LevelStats()
+ : frames(0),
+ displayed_frames(0),
+ start_ns_(-1),
+ end_ns_(-1),
+ duration_ns_(-1),
+ max_luma_picture_size_(0),
+ max_luma_picture_breadth_(0),
+ current_luma_size_(0),
+ max_luma_size_(0),
+ max_luma_end_ns_(0),
+ max_luma_sample_rate_grace_percent_(1.5),
+ first_altref(true),
+ frames_since_last_altref(0),
+ minimum_altref_distance(std::numeric_limits<int>::max()),
+ min_altref_end_ns(0),
+ max_cpb_window_size_(0),
+ max_cpb_window_end_ns_(0),
+ current_cpb_size_(0),
+ max_cpb_size_(0),
+ max_cpb_start_ns_(0),
+ max_cpb_end_ns_(0),
+ total_compressed_size_(0),
+ total_uncompressed_bits_(0),
+ frames_refreshed_(0),
+ max_frames_refreshed_(0),
+ max_column_tiles_(0),
+ estimate_last_frame_duration_(true) {}
+
+ ~Vp9LevelStats() = default;
+ Vp9LevelStats(Vp9LevelStats&& other) = delete;
+ Vp9LevelStats(const Vp9LevelStats& other) = delete;
+ Vp9LevelStats& operator=(Vp9LevelStats&& other) = delete;
+ Vp9LevelStats& operator=(const Vp9LevelStats& other) = delete;
+
+ // Collects stats on a VP9 frame. The frame must already be parsed by
+ // |parser|. |time_ns| is the start time of the frame in nanoseconds.
+ void AddFrame(const Vp9HeaderParser& parser, int64_t time_ns);
+
+ // Returns the current VP9 level. All of the video frames should have been
+ // processed with AddFrame before calling this function.
+ Vp9Level GetLevel() const;
+
+ // Returns the maximum luma samples (pixels) per second. The Alt-Ref frames
+ // are taken into account, therefore this number may be larger than the
+ // display luma samples per second
+ int64_t GetMaxLumaSampleRate() const;
+
+ // The maximum frame size (width * height) in samples.
+ int64_t GetMaxLumaPictureSize() const;
+
+ // The maximum frame breadth (max of width and height) in samples.
+ int64_t GetMaxLumaPictureBreadth() const;
+
+ // The average bitrate of the video in kbps.
+ double GetAverageBitRate() const;
+
+ // The largest data size for any 4 consecutive frames in kilobits.
+ double GetMaxCpbSize() const;
+
+ // The ratio of total bytes decompressed over total bytes compressed.
+ double GetCompressionRatio() const;
+
+ // The maximum number of VP9 column tiles.
+ int GetMaxColumnTiles() const;
+
+ // The minimum distance in frames between two consecutive alternate reference
+ // frames.
+ int GetMinimumAltrefDistance() const;
+
+ // The maximum number of reference frames that had to be stored.
+ int GetMaxReferenceFrames() const;
+
+ // Sets the duration of the video stream in nanoseconds. If the duration is
+ // not explictly set by this function then this class will use end - start
+ // as the duration.
+ void set_duration(int64_t time_ns) { duration_ns_ = time_ns; }
+ double max_luma_sample_rate_grace_percent() const {
+ return max_luma_sample_rate_grace_percent_;
+ }
+ void set_max_luma_sample_rate_grace_percent(double percent) {
+ max_luma_sample_rate_grace_percent_ = percent;
+ }
+ bool estimate_last_frame_duration() const {
+ return estimate_last_frame_duration_;
+ }
+
+ // If true try to estimate the last frame's duration if the stream's duration
+ // is not set or the stream's duration equals the last frame's timestamp.
+ void set_estimate_last_frame_duration(bool flag) {
+ estimate_last_frame_duration_ = flag;
+ }
+
+ private:
+ int frames;
+ int displayed_frames;
+
+ int64_t start_ns_;
+ int64_t end_ns_;
+ int64_t duration_ns_;
+
+ int64_t max_luma_picture_size_;
+ int64_t max_luma_picture_breadth_;
+
+ // This is used to calculate the maximum number of luma samples per second.
+ // The first value is the luma picture size and the second value is the time
+ // in nanoseconds of one frame.
+ std::queue<std::pair<int64_t, int64_t>> luma_window_;
+ int64_t current_luma_size_;
+ int64_t max_luma_size_;
+ int64_t max_luma_end_ns_;
+
+ // MaxLumaSampleRate = (ExampleFrameRate + ExampleFrameRate /
+ // MinimumAltrefDistance) * MaxLumaPictureSize. For levels 1-4
+ // ExampleFrameRate / MinimumAltrefDistance is non-integer, so using a sliding
+ // window of one frame to calculate MaxLumaSampleRate may have frames >
+ // (ExampleFrameRate + ExampleFrameRate / MinimumAltrefDistance) in the
+ // window. In order to address this issue, a grace percent of 1.5 was added.
+ double max_luma_sample_rate_grace_percent_;
+
+ bool first_altref;
+ int frames_since_last_altref;
+ int minimum_altref_distance;
+ int64_t min_altref_end_ns;
+
+ // This is used to calculate the maximum number of compressed bytes for four
+ // consecutive frames. The first value is the compressed frame size and the
+ // second value is the time in nanoseconds of one frame.
+ std::queue<std::pair<int64_t, int64_t>> cpb_window_;
+ int64_t max_cpb_window_size_;
+ int64_t max_cpb_window_end_ns_;
+ int64_t current_cpb_size_;
+ int64_t max_cpb_size_;
+ int64_t max_cpb_start_ns_;
+ int64_t max_cpb_end_ns_;
+
+ int64_t total_compressed_size_;
+ int64_t total_uncompressed_bits_;
+ int frames_refreshed_;
+ int max_frames_refreshed_;
+
+ int max_column_tiles_;
+
+ bool estimate_last_frame_duration_;
+};
+
+} // namespace vp9_parser
+
+#endif // LIBWEBM_COMMON_VP9_LEVEL_STATS_H_
diff --git a/common/vp9_level_stats_tests.cc b/common/vp9_level_stats_tests.cc
new file mode 100644
index 0000000..0dec071
--- /dev/null
+++ b/common/vp9_level_stats_tests.cc
@@ -0,0 +1,191 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "common/vp9_level_stats.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "common/hdr_util.h"
+#include "common/vp9_header_parser.h"
+#include "mkvparser/mkvparser.h"
+#include "mkvparser/mkvreader.h"
+#include "testing/test_util.h"
+
+namespace {
+
+// TODO(fgalligan): Refactor this test with other test files in this directory.
+class Vp9LevelStatsTests : public ::testing::Test {
+ public:
+ Vp9LevelStatsTests() : is_reader_open_(false) {}
+
+ ~Vp9LevelStatsTests() override { CloseReader(); }
+
+ void CloseReader() {
+ if (is_reader_open_) {
+ reader_.Close();
+ }
+ is_reader_open_ = false;
+ }
+
+ void CreateAndLoadSegment(const std::string& filename,
+ int expected_doc_type_ver) {
+ ASSERT_NE(0u, filename.length());
+ filename_ = test::GetTestFilePath(filename);
+ ASSERT_EQ(0, reader_.Open(filename_.c_str()));
+ is_reader_open_ = true;
+ pos_ = 0;
+ mkvparser::EBMLHeader ebml_header;
+ ebml_header.Parse(&reader_, pos_);
+ ASSERT_EQ(1, ebml_header.m_version);
+ ASSERT_EQ(1, ebml_header.m_readVersion);
+ ASSERT_STREQ("webm", ebml_header.m_docType);
+ ASSERT_EQ(expected_doc_type_ver, ebml_header.m_docTypeVersion);
+ ASSERT_EQ(2, ebml_header.m_docTypeReadVersion);
+ mkvparser::Segment* temp;
+ ASSERT_EQ(0, mkvparser::Segment::CreateInstance(&reader_, pos_, temp));
+ segment_.reset(temp);
+ ASSERT_FALSE(HasFailure());
+ ASSERT_GE(0, segment_->Load());
+ }
+
+ void CreateAndLoadSegment(const std::string& filename) {
+ CreateAndLoadSegment(filename, 4);
+ }
+
+ void ProcessTheFrames() {
+ std::vector<uint8_t> data;
+ size_t data_len = 0;
+ const mkvparser::Tracks* const parser_tracks = segment_->GetTracks();
+ ASSERT_TRUE(parser_tracks != NULL);
+ const mkvparser::Cluster* cluster = segment_->GetFirst();
+ ASSERT_TRUE(cluster);
+
+ while ((cluster != NULL) && !cluster->EOS()) {
+ const mkvparser::BlockEntry* block_entry;
+ long status = cluster->GetFirst(block_entry); // NOLINT
+ ASSERT_EQ(0, status);
+
+ while ((block_entry != NULL) && !block_entry->EOS()) {
+ const mkvparser::Block* const block = block_entry->GetBlock();
+ ASSERT_TRUE(block != NULL);
+ const long long trackNum = block->GetTrackNumber(); // NOLINT
+ const mkvparser::Track* const parser_track =
+ parser_tracks->GetTrackByNumber(
+ static_cast<unsigned long>(trackNum)); // NOLINT
+ ASSERT_TRUE(parser_track != NULL);
+ const long long track_type = parser_track->GetType(); // NOLINT
+
+ if (track_type == mkvparser::Track::kVideo) {
+ const int frame_count = block->GetFrameCount();
+ const long long time_ns = block->GetTime(cluster); // NOLINT
+
+ for (int i = 0; i < frame_count; ++i) {
+ const mkvparser::Block::Frame& frame = block->GetFrame(i);
+ if (static_cast<size_t>(frame.len) > data.size()) {
+ data.resize(frame.len);
+ data_len = static_cast<size_t>(frame.len);
+ }
+ ASSERT_FALSE(frame.Read(&reader_, &data[0]));
+ parser_.ParseUncompressedHeader(&data[0], data_len);
+ stats_.AddFrame(parser_, time_ns);
+ }
+ }
+
+ status = cluster->GetNext(block_entry, block_entry);
+ ASSERT_EQ(0, status);
+ }
+
+ cluster = segment_->GetNext(cluster);
+ }
+ }
+
+ protected:
+ mkvparser::MkvReader reader_;
+ bool is_reader_open_;
+ std::unique_ptr<mkvparser::Segment> segment_;
+ std::string filename_;
+ long long pos_; // NOLINT
+ vp9_parser::Vp9HeaderParser parser_;
+ vp9_parser::Vp9LevelStats stats_;
+};
+
+TEST_F(Vp9LevelStatsTests, VideoOnlyFile) {
+ CreateAndLoadSegment("test_stereo_left_right.webm");
+ ProcessTheFrames();
+ EXPECT_EQ(256, parser_.width());
+ EXPECT_EQ(144, parser_.height());
+ EXPECT_EQ(1, parser_.column_tiles());
+ EXPECT_EQ(0, parser_.frame_parallel_mode());
+
+ EXPECT_EQ(11, stats_.GetLevel());
+ EXPECT_EQ(479232, stats_.GetMaxLumaSampleRate());
+ EXPECT_EQ(36864, stats_.GetMaxLumaPictureSize());
+ EXPECT_DOUBLE_EQ(264.03233333333333, stats_.GetAverageBitRate());
+ EXPECT_DOUBLE_EQ(147.136, stats_.GetMaxCpbSize());
+ EXPECT_DOUBLE_EQ(19.267458404715583, stats_.GetCompressionRatio());
+ EXPECT_EQ(1, stats_.GetMaxColumnTiles());
+ EXPECT_EQ(11, stats_.GetMinimumAltrefDistance());
+ EXPECT_EQ(3, stats_.GetMaxReferenceFrames());
+
+ EXPECT_TRUE(stats_.estimate_last_frame_duration());
+ stats_.set_estimate_last_frame_duration(false);
+ EXPECT_DOUBLE_EQ(275.512, stats_.GetAverageBitRate());
+}
+
+TEST_F(Vp9LevelStatsTests, Muxed) {
+ CreateAndLoadSegment("bbb_480p_vp9_opus_1second.webm", 4);
+ ProcessTheFrames();
+ EXPECT_EQ(854, parser_.width());
+ EXPECT_EQ(480, parser_.height());
+ EXPECT_EQ(2, parser_.column_tiles());
+ EXPECT_EQ(1, parser_.frame_parallel_mode());
+
+ EXPECT_EQ(30, stats_.GetLevel());
+ EXPECT_EQ(9838080, stats_.GetMaxLumaSampleRate());
+ EXPECT_EQ(409920, stats_.GetMaxLumaPictureSize());
+ EXPECT_DOUBLE_EQ(447.09394572025053, stats_.GetAverageBitRate());
+ EXPECT_DOUBLE_EQ(118.464, stats_.GetMaxCpbSize());
+ EXPECT_DOUBLE_EQ(241.17670131398313, stats_.GetCompressionRatio());
+ EXPECT_EQ(2, stats_.GetMaxColumnTiles());
+ EXPECT_EQ(9, stats_.GetMinimumAltrefDistance());
+ EXPECT_EQ(3, stats_.GetMaxReferenceFrames());
+
+ stats_.set_estimate_last_frame_duration(false);
+ EXPECT_DOUBLE_EQ(468.38413361169108, stats_.GetAverageBitRate());
+}
+
+TEST_F(Vp9LevelStatsTests, SetDuration) {
+ CreateAndLoadSegment("test_stereo_left_right.webm");
+ ProcessTheFrames();
+ const int64_t kDurationNano = 2080000000; // 2.08 seconds
+ stats_.set_duration(kDurationNano);
+ EXPECT_EQ(256, parser_.width());
+ EXPECT_EQ(144, parser_.height());
+ EXPECT_EQ(1, parser_.column_tiles());
+ EXPECT_EQ(0, parser_.frame_parallel_mode());
+
+ EXPECT_EQ(11, stats_.GetLevel());
+ EXPECT_EQ(479232, stats_.GetMaxLumaSampleRate());
+ EXPECT_EQ(36864, stats_.GetMaxLumaPictureSize());
+ EXPECT_DOUBLE_EQ(264.9153846153846, stats_.GetAverageBitRate());
+ EXPECT_DOUBLE_EQ(147.136, stats_.GetMaxCpbSize());
+ EXPECT_DOUBLE_EQ(19.267458404715583, stats_.GetCompressionRatio());
+ EXPECT_EQ(1, stats_.GetMaxColumnTiles());
+ EXPECT_EQ(11, stats_.GetMinimumAltrefDistance());
+ EXPECT_EQ(3, stats_.GetMaxReferenceFrames());
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/common/webm_constants.h b/common/webm_constants.h
new file mode 100644
index 0000000..a082ce8
--- /dev/null
+++ b/common/webm_constants.h
@@ -0,0 +1,20 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef LIBWEBM_COMMON_WEBM_CONSTANTS_H_
+#define LIBWEBM_COMMON_WEBM_CONSTANTS_H_
+
+namespace libwebm {
+
+const double kNanosecondsPerSecond = 1000000000.0;
+const int kNanosecondsPerSecondi = 1000000000;
+const int kNanosecondsPerMillisecond = 1000000;
+
+} // namespace libwebm
+
+#endif // LIBWEBM_COMMON_WEBM_CONSTANTS_H_
diff --git a/common/webm_endian.cc b/common/webm_endian.cc
new file mode 100644
index 0000000..b1ef7ca
--- /dev/null
+++ b/common/webm_endian.cc
@@ -0,0 +1,85 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#include "common/webm_endian.h"
+
+#include <stdint.h>
+
+namespace {
+
+// Swaps unsigned 32 bit values to big endian if needed. Returns |value|
+// unmodified if architecture is big endian. Returns swapped bytes of |value|
+// if architecture is little endian. Returns 0 otherwise.
+uint32_t swap32_check_little_endian(uint32_t value) {
+ // Check endianness.
+ union {
+ uint32_t val32;
+ uint8_t c[4];
+ } check;
+ check.val32 = 0x01234567U;
+
+ // Check for big endian.
+ if (check.c[3] == 0x67)
+ return value;
+
+ // Check for not little endian.
+ if (check.c[0] != 0x67)
+ return 0;
+
+ return value << 24 | ((value << 8) & 0x0000FF00U) |
+ ((value >> 8) & 0x00FF0000U) | value >> 24;
+}
+
+// Swaps unsigned 64 bit values to big endian if needed. Returns |value|
+// unmodified if architecture is big endian. Returns swapped bytes of |value|
+// if architecture is little endian. Returns 0 otherwise.
+uint64_t swap64_check_little_endian(uint64_t value) {
+ // Check endianness.
+ union {
+ uint64_t val64;
+ uint8_t c[8];
+ } check;
+ check.val64 = 0x0123456789ABCDEFULL;
+
+ // Check for big endian.
+ if (check.c[7] == 0xEF)
+ return value;
+
+ // Check for not little endian.
+ if (check.c[0] != 0xEF)
+ return 0;
+
+ return value << 56 | ((value << 40) & 0x00FF000000000000ULL) |
+ ((value << 24) & 0x0000FF0000000000ULL) |
+ ((value << 8) & 0x000000FF00000000ULL) |
+ ((value >> 8) & 0x00000000FF000000ULL) |
+ ((value >> 24) & 0x0000000000FF0000ULL) |
+ ((value >> 40) & 0x000000000000FF00ULL) | value >> 56;
+}
+
+} // namespace
+
+namespace libwebm {
+
+uint32_t host_to_bigendian(uint32_t value) {
+ return swap32_check_little_endian(value);
+}
+
+uint32_t bigendian_to_host(uint32_t value) {
+ return swap32_check_little_endian(value);
+}
+
+uint64_t host_to_bigendian(uint64_t value) {
+ return swap64_check_little_endian(value);
+}
+
+uint64_t bigendian_to_host(uint64_t value) {
+ return swap64_check_little_endian(value);
+}
+
+} // namespace libwebm
diff --git a/common/webm_endian.h b/common/webm_endian.h
new file mode 100644
index 0000000..351778b
--- /dev/null
+++ b/common/webm_endian.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef LIBWEBM_COMMON_WEBM_ENDIAN_H_
+#define LIBWEBM_COMMON_WEBM_ENDIAN_H_
+
+#include <stdint.h>
+
+namespace libwebm {
+
+// Swaps unsigned 32 bit values to big endian if needed. Returns |value| if
+// architecture is big endian. Returns big endian value if architecture is
+// little endian. Returns 0 otherwise.
+uint32_t host_to_bigendian(uint32_t value);
+
+// Swaps unsigned 32 bit values to little endian if needed. Returns |value| if
+// architecture is big endian. Returns little endian value if architecture is
+// little endian. Returns 0 otherwise.
+uint32_t bigendian_to_host(uint32_t value);
+
+// Swaps unsigned 64 bit values to big endian if needed. Returns |value| if
+// architecture is big endian. Returns big endian value if architecture is
+// little endian. Returns 0 otherwise.
+uint64_t host_to_bigendian(uint64_t value);
+
+// Swaps unsigned 64 bit values to little endian if needed. Returns |value| if
+// architecture is big endian. Returns little endian value if architecture is
+// little endian. Returns 0 otherwise.
+uint64_t bigendian_to_host(uint64_t value);
+
+} // namespace libwebm
+
+#endif // LIBWEBM_COMMON_WEBM_ENDIAN_H_
diff --git a/common/webmids.h b/common/webmids.h
new file mode 100644
index 0000000..fc0c208
--- /dev/null
+++ b/common/webmids.h
@@ -0,0 +1,193 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef COMMON_WEBMIDS_H_
+#define COMMON_WEBMIDS_H_
+
+namespace libwebm {
+
+enum MkvId {
+ kMkvEBML = 0x1A45DFA3,
+ kMkvEBMLVersion = 0x4286,
+ kMkvEBMLReadVersion = 0x42F7,
+ kMkvEBMLMaxIDLength = 0x42F2,
+ kMkvEBMLMaxSizeLength = 0x42F3,
+ kMkvDocType = 0x4282,
+ kMkvDocTypeVersion = 0x4287,
+ kMkvDocTypeReadVersion = 0x4285,
+ kMkvVoid = 0xEC,
+ kMkvSignatureSlot = 0x1B538667,
+ kMkvSignatureAlgo = 0x7E8A,
+ kMkvSignatureHash = 0x7E9A,
+ kMkvSignaturePublicKey = 0x7EA5,
+ kMkvSignature = 0x7EB5,
+ kMkvSignatureElements = 0x7E5B,
+ kMkvSignatureElementList = 0x7E7B,
+ kMkvSignedElement = 0x6532,
+ // segment
+ kMkvSegment = 0x18538067,
+ // Meta Seek Information
+ kMkvSeekHead = 0x114D9B74,
+ kMkvSeek = 0x4DBB,
+ kMkvSeekID = 0x53AB,
+ kMkvSeekPosition = 0x53AC,
+ // Segment Information
+ kMkvInfo = 0x1549A966,
+ kMkvTimecodeScale = 0x2AD7B1,
+ kMkvDuration = 0x4489,
+ kMkvDateUTC = 0x4461,
+ kMkvTitle = 0x7BA9,
+ kMkvMuxingApp = 0x4D80,
+ kMkvWritingApp = 0x5741,
+ // Cluster
+ kMkvCluster = 0x1F43B675,
+ kMkvTimecode = 0xE7,
+ kMkvPrevSize = 0xAB,
+ kMkvBlockGroup = 0xA0,
+ kMkvBlock = 0xA1,
+ kMkvBlockDuration = 0x9B,
+ kMkvReferenceBlock = 0xFB,
+ kMkvLaceNumber = 0xCC,
+ kMkvSimpleBlock = 0xA3,
+ kMkvBlockAdditions = 0x75A1,
+ kMkvBlockMore = 0xA6,
+ kMkvBlockAddID = 0xEE,
+ kMkvBlockAdditional = 0xA5,
+ kMkvDiscardPadding = 0x75A2,
+ // Track
+ kMkvTracks = 0x1654AE6B,
+ kMkvTrackEntry = 0xAE,
+ kMkvTrackNumber = 0xD7,
+ kMkvTrackUID = 0x73C5,
+ kMkvTrackType = 0x83,
+ kMkvFlagEnabled = 0xB9,
+ kMkvFlagDefault = 0x88,
+ kMkvFlagForced = 0x55AA,
+ kMkvFlagLacing = 0x9C,
+ kMkvDefaultDuration = 0x23E383,
+ kMkvMaxBlockAdditionID = 0x55EE,
+ kMkvName = 0x536E,
+ kMkvLanguage = 0x22B59C,
+ kMkvCodecID = 0x86,
+ kMkvCodecPrivate = 0x63A2,
+ kMkvCodecName = 0x258688,
+ kMkvCodecDelay = 0x56AA,
+ kMkvSeekPreRoll = 0x56BB,
+ // video
+ kMkvVideo = 0xE0,
+ kMkvFlagInterlaced = 0x9A,
+ kMkvStereoMode = 0x53B8,
+ kMkvAlphaMode = 0x53C0,
+ kMkvPixelWidth = 0xB0,
+ kMkvPixelHeight = 0xBA,
+ kMkvPixelCropBottom = 0x54AA,
+ kMkvPixelCropTop = 0x54BB,
+ kMkvPixelCropLeft = 0x54CC,
+ kMkvPixelCropRight = 0x54DD,
+ kMkvDisplayWidth = 0x54B0,
+ kMkvDisplayHeight = 0x54BA,
+ kMkvDisplayUnit = 0x54B2,
+ kMkvAspectRatioType = 0x54B3,
+ kMkvColourSpace = 0x2EB524,
+ kMkvFrameRate = 0x2383E3,
+ // end video
+ // colour
+ kMkvColour = 0x55B0,
+ kMkvMatrixCoefficients = 0x55B1,
+ kMkvBitsPerChannel = 0x55B2,
+ kMkvChromaSubsamplingHorz = 0x55B3,
+ kMkvChromaSubsamplingVert = 0x55B4,
+ kMkvCbSubsamplingHorz = 0x55B5,
+ kMkvCbSubsamplingVert = 0x55B6,
+ kMkvChromaSitingHorz = 0x55B7,
+ kMkvChromaSitingVert = 0x55B8,
+ kMkvRange = 0x55B9,
+ kMkvTransferCharacteristics = 0x55BA,
+ kMkvPrimaries = 0x55BB,
+ kMkvMaxCLL = 0x55BC,
+ kMkvMaxFALL = 0x55BD,
+ // mastering metadata
+ kMkvMasteringMetadata = 0x55D0,
+ kMkvPrimaryRChromaticityX = 0x55D1,
+ kMkvPrimaryRChromaticityY = 0x55D2,
+ kMkvPrimaryGChromaticityX = 0x55D3,
+ kMkvPrimaryGChromaticityY = 0x55D4,
+ kMkvPrimaryBChromaticityX = 0x55D5,
+ kMkvPrimaryBChromaticityY = 0x55D6,
+ kMkvWhitePointChromaticityX = 0x55D7,
+ kMkvWhitePointChromaticityY = 0x55D8,
+ kMkvLuminanceMax = 0x55D9,
+ kMkvLuminanceMin = 0x55DA,
+ // end mastering metadata
+ // end colour
+ // projection
+ kMkvProjection = 0x7670,
+ kMkvProjectionType = 0x7671,
+ kMkvProjectionPrivate = 0x7672,
+ kMkvProjectionPoseYaw = 0x7673,
+ kMkvProjectionPosePitch = 0x7674,
+ kMkvProjectionPoseRoll = 0x7675,
+ // end projection
+ // audio
+ kMkvAudio = 0xE1,
+ kMkvSamplingFrequency = 0xB5,
+ kMkvOutputSamplingFrequency = 0x78B5,
+ kMkvChannels = 0x9F,
+ kMkvBitDepth = 0x6264,
+ // end audio
+ // ContentEncodings
+ kMkvContentEncodings = 0x6D80,
+ kMkvContentEncoding = 0x6240,
+ kMkvContentEncodingOrder = 0x5031,
+ kMkvContentEncodingScope = 0x5032,
+ kMkvContentEncodingType = 0x5033,
+ kMkvContentCompression = 0x5034,
+ kMkvContentCompAlgo = 0x4254,
+ kMkvContentCompSettings = 0x4255,
+ kMkvContentEncryption = 0x5035,
+ kMkvContentEncAlgo = 0x47E1,
+ kMkvContentEncKeyID = 0x47E2,
+ kMkvContentSignature = 0x47E3,
+ kMkvContentSigKeyID = 0x47E4,
+ kMkvContentSigAlgo = 0x47E5,
+ kMkvContentSigHashAlgo = 0x47E6,
+ kMkvContentEncAESSettings = 0x47E7,
+ kMkvAESSettingsCipherMode = 0x47E8,
+ kMkvAESSettingsCipherInitData = 0x47E9,
+ // end ContentEncodings
+ // Cueing Data
+ kMkvCues = 0x1C53BB6B,
+ kMkvCuePoint = 0xBB,
+ kMkvCueTime = 0xB3,
+ kMkvCueTrackPositions = 0xB7,
+ kMkvCueTrack = 0xF7,
+ kMkvCueClusterPosition = 0xF1,
+ kMkvCueBlockNumber = 0x5378,
+ // Chapters
+ kMkvChapters = 0x1043A770,
+ kMkvEditionEntry = 0x45B9,
+ kMkvChapterAtom = 0xB6,
+ kMkvChapterUID = 0x73C4,
+ kMkvChapterStringUID = 0x5654,
+ kMkvChapterTimeStart = 0x91,
+ kMkvChapterTimeEnd = 0x92,
+ kMkvChapterDisplay = 0x80,
+ kMkvChapString = 0x85,
+ kMkvChapLanguage = 0x437C,
+ kMkvChapCountry = 0x437E,
+ // Tags
+ kMkvTags = 0x1254C367,
+ kMkvTag = 0x7373,
+ kMkvSimpleTag = 0x67C8,
+ kMkvTagName = 0x45A3,
+ kMkvTagString = 0x4487
+};
+
+} // namespace libwebm
+
+#endif // COMMON_WEBMIDS_H_
diff --git a/dumpvtt.cc b/dumpvtt.cc
new file mode 100644
index 0000000..472da52
--- /dev/null
+++ b/dumpvtt.cc
@@ -0,0 +1,91 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#include <cstdio>
+#include <cstdlib>
+#include "webvtt/vttreader.h"
+#include "webvtt/webvttparser.h"
+
+int main(int argc, const char* argv[]) {
+ if (argc != 2) {
+ fprintf(stdout, "usage: dumpvtt <vtt file>\n");
+ return EXIT_SUCCESS;
+ }
+
+ libwebvtt::VttReader reader;
+ const char* const filename = argv[1];
+
+ if (int e = reader.Open(filename)) {
+ (void)e;
+ fprintf(stderr, "open failed\n");
+ return EXIT_FAILURE;
+ }
+
+ libwebvtt::Parser parser(&reader);
+
+ if (int e = parser.Init()) {
+ (void)e;
+ fprintf(stderr, "parser init failed\n");
+ return EXIT_FAILURE;
+ }
+
+ for (libwebvtt::Cue cue;;) {
+ const int e = parser.Parse(&cue);
+
+ if (e < 0) { // error
+ fprintf(stderr, "error parsing cue\n");
+ return EXIT_FAILURE;
+ }
+
+ if (e > 0) // EOF
+ return EXIT_SUCCESS;
+
+ fprintf(stdout, "cue identifier: \"%s\"\n", cue.identifier.c_str());
+
+ const libwebvtt::Time& st = cue.start_time;
+ fprintf(stdout, "cue start time: \"HH=%i MM=%i SS=%i SSS=%i\"\n", st.hours,
+ st.minutes, st.seconds, st.milliseconds);
+
+ const libwebvtt::Time& sp = cue.stop_time;
+ fprintf(stdout, "cue stop time: \"HH=%i MM=%i SS=%i SSS=%i\"\n", sp.hours,
+ sp.minutes, sp.seconds, sp.milliseconds);
+
+ {
+ typedef libwebvtt::Cue::settings_t::const_iterator iter_t;
+ iter_t i = cue.settings.begin();
+ const iter_t j = cue.settings.end();
+
+ if (i == j) {
+ fprintf(stdout, "cue setting: <no settings present>\n");
+ } else {
+ while (i != j) {
+ const libwebvtt::Setting& setting = *i++;
+ fprintf(stdout, "cue setting: name=%s value=%s\n",
+ setting.name.c_str(), setting.value.c_str());
+ }
+ }
+ }
+
+ {
+ typedef libwebvtt::Cue::payload_t::const_iterator iter_t;
+ iter_t i = cue.payload.begin();
+ const iter_t j = cue.payload.end();
+
+ int idx = 1;
+ while (i != j) {
+ const std::string& payload = *i++;
+ const char* const payload_str = payload.c_str();
+ fprintf(stdout, "cue payload[%i]: \"%s\"\n", idx, payload_str);
+ ++idx;
+ }
+ }
+
+ fprintf(stdout, "\n");
+ fflush(stdout);
+ }
+}
diff --git a/hdr_util.hpp b/hdr_util.hpp
new file mode 100644
index 0000000..7abcb3a
--- /dev/null
+++ b/hdr_util.hpp
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_HDR_UTIL_HPP_
+#define LIBWEBM_HDR_UTIL_HPP_
+
+// This file is a wrapper for the file included immediately after this comment.
+// New projects should not include this file: include the file included below.
+#include "common/hdr_util.h"
+
+#endif // LIBWEBM_HDR_UTIL_HPP_
diff --git a/iosbuild.sh b/iosbuild.sh
new file mode 100755
index 0000000..a183af3
--- /dev/null
+++ b/iosbuild.sh
@@ -0,0 +1,207 @@
+#!/bin/sh
+##
+## Copyright (c) 2015 The WebM project authors. All Rights Reserved.
+##
+## Use of this source code is governed by a BSD-style license
+## that can be found in the LICENSE file in the root of the source
+## tree. An additional intellectual property rights grant can be found
+## in the file PATENTS. All contributing project authors may
+## be found in the AUTHORS file in the root of the source tree.
+##
+## This script generates 'WebM.framework'. An iOS app can mux/demux WebM
+## container files by including 'WebM.framework'.
+##
+## Run ./iosbuild.sh to generate 'WebM.framework'. By default the framework
+## bundle will be created in a directory called framework. Use --out-dir to
+## change the output directory.
+##
+## This script is based on iosbuild.sh from the libwebp project.
+. $(dirname $0)/common/common.sh
+
+# Trap function. Cleans up build output.
+cleanup() {
+ local readonly res=$?
+ cd "${ORIG_PWD}"
+
+ for dir in ${LIBDIRS}; do
+ if [ -d "${dir}" ]; then
+ rm -rf "${dir}"
+ fi
+ done
+
+ if [ $res -ne 0 ]; then
+ elog "build exited with error ($res)"
+ fi
+}
+
+trap cleanup EXIT
+
+check_dir libwebm
+
+iosbuild_usage() {
+cat << EOF
+ Usage: ${0##*/} [arguments]
+ --help: Display this message and exit.
+ --out-dir: Override output directory (default is ${OUTDIR}).
+ --show-build-output: Show output from each library build.
+ --verbose: Output information about the environment and each stage of the
+ build.
+EOF
+}
+
+# Extract the latest SDK version from the final field of the form: iphoneosX.Y
+readonly SDK=$(xcodebuild -showsdks \
+ | grep iphoneos | sort | tail -n 1 | awk '{print substr($NF, 9)}'
+)
+
+# Extract Xcode version.
+readonly XCODE=$(xcodebuild -version | grep Xcode | cut -d " " -f2)
+if [ -z "${XCODE}" ]; then
+ echo "Xcode not available"
+ exit 1
+fi
+
+# Add iPhoneOS-V6 to the list of platforms below if you need armv6 support.
+# Note that iPhoneOS-V6 support is not available with the iOS6 SDK.
+readonly INCLUDES="common/file_util.h
+ common/hdr_util.h
+ common/webmids.h
+ mkvmuxer/mkvmuxer.h
+ mkvmuxer/mkvmuxertypes.h
+ mkvmuxer/mkvmuxerutil.h
+ mkvmuxer/mkvwriter.h
+ mkvparser/mkvparser.h
+ mkvparser/mkvreader.h"
+readonly PLATFORMS="iPhoneSimulator
+ iPhoneSimulator64
+ iPhoneOS-V7
+ iPhoneOS-V7s
+ iPhoneOS-V7-arm64"
+readonly TARGETDIR="WebM.framework"
+readonly DEVELOPER="$(xcode-select --print-path)"
+readonly PLATFORMSROOT="${DEVELOPER}/Platforms"
+readonly LIPO="$(xcrun -sdk iphoneos${SDK} -find lipo)"
+LIBLIST=""
+OPT_FLAGS="-DNDEBUG -O3"
+readonly SDK_MAJOR_VERSION="$(echo ${SDK} | awk -F '.' '{ print $1 }')"
+
+if [ -z "${SDK_MAJOR_VERSION}" ]; then
+ elog "iOS SDK not available"
+ exit 1
+elif [ "${SDK_MAJOR_VERSION}" -lt "6" ]; then
+ elog "You need iOS SDK version 6 or above"
+ exit 1
+else
+ vlog "iOS SDK Version ${SDK}"
+fi
+
+
+# Parse the command line.
+while [ -n "$1" ]; do
+ case "$1" in
+ --help)
+ iosbuild_usage
+ exit
+ ;;
+ --out-dir)
+ OUTDIR="$2"
+ shift
+ ;;
+ --enable-debug)
+ OPT_FLAGS="-g"
+ ;;
+ --show-build-output)
+ devnull=
+ ;;
+ --verbose)
+ VERBOSE=yes
+ ;;
+ *)
+ iosbuild_usage
+ exit 1
+ ;;
+ esac
+ shift
+done
+
+readonly OPT_FLAGS="${OPT_FLAGS}"
+readonly OUTDIR="${OUTDIR:-framework}"
+
+if [ "${VERBOSE}" = "yes" ]; then
+cat << EOF
+ OUTDIR=${OUTDIR}
+ INCLUDES=${INCLUDES}
+ PLATFORMS=${PLATFORMS}
+ TARGETDIR=${TARGETDIR}
+ DEVELOPER=${DEVELOPER}
+ LIPO=${LIPO}
+ OPT_FLAGS=${OPT_FLAGS}
+ ORIG_PWD=${ORIG_PWD}
+EOF
+fi
+
+rm -rf "${OUTDIR}/${TARGETDIR}"
+mkdir -p "${OUTDIR}/${TARGETDIR}/Headers/"
+
+for PLATFORM in ${PLATFORMS}; do
+ ARCH2=""
+ if [ "${PLATFORM}" = "iPhoneOS-V7-arm64" ]; then
+ PLATFORM="iPhoneOS"
+ ARCH="aarch64"
+ ARCH2="arm64"
+ elif [ "${PLATFORM}" = "iPhoneOS-V7s" ]; then
+ PLATFORM="iPhoneOS"
+ ARCH="armv7s"
+ elif [ "${PLATFORM}" = "iPhoneOS-V7" ]; then
+ PLATFORM="iPhoneOS"
+ ARCH="armv7"
+ elif [ "${PLATFORM}" = "iPhoneOS-V6" ]; then
+ PLATFORM="iPhoneOS"
+ ARCH="armv6"
+ elif [ "${PLATFORM}" = "iPhoneSimulator64" ]; then
+ PLATFORM="iPhoneSimulator"
+ ARCH="x86_64"
+ else
+ ARCH="i386"
+ fi
+
+ LIBDIR="${OUTDIR}/${PLATFORM}-${SDK}-${ARCH}"
+ LIBDIRS="${LIBDIRS} ${LIBDIR}"
+ LIBFILE="${LIBDIR}/libwebm.a"
+ eval mkdir -p "${LIBDIR}" ${devnull}
+
+ DEVROOT="${DEVELOPER}/Toolchains/XcodeDefault.xctoolchain"
+ SDKROOT="${PLATFORMSROOT}/"
+ SDKROOT="${SDKROOT}${PLATFORM}.platform/Developer/SDKs/${PLATFORM}${SDK}.sdk/"
+ CXXFLAGS="-arch ${ARCH2:-${ARCH}} -isysroot ${SDKROOT} ${OPT_FLAGS}
+ -miphoneos-version-min=6.0"
+
+ # enable bitcode if available
+ if [ "${SDK_MAJOR_VERSION}" -gt 8 ]; then
+ CXXFLAGS="${CXXFLAGS} -fembed-bitcode"
+ fi
+
+ # Build using the legacy makefile (instead of generating via cmake).
+ eval make -f Makefile.unix libwebm.a CXXFLAGS=\"${CXXFLAGS}\" ${devnull}
+
+ # copy lib and add it to LIBLIST.
+ eval cp libwebm.a "${LIBFILE}" ${devnull}
+ LIBLIST="${LIBLIST} ${LIBFILE}"
+
+ # clean build so we can go again.
+ eval make -f Makefile.unix clean ${devnull}
+done
+
+# create include sub dirs in framework dir.
+readonly framework_header_dir="${OUTDIR}/${TARGETDIR}/Headers"
+readonly framework_header_sub_dirs="common mkvmuxer mkvparser"
+for dir in ${framework_header_sub_dirs}; do
+ mkdir "${framework_header_dir}/${dir}"
+done
+
+for header_file in ${INCLUDES}; do
+ eval cp -p ${header_file} "${framework_header_dir}/${header_file}" ${devnull}
+done
+
+eval ${LIPO} -create ${LIBLIST} -output "${OUTDIR}/${TARGETDIR}/WebM" ${devnull}
+echo "Succesfully built ${TARGETDIR} in ${OUTDIR}."
diff --git a/m2ts/tests/webm2pes_tests.cc b/m2ts/tests/webm2pes_tests.cc
new file mode 100644
index 0000000..664856f
--- /dev/null
+++ b/m2ts/tests/webm2pes_tests.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "m2ts/webm2pes.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "common/file_util.h"
+#include "common/libwebm_util.h"
+#include "m2ts/vpxpes_parser.h"
+#include "testing/test_util.h"
+
+namespace {
+
+class Webm2PesTests : public ::testing::Test {
+ public:
+ // Constants for validating known values from input data.
+ const std::uint8_t kMinVideoStreamId = 0xE0;
+ const std::uint8_t kMaxVideoStreamId = 0xEF;
+ const int kPesHeaderSize = 6;
+ const int kPesOptionalHeaderStartOffset = kPesHeaderSize;
+ const int kPesOptionalHeaderSize = 9;
+ const int kPesOptionalHeaderMarkerValue = 0x2;
+ const int kWebm2PesOptHeaderRemainingSize = 6;
+ const int kBcmvHeaderSize = 10;
+
+ Webm2PesTests() = default;
+ ~Webm2PesTests() = default;
+
+ void CreateAndLoadTestInput() {
+ libwebm::Webm2Pes converter(input_file_name_, temp_file_name_.name());
+ ASSERT_TRUE(converter.ConvertToFile());
+ ASSERT_TRUE(parser_.Open(pes_file_name()));
+ }
+
+ bool VerifyPacketStartCode(const libwebm::VpxPesParser::PesHeader& header) {
+ // PES packets all start with the byte sequence 0x0 0x0 0x1.
+ if (header.start_code[0] != 0 || header.start_code[1] != 0 ||
+ header.start_code[2] != 1) {
+ return false;
+ }
+ return true;
+ }
+
+ const std::string& pes_file_name() const { return temp_file_name_.name(); }
+ libwebm::VpxPesParser* parser() { return &parser_; }
+
+ private:
+ const libwebm::TempFileDeleter temp_file_name_;
+ const std::string input_file_name_ =
+ test::GetTestFilePath("bbb_480p_vp9_opus_1second.webm");
+ libwebm::VpxPesParser parser_;
+};
+
+TEST_F(Webm2PesTests, CreatePesFile) { CreateAndLoadTestInput(); }
+
+TEST_F(Webm2PesTests, CanParseFirstPacket) {
+ CreateAndLoadTestInput();
+ libwebm::VpxPesParser::PesHeader header;
+ libwebm::VideoFrame frame;
+ ASSERT_TRUE(parser()->ParseNextPacket(&header, &frame));
+ EXPECT_TRUE(VerifyPacketStartCode(header));
+
+ // 9 bytes: PES optional header
+ // 10 bytes: BCMV Header
+ // 83 bytes: frame
+ // 102 bytes total in packet length field:
+ const std::size_t kPesPayloadLength = 102;
+ EXPECT_EQ(kPesPayloadLength, header.packet_length);
+
+ EXPECT_GE(header.stream_id, kMinVideoStreamId);
+ EXPECT_LE(header.stream_id, kMaxVideoStreamId);
+
+ // Test PesOptionalHeader values.
+ EXPECT_EQ(kPesOptionalHeaderMarkerValue, header.opt_header.marker);
+ EXPECT_EQ(kWebm2PesOptHeaderRemainingSize, header.opt_header.remaining_size);
+ EXPECT_EQ(0, header.opt_header.scrambling);
+ EXPECT_EQ(0, header.opt_header.priority);
+ EXPECT_EQ(0, header.opt_header.data_alignment);
+ EXPECT_EQ(0, header.opt_header.copyright);
+ EXPECT_EQ(0, header.opt_header.original);
+ EXPECT_EQ(1, header.opt_header.has_pts);
+ EXPECT_EQ(0, header.opt_header.has_dts);
+ EXPECT_EQ(0, header.opt_header.unused_fields);
+
+ // Test the BCMV header.
+ // Note: The length field of the BCMV header includes its own length.
+ const std::size_t kBcmvBaseLength = 10;
+ const std::size_t kFirstFrameLength = 83;
+ const libwebm::VpxPesParser::BcmvHeader kFirstBcmvHeader(kFirstFrameLength +
+ kBcmvBaseLength);
+ EXPECT_TRUE(header.bcmv_header.Valid());
+ EXPECT_EQ(kFirstBcmvHeader, header.bcmv_header);
+
+ // Parse the next packet to confirm correct parse and consumption of payload.
+ EXPECT_TRUE(parser()->ParseNextPacket(&header, &frame));
+}
+
+TEST_F(Webm2PesTests, CanMuxLargeBuffers) {
+ const std::size_t kBufferSize = 100 * 1024;
+ const std::int64_t kFakeTimestamp = libwebm::kNanosecondsPerSecond;
+ libwebm::VideoFrame fake_frame(kFakeTimestamp, libwebm::VideoFrame::kVP9);
+ ASSERT_TRUE(fake_frame.Init(kBufferSize));
+ std::memset(fake_frame.buffer().data.get(), 0x80, kBufferSize);
+ ASSERT_TRUE(fake_frame.SetBufferLength(kBufferSize));
+ libwebm::PacketDataBuffer pes_packet_buffer;
+ ASSERT_TRUE(
+ libwebm::Webm2Pes::WritePesPacket(fake_frame, &pes_packet_buffer));
+
+ // TODO(tomfinegan): Change VpxPesParser so it can read from a buffer, and get
+ // rid of this extra step.
+ libwebm::FilePtr pes_file(std::fopen(pes_file_name().c_str(), "wb"),
+ libwebm::FILEDeleter());
+ ASSERT_EQ(pes_packet_buffer.size(),
+ fwrite(&pes_packet_buffer[0], 1, pes_packet_buffer.size(),
+ pes_file.get()));
+ fclose(pes_file.get());
+ pes_file.release();
+
+ libwebm::VpxPesParser parser;
+ ASSERT_TRUE(parser.Open(pes_file_name()));
+ libwebm::VpxPesParser::PesHeader header;
+ libwebm::VideoFrame parsed_frame;
+ ASSERT_TRUE(parser.ParseNextPacket(&header, &parsed_frame));
+ EXPECT_EQ(fake_frame.nanosecond_pts(), parsed_frame.nanosecond_pts());
+ EXPECT_EQ(fake_frame.buffer().length, parsed_frame.buffer().length);
+ EXPECT_EQ(0, std::memcmp(fake_frame.buffer().data.get(),
+ parsed_frame.buffer().data.get(), kBufferSize));
+}
+
+TEST_F(Webm2PesTests, ParserConsumesAllInput) {
+ CreateAndLoadTestInput();
+ libwebm::VpxPesParser::PesHeader header;
+ libwebm::VideoFrame frame;
+ while (parser()->ParseNextPacket(&header, &frame) == true) {
+ EXPECT_TRUE(VerifyPacketStartCode(header));
+ }
+ EXPECT_EQ(0, parser()->BytesAvailable());
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/m2ts/vpxpes2ts.cc b/m2ts/vpxpes2ts.cc
new file mode 100644
index 0000000..7684b56
--- /dev/null
+++ b/m2ts/vpxpes2ts.cc
@@ -0,0 +1,217 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "m2ts/vpxpes2ts.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstdio>
+#include <vector>
+
+namespace libwebm {
+// TODO(tomfinegan): Dedupe this and PesHeaderField.
+// Stores a value and its size in bits for writing into a MPEG2 TS Header.
+// Maximum size is 64 bits. Users may call the Check() method to perform minimal
+// validation (size > 0 and <= 64).
+struct TsHeaderField {
+ TsHeaderField(std::uint64_t value, std::uint32_t size_in_bits,
+ std::uint8_t byte_index, std::uint8_t bits_to_shift)
+ : bits(value),
+ num_bits(size_in_bits),
+ index(byte_index),
+ shift(bits_to_shift) {}
+ TsHeaderField() = delete;
+ TsHeaderField(const TsHeaderField&) = default;
+ TsHeaderField(TsHeaderField&&) = default;
+ ~TsHeaderField() = default;
+ bool Check() const {
+ return num_bits > 0 && num_bits <= 64 && shift >= 0 && shift < 64;
+ }
+
+ // Value to be stored in the field.
+ std::uint64_t bits;
+
+ // Number of bits in the value.
+ const int num_bits;
+
+ // Index into the header for the byte in which |bits| will be written.
+ const std::uint8_t index;
+
+ // Number of bits to left shift value before or'ing. Ignored for whole bytes.
+ const int shift;
+};
+
+// Data storage for MPEG2 Transport Stream headers.
+// https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet
+struct TsHeader {
+ TsHeader(bool payload_start, bool adaptation_flag, std::uint8_t counter)
+ : is_payload_start(payload_start),
+ has_adaptation(adaptation_flag),
+ counter_value(counter) {}
+ TsHeader() = delete;
+ TsHeader(const TsHeader&) = default;
+ TsHeader(TsHeader&&) = default;
+ ~TsHeader() = default;
+
+ void Write(PacketDataBuffer* buffer) const;
+
+ // Indicates the packet is the beginning of a new fragmented payload.
+ const bool is_payload_start;
+
+ // Indicates the packet contains an adaptation field.
+ const bool has_adaptation;
+
+ // The sync byte is the bit pattern of 0x47 (ASCII char 'G').
+ const std::uint8_t kTsHeaderSyncByte = 0x47;
+ const std::uint8_t sync_byte = kTsHeaderSyncByte;
+
+ // Value for |continuity_counter|. Used to detect gaps when demuxing.
+ const std::uint8_t counter_value;
+
+ // Set when FEC is impossible. Always 0.
+ const TsHeaderField transport_error_indicator = TsHeaderField(0, 1, 1, 7);
+
+ // This MPEG2 TS header is the start of a new payload (aka PES packet).
+ const TsHeaderField payload_unit_start_indicator =
+ TsHeaderField(is_payload_start ? 1 : 0, 1, 1, 6);
+
+ // Set when the current packet has a higher priority than other packets with
+ // the same PID. Always 0 for VPX.
+ const TsHeaderField transport_priority = TsHeaderField(0, 1, 1, 5);
+
+ // https://en.wikipedia.org/wiki/MPEG_transport_stream#Packet_Identifier_.28PID.29
+ // 0x0020-0x1FFA May be assigned as needed to Program Map Tables, elementary
+ // streams and other data tables.
+ // Note: Though we hard code to 0x20, this value is actually 13 bits-- the
+ // buffer for the header is always set to 0, so it doesn't matter in practice.
+ const TsHeaderField pid = TsHeaderField(0x20, 8, 2, 0);
+
+ // Indicates scrambling key. Unused; always 0.
+ const TsHeaderField scrambling_control = TsHeaderField(0, 2, 3, 6);
+
+ // Adaptation field flag. Unused; always 0.
+ // TODO(tomfinegan): Not sure this is OK. Might need to add support for
+ // writing the Adaptation Field.
+ const TsHeaderField adaptation_field_flag =
+ TsHeaderField(has_adaptation ? 1 : 0, 1, 3, 5);
+
+ // Payload flag. All output packets created here have payloads. Always 1.
+ const TsHeaderField payload_flag = TsHeaderField(1, 1, 3, 4);
+
+ // Continuity counter. Two bit field that is incremented for every packet.
+ const TsHeaderField continuity_counter =
+ TsHeaderField(counter_value, 4, 3, 3);
+};
+
+void TsHeader::Write(PacketDataBuffer* buffer) const {
+ std::uint8_t* byte = &(*buffer)[0];
+ *byte = sync_byte;
+
+ *++byte = 0;
+ *byte |= transport_error_indicator.bits << transport_error_indicator.shift;
+ *byte |= payload_unit_start_indicator.bits
+ << payload_unit_start_indicator.shift;
+ *byte |= transport_priority.bits << transport_priority.shift;
+
+ *++byte = pid.bits & 0xff;
+
+ *++byte = 0;
+ *byte |= scrambling_control.bits << scrambling_control.shift;
+ *byte |= adaptation_field_flag.bits << adaptation_field_flag.shift;
+ *byte |= payload_flag.bits << payload_flag.shift;
+ *byte |= continuity_counter.bits; // last 4 bits.
+}
+
+bool VpxPes2Ts::ConvertToFile() {
+ output_file_ = FilePtr(fopen(output_file_name_.c_str(), "wb"), FILEDeleter());
+ if (output_file_ == nullptr) {
+ std::fprintf(stderr, "VpxPes2Ts: Cannot open %s for output.\n",
+ output_file_name_.c_str());
+ return false;
+ }
+ pes_converter_.reset(new Webm2Pes(input_file_name_, this));
+ if (pes_converter_ == nullptr) {
+ std::fprintf(stderr, "VpxPes2Ts: Out of memory.\n");
+ return false;
+ }
+ return pes_converter_->ConvertToPacketReceiver();
+}
+
+bool VpxPes2Ts::ReceivePacket(const PacketDataBuffer& packet_data) {
+ const int kTsHeaderSize = 4;
+ const int kTsPayloadSize = 184;
+ const int kTsPacketSize = kTsHeaderSize + kTsPayloadSize;
+ int bytes_to_packetize = static_cast<int>(packet_data.size());
+ std::uint8_t continuity_counter = 0;
+ std::size_t read_pos = 0;
+
+ ts_buffer_.reserve(kTsPacketSize);
+
+ while (bytes_to_packetize > 0) {
+ if (continuity_counter > 0xf)
+ continuity_counter = 0;
+
+ // Calculate payload size (need to know if we'll have to pad with an empty
+ // adaptation field).
+ int payload_size = std::min(bytes_to_packetize, kTsPayloadSize);
+
+ // Write the TS header.
+ const TsHeader header(
+ bytes_to_packetize == static_cast<int>(packet_data.size()),
+ payload_size != kTsPayloadSize, continuity_counter);
+ header.Write(&ts_buffer_);
+ int write_pos = kTsHeaderSize;
+
+ // (pre)Pad payload with an empty adaptation field. All packets must be
+ // |kTsPacketSize| (188).
+ if (payload_size < kTsPayloadSize) {
+ // We need at least 2 bytes to write an empty adaptation field.
+ if (payload_size == (kTsPayloadSize - 1)) {
+ payload_size--;
+ }
+
+ // Padding adaptation field:
+ // 8 bits: number of adaptation field bytes following this byte.
+ // 8 bits: unused (in this program) flags.
+ // This is followed by a run of 0xff to reach |kTsPayloadSize| (184)
+ // bytes.
+ const int pad_size = kTsPayloadSize - payload_size - 1 - 1;
+ ts_buffer_[write_pos++] = pad_size + 1;
+ ts_buffer_[write_pos++] = 0;
+
+ const std::uint8_t kStuffingByte = 0xff;
+ for (int i = 0; i < pad_size; ++i) {
+ ts_buffer_[write_pos++] = kStuffingByte;
+ }
+ }
+
+ for (int i = 0; i < payload_size; ++i) {
+ ts_buffer_[write_pos++] = packet_data[read_pos++];
+ }
+
+ bytes_to_packetize -= payload_size;
+ continuity_counter++;
+
+ if (write_pos != kTsPacketSize) {
+ fprintf(stderr, "VpxPes2Ts: Invalid packet length.\n");
+ return false;
+ }
+
+ // Write contents of |ts_buffer_| to |output_file_|.
+ // TODO(tomfinegan): Writing 188 bytes at a time isn't exactly efficient...
+ // Fix me.
+ if (static_cast<int>(std::fwrite(&ts_buffer_[0], 1, kTsPacketSize,
+ output_file_.get())) != kTsPacketSize) {
+ std::fprintf(stderr, "VpxPes2Ts: TS packet write failed.\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace libwebm
diff --git a/m2ts/vpxpes2ts.h b/m2ts/vpxpes2ts.h
new file mode 100644
index 0000000..2609a1f
--- /dev/null
+++ b/m2ts/vpxpes2ts.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_M2TS_VPXPES2TS_H_
+#define LIBWEBM_M2TS_VPXPES2TS_H_
+
+#include <memory>
+#include <string>
+
+#include "common/libwebm_util.h"
+#include "m2ts/webm2pes.h"
+
+namespace libwebm {
+
+class VpxPes2Ts : public PacketReceiverInterface {
+ public:
+ VpxPes2Ts(const std::string& input_file_name,
+ const std::string& output_file_name)
+ : input_file_name_(input_file_name),
+ output_file_name_(output_file_name) {}
+ virtual ~VpxPes2Ts() = default;
+ VpxPes2Ts() = delete;
+ VpxPes2Ts(const VpxPes2Ts&) = delete;
+ VpxPes2Ts(VpxPes2Ts&&) = delete;
+
+ bool ConvertToFile();
+
+ private:
+ bool ReceivePacket(const PacketDataBuffer& packet_data) override;
+
+ const std::string input_file_name_;
+ const std::string output_file_name_;
+
+ FilePtr output_file_;
+ std::unique_ptr<Webm2Pes> pes_converter_;
+ PacketDataBuffer ts_buffer_;
+};
+
+} // namespace libwebm
+
+#endif // LIBWEBM_M2TS_VPXPES2TS_H_
diff --git a/m2ts/vpxpes2ts_main.cc b/m2ts/vpxpes2ts_main.cc
new file mode 100644
index 0000000..435d805
--- /dev/null
+++ b/m2ts/vpxpes2ts_main.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "m2ts/vpxpes2ts.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+namespace {
+
+void Usage(const char* argv[]) {
+ printf("Usage: %s <WebM file> <output file>", argv[0]);
+}
+
+} // namespace
+
+int main(int argc, const char* argv[]) {
+ if (argc < 3) {
+ Usage(argv);
+ return EXIT_FAILURE;
+ }
+
+ const std::string input_path = argv[1];
+ const std::string output_path = argv[2];
+
+ libwebm::VpxPes2Ts converter(input_path, output_path);
+ return converter.ConvertToFile() == true ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/m2ts/vpxpes_parser.cc b/m2ts/vpxpes_parser.cc
new file mode 100644
index 0000000..4f6fe5c
--- /dev/null
+++ b/m2ts/vpxpes_parser.cc
@@ -0,0 +1,409 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "vpxpes_parser.h"
+
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <limits>
+#include <vector>
+
+#include "common/file_util.h"
+
+namespace libwebm {
+
+VpxPesParser::BcmvHeader::BcmvHeader(std::uint32_t len) : length(len) {
+ id[0] = 'B';
+ id[1] = 'C';
+ id[2] = 'M';
+ id[3] = 'V';
+}
+
+bool VpxPesParser::BcmvHeader::operator==(const BcmvHeader& other) const {
+ return (other.length == length && other.id[0] == id[0] &&
+ other.id[1] == id[1] && other.id[2] == id[2] && other.id[3] == id[3]);
+}
+
+bool VpxPesParser::BcmvHeader::Valid() const {
+ return (length > 0 && id[0] == 'B' && id[1] == 'C' && id[2] == 'M' &&
+ id[3] == 'V');
+}
+
+// TODO(tomfinegan): Break Open() into separate functions. One that opens the
+// file, and one that reads one packet at a time. As things are files larger
+// than the maximum availble memory for the current process cannot be loaded.
+bool VpxPesParser::Open(const std::string& pes_file) {
+ pes_file_size_ = static_cast<size_t>(libwebm::GetFileSize(pes_file));
+ if (pes_file_size_ <= 0)
+ return false;
+ pes_file_data_.reserve(static_cast<size_t>(pes_file_size_));
+ libwebm::FilePtr file = libwebm::FilePtr(std::fopen(pes_file.c_str(), "rb"),
+ libwebm::FILEDeleter());
+ int byte;
+ while ((byte = fgetc(file.get())) != EOF) {
+ pes_file_data_.push_back(static_cast<std::uint8_t>(byte));
+ }
+
+ if (!feof(file.get()) || ferror(file.get()) ||
+ pes_file_size_ != pes_file_data_.size()) {
+ return false;
+ }
+
+ read_pos_ = 0;
+ parse_state_ = kFindStartCode;
+ return true;
+}
+
+bool VpxPesParser::VerifyPacketStartCode() const {
+ if (read_pos_ + 2 > pes_file_data_.size())
+ return false;
+
+ // PES packets all start with the byte sequence 0x0 0x0 0x1.
+ if (pes_file_data_[read_pos_] != 0 || pes_file_data_[read_pos_ + 1] != 0 ||
+ pes_file_data_[read_pos_ + 2] != 1) {
+ return false;
+ }
+
+ return true;
+}
+
+bool VpxPesParser::ReadStreamId(std::uint8_t* stream_id) const {
+ if (!stream_id || BytesAvailable() < 4)
+ return false;
+
+ *stream_id = pes_file_data_[read_pos_ + 3];
+ return true;
+}
+
+bool VpxPesParser::ReadPacketLength(std::uint16_t* packet_length) const {
+ if (!packet_length || BytesAvailable() < 6)
+ return false;
+
+ // Read and byte swap 16 bit big endian length.
+ *packet_length =
+ (pes_file_data_[read_pos_ + 4] << 8) | pes_file_data_[read_pos_ + 5];
+
+ return true;
+}
+
+bool VpxPesParser::ParsePesHeader(PesHeader* header) {
+ if (!header || parse_state_ != kParsePesHeader)
+ return false;
+
+ if (!VerifyPacketStartCode())
+ return false;
+
+ std::size_t pos = read_pos_;
+ for (auto& a : header->start_code) {
+ a = pes_file_data_[pos++];
+ }
+
+ // PES Video stream IDs start at E0.
+ if (!ReadStreamId(&header->stream_id))
+ return false;
+
+ if (header->stream_id < kMinVideoStreamId ||
+ header->stream_id > kMaxVideoStreamId)
+ return false;
+
+ if (!ReadPacketLength(&header->packet_length))
+ return false;
+
+ read_pos_ += kPesHeaderSize;
+ parse_state_ = kParsePesOptionalHeader;
+ return true;
+}
+
+// TODO(tomfinegan): Make these masks constants.
+bool VpxPesParser::ParsePesOptionalHeader(PesOptionalHeader* header) {
+ if (!header || parse_state_ != kParsePesOptionalHeader ||
+ read_pos_ >= pes_file_size_) {
+ return false;
+ }
+
+ std::size_t consumed = 0;
+ PacketData poh_buffer;
+ if (!RemoveStartCodeEmulationPreventionBytes(&pes_file_data_[read_pos_],
+ kPesOptionalHeaderSize,
+ &poh_buffer, &consumed)) {
+ return false;
+ }
+
+ std::size_t offset = 0;
+ header->marker = (poh_buffer[offset] & 0x80) >> 6;
+ header->scrambling = (poh_buffer[offset] & 0x30) >> 4;
+ header->priority = (poh_buffer[offset] & 0x8) >> 3;
+ header->data_alignment = (poh_buffer[offset] & 0xc) >> 2;
+ header->copyright = (poh_buffer[offset] & 0x2) >> 1;
+ header->original = poh_buffer[offset] & 0x1;
+ offset++;
+
+ header->has_pts = (poh_buffer[offset] & 0x80) >> 7;
+ header->has_dts = (poh_buffer[offset] & 0x40) >> 6;
+ header->unused_fields = poh_buffer[offset] & 0x3f;
+ offset++;
+
+ header->remaining_size = poh_buffer[offset];
+ if (header->remaining_size !=
+ static_cast<int>(kWebm2PesOptHeaderRemainingSize))
+ return false;
+
+ size_t bytes_left = header->remaining_size;
+ offset++;
+
+ if (header->has_pts) {
+ // Read PTS markers. Format:
+ // PTS: 5 bytes
+ // 4 bits (flag: PTS present, but no DTS): 0x2 ('0010')
+ // 36 bits (90khz PTS):
+ // top 3 bits
+ // marker ('1')
+ // middle 15 bits
+ // marker ('1')
+ // bottom 15 bits
+ // marker ('1')
+ // TODO(tomfinegan): read/store the timestamp.
+ header->pts_dts_flag = (poh_buffer[offset] & 0x20) >> 4;
+ // Check the marker bits.
+ if ((poh_buffer[offset + 0] & 1) != 1 ||
+ (poh_buffer[offset + 2] & 1) != 1 ||
+ (poh_buffer[offset + 4] & 1) != 1) {
+ return false;
+ }
+
+ header->pts = (poh_buffer[offset] & 0xe) << 29 |
+ ((ReadUint16(&poh_buffer[offset + 1]) & ~1) << 14) |
+ (ReadUint16(&poh_buffer[offset + 3]) >> 1);
+ offset += 5;
+ bytes_left -= 5;
+ }
+
+ // Validate stuffing byte(s).
+ for (size_t i = 0; i < bytes_left; ++i) {
+ if (poh_buffer[offset + i] != 0xff)
+ return false;
+ }
+
+ read_pos_ += consumed;
+ parse_state_ = kParseBcmvHeader;
+
+ return true;
+}
+
+// Parses and validates a BCMV header.
+bool VpxPesParser::ParseBcmvHeader(BcmvHeader* header) {
+ if (!header || parse_state_ != kParseBcmvHeader)
+ return false;
+
+ PacketData bcmv_buffer;
+ std::size_t consumed = 0;
+ if (!RemoveStartCodeEmulationPreventionBytes(&pes_file_data_[read_pos_],
+ kBcmvHeaderSize, &bcmv_buffer,
+ &consumed)) {
+ return false;
+ }
+
+ std::size_t offset = 0;
+ header->id[0] = bcmv_buffer[offset++];
+ header->id[1] = bcmv_buffer[offset++];
+ header->id[2] = bcmv_buffer[offset++];
+ header->id[3] = bcmv_buffer[offset++];
+
+ header->length = 0;
+ header->length |= bcmv_buffer[offset++] << 24;
+ header->length |= bcmv_buffer[offset++] << 16;
+ header->length |= bcmv_buffer[offset++] << 8;
+ header->length |= bcmv_buffer[offset++];
+
+ // Length stored in the BCMV header is followed by 2 bytes of 0 padding.
+ if (bcmv_buffer[offset++] != 0 || bcmv_buffer[offset++] != 0)
+ return false;
+
+ if (!header->Valid())
+ return false;
+
+ parse_state_ = kFindStartCode;
+ read_pos_ += consumed;
+
+ return true;
+}
+
+bool VpxPesParser::FindStartCode(std::size_t origin,
+ std::size_t* offset) const {
+ if (read_pos_ + 2 >= pes_file_size_)
+ return false;
+
+ const std::size_t length = pes_file_size_ - origin;
+ if (length < 3)
+ return false;
+
+ const uint8_t* const data = &pes_file_data_[origin];
+ for (std::size_t i = 0; i < length - 3; ++i) {
+ if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) {
+ *offset = origin + i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool VpxPesParser::IsPayloadFragmented(const PesHeader& header) const {
+ return (header.packet_length != 0 &&
+ (header.packet_length - kPesOptionalHeaderSize) !=
+ header.bcmv_header.length);
+}
+
+bool VpxPesParser::AccumulateFragmentedPayload(std::size_t pes_packet_length,
+ std::size_t payload_length) {
+ const std::size_t first_fragment_length =
+ pes_packet_length - kPesOptionalHeaderSize - kBcmvHeaderSize;
+ for (std::size_t i = 0; i < first_fragment_length; ++i) {
+ payload_.push_back(pes_file_data_[read_pos_ + i]);
+ }
+ read_pos_ += first_fragment_length;
+ parse_state_ = kFindStartCode;
+
+ while (payload_.size() < payload_length) {
+ PesHeader header;
+ std::size_t packet_start_pos = read_pos_;
+ if (!FindStartCode(read_pos_, &packet_start_pos)) {
+ return false;
+ }
+ parse_state_ = kParsePesHeader;
+ read_pos_ = packet_start_pos;
+
+ if (!ParsePesHeader(&header)) {
+ return false;
+ }
+ if (!ParsePesOptionalHeader(&header.opt_header)) {
+ return false;
+ }
+
+ const std::size_t fragment_length =
+ header.packet_length - kPesOptionalHeaderSize;
+ std::size_t consumed = 0;
+ if (!RemoveStartCodeEmulationPreventionBytes(&pes_file_data_[read_pos_],
+ fragment_length, &payload_,
+ &consumed)) {
+ return false;
+ }
+ read_pos_ += consumed;
+ }
+ return true;
+}
+
+bool VpxPesParser::RemoveStartCodeEmulationPreventionBytes(
+ const std::uint8_t* raw_data, std::size_t bytes_required,
+ PacketData* processed_data, std::size_t* bytes_consumed) const {
+ if (bytes_required == 0 || !processed_data)
+ return false;
+
+ std::size_t num_zeros = 0;
+ std::size_t bytes_copied = 0;
+ const std::uint8_t* const end_of_input =
+ &pes_file_data_[0] + pes_file_data_.size();
+ std::size_t i;
+ for (i = 0; bytes_copied < bytes_required; ++i) {
+ if (raw_data + i > end_of_input)
+ return false;
+
+ bool skip = false;
+
+ const std::uint8_t byte = raw_data[i];
+ if (byte == 0) {
+ ++num_zeros;
+ } else if (byte == 0x3 && num_zeros == 2) {
+ skip = true;
+ num_zeros = 0;
+ } else {
+ num_zeros = 0;
+ }
+
+ if (skip == false) {
+ processed_data->push_back(byte);
+ ++bytes_copied;
+ }
+ }
+ *bytes_consumed = i;
+ return true;
+}
+
+int VpxPesParser::BytesAvailable() const {
+ return static_cast<int>(pes_file_data_.size() - read_pos_);
+}
+
+bool VpxPesParser::ParseNextPacket(PesHeader* header, VideoFrame* frame) {
+ if (!header || !frame || parse_state_ != kFindStartCode ||
+ BytesAvailable() == 0) {
+ return false;
+ }
+
+ std::size_t packet_start_pos = read_pos_;
+ if (!FindStartCode(read_pos_, &packet_start_pos)) {
+ return false;
+ }
+ parse_state_ = kParsePesHeader;
+ read_pos_ = packet_start_pos;
+
+ if (!ParsePesHeader(header)) {
+ return false;
+ }
+ if (!ParsePesOptionalHeader(&header->opt_header)) {
+ return false;
+ }
+ if (!ParseBcmvHeader(&header->bcmv_header)) {
+ return false;
+ }
+
+ // BCMV header length includes the length of the BCMVHeader itself. Adjust:
+ const std::size_t payload_length =
+ header->bcmv_header.length - BcmvHeader::size();
+
+ // Make sure there's enough input data to read the entire frame.
+ if (read_pos_ + payload_length > pes_file_data_.size()) {
+ // Need more data.
+ printf("VpxPesParser: Not enough data. Required: %u Available: %u\n",
+ static_cast<unsigned int>(payload_length),
+ static_cast<unsigned int>(pes_file_data_.size() - read_pos_));
+ parse_state_ = kFindStartCode;
+ read_pos_ = packet_start_pos;
+ return false;
+ }
+
+ if (IsPayloadFragmented(*header)) {
+ if (!AccumulateFragmentedPayload(header->packet_length, payload_length)) {
+ fprintf(stderr, "VpxPesParser: Failed parsing fragmented payload!\n");
+ return false;
+ }
+ } else {
+ std::size_t consumed = 0;
+ if (!RemoveStartCodeEmulationPreventionBytes(
+ &pes_file_data_[read_pos_], payload_length, &payload_, &consumed)) {
+ return false;
+ }
+ read_pos_ += consumed;
+ }
+
+ if (frame->buffer().capacity < payload_.size()) {
+ if (frame->Init(payload_.size()) == false) {
+ fprintf(stderr, "VpxPesParser: Out of memory.\n");
+ return false;
+ }
+ }
+ frame->set_nanosecond_pts(Khz90TicksToNanoseconds(header->opt_header.pts));
+ std::memcpy(frame->buffer().data.get(), &payload_[0], payload_.size());
+ frame->SetBufferLength(payload_.size());
+
+ payload_.clear();
+ parse_state_ = kFindStartCode;
+
+ return true;
+}
+
+} // namespace libwebm
diff --git a/m2ts/vpxpes_parser.h b/m2ts/vpxpes_parser.h
new file mode 100644
index 0000000..d18b4b7
--- /dev/null
+++ b/m2ts/vpxpes_parser.h
@@ -0,0 +1,177 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_M2TS_VPXPES_PARSER_H_
+#define LIBWEBM_M2TS_VPXPES_PARSER_H_
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "common/libwebm_util.h"
+#include "common/video_frame.h"
+
+namespace libwebm {
+
+// Parser for VPx PES. Requires that the _entire_ PES stream can be stored in
+// a std::vector<std::uint8_t> and read into memory when Open() is called.
+// TODO(tomfinegan): Support incremental parse.
+class VpxPesParser {
+ public:
+ typedef std::vector<std::uint8_t> PesFileData;
+ typedef std::vector<std::uint8_t> PacketData;
+
+ enum ParseState {
+ kFindStartCode,
+ kParsePesHeader,
+ kParsePesOptionalHeader,
+ kParseBcmvHeader,
+ };
+
+ struct PesOptionalHeader {
+ int marker = 0;
+ int scrambling = 0;
+ int priority = 0;
+ int data_alignment = 0;
+ int copyright = 0;
+ int original = 0;
+ int has_pts = 0;
+ int has_dts = 0;
+ int unused_fields = 0;
+ int remaining_size = 0;
+ int pts_dts_flag = 0;
+ std::uint64_t pts = 0;
+ int stuffing_byte = 0;
+ };
+
+ struct BcmvHeader {
+ BcmvHeader() = default;
+ ~BcmvHeader() = default;
+ BcmvHeader(const BcmvHeader&) = delete;
+ BcmvHeader(BcmvHeader&&) = delete;
+
+ // Convenience ctor for quick validation of expected values via operator==
+ // after parsing input.
+ explicit BcmvHeader(std::uint32_t len);
+
+ bool operator==(const BcmvHeader& other) const;
+
+ void Reset();
+ bool Valid() const;
+ static std::size_t size() { return 10; }
+
+ char id[4] = {0};
+ std::uint32_t length = 0;
+ };
+
+ struct PesHeader {
+ std::uint8_t start_code[4] = {0};
+ std::uint16_t packet_length = 0;
+ std::uint8_t stream_id = 0;
+ PesOptionalHeader opt_header;
+ BcmvHeader bcmv_header;
+ };
+
+ // Constants for validating known values from input data.
+ const std::uint8_t kMinVideoStreamId = 0xE0;
+ const std::uint8_t kMaxVideoStreamId = 0xEF;
+ const std::size_t kPesHeaderSize = 6;
+ const std::size_t kPesOptionalHeaderStartOffset = kPesHeaderSize;
+ const std::size_t kPesOptionalHeaderSize = 9;
+ const std::size_t kPesOptionalHeaderMarkerValue = 0x2;
+ const std::size_t kWebm2PesOptHeaderRemainingSize = 6;
+ const std::size_t kBcmvHeaderSize = 10;
+
+ VpxPesParser() = default;
+ ~VpxPesParser() = default;
+
+ // Opens file specified by |pes_file_path| and reads its contents. Returns
+ // true after successful read of input file.
+ bool Open(const std::string& pes_file_path);
+
+ // Parses the next packet in the PES. PES header information is stored in
+ // |header|, and the frame payload is stored in |frame|. Returns true when
+ // a full frame has been consumed from the PES.
+ bool ParseNextPacket(PesHeader* header, VideoFrame* frame);
+
+ // PES Header parsing utility functions.
+ // PES Header structure:
+ // Start code Stream ID Packet length (16 bits)
+ // / / ____/
+ // | | /
+ // Byte0 Byte1 Byte2 Byte3 Byte4 Byte5
+ // 0 0 1 X Y
+ bool VerifyPacketStartCode() const;
+ bool ReadStreamId(std::uint8_t* stream_id) const;
+ bool ReadPacketLength(std::uint16_t* packet_length) const;
+
+ std::uint64_t pes_file_size() const { return pes_file_size_; }
+ const PesFileData& pes_file_data() const { return pes_file_data_; }
+
+ // Returns number of unparsed bytes remaining.
+ int BytesAvailable() const;
+
+ private:
+ // Parses and verifies the static 6 byte portion that begins every PES packet.
+ bool ParsePesHeader(PesHeader* header);
+
+ // Parses a PES optional header, the optional header following the static
+ // header that begins the VPX PES packet.
+ // https://en.wikipedia.org/wiki/Packetized_elementary_stream
+ bool ParsePesOptionalHeader(PesOptionalHeader* header);
+
+ // Parses and validates the BCMV header. This immediately follows the optional
+ // header.
+ bool ParseBcmvHeader(BcmvHeader* header);
+
+ // Returns true when a start code is found and sets |offset| to the position
+ // of the start code relative to |pes_file_data_[read_pos_]|.
+ // Does not set |offset| value if the end of |pes_file_data_| is reached
+ // without locating a start code.
+ // Note: A start code is the byte sequence 0x00 0x00 0x01.
+ bool FindStartCode(std::size_t origin, std::size_t* offset) const;
+
+ // Returns true when a PES packet containing a BCMV header contains only a
+ // portion of the frame payload length reported by the BCMV header.
+ bool IsPayloadFragmented(const PesHeader& header) const;
+
+ // Parses PES and PES Optional header while accumulating payload data in
+ // |payload_|.
+ // Returns true once all payload fragments have been stored in |payload_|.
+ // Returns false if unable to accumulate full payload.
+ bool AccumulateFragmentedPayload(std::size_t pes_packet_length,
+ std::size_t payload_length);
+
+ // The byte sequence 0x0 0x0 0x1 is a start code in PES. When PES muxers
+ // encounter 0x0 0x0 0x1 or 0x0 0x0 0x3, an additional 0x3 is inserted into
+ // the PES. The following change occurs:
+ // 0x0 0x0 0x1 => 0x0 0x0 0x3 0x1
+ // 0x0 0x0 0x3 => 0x0 0x0 0x3 0x3
+ // PES demuxers must reverse the change:
+ // 0x0 0x0 0x3 0x1 => 0x0 0x0 0x1
+ // 0x0 0x0 0x3 0x3 => 0x0 0x0 0x3
+ // PES optional header, BCMV header, and payload data must be preprocessed to
+ // avoid potentially invalid data due to the presence of inserted bytes.
+ //
+ // Removes start code emulation prevention bytes while copying data from
+ // |raw_data| to |processed_data|. Returns true when |bytes_required| bytes
+ // have been written to |processed_data|. Reports bytes consumed during the
+ // operation via |bytes_consumed|.
+ bool RemoveStartCodeEmulationPreventionBytes(
+ const std::uint8_t* raw_data, std::size_t bytes_required,
+ PacketData* processed_data, std::size_t* bytes_consumed) const;
+
+ std::size_t pes_file_size_ = 0;
+ PacketData payload_;
+ PesFileData pes_file_data_;
+ std::size_t read_pos_ = 0;
+ ParseState parse_state_ = kFindStartCode;
+};
+
+} // namespace libwebm
+
+#endif // LIBWEBM_M2TS_VPXPES_PARSER_H_
diff --git a/m2ts/webm2pes.cc b/m2ts/webm2pes.cc
new file mode 100644
index 0000000..fc4b314
--- /dev/null
+++ b/m2ts/webm2pes.cc
@@ -0,0 +1,551 @@
+// Copyright (c) 2015 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "m2ts/webm2pes.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstdio>
+#include <cstring>
+#include <new>
+#include <vector>
+
+#include "common/libwebm_util.h"
+
+namespace libwebm {
+
+const std::size_t Webm2Pes::kMaxPayloadSize = 32768;
+
+namespace {
+
+std::string ToString(const char* str) {
+ return std::string((str == nullptr) ? "" : str);
+}
+
+} // namespace
+
+//
+// PesOptionalHeader methods.
+//
+
+void PesOptionalHeader::SetPtsBits(std::int64_t pts_90khz) {
+ std::uint64_t* pts_bits = &pts.bits;
+ *pts_bits = 0;
+
+ // PTS is broken up and stored in 40 bits as shown:
+ //
+ // PES PTS Only flag
+ // / Marker Marker Marker
+ // | / / /
+ // | | | |
+ // 7654 321 0 765432107654321 0 765432107654321 0
+ // 0010 PTS 32-30 1 PTS 29-15 1 PTS 14-0 1
+ const std::uint32_t pts1 = (pts_90khz >> 30) & 0x7;
+ const std::uint32_t pts2 = (pts_90khz >> 15) & 0x7FFF;
+ const std::uint32_t pts3 = pts_90khz & 0x7FFF;
+
+ std::uint8_t buffer[5] = {0};
+ // PTS only flag.
+ buffer[0] |= 1 << 5;
+ // Top 3 bits of PTS and 1 bit marker.
+ buffer[0] |= pts1 << 1;
+ // Marker.
+ buffer[0] |= 1;
+
+ // Next 15 bits of pts and 1 bit marker.
+ // Top 8 bits of second PTS chunk.
+ buffer[1] |= (pts2 >> 7) & 0xff;
+ // bottom 7 bits of second PTS chunk.
+ buffer[2] |= (pts2 << 1);
+ // Marker.
+ buffer[2] |= 1;
+
+ // Last 15 bits of pts and 1 bit marker.
+ // Top 8 bits of second PTS chunk.
+ buffer[3] |= (pts3 >> 7) & 0xff;
+ // bottom 7 bits of second PTS chunk.
+ buffer[4] |= (pts3 << 1);
+ // Marker.
+ buffer[4] |= 1;
+
+ // Write bits into PesHeaderField.
+ std::memcpy(reinterpret_cast<std::uint8_t*>(pts_bits), buffer, 5);
+}
+
+// Writes fields to |buffer| and returns true. Returns false when write or
+// field value validation fails.
+bool PesOptionalHeader::Write(bool write_pts, PacketDataBuffer* buffer) const {
+ if (buffer == nullptr) {
+ std::fprintf(stderr, "Webm2Pes: nullptr in opt header writer.\n");
+ return false;
+ }
+
+ const int kHeaderSize = 9;
+ std::uint8_t header[kHeaderSize] = {0};
+ std::uint8_t* byte = header;
+
+ if (marker.Check() != true || scrambling.Check() != true ||
+ priority.Check() != true || data_alignment.Check() != true ||
+ copyright.Check() != true || original.Check() != true ||
+ has_pts.Check() != true || has_dts.Check() != true ||
+ pts.Check() != true || stuffing_byte.Check() != true) {
+ std::fprintf(stderr, "Webm2Pes: Invalid PES Optional Header field.\n");
+ return false;
+ }
+
+ // TODO(tomfinegan): As noted in above, the PesHeaderFields should be an
+ // array (or some data structure) that can be iterated over.
+
+ // First byte of header, fields: marker, scrambling, priority, alignment,
+ // copyright, original.
+ *byte = 0;
+ *byte |= marker.bits << marker.shift;
+ *byte |= scrambling.bits << scrambling.shift;
+ *byte |= priority.bits << priority.shift;
+ *byte |= data_alignment.bits << data_alignment.shift;
+ *byte |= copyright.bits << copyright.shift;
+ *byte |= original.bits << original.shift;
+
+ // Second byte of header, fields: has_pts, has_dts, unused fields.
+ *++byte = 0;
+ if (write_pts == true)
+ *byte |= has_pts.bits << has_pts.shift;
+
+ *byte |= has_dts.bits << has_dts.shift;
+
+ // Third byte of header, fields: remaining size of header.
+ *++byte = remaining_size.bits & 0xff; // Field is 8 bits wide.
+
+ int num_stuffing_bytes =
+ (pts.num_bits + 7) / 8 + 1 /* always 1 stuffing byte */;
+ if (write_pts == true) {
+ // Write the PTS value as big endian and adjust stuffing byte count
+ // accordingly.
+ *++byte = pts.bits & 0xff;
+ *++byte = (pts.bits >> 8) & 0xff;
+ *++byte = (pts.bits >> 16) & 0xff;
+ *++byte = (pts.bits >> 24) & 0xff;
+ *++byte = (pts.bits >> 32) & 0xff;
+ num_stuffing_bytes = 1;
+ }
+
+ // Add the stuffing byte(s).
+ for (int i = 0; i < num_stuffing_bytes; ++i)
+ *++byte = stuffing_byte.bits & 0xff;
+
+ return CopyAndEscapeStartCodes(&header[0], kHeaderSize, buffer);
+}
+
+//
+// BCMVHeader methods.
+//
+
+bool BCMVHeader::Write(PacketDataBuffer* buffer) const {
+ if (buffer == nullptr) {
+ std::fprintf(stderr, "Webm2Pes: nullptr for buffer in BCMV Write.\n");
+ return false;
+ }
+ const int kBcmvSize = 4;
+ for (int i = 0; i < kBcmvSize; ++i)
+ buffer->push_back(bcmv[i]);
+
+ // Note: The 4 byte length field must include the size of the BCMV header.
+ const int kRemainingBytes = 6;
+ const uint32_t bcmv_total_length = length + static_cast<uint32_t>(size());
+ const uint8_t bcmv_buffer[kRemainingBytes] = {
+ static_cast<std::uint8_t>((bcmv_total_length >> 24) & 0xff),
+ static_cast<std::uint8_t>((bcmv_total_length >> 16) & 0xff),
+ static_cast<std::uint8_t>((bcmv_total_length >> 8) & 0xff),
+ static_cast<std::uint8_t>(bcmv_total_length & 0xff),
+ 0,
+ 0 /* 2 bytes 0 padding */};
+
+ return CopyAndEscapeStartCodes(bcmv_buffer, kRemainingBytes, buffer);
+}
+
+//
+// PesHeader methods.
+//
+
+// Writes out the header to |buffer|. Calls PesOptionalHeader::Write() to write
+// |optional_header| contents. Returns true when successful, false otherwise.
+bool PesHeader::Write(bool write_pts, PacketDataBuffer* buffer) const {
+ if (buffer == nullptr) {
+ std::fprintf(stderr, "Webm2Pes: nullptr in header writer.\n");
+ return false;
+ }
+
+ // Write |start_code|.
+ const int kStartCodeLength = 4;
+ for (int i = 0; i < kStartCodeLength; ++i)
+ buffer->push_back(start_code[i]);
+
+ // The length field here reports number of bytes following the field. The
+ // length of the optional header must be added to the payload length set by
+ // the user.
+ const std::size_t header_length =
+ packet_length + optional_header.size_in_bytes();
+ if (header_length > UINT16_MAX)
+ return false;
+
+ // Write |header_length| as big endian.
+ std::uint8_t byte = (header_length >> 8) & 0xff;
+ buffer->push_back(byte);
+ byte = header_length & 0xff;
+ buffer->push_back(byte);
+
+ // Write the (not really) optional header.
+ if (optional_header.Write(write_pts, buffer) != true) {
+ std::fprintf(stderr, "Webm2Pes: PES optional header write failed.");
+ return false;
+ }
+ return true;
+}
+
+//
+// Webm2Pes methods.
+//
+
+bool Webm2Pes::ConvertToFile() {
+ if (input_file_name_.empty() || output_file_name_.empty()) {
+ std::fprintf(stderr, "Webm2Pes: input and/or output file name(s) empty.\n");
+ return false;
+ }
+
+ output_file_ = FilePtr(fopen(output_file_name_.c_str(), "wb"), FILEDeleter());
+ if (output_file_ == nullptr) {
+ std::fprintf(stderr, "Webm2Pes: Cannot open %s for output.\n",
+ output_file_name_.c_str());
+ return false;
+ }
+
+ if (InitWebmParser() != true) {
+ std::fprintf(stderr, "Webm2Pes: Cannot initialize WebM parser.\n");
+ return false;
+ }
+
+ // Walk clusters in segment.
+ const mkvparser::Cluster* cluster = webm_parser_->GetFirst();
+ while (cluster != nullptr && cluster->EOS() == false) {
+ const mkvparser::BlockEntry* block_entry = nullptr;
+ std::int64_t block_status = cluster->GetFirst(block_entry);
+ if (block_status < 0) {
+ std::fprintf(stderr, "Webm2Pes: Cannot parse first block in %s.\n",
+ input_file_name_.c_str());
+ return false;
+ }
+
+ // Walk blocks in cluster.
+ while (block_entry != nullptr && block_entry->EOS() == false) {
+ const mkvparser::Block* block = block_entry->GetBlock();
+ if (block->GetTrackNumber() == video_track_num_) {
+ const int frame_count = block->GetFrameCount();
+
+ // Walk frames in block.
+ for (int frame_num = 0; frame_num < frame_count; ++frame_num) {
+ const mkvparser::Block::Frame& mkvparser_frame =
+ block->GetFrame(frame_num);
+
+ // Read the frame.
+ VideoFrame vpx_frame(block->GetTime(cluster), codec_);
+ if (ReadVideoFrame(mkvparser_frame, &vpx_frame) == false) {
+ fprintf(stderr, "Webm2Pes: frame read failed.\n");
+ return false;
+ }
+
+ // Write frame out as PES packet(s).
+ if (WritePesPacket(vpx_frame, &packet_data_) == false) {
+ std::fprintf(stderr, "Webm2Pes: WritePesPacket failed.\n");
+ return false;
+ }
+
+ // Write contents of |packet_data_| to |output_file_|.
+ if (std::fwrite(&packet_data_[0], 1, packet_data_.size(),
+ output_file_.get()) != packet_data_.size()) {
+ std::fprintf(stderr, "Webm2Pes: packet payload write failed.\n");
+ return false;
+ }
+ bytes_written_ += packet_data_.size();
+ }
+ }
+ block_status = cluster->GetNext(block_entry, block_entry);
+ if (block_status < 0) {
+ std::fprintf(stderr, "Webm2Pes: Cannot parse block in %s.\n",
+ input_file_name_.c_str());
+ return false;
+ }
+ }
+
+ cluster = webm_parser_->GetNext(cluster);
+ }
+
+ std::fflush(output_file_.get());
+ return true;
+}
+
+bool Webm2Pes::ConvertToPacketReceiver() {
+ if (input_file_name_.empty() || packet_sink_ == nullptr) {
+ std::fprintf(stderr, "Webm2Pes: input file name empty or null sink.\n");
+ return false;
+ }
+
+ if (InitWebmParser() != true) {
+ std::fprintf(stderr, "Webm2Pes: Cannot initialize WebM parser.\n");
+ return false;
+ }
+
+ // Walk clusters in segment.
+ const mkvparser::Cluster* cluster = webm_parser_->GetFirst();
+ while (cluster != nullptr && cluster->EOS() == false) {
+ const mkvparser::BlockEntry* block_entry = nullptr;
+ std::int64_t block_status = cluster->GetFirst(block_entry);
+ if (block_status < 0) {
+ std::fprintf(stderr, "Webm2Pes: Cannot parse first block in %s.\n",
+ input_file_name_.c_str());
+ return false;
+ }
+
+ // Walk blocks in cluster.
+ while (block_entry != nullptr && block_entry->EOS() == false) {
+ const mkvparser::Block* block = block_entry->GetBlock();
+ if (block->GetTrackNumber() == video_track_num_) {
+ const int frame_count = block->GetFrameCount();
+
+ // Walk frames in block.
+ for (int frame_num = 0; frame_num < frame_count; ++frame_num) {
+ const mkvparser::Block::Frame& mkvparser_frame =
+ block->GetFrame(frame_num);
+
+ // Read the frame.
+ VideoFrame frame(block->GetTime(cluster), codec_);
+ if (ReadVideoFrame(mkvparser_frame, &frame) == false) {
+ fprintf(stderr, "Webm2Pes: frame read failed.\n");
+ return false;
+ }
+
+ // Write frame out as PES packet(s).
+ if (WritePesPacket(frame, &packet_data_) == false) {
+ std::fprintf(stderr, "Webm2Pes: WritePesPacket failed.\n");
+ return false;
+ }
+ if (packet_sink_->ReceivePacket(packet_data_) != true) {
+ std::fprintf(stderr, "Webm2Pes: ReceivePacket failed.\n");
+ return false;
+ }
+ bytes_written_ += packet_data_.size();
+ }
+ }
+ block_status = cluster->GetNext(block_entry, block_entry);
+ if (block_status < 0) {
+ std::fprintf(stderr, "Webm2Pes: Cannot parse block in %s.\n",
+ input_file_name_.c_str());
+ return false;
+ }
+ }
+
+ cluster = webm_parser_->GetNext(cluster);
+ }
+
+ return true;
+}
+
+bool Webm2Pes::InitWebmParser() {
+ if (webm_reader_.Open(input_file_name_.c_str()) != 0) {
+ std::fprintf(stderr, "Webm2Pes: Cannot open %s as input.\n",
+ input_file_name_.c_str());
+ return false;
+ }
+
+ using mkvparser::Segment;
+ Segment* webm_parser = nullptr;
+ if (Segment::CreateInstance(&webm_reader_, 0 /* pos */,
+ webm_parser /* Segment*& */) != 0) {
+ std::fprintf(stderr, "Webm2Pes: Cannot create WebM parser.\n");
+ return false;
+ }
+ webm_parser_.reset(webm_parser);
+
+ if (webm_parser_->Load() != 0) {
+ std::fprintf(stderr, "Webm2Pes: Cannot parse %s.\n",
+ input_file_name_.c_str());
+ return false;
+ }
+
+ // Make sure there's a video track.
+ const mkvparser::Tracks* tracks = webm_parser_->GetTracks();
+ if (tracks == nullptr) {
+ std::fprintf(stderr, "Webm2Pes: %s has no tracks.\n",
+ input_file_name_.c_str());
+ return false;
+ }
+
+ timecode_scale_ = webm_parser_->GetInfo()->GetTimeCodeScale();
+
+ for (int track_index = 0;
+ track_index < static_cast<int>(tracks->GetTracksCount());
+ ++track_index) {
+ const mkvparser::Track* track = tracks->GetTrackByIndex(track_index);
+ if (track && track->GetType() == mkvparser::Track::kVideo) {
+ const std::string codec_id = ToString(track->GetCodecId());
+ if (codec_id == std::string("V_VP8")) {
+ codec_ = VideoFrame::kVP8;
+ } else if (codec_id == std::string("V_VP9")) {
+ codec_ = VideoFrame::kVP9;
+ } else {
+ fprintf(stderr, "Webm2Pes: Codec must be VP8 or VP9.\n");
+ return false;
+ }
+ video_track_num_ = track_index + 1;
+ break;
+ }
+ }
+ if (video_track_num_ < 1) {
+ std::fprintf(stderr, "Webm2Pes: No video track found in %s.\n",
+ input_file_name_.c_str());
+ return false;
+ }
+ return true;
+}
+
+bool Webm2Pes::ReadVideoFrame(const mkvparser::Block::Frame& mkvparser_frame,
+ VideoFrame* frame) {
+ if (mkvparser_frame.len < 1 || frame == nullptr)
+ return false;
+
+ const std::size_t mkv_len = static_cast<std::size_t>(mkvparser_frame.len);
+ if (mkv_len > frame->buffer().capacity) {
+ const std::size_t new_size = 2 * mkv_len;
+ if (frame->Init(new_size) == false) {
+ std::fprintf(stderr, "Webm2Pes: Out of memory.\n");
+ return false;
+ }
+ }
+ if (mkvparser_frame.Read(&webm_reader_, frame->buffer().data.get()) != 0) {
+ std::fprintf(stderr, "Webm2Pes: Error reading VPx frame!\n");
+ return false;
+ }
+ return frame->SetBufferLength(mkv_len);
+}
+
+bool Webm2Pes::WritePesPacket(const VideoFrame& frame,
+ PacketDataBuffer* packet_data) {
+ if (frame.buffer().data.get() == nullptr || frame.buffer().length < 1)
+ return false;
+
+ Ranges frame_ranges;
+ if (frame.codec() == VideoFrame::kVP9) {
+ bool error = false;
+ const bool has_superframe_index =
+ ParseVP9SuperFrameIndex(frame.buffer().data.get(),
+ frame.buffer().length, &frame_ranges, &error);
+ if (error) {
+ std::fprintf(stderr, "Webm2Pes: Superframe index parse failed.\n");
+ return false;
+ }
+ if (has_superframe_index == false) {
+ frame_ranges.push_back(Range(0, frame.buffer().length));
+ }
+ } else {
+ frame_ranges.push_back(Range(0, frame.buffer().length));
+ }
+
+ const std::int64_t khz90_pts =
+ NanosecondsTo90KhzTicks(frame.nanosecond_pts());
+ PesHeader header;
+ header.optional_header.SetPtsBits(khz90_pts);
+
+ packet_data->clear();
+
+ for (const Range& packet_payload_range : frame_ranges) {
+ std::size_t extra_bytes = 0;
+ if (packet_payload_range.length > kMaxPayloadSize) {
+ extra_bytes = packet_payload_range.length - kMaxPayloadSize;
+ }
+ if (packet_payload_range.length + packet_payload_range.offset >
+ frame.buffer().length) {
+ std::fprintf(stderr, "Webm2Pes: Invalid frame length.\n");
+ return false;
+ }
+
+ // First packet of new frame. Always include PTS and BCMV header.
+ header.packet_length =
+ packet_payload_range.length - extra_bytes + BCMVHeader::size();
+ if (header.Write(true, packet_data) != true) {
+ std::fprintf(stderr, "Webm2Pes: packet header write failed.\n");
+ return false;
+ }
+
+ BCMVHeader bcmv_header(static_cast<uint32_t>(packet_payload_range.length));
+ if (bcmv_header.Write(packet_data) != true) {
+ std::fprintf(stderr, "Webm2Pes: BCMV write failed.\n");
+ return false;
+ }
+
+ // Insert the payload at the end of |packet_data|.
+ const std::uint8_t* const payload_start =
+ frame.buffer().data.get() + packet_payload_range.offset;
+
+ const std::size_t bytes_to_copy = packet_payload_range.length - extra_bytes;
+ if (CopyAndEscapeStartCodes(payload_start, bytes_to_copy, packet_data) ==
+ false) {
+ fprintf(stderr, "Webm2Pes: Payload write failed.\n");
+ return false;
+ }
+
+ std::size_t bytes_copied = bytes_to_copy;
+ while (extra_bytes) {
+ // Write PES packets for the remaining data, but omit the PTS and BCMV
+ // header.
+ const std::size_t extra_bytes_to_copy =
+ std::min(kMaxPayloadSize, extra_bytes);
+ extra_bytes -= extra_bytes_to_copy;
+ header.packet_length = extra_bytes_to_copy;
+ if (header.Write(false, packet_data) != true) {
+ fprintf(stderr, "Webm2pes: fragment write failed.\n");
+ return false;
+ }
+
+ const std::uint8_t* fragment_start = payload_start + bytes_copied;
+ if (CopyAndEscapeStartCodes(fragment_start, extra_bytes_to_copy,
+ packet_data) == false) {
+ fprintf(stderr, "Webm2Pes: Payload write failed.\n");
+ return false;
+ }
+
+ bytes_copied += extra_bytes_to_copy;
+ }
+ }
+
+ return true;
+}
+
+bool CopyAndEscapeStartCodes(const std::uint8_t* raw_input,
+ std::size_t raw_input_length,
+ PacketDataBuffer* packet_buffer) {
+ if (raw_input == nullptr || raw_input_length < 1 || packet_buffer == nullptr)
+ return false;
+
+ int num_zeros = 0;
+ for (std::size_t i = 0; i < raw_input_length; ++i) {
+ const uint8_t byte = raw_input[i];
+
+ if (byte == 0) {
+ ++num_zeros;
+ } else if (num_zeros >= 2 && (byte == 0x1 || byte == 0x3)) {
+ packet_buffer->push_back(0x3);
+ num_zeros = 0;
+ } else {
+ num_zeros = 0;
+ }
+
+ packet_buffer->push_back(byte);
+ }
+
+ return true;
+}
+
+} // namespace libwebm
diff --git a/m2ts/webm2pes.h b/m2ts/webm2pes.h
new file mode 100644
index 0000000..6dcb0fd
--- /dev/null
+++ b/m2ts/webm2pes.h
@@ -0,0 +1,274 @@
+// Copyright (c) 2015 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_M2TS_WEBM2PES_H_
+#define LIBWEBM_M2TS_WEBM2PES_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "common/libwebm_util.h"
+#include "common/video_frame.h"
+#include "mkvparser/mkvparser.h"
+#include "mkvparser/mkvreader.h"
+
+// Webm2pes
+//
+// Webm2pes consumes a WebM file containing a VP8 or VP9 video stream and
+// outputs a PES stream suitable for inclusion in a MPEG2 Transport Stream.
+//
+// In the simplest case the PES stream output by Webm2pes consists of a sequence
+// of PES packets with the following structure:
+// | PES Header w/PTS | BCMV Header | Payload (VPx frame) |
+//
+// More typically the output will look like the following due to the PES
+// payload size limitations caused by the format of the PES header.
+// The PES header contains only 2 bytes of storage for expressing payload size.
+// VPx PES streams containing fragmented packets look like this:
+//
+// | PH PTS | BCMV | Payload fragment 1 | PH | Payload fragment 2 | ...
+//
+// PH = PES Header
+// PH PTS = PES Header with PTS
+// BCMV = BCMV Header
+//
+// Note that start codes are properly escaped by Webm2pes, and start code
+// emulation prevention bytes must be stripped from the output stream before
+// it can be parsed.
+
+namespace libwebm {
+
+// Stores a value and its size in bits for writing into a PES Optional Header.
+// Maximum size is 64 bits. Users may call the Check() method to perform minimal
+// validation (size > 0 and <= 64).
+struct PesHeaderField {
+ PesHeaderField(std::uint64_t value, std::uint32_t size_in_bits,
+ std::uint8_t byte_index, std::uint8_t bits_to_shift)
+ : bits(value),
+ num_bits(size_in_bits),
+ index(byte_index),
+ shift(bits_to_shift) {}
+ PesHeaderField() = delete;
+ PesHeaderField(const PesHeaderField&) = default;
+ PesHeaderField(PesHeaderField&&) = default;
+ ~PesHeaderField() = default;
+ bool Check() const {
+ return num_bits > 0 && num_bits <= 64 && shift >= 0 && shift < 64;
+ }
+
+ // Value to be stored in the field.
+ std::uint64_t bits;
+
+ // Number of bits in the value.
+ const int num_bits;
+
+ // Index into the header for the byte in which |bits| will be written.
+ const std::uint8_t index;
+
+ // Number of bits to shift value before or'ing.
+ const int shift;
+};
+
+// Data is stored in buffers before being written to output files.
+typedef std::vector<std::uint8_t> PacketDataBuffer;
+
+// Storage for PES Optional Header values. Fields written in order using sizes
+// specified.
+struct PesOptionalHeader {
+ // TODO(tomfinegan): The fields could be in an array, which would allow the
+ // code writing the optional header to iterate over the fields instead of
+ // having code for dealing with each one.
+
+ // 2 bits (marker): 2 ('10')
+ const PesHeaderField marker = PesHeaderField(2, 2, 0, 6);
+
+ // 2 bits (no scrambling): 0x0 ('00')
+ const PesHeaderField scrambling = PesHeaderField(0, 2, 0, 4);
+
+ // 1 bit (priority): 0x0 ('0')
+ const PesHeaderField priority = PesHeaderField(0, 1, 0, 3);
+
+ // TODO(tomfinegan): The BCMV header could be considered a sync word, and this
+ // field should be 1 when a sync word follows the packet. Clarify.
+ // 1 bit (data alignment): 0x0 ('0')
+ const PesHeaderField data_alignment = PesHeaderField(0, 1, 0, 2);
+
+ // 1 bit (copyright): 0x0 ('0')
+ const PesHeaderField copyright = PesHeaderField(0, 1, 0, 1);
+
+ // 1 bit (original/copy): 0x0 ('0')
+ const PesHeaderField original = PesHeaderField(0, 1, 0, 0);
+
+ // 1 bit (has_pts): 0x1 ('1')
+ const PesHeaderField has_pts = PesHeaderField(1, 1, 1, 7);
+
+ // 1 bit (has_dts): 0x0 ('0')
+ const PesHeaderField has_dts = PesHeaderField(0, 1, 1, 6);
+
+ // 6 bits (unused fields): 0x0 ('000000')
+ const PesHeaderField unused = PesHeaderField(0, 6, 1, 0);
+
+ // 8 bits (size of remaining data in the Header).
+ const PesHeaderField remaining_size = PesHeaderField(6, 8, 2, 0);
+
+ // PTS: 5 bytes
+ // 4 bits (flag: PTS present, but no DTS): 0x2 ('0010')
+ // 36 bits (90khz PTS):
+ // top 3 bits
+ // marker ('1')
+ // middle 15 bits
+ // marker ('1')
+ // bottom 15 bits
+ // marker ('1')
+ PesHeaderField pts = PesHeaderField(0, 40, 3, 0);
+
+ PesHeaderField stuffing_byte = PesHeaderField(0xFF, 8, 8, 0);
+
+ // PTS omitted in fragments. Size remains unchanged: More stuffing bytes.
+ bool fragment = false;
+
+ static std::size_t size_in_bytes() { return 9; }
+
+ // Writes |pts_90khz| to |pts| per format described at its declaration above.
+ void SetPtsBits(std::int64_t pts_90khz);
+
+ // Writes fields to |buffer| and returns true. Returns false when write or
+ // field value validation fails.
+ bool Write(bool write_pts, PacketDataBuffer* buffer) const;
+};
+
+// Describes custom 10 byte header that immediately follows the PES Optional
+// Header in each PES packet output by Webm2Pes:
+// 4 byte 'B' 'C' 'M' 'V'
+// 4 byte big-endian length of frame
+// 2 bytes 0 padding
+struct BCMVHeader {
+ explicit BCMVHeader(std::uint32_t frame_length) : length(frame_length) {}
+ BCMVHeader() = delete;
+ BCMVHeader(const BCMVHeader&) = delete;
+ BCMVHeader(BCMVHeader&&) = delete;
+ ~BCMVHeader() = default;
+ const std::uint8_t bcmv[4] = {'B', 'C', 'M', 'V'};
+ const std::uint32_t length;
+
+ static std::size_t size() { return 10; }
+
+ // Write the BCMV Header into |buffer|. Caller responsible for ensuring
+ // destination buffer is of size >= BCMVHeader::size().
+ bool Write(PacketDataBuffer* buffer) const;
+ bool Write(uint8_t* buffer);
+};
+
+struct PesHeader {
+ const std::uint8_t start_code[4] = {
+ 0x00, 0x00,
+ 0x01, // 0x000001 is the PES packet start code prefix.
+ 0xE0}; // 0xE0 is the minimum video stream ID.
+ std::uint16_t packet_length = 0; // Number of bytes _after_ this field.
+ PesOptionalHeader optional_header;
+ std::size_t size() const {
+ return optional_header.size_in_bytes() +
+ 6 /* start_code + packet_length */ + packet_length;
+ }
+
+ // Writes out the header to |buffer|. Calls PesOptionalHeader::Write() to
+ // write |optional_header| contents. Returns true when successful, false
+ // otherwise.
+ bool Write(bool write_pts, PacketDataBuffer* buffer) const;
+};
+
+class PacketReceiverInterface {
+ public:
+ virtual ~PacketReceiverInterface() {}
+ virtual bool ReceivePacket(const PacketDataBuffer& packet) = 0;
+};
+
+// Converts the VP9 track of a WebM file to a Packetized Elementary Stream
+// suitable for use in a MPEG2TS.
+// https://en.wikipedia.org/wiki/Packetized_elementary_stream
+// https://en.wikipedia.org/wiki/MPEG_transport_stream
+class Webm2Pes {
+ public:
+ static const std::size_t kMaxPayloadSize;
+
+ Webm2Pes(const std::string& input_file, const std::string& output_file)
+ : input_file_name_(input_file), output_file_name_(output_file) {}
+ Webm2Pes(const std::string& input_file, PacketReceiverInterface* packet_sink)
+ : input_file_name_(input_file), packet_sink_(packet_sink) {}
+
+ Webm2Pes() = delete;
+ Webm2Pes(const Webm2Pes&) = delete;
+ Webm2Pes(Webm2Pes&&) = delete;
+ ~Webm2Pes() = default;
+
+ // Converts the VPx video stream to a PES file and returns true. Returns false
+ // to report failure.
+ bool ConvertToFile();
+
+ // Converts the VPx video stream to a sequence of PES packets, and calls the
+ // PacketReceiverInterface::ReceivePacket() once for each VPx frame. The
+ // packet sent to the receiver may contain multiple PES packets. Returns only
+ // after full conversion or error. Returns true for success, and false when
+ // an error occurs.
+ bool ConvertToPacketReceiver();
+
+ // Writes |vpx_frame| out as PES packet[s] and stores output in |packet_data|.
+ // Returns true for success, false for failure.
+ static bool WritePesPacket(const VideoFrame& frame,
+ PacketDataBuffer* packet_data);
+
+ uint64_t bytes_written() const { return bytes_written_; }
+
+ private:
+ bool InitWebmParser();
+ bool ReadVideoFrame(const mkvparser::Block::Frame& mkvparser_frame,
+ VideoFrame* frame);
+
+ const std::string input_file_name_;
+ const std::string output_file_name_;
+ std::unique_ptr<mkvparser::Segment> webm_parser_;
+ mkvparser::MkvReader webm_reader_;
+ FilePtr output_file_;
+
+ // Video track num in the WebM file.
+ int video_track_num_ = 0;
+
+ // Video codec reported by CodecName from Video TrackEntry.
+ VideoFrame::Codec codec_;
+
+ // Input timecode scale.
+ std::int64_t timecode_scale_ = 1000000;
+
+ // Packet sink; when constructed with a PacketReceiverInterface*, packet and
+ // type of packet are sent to |packet_sink_| instead of written to an output
+ // file.
+ PacketReceiverInterface* packet_sink_ = nullptr;
+
+ PacketDataBuffer packet_data_;
+
+ std::uint64_t bytes_written_ = 0;
+};
+
+// Copies |raw_input_length| bytes from |raw_input| to |packet_buffer| while
+// escaping start codes. Returns true when bytes are successfully copied.
+// A start code is the 3 byte sequence 0x00 0x00 0x01. When
+// the sequence is encountered, the value 0x03 is inserted. To avoid
+// any ambiguity at reassembly time, the same is done for the sequence
+// 0x00 0x00 0x03. So, the following transformation occurs for when either
+// of the noted sequences is encountered:
+//
+// 0x00 0x00 0x01 => 0x00 0x00 0x03 0x01
+// 0x00 0x00 0x03 => 0x00 0x00 0x03 0x03
+bool CopyAndEscapeStartCodes(const std::uint8_t* raw_input,
+ std::size_t raw_input_length,
+ PacketDataBuffer* packet_buffer);
+} // namespace libwebm
+
+#endif // LIBWEBM_M2TS_WEBM2PES_H_
diff --git a/m2ts/webm2pes_main.cc b/m2ts/webm2pes_main.cc
new file mode 100644
index 0000000..075e55c
--- /dev/null
+++ b/m2ts/webm2pes_main.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2015 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "m2ts/webm2pes.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <string>
+
+namespace {
+
+void Usage(const char* argv[]) {
+ printf("Usage: %s <WebM file> <output file>", argv[0]);
+}
+
+} // namespace
+
+int main(int argc, const char* argv[]) {
+ if (argc < 3) {
+ Usage(argv);
+ return EXIT_FAILURE;
+ }
+
+ const std::string input_path = argv[1];
+ const std::string output_path = argv[2];
+
+ libwebm::Webm2Pes converter(input_path, output_path);
+ return converter.ConvertToFile() == true ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/mkvmuxer.hpp b/mkvmuxer.hpp
new file mode 100644
index 0000000..092592b
--- /dev/null
+++ b/mkvmuxer.hpp
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_MKVMUXER_HPP_
+#define LIBWEBM_MKVMUXER_HPP_
+
+// This file is a wrapper for the file included immediately after this comment.
+// New projects should not include this file: include the file included below.
+#include "mkvmuxer/mkvmuxer.h"
+
+#endif // LIBWEBM_MKVMUXER_HPP_
diff --git a/mkvmuxer/mkvmuxer.cc b/mkvmuxer/mkvmuxer.cc
new file mode 100644
index 0000000..5120312
--- /dev/null
+++ b/mkvmuxer/mkvmuxer.cc
@@ -0,0 +1,4221 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#include "mkvmuxer/mkvmuxer.h"
+
+#include <stdint.h>
+
+#include <cfloat>
+#include <climits>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <memory>
+#include <new>
+#include <string>
+#include <vector>
+
+#include "common/webmids.h"
+#include "mkvmuxer/mkvmuxerutil.h"
+#include "mkvmuxer/mkvwriter.h"
+#include "mkvparser/mkvparser.h"
+
+namespace mkvmuxer {
+
+const float PrimaryChromaticity::kChromaticityMin = 0.0f;
+const float PrimaryChromaticity::kChromaticityMax = 1.0f;
+const float MasteringMetadata::kMinLuminance = 0.0f;
+const float MasteringMetadata::kMinLuminanceMax = 999.99f;
+const float MasteringMetadata::kMaxLuminanceMax = 9999.99f;
+const float MasteringMetadata::kValueNotPresent = FLT_MAX;
+const uint64_t Colour::kValueNotPresent = UINT64_MAX;
+
+namespace {
+
+const char kDocTypeWebm[] = "webm";
+const char kDocTypeMatroska[] = "matroska";
+
+// Deallocate the string designated by |dst|, and then copy the |src|
+// string to |dst|. The caller owns both the |src| string and the
+// |dst| copy (hence the caller is responsible for eventually
+// deallocating the strings, either directly, or indirectly via
+// StrCpy). Returns true if the source string was successfully copied
+// to the destination.
+bool StrCpy(const char* src, char** dst_ptr) {
+ if (dst_ptr == NULL)
+ return false;
+
+ char*& dst = *dst_ptr;
+
+ delete[] dst;
+ dst = NULL;
+
+ if (src == NULL)
+ return true;
+
+ const size_t size = strlen(src) + 1;
+
+ dst = new (std::nothrow) char[size]; // NOLINT
+ if (dst == NULL)
+ return false;
+
+ strcpy(dst, src); // NOLINT
+ return true;
+}
+
+typedef std::unique_ptr<PrimaryChromaticity> PrimaryChromaticityPtr;
+bool CopyChromaticity(const PrimaryChromaticity* src,
+ PrimaryChromaticityPtr* dst) {
+ if (!dst)
+ return false;
+
+ dst->reset(new (std::nothrow) PrimaryChromaticity(src->x(), src->y()));
+ if (!dst->get())
+ return false;
+
+ return true;
+}
+
+} // namespace
+
+///////////////////////////////////////////////////////////////
+//
+// IMkvWriter Class
+
+IMkvWriter::IMkvWriter() {}
+
+IMkvWriter::~IMkvWriter() {}
+
+bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version,
+ const char* const doc_type) {
+ // Level 0
+ uint64_t size =
+ EbmlElementSize(libwebm::kMkvEBMLVersion, static_cast<uint64>(1));
+ size += EbmlElementSize(libwebm::kMkvEBMLReadVersion, static_cast<uint64>(1));
+ size += EbmlElementSize(libwebm::kMkvEBMLMaxIDLength, static_cast<uint64>(4));
+ size +=
+ EbmlElementSize(libwebm::kMkvEBMLMaxSizeLength, static_cast<uint64>(8));
+ size += EbmlElementSize(libwebm::kMkvDocType, doc_type);
+ size += EbmlElementSize(libwebm::kMkvDocTypeVersion,
+ static_cast<uint64>(doc_type_version));
+ size +=
+ EbmlElementSize(libwebm::kMkvDocTypeReadVersion, static_cast<uint64>(2));
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvEBML, size))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvEBMLVersion,
+ static_cast<uint64>(1))) {
+ return false;
+ }
+ if (!WriteEbmlElement(writer, libwebm::kMkvEBMLReadVersion,
+ static_cast<uint64>(1))) {
+ return false;
+ }
+ if (!WriteEbmlElement(writer, libwebm::kMkvEBMLMaxIDLength,
+ static_cast<uint64>(4))) {
+ return false;
+ }
+ if (!WriteEbmlElement(writer, libwebm::kMkvEBMLMaxSizeLength,
+ static_cast<uint64>(8))) {
+ return false;
+ }
+ if (!WriteEbmlElement(writer, libwebm::kMkvDocType, doc_type))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvDocTypeVersion,
+ static_cast<uint64>(doc_type_version))) {
+ return false;
+ }
+ if (!WriteEbmlElement(writer, libwebm::kMkvDocTypeReadVersion,
+ static_cast<uint64>(2))) {
+ return false;
+ }
+
+ return true;
+}
+
+bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version) {
+ return WriteEbmlHeader(writer, doc_type_version, kDocTypeWebm);
+}
+
+bool WriteEbmlHeader(IMkvWriter* writer) {
+ return WriteEbmlHeader(writer, mkvmuxer::Segment::kDefaultDocTypeVersion);
+}
+
+bool ChunkedCopy(mkvparser::IMkvReader* source, mkvmuxer::IMkvWriter* dst,
+ int64_t start, int64_t size) {
+ // TODO(vigneshv): Check if this is a reasonable value.
+ const uint32_t kBufSize = 2048;
+ uint8_t* buf = new uint8_t[kBufSize];
+ int64_t offset = start;
+ while (size > 0) {
+ const int64_t read_len = (size > kBufSize) ? kBufSize : size;
+ if (source->Read(offset, static_cast<long>(read_len), buf))
+ return false;
+ dst->Write(buf, static_cast<uint32_t>(read_len));
+ offset += read_len;
+ size -= read_len;
+ }
+ delete[] buf;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// Frame Class
+
+Frame::Frame()
+ : add_id_(0),
+ additional_(NULL),
+ additional_length_(0),
+ duration_(0),
+ duration_set_(false),
+ frame_(NULL),
+ is_key_(false),
+ length_(0),
+ track_number_(0),
+ timestamp_(0),
+ discard_padding_(0),
+ reference_block_timestamp_(0),
+ reference_block_timestamp_set_(false) {}
+
+Frame::~Frame() {
+ delete[] frame_;
+ delete[] additional_;
+}
+
+bool Frame::CopyFrom(const Frame& frame) {
+ delete[] frame_;
+ frame_ = NULL;
+ length_ = 0;
+ if (frame.length() > 0 && frame.frame() != NULL &&
+ !Init(frame.frame(), frame.length())) {
+ return false;
+ }
+ add_id_ = 0;
+ delete[] additional_;
+ additional_ = NULL;
+ additional_length_ = 0;
+ if (frame.additional_length() > 0 && frame.additional() != NULL &&
+ !AddAdditionalData(frame.additional(), frame.additional_length(),
+ frame.add_id())) {
+ return false;
+ }
+ duration_ = frame.duration();
+ duration_set_ = frame.duration_set();
+ is_key_ = frame.is_key();
+ track_number_ = frame.track_number();
+ timestamp_ = frame.timestamp();
+ discard_padding_ = frame.discard_padding();
+ reference_block_timestamp_ = frame.reference_block_timestamp();
+ reference_block_timestamp_set_ = frame.reference_block_timestamp_set();
+ return true;
+}
+
+bool Frame::Init(const uint8_t* frame, uint64_t length) {
+ uint8_t* const data =
+ new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT
+ if (!data)
+ return false;
+
+ delete[] frame_;
+ frame_ = data;
+ length_ = length;
+
+ memcpy(frame_, frame, static_cast<size_t>(length_));
+ return true;
+}
+
+bool Frame::AddAdditionalData(const uint8_t* additional, uint64_t length,
+ uint64_t add_id) {
+ uint8_t* const data =
+ new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT
+ if (!data)
+ return false;
+
+ delete[] additional_;
+ additional_ = data;
+ additional_length_ = length;
+ add_id_ = add_id;
+
+ memcpy(additional_, additional, static_cast<size_t>(additional_length_));
+ return true;
+}
+
+bool Frame::IsValid() const {
+ if (length_ == 0 || !frame_) {
+ return false;
+ }
+ if ((additional_length_ != 0 && !additional_) ||
+ (additional_ != NULL && additional_length_ == 0)) {
+ return false;
+ }
+ if (track_number_ == 0 || track_number_ > kMaxTrackNumber) {
+ return false;
+ }
+ if (!CanBeSimpleBlock() && !is_key_ && !reference_block_timestamp_set_) {
+ return false;
+ }
+ return true;
+}
+
+bool Frame::CanBeSimpleBlock() const {
+ return additional_ == NULL && discard_padding_ == 0 && duration_ == 0;
+}
+
+void Frame::set_duration(uint64_t duration) {
+ duration_ = duration;
+ duration_set_ = true;
+}
+
+void Frame::set_reference_block_timestamp(int64_t reference_block_timestamp) {
+ reference_block_timestamp_ = reference_block_timestamp;
+ reference_block_timestamp_set_ = true;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// CuePoint Class
+
+CuePoint::CuePoint()
+ : time_(0),
+ track_(0),
+ cluster_pos_(0),
+ block_number_(1),
+ output_block_number_(true) {}
+
+CuePoint::~CuePoint() {}
+
+bool CuePoint::Write(IMkvWriter* writer) const {
+ if (!writer || track_ < 1 || cluster_pos_ < 1)
+ return false;
+
+ uint64_t size = EbmlElementSize(libwebm::kMkvCueClusterPosition,
+ static_cast<uint64>(cluster_pos_));
+ size += EbmlElementSize(libwebm::kMkvCueTrack, static_cast<uint64>(track_));
+ if (output_block_number_ && block_number_ > 1)
+ size += EbmlElementSize(libwebm::kMkvCueBlockNumber,
+ static_cast<uint64>(block_number_));
+ const uint64_t track_pos_size =
+ EbmlMasterElementSize(libwebm::kMkvCueTrackPositions, size) + size;
+ const uint64_t payload_size =
+ EbmlElementSize(libwebm::kMkvCueTime, static_cast<uint64>(time_)) +
+ track_pos_size;
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvCuePoint, payload_size))
+ return false;
+
+ const int64_t payload_position = writer->Position();
+ if (payload_position < 0)
+ return false;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvCueTime,
+ static_cast<uint64>(time_))) {
+ return false;
+ }
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvCueTrackPositions, size))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvCueTrack,
+ static_cast<uint64>(track_))) {
+ return false;
+ }
+ if (!WriteEbmlElement(writer, libwebm::kMkvCueClusterPosition,
+ static_cast<uint64>(cluster_pos_))) {
+ return false;
+ }
+ if (output_block_number_ && block_number_ > 1) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvCueBlockNumber,
+ static_cast<uint64>(block_number_))) {
+ return false;
+ }
+ }
+
+ const int64_t stop_position = writer->Position();
+ if (stop_position < 0)
+ return false;
+
+ if (stop_position - payload_position != static_cast<int64_t>(payload_size))
+ return false;
+
+ return true;
+}
+
+uint64_t CuePoint::PayloadSize() const {
+ uint64_t size = EbmlElementSize(libwebm::kMkvCueClusterPosition,
+ static_cast<uint64>(cluster_pos_));
+ size += EbmlElementSize(libwebm::kMkvCueTrack, static_cast<uint64>(track_));
+ if (output_block_number_ && block_number_ > 1)
+ size += EbmlElementSize(libwebm::kMkvCueBlockNumber,
+ static_cast<uint64>(block_number_));
+ const uint64_t track_pos_size =
+ EbmlMasterElementSize(libwebm::kMkvCueTrackPositions, size) + size;
+ const uint64_t payload_size =
+ EbmlElementSize(libwebm::kMkvCueTime, static_cast<uint64>(time_)) +
+ track_pos_size;
+
+ return payload_size;
+}
+
+uint64_t CuePoint::Size() const {
+ const uint64_t payload_size = PayloadSize();
+ return EbmlMasterElementSize(libwebm::kMkvCuePoint, payload_size) +
+ payload_size;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// Cues Class
+
+Cues::Cues()
+ : cue_entries_capacity_(0),
+ cue_entries_size_(0),
+ cue_entries_(NULL),
+ output_block_number_(true) {}
+
+Cues::~Cues() {
+ if (cue_entries_) {
+ for (int32_t i = 0; i < cue_entries_size_; ++i) {
+ CuePoint* const cue = cue_entries_[i];
+ delete cue;
+ }
+ delete[] cue_entries_;
+ }
+}
+
+bool Cues::AddCue(CuePoint* cue) {
+ if (!cue)
+ return false;
+
+ if ((cue_entries_size_ + 1) > cue_entries_capacity_) {
+ // Add more CuePoints.
+ const int32_t new_capacity =
+ (!cue_entries_capacity_) ? 2 : cue_entries_capacity_ * 2;
+
+ if (new_capacity < 1)
+ return false;
+
+ CuePoint** const cues =
+ new (std::nothrow) CuePoint*[new_capacity]; // NOLINT
+ if (!cues)
+ return false;
+
+ for (int32_t i = 0; i < cue_entries_size_; ++i) {
+ cues[i] = cue_entries_[i];
+ }
+
+ delete[] cue_entries_;
+
+ cue_entries_ = cues;
+ cue_entries_capacity_ = new_capacity;
+ }
+
+ cue->set_output_block_number(output_block_number_);
+ cue_entries_[cue_entries_size_++] = cue;
+ return true;
+}
+
+CuePoint* Cues::GetCueByIndex(int32_t index) const {
+ if (cue_entries_ == NULL)
+ return NULL;
+
+ if (index >= cue_entries_size_)
+ return NULL;
+
+ return cue_entries_[index];
+}
+
+uint64_t Cues::Size() {
+ uint64_t size = 0;
+ for (int32_t i = 0; i < cue_entries_size_; ++i)
+ size += GetCueByIndex(i)->Size();
+ size += EbmlMasterElementSize(libwebm::kMkvCues, size);
+ return size;
+}
+
+bool Cues::Write(IMkvWriter* writer) const {
+ if (!writer)
+ return false;
+
+ uint64_t size = 0;
+ for (int32_t i = 0; i < cue_entries_size_; ++i) {
+ const CuePoint* const cue = GetCueByIndex(i);
+
+ if (!cue)
+ return false;
+
+ size += cue->Size();
+ }
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvCues, size))
+ return false;
+
+ const int64_t payload_position = writer->Position();
+ if (payload_position < 0)
+ return false;
+
+ for (int32_t i = 0; i < cue_entries_size_; ++i) {
+ const CuePoint* const cue = GetCueByIndex(i);
+
+ if (!cue->Write(writer))
+ return false;
+ }
+
+ const int64_t stop_position = writer->Position();
+ if (stop_position < 0)
+ return false;
+
+ if (stop_position - payload_position != static_cast<int64_t>(size))
+ return false;
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// ContentEncAESSettings Class
+
+ContentEncAESSettings::ContentEncAESSettings() : cipher_mode_(kCTR) {}
+
+uint64_t ContentEncAESSettings::Size() const {
+ const uint64_t payload = PayloadSize();
+ const uint64_t size =
+ EbmlMasterElementSize(libwebm::kMkvContentEncAESSettings, payload) +
+ payload;
+ return size;
+}
+
+bool ContentEncAESSettings::Write(IMkvWriter* writer) const {
+ const uint64_t payload = PayloadSize();
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncAESSettings,
+ payload))
+ return false;
+
+ const int64_t payload_position = writer->Position();
+ if (payload_position < 0)
+ return false;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvAESSettingsCipherMode,
+ static_cast<uint64>(cipher_mode_))) {
+ return false;
+ }
+
+ const int64_t stop_position = writer->Position();
+ if (stop_position < 0 ||
+ stop_position - payload_position != static_cast<int64_t>(payload))
+ return false;
+
+ return true;
+}
+
+uint64_t ContentEncAESSettings::PayloadSize() const {
+ uint64_t size = EbmlElementSize(libwebm::kMkvAESSettingsCipherMode,
+ static_cast<uint64>(cipher_mode_));
+ return size;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// ContentEncoding Class
+
+ContentEncoding::ContentEncoding()
+ : enc_algo_(5),
+ enc_key_id_(NULL),
+ encoding_order_(0),
+ encoding_scope_(1),
+ encoding_type_(1),
+ enc_key_id_length_(0) {}
+
+ContentEncoding::~ContentEncoding() { delete[] enc_key_id_; }
+
+bool ContentEncoding::SetEncryptionID(const uint8_t* id, uint64_t length) {
+ if (!id || length < 1)
+ return false;
+
+ delete[] enc_key_id_;
+
+ enc_key_id_ =
+ new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT
+ if (!enc_key_id_)
+ return false;
+
+ memcpy(enc_key_id_, id, static_cast<size_t>(length));
+ enc_key_id_length_ = length;
+
+ return true;
+}
+
+uint64_t ContentEncoding::Size() const {
+ const uint64_t encryption_size = EncryptionSize();
+ const uint64_t encoding_size = EncodingSize(0, encryption_size);
+ const uint64_t encodings_size =
+ EbmlMasterElementSize(libwebm::kMkvContentEncoding, encoding_size) +
+ encoding_size;
+
+ return encodings_size;
+}
+
+bool ContentEncoding::Write(IMkvWriter* writer) const {
+ const uint64_t encryption_size = EncryptionSize();
+ const uint64_t encoding_size = EncodingSize(0, encryption_size);
+ const uint64_t size =
+ EbmlMasterElementSize(libwebm::kMkvContentEncoding, encoding_size) +
+ encoding_size;
+
+ const int64_t payload_position = writer->Position();
+ if (payload_position < 0)
+ return false;
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncoding,
+ encoding_size))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingOrder,
+ static_cast<uint64>(encoding_order_)))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingScope,
+ static_cast<uint64>(encoding_scope_)))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvContentEncodingType,
+ static_cast<uint64>(encoding_type_)))
+ return false;
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncryption,
+ encryption_size))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvContentEncAlgo,
+ static_cast<uint64>(enc_algo_))) {
+ return false;
+ }
+ if (!WriteEbmlElement(writer, libwebm::kMkvContentEncKeyID, enc_key_id_,
+ enc_key_id_length_))
+ return false;
+
+ if (!enc_aes_settings_.Write(writer))
+ return false;
+
+ const int64_t stop_position = writer->Position();
+ if (stop_position < 0 ||
+ stop_position - payload_position != static_cast<int64_t>(size))
+ return false;
+
+ return true;
+}
+
+uint64_t ContentEncoding::EncodingSize(uint64_t compresion_size,
+ uint64_t encryption_size) const {
+ // TODO(fgalligan): Add support for compression settings.
+ if (compresion_size != 0)
+ return 0;
+
+ uint64_t encoding_size = 0;
+
+ if (encryption_size > 0) {
+ encoding_size +=
+ EbmlMasterElementSize(libwebm::kMkvContentEncryption, encryption_size) +
+ encryption_size;
+ }
+ encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingType,
+ static_cast<uint64>(encoding_type_));
+ encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingScope,
+ static_cast<uint64>(encoding_scope_));
+ encoding_size += EbmlElementSize(libwebm::kMkvContentEncodingOrder,
+ static_cast<uint64>(encoding_order_));
+
+ return encoding_size;
+}
+
+uint64_t ContentEncoding::EncryptionSize() const {
+ const uint64_t aes_size = enc_aes_settings_.Size();
+
+ uint64_t encryption_size = EbmlElementSize(libwebm::kMkvContentEncKeyID,
+ enc_key_id_, enc_key_id_length_);
+ encryption_size += EbmlElementSize(libwebm::kMkvContentEncAlgo,
+ static_cast<uint64>(enc_algo_));
+
+ return encryption_size + aes_size;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// Track Class
+
+Track::Track(unsigned int* seed)
+ : codec_id_(NULL),
+ codec_private_(NULL),
+ language_(NULL),
+ max_block_additional_id_(0),
+ name_(NULL),
+ number_(0),
+ type_(0),
+ uid_(MakeUID(seed)),
+ codec_delay_(0),
+ seek_pre_roll_(0),
+ default_duration_(0),
+ codec_private_length_(0),
+ content_encoding_entries_(NULL),
+ content_encoding_entries_size_(0) {}
+
+Track::~Track() {
+ delete[] codec_id_;
+ delete[] codec_private_;
+ delete[] language_;
+ delete[] name_;
+
+ if (content_encoding_entries_) {
+ for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
+ ContentEncoding* const encoding = content_encoding_entries_[i];
+ delete encoding;
+ }
+ delete[] content_encoding_entries_;
+ }
+}
+
+bool Track::AddContentEncoding() {
+ const uint32_t count = content_encoding_entries_size_ + 1;
+
+ ContentEncoding** const content_encoding_entries =
+ new (std::nothrow) ContentEncoding*[count]; // NOLINT
+ if (!content_encoding_entries)
+ return false;
+
+ ContentEncoding* const content_encoding =
+ new (std::nothrow) ContentEncoding(); // NOLINT
+ if (!content_encoding) {
+ delete[] content_encoding_entries;
+ return false;
+ }
+
+ for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
+ content_encoding_entries[i] = content_encoding_entries_[i];
+ }
+
+ delete[] content_encoding_entries_;
+
+ content_encoding_entries_ = content_encoding_entries;
+ content_encoding_entries_[content_encoding_entries_size_] = content_encoding;
+ content_encoding_entries_size_ = count;
+ return true;
+}
+
+ContentEncoding* Track::GetContentEncodingByIndex(uint32_t index) const {
+ if (content_encoding_entries_ == NULL)
+ return NULL;
+
+ if (index >= content_encoding_entries_size_)
+ return NULL;
+
+ return content_encoding_entries_[index];
+}
+
+uint64_t Track::PayloadSize() const {
+ uint64_t size =
+ EbmlElementSize(libwebm::kMkvTrackNumber, static_cast<uint64>(number_));
+ size += EbmlElementSize(libwebm::kMkvTrackUID, static_cast<uint64>(uid_));
+ size += EbmlElementSize(libwebm::kMkvTrackType, static_cast<uint64>(type_));
+ if (codec_id_)
+ size += EbmlElementSize(libwebm::kMkvCodecID, codec_id_);
+ if (codec_private_)
+ size += EbmlElementSize(libwebm::kMkvCodecPrivate, codec_private_,
+ codec_private_length_);
+ if (language_)
+ size += EbmlElementSize(libwebm::kMkvLanguage, language_);
+ if (name_)
+ size += EbmlElementSize(libwebm::kMkvName, name_);
+ if (max_block_additional_id_) {
+ size += EbmlElementSize(libwebm::kMkvMaxBlockAdditionID,
+ static_cast<uint64>(max_block_additional_id_));
+ }
+ if (codec_delay_) {
+ size += EbmlElementSize(libwebm::kMkvCodecDelay,
+ static_cast<uint64>(codec_delay_));
+ }
+ if (seek_pre_roll_) {
+ size += EbmlElementSize(libwebm::kMkvSeekPreRoll,
+ static_cast<uint64>(seek_pre_roll_));
+ }
+ if (default_duration_) {
+ size += EbmlElementSize(libwebm::kMkvDefaultDuration,
+ static_cast<uint64>(default_duration_));
+ }
+
+ if (content_encoding_entries_size_ > 0) {
+ uint64_t content_encodings_size = 0;
+ for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
+ ContentEncoding* const encoding = content_encoding_entries_[i];
+ content_encodings_size += encoding->Size();
+ }
+
+ size += EbmlMasterElementSize(libwebm::kMkvContentEncodings,
+ content_encodings_size) +
+ content_encodings_size;
+ }
+
+ return size;
+}
+
+uint64_t Track::Size() const {
+ uint64_t size = PayloadSize();
+ size += EbmlMasterElementSize(libwebm::kMkvTrackEntry, size);
+ return size;
+}
+
+bool Track::Write(IMkvWriter* writer) const {
+ if (!writer)
+ return false;
+
+ // mandatory elements without a default value.
+ if (!type_ || !codec_id_)
+ return false;
+
+ // AV1 tracks require a CodecPrivate. See
+ // https://github.com/Matroska-Org/matroska-specification/blob/av1-mappin/codec/av1.md
+ // TODO(tomfinegan): Update the above link to the AV1 Matroska mappings to
+ // point to a stable version once it is finalized, or our own WebM mappings
+ // page on webmproject.org should we decide to release them.
+ if (!strcmp(codec_id_, Tracks::kAv1CodecId) && !codec_private_)
+ return false;
+
+ // |size| may be bigger than what is written out in this function because
+ // derived classes may write out more data in the Track element.
+ const uint64_t payload_size = PayloadSize();
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvTrackEntry, payload_size))
+ return false;
+
+ uint64_t size =
+ EbmlElementSize(libwebm::kMkvTrackNumber, static_cast<uint64>(number_));
+ size += EbmlElementSize(libwebm::kMkvTrackUID, static_cast<uint64>(uid_));
+ size += EbmlElementSize(libwebm::kMkvTrackType, static_cast<uint64>(type_));
+ if (codec_id_)
+ size += EbmlElementSize(libwebm::kMkvCodecID, codec_id_);
+ if (codec_private_)
+ size += EbmlElementSize(libwebm::kMkvCodecPrivate, codec_private_,
+ static_cast<uint64>(codec_private_length_));
+ if (language_)
+ size += EbmlElementSize(libwebm::kMkvLanguage, language_);
+ if (name_)
+ size += EbmlElementSize(libwebm::kMkvName, name_);
+ if (max_block_additional_id_)
+ size += EbmlElementSize(libwebm::kMkvMaxBlockAdditionID,
+ static_cast<uint64>(max_block_additional_id_));
+ if (codec_delay_)
+ size += EbmlElementSize(libwebm::kMkvCodecDelay,
+ static_cast<uint64>(codec_delay_));
+ if (seek_pre_roll_)
+ size += EbmlElementSize(libwebm::kMkvSeekPreRoll,
+ static_cast<uint64>(seek_pre_roll_));
+ if (default_duration_)
+ size += EbmlElementSize(libwebm::kMkvDefaultDuration,
+ static_cast<uint64>(default_duration_));
+
+ const int64_t payload_position = writer->Position();
+ if (payload_position < 0)
+ return false;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvTrackNumber,
+ static_cast<uint64>(number_)))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvTrackUID,
+ static_cast<uint64>(uid_)))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvTrackType,
+ static_cast<uint64>(type_)))
+ return false;
+ if (max_block_additional_id_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvMaxBlockAdditionID,
+ static_cast<uint64>(max_block_additional_id_))) {
+ return false;
+ }
+ }
+ if (codec_delay_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvCodecDelay,
+ static_cast<uint64>(codec_delay_)))
+ return false;
+ }
+ if (seek_pre_roll_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvSeekPreRoll,
+ static_cast<uint64>(seek_pre_roll_)))
+ return false;
+ }
+ if (default_duration_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvDefaultDuration,
+ static_cast<uint64>(default_duration_)))
+ return false;
+ }
+ if (codec_id_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvCodecID, codec_id_))
+ return false;
+ }
+ if (codec_private_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvCodecPrivate, codec_private_,
+ static_cast<uint64>(codec_private_length_)))
+ return false;
+ }
+ if (language_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvLanguage, language_))
+ return false;
+ }
+ if (name_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvName, name_))
+ return false;
+ }
+
+ int64_t stop_position = writer->Position();
+ if (stop_position < 0 ||
+ stop_position - payload_position != static_cast<int64_t>(size))
+ return false;
+
+ if (content_encoding_entries_size_ > 0) {
+ uint64_t content_encodings_size = 0;
+ for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
+ ContentEncoding* const encoding = content_encoding_entries_[i];
+ content_encodings_size += encoding->Size();
+ }
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvContentEncodings,
+ content_encodings_size))
+ return false;
+
+ for (uint32_t i = 0; i < content_encoding_entries_size_; ++i) {
+ ContentEncoding* const encoding = content_encoding_entries_[i];
+ if (!encoding->Write(writer))
+ return false;
+ }
+ }
+
+ stop_position = writer->Position();
+ if (stop_position < 0)
+ return false;
+ return true;
+}
+
+bool Track::SetCodecPrivate(const uint8_t* codec_private, uint64_t length) {
+ if (!codec_private || length < 1)
+ return false;
+
+ delete[] codec_private_;
+
+ codec_private_ =
+ new (std::nothrow) uint8_t[static_cast<size_t>(length)]; // NOLINT
+ if (!codec_private_)
+ return false;
+
+ memcpy(codec_private_, codec_private, static_cast<size_t>(length));
+ codec_private_length_ = length;
+
+ return true;
+}
+
+void Track::set_codec_id(const char* codec_id) {
+ if (codec_id) {
+ delete[] codec_id_;
+
+ const size_t length = strlen(codec_id) + 1;
+ codec_id_ = new (std::nothrow) char[length]; // NOLINT
+ if (codec_id_) {
+#ifdef _MSC_VER
+ strcpy_s(codec_id_, length, codec_id);
+#else
+ strcpy(codec_id_, codec_id);
+#endif
+ }
+ }
+}
+
+// TODO(fgalligan): Vet the language parameter.
+void Track::set_language(const char* language) {
+ if (language) {
+ delete[] language_;
+
+ const size_t length = strlen(language) + 1;
+ language_ = new (std::nothrow) char[length]; // NOLINT
+ if (language_) {
+#ifdef _MSC_VER
+ strcpy_s(language_, length, language);
+#else
+ strcpy(language_, language);
+#endif
+ }
+ }
+}
+
+void Track::set_name(const char* name) {
+ if (name) {
+ delete[] name_;
+
+ const size_t length = strlen(name) + 1;
+ name_ = new (std::nothrow) char[length]; // NOLINT
+ if (name_) {
+#ifdef _MSC_VER
+ strcpy_s(name_, length, name);
+#else
+ strcpy(name_, name);
+#endif
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////
+//
+// Colour and its child elements
+
+uint64_t PrimaryChromaticity::PrimaryChromaticitySize(
+ libwebm::MkvId x_id, libwebm::MkvId y_id) const {
+ return EbmlElementSize(x_id, x_) + EbmlElementSize(y_id, y_);
+}
+
+bool PrimaryChromaticity::Write(IMkvWriter* writer, libwebm::MkvId x_id,
+ libwebm::MkvId y_id) const {
+ if (!Valid()) {
+ return false;
+ }
+ return WriteEbmlElement(writer, x_id, x_) &&
+ WriteEbmlElement(writer, y_id, y_);
+}
+
+bool PrimaryChromaticity::Valid() const {
+ return (x_ >= kChromaticityMin && x_ <= kChromaticityMax &&
+ y_ >= kChromaticityMin && y_ <= kChromaticityMax);
+}
+
+uint64_t MasteringMetadata::MasteringMetadataSize() const {
+ uint64_t size = PayloadSize();
+
+ if (size > 0)
+ size += EbmlMasterElementSize(libwebm::kMkvMasteringMetadata, size);
+
+ return size;
+}
+
+bool MasteringMetadata::Valid() const {
+ if (luminance_min_ != kValueNotPresent) {
+ if (luminance_min_ < kMinLuminance || luminance_min_ > kMinLuminanceMax ||
+ luminance_min_ > luminance_max_) {
+ return false;
+ }
+ }
+ if (luminance_max_ != kValueNotPresent) {
+ if (luminance_max_ < kMinLuminance || luminance_max_ > kMaxLuminanceMax ||
+ luminance_max_ < luminance_min_) {
+ return false;
+ }
+ }
+ if (r_ && !r_->Valid())
+ return false;
+ if (g_ && !g_->Valid())
+ return false;
+ if (b_ && !b_->Valid())
+ return false;
+ if (white_point_ && !white_point_->Valid())
+ return false;
+
+ return true;
+}
+
+bool MasteringMetadata::Write(IMkvWriter* writer) const {
+ const uint64_t size = PayloadSize();
+
+ // Don't write an empty element.
+ if (size == 0)
+ return true;
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvMasteringMetadata, size))
+ return false;
+ if (luminance_max_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvLuminanceMax, luminance_max_)) {
+ return false;
+ }
+ if (luminance_min_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvLuminanceMin, luminance_min_)) {
+ return false;
+ }
+ if (r_ && !r_->Write(writer, libwebm::kMkvPrimaryRChromaticityX,
+ libwebm::kMkvPrimaryRChromaticityY)) {
+ return false;
+ }
+ if (g_ && !g_->Write(writer, libwebm::kMkvPrimaryGChromaticityX,
+ libwebm::kMkvPrimaryGChromaticityY)) {
+ return false;
+ }
+ if (b_ && !b_->Write(writer, libwebm::kMkvPrimaryBChromaticityX,
+ libwebm::kMkvPrimaryBChromaticityY)) {
+ return false;
+ }
+ if (white_point_ &&
+ !white_point_->Write(writer, libwebm::kMkvWhitePointChromaticityX,
+ libwebm::kMkvWhitePointChromaticityY)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool MasteringMetadata::SetChromaticity(
+ const PrimaryChromaticity* r, const PrimaryChromaticity* g,
+ const PrimaryChromaticity* b, const PrimaryChromaticity* white_point) {
+ PrimaryChromaticityPtr r_ptr(nullptr);
+ if (r) {
+ if (!CopyChromaticity(r, &r_ptr))
+ return false;
+ }
+ PrimaryChromaticityPtr g_ptr(nullptr);
+ if (g) {
+ if (!CopyChromaticity(g, &g_ptr))
+ return false;
+ }
+ PrimaryChromaticityPtr b_ptr(nullptr);
+ if (b) {
+ if (!CopyChromaticity(b, &b_ptr))
+ return false;
+ }
+ PrimaryChromaticityPtr wp_ptr(nullptr);
+ if (white_point) {
+ if (!CopyChromaticity(white_point, &wp_ptr))
+ return false;
+ }
+
+ r_ = r_ptr.release();
+ g_ = g_ptr.release();
+ b_ = b_ptr.release();
+ white_point_ = wp_ptr.release();
+ return true;
+}
+
+uint64_t MasteringMetadata::PayloadSize() const {
+ uint64_t size = 0;
+
+ if (luminance_max_ != kValueNotPresent)
+ size += EbmlElementSize(libwebm::kMkvLuminanceMax, luminance_max_);
+ if (luminance_min_ != kValueNotPresent)
+ size += EbmlElementSize(libwebm::kMkvLuminanceMin, luminance_min_);
+
+ if (r_) {
+ size += r_->PrimaryChromaticitySize(libwebm::kMkvPrimaryRChromaticityX,
+ libwebm::kMkvPrimaryRChromaticityY);
+ }
+ if (g_) {
+ size += g_->PrimaryChromaticitySize(libwebm::kMkvPrimaryGChromaticityX,
+ libwebm::kMkvPrimaryGChromaticityY);
+ }
+ if (b_) {
+ size += b_->PrimaryChromaticitySize(libwebm::kMkvPrimaryBChromaticityX,
+ libwebm::kMkvPrimaryBChromaticityY);
+ }
+ if (white_point_) {
+ size += white_point_->PrimaryChromaticitySize(
+ libwebm::kMkvWhitePointChromaticityX,
+ libwebm::kMkvWhitePointChromaticityY);
+ }
+
+ return size;
+}
+
+uint64_t Colour::ColourSize() const {
+ uint64_t size = PayloadSize();
+
+ if (size > 0)
+ size += EbmlMasterElementSize(libwebm::kMkvColour, size);
+
+ return size;
+}
+
+bool Colour::Valid() const {
+ if (mastering_metadata_ && !mastering_metadata_->Valid())
+ return false;
+ if (matrix_coefficients_ != kValueNotPresent &&
+ !IsMatrixCoefficientsValueValid(matrix_coefficients_)) {
+ return false;
+ }
+ if (chroma_siting_horz_ != kValueNotPresent &&
+ !IsChromaSitingHorzValueValid(chroma_siting_horz_)) {
+ return false;
+ }
+ if (chroma_siting_vert_ != kValueNotPresent &&
+ !IsChromaSitingVertValueValid(chroma_siting_vert_)) {
+ return false;
+ }
+ if (range_ != kValueNotPresent && !IsColourRangeValueValid(range_))
+ return false;
+ if (transfer_characteristics_ != kValueNotPresent &&
+ !IsTransferCharacteristicsValueValid(transfer_characteristics_)) {
+ return false;
+ }
+ if (primaries_ != kValueNotPresent && !IsPrimariesValueValid(primaries_))
+ return false;
+
+ return true;
+}
+
+bool Colour::Write(IMkvWriter* writer) const {
+ const uint64_t size = PayloadSize();
+
+ // Don't write an empty element.
+ if (size == 0)
+ return true;
+
+ // Don't write an invalid element.
+ if (!Valid())
+ return false;
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvColour, size))
+ return false;
+
+ if (matrix_coefficients_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvMatrixCoefficients,
+ static_cast<uint64>(matrix_coefficients_))) {
+ return false;
+ }
+ if (bits_per_channel_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvBitsPerChannel,
+ static_cast<uint64>(bits_per_channel_))) {
+ return false;
+ }
+ if (chroma_subsampling_horz_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvChromaSubsamplingHorz,
+ static_cast<uint64>(chroma_subsampling_horz_))) {
+ return false;
+ }
+ if (chroma_subsampling_vert_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvChromaSubsamplingVert,
+ static_cast<uint64>(chroma_subsampling_vert_))) {
+ return false;
+ }
+
+ if (cb_subsampling_horz_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvCbSubsamplingHorz,
+ static_cast<uint64>(cb_subsampling_horz_))) {
+ return false;
+ }
+ if (cb_subsampling_vert_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvCbSubsamplingVert,
+ static_cast<uint64>(cb_subsampling_vert_))) {
+ return false;
+ }
+ if (chroma_siting_horz_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvChromaSitingHorz,
+ static_cast<uint64>(chroma_siting_horz_))) {
+ return false;
+ }
+ if (chroma_siting_vert_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvChromaSitingVert,
+ static_cast<uint64>(chroma_siting_vert_))) {
+ return false;
+ }
+ if (range_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvRange,
+ static_cast<uint64>(range_))) {
+ return false;
+ }
+ if (transfer_characteristics_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvTransferCharacteristics,
+ static_cast<uint64>(transfer_characteristics_))) {
+ return false;
+ }
+ if (primaries_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvPrimaries,
+ static_cast<uint64>(primaries_))) {
+ return false;
+ }
+ if (max_cll_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvMaxCLL,
+ static_cast<uint64>(max_cll_))) {
+ return false;
+ }
+ if (max_fall_ != kValueNotPresent &&
+ !WriteEbmlElement(writer, libwebm::kMkvMaxFALL,
+ static_cast<uint64>(max_fall_))) {
+ return false;
+ }
+
+ if (mastering_metadata_ && !mastering_metadata_->Write(writer))
+ return false;
+
+ return true;
+}
+
+bool Colour::SetMasteringMetadata(const MasteringMetadata& mastering_metadata) {
+ std::unique_ptr<MasteringMetadata> mm_ptr(new MasteringMetadata());
+ if (!mm_ptr.get())
+ return false;
+
+ mm_ptr->set_luminance_max(mastering_metadata.luminance_max());
+ mm_ptr->set_luminance_min(mastering_metadata.luminance_min());
+
+ if (!mm_ptr->SetChromaticity(mastering_metadata.r(), mastering_metadata.g(),
+ mastering_metadata.b(),
+ mastering_metadata.white_point())) {
+ return false;
+ }
+
+ delete mastering_metadata_;
+ mastering_metadata_ = mm_ptr.release();
+ return true;
+}
+
+uint64_t Colour::PayloadSize() const {
+ uint64_t size = 0;
+
+ if (matrix_coefficients_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvMatrixCoefficients,
+ static_cast<uint64>(matrix_coefficients_));
+ }
+ if (bits_per_channel_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvBitsPerChannel,
+ static_cast<uint64>(bits_per_channel_));
+ }
+ if (chroma_subsampling_horz_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvChromaSubsamplingHorz,
+ static_cast<uint64>(chroma_subsampling_horz_));
+ }
+ if (chroma_subsampling_vert_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvChromaSubsamplingVert,
+ static_cast<uint64>(chroma_subsampling_vert_));
+ }
+ if (cb_subsampling_horz_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvCbSubsamplingHorz,
+ static_cast<uint64>(cb_subsampling_horz_));
+ }
+ if (cb_subsampling_vert_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvCbSubsamplingVert,
+ static_cast<uint64>(cb_subsampling_vert_));
+ }
+ if (chroma_siting_horz_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvChromaSitingHorz,
+ static_cast<uint64>(chroma_siting_horz_));
+ }
+ if (chroma_siting_vert_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvChromaSitingVert,
+ static_cast<uint64>(chroma_siting_vert_));
+ }
+ if (range_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvRange, static_cast<uint64>(range_));
+ }
+ if (transfer_characteristics_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvTransferCharacteristics,
+ static_cast<uint64>(transfer_characteristics_));
+ }
+ if (primaries_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvPrimaries,
+ static_cast<uint64>(primaries_));
+ }
+ if (max_cll_ != kValueNotPresent) {
+ size += EbmlElementSize(libwebm::kMkvMaxCLL, static_cast<uint64>(max_cll_));
+ }
+ if (max_fall_ != kValueNotPresent) {
+ size +=
+ EbmlElementSize(libwebm::kMkvMaxFALL, static_cast<uint64>(max_fall_));
+ }
+
+ if (mastering_metadata_)
+ size += mastering_metadata_->MasteringMetadataSize();
+
+ return size;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// Projection element
+
+uint64_t Projection::ProjectionSize() const {
+ uint64_t size = PayloadSize();
+
+ if (size > 0)
+ size += EbmlMasterElementSize(libwebm::kMkvProjection, size);
+
+ return size;
+}
+
+bool Projection::Write(IMkvWriter* writer) const {
+ const uint64_t size = PayloadSize();
+
+ // Don't write an empty element.
+ if (size == 0)
+ return true;
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvProjection, size))
+ return false;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvProjectionType,
+ static_cast<uint64>(type_))) {
+ return false;
+ }
+
+ if (private_data_length_ > 0 && private_data_ != NULL &&
+ !WriteEbmlElement(writer, libwebm::kMkvProjectionPrivate, private_data_,
+ private_data_length_)) {
+ return false;
+ }
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPoseYaw, pose_yaw_))
+ return false;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPosePitch,
+ pose_pitch_)) {
+ return false;
+ }
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvProjectionPoseRoll, pose_roll_)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool Projection::SetProjectionPrivate(const uint8_t* data,
+ uint64_t data_length) {
+ if (data == NULL || data_length == 0) {
+ return false;
+ }
+
+ if (data_length != static_cast<size_t>(data_length)) {
+ return false;
+ }
+
+ uint8_t* new_private_data =
+ new (std::nothrow) uint8_t[static_cast<size_t>(data_length)];
+ if (new_private_data == NULL) {
+ return false;
+ }
+
+ delete[] private_data_;
+ private_data_ = new_private_data;
+ private_data_length_ = data_length;
+ memcpy(private_data_, data, static_cast<size_t>(data_length));
+
+ return true;
+}
+
+uint64_t Projection::PayloadSize() const {
+ uint64_t size =
+ EbmlElementSize(libwebm::kMkvProjection, static_cast<uint64>(type_));
+
+ if (private_data_length_ > 0 && private_data_ != NULL) {
+ size += EbmlElementSize(libwebm::kMkvProjectionPrivate, private_data_,
+ private_data_length_);
+ }
+
+ size += EbmlElementSize(libwebm::kMkvProjectionPoseYaw, pose_yaw_);
+ size += EbmlElementSize(libwebm::kMkvProjectionPosePitch, pose_pitch_);
+ size += EbmlElementSize(libwebm::kMkvProjectionPoseRoll, pose_roll_);
+
+ return size;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// VideoTrack Class
+
+VideoTrack::VideoTrack(unsigned int* seed)
+ : Track(seed),
+ display_height_(0),
+ display_width_(0),
+ pixel_height_(0),
+ pixel_width_(0),
+ crop_left_(0),
+ crop_right_(0),
+ crop_top_(0),
+ crop_bottom_(0),
+ frame_rate_(0.0),
+ height_(0),
+ stereo_mode_(0),
+ alpha_mode_(0),
+ width_(0),
+ colour_space_(NULL),
+ colour_(NULL),
+ projection_(NULL) {}
+
+VideoTrack::~VideoTrack() {
+ delete colour_;
+ delete projection_;
+}
+
+bool VideoTrack::SetStereoMode(uint64_t stereo_mode) {
+ if (stereo_mode != kMono && stereo_mode != kSideBySideLeftIsFirst &&
+ stereo_mode != kTopBottomRightIsFirst &&
+ stereo_mode != kTopBottomLeftIsFirst &&
+ stereo_mode != kSideBySideRightIsFirst)
+ return false;
+
+ stereo_mode_ = stereo_mode;
+ return true;
+}
+
+bool VideoTrack::SetAlphaMode(uint64_t alpha_mode) {
+ if (alpha_mode != kNoAlpha && alpha_mode != kAlpha)
+ return false;
+
+ alpha_mode_ = alpha_mode;
+ return true;
+}
+
+uint64_t VideoTrack::PayloadSize() const {
+ const uint64_t parent_size = Track::PayloadSize();
+
+ uint64_t size = VideoPayloadSize();
+ size += EbmlMasterElementSize(libwebm::kMkvVideo, size);
+
+ return parent_size + size;
+}
+
+bool VideoTrack::Write(IMkvWriter* writer) const {
+ if (!Track::Write(writer))
+ return false;
+
+ const uint64_t size = VideoPayloadSize();
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvVideo, size))
+ return false;
+
+ const int64_t payload_position = writer->Position();
+ if (payload_position < 0)
+ return false;
+
+ if (!WriteEbmlElement(
+ writer, libwebm::kMkvPixelWidth,
+ static_cast<uint64>((pixel_width_ > 0) ? pixel_width_ : width_)))
+ return false;
+ if (!WriteEbmlElement(
+ writer, libwebm::kMkvPixelHeight,
+ static_cast<uint64>((pixel_height_ > 0) ? pixel_height_ : height_)))
+ return false;
+ if (display_width_ > 0) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvDisplayWidth,
+ static_cast<uint64>(display_width_)))
+ return false;
+ }
+ if (display_height_ > 0) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvDisplayHeight,
+ static_cast<uint64>(display_height_)))
+ return false;
+ }
+ if (crop_left_ > 0) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropLeft,
+ static_cast<uint64>(crop_left_)))
+ return false;
+ }
+ if (crop_right_ > 0) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropRight,
+ static_cast<uint64>(crop_right_)))
+ return false;
+ }
+ if (crop_top_ > 0) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropTop,
+ static_cast<uint64>(crop_top_)))
+ return false;
+ }
+ if (crop_bottom_ > 0) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvPixelCropBottom,
+ static_cast<uint64>(crop_bottom_)))
+ return false;
+ }
+ if (stereo_mode_ > kMono) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvStereoMode,
+ static_cast<uint64>(stereo_mode_)))
+ return false;
+ }
+ if (alpha_mode_ > kNoAlpha) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvAlphaMode,
+ static_cast<uint64>(alpha_mode_)))
+ return false;
+ }
+ if (colour_space_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvColourSpace, colour_space_))
+ return false;
+ }
+ if (frame_rate_ > 0.0) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvFrameRate,
+ static_cast<float>(frame_rate_))) {
+ return false;
+ }
+ }
+ if (colour_) {
+ if (!colour_->Write(writer))
+ return false;
+ }
+ if (projection_) {
+ if (!projection_->Write(writer))
+ return false;
+ }
+
+ const int64_t stop_position = writer->Position();
+ if (stop_position < 0 ||
+ stop_position - payload_position != static_cast<int64_t>(size)) {
+ return false;
+ }
+
+ return true;
+}
+
+void VideoTrack::set_colour_space(const char* colour_space) {
+ if (colour_space) {
+ delete[] colour_space_;
+
+ const size_t length = strlen(colour_space) + 1;
+ colour_space_ = new (std::nothrow) char[length]; // NOLINT
+ if (colour_space_) {
+#ifdef _MSC_VER
+ strcpy_s(colour_space_, length, colour_space);
+#else
+ strcpy(colour_space_, colour_space);
+#endif
+ }
+ }
+}
+
+bool VideoTrack::SetColour(const Colour& colour) {
+ std::unique_ptr<Colour> colour_ptr(new Colour());
+ if (!colour_ptr.get())
+ return false;
+
+ if (colour.mastering_metadata()) {
+ if (!colour_ptr->SetMasteringMetadata(*colour.mastering_metadata()))
+ return false;
+ }
+
+ colour_ptr->set_matrix_coefficients(colour.matrix_coefficients());
+ colour_ptr->set_bits_per_channel(colour.bits_per_channel());
+ colour_ptr->set_chroma_subsampling_horz(colour.chroma_subsampling_horz());
+ colour_ptr->set_chroma_subsampling_vert(colour.chroma_subsampling_vert());
+ colour_ptr->set_cb_subsampling_horz(colour.cb_subsampling_horz());
+ colour_ptr->set_cb_subsampling_vert(colour.cb_subsampling_vert());
+ colour_ptr->set_chroma_siting_horz(colour.chroma_siting_horz());
+ colour_ptr->set_chroma_siting_vert(colour.chroma_siting_vert());
+ colour_ptr->set_range(colour.range());
+ colour_ptr->set_transfer_characteristics(colour.transfer_characteristics());
+ colour_ptr->set_primaries(colour.primaries());
+ colour_ptr->set_max_cll(colour.max_cll());
+ colour_ptr->set_max_fall(colour.max_fall());
+ delete colour_;
+ colour_ = colour_ptr.release();
+ return true;
+}
+
+bool VideoTrack::SetProjection(const Projection& projection) {
+ std::unique_ptr<Projection> projection_ptr(new Projection());
+ if (!projection_ptr.get())
+ return false;
+
+ if (projection.private_data()) {
+ if (!projection_ptr->SetProjectionPrivate(
+ projection.private_data(), projection.private_data_length())) {
+ return false;
+ }
+ }
+
+ projection_ptr->set_type(projection.type());
+ projection_ptr->set_pose_yaw(projection.pose_yaw());
+ projection_ptr->set_pose_pitch(projection.pose_pitch());
+ projection_ptr->set_pose_roll(projection.pose_roll());
+ delete projection_;
+ projection_ = projection_ptr.release();
+ return true;
+}
+
+uint64_t VideoTrack::VideoPayloadSize() const {
+ uint64_t size = EbmlElementSize(
+ libwebm::kMkvPixelWidth,
+ static_cast<uint64>((pixel_width_ > 0) ? pixel_width_ : width_));
+ size += EbmlElementSize(
+ libwebm::kMkvPixelHeight,
+ static_cast<uint64>((pixel_height_ > 0) ? pixel_height_ : height_));
+ if (display_width_ > 0)
+ size += EbmlElementSize(libwebm::kMkvDisplayWidth,
+ static_cast<uint64>(display_width_));
+ if (display_height_ > 0)
+ size += EbmlElementSize(libwebm::kMkvDisplayHeight,
+ static_cast<uint64>(display_height_));
+ if (crop_left_ > 0)
+ size += EbmlElementSize(libwebm::kMkvPixelCropLeft,
+ static_cast<uint64>(crop_left_));
+ if (crop_right_ > 0)
+ size += EbmlElementSize(libwebm::kMkvPixelCropRight,
+ static_cast<uint64>(crop_right_));
+ if (crop_top_ > 0)
+ size += EbmlElementSize(libwebm::kMkvPixelCropTop,
+ static_cast<uint64>(crop_top_));
+ if (crop_bottom_ > 0)
+ size += EbmlElementSize(libwebm::kMkvPixelCropBottom,
+ static_cast<uint64>(crop_bottom_));
+ if (stereo_mode_ > kMono)
+ size += EbmlElementSize(libwebm::kMkvStereoMode,
+ static_cast<uint64>(stereo_mode_));
+ if (alpha_mode_ > kNoAlpha)
+ size += EbmlElementSize(libwebm::kMkvAlphaMode,
+ static_cast<uint64>(alpha_mode_));
+ if (frame_rate_ > 0.0)
+ size += EbmlElementSize(libwebm::kMkvFrameRate,
+ static_cast<float>(frame_rate_));
+ if (colour_space_)
+ size += EbmlElementSize(libwebm::kMkvColourSpace, colour_space_);
+ if (colour_)
+ size += colour_->ColourSize();
+ if (projection_)
+ size += projection_->ProjectionSize();
+
+ return size;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// AudioTrack Class
+
+AudioTrack::AudioTrack(unsigned int* seed)
+ : Track(seed), bit_depth_(0), channels_(1), sample_rate_(0.0) {}
+
+AudioTrack::~AudioTrack() {}
+
+uint64_t AudioTrack::PayloadSize() const {
+ const uint64_t parent_size = Track::PayloadSize();
+
+ uint64_t size = EbmlElementSize(libwebm::kMkvSamplingFrequency,
+ static_cast<float>(sample_rate_));
+ size +=
+ EbmlElementSize(libwebm::kMkvChannels, static_cast<uint64>(channels_));
+ if (bit_depth_ > 0)
+ size +=
+ EbmlElementSize(libwebm::kMkvBitDepth, static_cast<uint64>(bit_depth_));
+ size += EbmlMasterElementSize(libwebm::kMkvAudio, size);
+
+ return parent_size + size;
+}
+
+bool AudioTrack::Write(IMkvWriter* writer) const {
+ if (!Track::Write(writer))
+ return false;
+
+ // Calculate AudioSettings size.
+ uint64_t size = EbmlElementSize(libwebm::kMkvSamplingFrequency,
+ static_cast<float>(sample_rate_));
+ size +=
+ EbmlElementSize(libwebm::kMkvChannels, static_cast<uint64>(channels_));
+ if (bit_depth_ > 0)
+ size +=
+ EbmlElementSize(libwebm::kMkvBitDepth, static_cast<uint64>(bit_depth_));
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvAudio, size))
+ return false;
+
+ const int64_t payload_position = writer->Position();
+ if (payload_position < 0)
+ return false;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvSamplingFrequency,
+ static_cast<float>(sample_rate_)))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvChannels,
+ static_cast<uint64>(channels_)))
+ return false;
+ if (bit_depth_ > 0)
+ if (!WriteEbmlElement(writer, libwebm::kMkvBitDepth,
+ static_cast<uint64>(bit_depth_)))
+ return false;
+
+ const int64_t stop_position = writer->Position();
+ if (stop_position < 0 ||
+ stop_position - payload_position != static_cast<int64_t>(size))
+ return false;
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// Tracks Class
+
+const char Tracks::kOpusCodecId[] = "A_OPUS";
+const char Tracks::kVorbisCodecId[] = "A_VORBIS";
+const char Tracks::kAv1CodecId[] = "V_AV1";
+const char Tracks::kVp8CodecId[] = "V_VP8";
+const char Tracks::kVp9CodecId[] = "V_VP9";
+const char Tracks::kWebVttCaptionsId[] = "D_WEBVTT/CAPTIONS";
+const char Tracks::kWebVttDescriptionsId[] = "D_WEBVTT/DESCRIPTIONS";
+const char Tracks::kWebVttMetadataId[] = "D_WEBVTT/METADATA";
+const char Tracks::kWebVttSubtitlesId[] = "D_WEBVTT/SUBTITLES";
+
+Tracks::Tracks()
+ : track_entries_(NULL), track_entries_size_(0), wrote_tracks_(false) {}
+
+Tracks::~Tracks() {
+ if (track_entries_) {
+ for (uint32_t i = 0; i < track_entries_size_; ++i) {
+ Track* const track = track_entries_[i];
+ delete track;
+ }
+ delete[] track_entries_;
+ }
+}
+
+bool Tracks::AddTrack(Track* track, int32_t number) {
+ if (number < 0 || wrote_tracks_)
+ return false;
+
+ // This muxer only supports track numbers in the range [1, 126], in
+ // order to be able (to use Matroska integer representation) to
+ // serialize the block header (of which the track number is a part)
+ // for a frame using exactly 4 bytes.
+
+ if (number > 0x7E)
+ return false;
+
+ uint32_t track_num = number;
+
+ if (track_num > 0) {
+ // Check to make sure a track does not already have |track_num|.
+ for (uint32_t i = 0; i < track_entries_size_; ++i) {
+ if (track_entries_[i]->number() == track_num)
+ return false;
+ }
+ }
+
+ const uint32_t count = track_entries_size_ + 1;
+
+ Track** const track_entries = new (std::nothrow) Track*[count]; // NOLINT
+ if (!track_entries)
+ return false;
+
+ for (uint32_t i = 0; i < track_entries_size_; ++i) {
+ track_entries[i] = track_entries_[i];
+ }
+
+ delete[] track_entries_;
+
+ // Find the lowest availible track number > 0.
+ if (track_num == 0) {
+ track_num = count;
+
+ // Check to make sure a track does not already have |track_num|.
+ bool exit = false;
+ do {
+ exit = true;
+ for (uint32_t i = 0; i < track_entries_size_; ++i) {
+ if (track_entries[i]->number() == track_num) {
+ track_num++;
+ exit = false;
+ break;
+ }
+ }
+ } while (!exit);
+ }
+ track->set_number(track_num);
+
+ track_entries_ = track_entries;
+ track_entries_[track_entries_size_] = track;
+ track_entries_size_ = count;
+ return true;
+}
+
+const Track* Tracks::GetTrackByIndex(uint32_t index) const {
+ if (track_entries_ == NULL)
+ return NULL;
+
+ if (index >= track_entries_size_)
+ return NULL;
+
+ return track_entries_[index];
+}
+
+Track* Tracks::GetTrackByNumber(uint64_t track_number) const {
+ const int32_t count = track_entries_size();
+ for (int32_t i = 0; i < count; ++i) {
+ if (track_entries_[i]->number() == track_number)
+ return track_entries_[i];
+ }
+
+ return NULL;
+}
+
+bool Tracks::TrackIsAudio(uint64_t track_number) const {
+ const Track* const track = GetTrackByNumber(track_number);
+
+ if (track->type() == kAudio)
+ return true;
+
+ return false;
+}
+
+bool Tracks::TrackIsVideo(uint64_t track_number) const {
+ const Track* const track = GetTrackByNumber(track_number);
+
+ if (track->type() == kVideo)
+ return true;
+
+ return false;
+}
+
+bool Tracks::Write(IMkvWriter* writer) const {
+ uint64_t size = 0;
+ const int32_t count = track_entries_size();
+ for (int32_t i = 0; i < count; ++i) {
+ const Track* const track = GetTrackByIndex(i);
+
+ if (!track)
+ return false;
+
+ size += track->Size();
+ }
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvTracks, size))
+ return false;
+
+ const int64_t payload_position = writer->Position();
+ if (payload_position < 0)
+ return false;
+
+ for (int32_t i = 0; i < count; ++i) {
+ const Track* const track = GetTrackByIndex(i);
+ if (!track->Write(writer))
+ return false;
+ }
+
+ const int64_t stop_position = writer->Position();
+ if (stop_position < 0 ||
+ stop_position - payload_position != static_cast<int64_t>(size))
+ return false;
+
+ wrote_tracks_ = true;
+ return true;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// Chapter Class
+
+bool Chapter::set_id(const char* id) { return StrCpy(id, &id_); }
+
+void Chapter::set_time(const Segment& segment, uint64_t start_ns,
+ uint64_t end_ns) {
+ const SegmentInfo* const info = segment.GetSegmentInfo();
+ const uint64_t timecode_scale = info->timecode_scale();
+ start_timecode_ = start_ns / timecode_scale;
+ end_timecode_ = end_ns / timecode_scale;
+}
+
+bool Chapter::add_string(const char* title, const char* language,
+ const char* country) {
+ if (!ExpandDisplaysArray())
+ return false;
+
+ Display& d = displays_[displays_count_++];
+ d.Init();
+
+ if (!d.set_title(title))
+ return false;
+
+ if (!d.set_language(language))
+ return false;
+
+ if (!d.set_country(country))
+ return false;
+
+ return true;
+}
+
+Chapter::Chapter() {
+ // This ctor only constructs the object. Proper initialization is
+ // done in Init() (called in Chapters::AddChapter()). The only
+ // reason we bother implementing this ctor is because we had to
+ // declare it as private (along with the dtor), in order to prevent
+ // clients from creating Chapter instances (a privelege we grant
+ // only to the Chapters class). Doing no initialization here also
+ // means that creating arrays of chapter objects is more efficient,
+ // because we only initialize each new chapter object as it becomes
+ // active on the array.
+}
+
+Chapter::~Chapter() {}
+
+void Chapter::Init(unsigned int* seed) {
+ id_ = NULL;
+ start_timecode_ = 0;
+ end_timecode_ = 0;
+ displays_ = NULL;
+ displays_size_ = 0;
+ displays_count_ = 0;
+ uid_ = MakeUID(seed);
+}
+
+void Chapter::ShallowCopy(Chapter* dst) const {
+ dst->id_ = id_;
+ dst->start_timecode_ = start_timecode_;
+ dst->end_timecode_ = end_timecode_;
+ dst->uid_ = uid_;
+ dst->displays_ = displays_;
+ dst->displays_size_ = displays_size_;
+ dst->displays_count_ = displays_count_;
+}
+
+void Chapter::Clear() {
+ StrCpy(NULL, &id_);
+
+ while (displays_count_ > 0) {
+ Display& d = displays_[--displays_count_];
+ d.Clear();
+ }
+
+ delete[] displays_;
+ displays_ = NULL;
+
+ displays_size_ = 0;
+}
+
+bool Chapter::ExpandDisplaysArray() {
+ if (displays_size_ > displays_count_)
+ return true; // nothing to do yet
+
+ const int size = (displays_size_ == 0) ? 1 : 2 * displays_size_;
+
+ Display* const displays = new (std::nothrow) Display[size]; // NOLINT
+ if (displays == NULL)
+ return false;
+
+ for (int idx = 0; idx < displays_count_; ++idx) {
+ displays[idx] = displays_[idx]; // shallow copy
+ }
+
+ delete[] displays_;
+
+ displays_ = displays;
+ displays_size_ = size;
+
+ return true;
+}
+
+uint64_t Chapter::WriteAtom(IMkvWriter* writer) const {
+ uint64_t payload_size =
+ EbmlElementSize(libwebm::kMkvChapterStringUID, id_) +
+ EbmlElementSize(libwebm::kMkvChapterUID, static_cast<uint64>(uid_)) +
+ EbmlElementSize(libwebm::kMkvChapterTimeStart,
+ static_cast<uint64>(start_timecode_)) +
+ EbmlElementSize(libwebm::kMkvChapterTimeEnd,
+ static_cast<uint64>(end_timecode_));
+
+ for (int idx = 0; idx < displays_count_; ++idx) {
+ const Display& d = displays_[idx];
+ payload_size += d.WriteDisplay(NULL);
+ }
+
+ const uint64_t atom_size =
+ EbmlMasterElementSize(libwebm::kMkvChapterAtom, payload_size) +
+ payload_size;
+
+ if (writer == NULL)
+ return atom_size;
+
+ const int64_t start = writer->Position();
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapterAtom, payload_size))
+ return 0;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvChapterStringUID, id_))
+ return 0;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvChapterUID,
+ static_cast<uint64>(uid_)))
+ return 0;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvChapterTimeStart,
+ static_cast<uint64>(start_timecode_)))
+ return 0;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvChapterTimeEnd,
+ static_cast<uint64>(end_timecode_)))
+ return 0;
+
+ for (int idx = 0; idx < displays_count_; ++idx) {
+ const Display& d = displays_[idx];
+
+ if (!d.WriteDisplay(writer))
+ return 0;
+ }
+
+ const int64_t stop = writer->Position();
+
+ if (stop >= start && uint64_t(stop - start) != atom_size)
+ return 0;
+
+ return atom_size;
+}
+
+void Chapter::Display::Init() {
+ title_ = NULL;
+ language_ = NULL;
+ country_ = NULL;
+}
+
+void Chapter::Display::Clear() {
+ StrCpy(NULL, &title_);
+ StrCpy(NULL, &language_);
+ StrCpy(NULL, &country_);
+}
+
+bool Chapter::Display::set_title(const char* title) {
+ return StrCpy(title, &title_);
+}
+
+bool Chapter::Display::set_language(const char* language) {
+ return StrCpy(language, &language_);
+}
+
+bool Chapter::Display::set_country(const char* country) {
+ return StrCpy(country, &country_);
+}
+
+uint64_t Chapter::Display::WriteDisplay(IMkvWriter* writer) const {
+ uint64_t payload_size = EbmlElementSize(libwebm::kMkvChapString, title_);
+
+ if (language_)
+ payload_size += EbmlElementSize(libwebm::kMkvChapLanguage, language_);
+
+ if (country_)
+ payload_size += EbmlElementSize(libwebm::kMkvChapCountry, country_);
+
+ const uint64_t display_size =
+ EbmlMasterElementSize(libwebm::kMkvChapterDisplay, payload_size) +
+ payload_size;
+
+ if (writer == NULL)
+ return display_size;
+
+ const int64_t start = writer->Position();
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapterDisplay,
+ payload_size))
+ return 0;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvChapString, title_))
+ return 0;
+
+ if (language_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvChapLanguage, language_))
+ return 0;
+ }
+
+ if (country_) {
+ if (!WriteEbmlElement(writer, libwebm::kMkvChapCountry, country_))
+ return 0;
+ }
+
+ const int64_t stop = writer->Position();
+
+ if (stop >= start && uint64_t(stop - start) != display_size)
+ return 0;
+
+ return display_size;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// Chapters Class
+
+Chapters::Chapters() : chapters_size_(0), chapters_count_(0), chapters_(NULL) {}
+
+Chapters::~Chapters() {
+ while (chapters_count_ > 0) {
+ Chapter& chapter = chapters_[--chapters_count_];
+ chapter.Clear();
+ }
+
+ delete[] chapters_;
+ chapters_ = NULL;
+}
+
+int Chapters::Count() const { return chapters_count_; }
+
+Chapter* Chapters::AddChapter(unsigned int* seed) {
+ if (!ExpandChaptersArray())
+ return NULL;
+
+ Chapter& chapter = chapters_[chapters_count_++];
+ chapter.Init(seed);
+
+ return &chapter;
+}
+
+bool Chapters::Write(IMkvWriter* writer) const {
+ if (writer == NULL)
+ return false;
+
+ const uint64_t payload_size = WriteEdition(NULL); // return size only
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvChapters, payload_size))
+ return false;
+
+ const int64_t start = writer->Position();
+
+ if (WriteEdition(writer) == 0) // error
+ return false;
+
+ const int64_t stop = writer->Position();
+
+ if (stop >= start && uint64_t(stop - start) != payload_size)
+ return false;
+
+ return true;
+}
+
+bool Chapters::ExpandChaptersArray() {
+ if (chapters_size_ > chapters_count_)
+ return true; // nothing to do yet
+
+ const int size = (chapters_size_ == 0) ? 1 : 2 * chapters_size_;
+
+ Chapter* const chapters = new (std::nothrow) Chapter[size]; // NOLINT
+ if (chapters == NULL)
+ return false;
+
+ for (int idx = 0; idx < chapters_count_; ++idx) {
+ const Chapter& src = chapters_[idx];
+ Chapter* const dst = chapters + idx;
+ src.ShallowCopy(dst);
+ }
+
+ delete[] chapters_;
+
+ chapters_ = chapters;
+ chapters_size_ = size;
+
+ return true;
+}
+
+uint64_t Chapters::WriteEdition(IMkvWriter* writer) const {
+ uint64_t payload_size = 0;
+
+ for (int idx = 0; idx < chapters_count_; ++idx) {
+ const Chapter& chapter = chapters_[idx];
+ payload_size += chapter.WriteAtom(NULL);
+ }
+
+ const uint64_t edition_size =
+ EbmlMasterElementSize(libwebm::kMkvEditionEntry, payload_size) +
+ payload_size;
+
+ if (writer == NULL) // return size only
+ return edition_size;
+
+ const int64_t start = writer->Position();
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvEditionEntry, payload_size))
+ return 0; // error
+
+ for (int idx = 0; idx < chapters_count_; ++idx) {
+ const Chapter& chapter = chapters_[idx];
+
+ const uint64_t chapter_size = chapter.WriteAtom(writer);
+ if (chapter_size == 0) // error
+ return 0;
+ }
+
+ const int64_t stop = writer->Position();
+
+ if (stop >= start && uint64_t(stop - start) != edition_size)
+ return 0;
+
+ return edition_size;
+}
+
+// Tag Class
+
+bool Tag::add_simple_tag(const char* tag_name, const char* tag_string) {
+ if (!ExpandSimpleTagsArray())
+ return false;
+
+ SimpleTag& st = simple_tags_[simple_tags_count_++];
+ st.Init();
+
+ if (!st.set_tag_name(tag_name))
+ return false;
+
+ if (!st.set_tag_string(tag_string))
+ return false;
+
+ return true;
+}
+
+Tag::Tag() {
+ simple_tags_ = NULL;
+ simple_tags_size_ = 0;
+ simple_tags_count_ = 0;
+}
+
+Tag::~Tag() {}
+
+void Tag::ShallowCopy(Tag* dst) const {
+ dst->simple_tags_ = simple_tags_;
+ dst->simple_tags_size_ = simple_tags_size_;
+ dst->simple_tags_count_ = simple_tags_count_;
+}
+
+void Tag::Clear() {
+ while (simple_tags_count_ > 0) {
+ SimpleTag& st = simple_tags_[--simple_tags_count_];
+ st.Clear();
+ }
+
+ delete[] simple_tags_;
+ simple_tags_ = NULL;
+
+ simple_tags_size_ = 0;
+}
+
+bool Tag::ExpandSimpleTagsArray() {
+ if (simple_tags_size_ > simple_tags_count_)
+ return true; // nothing to do yet
+
+ const int size = (simple_tags_size_ == 0) ? 1 : 2 * simple_tags_size_;
+
+ SimpleTag* const simple_tags = new (std::nothrow) SimpleTag[size]; // NOLINT
+ if (simple_tags == NULL)
+ return false;
+
+ for (int idx = 0; idx < simple_tags_count_; ++idx) {
+ simple_tags[idx] = simple_tags_[idx]; // shallow copy
+ }
+
+ delete[] simple_tags_;
+
+ simple_tags_ = simple_tags;
+ simple_tags_size_ = size;
+
+ return true;
+}
+
+uint64_t Tag::Write(IMkvWriter* writer) const {
+ uint64_t payload_size = 0;
+
+ for (int idx = 0; idx < simple_tags_count_; ++idx) {
+ const SimpleTag& st = simple_tags_[idx];
+ payload_size += st.Write(NULL);
+ }
+
+ const uint64_t tag_size =
+ EbmlMasterElementSize(libwebm::kMkvTag, payload_size) + payload_size;
+
+ if (writer == NULL)
+ return tag_size;
+
+ const int64_t start = writer->Position();
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvTag, payload_size))
+ return 0;
+
+ for (int idx = 0; idx < simple_tags_count_; ++idx) {
+ const SimpleTag& st = simple_tags_[idx];
+
+ if (!st.Write(writer))
+ return 0;
+ }
+
+ const int64_t stop = writer->Position();
+
+ if (stop >= start && uint64_t(stop - start) != tag_size)
+ return 0;
+
+ return tag_size;
+}
+
+// Tag::SimpleTag
+
+void Tag::SimpleTag::Init() {
+ tag_name_ = NULL;
+ tag_string_ = NULL;
+}
+
+void Tag::SimpleTag::Clear() {
+ StrCpy(NULL, &tag_name_);
+ StrCpy(NULL, &tag_string_);
+}
+
+bool Tag::SimpleTag::set_tag_name(const char* tag_name) {
+ return StrCpy(tag_name, &tag_name_);
+}
+
+bool Tag::SimpleTag::set_tag_string(const char* tag_string) {
+ return StrCpy(tag_string, &tag_string_);
+}
+
+uint64_t Tag::SimpleTag::Write(IMkvWriter* writer) const {
+ uint64_t payload_size = EbmlElementSize(libwebm::kMkvTagName, tag_name_);
+
+ payload_size += EbmlElementSize(libwebm::kMkvTagString, tag_string_);
+
+ const uint64_t simple_tag_size =
+ EbmlMasterElementSize(libwebm::kMkvSimpleTag, payload_size) +
+ payload_size;
+
+ if (writer == NULL)
+ return simple_tag_size;
+
+ const int64_t start = writer->Position();
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvSimpleTag, payload_size))
+ return 0;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvTagName, tag_name_))
+ return 0;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvTagString, tag_string_))
+ return 0;
+
+ const int64_t stop = writer->Position();
+
+ if (stop >= start && uint64_t(stop - start) != simple_tag_size)
+ return 0;
+
+ return simple_tag_size;
+}
+
+// Tags Class
+
+Tags::Tags() : tags_size_(0), tags_count_(0), tags_(NULL) {}
+
+Tags::~Tags() {
+ while (tags_count_ > 0) {
+ Tag& tag = tags_[--tags_count_];
+ tag.Clear();
+ }
+
+ delete[] tags_;
+ tags_ = NULL;
+}
+
+int Tags::Count() const { return tags_count_; }
+
+Tag* Tags::AddTag() {
+ if (!ExpandTagsArray())
+ return NULL;
+
+ Tag& tag = tags_[tags_count_++];
+
+ return &tag;
+}
+
+bool Tags::Write(IMkvWriter* writer) const {
+ if (writer == NULL)
+ return false;
+
+ uint64_t payload_size = 0;
+
+ for (int idx = 0; idx < tags_count_; ++idx) {
+ const Tag& tag = tags_[idx];
+ payload_size += tag.Write(NULL);
+ }
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvTags, payload_size))
+ return false;
+
+ const int64_t start = writer->Position();
+
+ for (int idx = 0; idx < tags_count_; ++idx) {
+ const Tag& tag = tags_[idx];
+
+ const uint64_t tag_size = tag.Write(writer);
+ if (tag_size == 0) // error
+ return 0;
+ }
+
+ const int64_t stop = writer->Position();
+
+ if (stop >= start && uint64_t(stop - start) != payload_size)
+ return false;
+
+ return true;
+}
+
+bool Tags::ExpandTagsArray() {
+ if (tags_size_ > tags_count_)
+ return true; // nothing to do yet
+
+ const int size = (tags_size_ == 0) ? 1 : 2 * tags_size_;
+
+ Tag* const tags = new (std::nothrow) Tag[size]; // NOLINT
+ if (tags == NULL)
+ return false;
+
+ for (int idx = 0; idx < tags_count_; ++idx) {
+ const Tag& src = tags_[idx];
+ Tag* const dst = tags + idx;
+ src.ShallowCopy(dst);
+ }
+
+ delete[] tags_;
+
+ tags_ = tags;
+ tags_size_ = size;
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// Cluster class
+
+Cluster::Cluster(uint64_t timecode, int64_t cues_pos, uint64_t timecode_scale,
+ bool write_last_frame_with_duration, bool fixed_size_timecode)
+ : blocks_added_(0),
+ finalized_(false),
+ fixed_size_timecode_(fixed_size_timecode),
+ header_written_(false),
+ payload_size_(0),
+ position_for_cues_(cues_pos),
+ size_position_(-1),
+ timecode_(timecode),
+ timecode_scale_(timecode_scale),
+ write_last_frame_with_duration_(write_last_frame_with_duration),
+ writer_(NULL) {}
+
+Cluster::~Cluster() {
+ // Delete any stored frames that are left behind. This will happen if the
+ // Cluster was not Finalized for whatever reason.
+ while (!stored_frames_.empty()) {
+ while (!stored_frames_.begin()->second.empty()) {
+ delete stored_frames_.begin()->second.front();
+ stored_frames_.begin()->second.pop_front();
+ }
+ stored_frames_.erase(stored_frames_.begin()->first);
+ }
+}
+
+bool Cluster::Init(IMkvWriter* ptr_writer) {
+ if (!ptr_writer) {
+ return false;
+ }
+ writer_ = ptr_writer;
+ return true;
+}
+
+bool Cluster::AddFrame(const Frame* const frame) {
+ return QueueOrWriteFrame(frame);
+}
+
+bool Cluster::AddFrame(const uint8_t* data, uint64_t length,
+ uint64_t track_number, uint64_t abs_timecode,
+ bool is_key) {
+ Frame frame;
+ if (!frame.Init(data, length))
+ return false;
+ frame.set_track_number(track_number);
+ frame.set_timestamp(abs_timecode);
+ frame.set_is_key(is_key);
+ return QueueOrWriteFrame(&frame);
+}
+
+bool Cluster::AddFrameWithAdditional(const uint8_t* data, uint64_t length,
+ const uint8_t* additional,
+ uint64_t additional_length,
+ uint64_t add_id, uint64_t track_number,
+ uint64_t abs_timecode, bool is_key) {
+ if (!additional || additional_length == 0) {
+ return false;
+ }
+ Frame frame;
+ if (!frame.Init(data, length) ||
+ !frame.AddAdditionalData(additional, additional_length, add_id)) {
+ return false;
+ }
+ frame.set_track_number(track_number);
+ frame.set_timestamp(abs_timecode);
+ frame.set_is_key(is_key);
+ return QueueOrWriteFrame(&frame);
+}
+
+bool Cluster::AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length,
+ int64_t discard_padding,
+ uint64_t track_number,
+ uint64_t abs_timecode, bool is_key) {
+ Frame frame;
+ if (!frame.Init(data, length))
+ return false;
+ frame.set_discard_padding(discard_padding);
+ frame.set_track_number(track_number);
+ frame.set_timestamp(abs_timecode);
+ frame.set_is_key(is_key);
+ return QueueOrWriteFrame(&frame);
+}
+
+bool Cluster::AddMetadata(const uint8_t* data, uint64_t length,
+ uint64_t track_number, uint64_t abs_timecode,
+ uint64_t duration_timecode) {
+ Frame frame;
+ if (!frame.Init(data, length))
+ return false;
+ frame.set_track_number(track_number);
+ frame.set_timestamp(abs_timecode);
+ frame.set_duration(duration_timecode);
+ frame.set_is_key(true); // All metadata blocks are keyframes.
+ return QueueOrWriteFrame(&frame);
+}
+
+void Cluster::AddPayloadSize(uint64_t size) { payload_size_ += size; }
+
+bool Cluster::Finalize() {
+ return !write_last_frame_with_duration_ && Finalize(false, 0);
+}
+
+bool Cluster::Finalize(bool set_last_frame_duration, uint64_t duration) {
+ if (!writer_ || finalized_)
+ return false;
+
+ if (write_last_frame_with_duration_) {
+ // Write out held back Frames. This essentially performs a k-way merge
+ // across all tracks in the increasing order of timestamps.
+ while (!stored_frames_.empty()) {
+ Frame* frame = stored_frames_.begin()->second.front();
+
+ // Get the next frame to write (frame with least timestamp across all
+ // tracks).
+ for (FrameMapIterator frames_iterator = ++stored_frames_.begin();
+ frames_iterator != stored_frames_.end(); ++frames_iterator) {
+ if (frames_iterator->second.front()->timestamp() < frame->timestamp()) {
+ frame = frames_iterator->second.front();
+ }
+ }
+
+ // Set the duration if it's the last frame for the track.
+ if (set_last_frame_duration &&
+ stored_frames_[frame->track_number()].size() == 1 &&
+ !frame->duration_set()) {
+ frame->set_duration(duration - frame->timestamp());
+ if (!frame->is_key() && !frame->reference_block_timestamp_set()) {
+ frame->set_reference_block_timestamp(
+ last_block_timestamp_[frame->track_number()]);
+ }
+ }
+
+ // Write the frame and remove it from |stored_frames_|.
+ const bool wrote_frame = DoWriteFrame(frame);
+ stored_frames_[frame->track_number()].pop_front();
+ if (stored_frames_[frame->track_number()].empty()) {
+ stored_frames_.erase(frame->track_number());
+ }
+ delete frame;
+ if (!wrote_frame)
+ return false;
+ }
+ }
+
+ if (size_position_ == -1)
+ return false;
+
+ if (writer_->Seekable()) {
+ const int64_t pos = writer_->Position();
+
+ if (writer_->Position(size_position_))
+ return false;
+
+ if (WriteUIntSize(writer_, payload_size(), 8))
+ return false;
+
+ if (writer_->Position(pos))
+ return false;
+ }
+
+ finalized_ = true;
+
+ return true;
+}
+
+uint64_t Cluster::Size() const {
+ const uint64_t element_size =
+ EbmlMasterElementSize(libwebm::kMkvCluster, 0xFFFFFFFFFFFFFFFFULL) +
+ payload_size_;
+ return element_size;
+}
+
+bool Cluster::PreWriteBlock() {
+ if (finalized_)
+ return false;
+
+ if (!header_written_) {
+ if (!WriteClusterHeader())
+ return false;
+ }
+
+ return true;
+}
+
+void Cluster::PostWriteBlock(uint64_t element_size) {
+ AddPayloadSize(element_size);
+ ++blocks_added_;
+}
+
+int64_t Cluster::GetRelativeTimecode(int64_t abs_timecode) const {
+ const int64_t cluster_timecode = this->Cluster::timecode();
+ const int64_t rel_timecode =
+ static_cast<int64_t>(abs_timecode) - cluster_timecode;
+
+ if (rel_timecode < 0 || rel_timecode > kMaxBlockTimecode)
+ return -1;
+
+ return rel_timecode;
+}
+
+bool Cluster::DoWriteFrame(const Frame* const frame) {
+ if (!frame || !frame->IsValid())
+ return false;
+
+ if (!PreWriteBlock())
+ return false;
+
+ const uint64_t element_size = WriteFrame(writer_, frame, this);
+ if (element_size == 0)
+ return false;
+
+ PostWriteBlock(element_size);
+ last_block_timestamp_[frame->track_number()] = frame->timestamp();
+ return true;
+}
+
+bool Cluster::QueueOrWriteFrame(const Frame* const frame) {
+ if (!frame || !frame->IsValid())
+ return false;
+
+ // If |write_last_frame_with_duration_| is not set, then write the frame right
+ // away.
+ if (!write_last_frame_with_duration_) {
+ return DoWriteFrame(frame);
+ }
+
+ // Queue the current frame.
+ uint64_t track_number = frame->track_number();
+ Frame* const frame_to_store = new Frame();
+ frame_to_store->CopyFrom(*frame);
+ stored_frames_[track_number].push_back(frame_to_store);
+
+ // Iterate through all queued frames in the current track except the last one
+ // and write it if it is okay to do so (i.e.) no other track has an held back
+ // frame with timestamp <= the timestamp of the frame in question.
+ std::vector<std::list<Frame*>::iterator> frames_to_erase;
+ for (std::list<Frame*>::iterator
+ current_track_iterator = stored_frames_[track_number].begin(),
+ end = --stored_frames_[track_number].end();
+ current_track_iterator != end; ++current_track_iterator) {
+ const Frame* const frame_to_write = *current_track_iterator;
+ bool okay_to_write = true;
+ for (FrameMapIterator track_iterator = stored_frames_.begin();
+ track_iterator != stored_frames_.end(); ++track_iterator) {
+ if (track_iterator->first == track_number) {
+ continue;
+ }
+ if (track_iterator->second.front()->timestamp() <
+ frame_to_write->timestamp()) {
+ okay_to_write = false;
+ break;
+ }
+ }
+ if (okay_to_write) {
+ const bool wrote_frame = DoWriteFrame(frame_to_write);
+ delete frame_to_write;
+ if (!wrote_frame)
+ return false;
+ frames_to_erase.push_back(current_track_iterator);
+ } else {
+ break;
+ }
+ }
+ for (std::vector<std::list<Frame*>::iterator>::iterator iterator =
+ frames_to_erase.begin();
+ iterator != frames_to_erase.end(); ++iterator) {
+ stored_frames_[track_number].erase(*iterator);
+ }
+ return true;
+}
+
+bool Cluster::WriteClusterHeader() {
+ if (finalized_)
+ return false;
+
+ if (WriteID(writer_, libwebm::kMkvCluster))
+ return false;
+
+ // Save for later.
+ size_position_ = writer_->Position();
+
+ // Write "unknown" (EBML coded -1) as cluster size value. We need to write 8
+ // bytes because we do not know how big our cluster will be.
+ if (SerializeInt(writer_, kEbmlUnknownValue, 8))
+ return false;
+
+ if (!WriteEbmlElement(writer_, libwebm::kMkvTimecode, timecode(),
+ fixed_size_timecode_ ? 8 : 0)) {
+ return false;
+ }
+ AddPayloadSize(EbmlElementSize(libwebm::kMkvTimecode, timecode(),
+ fixed_size_timecode_ ? 8 : 0));
+ header_written_ = true;
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// SeekHead Class
+
+SeekHead::SeekHead() : start_pos_(0ULL) {
+ for (int32_t i = 0; i < kSeekEntryCount; ++i) {
+ seek_entry_id_[i] = 0;
+ seek_entry_pos_[i] = 0;
+ }
+}
+
+SeekHead::~SeekHead() {}
+
+bool SeekHead::Finalize(IMkvWriter* writer) const {
+ if (writer->Seekable()) {
+ if (start_pos_ == -1)
+ return false;
+
+ uint64_t payload_size = 0;
+ uint64_t entry_size[kSeekEntryCount];
+
+ for (int32_t i = 0; i < kSeekEntryCount; ++i) {
+ if (seek_entry_id_[i] != 0) {
+ entry_size[i] = EbmlElementSize(libwebm::kMkvSeekID,
+ static_cast<uint64>(seek_entry_id_[i]));
+ entry_size[i] += EbmlElementSize(
+ libwebm::kMkvSeekPosition, static_cast<uint64>(seek_entry_pos_[i]));
+
+ payload_size +=
+ EbmlMasterElementSize(libwebm::kMkvSeek, entry_size[i]) +
+ entry_size[i];
+ }
+ }
+
+ // No SeekHead elements
+ if (payload_size == 0)
+ return true;
+
+ const int64_t pos = writer->Position();
+ if (writer->Position(start_pos_))
+ return false;
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvSeekHead, payload_size))
+ return false;
+
+ for (int32_t i = 0; i < kSeekEntryCount; ++i) {
+ if (seek_entry_id_[i] != 0) {
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvSeek, entry_size[i]))
+ return false;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvSeekID,
+ static_cast<uint64>(seek_entry_id_[i])))
+ return false;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvSeekPosition,
+ static_cast<uint64>(seek_entry_pos_[i])))
+ return false;
+ }
+ }
+
+ const uint64_t total_entry_size = kSeekEntryCount * MaxEntrySize();
+ const uint64_t total_size =
+ EbmlMasterElementSize(libwebm::kMkvSeekHead, total_entry_size) +
+ total_entry_size;
+ const int64_t size_left = total_size - (writer->Position() - start_pos_);
+
+ const uint64_t bytes_written = WriteVoidElement(writer, size_left);
+ if (!bytes_written)
+ return false;
+
+ if (writer->Position(pos))
+ return false;
+ }
+
+ return true;
+}
+
+bool SeekHead::Write(IMkvWriter* writer) {
+ const uint64_t entry_size = kSeekEntryCount * MaxEntrySize();
+ const uint64_t size =
+ EbmlMasterElementSize(libwebm::kMkvSeekHead, entry_size);
+
+ start_pos_ = writer->Position();
+
+ const uint64_t bytes_written = WriteVoidElement(writer, size + entry_size);
+ if (!bytes_written)
+ return false;
+
+ return true;
+}
+
+bool SeekHead::AddSeekEntry(uint32_t id, uint64_t pos) {
+ for (int32_t i = 0; i < kSeekEntryCount; ++i) {
+ if (seek_entry_id_[i] == 0) {
+ seek_entry_id_[i] = id;
+ seek_entry_pos_[i] = pos;
+ return true;
+ }
+ }
+ return false;
+}
+
+uint32_t SeekHead::GetId(int index) const {
+ if (index < 0 || index >= kSeekEntryCount)
+ return UINT_MAX;
+ return seek_entry_id_[index];
+}
+
+uint64_t SeekHead::GetPosition(int index) const {
+ if (index < 0 || index >= kSeekEntryCount)
+ return ULLONG_MAX;
+ return seek_entry_pos_[index];
+}
+
+bool SeekHead::SetSeekEntry(int index, uint32_t id, uint64_t position) {
+ if (index < 0 || index >= kSeekEntryCount)
+ return false;
+ seek_entry_id_[index] = id;
+ seek_entry_pos_[index] = position;
+ return true;
+}
+
+uint64_t SeekHead::MaxEntrySize() const {
+ const uint64_t max_entry_payload_size =
+ EbmlElementSize(libwebm::kMkvSeekID,
+ static_cast<uint64>(UINT64_C(0xffffffff))) +
+ EbmlElementSize(libwebm::kMkvSeekPosition,
+ static_cast<uint64>(UINT64_C(0xffffffffffffffff)));
+ const uint64_t max_entry_size =
+ EbmlMasterElementSize(libwebm::kMkvSeek, max_entry_payload_size) +
+ max_entry_payload_size;
+
+ return max_entry_size;
+}
+
+///////////////////////////////////////////////////////////////
+//
+// SegmentInfo Class
+
+SegmentInfo::SegmentInfo()
+ : duration_(-1.0),
+ muxing_app_(NULL),
+ timecode_scale_(1000000ULL),
+ writing_app_(NULL),
+ date_utc_(LLONG_MIN),
+ duration_pos_(-1) {}
+
+SegmentInfo::~SegmentInfo() {
+ delete[] muxing_app_;
+ delete[] writing_app_;
+}
+
+bool SegmentInfo::Init() {
+ int32_t major;
+ int32_t minor;
+ int32_t build;
+ int32_t revision;
+ GetVersion(&major, &minor, &build, &revision);
+ char temp[256];
+#ifdef _MSC_VER
+ sprintf_s(temp, sizeof(temp) / sizeof(temp[0]), "libwebm-%d.%d.%d.%d", major,
+ minor, build, revision);
+#else
+ snprintf(temp, sizeof(temp) / sizeof(temp[0]), "libwebm-%d.%d.%d.%d", major,
+ minor, build, revision);
+#endif
+
+ const size_t app_len = strlen(temp) + 1;
+
+ delete[] muxing_app_;
+
+ muxing_app_ = new (std::nothrow) char[app_len]; // NOLINT
+ if (!muxing_app_)
+ return false;
+
+#ifdef _MSC_VER
+ strcpy_s(muxing_app_, app_len, temp);
+#else
+ strcpy(muxing_app_, temp);
+#endif
+
+ set_writing_app(temp);
+ if (!writing_app_)
+ return false;
+ return true;
+}
+
+bool SegmentInfo::Finalize(IMkvWriter* writer) const {
+ if (!writer)
+ return false;
+
+ if (duration_ > 0.0) {
+ if (writer->Seekable()) {
+ if (duration_pos_ == -1)
+ return false;
+
+ const int64_t pos = writer->Position();
+
+ if (writer->Position(duration_pos_))
+ return false;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvDuration,
+ static_cast<float>(duration_)))
+ return false;
+
+ if (writer->Position(pos))
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool SegmentInfo::Write(IMkvWriter* writer) {
+ if (!writer || !muxing_app_ || !writing_app_)
+ return false;
+
+ uint64_t size = EbmlElementSize(libwebm::kMkvTimecodeScale,
+ static_cast<uint64>(timecode_scale_));
+ if (duration_ > 0.0)
+ size +=
+ EbmlElementSize(libwebm::kMkvDuration, static_cast<float>(duration_));
+ if (date_utc_ != LLONG_MIN)
+ size += EbmlDateElementSize(libwebm::kMkvDateUTC);
+ size += EbmlElementSize(libwebm::kMkvMuxingApp, muxing_app_);
+ size += EbmlElementSize(libwebm::kMkvWritingApp, writing_app_);
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvInfo, size))
+ return false;
+
+ const int64_t payload_position = writer->Position();
+ if (payload_position < 0)
+ return false;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvTimecodeScale,
+ static_cast<uint64>(timecode_scale_)))
+ return false;
+
+ if (duration_ > 0.0) {
+ // Save for later
+ duration_pos_ = writer->Position();
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvDuration,
+ static_cast<float>(duration_)))
+ return false;
+ }
+
+ if (date_utc_ != LLONG_MIN)
+ WriteEbmlDateElement(writer, libwebm::kMkvDateUTC, date_utc_);
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvMuxingApp, muxing_app_))
+ return false;
+ if (!WriteEbmlElement(writer, libwebm::kMkvWritingApp, writing_app_))
+ return false;
+
+ const int64_t stop_position = writer->Position();
+ if (stop_position < 0 ||
+ stop_position - payload_position != static_cast<int64_t>(size))
+ return false;
+
+ return true;
+}
+
+void SegmentInfo::set_muxing_app(const char* app) {
+ if (app) {
+ const size_t length = strlen(app) + 1;
+ char* temp_str = new (std::nothrow) char[length]; // NOLINT
+ if (!temp_str)
+ return;
+
+#ifdef _MSC_VER
+ strcpy_s(temp_str, length, app);
+#else
+ strcpy(temp_str, app);
+#endif
+
+ delete[] muxing_app_;
+ muxing_app_ = temp_str;
+ }
+}
+
+void SegmentInfo::set_writing_app(const char* app) {
+ if (app) {
+ const size_t length = strlen(app) + 1;
+ char* temp_str = new (std::nothrow) char[length]; // NOLINT
+ if (!temp_str)
+ return;
+
+#ifdef _MSC_VER
+ strcpy_s(temp_str, length, app);
+#else
+ strcpy(temp_str, app);
+#endif
+
+ delete[] writing_app_;
+ writing_app_ = temp_str;
+ }
+}
+
+///////////////////////////////////////////////////////////////
+//
+// Segment Class
+
+Segment::Segment()
+ : chunk_count_(0),
+ chunk_name_(NULL),
+ chunk_writer_cluster_(NULL),
+ chunk_writer_cues_(NULL),
+ chunk_writer_header_(NULL),
+ chunking_(false),
+ chunking_base_name_(NULL),
+ cluster_list_(NULL),
+ cluster_list_capacity_(0),
+ cluster_list_size_(0),
+ cues_position_(kAfterClusters),
+ cues_track_(0),
+ force_new_cluster_(false),
+ frames_(NULL),
+ frames_capacity_(0),
+ frames_size_(0),
+ has_video_(false),
+ header_written_(false),
+ last_block_duration_(0),
+ last_timestamp_(0),
+ max_cluster_duration_(kDefaultMaxClusterDuration),
+ max_cluster_size_(0),
+ mode_(kFile),
+ new_cuepoint_(false),
+ output_cues_(true),
+ accurate_cluster_duration_(false),
+ fixed_size_cluster_timecode_(false),
+ estimate_file_duration_(false),
+ payload_pos_(0),
+ size_position_(0),
+ doc_type_version_(kDefaultDocTypeVersion),
+ doc_type_version_written_(0),
+ duration_(0.0),
+ writer_cluster_(NULL),
+ writer_cues_(NULL),
+ writer_header_(NULL) {
+ const time_t curr_time = time(NULL);
+ seed_ = static_cast<unsigned int>(curr_time);
+#ifdef _WIN32
+ srand(seed_);
+#endif
+}
+
+Segment::~Segment() {
+ if (cluster_list_) {
+ for (int32_t i = 0; i < cluster_list_size_; ++i) {
+ Cluster* const cluster = cluster_list_[i];
+ delete cluster;
+ }
+ delete[] cluster_list_;
+ }
+
+ if (frames_) {
+ for (int32_t i = 0; i < frames_size_; ++i) {
+ Frame* const frame = frames_[i];
+ delete frame;
+ }
+ delete[] frames_;
+ }
+
+ delete[] chunk_name_;
+ delete[] chunking_base_name_;
+
+ if (chunk_writer_cluster_) {
+ chunk_writer_cluster_->Close();
+ delete chunk_writer_cluster_;
+ }
+ if (chunk_writer_cues_) {
+ chunk_writer_cues_->Close();
+ delete chunk_writer_cues_;
+ }
+ if (chunk_writer_header_) {
+ chunk_writer_header_->Close();
+ delete chunk_writer_header_;
+ }
+}
+
+void Segment::MoveCuesBeforeClustersHelper(uint64_t diff, int32_t index,
+ uint64_t* cues_size) {
+ CuePoint* const cue_point = cues_.GetCueByIndex(index);
+ if (cue_point == NULL)
+ return;
+ const uint64_t old_cue_point_size = cue_point->Size();
+ const uint64_t cluster_pos = cue_point->cluster_pos() + diff;
+ cue_point->set_cluster_pos(cluster_pos); // update the new cluster position
+ // New size of the cue is computed as follows
+ // Let a = current sum of size of all CuePoints
+ // Let b = Increase in Cue Point's size due to this iteration
+ // Let c = Increase in size of Cues Element's length due to this iteration
+ // (This is computed as CodedSize(a + b) - CodedSize(a))
+ // Let d = b + c. Now d is the |diff| passed to the next recursive call.
+ // Let e = a + b. Now e is the |cues_size| passed to the next recursive
+ // call.
+ const uint64_t cue_point_size_diff = cue_point->Size() - old_cue_point_size;
+ const uint64_t cue_size_diff =
+ GetCodedUIntSize(*cues_size + cue_point_size_diff) -
+ GetCodedUIntSize(*cues_size);
+ *cues_size += cue_point_size_diff;
+ diff = cue_size_diff + cue_point_size_diff;
+ if (diff > 0) {
+ for (int32_t i = 0; i < cues_.cue_entries_size(); ++i) {
+ MoveCuesBeforeClustersHelper(diff, i, cues_size);
+ }
+ }
+}
+
+void Segment::MoveCuesBeforeClusters() {
+ const uint64_t current_cue_size = cues_.Size();
+ uint64_t cue_size = 0;
+ for (int32_t i = 0; i < cues_.cue_entries_size(); ++i)
+ cue_size += cues_.GetCueByIndex(i)->Size();
+ for (int32_t i = 0; i < cues_.cue_entries_size(); ++i)
+ MoveCuesBeforeClustersHelper(current_cue_size, i, &cue_size);
+
+ // Adjust the Seek Entry to reflect the change in position
+ // of Cluster and Cues
+ int32_t cluster_index = 0;
+ int32_t cues_index = 0;
+ for (int32_t i = 0; i < SeekHead::kSeekEntryCount; ++i) {
+ if (seek_head_.GetId(i) == libwebm::kMkvCluster)
+ cluster_index = i;
+ if (seek_head_.GetId(i) == libwebm::kMkvCues)
+ cues_index = i;
+ }
+ seek_head_.SetSeekEntry(cues_index, libwebm::kMkvCues,
+ seek_head_.GetPosition(cluster_index));
+ seek_head_.SetSeekEntry(cluster_index, libwebm::kMkvCluster,
+ cues_.Size() + seek_head_.GetPosition(cues_index));
+}
+
+bool Segment::Init(IMkvWriter* ptr_writer) {
+ if (!ptr_writer) {
+ return false;
+ }
+ writer_cluster_ = ptr_writer;
+ writer_cues_ = ptr_writer;
+ writer_header_ = ptr_writer;
+ memset(&track_frames_written_, 0,
+ sizeof(track_frames_written_[0]) * kMaxTrackNumber);
+ memset(&last_track_timestamp_, 0,
+ sizeof(last_track_timestamp_[0]) * kMaxTrackNumber);
+ return segment_info_.Init();
+}
+
+bool Segment::CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader,
+ IMkvWriter* writer) {
+ if (!writer->Seekable() || chunking_)
+ return false;
+ const int64_t cluster_offset =
+ cluster_list_[0]->size_position() - GetUIntSize(libwebm::kMkvCluster);
+
+ // Copy the headers.
+ if (!ChunkedCopy(reader, writer, 0, cluster_offset))
+ return false;
+
+ // Recompute cue positions and seek entries.
+ MoveCuesBeforeClusters();
+
+ // Write cues and seek entries.
+ // TODO(vigneshv): As of now, it's safe to call seek_head_.Finalize() for the
+ // second time with a different writer object. But the name Finalize() doesn't
+ // indicate something we want to call more than once. So consider renaming it
+ // to write() or some such.
+ if (!cues_.Write(writer) || !seek_head_.Finalize(writer))
+ return false;
+
+ // Copy the Clusters.
+ if (!ChunkedCopy(reader, writer, cluster_offset,
+ cluster_end_offset_ - cluster_offset))
+ return false;
+
+ // Update the Segment size in case the Cues size has changed.
+ const int64_t pos = writer->Position();
+ const int64_t segment_size = writer->Position() - payload_pos_;
+ if (writer->Position(size_position_) ||
+ WriteUIntSize(writer, segment_size, 8) || writer->Position(pos))
+ return false;
+ return true;
+}
+
+bool Segment::Finalize() {
+ if (WriteFramesAll() < 0)
+ return false;
+
+ // In kLive mode, call Cluster::Finalize only if |accurate_cluster_duration_|
+ // is set. In all other modes, always call Cluster::Finalize.
+ if ((mode_ == kLive ? accurate_cluster_duration_ : true) &&
+ cluster_list_size_ > 0) {
+ // Update last cluster's size
+ Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1];
+
+ // For the last frame of the last Cluster, we don't write it as a BlockGroup
+ // with Duration unless the frame itself has duration set explicitly.
+ if (!old_cluster || !old_cluster->Finalize(false, 0))
+ return false;
+ }
+
+ if (mode_ == kFile) {
+ if (chunking_ && chunk_writer_cluster_) {
+ chunk_writer_cluster_->Close();
+ chunk_count_++;
+ }
+
+ double duration =
+ (static_cast<double>(last_timestamp_) + last_block_duration_) /
+ segment_info_.timecode_scale();
+ if (duration_ > 0.0) {
+ duration = duration_;
+ } else {
+ if (last_block_duration_ == 0 && estimate_file_duration_) {
+ const int num_tracks = static_cast<int>(tracks_.track_entries_size());
+ for (int i = 0; i < num_tracks; ++i) {
+ if (track_frames_written_[i] < 2)
+ continue;
+
+ // Estimate the duration for the last block of a Track.
+ const double nano_per_frame =
+ static_cast<double>(last_track_timestamp_[i]) /
+ (track_frames_written_[i] - 1);
+ const double track_duration =
+ (last_track_timestamp_[i] + nano_per_frame) /
+ segment_info_.timecode_scale();
+ if (track_duration > duration)
+ duration = track_duration;
+ }
+ }
+ }
+ segment_info_.set_duration(duration);
+ if (!segment_info_.Finalize(writer_header_))
+ return false;
+
+ if (output_cues_)
+ if (!seek_head_.AddSeekEntry(libwebm::kMkvCues, MaxOffset()))
+ return false;
+
+ if (chunking_) {
+ if (!chunk_writer_cues_)
+ return false;
+
+ char* name = NULL;
+ if (!UpdateChunkName("cues", &name))
+ return false;
+
+ const bool cues_open = chunk_writer_cues_->Open(name);
+ delete[] name;
+ if (!cues_open)
+ return false;
+ }
+
+ cluster_end_offset_ = writer_cluster_->Position();
+
+ // Write the seek headers and cues
+ if (output_cues_)
+ if (!cues_.Write(writer_cues_))
+ return false;
+
+ if (!seek_head_.Finalize(writer_header_))
+ return false;
+
+ if (writer_header_->Seekable()) {
+ if (size_position_ == -1)
+ return false;
+
+ const int64_t segment_size = MaxOffset();
+ if (segment_size < 1)
+ return false;
+
+ const int64_t pos = writer_header_->Position();
+ UpdateDocTypeVersion();
+ if (doc_type_version_ != doc_type_version_written_) {
+ if (writer_header_->Position(0))
+ return false;
+
+ const char* const doc_type =
+ DocTypeIsWebm() ? kDocTypeWebm : kDocTypeMatroska;
+ if (!WriteEbmlHeader(writer_header_, doc_type_version_, doc_type))
+ return false;
+ if (writer_header_->Position() != ebml_header_size_)
+ return false;
+
+ doc_type_version_written_ = doc_type_version_;
+ }
+
+ if (writer_header_->Position(size_position_))
+ return false;
+
+ if (WriteUIntSize(writer_header_, segment_size, 8))
+ return false;
+
+ if (writer_header_->Position(pos))
+ return false;
+ }
+
+ if (chunking_) {
+ // Do not close any writers until the segment size has been written,
+ // otherwise the size may be off.
+ if (!chunk_writer_cues_ || !chunk_writer_header_)
+ return false;
+
+ chunk_writer_cues_->Close();
+ chunk_writer_header_->Close();
+ }
+ }
+
+ return true;
+}
+
+Track* Segment::AddTrack(int32_t number) {
+ Track* const track = new (std::nothrow) Track(&seed_); // NOLINT
+
+ if (!track)
+ return NULL;
+
+ if (!tracks_.AddTrack(track, number)) {
+ delete track;
+ return NULL;
+ }
+
+ return track;
+}
+
+Chapter* Segment::AddChapter() { return chapters_.AddChapter(&seed_); }
+
+Tag* Segment::AddTag() { return tags_.AddTag(); }
+
+uint64_t Segment::AddVideoTrack(int32_t width, int32_t height, int32_t number) {
+ VideoTrack* const track = new (std::nothrow) VideoTrack(&seed_); // NOLINT
+ if (!track)
+ return 0;
+
+ track->set_type(Tracks::kVideo);
+ track->set_codec_id(Tracks::kVp8CodecId);
+ track->set_width(width);
+ track->set_height(height);
+
+ if (!tracks_.AddTrack(track, number)) {
+ delete track;
+ return 0;
+ }
+ has_video_ = true;
+
+ return track->number();
+}
+
+bool Segment::AddCuePoint(uint64_t timestamp, uint64_t track) {
+ if (cluster_list_size_ < 1)
+ return false;
+
+ const Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
+ if (!cluster)
+ return false;
+
+ CuePoint* const cue = new (std::nothrow) CuePoint(); // NOLINT
+ if (!cue)
+ return false;
+
+ cue->set_time(timestamp / segment_info_.timecode_scale());
+ cue->set_block_number(cluster->blocks_added());
+ cue->set_cluster_pos(cluster->position_for_cues());
+ cue->set_track(track);
+ if (!cues_.AddCue(cue)) {
+ delete cue;
+ return false;
+ }
+
+ new_cuepoint_ = false;
+ return true;
+}
+
+uint64_t Segment::AddAudioTrack(int32_t sample_rate, int32_t channels,
+ int32_t number) {
+ AudioTrack* const track = new (std::nothrow) AudioTrack(&seed_); // NOLINT
+ if (!track)
+ return 0;
+
+ track->set_type(Tracks::kAudio);
+ track->set_codec_id(Tracks::kVorbisCodecId);
+ track->set_sample_rate(sample_rate);
+ track->set_channels(channels);
+
+ if (!tracks_.AddTrack(track, number)) {
+ delete track;
+ return 0;
+ }
+
+ return track->number();
+}
+
+bool Segment::AddFrame(const uint8_t* data, uint64_t length,
+ uint64_t track_number, uint64_t timestamp, bool is_key) {
+ if (!data)
+ return false;
+
+ Frame frame;
+ if (!frame.Init(data, length))
+ return false;
+ frame.set_track_number(track_number);
+ frame.set_timestamp(timestamp);
+ frame.set_is_key(is_key);
+ return AddGenericFrame(&frame);
+}
+
+bool Segment::AddFrameWithAdditional(const uint8_t* data, uint64_t length,
+ const uint8_t* additional,
+ uint64_t additional_length,
+ uint64_t add_id, uint64_t track_number,
+ uint64_t timestamp, bool is_key) {
+ if (!data || !additional)
+ return false;
+
+ Frame frame;
+ if (!frame.Init(data, length) ||
+ !frame.AddAdditionalData(additional, additional_length, add_id)) {
+ return false;
+ }
+ frame.set_track_number(track_number);
+ frame.set_timestamp(timestamp);
+ frame.set_is_key(is_key);
+ return AddGenericFrame(&frame);
+}
+
+bool Segment::AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length,
+ int64_t discard_padding,
+ uint64_t track_number,
+ uint64_t timestamp, bool is_key) {
+ if (!data)
+ return false;
+
+ Frame frame;
+ if (!frame.Init(data, length))
+ return false;
+ frame.set_discard_padding(discard_padding);
+ frame.set_track_number(track_number);
+ frame.set_timestamp(timestamp);
+ frame.set_is_key(is_key);
+ return AddGenericFrame(&frame);
+}
+
+bool Segment::AddMetadata(const uint8_t* data, uint64_t length,
+ uint64_t track_number, uint64_t timestamp_ns,
+ uint64_t duration_ns) {
+ if (!data)
+ return false;
+
+ Frame frame;
+ if (!frame.Init(data, length))
+ return false;
+ frame.set_track_number(track_number);
+ frame.set_timestamp(timestamp_ns);
+ frame.set_duration(duration_ns);
+ frame.set_is_key(true); // All metadata blocks are keyframes.
+ return AddGenericFrame(&frame);
+}
+
+bool Segment::AddGenericFrame(const Frame* frame) {
+ if (!frame)
+ return false;
+
+ if (!CheckHeaderInfo())
+ return false;
+
+ // Check for non-monotonically increasing timestamps.
+ if (frame->timestamp() < last_timestamp_)
+ return false;
+
+ // Check if the track number is valid.
+ if (!tracks_.GetTrackByNumber(frame->track_number()))
+ return false;
+
+ if (frame->discard_padding() != 0)
+ doc_type_version_ = 4;
+
+ if (cluster_list_size_ > 0) {
+ const uint64_t timecode_scale = segment_info_.timecode_scale();
+ const uint64_t frame_timecode = frame->timestamp() / timecode_scale;
+
+ const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1];
+ const uint64_t last_cluster_timecode = last_cluster->timecode();
+
+ const uint64_t rel_timecode = frame_timecode - last_cluster_timecode;
+ if (rel_timecode > kMaxBlockTimecode) {
+ force_new_cluster_ = true;
+ }
+ }
+
+ // If the segment has a video track hold onto audio frames to make sure the
+ // audio that is associated with the start time of a video key-frame is
+ // muxed into the same cluster.
+ if (has_video_ && tracks_.TrackIsAudio(frame->track_number()) &&
+ !force_new_cluster_) {
+ Frame* const new_frame = new (std::nothrow) Frame();
+ if (!new_frame || !new_frame->CopyFrom(*frame)) {
+ delete new_frame;
+ return false;
+ }
+ if (!QueueFrame(new_frame)) {
+ delete new_frame;
+ return false;
+ }
+ track_frames_written_[frame->track_number() - 1]++;
+ return true;
+ }
+
+ if (!DoNewClusterProcessing(frame->track_number(), frame->timestamp(),
+ frame->is_key())) {
+ return false;
+ }
+
+ if (cluster_list_size_ < 1)
+ return false;
+
+ Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
+ if (!cluster)
+ return false;
+
+ // If the Frame is not a SimpleBlock, then set the reference_block_timestamp
+ // if it is not set already.
+ bool frame_created = false;
+ if (!frame->CanBeSimpleBlock() && !frame->is_key() &&
+ !frame->reference_block_timestamp_set()) {
+ Frame* const new_frame = new (std::nothrow) Frame();
+ if (!new_frame || !new_frame->CopyFrom(*frame)) {
+ delete new_frame;
+ return false;
+ }
+ new_frame->set_reference_block_timestamp(
+ last_track_timestamp_[frame->track_number() - 1]);
+ frame = new_frame;
+ frame_created = true;
+ }
+
+ if (!cluster->AddFrame(frame))
+ return false;
+
+ if (new_cuepoint_ && cues_track_ == frame->track_number()) {
+ if (!AddCuePoint(frame->timestamp(), cues_track_))
+ return false;
+ }
+
+ last_timestamp_ = frame->timestamp();
+ last_track_timestamp_[frame->track_number() - 1] = frame->timestamp();
+ last_block_duration_ = frame->duration();
+ track_frames_written_[frame->track_number() - 1]++;
+
+ if (frame_created)
+ delete frame;
+ return true;
+}
+
+void Segment::OutputCues(bool output_cues) { output_cues_ = output_cues; }
+
+void Segment::AccurateClusterDuration(bool accurate_cluster_duration) {
+ accurate_cluster_duration_ = accurate_cluster_duration;
+}
+
+void Segment::UseFixedSizeClusterTimecode(bool fixed_size_cluster_timecode) {
+ fixed_size_cluster_timecode_ = fixed_size_cluster_timecode;
+}
+
+bool Segment::SetChunking(bool chunking, const char* filename) {
+ if (chunk_count_ > 0)
+ return false;
+
+ if (chunking) {
+ if (!filename)
+ return false;
+
+ // Check if we are being set to what is already set.
+ if (chunking_ && !strcmp(filename, chunking_base_name_))
+ return true;
+
+ const size_t name_length = strlen(filename) + 1;
+ char* const temp = new (std::nothrow) char[name_length]; // NOLINT
+ if (!temp)
+ return false;
+
+#ifdef _MSC_VER
+ strcpy_s(temp, name_length, filename);
+#else
+ strcpy(temp, filename);
+#endif
+
+ delete[] chunking_base_name_;
+ chunking_base_name_ = temp;
+
+ if (!UpdateChunkName("chk", &chunk_name_))
+ return false;
+
+ if (!chunk_writer_cluster_) {
+ chunk_writer_cluster_ = new (std::nothrow) MkvWriter(); // NOLINT
+ if (!chunk_writer_cluster_)
+ return false;
+ }
+
+ if (!chunk_writer_cues_) {
+ chunk_writer_cues_ = new (std::nothrow) MkvWriter(); // NOLINT
+ if (!chunk_writer_cues_)
+ return false;
+ }
+
+ if (!chunk_writer_header_) {
+ chunk_writer_header_ = new (std::nothrow) MkvWriter(); // NOLINT
+ if (!chunk_writer_header_)
+ return false;
+ }
+
+ if (!chunk_writer_cluster_->Open(chunk_name_))
+ return false;
+
+ const size_t header_length = strlen(filename) + strlen(".hdr") + 1;
+ char* const header = new (std::nothrow) char[header_length]; // NOLINT
+ if (!header)
+ return false;
+
+#ifdef _MSC_VER
+ strcpy_s(header, header_length - strlen(".hdr"), chunking_base_name_);
+ strcat_s(header, header_length, ".hdr");
+#else
+ strcpy(header, chunking_base_name_);
+ strcat(header, ".hdr");
+#endif
+ if (!chunk_writer_header_->Open(header)) {
+ delete[] header;
+ return false;
+ }
+
+ writer_cluster_ = chunk_writer_cluster_;
+ writer_cues_ = chunk_writer_cues_;
+ writer_header_ = chunk_writer_header_;
+
+ delete[] header;
+ }
+
+ chunking_ = chunking;
+
+ return true;
+}
+
+bool Segment::CuesTrack(uint64_t track_number) {
+ const Track* const track = GetTrackByNumber(track_number);
+ if (!track)
+ return false;
+
+ cues_track_ = track_number;
+ return true;
+}
+
+void Segment::ForceNewClusterOnNextFrame() { force_new_cluster_ = true; }
+
+Track* Segment::GetTrackByNumber(uint64_t track_number) const {
+ return tracks_.GetTrackByNumber(track_number);
+}
+
+bool Segment::WriteSegmentHeader() {
+ UpdateDocTypeVersion();
+
+ const char* const doc_type =
+ DocTypeIsWebm() ? kDocTypeWebm : kDocTypeMatroska;
+ if (!WriteEbmlHeader(writer_header_, doc_type_version_, doc_type))
+ return false;
+ doc_type_version_written_ = doc_type_version_;
+ ebml_header_size_ = static_cast<int32_t>(writer_header_->Position());
+
+ // Write "unknown" (-1) as segment size value. If mode is kFile, Segment
+ // will write over duration when the file is finalized.
+ if (WriteID(writer_header_, libwebm::kMkvSegment))
+ return false;
+
+ // Save for later.
+ size_position_ = writer_header_->Position();
+
+ // Write "unknown" (EBML coded -1) as segment size value. We need to write 8
+ // bytes because if we are going to overwrite the segment size later we do
+ // not know how big our segment will be.
+ if (SerializeInt(writer_header_, kEbmlUnknownValue, 8))
+ return false;
+
+ payload_pos_ = writer_header_->Position();
+
+ if (mode_ == kFile && writer_header_->Seekable()) {
+ // Set the duration > 0.0 so SegmentInfo will write out the duration. When
+ // the muxer is done writing we will set the correct duration and have
+ // SegmentInfo upadte it.
+ segment_info_.set_duration(1.0);
+
+ if (!seek_head_.Write(writer_header_))
+ return false;
+ }
+
+ if (!seek_head_.AddSeekEntry(libwebm::kMkvInfo, MaxOffset()))
+ return false;
+ if (!segment_info_.Write(writer_header_))
+ return false;
+
+ if (!seek_head_.AddSeekEntry(libwebm::kMkvTracks, MaxOffset()))
+ return false;
+ if (!tracks_.Write(writer_header_))
+ return false;
+
+ if (chapters_.Count() > 0) {
+ if (!seek_head_.AddSeekEntry(libwebm::kMkvChapters, MaxOffset()))
+ return false;
+ if (!chapters_.Write(writer_header_))
+ return false;
+ }
+
+ if (tags_.Count() > 0) {
+ if (!seek_head_.AddSeekEntry(libwebm::kMkvTags, MaxOffset()))
+ return false;
+ if (!tags_.Write(writer_header_))
+ return false;
+ }
+
+ if (chunking_ && (mode_ == kLive || !writer_header_->Seekable())) {
+ if (!chunk_writer_header_)
+ return false;
+
+ chunk_writer_header_->Close();
+ }
+
+ header_written_ = true;
+
+ return true;
+}
+
+// Here we are testing whether to create a new cluster, given a frame
+// having time frame_timestamp_ns.
+//
+int Segment::TestFrame(uint64_t track_number, uint64_t frame_timestamp_ns,
+ bool is_key) const {
+ if (force_new_cluster_)
+ return 1;
+
+ // If no clusters have been created yet, then create a new cluster
+ // and write this frame immediately, in the new cluster. This path
+ // should only be followed once, the first time we attempt to write
+ // a frame.
+
+ if (cluster_list_size_ <= 0)
+ return 1;
+
+ // There exists at least one cluster. We must compare the frame to
+ // the last cluster, in order to determine whether the frame is
+ // written to the existing cluster, or that a new cluster should be
+ // created.
+
+ const uint64_t timecode_scale = segment_info_.timecode_scale();
+ const uint64_t frame_timecode = frame_timestamp_ns / timecode_scale;
+
+ const Cluster* const last_cluster = cluster_list_[cluster_list_size_ - 1];
+ const uint64_t last_cluster_timecode = last_cluster->timecode();
+
+ // For completeness we test for the case when the frame's timecode
+ // is less than the cluster's timecode. Although in principle that
+ // is allowed, this muxer doesn't actually write clusters like that,
+ // so this indicates a bug somewhere in our algorithm.
+
+ if (frame_timecode < last_cluster_timecode) // should never happen
+ return -1;
+
+ // If the frame has a timestamp significantly larger than the last
+ // cluster (in Matroska, cluster-relative timestamps are serialized
+ // using a 16-bit signed integer), then we cannot write this frame
+ // to that cluster, and so we must create a new cluster.
+
+ const int64_t delta_timecode = frame_timecode - last_cluster_timecode;
+
+ if (delta_timecode > kMaxBlockTimecode)
+ return 2;
+
+ // We decide to create a new cluster when we have a video keyframe.
+ // This will flush queued (audio) frames, and write the keyframe
+ // immediately, in the newly-created cluster.
+
+ if (is_key && tracks_.TrackIsVideo(track_number))
+ return 1;
+
+ // Create a new cluster if we have accumulated too many frames
+ // already, where "too many" is defined as "the total time of frames
+ // in the cluster exceeds a threshold".
+
+ const uint64_t delta_ns = delta_timecode * timecode_scale;
+
+ if (max_cluster_duration_ > 0 && delta_ns >= max_cluster_duration_)
+ return 1;
+
+ // This is similar to the case above, with the difference that a new
+ // cluster is created when the size of the current cluster exceeds a
+ // threshold.
+
+ const uint64_t cluster_size = last_cluster->payload_size();
+
+ if (max_cluster_size_ > 0 && cluster_size >= max_cluster_size_)
+ return 1;
+
+ // There's no need to create a new cluster, so emit this frame now.
+
+ return 0;
+}
+
+bool Segment::MakeNewCluster(uint64_t frame_timestamp_ns) {
+ const int32_t new_size = cluster_list_size_ + 1;
+
+ if (new_size > cluster_list_capacity_) {
+ // Add more clusters.
+ const int32_t new_capacity =
+ (cluster_list_capacity_ <= 0) ? 1 : cluster_list_capacity_ * 2;
+ Cluster** const clusters =
+ new (std::nothrow) Cluster*[new_capacity]; // NOLINT
+ if (!clusters)
+ return false;
+
+ for (int32_t i = 0; i < cluster_list_size_; ++i) {
+ clusters[i] = cluster_list_[i];
+ }
+
+ delete[] cluster_list_;
+
+ cluster_list_ = clusters;
+ cluster_list_capacity_ = new_capacity;
+ }
+
+ if (!WriteFramesLessThan(frame_timestamp_ns))
+ return false;
+
+ if (cluster_list_size_ > 0) {
+ // Update old cluster's size
+ Cluster* const old_cluster = cluster_list_[cluster_list_size_ - 1];
+
+ if (!old_cluster || !old_cluster->Finalize(true, frame_timestamp_ns))
+ return false;
+ }
+
+ if (output_cues_)
+ new_cuepoint_ = true;
+
+ if (chunking_ && cluster_list_size_ > 0) {
+ chunk_writer_cluster_->Close();
+ chunk_count_++;
+
+ if (!UpdateChunkName("chk", &chunk_name_))
+ return false;
+ if (!chunk_writer_cluster_->Open(chunk_name_))
+ return false;
+ }
+
+ const uint64_t timecode_scale = segment_info_.timecode_scale();
+ const uint64_t frame_timecode = frame_timestamp_ns / timecode_scale;
+
+ uint64_t cluster_timecode = frame_timecode;
+
+ if (frames_size_ > 0) {
+ const Frame* const f = frames_[0]; // earliest queued frame
+ const uint64_t ns = f->timestamp();
+ const uint64_t tc = ns / timecode_scale;
+
+ if (tc < cluster_timecode)
+ cluster_timecode = tc;
+ }
+
+ Cluster*& cluster = cluster_list_[cluster_list_size_];
+ const int64_t offset = MaxOffset();
+ cluster = new (std::nothrow)
+ Cluster(cluster_timecode, offset, segment_info_.timecode_scale(),
+ accurate_cluster_duration_, fixed_size_cluster_timecode_);
+ if (!cluster)
+ return false;
+
+ if (!cluster->Init(writer_cluster_))
+ return false;
+
+ cluster_list_size_ = new_size;
+ return true;
+}
+
+bool Segment::DoNewClusterProcessing(uint64_t track_number,
+ uint64_t frame_timestamp_ns, bool is_key) {
+ for (;;) {
+ // Based on the characteristics of the current frame and current
+ // cluster, decide whether to create a new cluster.
+ const int result = TestFrame(track_number, frame_timestamp_ns, is_key);
+ if (result < 0) // error
+ return false;
+
+ // Always set force_new_cluster_ to false after TestFrame.
+ force_new_cluster_ = false;
+
+ // A non-zero result means create a new cluster.
+ if (result > 0 && !MakeNewCluster(frame_timestamp_ns))
+ return false;
+
+ // Write queued (audio) frames.
+ const int frame_count = WriteFramesAll();
+ if (frame_count < 0) // error
+ return false;
+
+ // Write the current frame to the current cluster (if TestFrame
+ // returns 0) or to a newly created cluster (TestFrame returns 1).
+ if (result <= 1)
+ return true;
+
+ // TestFrame returned 2, which means there was a large time
+ // difference between the cluster and the frame itself. Do the
+ // test again, comparing the frame to the new cluster.
+ }
+}
+
+bool Segment::CheckHeaderInfo() {
+ if (!header_written_) {
+ if (!WriteSegmentHeader())
+ return false;
+
+ if (!seek_head_.AddSeekEntry(libwebm::kMkvCluster, MaxOffset()))
+ return false;
+
+ if (output_cues_ && cues_track_ == 0) {
+ // Check for a video track
+ for (uint32_t i = 0; i < tracks_.track_entries_size(); ++i) {
+ const Track* const track = tracks_.GetTrackByIndex(i);
+ if (!track)
+ return false;
+
+ if (tracks_.TrackIsVideo(track->number())) {
+ cues_track_ = track->number();
+ break;
+ }
+ }
+
+ // Set first track found
+ if (cues_track_ == 0) {
+ const Track* const track = tracks_.GetTrackByIndex(0);
+ if (!track)
+ return false;
+
+ cues_track_ = track->number();
+ }
+ }
+ }
+ return true;
+}
+
+void Segment::UpdateDocTypeVersion() {
+ for (uint32_t index = 0; index < tracks_.track_entries_size(); ++index) {
+ const Track* track = tracks_.GetTrackByIndex(index);
+ if (track == NULL)
+ break;
+ if ((track->codec_delay() || track->seek_pre_roll()) &&
+ doc_type_version_ < 4) {
+ doc_type_version_ = 4;
+ break;
+ }
+ }
+}
+
+bool Segment::UpdateChunkName(const char* ext, char** name) const {
+ if (!name || !ext)
+ return false;
+
+ char ext_chk[64];
+#ifdef _MSC_VER
+ sprintf_s(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext);
+#else
+ snprintf(ext_chk, sizeof(ext_chk), "_%06d.%s", chunk_count_, ext);
+#endif
+
+ const size_t length = strlen(chunking_base_name_) + strlen(ext_chk) + 1;
+ char* const str = new (std::nothrow) char[length]; // NOLINT
+ if (!str)
+ return false;
+
+#ifdef _MSC_VER
+ strcpy_s(str, length - strlen(ext_chk), chunking_base_name_);
+ strcat_s(str, length, ext_chk);
+#else
+ strcpy(str, chunking_base_name_);
+ strcat(str, ext_chk);
+#endif
+
+ delete[] * name;
+ *name = str;
+
+ return true;
+}
+
+int64_t Segment::MaxOffset() {
+ if (!writer_header_)
+ return -1;
+
+ int64_t offset = writer_header_->Position() - payload_pos_;
+
+ if (chunking_) {
+ for (int32_t i = 0; i < cluster_list_size_; ++i) {
+ Cluster* const cluster = cluster_list_[i];
+ offset += cluster->Size();
+ }
+
+ if (writer_cues_)
+ offset += writer_cues_->Position();
+ }
+
+ return offset;
+}
+
+bool Segment::QueueFrame(Frame* frame) {
+ const int32_t new_size = frames_size_ + 1;
+
+ if (new_size > frames_capacity_) {
+ // Add more frames.
+ const int32_t new_capacity = (!frames_capacity_) ? 2 : frames_capacity_ * 2;
+
+ if (new_capacity < 1)
+ return false;
+
+ Frame** const frames = new (std::nothrow) Frame*[new_capacity]; // NOLINT
+ if (!frames)
+ return false;
+
+ for (int32_t i = 0; i < frames_size_; ++i) {
+ frames[i] = frames_[i];
+ }
+
+ delete[] frames_;
+ frames_ = frames;
+ frames_capacity_ = new_capacity;
+ }
+
+ frames_[frames_size_++] = frame;
+
+ return true;
+}
+
+int Segment::WriteFramesAll() {
+ if (frames_ == NULL)
+ return 0;
+
+ if (cluster_list_size_ < 1)
+ return -1;
+
+ Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
+
+ if (!cluster)
+ return -1;
+
+ for (int32_t i = 0; i < frames_size_; ++i) {
+ Frame*& frame = frames_[i];
+ // TODO(jzern/vigneshv): using Segment::AddGenericFrame here would limit the
+ // places where |doc_type_version_| needs to be updated.
+ if (frame->discard_padding() != 0)
+ doc_type_version_ = 4;
+ if (!cluster->AddFrame(frame))
+ return -1;
+
+ if (new_cuepoint_ && cues_track_ == frame->track_number()) {
+ if (!AddCuePoint(frame->timestamp(), cues_track_))
+ return -1;
+ }
+
+ if (frame->timestamp() > last_timestamp_) {
+ last_timestamp_ = frame->timestamp();
+ last_track_timestamp_[frame->track_number() - 1] = frame->timestamp();
+ }
+
+ delete frame;
+ frame = NULL;
+ }
+
+ const int result = frames_size_;
+ frames_size_ = 0;
+
+ return result;
+}
+
+bool Segment::WriteFramesLessThan(uint64_t timestamp) {
+ // Check |cluster_list_size_| to see if this is the first cluster. If it is
+ // the first cluster the audio frames that are less than the first video
+ // timesatmp will be written in a later step.
+ if (frames_size_ > 0 && cluster_list_size_ > 0) {
+ if (!frames_)
+ return false;
+
+ Cluster* const cluster = cluster_list_[cluster_list_size_ - 1];
+ if (!cluster)
+ return false;
+
+ int32_t shift_left = 0;
+
+ // TODO(fgalligan): Change this to use the durations of frames instead of
+ // the next frame's start time if the duration is accurate.
+ for (int32_t i = 1; i < frames_size_; ++i) {
+ const Frame* const frame_curr = frames_[i];
+
+ if (frame_curr->timestamp() > timestamp)
+ break;
+
+ const Frame* const frame_prev = frames_[i - 1];
+ if (frame_prev->discard_padding() != 0)
+ doc_type_version_ = 4;
+ if (!cluster->AddFrame(frame_prev))
+ return false;
+
+ if (new_cuepoint_ && cues_track_ == frame_prev->track_number()) {
+ if (!AddCuePoint(frame_prev->timestamp(), cues_track_))
+ return false;
+ }
+
+ ++shift_left;
+ if (frame_prev->timestamp() > last_timestamp_) {
+ last_timestamp_ = frame_prev->timestamp();
+ last_track_timestamp_[frame_prev->track_number() - 1] =
+ frame_prev->timestamp();
+ }
+
+ delete frame_prev;
+ }
+
+ if (shift_left > 0) {
+ if (shift_left >= frames_size_)
+ return false;
+
+ const int32_t new_frames_size = frames_size_ - shift_left;
+ for (int32_t i = 0; i < new_frames_size; ++i) {
+ frames_[i] = frames_[i + shift_left];
+ }
+
+ frames_size_ = new_frames_size;
+ }
+ }
+
+ return true;
+}
+
+bool Segment::DocTypeIsWebm() const {
+ const int kNumCodecIds = 9;
+
+ // TODO(vigneshv): Tweak .clang-format.
+ const char* kWebmCodecIds[kNumCodecIds] = {
+ Tracks::kOpusCodecId, Tracks::kVorbisCodecId,
+ Tracks::kAv1CodecId, Tracks::kVp8CodecId,
+ Tracks::kVp9CodecId, Tracks::kWebVttCaptionsId,
+ Tracks::kWebVttDescriptionsId, Tracks::kWebVttMetadataId,
+ Tracks::kWebVttSubtitlesId};
+
+ const int num_tracks = static_cast<int>(tracks_.track_entries_size());
+ for (int track_index = 0; track_index < num_tracks; ++track_index) {
+ const Track* const track = tracks_.GetTrackByIndex(track_index);
+ const std::string codec_id = track->codec_id();
+
+ bool id_is_webm = false;
+ for (int id_index = 0; id_index < kNumCodecIds; ++id_index) {
+ if (codec_id == kWebmCodecIds[id_index]) {
+ id_is_webm = true;
+ break;
+ }
+ }
+
+ if (!id_is_webm)
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace mkvmuxer
diff --git a/mkvmuxer/mkvmuxer.h b/mkvmuxer/mkvmuxer.h
new file mode 100644
index 0000000..f2db377
--- /dev/null
+++ b/mkvmuxer/mkvmuxer.h
@@ -0,0 +1,1924 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef MKVMUXER_MKVMUXER_H_
+#define MKVMUXER_MKVMUXER_H_
+
+#include <stdint.h>
+
+#include <cstddef>
+#include <list>
+#include <map>
+
+#include "common/webmids.h"
+#include "mkvmuxer/mkvmuxertypes.h"
+
+// For a description of the WebM elements see
+// http://www.webmproject.org/code/specs/container/.
+
+namespace mkvparser {
+class IMkvReader;
+} // namespace mkvparser
+
+namespace mkvmuxer {
+
+class MkvWriter;
+class Segment;
+
+const uint64_t kMaxTrackNumber = 126;
+
+///////////////////////////////////////////////////////////////
+// Interface used by the mkvmuxer to write out the Mkv data.
+class IMkvWriter {
+ public:
+ // Writes out |len| bytes of |buf|. Returns 0 on success.
+ virtual int32 Write(const void* buf, uint32 len) = 0;
+
+ // Returns the offset of the output position from the beginning of the
+ // output.
+ virtual int64 Position() const = 0;
+
+ // Set the current File position. Returns 0 on success.
+ virtual int32 Position(int64 position) = 0;
+
+ // Returns true if the writer is seekable.
+ virtual bool Seekable() const = 0;
+
+ // Element start notification. Called whenever an element identifier is about
+ // to be written to the stream. |element_id| is the element identifier, and
+ // |position| is the location in the WebM stream where the first octet of the
+ // element identifier will be written.
+ // Note: the |MkvId| enumeration in webmids.hpp defines element values.
+ virtual void ElementStartNotify(uint64 element_id, int64 position) = 0;
+
+ protected:
+ IMkvWriter();
+ virtual ~IMkvWriter();
+
+ private:
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(IMkvWriter);
+};
+
+// Writes out the EBML header for a WebM file, but allows caller to specify
+// DocType. This function must be called before any other libwebm writing
+// functions are called.
+bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version,
+ const char* const doc_type);
+
+// Writes out the EBML header for a WebM file. This function must be called
+// before any other libwebm writing functions are called.
+bool WriteEbmlHeader(IMkvWriter* writer, uint64_t doc_type_version);
+
+// Deprecated. Writes out EBML header with doc_type_version as
+// kDefaultDocTypeVersion. Exists for backward compatibility.
+bool WriteEbmlHeader(IMkvWriter* writer);
+
+// Copies in Chunk from source to destination between the given byte positions
+bool ChunkedCopy(mkvparser::IMkvReader* source, IMkvWriter* dst, int64_t start,
+ int64_t size);
+
+///////////////////////////////////////////////////////////////
+// Class to hold data the will be written to a block.
+class Frame {
+ public:
+ Frame();
+ ~Frame();
+
+ // Sets this frame's contents based on |frame|. Returns true on success. On
+ // failure, this frame's existing contents may be lost.
+ bool CopyFrom(const Frame& frame);
+
+ // Copies |frame| data into |frame_|. Returns true on success.
+ bool Init(const uint8_t* frame, uint64_t length);
+
+ // Copies |additional| data into |additional_|. Returns true on success.
+ bool AddAdditionalData(const uint8_t* additional, uint64_t length,
+ uint64_t add_id);
+
+ // Returns true if the frame has valid parameters.
+ bool IsValid() const;
+
+ // Returns true if the frame can be written as a SimpleBlock based on current
+ // parameters.
+ bool CanBeSimpleBlock() const;
+
+ uint64_t add_id() const { return add_id_; }
+ const uint8_t* additional() const { return additional_; }
+ uint64_t additional_length() const { return additional_length_; }
+ void set_duration(uint64_t duration);
+ uint64_t duration() const { return duration_; }
+ bool duration_set() const { return duration_set_; }
+ const uint8_t* frame() const { return frame_; }
+ void set_is_key(bool key) { is_key_ = key; }
+ bool is_key() const { return is_key_; }
+ uint64_t length() const { return length_; }
+ void set_track_number(uint64_t track_number) { track_number_ = track_number; }
+ uint64_t track_number() const { return track_number_; }
+ void set_timestamp(uint64_t timestamp) { timestamp_ = timestamp; }
+ uint64_t timestamp() const { return timestamp_; }
+ void set_discard_padding(int64_t discard_padding) {
+ discard_padding_ = discard_padding;
+ }
+ int64_t discard_padding() const { return discard_padding_; }
+ void set_reference_block_timestamp(int64_t reference_block_timestamp);
+ int64_t reference_block_timestamp() const {
+ return reference_block_timestamp_;
+ }
+ bool reference_block_timestamp_set() const {
+ return reference_block_timestamp_set_;
+ }
+
+ private:
+ // Id of the Additional data.
+ uint64_t add_id_;
+
+ // Pointer to additional data. Owned by this class.
+ uint8_t* additional_;
+
+ // Length of the additional data.
+ uint64_t additional_length_;
+
+ // Duration of the frame in nanoseconds.
+ uint64_t duration_;
+
+ // Flag indicating that |duration_| has been set. Setting duration causes the
+ // frame to be written out as a Block with BlockDuration instead of as a
+ // SimpleBlock.
+ bool duration_set_;
+
+ // Pointer to the data. Owned by this class.
+ uint8_t* frame_;
+
+ // Flag telling if the data should set the key flag of a block.
+ bool is_key_;
+
+ // Length of the data.
+ uint64_t length_;
+
+ // Mkv track number the data is associated with.
+ uint64_t track_number_;
+
+ // Timestamp of the data in nanoseconds.
+ uint64_t timestamp_;
+
+ // Discard padding for the frame.
+ int64_t discard_padding_;
+
+ // Reference block timestamp.
+ int64_t reference_block_timestamp_;
+
+ // Flag indicating if |reference_block_timestamp_| has been set.
+ bool reference_block_timestamp_set_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Frame);
+};
+
+///////////////////////////////////////////////////////////////
+// Class to hold one cue point in a Cues element.
+class CuePoint {
+ public:
+ CuePoint();
+ ~CuePoint();
+
+ // Returns the size in bytes for the entire CuePoint element.
+ uint64_t Size() const;
+
+ // Output the CuePoint element to the writer. Returns true on success.
+ bool Write(IMkvWriter* writer) const;
+
+ void set_time(uint64_t time) { time_ = time; }
+ uint64_t time() const { return time_; }
+ void set_track(uint64_t track) { track_ = track; }
+ uint64_t track() const { return track_; }
+ void set_cluster_pos(uint64_t cluster_pos) { cluster_pos_ = cluster_pos; }
+ uint64_t cluster_pos() const { return cluster_pos_; }
+ void set_block_number(uint64_t block_number) { block_number_ = block_number; }
+ uint64_t block_number() const { return block_number_; }
+ void set_output_block_number(bool output_block_number) {
+ output_block_number_ = output_block_number;
+ }
+ bool output_block_number() const { return output_block_number_; }
+
+ private:
+ // Returns the size in bytes for the payload of the CuePoint element.
+ uint64_t PayloadSize() const;
+
+ // Absolute timecode according to the segment time base.
+ uint64_t time_;
+
+ // The Track element associated with the CuePoint.
+ uint64_t track_;
+
+ // The position of the Cluster containing the Block.
+ uint64_t cluster_pos_;
+
+ // Number of the Block within the Cluster, starting from 1.
+ uint64_t block_number_;
+
+ // If true the muxer will write out the block number for the cue if the
+ // block number is different than the default of 1. Default is set to true.
+ bool output_block_number_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(CuePoint);
+};
+
+///////////////////////////////////////////////////////////////
+// Cues element.
+class Cues {
+ public:
+ Cues();
+ ~Cues();
+
+ // Adds a cue point to the Cues element. Returns true on success.
+ bool AddCue(CuePoint* cue);
+
+ // Returns the cue point by index. Returns NULL if there is no cue point
+ // match.
+ CuePoint* GetCueByIndex(int32_t index) const;
+
+ // Returns the total size of the Cues element
+ uint64_t Size();
+
+ // Output the Cues element to the writer. Returns true on success.
+ bool Write(IMkvWriter* writer) const;
+
+ int32_t cue_entries_size() const { return cue_entries_size_; }
+ void set_output_block_number(bool output_block_number) {
+ output_block_number_ = output_block_number;
+ }
+ bool output_block_number() const { return output_block_number_; }
+
+ private:
+ // Number of allocated elements in |cue_entries_|.
+ int32_t cue_entries_capacity_;
+
+ // Number of CuePoints in |cue_entries_|.
+ int32_t cue_entries_size_;
+
+ // CuePoint list.
+ CuePoint** cue_entries_;
+
+ // If true the muxer will write out the block number for the cue if the
+ // block number is different than the default of 1. Default is set to true.
+ bool output_block_number_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Cues);
+};
+
+///////////////////////////////////////////////////////////////
+// ContentEncAESSettings element
+class ContentEncAESSettings {
+ public:
+ enum { kCTR = 1 };
+
+ ContentEncAESSettings();
+ ~ContentEncAESSettings() {}
+
+ // Returns the size in bytes for the ContentEncAESSettings element.
+ uint64_t Size() const;
+
+ // Writes out the ContentEncAESSettings element to |writer|. Returns true on
+ // success.
+ bool Write(IMkvWriter* writer) const;
+
+ uint64_t cipher_mode() const { return cipher_mode_; }
+
+ private:
+ // Returns the size in bytes for the payload of the ContentEncAESSettings
+ // element.
+ uint64_t PayloadSize() const;
+
+ // Sub elements
+ uint64_t cipher_mode_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncAESSettings);
+};
+
+///////////////////////////////////////////////////////////////
+// ContentEncoding element
+// Elements used to describe if the track data has been encrypted or
+// compressed with zlib or header stripping.
+// Currently only whole frames can be encrypted with AES. This dictates that
+// ContentEncodingOrder will be 0, ContentEncodingScope will be 1,
+// ContentEncodingType will be 1, and ContentEncAlgo will be 5.
+class ContentEncoding {
+ public:
+ ContentEncoding();
+ ~ContentEncoding();
+
+ // Sets the content encryption id. Copies |length| bytes from |id| to
+ // |enc_key_id_|. Returns true on success.
+ bool SetEncryptionID(const uint8_t* id, uint64_t length);
+
+ // Returns the size in bytes for the ContentEncoding element.
+ uint64_t Size() const;
+
+ // Writes out the ContentEncoding element to |writer|. Returns true on
+ // success.
+ bool Write(IMkvWriter* writer) const;
+
+ uint64_t enc_algo() const { return enc_algo_; }
+ uint64_t encoding_order() const { return encoding_order_; }
+ uint64_t encoding_scope() const { return encoding_scope_; }
+ uint64_t encoding_type() const { return encoding_type_; }
+ ContentEncAESSettings* enc_aes_settings() { return &enc_aes_settings_; }
+
+ private:
+ // Returns the size in bytes for the encoding elements.
+ uint64_t EncodingSize(uint64_t compresion_size,
+ uint64_t encryption_size) const;
+
+ // Returns the size in bytes for the encryption elements.
+ uint64_t EncryptionSize() const;
+
+ // Track element names
+ uint64_t enc_algo_;
+ uint8_t* enc_key_id_;
+ uint64_t encoding_order_;
+ uint64_t encoding_scope_;
+ uint64_t encoding_type_;
+
+ // ContentEncAESSettings element.
+ ContentEncAESSettings enc_aes_settings_;
+
+ // Size of the ContentEncKeyID data in bytes.
+ uint64_t enc_key_id_length_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding);
+};
+
+///////////////////////////////////////////////////////////////
+// Colour element.
+class PrimaryChromaticity {
+ public:
+ static const float kChromaticityMin;
+ static const float kChromaticityMax;
+
+ PrimaryChromaticity(float x_val, float y_val) : x_(x_val), y_(y_val) {}
+ PrimaryChromaticity() : x_(0), y_(0) {}
+ ~PrimaryChromaticity() {}
+
+ // Returns sum of |x_id| and |y_id| element id sizes and payload sizes.
+ uint64_t PrimaryChromaticitySize(libwebm::MkvId x_id,
+ libwebm::MkvId y_id) const;
+ bool Valid() const;
+ bool Write(IMkvWriter* writer, libwebm::MkvId x_id,
+ libwebm::MkvId y_id) const;
+
+ float x() const { return x_; }
+ void set_x(float new_x) { x_ = new_x; }
+ float y() const { return y_; }
+ void set_y(float new_y) { y_ = new_y; }
+
+ private:
+ float x_;
+ float y_;
+};
+
+class MasteringMetadata {
+ public:
+ static const float kValueNotPresent;
+ static const float kMinLuminance;
+ static const float kMinLuminanceMax;
+ static const float kMaxLuminanceMax;
+
+ MasteringMetadata()
+ : luminance_max_(kValueNotPresent),
+ luminance_min_(kValueNotPresent),
+ r_(NULL),
+ g_(NULL),
+ b_(NULL),
+ white_point_(NULL) {}
+ ~MasteringMetadata() {
+ delete r_;
+ delete g_;
+ delete b_;
+ delete white_point_;
+ }
+
+ // Returns total size of the MasteringMetadata element.
+ uint64_t MasteringMetadataSize() const;
+ bool Valid() const;
+ bool Write(IMkvWriter* writer) const;
+
+ // Copies non-null chromaticity.
+ bool SetChromaticity(const PrimaryChromaticity* r,
+ const PrimaryChromaticity* g,
+ const PrimaryChromaticity* b,
+ const PrimaryChromaticity* white_point);
+ const PrimaryChromaticity* r() const { return r_; }
+ const PrimaryChromaticity* g() const { return g_; }
+ const PrimaryChromaticity* b() const { return b_; }
+ const PrimaryChromaticity* white_point() const { return white_point_; }
+
+ float luminance_max() const { return luminance_max_; }
+ void set_luminance_max(float luminance_max) {
+ luminance_max_ = luminance_max;
+ }
+ float luminance_min() const { return luminance_min_; }
+ void set_luminance_min(float luminance_min) {
+ luminance_min_ = luminance_min;
+ }
+
+ private:
+ // Returns size of MasteringMetadata child elements.
+ uint64_t PayloadSize() const;
+
+ float luminance_max_;
+ float luminance_min_;
+ PrimaryChromaticity* r_;
+ PrimaryChromaticity* g_;
+ PrimaryChromaticity* b_;
+ PrimaryChromaticity* white_point_;
+};
+
+class Colour {
+ public:
+ enum MatrixCoefficients {
+ kGbr = 0,
+ kBt709 = 1,
+ kUnspecifiedMc = 2,
+ kReserved = 3,
+ kFcc = 4,
+ kBt470bg = 5,
+ kSmpte170MMc = 6,
+ kSmpte240MMc = 7,
+ kYcocg = 8,
+ kBt2020NonConstantLuminance = 9,
+ kBt2020ConstantLuminance = 10,
+ };
+ enum ChromaSitingHorz {
+ kUnspecifiedCsh = 0,
+ kLeftCollocated = 1,
+ kHalfCsh = 2,
+ };
+ enum ChromaSitingVert {
+ kUnspecifiedCsv = 0,
+ kTopCollocated = 1,
+ kHalfCsv = 2,
+ };
+ enum Range {
+ kUnspecifiedCr = 0,
+ kBroadcastRange = 1,
+ kFullRange = 2,
+ kMcTcDefined = 3, // Defined by MatrixCoefficients/TransferCharacteristics.
+ };
+ enum TransferCharacteristics {
+ kIturBt709Tc = 1,
+ kUnspecifiedTc = 2,
+ kReservedTc = 3,
+ kGamma22Curve = 4,
+ kGamma28Curve = 5,
+ kSmpte170MTc = 6,
+ kSmpte240MTc = 7,
+ kLinear = 8,
+ kLog = 9,
+ kLogSqrt = 10,
+ kIec6196624 = 11,
+ kIturBt1361ExtendedColourGamut = 12,
+ kIec6196621 = 13,
+ kIturBt202010bit = 14,
+ kIturBt202012bit = 15,
+ kSmpteSt2084 = 16,
+ kSmpteSt4281Tc = 17,
+ kAribStdB67Hlg = 18,
+ };
+ enum Primaries {
+ kReservedP0 = 0,
+ kIturBt709P = 1,
+ kUnspecifiedP = 2,
+ kReservedP3 = 3,
+ kIturBt470M = 4,
+ kIturBt470Bg = 5,
+ kSmpte170MP = 6,
+ kSmpte240MP = 7,
+ kFilm = 8,
+ kIturBt2020 = 9,
+ kSmpteSt4281P = 10,
+ kJedecP22Phosphors = 22,
+ };
+ static const uint64_t kValueNotPresent;
+ Colour()
+ : matrix_coefficients_(kValueNotPresent),
+ bits_per_channel_(kValueNotPresent),
+ chroma_subsampling_horz_(kValueNotPresent),
+ chroma_subsampling_vert_(kValueNotPresent),
+ cb_subsampling_horz_(kValueNotPresent),
+ cb_subsampling_vert_(kValueNotPresent),
+ chroma_siting_horz_(kValueNotPresent),
+ chroma_siting_vert_(kValueNotPresent),
+ range_(kValueNotPresent),
+ transfer_characteristics_(kValueNotPresent),
+ primaries_(kValueNotPresent),
+ max_cll_(kValueNotPresent),
+ max_fall_(kValueNotPresent),
+ mastering_metadata_(NULL) {}
+ ~Colour() { delete mastering_metadata_; }
+
+ // Returns total size of the Colour element.
+ uint64_t ColourSize() const;
+ bool Valid() const;
+ bool Write(IMkvWriter* writer) const;
+
+ // Deep copies |mastering_metadata|.
+ bool SetMasteringMetadata(const MasteringMetadata& mastering_metadata);
+
+ const MasteringMetadata* mastering_metadata() const {
+ return mastering_metadata_;
+ }
+
+ uint64_t matrix_coefficients() const { return matrix_coefficients_; }
+ void set_matrix_coefficients(uint64_t matrix_coefficients) {
+ matrix_coefficients_ = matrix_coefficients;
+ }
+ uint64_t bits_per_channel() const { return bits_per_channel_; }
+ void set_bits_per_channel(uint64_t bits_per_channel) {
+ bits_per_channel_ = bits_per_channel;
+ }
+ uint64_t chroma_subsampling_horz() const { return chroma_subsampling_horz_; }
+ void set_chroma_subsampling_horz(uint64_t chroma_subsampling_horz) {
+ chroma_subsampling_horz_ = chroma_subsampling_horz;
+ }
+ uint64_t chroma_subsampling_vert() const { return chroma_subsampling_vert_; }
+ void set_chroma_subsampling_vert(uint64_t chroma_subsampling_vert) {
+ chroma_subsampling_vert_ = chroma_subsampling_vert;
+ }
+ uint64_t cb_subsampling_horz() const { return cb_subsampling_horz_; }
+ void set_cb_subsampling_horz(uint64_t cb_subsampling_horz) {
+ cb_subsampling_horz_ = cb_subsampling_horz;
+ }
+ uint64_t cb_subsampling_vert() const { return cb_subsampling_vert_; }
+ void set_cb_subsampling_vert(uint64_t cb_subsampling_vert) {
+ cb_subsampling_vert_ = cb_subsampling_vert;
+ }
+ uint64_t chroma_siting_horz() const { return chroma_siting_horz_; }
+ void set_chroma_siting_horz(uint64_t chroma_siting_horz) {
+ chroma_siting_horz_ = chroma_siting_horz;
+ }
+ uint64_t chroma_siting_vert() const { return chroma_siting_vert_; }
+ void set_chroma_siting_vert(uint64_t chroma_siting_vert) {
+ chroma_siting_vert_ = chroma_siting_vert;
+ }
+ uint64_t range() const { return range_; }
+ void set_range(uint64_t range) { range_ = range; }
+ uint64_t transfer_characteristics() const {
+ return transfer_characteristics_;
+ }
+ void set_transfer_characteristics(uint64_t transfer_characteristics) {
+ transfer_characteristics_ = transfer_characteristics;
+ }
+ uint64_t primaries() const { return primaries_; }
+ void set_primaries(uint64_t primaries) { primaries_ = primaries; }
+ uint64_t max_cll() const { return max_cll_; }
+ void set_max_cll(uint64_t max_cll) { max_cll_ = max_cll; }
+ uint64_t max_fall() const { return max_fall_; }
+ void set_max_fall(uint64_t max_fall) { max_fall_ = max_fall; }
+
+ private:
+ // Returns size of Colour child elements.
+ uint64_t PayloadSize() const;
+
+ uint64_t matrix_coefficients_;
+ uint64_t bits_per_channel_;
+ uint64_t chroma_subsampling_horz_;
+ uint64_t chroma_subsampling_vert_;
+ uint64_t cb_subsampling_horz_;
+ uint64_t cb_subsampling_vert_;
+ uint64_t chroma_siting_horz_;
+ uint64_t chroma_siting_vert_;
+ uint64_t range_;
+ uint64_t transfer_characteristics_;
+ uint64_t primaries_;
+ uint64_t max_cll_;
+ uint64_t max_fall_;
+
+ MasteringMetadata* mastering_metadata_;
+};
+
+///////////////////////////////////////////////////////////////
+// Projection element.
+class Projection {
+ public:
+ enum ProjectionType {
+ kTypeNotPresent = -1,
+ kRectangular = 0,
+ kEquirectangular = 1,
+ kCubeMap = 2,
+ kMesh = 3,
+ };
+ static const uint64_t kValueNotPresent;
+ Projection()
+ : type_(kRectangular),
+ pose_yaw_(0.0),
+ pose_pitch_(0.0),
+ pose_roll_(0.0),
+ private_data_(NULL),
+ private_data_length_(0) {}
+ ~Projection() { delete[] private_data_; }
+
+ uint64_t ProjectionSize() const;
+ bool Write(IMkvWriter* writer) const;
+
+ bool SetProjectionPrivate(const uint8_t* private_data,
+ uint64_t private_data_length);
+
+ ProjectionType type() const { return type_; }
+ void set_type(ProjectionType type) { type_ = type; }
+ float pose_yaw() const { return pose_yaw_; }
+ void set_pose_yaw(float pose_yaw) { pose_yaw_ = pose_yaw; }
+ float pose_pitch() const { return pose_pitch_; }
+ void set_pose_pitch(float pose_pitch) { pose_pitch_ = pose_pitch; }
+ float pose_roll() const { return pose_roll_; }
+ void set_pose_roll(float pose_roll) { pose_roll_ = pose_roll; }
+ uint8_t* private_data() const { return private_data_; }
+ uint64_t private_data_length() const { return private_data_length_; }
+
+ private:
+ // Returns size of VideoProjection child elements.
+ uint64_t PayloadSize() const;
+
+ ProjectionType type_;
+ float pose_yaw_;
+ float pose_pitch_;
+ float pose_roll_;
+ uint8_t* private_data_;
+ uint64_t private_data_length_;
+};
+
+///////////////////////////////////////////////////////////////
+// Track element.
+class Track {
+ public:
+ // The |seed| parameter is used to synthesize a UID for the track.
+ explicit Track(unsigned int* seed);
+ virtual ~Track();
+
+ // Adds a ContentEncoding element to the Track. Returns true on success.
+ virtual bool AddContentEncoding();
+
+ // Returns the ContentEncoding by index. Returns NULL if there is no
+ // ContentEncoding match.
+ ContentEncoding* GetContentEncodingByIndex(uint32_t index) const;
+
+ // Returns the size in bytes for the payload of the Track element.
+ virtual uint64_t PayloadSize() const;
+
+ // Returns the size in bytes of the Track element.
+ virtual uint64_t Size() const;
+
+ // Output the Track element to the writer. Returns true on success.
+ virtual bool Write(IMkvWriter* writer) const;
+
+ // Sets the CodecPrivate element of the Track element. Copies |length|
+ // bytes from |codec_private| to |codec_private_|. Returns true on success.
+ bool SetCodecPrivate(const uint8_t* codec_private, uint64_t length);
+
+ void set_codec_id(const char* codec_id);
+ const char* codec_id() const { return codec_id_; }
+ const uint8_t* codec_private() const { return codec_private_; }
+ void set_language(const char* language);
+ const char* language() const { return language_; }
+ void set_max_block_additional_id(uint64_t max_block_additional_id) {
+ max_block_additional_id_ = max_block_additional_id;
+ }
+ uint64_t max_block_additional_id() const { return max_block_additional_id_; }
+ void set_name(const char* name);
+ const char* name() const { return name_; }
+ void set_number(uint64_t number) { number_ = number; }
+ uint64_t number() const { return number_; }
+ void set_type(uint64_t type) { type_ = type; }
+ uint64_t type() const { return type_; }
+ void set_uid(uint64_t uid) { uid_ = uid; }
+ uint64_t uid() const { return uid_; }
+ void set_codec_delay(uint64_t codec_delay) { codec_delay_ = codec_delay; }
+ uint64_t codec_delay() const { return codec_delay_; }
+ void set_seek_pre_roll(uint64_t seek_pre_roll) {
+ seek_pre_roll_ = seek_pre_roll;
+ }
+ uint64_t seek_pre_roll() const { return seek_pre_roll_; }
+ void set_default_duration(uint64_t default_duration) {
+ default_duration_ = default_duration;
+ }
+ uint64_t default_duration() const { return default_duration_; }
+
+ uint64_t codec_private_length() const { return codec_private_length_; }
+ uint32_t content_encoding_entries_size() const {
+ return content_encoding_entries_size_;
+ }
+
+ private:
+ // Track element names.
+ char* codec_id_;
+ uint8_t* codec_private_;
+ char* language_;
+ uint64_t max_block_additional_id_;
+ char* name_;
+ uint64_t number_;
+ uint64_t type_;
+ uint64_t uid_;
+ uint64_t codec_delay_;
+ uint64_t seek_pre_roll_;
+ uint64_t default_duration_;
+
+ // Size of the CodecPrivate data in bytes.
+ uint64_t codec_private_length_;
+
+ // ContentEncoding element list.
+ ContentEncoding** content_encoding_entries_;
+
+ // Number of ContentEncoding elements added.
+ uint32_t content_encoding_entries_size_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Track);
+};
+
+///////////////////////////////////////////////////////////////
+// Track that has video specific elements.
+class VideoTrack : public Track {
+ public:
+ // Supported modes for stereo 3D.
+ enum StereoMode {
+ kMono = 0,
+ kSideBySideLeftIsFirst = 1,
+ kTopBottomRightIsFirst = 2,
+ kTopBottomLeftIsFirst = 3,
+ kSideBySideRightIsFirst = 11
+ };
+
+ enum AlphaMode { kNoAlpha = 0, kAlpha = 1 };
+
+ // The |seed| parameter is used to synthesize a UID for the track.
+ explicit VideoTrack(unsigned int* seed);
+ virtual ~VideoTrack();
+
+ // Returns the size in bytes for the payload of the Track element plus the
+ // video specific elements.
+ virtual uint64_t PayloadSize() const;
+
+ // Output the VideoTrack element to the writer. Returns true on success.
+ virtual bool Write(IMkvWriter* writer) const;
+
+ // Sets the video's stereo mode. Returns true on success.
+ bool SetStereoMode(uint64_t stereo_mode);
+
+ // Sets the video's alpha mode. Returns true on success.
+ bool SetAlphaMode(uint64_t alpha_mode);
+
+ void set_display_height(uint64_t height) { display_height_ = height; }
+ uint64_t display_height() const { return display_height_; }
+ void set_display_width(uint64_t width) { display_width_ = width; }
+ uint64_t display_width() const { return display_width_; }
+ void set_pixel_height(uint64_t height) { pixel_height_ = height; }
+ uint64_t pixel_height() const { return pixel_height_; }
+ void set_pixel_width(uint64_t width) { pixel_width_ = width; }
+ uint64_t pixel_width() const { return pixel_width_; }
+
+ void set_crop_left(uint64_t crop_left) { crop_left_ = crop_left; }
+ uint64_t crop_left() const { return crop_left_; }
+ void set_crop_right(uint64_t crop_right) { crop_right_ = crop_right; }
+ uint64_t crop_right() const { return crop_right_; }
+ void set_crop_top(uint64_t crop_top) { crop_top_ = crop_top; }
+ uint64_t crop_top() const { return crop_top_; }
+ void set_crop_bottom(uint64_t crop_bottom) { crop_bottom_ = crop_bottom; }
+ uint64_t crop_bottom() const { return crop_bottom_; }
+
+ void set_frame_rate(double frame_rate) { frame_rate_ = frame_rate; }
+ double frame_rate() const { return frame_rate_; }
+ void set_height(uint64_t height) { height_ = height; }
+ uint64_t height() const { return height_; }
+ uint64_t stereo_mode() { return stereo_mode_; }
+ uint64_t alpha_mode() { return alpha_mode_; }
+ void set_width(uint64_t width) { width_ = width; }
+ uint64_t width() const { return width_; }
+ void set_colour_space(const char* colour_space);
+ const char* colour_space() const { return colour_space_; }
+
+ Colour* colour() { return colour_; }
+
+ // Deep copies |colour|.
+ bool SetColour(const Colour& colour);
+
+ Projection* projection() { return projection_; }
+
+ // Deep copies |projection|.
+ bool SetProjection(const Projection& projection);
+
+ private:
+ // Returns the size in bytes of the Video element.
+ uint64_t VideoPayloadSize() const;
+
+ // Video track element names.
+ uint64_t display_height_;
+ uint64_t display_width_;
+ uint64_t pixel_height_;
+ uint64_t pixel_width_;
+ uint64_t crop_left_;
+ uint64_t crop_right_;
+ uint64_t crop_top_;
+ uint64_t crop_bottom_;
+ double frame_rate_;
+ uint64_t height_;
+ uint64_t stereo_mode_;
+ uint64_t alpha_mode_;
+ uint64_t width_;
+ char* colour_space_;
+
+ Colour* colour_;
+ Projection* projection_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(VideoTrack);
+};
+
+///////////////////////////////////////////////////////////////
+// Track that has audio specific elements.
+class AudioTrack : public Track {
+ public:
+ // The |seed| parameter is used to synthesize a UID for the track.
+ explicit AudioTrack(unsigned int* seed);
+ virtual ~AudioTrack();
+
+ // Returns the size in bytes for the payload of the Track element plus the
+ // audio specific elements.
+ virtual uint64_t PayloadSize() const;
+
+ // Output the AudioTrack element to the writer. Returns true on success.
+ virtual bool Write(IMkvWriter* writer) const;
+
+ void set_bit_depth(uint64_t bit_depth) { bit_depth_ = bit_depth; }
+ uint64_t bit_depth() const { return bit_depth_; }
+ void set_channels(uint64_t channels) { channels_ = channels; }
+ uint64_t channels() const { return channels_; }
+ void set_sample_rate(double sample_rate) { sample_rate_ = sample_rate; }
+ double sample_rate() const { return sample_rate_; }
+
+ private:
+ // Audio track element names.
+ uint64_t bit_depth_;
+ uint64_t channels_;
+ double sample_rate_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(AudioTrack);
+};
+
+///////////////////////////////////////////////////////////////
+// Tracks element
+class Tracks {
+ public:
+ // Audio and video type defined by the Matroska specs.
+ enum { kVideo = 0x1, kAudio = 0x2 };
+
+ static const char kOpusCodecId[];
+ static const char kVorbisCodecId[];
+ static const char kAv1CodecId[];
+ static const char kVp8CodecId[];
+ static const char kVp9CodecId[];
+ static const char kWebVttCaptionsId[];
+ static const char kWebVttDescriptionsId[];
+ static const char kWebVttMetadataId[];
+ static const char kWebVttSubtitlesId[];
+
+ Tracks();
+ ~Tracks();
+
+ // Adds a Track element to the Tracks object. |track| will be owned and
+ // deleted by the Tracks object. Returns true on success. |number| is the
+ // number to use for the track. |number| must be >= 0. If |number| == 0
+ // then the muxer will decide on the track number.
+ bool AddTrack(Track* track, int32_t number);
+
+ // Returns the track by index. Returns NULL if there is no track match.
+ const Track* GetTrackByIndex(uint32_t idx) const;
+
+ // Search the Tracks and return the track that matches |tn|. Returns NULL
+ // if there is no track match.
+ Track* GetTrackByNumber(uint64_t track_number) const;
+
+ // Returns true if the track number is an audio track.
+ bool TrackIsAudio(uint64_t track_number) const;
+
+ // Returns true if the track number is a video track.
+ bool TrackIsVideo(uint64_t track_number) const;
+
+ // Output the Tracks element to the writer. Returns true on success.
+ bool Write(IMkvWriter* writer) const;
+
+ uint32_t track_entries_size() const { return track_entries_size_; }
+
+ private:
+ // Track element list.
+ Track** track_entries_;
+
+ // Number of Track elements added.
+ uint32_t track_entries_size_;
+
+ // Whether or not Tracks element has already been written via IMkvWriter.
+ mutable bool wrote_tracks_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tracks);
+};
+
+///////////////////////////////////////////////////////////////
+// Chapter element
+//
+class Chapter {
+ public:
+ // Set the identifier for this chapter. (This corresponds to the
+ // Cue Identifier line in WebVTT.)
+ // TODO(matthewjheaney): the actual serialization of this item in
+ // MKV is pending.
+ bool set_id(const char* id);
+
+ // Converts the nanosecond start and stop times of this chapter to
+ // their corresponding timecode values, and stores them that way.
+ void set_time(const Segment& segment, uint64_t start_time_ns,
+ uint64_t end_time_ns);
+
+ // Sets the uid for this chapter. Primarily used to enable
+ // deterministic output from the muxer.
+ void set_uid(const uint64_t uid) { uid_ = uid; }
+
+ // Add a title string to this chapter, per the semantics described
+ // here:
+ // http://www.matroska.org/technical/specs/index.html
+ //
+ // The title ("chapter string") is a UTF-8 string.
+ //
+ // The language has ISO 639-2 representation, described here:
+ // http://www.loc.gov/standards/iso639-2/englangn.html
+ // http://www.loc.gov/standards/iso639-2/php/English_list.php
+ // If you specify NULL as the language value, this implies
+ // English ("eng").
+ //
+ // The country value corresponds to the codes listed here:
+ // http://www.iana.org/domains/root/db/
+ //
+ // The function returns false if the string could not be allocated.
+ bool add_string(const char* title, const char* language, const char* country);
+
+ private:
+ friend class Chapters;
+
+ // For storage of chapter titles that differ by language.
+ class Display {
+ public:
+ // Establish representation invariant for new Display object.
+ void Init();
+
+ // Reclaim resources, in anticipation of destruction.
+ void Clear();
+
+ // Copies the title to the |title_| member. Returns false on
+ // error.
+ bool set_title(const char* title);
+
+ // Copies the language to the |language_| member. Returns false
+ // on error.
+ bool set_language(const char* language);
+
+ // Copies the country to the |country_| member. Returns false on
+ // error.
+ bool set_country(const char* country);
+
+ // If |writer| is non-NULL, serialize the Display sub-element of
+ // the Atom into the stream. Returns the Display element size on
+ // success, 0 if error.
+ uint64_t WriteDisplay(IMkvWriter* writer) const;
+
+ private:
+ char* title_;
+ char* language_;
+ char* country_;
+ };
+
+ Chapter();
+ ~Chapter();
+
+ // Establish the representation invariant for a newly-created
+ // Chapter object. The |seed| parameter is used to create the UID
+ // for this chapter atom.
+ void Init(unsigned int* seed);
+
+ // Copies this Chapter object to a different one. This is used when
+ // expanding a plain array of Chapter objects (see Chapters).
+ void ShallowCopy(Chapter* dst) const;
+
+ // Reclaim resources used by this Chapter object, pending its
+ // destruction.
+ void Clear();
+
+ // If there is no storage remaining on the |displays_| array for a
+ // new display object, creates a new, longer array and copies the
+ // existing Display objects to the new array. Returns false if the
+ // array cannot be expanded.
+ bool ExpandDisplaysArray();
+
+ // If |writer| is non-NULL, serialize the Atom sub-element into the
+ // stream. Returns the total size of the element on success, 0 if
+ // error.
+ uint64_t WriteAtom(IMkvWriter* writer) const;
+
+ // The string identifier for this chapter (corresponds to WebVTT cue
+ // identifier).
+ char* id_;
+
+ // Start timecode of the chapter.
+ uint64_t start_timecode_;
+
+ // Stop timecode of the chapter.
+ uint64_t end_timecode_;
+
+ // The binary identifier for this chapter.
+ uint64_t uid_;
+
+ // The Atom element can contain multiple Display sub-elements, as
+ // the same logical title can be rendered in different languages.
+ Display* displays_;
+
+ // The physical length (total size) of the |displays_| array.
+ int displays_size_;
+
+ // The logical length (number of active elements) on the |displays_|
+ // array.
+ int displays_count_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapter);
+};
+
+///////////////////////////////////////////////////////////////
+// Chapters element
+//
+class Chapters {
+ public:
+ Chapters();
+ ~Chapters();
+
+ Chapter* AddChapter(unsigned int* seed);
+
+ // Returns the number of chapters that have been added.
+ int Count() const;
+
+ // Output the Chapters element to the writer. Returns true on success.
+ bool Write(IMkvWriter* writer) const;
+
+ private:
+ // Expands the chapters_ array if there is not enough space to contain
+ // another chapter object. Returns true on success.
+ bool ExpandChaptersArray();
+
+ // If |writer| is non-NULL, serialize the Edition sub-element of the
+ // Chapters element into the stream. Returns the Edition element
+ // size on success, 0 if error.
+ uint64_t WriteEdition(IMkvWriter* writer) const;
+
+ // Total length of the chapters_ array.
+ int chapters_size_;
+
+ // Number of active chapters on the chapters_ array.
+ int chapters_count_;
+
+ // Array for storage of chapter objects.
+ Chapter* chapters_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Chapters);
+};
+
+///////////////////////////////////////////////////////////////
+// Tag element
+//
+class Tag {
+ public:
+ bool add_simple_tag(const char* tag_name, const char* tag_string);
+
+ private:
+ // Tags calls Clear and the destructor of Tag
+ friend class Tags;
+
+ // For storage of simple tags
+ class SimpleTag {
+ public:
+ // Establish representation invariant for new SimpleTag object.
+ void Init();
+
+ // Reclaim resources, in anticipation of destruction.
+ void Clear();
+
+ // Copies the title to the |tag_name_| member. Returns false on
+ // error.
+ bool set_tag_name(const char* tag_name);
+
+ // Copies the language to the |tag_string_| member. Returns false
+ // on error.
+ bool set_tag_string(const char* tag_string);
+
+ // If |writer| is non-NULL, serialize the SimpleTag sub-element of
+ // the Atom into the stream. Returns the SimpleTag element size on
+ // success, 0 if error.
+ uint64_t Write(IMkvWriter* writer) const;
+
+ private:
+ char* tag_name_;
+ char* tag_string_;
+ };
+
+ Tag();
+ ~Tag();
+
+ // Copies this Tag object to a different one. This is used when
+ // expanding a plain array of Tag objects (see Tags).
+ void ShallowCopy(Tag* dst) const;
+
+ // Reclaim resources used by this Tag object, pending its
+ // destruction.
+ void Clear();
+
+ // If there is no storage remaining on the |simple_tags_| array for a
+ // new display object, creates a new, longer array and copies the
+ // existing SimpleTag objects to the new array. Returns false if the
+ // array cannot be expanded.
+ bool ExpandSimpleTagsArray();
+
+ // If |writer| is non-NULL, serialize the Tag sub-element into the
+ // stream. Returns the total size of the element on success, 0 if
+ // error.
+ uint64_t Write(IMkvWriter* writer) const;
+
+ // The Atom element can contain multiple SimpleTag sub-elements
+ SimpleTag* simple_tags_;
+
+ // The physical length (total size) of the |simple_tags_| array.
+ int simple_tags_size_;
+
+ // The logical length (number of active elements) on the |simple_tags_|
+ // array.
+ int simple_tags_count_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tag);
+};
+
+///////////////////////////////////////////////////////////////
+// Tags element
+//
+class Tags {
+ public:
+ Tags();
+ ~Tags();
+
+ Tag* AddTag();
+
+ // Returns the number of tags that have been added.
+ int Count() const;
+
+ // Output the Tags element to the writer. Returns true on success.
+ bool Write(IMkvWriter* writer) const;
+
+ private:
+ // Expands the tags_ array if there is not enough space to contain
+ // another tag object. Returns true on success.
+ bool ExpandTagsArray();
+
+ // Total length of the tags_ array.
+ int tags_size_;
+
+ // Number of active tags on the tags_ array.
+ int tags_count_;
+
+ // Array for storage of tag objects.
+ Tag* tags_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Tags);
+};
+
+///////////////////////////////////////////////////////////////
+// Cluster element
+//
+// Notes:
+// |Init| must be called before any other method in this class.
+class Cluster {
+ public:
+ // |timecode| is the absolute timecode of the cluster. |cues_pos| is the
+ // position for the cluster within the segment that should be written in
+ // the cues element. |timecode_scale| is the timecode scale of the segment.
+ Cluster(uint64_t timecode, int64_t cues_pos, uint64_t timecode_scale,
+ bool write_last_frame_with_duration = false,
+ bool fixed_size_timecode = false);
+ ~Cluster();
+
+ bool Init(IMkvWriter* ptr_writer);
+
+ // Adds a frame to be output in the file. The frame is written out through
+ // |writer_| if successful. Returns true on success.
+ bool AddFrame(const Frame* frame);
+
+ // Adds a frame to be output in the file. The frame is written out through
+ // |writer_| if successful. Returns true on success.
+ // Inputs:
+ // data: Pointer to the data
+ // length: Length of the data
+ // track_number: Track to add the data to. Value returned by Add track
+ // functions. The range of allowed values is [1, 126].
+ // timecode: Absolute (not relative to cluster) timestamp of the
+ // frame, expressed in timecode units.
+ // is_key: Flag telling whether or not this frame is a key frame.
+ bool AddFrame(const uint8_t* data, uint64_t length, uint64_t track_number,
+ uint64_t timecode, // timecode units (absolute)
+ bool is_key);
+
+ // Adds a frame to be output in the file. The frame is written out through
+ // |writer_| if successful. Returns true on success.
+ // Inputs:
+ // data: Pointer to the data
+ // length: Length of the data
+ // additional: Pointer to the additional data
+ // additional_length: Length of the additional data
+ // add_id: Value of BlockAddID element
+ // track_number: Track to add the data to. Value returned by Add track
+ // functions. The range of allowed values is [1, 126].
+ // abs_timecode: Absolute (not relative to cluster) timestamp of the
+ // frame, expressed in timecode units.
+ // is_key: Flag telling whether or not this frame is a key frame.
+ bool AddFrameWithAdditional(const uint8_t* data, uint64_t length,
+ const uint8_t* additional,
+ uint64_t additional_length, uint64_t add_id,
+ uint64_t track_number, uint64_t abs_timecode,
+ bool is_key);
+
+ // Adds a frame to be output in the file. The frame is written out through
+ // |writer_| if successful. Returns true on success.
+ // Inputs:
+ // data: Pointer to the data.
+ // length: Length of the data.
+ // discard_padding: DiscardPadding element value.
+ // track_number: Track to add the data to. Value returned by Add track
+ // functions. The range of allowed values is [1, 126].
+ // abs_timecode: Absolute (not relative to cluster) timestamp of the
+ // frame, expressed in timecode units.
+ // is_key: Flag telling whether or not this frame is a key frame.
+ bool AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length,
+ int64_t discard_padding,
+ uint64_t track_number, uint64_t abs_timecode,
+ bool is_key);
+
+ // Writes a frame of metadata to the output medium; returns true on
+ // success.
+ // Inputs:
+ // data: Pointer to the data
+ // length: Length of the data
+ // track_number: Track to add the data to. Value returned by Add track
+ // functions. The range of allowed values is [1, 126].
+ // timecode: Absolute (not relative to cluster) timestamp of the
+ // metadata frame, expressed in timecode units.
+ // duration: Duration of metadata frame, in timecode units.
+ //
+ // The metadata frame is written as a block group, with a duration
+ // sub-element but no reference time sub-elements (indicating that
+ // it is considered a keyframe, per Matroska semantics).
+ bool AddMetadata(const uint8_t* data, uint64_t length, uint64_t track_number,
+ uint64_t timecode, uint64_t duration);
+
+ // Increments the size of the cluster's data in bytes.
+ void AddPayloadSize(uint64_t size);
+
+ // Closes the cluster so no more data can be written to it. Will update the
+ // cluster's size if |writer_| is seekable. Returns true on success. This
+ // variant of Finalize() fails when |write_last_frame_with_duration_| is set
+ // to true.
+ bool Finalize();
+
+ // Closes the cluster so no more data can be written to it. Will update the
+ // cluster's size if |writer_| is seekable. Returns true on success.
+ // Inputs:
+ // set_last_frame_duration: Boolean indicating whether or not the duration
+ // of the last frame should be set. If set to
+ // false, the |duration| value is ignored and
+ // |write_last_frame_with_duration_| will not be
+ // honored.
+ // duration: Duration of the Cluster in timecode scale.
+ bool Finalize(bool set_last_frame_duration, uint64_t duration);
+
+ // Returns the size in bytes for the entire Cluster element.
+ uint64_t Size() const;
+
+ // Given |abs_timecode|, calculates timecode relative to most recent timecode.
+ // Returns -1 on failure, or a relative timecode.
+ int64_t GetRelativeTimecode(int64_t abs_timecode) const;
+
+ int64_t size_position() const { return size_position_; }
+ int32_t blocks_added() const { return blocks_added_; }
+ uint64_t payload_size() const { return payload_size_; }
+ int64_t position_for_cues() const { return position_for_cues_; }
+ uint64_t timecode() const { return timecode_; }
+ uint64_t timecode_scale() const { return timecode_scale_; }
+ void set_write_last_frame_with_duration(bool write_last_frame_with_duration) {
+ write_last_frame_with_duration_ = write_last_frame_with_duration;
+ }
+ bool write_last_frame_with_duration() const {
+ return write_last_frame_with_duration_;
+ }
+
+ private:
+ // Iterator type for the |stored_frames_| map.
+ typedef std::map<uint64_t, std::list<Frame*> >::iterator FrameMapIterator;
+
+ // Utility method that confirms that blocks can still be added, and that the
+ // cluster header has been written. Used by |DoWriteFrame*|. Returns true
+ // when successful.
+ bool PreWriteBlock();
+
+ // Utility method used by the |DoWriteFrame*| methods that handles the book
+ // keeping required after each block is written.
+ void PostWriteBlock(uint64_t element_size);
+
+ // Does some verification and calls WriteFrame.
+ bool DoWriteFrame(const Frame* const frame);
+
+ // Either holds back the given frame, or writes it out depending on whether or
+ // not |write_last_frame_with_duration_| is set.
+ bool QueueOrWriteFrame(const Frame* const frame);
+
+ // Outputs the Cluster header to |writer_|. Returns true on success.
+ bool WriteClusterHeader();
+
+ // Number of blocks added to the cluster.
+ int32_t blocks_added_;
+
+ // Flag telling if the cluster has been closed.
+ bool finalized_;
+
+ // Flag indicating whether the cluster's timecode will always be written out
+ // using 8 bytes.
+ bool fixed_size_timecode_;
+
+ // Flag telling if the cluster's header has been written.
+ bool header_written_;
+
+ // The size of the cluster elements in bytes.
+ uint64_t payload_size_;
+
+ // The file position used for cue points.
+ const int64_t position_for_cues_;
+
+ // The file position of the cluster's size element.
+ int64_t size_position_;
+
+ // The absolute timecode of the cluster.
+ const uint64_t timecode_;
+
+ // The timecode scale of the Segment containing the cluster.
+ const uint64_t timecode_scale_;
+
+ // Flag indicating whether the last frame of the cluster should be written as
+ // a Block with Duration. If set to true, then it will result in holding back
+ // of frames and the parameterized version of Finalize() must be called to
+ // finish writing the Cluster.
+ bool write_last_frame_with_duration_;
+
+ // Map used to hold back frames, if required. Track number is the key.
+ std::map<uint64_t, std::list<Frame*> > stored_frames_;
+
+ // Map from track number to the timestamp of the last block written for that
+ // track.
+ std::map<uint64_t, uint64_t> last_block_timestamp_;
+
+ // Pointer to the writer object. Not owned by this class.
+ IMkvWriter* writer_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Cluster);
+};
+
+///////////////////////////////////////////////////////////////
+// SeekHead element
+class SeekHead {
+ public:
+ SeekHead();
+ ~SeekHead();
+
+ // TODO(fgalligan): Change this to reserve a certain size. Then check how
+ // big the seek entry to be added is as not every seek entry will be the
+ // maximum size it could be.
+ // Adds a seek entry to be written out when the element is finalized. |id|
+ // must be the coded mkv element id. |pos| is the file position of the
+ // element. Returns true on success.
+ bool AddSeekEntry(uint32_t id, uint64_t pos);
+
+ // Writes out SeekHead and SeekEntry elements. Returns true on success.
+ bool Finalize(IMkvWriter* writer) const;
+
+ // Returns the id of the Seek Entry at the given index. Returns -1 if index is
+ // out of range.
+ uint32_t GetId(int index) const;
+
+ // Returns the position of the Seek Entry at the given index. Returns -1 if
+ // index is out of range.
+ uint64_t GetPosition(int index) const;
+
+ // Sets the Seek Entry id and position at given index.
+ // Returns true on success.
+ bool SetSeekEntry(int index, uint32_t id, uint64_t position);
+
+ // Reserves space by writing out a Void element which will be updated with
+ // a SeekHead element later. Returns true on success.
+ bool Write(IMkvWriter* writer);
+
+ // We are going to put a cap on the number of Seek Entries.
+ const static int32_t kSeekEntryCount = 5;
+
+ private:
+ // Returns the maximum size in bytes of one seek entry.
+ uint64_t MaxEntrySize() const;
+
+ // Seek entry id element list.
+ uint32_t seek_entry_id_[kSeekEntryCount];
+
+ // Seek entry pos element list.
+ uint64_t seek_entry_pos_[kSeekEntryCount];
+
+ // The file position of SeekHead element.
+ int64_t start_pos_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(SeekHead);
+};
+
+///////////////////////////////////////////////////////////////
+// Segment Information element
+class SegmentInfo {
+ public:
+ SegmentInfo();
+ ~SegmentInfo();
+
+ // Will update the duration if |duration_| is > 0.0. Returns true on success.
+ bool Finalize(IMkvWriter* writer) const;
+
+ // Sets |muxing_app_| and |writing_app_|.
+ bool Init();
+
+ // Output the Segment Information element to the writer. Returns true on
+ // success.
+ bool Write(IMkvWriter* writer);
+
+ void set_duration(double duration) { duration_ = duration; }
+ double duration() const { return duration_; }
+ void set_muxing_app(const char* app);
+ const char* muxing_app() const { return muxing_app_; }
+ void set_timecode_scale(uint64_t scale) { timecode_scale_ = scale; }
+ uint64_t timecode_scale() const { return timecode_scale_; }
+ void set_writing_app(const char* app);
+ const char* writing_app() const { return writing_app_; }
+ void set_date_utc(int64_t date_utc) { date_utc_ = date_utc; }
+ int64_t date_utc() const { return date_utc_; }
+
+ private:
+ // Segment Information element names.
+ // Initially set to -1 to signify that a duration has not been set and should
+ // not be written out.
+ double duration_;
+ // Set to libwebm-%d.%d.%d.%d, major, minor, build, revision.
+ char* muxing_app_;
+ uint64_t timecode_scale_;
+ // Initially set to libwebm-%d.%d.%d.%d, major, minor, build, revision.
+ char* writing_app_;
+ // LLONG_MIN when DateUTC is not set.
+ int64_t date_utc_;
+
+ // The file position of the duration element.
+ int64_t duration_pos_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(SegmentInfo);
+};
+
+///////////////////////////////////////////////////////////////
+// This class represents the main segment in a WebM file. Currently only
+// supports one Segment element.
+//
+// Notes:
+// |Init| must be called before any other method in this class.
+class Segment {
+ public:
+ enum Mode { kLive = 0x1, kFile = 0x2 };
+
+ enum CuesPosition {
+ kAfterClusters = 0x0, // Position Cues after Clusters - Default
+ kBeforeClusters = 0x1 // Position Cues before Clusters
+ };
+
+ static const uint32_t kDefaultDocTypeVersion = 4;
+ static const uint64_t kDefaultMaxClusterDuration = 30000000000ULL;
+
+ Segment();
+ ~Segment();
+
+ // Initializes |SegmentInfo| and returns result. Always returns false when
+ // |ptr_writer| is NULL.
+ bool Init(IMkvWriter* ptr_writer);
+
+ // Adds a generic track to the segment. Returns the newly-allocated
+ // track object (which is owned by the segment) on success, NULL on
+ // error. |number| is the number to use for the track. |number|
+ // must be >= 0. If |number| == 0 then the muxer will decide on the
+ // track number.
+ Track* AddTrack(int32_t number);
+
+ // Adds a Vorbis audio track to the segment. Returns the number of the track
+ // on success, 0 on error. |number| is the number to use for the audio track.
+ // |number| must be >= 0. If |number| == 0 then the muxer will decide on
+ // the track number.
+ uint64_t AddAudioTrack(int32_t sample_rate, int32_t channels, int32_t number);
+
+ // Adds an empty chapter to the chapters of this segment. Returns
+ // non-NULL on success. After adding the chapter, the caller should
+ // populate its fields via the Chapter member functions.
+ Chapter* AddChapter();
+
+ // Adds an empty tag to the tags of this segment. Returns
+ // non-NULL on success. After adding the tag, the caller should
+ // populate its fields via the Tag member functions.
+ Tag* AddTag();
+
+ // Adds a cue point to the Cues element. |timestamp| is the time in
+ // nanoseconds of the cue's time. |track| is the Track of the Cue. This
+ // function must be called after AddFrame to calculate the correct
+ // BlockNumber for the CuePoint. Returns true on success.
+ bool AddCuePoint(uint64_t timestamp, uint64_t track);
+
+ // Adds a frame to be output in the file. Returns true on success.
+ // Inputs:
+ // data: Pointer to the data
+ // length: Length of the data
+ // track_number: Track to add the data to. Value returned by Add track
+ // functions.
+ // timestamp: Timestamp of the frame in nanoseconds from 0.
+ // is_key: Flag telling whether or not this frame is a key frame.
+ bool AddFrame(const uint8_t* data, uint64_t length, uint64_t track_number,
+ uint64_t timestamp_ns, bool is_key);
+
+ // Writes a frame of metadata to the output medium; returns true on
+ // success.
+ // Inputs:
+ // data: Pointer to the data
+ // length: Length of the data
+ // track_number: Track to add the data to. Value returned by Add track
+ // functions.
+ // timecode: Absolute timestamp of the metadata frame, expressed
+ // in nanosecond units.
+ // duration: Duration of metadata frame, in nanosecond units.
+ //
+ // The metadata frame is written as a block group, with a duration
+ // sub-element but no reference time sub-elements (indicating that
+ // it is considered a keyframe, per Matroska semantics).
+ bool AddMetadata(const uint8_t* data, uint64_t length, uint64_t track_number,
+ uint64_t timestamp_ns, uint64_t duration_ns);
+
+ // Writes a frame with additional data to the output medium; returns true on
+ // success.
+ // Inputs:
+ // data: Pointer to the data.
+ // length: Length of the data.
+ // additional: Pointer to additional data.
+ // additional_length: Length of additional data.
+ // add_id: Additional ID which identifies the type of additional data.
+ // track_number: Track to add the data to. Value returned by Add track
+ // functions.
+ // timestamp: Absolute timestamp of the frame, expressed in nanosecond
+ // units.
+ // is_key: Flag telling whether or not this frame is a key frame.
+ bool AddFrameWithAdditional(const uint8_t* data, uint64_t length,
+ const uint8_t* additional,
+ uint64_t additional_length, uint64_t add_id,
+ uint64_t track_number, uint64_t timestamp,
+ bool is_key);
+
+ // Writes a frame with DiscardPadding to the output medium; returns true on
+ // success.
+ // Inputs:
+ // data: Pointer to the data.
+ // length: Length of the data.
+ // discard_padding: DiscardPadding element value.
+ // track_number: Track to add the data to. Value returned by Add track
+ // functions.
+ // timestamp: Absolute timestamp of the frame, expressed in nanosecond
+ // units.
+ // is_key: Flag telling whether or not this frame is a key frame.
+ bool AddFrameWithDiscardPadding(const uint8_t* data, uint64_t length,
+ int64_t discard_padding,
+ uint64_t track_number, uint64_t timestamp,
+ bool is_key);
+
+ // Writes a Frame to the output medium. Chooses the correct way of writing
+ // the frame (Block vs SimpleBlock) based on the parameters passed.
+ // Inputs:
+ // frame: frame object
+ bool AddGenericFrame(const Frame* frame);
+
+ // Adds a VP8 video track to the segment. Returns the number of the track on
+ // success, 0 on error. |number| is the number to use for the video track.
+ // |number| must be >= 0. If |number| == 0 then the muxer will decide on
+ // the track number.
+ uint64_t AddVideoTrack(int32_t width, int32_t height, int32_t number);
+
+ // This function must be called after Finalize() if you need a copy of the
+ // output with Cues written before the Clusters. It will return false if the
+ // writer is not seekable of if chunking is set to true.
+ // Input parameters:
+ // reader - an IMkvReader object created with the same underlying file of the
+ // current writer object. Make sure to close the existing writer
+ // object before creating this so that all the data is properly
+ // flushed and available for reading.
+ // writer - an IMkvWriter object pointing to a *different* file than the one
+ // pointed by the current writer object. This file will contain the
+ // Cues element before the Clusters.
+ bool CopyAndMoveCuesBeforeClusters(mkvparser::IMkvReader* reader,
+ IMkvWriter* writer);
+
+ // Sets which track to use for the Cues element. Must have added the track
+ // before calling this function. Returns true on success. |track_number| is
+ // returned by the Add track functions.
+ bool CuesTrack(uint64_t track_number);
+
+ // This will force the muxer to create a new Cluster when the next frame is
+ // added.
+ void ForceNewClusterOnNextFrame();
+
+ // Writes out any frames that have not been written out. Finalizes the last
+ // cluster. May update the size and duration of the segment. May output the
+ // Cues element. May finalize the SeekHead element. Returns true on success.
+ bool Finalize();
+
+ // Returns the Cues object.
+ Cues* GetCues() { return &cues_; }
+
+ // Returns the Segment Information object.
+ const SegmentInfo* GetSegmentInfo() const { return &segment_info_; }
+ SegmentInfo* GetSegmentInfo() { return &segment_info_; }
+
+ // Search the Tracks and return the track that matches |track_number|.
+ // Returns NULL if there is no track match.
+ Track* GetTrackByNumber(uint64_t track_number) const;
+
+ // Toggles whether to output a cues element.
+ void OutputCues(bool output_cues);
+
+ // Toggles whether to write the last frame in each Cluster with Duration.
+ void AccurateClusterDuration(bool accurate_cluster_duration);
+
+ // Toggles whether to write the Cluster Timecode using exactly 8 bytes.
+ void UseFixedSizeClusterTimecode(bool fixed_size_cluster_timecode);
+
+ // Sets if the muxer will output files in chunks or not. |chunking| is a
+ // flag telling whether or not to turn on chunking. |filename| is the base
+ // filename for the chunk files. The header chunk file will be named
+ // |filename|.hdr and the data chunks will be named
+ // |filename|_XXXXXX.chk. Chunking implies that the muxer will be writing
+ // to files so the muxer will use the default MkvWriter class to control
+ // what data is written to what files. Returns true on success.
+ // TODO: Should we change the IMkvWriter Interface to add Open and Close?
+ // That will force the interface to be dependent on files.
+ bool SetChunking(bool chunking, const char* filename);
+
+ bool chunking() const { return chunking_; }
+ uint64_t cues_track() const { return cues_track_; }
+ void set_max_cluster_duration(uint64_t max_cluster_duration) {
+ max_cluster_duration_ = max_cluster_duration;
+ }
+ uint64_t max_cluster_duration() const { return max_cluster_duration_; }
+ void set_max_cluster_size(uint64_t max_cluster_size) {
+ max_cluster_size_ = max_cluster_size;
+ }
+ uint64_t max_cluster_size() const { return max_cluster_size_; }
+ void set_mode(Mode mode) { mode_ = mode; }
+ Mode mode() const { return mode_; }
+ CuesPosition cues_position() const { return cues_position_; }
+ bool output_cues() const { return output_cues_; }
+ void set_estimate_file_duration(bool estimate_duration) {
+ estimate_file_duration_ = estimate_duration;
+ }
+ bool estimate_file_duration() const { return estimate_file_duration_; }
+ const SegmentInfo* segment_info() const { return &segment_info_; }
+ void set_duration(double duration) { duration_ = duration; }
+ double duration() const { return duration_; }
+
+ // Returns true when codec IDs are valid for WebM.
+ bool DocTypeIsWebm() const;
+
+ private:
+ // Checks if header information has been output and initialized. If not it
+ // will output the Segment element and initialize the SeekHead elment and
+ // Cues elements.
+ bool CheckHeaderInfo();
+
+ // Sets |doc_type_version_| based on the current element requirements.
+ void UpdateDocTypeVersion();
+
+ // Sets |name| according to how many chunks have been written. |ext| is the
+ // file extension. |name| must be deleted by the calling app. Returns true
+ // on success.
+ bool UpdateChunkName(const char* ext, char** name) const;
+
+ // Returns the maximum offset within the segment's payload. When chunking
+ // this function is needed to determine offsets of elements within the
+ // chunked files. Returns -1 on error.
+ int64_t MaxOffset();
+
+ // Adds the frame to our frame array.
+ bool QueueFrame(Frame* frame);
+
+ // Output all frames that are queued. Returns -1 on error, otherwise
+ // it returns the number of frames written.
+ int WriteFramesAll();
+
+ // Output all frames that are queued that have an end time that is less
+ // then |timestamp|. Returns true on success and if there are no frames
+ // queued.
+ bool WriteFramesLessThan(uint64_t timestamp);
+
+ // Outputs the segment header, Segment Information element, SeekHead element,
+ // and Tracks element to |writer_|.
+ bool WriteSegmentHeader();
+
+ // Given a frame with the specified timestamp (nanosecond units) and
+ // keyframe status, determine whether a new cluster should be
+ // created, before writing enqueued frames and the frame itself. The
+ // function returns one of the following values:
+ // -1 = error: an out-of-order frame was detected
+ // 0 = do not create a new cluster, and write frame to the existing cluster
+ // 1 = create a new cluster, and write frame to that new cluster
+ // 2 = create a new cluster, and re-run test
+ int TestFrame(uint64_t track_num, uint64_t timestamp_ns, bool key) const;
+
+ // Create a new cluster, using the earlier of the first enqueued
+ // frame, or the indicated time. Returns true on success.
+ bool MakeNewCluster(uint64_t timestamp_ns);
+
+ // Checks whether a new cluster needs to be created, and if so
+ // creates a new cluster. Returns false if creation of a new cluster
+ // was necessary but creation was not successful.
+ bool DoNewClusterProcessing(uint64_t track_num, uint64_t timestamp_ns,
+ bool key);
+
+ // Adjusts Cue Point values (to place Cues before Clusters) so that they
+ // reflect the correct offsets.
+ void MoveCuesBeforeClusters();
+
+ // This function recursively computes the correct cluster offsets (this is
+ // done to move the Cues before Clusters). It recursively updates the change
+ // in size (which indicates a change in cluster offset) until no sizes change.
+ // Parameters:
+ // diff - indicates the difference in size of the Cues element that needs to
+ // accounted for.
+ // index - index in the list of Cues which is currently being adjusted.
+ // cue_size - sum of size of all the CuePoint elements.
+ void MoveCuesBeforeClustersHelper(uint64_t diff, int index,
+ uint64_t* cue_size);
+
+ // Seeds the random number generator used to make UIDs.
+ unsigned int seed_;
+
+ // WebM elements
+ Cues cues_;
+ SeekHead seek_head_;
+ SegmentInfo segment_info_;
+ Tracks tracks_;
+ Chapters chapters_;
+ Tags tags_;
+
+ // Number of chunks written.
+ int chunk_count_;
+
+ // Current chunk filename.
+ char* chunk_name_;
+
+ // Default MkvWriter object created by this class used for writing clusters
+ // out in separate files.
+ MkvWriter* chunk_writer_cluster_;
+
+ // Default MkvWriter object created by this class used for writing Cues
+ // element out to a file.
+ MkvWriter* chunk_writer_cues_;
+
+ // Default MkvWriter object created by this class used for writing the
+ // Matroska header out to a file.
+ MkvWriter* chunk_writer_header_;
+
+ // Flag telling whether or not the muxer is chunking output to multiple
+ // files.
+ bool chunking_;
+
+ // Base filename for the chunked files.
+ char* chunking_base_name_;
+
+ // File position offset where the Clusters end.
+ int64_t cluster_end_offset_;
+
+ // List of clusters.
+ Cluster** cluster_list_;
+
+ // Number of cluster pointers allocated in the cluster list.
+ int32_t cluster_list_capacity_;
+
+ // Number of clusters in the cluster list.
+ int32_t cluster_list_size_;
+
+ // Indicates whether Cues should be written before or after Clusters
+ CuesPosition cues_position_;
+
+ // Track number that is associated with the cues element for this segment.
+ uint64_t cues_track_;
+
+ // Tells the muxer to force a new cluster on the next Block.
+ bool force_new_cluster_;
+
+ // List of stored audio frames. These variables are used to store frames so
+ // the muxer can follow the guideline "Audio blocks that contain the video
+ // key frame's timecode should be in the same cluster as the video key frame
+ // block."
+ Frame** frames_;
+
+ // Number of frame pointers allocated in the frame list.
+ int32_t frames_capacity_;
+
+ // Number of frames in the frame list.
+ int32_t frames_size_;
+
+ // Flag telling if a video track has been added to the segment.
+ bool has_video_;
+
+ // Flag telling if the segment's header has been written.
+ bool header_written_;
+
+ // Duration of the last block in nanoseconds.
+ uint64_t last_block_duration_;
+
+ // Last timestamp in nanoseconds added to a cluster.
+ uint64_t last_timestamp_;
+
+ // Last timestamp in nanoseconds by track number added to a cluster.
+ uint64_t last_track_timestamp_[kMaxTrackNumber];
+
+ // Number of frames written per track.
+ uint64_t track_frames_written_[kMaxTrackNumber];
+
+ // Maximum time in nanoseconds for a cluster duration. This variable is a
+ // guideline and some clusters may have a longer duration. Default is 30
+ // seconds.
+ uint64_t max_cluster_duration_;
+
+ // Maximum size in bytes for a cluster. This variable is a guideline and
+ // some clusters may have a larger size. Default is 0 which signifies that
+ // the muxer will decide the size.
+ uint64_t max_cluster_size_;
+
+ // The mode that segment is in. If set to |kLive| the writer must not
+ // seek backwards.
+ Mode mode_;
+
+ // Flag telling the muxer that a new cue point should be added.
+ bool new_cuepoint_;
+
+ // TODO(fgalligan): Should we add support for more than one Cues element?
+ // Flag whether or not the muxer should output a Cues element.
+ bool output_cues_;
+
+ // Flag whether or not the last frame in each Cluster will have a Duration
+ // element in it.
+ bool accurate_cluster_duration_;
+
+ // Flag whether or not to write the Cluster Timecode using exactly 8 bytes.
+ bool fixed_size_cluster_timecode_;
+
+ // Flag whether or not to estimate the file duration.
+ bool estimate_file_duration_;
+
+ // The size of the EBML header, used to validate the header if
+ // WriteEbmlHeader() is called more than once.
+ int32_t ebml_header_size_;
+
+ // The file position of the segment's payload.
+ int64_t payload_pos_;
+
+ // The file position of the element's size.
+ int64_t size_position_;
+
+ // Current DocTypeVersion (|doc_type_version_|) and that written in
+ // WriteSegmentHeader().
+ // WriteEbmlHeader() will be called from Finalize() if |doc_type_version_|
+ // differs from |doc_type_version_written_|.
+ uint32_t doc_type_version_;
+ uint32_t doc_type_version_written_;
+
+ // If |duration_| is > 0, then explicitly set the duration of the segment.
+ double duration_;
+
+ // Pointer to the writer objects. Not owned by this class.
+ IMkvWriter* writer_cluster_;
+ IMkvWriter* writer_cues_;
+ IMkvWriter* writer_header_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(Segment);
+};
+
+} // namespace mkvmuxer
+
+#endif // MKVMUXER_MKVMUXER_H_
diff --git a/mkvmuxer/mkvmuxertypes.h b/mkvmuxer/mkvmuxertypes.h
new file mode 100644
index 0000000..e5db121
--- /dev/null
+++ b/mkvmuxer/mkvmuxertypes.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef MKVMUXER_MKVMUXERTYPES_H_
+#define MKVMUXER_MKVMUXERTYPES_H_
+
+namespace mkvmuxer {
+typedef unsigned char uint8;
+typedef short int16;
+typedef int int32;
+typedef unsigned int uint32;
+typedef long long int64;
+typedef unsigned long long uint64;
+} // namespace mkvmuxer
+
+// Copied from Chromium basictypes.h
+// A macro to disallow the copy constructor and operator= functions
+// This should be used in the private: declarations for a class
+#define LIBWEBM_DISALLOW_COPY_AND_ASSIGN(TypeName) \
+ TypeName(const TypeName&); \
+ void operator=(const TypeName&)
+
+#endif // MKVMUXER_MKVMUXERTYPES_HPP_
diff --git a/mkvmuxer/mkvmuxerutil.cc b/mkvmuxer/mkvmuxerutil.cc
new file mode 100644
index 0000000..6436817
--- /dev/null
+++ b/mkvmuxer/mkvmuxerutil.cc
@@ -0,0 +1,743 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#include "mkvmuxer/mkvmuxerutil.h"
+
+#ifdef __ANDROID__
+#include <fcntl.h>
+#include <unistd.h>
+#endif
+
+#include <cassert>
+#include <cmath>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <new>
+
+#include "common/webmids.h"
+#include "mkvmuxer/mkvmuxer.h"
+#include "mkvmuxer/mkvwriter.h"
+
+namespace mkvmuxer {
+
+namespace {
+
+// Date elements are always 8 octets in size.
+const int kDateElementSize = 8;
+
+uint64 WriteBlock(IMkvWriter* writer, const Frame* const frame, int64 timecode,
+ uint64 timecode_scale) {
+ uint64 block_additional_elem_size = 0;
+ uint64 block_addid_elem_size = 0;
+ uint64 block_more_payload_size = 0;
+ uint64 block_more_elem_size = 0;
+ uint64 block_additions_payload_size = 0;
+ uint64 block_additions_elem_size = 0;
+ if (frame->additional()) {
+ block_additional_elem_size =
+ EbmlElementSize(libwebm::kMkvBlockAdditional, frame->additional(),
+ frame->additional_length());
+ block_addid_elem_size = EbmlElementSize(
+ libwebm::kMkvBlockAddID, static_cast<uint64>(frame->add_id()));
+
+ block_more_payload_size =
+ block_addid_elem_size + block_additional_elem_size;
+ block_more_elem_size =
+ EbmlMasterElementSize(libwebm::kMkvBlockMore, block_more_payload_size) +
+ block_more_payload_size;
+ block_additions_payload_size = block_more_elem_size;
+ block_additions_elem_size =
+ EbmlMasterElementSize(libwebm::kMkvBlockAdditions,
+ block_additions_payload_size) +
+ block_additions_payload_size;
+ }
+
+ uint64 discard_padding_elem_size = 0;
+ if (frame->discard_padding() != 0) {
+ discard_padding_elem_size =
+ EbmlElementSize(libwebm::kMkvDiscardPadding,
+ static_cast<int64>(frame->discard_padding()));
+ }
+
+ const uint64 reference_block_timestamp =
+ frame->reference_block_timestamp() / timecode_scale;
+ uint64 reference_block_elem_size = 0;
+ if (!frame->is_key()) {
+ reference_block_elem_size =
+ EbmlElementSize(libwebm::kMkvReferenceBlock, reference_block_timestamp);
+ }
+
+ const uint64 duration = frame->duration() / timecode_scale;
+ uint64 block_duration_elem_size = 0;
+ if (duration > 0)
+ block_duration_elem_size =
+ EbmlElementSize(libwebm::kMkvBlockDuration, duration);
+
+ const uint64 block_payload_size = 4 + frame->length();
+ const uint64 block_elem_size =
+ EbmlMasterElementSize(libwebm::kMkvBlock, block_payload_size) +
+ block_payload_size;
+
+ const uint64 block_group_payload_size =
+ block_elem_size + block_additions_elem_size + block_duration_elem_size +
+ discard_padding_elem_size + reference_block_elem_size;
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlockGroup,
+ block_group_payload_size)) {
+ return 0;
+ }
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlock, block_payload_size))
+ return 0;
+
+ if (WriteUInt(writer, frame->track_number()))
+ return 0;
+
+ if (SerializeInt(writer, timecode, 2))
+ return 0;
+
+ // For a Block, flags is always 0.
+ if (SerializeInt(writer, 0, 1))
+ return 0;
+
+ if (writer->Write(frame->frame(), static_cast<uint32>(frame->length())))
+ return 0;
+
+ if (frame->additional()) {
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlockAdditions,
+ block_additions_payload_size)) {
+ return 0;
+ }
+
+ if (!WriteEbmlMasterElement(writer, libwebm::kMkvBlockMore,
+ block_more_payload_size))
+ return 0;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvBlockAddID,
+ static_cast<uint64>(frame->add_id())))
+ return 0;
+
+ if (!WriteEbmlElement(writer, libwebm::kMkvBlockAdditional,
+ frame->additional(), frame->additional_length())) {
+ return 0;
+ }
+ }
+
+ if (frame->discard_padding() != 0 &&
+ !WriteEbmlElement(writer, libwebm::kMkvDiscardPadding,
+ static_cast<int64>(frame->discard_padding()))) {
+ return false;
+ }
+
+ if (!frame->is_key() && !WriteEbmlElement(writer, libwebm::kMkvReferenceBlock,
+ reference_block_timestamp)) {
+ return false;
+ }
+
+ if (duration > 0 &&
+ !WriteEbmlElement(writer, libwebm::kMkvBlockDuration, duration)) {
+ return false;
+ }
+ return EbmlMasterElementSize(libwebm::kMkvBlockGroup,
+ block_group_payload_size) +
+ block_group_payload_size;
+}
+
+uint64 WriteSimpleBlock(IMkvWriter* writer, const Frame* const frame,
+ int64 timecode) {
+ if (WriteID(writer, libwebm::kMkvSimpleBlock))
+ return 0;
+
+ const int32 size = static_cast<int32>(frame->length()) + 4;
+ if (WriteUInt(writer, size))
+ return 0;
+
+ if (WriteUInt(writer, static_cast<uint64>(frame->track_number())))
+ return 0;
+
+ if (SerializeInt(writer, timecode, 2))
+ return 0;
+
+ uint64 flags = 0;
+ if (frame->is_key())
+ flags |= 0x80;
+
+ if (SerializeInt(writer, flags, 1))
+ return 0;
+
+ if (writer->Write(frame->frame(), static_cast<uint32>(frame->length())))
+ return 0;
+
+ return GetUIntSize(libwebm::kMkvSimpleBlock) + GetCodedUIntSize(size) + 4 +
+ frame->length();
+}
+
+} // namespace
+
+int32 GetCodedUIntSize(uint64 value) {
+ if (value < 0x000000000000007FULL)
+ return 1;
+ else if (value < 0x0000000000003FFFULL)
+ return 2;
+ else if (value < 0x00000000001FFFFFULL)
+ return 3;
+ else if (value < 0x000000000FFFFFFFULL)
+ return 4;
+ else if (value < 0x00000007FFFFFFFFULL)
+ return 5;
+ else if (value < 0x000003FFFFFFFFFFULL)
+ return 6;
+ else if (value < 0x0001FFFFFFFFFFFFULL)
+ return 7;
+ return 8;
+}
+
+int32 GetUIntSize(uint64 value) {
+ if (value < 0x0000000000000100ULL)
+ return 1;
+ else if (value < 0x0000000000010000ULL)
+ return 2;
+ else if (value < 0x0000000001000000ULL)
+ return 3;
+ else if (value < 0x0000000100000000ULL)
+ return 4;
+ else if (value < 0x0000010000000000ULL)
+ return 5;
+ else if (value < 0x0001000000000000ULL)
+ return 6;
+ else if (value < 0x0100000000000000ULL)
+ return 7;
+ return 8;
+}
+
+int32 GetIntSize(int64 value) {
+ // Doubling the requested value ensures positive values with their high bit
+ // set are written with 0-padding to avoid flipping the signedness.
+ const uint64 v = (value < 0) ? value ^ -1LL : value;
+ return GetUIntSize(2 * v);
+}
+
+uint64 EbmlMasterElementSize(uint64 type, uint64 value) {
+ // Size of EBML ID
+ int32 ebml_size = GetUIntSize(type);
+
+ // Datasize
+ ebml_size += GetCodedUIntSize(value);
+
+ return ebml_size;
+}
+
+uint64 EbmlElementSize(uint64 type, int64 value) {
+ // Size of EBML ID
+ int32 ebml_size = GetUIntSize(type);
+
+ // Datasize
+ ebml_size += GetIntSize(value);
+
+ // Size of Datasize
+ ebml_size++;
+
+ return ebml_size;
+}
+
+uint64 EbmlElementSize(uint64 type, uint64 value) {
+ return EbmlElementSize(type, value, 0);
+}
+
+uint64 EbmlElementSize(uint64 type, uint64 value, uint64 fixed_size) {
+ // Size of EBML ID
+ uint64 ebml_size = GetUIntSize(type);
+
+ // Datasize
+ ebml_size += (fixed_size > 0) ? fixed_size : GetUIntSize(value);
+
+ // Size of Datasize
+ ebml_size++;
+
+ return ebml_size;
+}
+
+uint64 EbmlElementSize(uint64 type, float /* value */) {
+ // Size of EBML ID
+ uint64 ebml_size = GetUIntSize(type);
+
+ // Datasize
+ ebml_size += sizeof(float);
+
+ // Size of Datasize
+ ebml_size++;
+
+ return ebml_size;
+}
+
+uint64 EbmlElementSize(uint64 type, const char* value) {
+ if (!value)
+ return 0;
+
+ // Size of EBML ID
+ uint64 ebml_size = GetUIntSize(type);
+
+ // Datasize
+ ebml_size += strlen(value);
+
+ // Size of Datasize
+ ebml_size += GetCodedUIntSize(strlen(value));
+
+ return ebml_size;
+}
+
+uint64 EbmlElementSize(uint64 type, const uint8* value, uint64 size) {
+ if (!value)
+ return 0;
+
+ // Size of EBML ID
+ uint64 ebml_size = GetUIntSize(type);
+
+ // Datasize
+ ebml_size += size;
+
+ // Size of Datasize
+ ebml_size += GetCodedUIntSize(size);
+
+ return ebml_size;
+}
+
+uint64 EbmlDateElementSize(uint64 type) {
+ // Size of EBML ID
+ uint64 ebml_size = GetUIntSize(type);
+
+ // Datasize
+ ebml_size += kDateElementSize;
+
+ // Size of Datasize
+ ebml_size++;
+
+ return ebml_size;
+}
+
+int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size) {
+ if (!writer || size < 1 || size > 8)
+ return -1;
+
+ for (int32 i = 1; i <= size; ++i) {
+ const int32 byte_count = size - i;
+ const int32 bit_count = byte_count * 8;
+
+ const int64 bb = value >> bit_count;
+ const uint8 b = static_cast<uint8>(bb);
+
+ const int32 status = writer->Write(&b, 1);
+
+ if (status < 0)
+ return status;
+ }
+
+ return 0;
+}
+
+int32 SerializeFloat(IMkvWriter* writer, float f) {
+ if (!writer)
+ return -1;
+
+ assert(sizeof(uint32) == sizeof(float));
+ // This union is merely used to avoid a reinterpret_cast from float& to
+ // uint32& which will result in violation of strict aliasing.
+ union U32 {
+ uint32 u32;
+ float f;
+ } value;
+ value.f = f;
+
+ for (int32 i = 1; i <= 4; ++i) {
+ const int32 byte_count = 4 - i;
+ const int32 bit_count = byte_count * 8;
+
+ const uint8 byte = static_cast<uint8>(value.u32 >> bit_count);
+
+ const int32 status = writer->Write(&byte, 1);
+
+ if (status < 0)
+ return status;
+ }
+
+ return 0;
+}
+
+int32 WriteUInt(IMkvWriter* writer, uint64 value) {
+ if (!writer)
+ return -1;
+
+ int32 size = GetCodedUIntSize(value);
+
+ return WriteUIntSize(writer, value, size);
+}
+
+int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size) {
+ if (!writer || size < 0 || size > 8)
+ return -1;
+
+ if (size > 0) {
+ const uint64 bit = 1LL << (size * 7);
+
+ if (value > (bit - 2))
+ return -1;
+
+ value |= bit;
+ } else {
+ size = 1;
+ int64 bit;
+
+ for (;;) {
+ bit = 1LL << (size * 7);
+ const uint64 max = bit - 2;
+
+ if (value <= max)
+ break;
+
+ ++size;
+ }
+
+ if (size > 8)
+ return false;
+
+ value |= bit;
+ }
+
+ return SerializeInt(writer, value, size);
+}
+
+int32 WriteID(IMkvWriter* writer, uint64 type) {
+ if (!writer)
+ return -1;
+
+ writer->ElementStartNotify(type, writer->Position());
+
+ const int32 size = GetUIntSize(type);
+
+ return SerializeInt(writer, type, size);
+}
+
+bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 type, uint64 size) {
+ if (!writer)
+ return false;
+
+ if (WriteID(writer, type))
+ return false;
+
+ if (WriteUInt(writer, size))
+ return false;
+
+ return true;
+}
+
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value) {
+ return WriteEbmlElement(writer, type, value, 0);
+}
+
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value,
+ uint64 fixed_size) {
+ if (!writer)
+ return false;
+
+ if (WriteID(writer, type))
+ return false;
+
+ uint64 size = GetUIntSize(value);
+ if (fixed_size > 0) {
+ if (size > fixed_size)
+ return false;
+ size = fixed_size;
+ }
+ if (WriteUInt(writer, size))
+ return false;
+
+ if (SerializeInt(writer, value, static_cast<int32>(size)))
+ return false;
+
+ return true;
+}
+
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, int64 value) {
+ if (!writer)
+ return false;
+
+ if (WriteID(writer, type))
+ return 0;
+
+ const uint64 size = GetIntSize(value);
+ if (WriteUInt(writer, size))
+ return false;
+
+ if (SerializeInt(writer, value, static_cast<int32>(size)))
+ return false;
+
+ return true;
+}
+
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, float value) {
+ if (!writer)
+ return false;
+
+ if (WriteID(writer, type))
+ return false;
+
+ if (WriteUInt(writer, 4))
+ return false;
+
+ if (SerializeFloat(writer, value))
+ return false;
+
+ return true;
+}
+
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const char* value) {
+ if (!writer || !value)
+ return false;
+
+ if (WriteID(writer, type))
+ return false;
+
+ const uint64 length = strlen(value);
+ if (WriteUInt(writer, length))
+ return false;
+
+ if (writer->Write(value, static_cast<uint32>(length)))
+ return false;
+
+ return true;
+}
+
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const uint8* value,
+ uint64 size) {
+ if (!writer || !value || size < 1)
+ return false;
+
+ if (WriteID(writer, type))
+ return false;
+
+ if (WriteUInt(writer, size))
+ return false;
+
+ if (writer->Write(value, static_cast<uint32>(size)))
+ return false;
+
+ return true;
+}
+
+bool WriteEbmlDateElement(IMkvWriter* writer, uint64 type, int64 value) {
+ if (!writer)
+ return false;
+
+ if (WriteID(writer, type))
+ return false;
+
+ if (WriteUInt(writer, kDateElementSize))
+ return false;
+
+ if (SerializeInt(writer, value, kDateElementSize))
+ return false;
+
+ return true;
+}
+
+uint64 WriteFrame(IMkvWriter* writer, const Frame* const frame,
+ Cluster* cluster) {
+ if (!writer || !frame || !frame->IsValid() || !cluster ||
+ !cluster->timecode_scale())
+ return 0;
+
+ // Technically the timecode for a block can be less than the
+ // timecode for the cluster itself (remember that block timecode
+ // is a signed, 16-bit integer). However, as a simplification we
+ // only permit non-negative cluster-relative timecodes for blocks.
+ const int64 relative_timecode = cluster->GetRelativeTimecode(
+ frame->timestamp() / cluster->timecode_scale());
+ if (relative_timecode < 0 || relative_timecode > kMaxBlockTimecode)
+ return 0;
+
+ return frame->CanBeSimpleBlock()
+ ? WriteSimpleBlock(writer, frame, relative_timecode)
+ : WriteBlock(writer, frame, relative_timecode,
+ cluster->timecode_scale());
+}
+
+uint64 WriteVoidElement(IMkvWriter* writer, uint64 size) {
+ if (!writer)
+ return false;
+
+ // Subtract one for the void ID and the coded size.
+ uint64 void_entry_size = size - 1 - GetCodedUIntSize(size - 1);
+ uint64 void_size = EbmlMasterElementSize(libwebm::kMkvVoid, void_entry_size) +
+ void_entry_size;
+
+ if (void_size != size)
+ return 0;
+
+ const int64 payload_position = writer->Position();
+ if (payload_position < 0)
+ return 0;
+
+ if (WriteID(writer, libwebm::kMkvVoid))
+ return 0;
+
+ if (WriteUInt(writer, void_entry_size))
+ return 0;
+
+ const uint8 value = 0;
+ for (int32 i = 0; i < static_cast<int32>(void_entry_size); ++i) {
+ if (writer->Write(&value, 1))
+ return 0;
+ }
+
+ const int64 stop_position = writer->Position();
+ if (stop_position < 0 ||
+ stop_position - payload_position != static_cast<int64>(void_size))
+ return 0;
+
+ return void_size;
+}
+
+void GetVersion(int32* major, int32* minor, int32* build, int32* revision) {
+ *major = 0;
+ *minor = 2;
+ *build = 1;
+ *revision = 0;
+}
+
+uint64 MakeUID(unsigned int* seed) {
+ uint64 uid = 0;
+
+#ifdef __MINGW32__
+ srand(*seed);
+#endif
+
+ for (int i = 0; i < 7; ++i) { // avoid problems with 8-byte values
+ uid <<= 8;
+
+// TODO(fgalligan): Move random number generation to platform specific code.
+#ifdef _MSC_VER
+ (void)seed;
+ const int32 nn = rand();
+#elif __ANDROID__
+ (void)seed;
+ int32 temp_num = 1;
+ int fd = open("/dev/urandom", O_RDONLY);
+ if (fd != -1) {
+ read(fd, &temp_num, sizeof(temp_num));
+ close(fd);
+ }
+ const int32 nn = temp_num;
+#elif defined __MINGW32__
+ const int32 nn = rand();
+#else
+ const int32 nn = rand_r(seed);
+#endif
+ const int32 n = 0xFF & (nn >> 4); // throw away low-order bits
+
+ uid |= n;
+ }
+
+ return uid;
+}
+
+bool IsMatrixCoefficientsValueValid(uint64_t value) {
+ switch (value) {
+ case mkvmuxer::Colour::kGbr:
+ case mkvmuxer::Colour::kBt709:
+ case mkvmuxer::Colour::kUnspecifiedMc:
+ case mkvmuxer::Colour::kReserved:
+ case mkvmuxer::Colour::kFcc:
+ case mkvmuxer::Colour::kBt470bg:
+ case mkvmuxer::Colour::kSmpte170MMc:
+ case mkvmuxer::Colour::kSmpte240MMc:
+ case mkvmuxer::Colour::kYcocg:
+ case mkvmuxer::Colour::kBt2020NonConstantLuminance:
+ case mkvmuxer::Colour::kBt2020ConstantLuminance:
+ return true;
+ }
+ return false;
+}
+
+bool IsChromaSitingHorzValueValid(uint64_t value) {
+ switch (value) {
+ case mkvmuxer::Colour::kUnspecifiedCsh:
+ case mkvmuxer::Colour::kLeftCollocated:
+ case mkvmuxer::Colour::kHalfCsh:
+ return true;
+ }
+ return false;
+}
+
+bool IsChromaSitingVertValueValid(uint64_t value) {
+ switch (value) {
+ case mkvmuxer::Colour::kUnspecifiedCsv:
+ case mkvmuxer::Colour::kTopCollocated:
+ case mkvmuxer::Colour::kHalfCsv:
+ return true;
+ }
+ return false;
+}
+
+bool IsColourRangeValueValid(uint64_t value) {
+ switch (value) {
+ case mkvmuxer::Colour::kUnspecifiedCr:
+ case mkvmuxer::Colour::kBroadcastRange:
+ case mkvmuxer::Colour::kFullRange:
+ case mkvmuxer::Colour::kMcTcDefined:
+ return true;
+ }
+ return false;
+}
+
+bool IsTransferCharacteristicsValueValid(uint64_t value) {
+ switch (value) {
+ case mkvmuxer::Colour::kIturBt709Tc:
+ case mkvmuxer::Colour::kUnspecifiedTc:
+ case mkvmuxer::Colour::kReservedTc:
+ case mkvmuxer::Colour::kGamma22Curve:
+ case mkvmuxer::Colour::kGamma28Curve:
+ case mkvmuxer::Colour::kSmpte170MTc:
+ case mkvmuxer::Colour::kSmpte240MTc:
+ case mkvmuxer::Colour::kLinear:
+ case mkvmuxer::Colour::kLog:
+ case mkvmuxer::Colour::kLogSqrt:
+ case mkvmuxer::Colour::kIec6196624:
+ case mkvmuxer::Colour::kIturBt1361ExtendedColourGamut:
+ case mkvmuxer::Colour::kIec6196621:
+ case mkvmuxer::Colour::kIturBt202010bit:
+ case mkvmuxer::Colour::kIturBt202012bit:
+ case mkvmuxer::Colour::kSmpteSt2084:
+ case mkvmuxer::Colour::kSmpteSt4281Tc:
+ case mkvmuxer::Colour::kAribStdB67Hlg:
+ return true;
+ }
+ return false;
+}
+
+bool IsPrimariesValueValid(uint64_t value) {
+ switch (value) {
+ case mkvmuxer::Colour::kReservedP0:
+ case mkvmuxer::Colour::kIturBt709P:
+ case mkvmuxer::Colour::kUnspecifiedP:
+ case mkvmuxer::Colour::kReservedP3:
+ case mkvmuxer::Colour::kIturBt470M:
+ case mkvmuxer::Colour::kIturBt470Bg:
+ case mkvmuxer::Colour::kSmpte170MP:
+ case mkvmuxer::Colour::kSmpte240MP:
+ case mkvmuxer::Colour::kFilm:
+ case mkvmuxer::Colour::kIturBt2020:
+ case mkvmuxer::Colour::kSmpteSt4281P:
+ case mkvmuxer::Colour::kJedecP22Phosphors:
+ return true;
+ }
+ return false;
+}
+
+} // namespace mkvmuxer
diff --git a/mkvmuxer/mkvmuxerutil.h b/mkvmuxer/mkvmuxerutil.h
new file mode 100644
index 0000000..3355428
--- /dev/null
+++ b/mkvmuxer/mkvmuxerutil.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef MKVMUXER_MKVMUXERUTIL_H_
+#define MKVMUXER_MKVMUXERUTIL_H_
+
+#include "mkvmuxertypes.h"
+
+#include "stdint.h"
+
+namespace mkvmuxer {
+class Cluster;
+class Frame;
+class IMkvWriter;
+
+// TODO(tomfinegan): mkvmuxer:: integer types continue to be used here because
+// changing them causes pain for downstream projects. It would be nice if a
+// solution that allows removal of the mkvmuxer:: integer types while avoiding
+// pain for downstream users of libwebm. Considering that mkvmuxerutil.{cc,h}
+// are really, for the great majority of cases, EBML size calculation and writer
+// functions, perhaps a more EBML focused utility would be the way to go as a
+// first step.
+
+const uint64 kEbmlUnknownValue = 0x01FFFFFFFFFFFFFFULL;
+const int64 kMaxBlockTimecode = 0x07FFFLL;
+
+// Writes out |value| in Big Endian order. Returns 0 on success.
+int32 SerializeInt(IMkvWriter* writer, int64 value, int32 size);
+
+// Writes out |f| in Big Endian order. Returns 0 on success.
+int32 SerializeFloat(IMkvWriter* writer, float f);
+
+// Returns the size in bytes of the element.
+int32 GetUIntSize(uint64 value);
+int32 GetIntSize(int64 value);
+int32 GetCodedUIntSize(uint64 value);
+uint64 EbmlMasterElementSize(uint64 type, uint64 value);
+uint64 EbmlElementSize(uint64 type, int64 value);
+uint64 EbmlElementSize(uint64 type, uint64 value);
+uint64 EbmlElementSize(uint64 type, float value);
+uint64 EbmlElementSize(uint64 type, const char* value);
+uint64 EbmlElementSize(uint64 type, const uint8* value, uint64 size);
+uint64 EbmlDateElementSize(uint64 type);
+
+// Returns the size in bytes of the element assuming that the element was
+// written using |fixed_size| bytes. If |fixed_size| is set to zero, then it
+// computes the necessary number of bytes based on |value|.
+uint64 EbmlElementSize(uint64 type, uint64 value, uint64 fixed_size);
+
+// Creates an EBML coded number from |value| and writes it out. The size of
+// the coded number is determined by the value of |value|. |value| must not
+// be in a coded form. Returns 0 on success.
+int32 WriteUInt(IMkvWriter* writer, uint64 value);
+
+// Creates an EBML coded number from |value| and writes it out. The size of
+// the coded number is determined by the value of |size|. |value| must not
+// be in a coded form. Returns 0 on success.
+int32 WriteUIntSize(IMkvWriter* writer, uint64 value, int32 size);
+
+// Output an Mkv master element. Returns true if the element was written.
+bool WriteEbmlMasterElement(IMkvWriter* writer, uint64 value, uint64 size);
+
+// Outputs an Mkv ID, calls |IMkvWriter::ElementStartNotify|, and passes the
+// ID to |SerializeInt|. Returns 0 on success.
+int32 WriteID(IMkvWriter* writer, uint64 type);
+
+// Output an Mkv non-master element. Returns true if the element was written.
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value);
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, int64 value);
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, float value);
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const char* value);
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, const uint8* value,
+ uint64 size);
+bool WriteEbmlDateElement(IMkvWriter* writer, uint64 type, int64 value);
+
+// Output an Mkv non-master element using fixed size. The element will be
+// written out using exactly |fixed_size| bytes. If |fixed_size| is set to zero
+// then it computes the necessary number of bytes based on |value|. Returns true
+// if the element was written.
+bool WriteEbmlElement(IMkvWriter* writer, uint64 type, uint64 value,
+ uint64 fixed_size);
+
+// Output a Mkv Frame. It decides the correct element to write (Block vs
+// SimpleBlock) based on the parameters of the Frame.
+uint64 WriteFrame(IMkvWriter* writer, const Frame* const frame,
+ Cluster* cluster);
+
+// Output a void element. |size| must be the entire size in bytes that will be
+// void. The function will calculate the size of the void header and subtract
+// it from |size|.
+uint64 WriteVoidElement(IMkvWriter* writer, uint64 size);
+
+// Returns the version number of the muxer in |major|, |minor|, |build|,
+// and |revision|.
+void GetVersion(int32* major, int32* minor, int32* build, int32* revision);
+
+// Returns a random number to be used for UID, using |seed| to seed
+// the random-number generator (see POSIX rand_r() for semantics).
+uint64 MakeUID(unsigned int* seed);
+
+// Colour field validation helpers. All return true when |value| is valid.
+bool IsMatrixCoefficientsValueValid(uint64_t value);
+bool IsChromaSitingHorzValueValid(uint64_t value);
+bool IsChromaSitingVertValueValid(uint64_t value);
+bool IsColourRangeValueValid(uint64_t value);
+bool IsTransferCharacteristicsValueValid(uint64_t value);
+bool IsPrimariesValueValid(uint64_t value);
+
+} // namespace mkvmuxer
+
+#endif // MKVMUXER_MKVMUXERUTIL_H_
diff --git a/mkvmuxer/mkvwriter.cc b/mkvmuxer/mkvwriter.cc
new file mode 100644
index 0000000..d668384
--- /dev/null
+++ b/mkvmuxer/mkvwriter.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#include "mkvmuxer/mkvwriter.h"
+
+#include <sys/types.h>
+
+#ifdef _MSC_VER
+#include <share.h> // for _SH_DENYWR
+#endif
+
+namespace mkvmuxer {
+
+MkvWriter::MkvWriter() : file_(NULL), writer_owns_file_(true) {}
+
+MkvWriter::MkvWriter(FILE* fp) : file_(fp), writer_owns_file_(false) {}
+
+MkvWriter::~MkvWriter() { Close(); }
+
+int32 MkvWriter::Write(const void* buffer, uint32 length) {
+ if (!file_)
+ return -1;
+
+ if (length == 0)
+ return 0;
+
+ if (buffer == NULL)
+ return -1;
+
+ const size_t bytes_written = fwrite(buffer, 1, length, file_);
+
+ return (bytes_written == length) ? 0 : -1;
+}
+
+bool MkvWriter::Open(const char* filename) {
+ if (filename == NULL)
+ return false;
+
+ if (file_)
+ return false;
+
+#ifdef _MSC_VER
+ file_ = _fsopen(filename, "wb", _SH_DENYWR);
+#else
+ file_ = fopen(filename, "wb");
+#endif
+ if (file_ == NULL)
+ return false;
+ return true;
+}
+
+void MkvWriter::Close() {
+ if (file_ && writer_owns_file_) {
+ fclose(file_);
+ }
+ file_ = NULL;
+}
+
+int64 MkvWriter::Position() const {
+ if (!file_)
+ return 0;
+
+#ifdef _MSC_VER
+ return _ftelli64(file_);
+#else
+ return ftell(file_);
+#endif
+}
+
+int32 MkvWriter::Position(int64 position) {
+ if (!file_)
+ return -1;
+
+#ifdef _MSC_VER
+ return _fseeki64(file_, position, SEEK_SET);
+#elif defined(_WIN32)
+ return fseeko64(file_, static_cast<off_t>(position), SEEK_SET);
+#else
+ return fseeko(file_, static_cast<off_t>(position), SEEK_SET);
+#endif
+}
+
+bool MkvWriter::Seekable() const { return true; }
+
+void MkvWriter::ElementStartNotify(uint64, int64) {}
+
+} // namespace mkvmuxer
diff --git a/mkvmuxer/mkvwriter.h b/mkvmuxer/mkvwriter.h
new file mode 100644
index 0000000..4227c63
--- /dev/null
+++ b/mkvmuxer/mkvwriter.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef MKVMUXER_MKVWRITER_H_
+#define MKVMUXER_MKVWRITER_H_
+
+#include <stdio.h>
+
+#include "mkvmuxer/mkvmuxer.h"
+#include "mkvmuxer/mkvmuxertypes.h"
+
+namespace mkvmuxer {
+
+// Default implementation of the IMkvWriter interface on Windows.
+class MkvWriter : public IMkvWriter {
+ public:
+ MkvWriter();
+ explicit MkvWriter(FILE* fp);
+ virtual ~MkvWriter();
+
+ // IMkvWriter interface
+ virtual int64 Position() const;
+ virtual int32 Position(int64 position);
+ virtual bool Seekable() const;
+ virtual int32 Write(const void* buffer, uint32 length);
+ virtual void ElementStartNotify(uint64 element_id, int64 position);
+
+ // Creates and opens a file for writing. |filename| is the name of the file
+ // to open. This function will overwrite the contents of |filename|. Returns
+ // true on success.
+ bool Open(const char* filename);
+
+ // Closes an opened file.
+ void Close();
+
+ private:
+ // File handle to output file.
+ FILE* file_;
+ bool writer_owns_file_;
+
+ LIBWEBM_DISALLOW_COPY_AND_ASSIGN(MkvWriter);
+};
+
+} // namespace mkvmuxer
+
+#endif // MKVMUXER_MKVWRITER_H_
diff --git a/mkvmuxer_sample.cc b/mkvmuxer_sample.cc
new file mode 100644
index 0000000..9ef5569
--- /dev/null
+++ b/mkvmuxer_sample.cc
@@ -0,0 +1,802 @@
+// Copyright (c) 2011 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include <stdint.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <list>
+#include <memory>
+#include <string>
+
+// libwebm common includes.
+#include "common/file_util.h"
+#include "common/hdr_util.h"
+
+// libwebm mkvparser includes
+#include "mkvparser/mkvparser.h"
+#include "mkvparser/mkvreader.h"
+
+// libwebm mkvmuxer includes
+#include "mkvmuxer/mkvmuxer.h"
+#include "mkvmuxer/mkvmuxertypes.h"
+#include "mkvmuxer/mkvwriter.h"
+
+#include "sample_muxer_metadata.h"
+
+namespace {
+
+void Usage() {
+ printf("Usage: mkvmuxer_sample -i input -o output [options]\n");
+ printf("\n");
+ printf("Main options:\n");
+ printf(" -h | -? show help\n");
+ printf(" -video <int> >0 outputs video\n");
+ printf(" -audio <int> >0 outputs audio\n");
+ printf(" -live <int> >0 puts the muxer into live mode\n");
+ printf(" 0 puts the muxer into file mode\n");
+ printf(" -output_cues <int> >0 outputs cues element\n");
+ printf(" -cues_on_video_track <int> >0 outputs cues on video track\n");
+ printf(" -cues_on_audio_track <int> >0 outputs cues on audio track\n");
+ printf(" -max_cluster_duration <double> in seconds\n");
+ printf(" -max_cluster_size <int> in bytes\n");
+ printf(" -switch_tracks <int> >0 switches tracks in output\n");
+ printf(" -audio_track_number <int> >0 Changes the audio track number\n");
+ printf(" -video_track_number <int> >0 Changes the video track number\n");
+ printf(" -chunking <string> Chunk output\n");
+ printf(" -copy_tags <int> >0 Copies the tags\n");
+ printf(" -accurate_cluster_duration <int> ");
+ printf(">0 Writes the last frame in each cluster with Duration\n");
+ printf(" -fixed_size_cluster_timecode <int> ");
+ printf(">0 Writes the cluster timecode using exactly 8 bytes\n");
+ printf(" -copy_input_duration >0 Copies the input duration\n");
+ printf("\n");
+ printf("Video options:\n");
+ printf(" -display_width <int> Display width in pixels\n");
+ printf(" -display_height <int> Display height in pixels\n");
+ printf(" -pixel_width <int> Override pixel width\n");
+ printf(" -pixel_height <int> Override pixel height\n");
+ printf(" -projection_type <int> Set/override projection type:\n");
+ printf(" 0: Rectangular\n");
+ printf(" 1: Equirectangular\n");
+ printf(" 2: Cube map\n");
+ printf(" 3: Mesh\n");
+ printf(" -projection_file <string> Override projection private data");
+ printf(" with contents of this file\n");
+ printf(" -projection_pose_yaw <float> Projection pose yaw\n");
+ printf(" -projection_pose_pitch <float> Projection pose pitch\n");
+ printf(" -projection_pose_roll <float> Projection pose roll\n");
+ printf(" -stereo_mode <int> 3D video mode\n");
+ printf("\n");
+ printf("VP9 options:\n");
+ printf(" -profile <int> VP9 profile\n");
+ printf(" -level <int> VP9 level\n");
+ printf("\n");
+ printf("Cues options:\n");
+ printf(" -output_cues_block_number <int> >0 outputs cue block number\n");
+ printf(" -cues_before_clusters <int> >0 puts Cues before Clusters\n");
+ printf("\n");
+ printf("Metadata options:\n");
+ printf(" -webvtt-subtitles <vttfile> ");
+ printf("add WebVTT subtitles as metadata track\n");
+ printf(" -webvtt-captions <vttfile> ");
+ printf("add WebVTT captions as metadata track\n");
+ printf(" -webvtt-descriptions <vttfile> ");
+ printf("add WebVTT descriptions as metadata track\n");
+ printf(" -webvtt-metadata <vttfile> ");
+ printf("add WebVTT subtitles as metadata track\n");
+ printf(" -webvtt-chapters <vttfile> ");
+ printf("add WebVTT chapters as MKV chapters element\n");
+}
+
+struct MetadataFile {
+ const char* name;
+ SampleMuxerMetadata::Kind kind;
+};
+
+typedef std::list<MetadataFile> metadata_files_t;
+
+// Cache the WebVTT filenames specified as command-line args.
+bool LoadMetadataFiles(const metadata_files_t& files,
+ SampleMuxerMetadata* metadata) {
+ typedef metadata_files_t::const_iterator iter_t;
+
+ iter_t i = files.begin();
+ const iter_t j = files.end();
+
+ while (i != j) {
+ const metadata_files_t::value_type& v = *i++;
+
+ if (!metadata->Load(v.name, v.kind))
+ return false;
+ }
+
+ return true;
+}
+
+int ParseArgWebVTT(char* argv[], int* argv_index, int argc_check,
+ metadata_files_t* metadata_files) {
+ int& i = *argv_index;
+
+ enum { kCount = 5 };
+ struct Arg {
+ const char* name;
+ SampleMuxerMetadata::Kind kind;
+ };
+ const Arg args[kCount] = {
+ {"-webvtt-subtitles", SampleMuxerMetadata::kSubtitles},
+ {"-webvtt-captions", SampleMuxerMetadata::kCaptions},
+ {"-webvtt-descriptions", SampleMuxerMetadata::kDescriptions},
+ {"-webvtt-metadata", SampleMuxerMetadata::kMetadata},
+ {"-webvtt-chapters", SampleMuxerMetadata::kChapters}};
+
+ for (int idx = 0; idx < kCount; ++idx) {
+ const Arg& arg = args[idx];
+
+ if (strcmp(arg.name, argv[i]) != 0) // no match
+ continue;
+
+ ++i; // consume arg name here
+
+ if (i > argc_check) {
+ printf("missing value for %s\n", arg.name);
+ return -1; // error
+ }
+
+ MetadataFile f;
+ f.name = argv[i]; // arg value is consumed via caller's loop idx
+ f.kind = arg.kind;
+
+ metadata_files->push_back(f);
+ return 1; // successfully parsed WebVTT arg
+ }
+
+ return 0; // not a WebVTT arg
+}
+
+bool CopyVideoProjection(const mkvparser::Projection& parser_projection,
+ mkvmuxer::Projection* muxer_projection) {
+ typedef mkvmuxer::Projection::ProjectionType MuxerProjType;
+ const int kTypeNotPresent = mkvparser::Projection::kTypeNotPresent;
+ if (parser_projection.type != kTypeNotPresent) {
+ muxer_projection->set_type(
+ static_cast<MuxerProjType>(parser_projection.type));
+ }
+ if (parser_projection.private_data &&
+ parser_projection.private_data_length > 0) {
+ if (!muxer_projection->SetProjectionPrivate(
+ parser_projection.private_data,
+ parser_projection.private_data_length)) {
+ return false;
+ }
+ }
+
+ const float kValueNotPresent = mkvparser::Projection::kValueNotPresent;
+ if (parser_projection.pose_yaw != kValueNotPresent)
+ muxer_projection->set_pose_yaw(parser_projection.pose_yaw);
+ if (parser_projection.pose_pitch != kValueNotPresent)
+ muxer_projection->set_pose_pitch(parser_projection.pose_pitch);
+ if (parser_projection.pose_roll != kValueNotPresent)
+ muxer_projection->set_pose_roll(parser_projection.pose_roll);
+ return true;
+}
+} // end namespace
+
+int main(int argc, char* argv[]) {
+ char* input = NULL;
+ char* output = NULL;
+
+ // Segment variables
+ bool output_video = true;
+ bool output_audio = true;
+ bool live_mode = false;
+ bool output_cues = true;
+ bool cues_before_clusters = false;
+ bool cues_on_video_track = true;
+ bool cues_on_audio_track = false;
+ uint64_t max_cluster_duration = 0;
+ uint64_t max_cluster_size = 0;
+ bool switch_tracks = false;
+ int audio_track_number = 0; // 0 tells muxer to decide.
+ int video_track_number = 0; // 0 tells muxer to decide.
+ bool chunking = false;
+ bool copy_tags = false;
+ const char* chunk_name = NULL;
+ bool accurate_cluster_duration = false;
+ bool fixed_size_cluster_timecode = false;
+ bool copy_input_duration = false;
+
+ bool output_cues_block_number = true;
+
+ uint64_t display_width = 0;
+ uint64_t display_height = 0;
+ uint64_t pixel_width = 0;
+ uint64_t pixel_height = 0;
+ uint64_t stereo_mode = 0;
+ const char* projection_file = 0;
+ int64_t projection_type = mkvparser::Projection::kTypeNotPresent;
+ float projection_pose_roll = mkvparser::Projection::kValueNotPresent;
+ float projection_pose_pitch = mkvparser::Projection::kValueNotPresent;
+ float projection_pose_yaw = mkvparser::Projection::kValueNotPresent;
+ int vp9_profile = -1; // No profile set.
+ int vp9_level = -1; // No level set.
+
+ metadata_files_t metadata_files;
+
+ const int argc_check = argc - 1;
+ for (int i = 1; i < argc; ++i) {
+ char* end;
+
+ if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) {
+ Usage();
+ return EXIT_SUCCESS;
+ } else if (!strcmp("-i", argv[i]) && i < argc_check) {
+ input = argv[++i];
+ } else if (!strcmp("-o", argv[i]) && i < argc_check) {
+ output = argv[++i];
+ } else if (!strcmp("-video", argv[i]) && i < argc_check) {
+ output_video = strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (!strcmp("-audio", argv[i]) && i < argc_check) {
+ output_audio = strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (!strcmp("-live", argv[i]) && i < argc_check) {
+ live_mode = strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (!strcmp("-output_cues", argv[i]) && i < argc_check) {
+ output_cues = strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (!strcmp("-cues_before_clusters", argv[i]) && i < argc_check) {
+ cues_before_clusters = strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (!strcmp("-cues_on_video_track", argv[i]) && i < argc_check) {
+ cues_on_video_track = strtol(argv[++i], &end, 10) == 0 ? false : true;
+ if (cues_on_video_track)
+ cues_on_audio_track = false;
+ } else if (!strcmp("-cues_on_audio_track", argv[i]) && i < argc_check) {
+ cues_on_audio_track = strtol(argv[++i], &end, 10) == 0 ? false : true;
+ if (cues_on_audio_track)
+ cues_on_video_track = false;
+ } else if (!strcmp("-max_cluster_duration", argv[i]) && i < argc_check) {
+ const double seconds = strtod(argv[++i], &end);
+ max_cluster_duration = static_cast<uint64_t>(seconds * 1000000000.0);
+ } else if (!strcmp("-max_cluster_size", argv[i]) && i < argc_check) {
+ max_cluster_size = strtol(argv[++i], &end, 10);
+ } else if (!strcmp("-switch_tracks", argv[i]) && i < argc_check) {
+ switch_tracks = strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (!strcmp("-audio_track_number", argv[i]) && i < argc_check) {
+ audio_track_number = static_cast<int>(strtol(argv[++i], &end, 10));
+ } else if (!strcmp("-video_track_number", argv[i]) && i < argc_check) {
+ video_track_number = static_cast<int>(strtol(argv[++i], &end, 10));
+ } else if (!strcmp("-chunking", argv[i]) && i < argc_check) {
+ chunking = true;
+ chunk_name = argv[++i];
+ } else if (!strcmp("-copy_tags", argv[i]) && i < argc_check) {
+ copy_tags = strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (!strcmp("-accurate_cluster_duration", argv[i]) &&
+ i < argc_check) {
+ accurate_cluster_duration =
+ strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (!strcmp("-fixed_size_cluster_timecode", argv[i]) &&
+ i < argc_check) {
+ fixed_size_cluster_timecode =
+ strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (!strcmp("-copy_input_duration", argv[i]) && i < argc_check) {
+ copy_input_duration = strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (!strcmp("-display_width", argv[i]) && i < argc_check) {
+ display_width = strtol(argv[++i], &end, 10);
+ } else if (!strcmp("-display_height", argv[i]) && i < argc_check) {
+ display_height = strtol(argv[++i], &end, 10);
+ } else if (!strcmp("-pixel_width", argv[i]) && i < argc_check) {
+ pixel_width = strtol(argv[++i], &end, 10);
+ } else if (!strcmp("-pixel_height", argv[i]) && i < argc_check) {
+ pixel_height = strtol(argv[++i], &end, 10);
+ } else if (!strcmp("-stereo_mode", argv[i]) && i < argc_check) {
+ stereo_mode = strtol(argv[++i], &end, 10);
+ } else if (!strcmp("-projection_type", argv[i]) && i < argc_check) {
+ projection_type = strtol(argv[++i], &end, 10);
+ } else if (!strcmp("-projection_file", argv[i]) && i < argc_check) {
+ projection_file = argv[++i];
+ } else if (!strcmp("-projection_pose_roll", argv[i]) && i < argc_check) {
+ projection_pose_roll = strtof(argv[++i], &end);
+ } else if (!strcmp("-projection_pose_pitch", argv[i]) && i < argc_check) {
+ projection_pose_pitch = strtof(argv[++i], &end);
+ } else if (!strcmp("-projection_pose_yaw", argv[i]) && i < argc_check) {
+ projection_pose_yaw = strtof(argv[++i], &end);
+ } else if (!strcmp("-profile", argv[i]) && i < argc_check) {
+ vp9_profile = static_cast<int>(strtol(argv[++i], &end, 10));
+ } else if (!strcmp("-level", argv[i]) && i < argc_check) {
+ vp9_level = static_cast<int>(strtol(argv[++i], &end, 10));
+ } else if (!strcmp("-output_cues_block_number", argv[i]) &&
+ i < argc_check) {
+ output_cues_block_number =
+ strtol(argv[++i], &end, 10) == 0 ? false : true;
+ } else if (int e = ParseArgWebVTT(argv, &i, argc_check, &metadata_files)) {
+ if (e < 0)
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (input == NULL || output == NULL) {
+ Usage();
+ return EXIT_FAILURE;
+ }
+
+ // Get parser header info
+ mkvparser::MkvReader reader;
+
+ if (reader.Open(input)) {
+ printf("\n Filename is invalid or error while opening.\n");
+ return EXIT_FAILURE;
+ }
+
+ long long pos = 0;
+ mkvparser::EBMLHeader ebml_header;
+ long long ret = ebml_header.Parse(&reader, pos);
+ if (ret) {
+ printf("\n EBMLHeader::Parse() failed.");
+ return EXIT_FAILURE;
+ }
+
+ mkvparser::Segment* parser_segment_;
+ ret = mkvparser::Segment::CreateInstance(&reader, pos, parser_segment_);
+ if (ret) {
+ printf("\n Segment::CreateInstance() failed.");
+ return EXIT_FAILURE;
+ }
+
+ const std::unique_ptr<mkvparser::Segment> parser_segment(parser_segment_);
+ ret = parser_segment->Load();
+ if (ret < 0) {
+ printf("\n Segment::Load() failed.");
+ return EXIT_FAILURE;
+ }
+
+ const mkvparser::SegmentInfo* const segment_info = parser_segment->GetInfo();
+ if (segment_info == NULL) {
+ printf("\n Segment::GetInfo() failed.");
+ return EXIT_FAILURE;
+ }
+ const long long timeCodeScale = segment_info->GetTimeCodeScale();
+
+ // Set muxer header info
+ mkvmuxer::MkvWriter writer;
+
+ const std::string temp_file =
+ cues_before_clusters ? libwebm::GetTempFileName() : output;
+ if (!writer.Open(temp_file.c_str())) {
+ printf("\n Filename is invalid or error while opening.\n");
+ return EXIT_FAILURE;
+ }
+
+ // Set Segment element attributes
+ mkvmuxer::Segment muxer_segment;
+
+ if (!muxer_segment.Init(&writer)) {
+ printf("\n Could not initialize muxer segment!\n");
+ return EXIT_FAILURE;
+ }
+
+ muxer_segment.AccurateClusterDuration(accurate_cluster_duration);
+ muxer_segment.UseFixedSizeClusterTimecode(fixed_size_cluster_timecode);
+
+ if (live_mode)
+ muxer_segment.set_mode(mkvmuxer::Segment::kLive);
+ else
+ muxer_segment.set_mode(mkvmuxer::Segment::kFile);
+
+ if (chunking)
+ muxer_segment.SetChunking(true, chunk_name);
+
+ if (max_cluster_duration > 0)
+ muxer_segment.set_max_cluster_duration(max_cluster_duration);
+ if (max_cluster_size > 0)
+ muxer_segment.set_max_cluster_size(max_cluster_size);
+ muxer_segment.OutputCues(output_cues);
+
+ // Set SegmentInfo element attributes
+ mkvmuxer::SegmentInfo* const info = muxer_segment.GetSegmentInfo();
+ info->set_timecode_scale(timeCodeScale);
+ info->set_writing_app("mkvmuxer_sample");
+
+ const mkvparser::Tags* const tags = parser_segment->GetTags();
+ if (copy_tags && tags) {
+ for (int i = 0; i < tags->GetTagCount(); i++) {
+ const mkvparser::Tags::Tag* const tag = tags->GetTag(i);
+ mkvmuxer::Tag* muxer_tag = muxer_segment.AddTag();
+
+ for (int j = 0; j < tag->GetSimpleTagCount(); j++) {
+ const mkvparser::Tags::SimpleTag* const simple_tag =
+ tag->GetSimpleTag(j);
+ muxer_tag->add_simple_tag(simple_tag->GetTagName(),
+ simple_tag->GetTagString());
+ }
+ }
+ }
+
+ // Set Tracks element attributes
+ const mkvparser::Tracks* const parser_tracks = parser_segment->GetTracks();
+ unsigned long i = 0;
+ uint64_t vid_track = 0; // no track added
+ uint64_t aud_track = 0; // no track added
+
+ using mkvparser::Track;
+
+ while (i != parser_tracks->GetTracksCount()) {
+ unsigned long track_num = i++;
+ if (switch_tracks)
+ track_num = i % parser_tracks->GetTracksCount();
+
+ const Track* const parser_track = parser_tracks->GetTrackByIndex(track_num);
+
+ if (parser_track == NULL)
+ continue;
+
+ // TODO(fgalligan): Add support for language to parser.
+ const char* const track_name = parser_track->GetNameAsUTF8();
+
+ const long long track_type = parser_track->GetType();
+
+ if (track_type == Track::kVideo && output_video) {
+ // Get the video track from the parser
+ const mkvparser::VideoTrack* const pVideoTrack =
+ static_cast<const mkvparser::VideoTrack*>(parser_track);
+ const long long width = pVideoTrack->GetWidth();
+ const long long height = pVideoTrack->GetHeight();
+
+ // Add the video track to the muxer
+ vid_track = muxer_segment.AddVideoTrack(static_cast<int>(width),
+ static_cast<int>(height),
+ video_track_number);
+ if (!vid_track) {
+ printf("\n Could not add video track.\n");
+ return EXIT_FAILURE;
+ }
+
+ mkvmuxer::VideoTrack* const video = static_cast<mkvmuxer::VideoTrack*>(
+ muxer_segment.GetTrackByNumber(vid_track));
+ if (!video) {
+ printf("\n Could not get video track.\n");
+ return EXIT_FAILURE;
+ }
+
+ if (pVideoTrack->GetColour()) {
+ mkvmuxer::Colour muxer_colour;
+ if (!libwebm::CopyColour(*pVideoTrack->GetColour(), &muxer_colour))
+ return EXIT_FAILURE;
+ if (!video->SetColour(muxer_colour))
+ return EXIT_FAILURE;
+ }
+
+ if (pVideoTrack->GetProjection() ||
+ projection_type != mkvparser::Projection::kTypeNotPresent) {
+ mkvmuxer::Projection muxer_projection;
+ const mkvparser::Projection* const parser_projection =
+ pVideoTrack->GetProjection();
+ typedef mkvmuxer::Projection::ProjectionType MuxerProjType;
+ if (parser_projection &&
+ !CopyVideoProjection(*parser_projection, &muxer_projection)) {
+ printf("\n Unable to copy video projection.\n");
+ return EXIT_FAILURE;
+ }
+ // Override the values that came from parser if set on command line.
+ if (projection_type != mkvparser::Projection::kTypeNotPresent) {
+ muxer_projection.set_type(
+ static_cast<MuxerProjType>(projection_type));
+ if (projection_type == mkvparser::Projection::kRectangular &&
+ projection_file != NULL) {
+ printf("\n Rectangular projection must not have private data.\n");
+ return EXIT_FAILURE;
+ } else if ((projection_type == mkvparser::Projection::kCubeMap ||
+ projection_type == mkvparser::Projection::kMesh) &&
+ projection_file == NULL) {
+ printf("\n Mesh or CubeMap projection must have private data.\n");
+ return EXIT_FAILURE;
+ }
+ if (projection_file != NULL) {
+ std::string contents;
+ if (!libwebm::GetFileContents(projection_file, &contents) ||
+ contents.size() == 0) {
+ printf("\n Failed to read file \"%s\" or file is empty\n",
+ projection_file);
+ return EXIT_FAILURE;
+ }
+ if (!muxer_projection.SetProjectionPrivate(
+ reinterpret_cast<uint8_t*>(&contents[0]),
+ contents.size())) {
+ printf("\n Failed to SetProjectionPrivate of length %zu.\n",
+ contents.size());
+ return EXIT_FAILURE;
+ }
+ }
+ }
+ const float kValueNotPresent = mkvparser::Projection::kValueNotPresent;
+ if (projection_pose_yaw != kValueNotPresent)
+ muxer_projection.set_pose_yaw(projection_pose_yaw);
+ if (projection_pose_pitch != kValueNotPresent)
+ muxer_projection.set_pose_pitch(projection_pose_pitch);
+ if (projection_pose_roll != kValueNotPresent)
+ muxer_projection.set_pose_roll(projection_pose_roll);
+
+ if (!video->SetProjection(muxer_projection))
+ return EXIT_FAILURE;
+ }
+
+ if (track_name)
+ video->set_name(track_name);
+
+ video->set_codec_id(pVideoTrack->GetCodecId());
+
+ if (display_width > 0)
+ video->set_display_width(display_width);
+ if (display_height > 0)
+ video->set_display_height(display_height);
+ if (pixel_width > 0)
+ video->set_pixel_width(pixel_width);
+ if (pixel_height > 0)
+ video->set_pixel_height(pixel_height);
+ if (stereo_mode > 0)
+ video->SetStereoMode(stereo_mode);
+
+ const double rate = pVideoTrack->GetFrameRate();
+ if (rate > 0.0) {
+ video->set_frame_rate(rate);
+ }
+
+ size_t parser_private_size;
+ const unsigned char* const parser_private_data =
+ pVideoTrack->GetCodecPrivate(parser_private_size);
+
+ if (!strcmp(video->codec_id(), mkvmuxer::Tracks::kAv1CodecId)) {
+ if (parser_private_data == NULL || parser_private_size == 0) {
+ printf("AV1 input track has no CodecPrivate. %s is invalid.", input);
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (!strcmp(video->codec_id(), mkvmuxer::Tracks::kVp9CodecId) &&
+ (vp9_profile >= 0 || vp9_level >= 0)) {
+ const int kMaxVp9PrivateSize = 6;
+ unsigned char vp9_private_data[kMaxVp9PrivateSize];
+ int vp9_private_size = 0;
+ if (vp9_profile >= 0) {
+ if (vp9_profile < 0 || vp9_profile > 3) {
+ printf("\n VP9 profile(%d) is not valid.\n", vp9_profile);
+ return EXIT_FAILURE;
+ }
+ const uint8_t kVp9ProfileId = 1;
+ const uint8_t kVp9ProfileIdLength = 1;
+ vp9_private_data[vp9_private_size++] = kVp9ProfileId;
+ vp9_private_data[vp9_private_size++] = kVp9ProfileIdLength;
+ vp9_private_data[vp9_private_size++] = vp9_profile;
+ }
+
+ if (vp9_level >= 0) {
+ const int kNumLevels = 14;
+ const int levels[kNumLevels] = {10, 11, 20, 21, 30, 31, 40,
+ 41, 50, 51, 52, 60, 61, 62};
+ bool level_is_valid = false;
+ for (int i = 0; i < kNumLevels; ++i) {
+ if (vp9_level == levels[i]) {
+ level_is_valid = true;
+ break;
+ }
+ }
+ if (!level_is_valid) {
+ printf("\n VP9 level(%d) is not valid.\n", vp9_level);
+ return EXIT_FAILURE;
+ }
+ const uint8_t kVp9LevelId = 2;
+ const uint8_t kVp9LevelIdLength = 1;
+ vp9_private_data[vp9_private_size++] = kVp9LevelId;
+ vp9_private_data[vp9_private_size++] = kVp9LevelIdLength;
+ vp9_private_data[vp9_private_size++] = vp9_level;
+ }
+ if (!video->SetCodecPrivate(vp9_private_data, vp9_private_size)) {
+ printf("\n Could not add video private data.\n");
+ return EXIT_FAILURE;
+ }
+ } else if (parser_private_data && parser_private_size > 0) {
+ if (!video->SetCodecPrivate(parser_private_data, parser_private_size)) {
+ printf("\n Could not add video private data.\n");
+ return EXIT_FAILURE;
+ }
+ }
+ } else if (track_type == Track::kAudio && output_audio) {
+ // Get the audio track from the parser
+ const mkvparser::AudioTrack* const pAudioTrack =
+ static_cast<const mkvparser::AudioTrack*>(parser_track);
+ const long long channels = pAudioTrack->GetChannels();
+ const double sample_rate = pAudioTrack->GetSamplingRate();
+
+ // Add the audio track to the muxer
+ aud_track = muxer_segment.AddAudioTrack(static_cast<int>(sample_rate),
+ static_cast<int>(channels),
+ audio_track_number);
+ if (!aud_track) {
+ printf("\n Could not add audio track.\n");
+ return EXIT_FAILURE;
+ }
+
+ mkvmuxer::AudioTrack* const audio = static_cast<mkvmuxer::AudioTrack*>(
+ muxer_segment.GetTrackByNumber(aud_track));
+ if (!audio) {
+ printf("\n Could not get audio track.\n");
+ return EXIT_FAILURE;
+ }
+
+ if (track_name)
+ audio->set_name(track_name);
+
+ audio->set_codec_id(pAudioTrack->GetCodecId());
+
+ size_t private_size;
+ const unsigned char* const private_data =
+ pAudioTrack->GetCodecPrivate(private_size);
+ if (private_size > 0) {
+ if (!audio->SetCodecPrivate(private_data, private_size)) {
+ printf("\n Could not add audio private data.\n");
+ return EXIT_FAILURE;
+ }
+ }
+
+ const long long bit_depth = pAudioTrack->GetBitDepth();
+ if (bit_depth > 0)
+ audio->set_bit_depth(bit_depth);
+
+ if (pAudioTrack->GetCodecDelay())
+ audio->set_codec_delay(pAudioTrack->GetCodecDelay());
+ if (pAudioTrack->GetSeekPreRoll())
+ audio->set_seek_pre_roll(pAudioTrack->GetSeekPreRoll());
+ }
+ }
+
+ // We have created all the video and audio tracks. If any WebVTT
+ // files were specified as command-line args, then parse them and
+ // add a track to the output file corresponding to each metadata
+ // input file.
+
+ SampleMuxerMetadata metadata;
+
+ if (!metadata.Init(&muxer_segment)) {
+ printf("\n Could not initialize metadata cache.\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!LoadMetadataFiles(metadata_files, &metadata))
+ return EXIT_FAILURE;
+
+ if (!metadata.AddChapters())
+ return EXIT_FAILURE;
+
+ // Set Cues element attributes
+ mkvmuxer::Cues* const cues = muxer_segment.GetCues();
+ cues->set_output_block_number(output_cues_block_number);
+ if (cues_on_video_track && vid_track)
+ muxer_segment.CuesTrack(vid_track);
+ if (cues_on_audio_track && aud_track)
+ muxer_segment.CuesTrack(aud_track);
+
+ // Write clusters
+ unsigned char* data = NULL;
+ long data_len = 0;
+
+ const mkvparser::Cluster* cluster = parser_segment->GetFirst();
+
+ while (cluster != NULL && !cluster->EOS()) {
+ const mkvparser::BlockEntry* block_entry;
+
+ long status = cluster->GetFirst(block_entry);
+
+ if (status) {
+ printf("\n Could not get first block of cluster.\n");
+ return EXIT_FAILURE;
+ }
+
+ while (block_entry != NULL && !block_entry->EOS()) {
+ const mkvparser::Block* const block = block_entry->GetBlock();
+ const long long trackNum = block->GetTrackNumber();
+ const mkvparser::Track* const parser_track =
+ parser_tracks->GetTrackByNumber(static_cast<unsigned long>(trackNum));
+
+ // When |parser_track| is NULL, it means that the track number in the
+ // Block is invalid (i.e.) the was no TrackEntry corresponding to the
+ // track number. So we reject the file.
+ if (!parser_track) {
+ return EXIT_FAILURE;
+ }
+
+ const long long track_type = parser_track->GetType();
+ const long long time_ns = block->GetTime(cluster);
+
+ // Flush any metadata frames to the output file, before we write
+ // the current block.
+ if (!metadata.Write(time_ns))
+ return EXIT_FAILURE;
+
+ if ((track_type == Track::kAudio && output_audio) ||
+ (track_type == Track::kVideo && output_video)) {
+ const int frame_count = block->GetFrameCount();
+
+ for (int i = 0; i < frame_count; ++i) {
+ const mkvparser::Block::Frame& frame = block->GetFrame(i);
+
+ if (frame.len > data_len) {
+ delete[] data;
+ data = new unsigned char[frame.len];
+ if (!data)
+ return EXIT_FAILURE;
+ data_len = frame.len;
+ }
+
+ if (frame.Read(&reader, data))
+ return EXIT_FAILURE;
+
+ mkvmuxer::Frame muxer_frame;
+ if (!muxer_frame.Init(data, frame.len))
+ return EXIT_FAILURE;
+ muxer_frame.set_track_number(track_type == Track::kAudio ? aud_track
+ : vid_track);
+ if (block->GetDiscardPadding())
+ muxer_frame.set_discard_padding(block->GetDiscardPadding());
+ muxer_frame.set_timestamp(time_ns);
+ muxer_frame.set_is_key(block->IsKey());
+ if (!muxer_segment.AddGenericFrame(&muxer_frame)) {
+ printf("\n Could not add frame.\n");
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ status = cluster->GetNext(block_entry, block_entry);
+
+ if (status) {
+ printf("\n Could not get next block of cluster.\n");
+ return EXIT_FAILURE;
+ }
+ }
+
+ cluster = parser_segment->GetNext(cluster);
+ }
+
+ // We have exhausted all video and audio frames in the input file.
+ // Flush any remaining metadata frames to the output file.
+ if (!metadata.Write(-1))
+ return EXIT_FAILURE;
+
+ if (copy_input_duration) {
+ const double input_duration =
+ static_cast<double>(segment_info->GetDuration()) / timeCodeScale;
+ muxer_segment.set_duration(input_duration);
+ }
+
+ if (!muxer_segment.Finalize()) {
+ printf("Finalization of segment failed.\n");
+ return EXIT_FAILURE;
+ }
+
+ reader.Close();
+ writer.Close();
+
+ if (cues_before_clusters) {
+ if (reader.Open(temp_file.c_str())) {
+ printf("\n Filename is invalid or error while opening.\n");
+ return EXIT_FAILURE;
+ }
+ if (!writer.Open(output)) {
+ printf("\n Filename is invalid or error while opening.\n");
+ return EXIT_FAILURE;
+ }
+ if (!muxer_segment.CopyAndMoveCuesBeforeClusters(&reader, &writer)) {
+ printf("\n Unable to copy and move cues before clusters.\n");
+ return EXIT_FAILURE;
+ }
+ reader.Close();
+ writer.Close();
+ remove(temp_file.c_str());
+ }
+
+ delete[] data;
+
+ return EXIT_SUCCESS;
+}
diff --git a/mkvmuxertypes.hpp b/mkvmuxertypes.hpp
new file mode 100644
index 0000000..78478f4
--- /dev/null
+++ b/mkvmuxertypes.hpp
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_MKVMUXERTYPES_HPP_
+#define LIBWEBM_MKVMUXERTYPES_HPP_
+
+// This file is a wrapper for the file included immediately after this comment.
+// New projects should not include this file: include the file included below.
+#include "mkvmuxer/mkvmuxertypes.h"
+
+#endif // LIBWEBM_MKVMUXERTYPES_HPP_
diff --git a/mkvmuxerutil.hpp b/mkvmuxerutil.hpp
new file mode 100644
index 0000000..a26ba18
--- /dev/null
+++ b/mkvmuxerutil.hpp
@@ -0,0 +1,18 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_MKVMUXERUTIL_HPP_
+#define LIBWEBM_MKVMUXERUTIL_HPP_
+
+// This file is a wrapper for the file included immediately after this comment.
+// New projects should not include this file: include the file included below.
+#include "mkvmuxer/mkvmuxerutil.h"
+
+using mkvmuxer::EbmlElementSize;
+using mkvmuxer::EbmlMasterElementSize;
+
+#endif // LIBWEBM_MKVMUXERUTIL_HPP_
diff --git a/mkvparser.hpp b/mkvparser.hpp
new file mode 100644
index 0000000..3f86292
--- /dev/null
+++ b/mkvparser.hpp
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_MKVPARSER_HPP_
+#define LIBWEBM_MKVPARSER_HPP_
+
+// This file is a wrapper for the file included immediately after this comment.
+// New projects should not include this file: include the file included below.
+#include "mkvparser/mkvparser.h"
+
+#endif // LIBWEBM_MKVPARSER_HPP_
diff --git a/mkvparser/mkvparser.cc b/mkvparser/mkvparser.cc
new file mode 100644
index 0000000..412e6a5
--- /dev/null
+++ b/mkvparser/mkvparser.cc
@@ -0,0 +1,8076 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "mkvparser/mkvparser.h"
+
+#if defined(_MSC_VER) && _MSC_VER < 1800
+#include <float.h> // _isnan() / _finite()
+#define MSC_COMPAT
+#endif
+
+#include <cassert>
+#include <cfloat>
+#include <climits>
+#include <cmath>
+#include <cstring>
+#include <memory>
+#include <new>
+
+#include "common/webmids.h"
+
+namespace mkvparser {
+const long long kStringElementSizeLimit = 20 * 1000 * 1000;
+const float MasteringMetadata::kValueNotPresent = FLT_MAX;
+const long long Colour::kValueNotPresent = LLONG_MAX;
+const float Projection::kValueNotPresent = FLT_MAX;
+
+#ifdef MSC_COMPAT
+inline bool isnan(double val) { return !!_isnan(val); }
+inline bool isinf(double val) { return !_finite(val); }
+#else
+inline bool isnan(double val) { return std::isnan(val); }
+inline bool isinf(double val) { return std::isinf(val); }
+#endif // MSC_COMPAT
+
+template <typename Type>
+Type* SafeArrayAlloc(unsigned long long num_elements,
+ unsigned long long element_size) {
+ if (num_elements == 0 || element_size == 0)
+ return NULL;
+
+ const size_t kMaxAllocSize = 0x80000000; // 2GiB
+ const unsigned long long num_bytes = num_elements * element_size;
+ if (element_size > (kMaxAllocSize / num_elements))
+ return NULL;
+ if (num_bytes != static_cast<size_t>(num_bytes))
+ return NULL;
+
+ return new (std::nothrow) Type[static_cast<size_t>(num_bytes)];
+}
+
+void GetVersion(int& major, int& minor, int& build, int& revision) {
+ major = 1;
+ minor = 0;
+ build = 0;
+ revision = 30;
+}
+
+long long ReadUInt(IMkvReader* pReader, long long pos, long& len) {
+ if (!pReader || pos < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ len = 1;
+ unsigned char b;
+ int status = pReader->Read(pos, 1, &b);
+
+ if (status < 0) // error or underflow
+ return status;
+
+ if (status > 0) // interpreted as "underflow"
+ return E_BUFFER_NOT_FULL;
+
+ if (b == 0) // we can't handle u-int values larger than 8 bytes
+ return E_FILE_FORMAT_INVALID;
+
+ unsigned char m = 0x80;
+
+ while (!(b & m)) {
+ m >>= 1;
+ ++len;
+ }
+
+ long long result = b & (~m);
+ ++pos;
+
+ for (int i = 1; i < len; ++i) {
+ status = pReader->Read(pos, 1, &b);
+
+ if (status < 0) {
+ len = 1;
+ return status;
+ }
+
+ if (status > 0) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result <<= 8;
+ result |= b;
+
+ ++pos;
+ }
+
+ return result;
+}
+
+// Reads an EBML ID and returns it.
+// An ID must at least 1 byte long, cannot exceed 4, and its value must be
+// greater than 0.
+// See known EBML values and EBMLMaxIDLength:
+// http://www.matroska.org/technical/specs/index.html
+// Returns the ID, or a value less than 0 to report an error while reading the
+// ID.
+long long ReadID(IMkvReader* pReader, long long pos, long& len) {
+ if (pReader == NULL || pos < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ // Read the first byte. The length in bytes of the ID is determined by
+ // finding the first set bit in the first byte of the ID.
+ unsigned char temp_byte = 0;
+ int read_status = pReader->Read(pos, 1, &temp_byte);
+
+ if (read_status < 0)
+ return E_FILE_FORMAT_INVALID;
+ else if (read_status > 0) // No data to read.
+ return E_BUFFER_NOT_FULL;
+
+ if (temp_byte == 0) // ID length > 8 bytes; invalid file.
+ return E_FILE_FORMAT_INVALID;
+
+ int bit_pos = 0;
+ const int kMaxIdLengthInBytes = 4;
+ const int kCheckByte = 0x80;
+
+ // Find the first bit that's set.
+ bool found_bit = false;
+ for (; bit_pos < kMaxIdLengthInBytes; ++bit_pos) {
+ if ((kCheckByte >> bit_pos) & temp_byte) {
+ found_bit = true;
+ break;
+ }
+ }
+
+ if (!found_bit) {
+ // The value is too large to be a valid ID.
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ // Read the remaining bytes of the ID (if any).
+ const int id_length = bit_pos + 1;
+ long long ebml_id = temp_byte;
+ for (int i = 1; i < id_length; ++i) {
+ ebml_id <<= 8;
+ read_status = pReader->Read(pos + i, 1, &temp_byte);
+
+ if (read_status < 0)
+ return E_FILE_FORMAT_INVALID;
+ else if (read_status > 0)
+ return E_BUFFER_NOT_FULL;
+
+ ebml_id |= temp_byte;
+ }
+
+ len = id_length;
+ return ebml_id;
+}
+
+long long GetUIntLength(IMkvReader* pReader, long long pos, long& len) {
+ if (!pReader || pos < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ long long total, available;
+
+ int status = pReader->Length(&total, &available);
+ if (status < 0 || (total >= 0 && available > total))
+ return E_FILE_FORMAT_INVALID;
+
+ len = 1;
+
+ if (pos >= available)
+ return pos; // too few bytes available
+
+ unsigned char b;
+
+ status = pReader->Read(pos, 1, &b);
+
+ if (status != 0)
+ return status;
+
+ if (b == 0) // we can't handle u-int values larger than 8 bytes
+ return E_FILE_FORMAT_INVALID;
+
+ unsigned char m = 0x80;
+
+ while (!(b & m)) {
+ m >>= 1;
+ ++len;
+ }
+
+ return 0; // success
+}
+
+// TODO(vigneshv): This function assumes that unsigned values never have their
+// high bit set.
+long long UnserializeUInt(IMkvReader* pReader, long long pos, long long size) {
+ if (!pReader || pos < 0 || (size <= 0) || (size > 8))
+ return E_FILE_FORMAT_INVALID;
+
+ long long result = 0;
+
+ for (long long i = 0; i < size; ++i) {
+ unsigned char b;
+
+ const long status = pReader->Read(pos, 1, &b);
+
+ if (status < 0)
+ return status;
+
+ result <<= 8;
+ result |= b;
+
+ ++pos;
+ }
+
+ return result;
+}
+
+long UnserializeFloat(IMkvReader* pReader, long long pos, long long size_,
+ double& result) {
+ if (!pReader || pos < 0 || ((size_ != 4) && (size_ != 8)))
+ return E_FILE_FORMAT_INVALID;
+
+ const long size = static_cast<long>(size_);
+
+ unsigned char buf[8];
+
+ const int status = pReader->Read(pos, size, buf);
+
+ if (status < 0) // error
+ return status;
+
+ if (size == 4) {
+ union {
+ float f;
+ unsigned long ff;
+ };
+
+ ff = 0;
+
+ for (int i = 0;;) {
+ ff |= buf[i];
+
+ if (++i >= 4)
+ break;
+
+ ff <<= 8;
+ }
+
+ result = f;
+ } else {
+ union {
+ double d;
+ unsigned long long dd;
+ };
+
+ dd = 0;
+
+ for (int i = 0;;) {
+ dd |= buf[i];
+
+ if (++i >= 8)
+ break;
+
+ dd <<= 8;
+ }
+
+ result = d;
+ }
+
+ if (mkvparser::isinf(result) || mkvparser::isnan(result))
+ return E_FILE_FORMAT_INVALID;
+
+ return 0;
+}
+
+long UnserializeInt(IMkvReader* pReader, long long pos, long long size,
+ long long& result_ref) {
+ if (!pReader || pos < 0 || size < 1 || size > 8)
+ return E_FILE_FORMAT_INVALID;
+
+ signed char first_byte = 0;
+ const long status = pReader->Read(pos, 1, (unsigned char*)&first_byte);
+
+ if (status < 0)
+ return status;
+
+ unsigned long long result = first_byte;
+ ++pos;
+
+ for (long i = 1; i < size; ++i) {
+ unsigned char b;
+
+ const long status = pReader->Read(pos, 1, &b);
+
+ if (status < 0)
+ return status;
+
+ result <<= 8;
+ result |= b;
+
+ ++pos;
+ }
+
+ result_ref = static_cast<long long>(result);
+ return 0;
+}
+
+long UnserializeString(IMkvReader* pReader, long long pos, long long size,
+ char*& str) {
+ delete[] str;
+ str = NULL;
+
+ if (size >= LONG_MAX || size < 0 || size > kStringElementSizeLimit)
+ return E_FILE_FORMAT_INVALID;
+
+ // +1 for '\0' terminator
+ const long required_size = static_cast<long>(size) + 1;
+
+ str = SafeArrayAlloc<char>(1, required_size);
+ if (str == NULL)
+ return E_FILE_FORMAT_INVALID;
+
+ unsigned char* const buf = reinterpret_cast<unsigned char*>(str);
+
+ const long status = pReader->Read(pos, static_cast<long>(size), buf);
+
+ if (status) {
+ delete[] str;
+ str = NULL;
+
+ return status;
+ }
+
+ str[required_size - 1] = '\0';
+ return 0;
+}
+
+long ParseElementHeader(IMkvReader* pReader, long long& pos, long long stop,
+ long long& id, long long& size) {
+ if (stop >= 0 && pos >= stop)
+ return E_FILE_FORMAT_INVALID;
+
+ long len;
+
+ id = ReadID(pReader, pos, len);
+
+ if (id < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume id
+
+ if (stop >= 0 && pos >= stop)
+ return E_FILE_FORMAT_INVALID;
+
+ size = ReadUInt(pReader, pos, len);
+
+ if (size < 0 || len < 1 || len > 8) {
+ // Invalid: Negative payload size, negative or 0 length integer, or integer
+ // larger than 64 bits (libwebm cannot handle them).
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ // Avoid rolling over pos when very close to LLONG_MAX.
+ const unsigned long long rollover_check =
+ static_cast<unsigned long long>(pos) + len;
+ if (rollover_check > LLONG_MAX)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume length of size
+
+ // pos now designates payload
+
+ if (stop >= 0 && pos > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ return 0; // success
+}
+
+bool Match(IMkvReader* pReader, long long& pos, unsigned long expected_id,
+ long long& val) {
+ if (!pReader || pos < 0)
+ return false;
+
+ long long total = 0;
+ long long available = 0;
+
+ const long status = pReader->Length(&total, &available);
+ if (status < 0 || (total >= 0 && available > total))
+ return false;
+
+ long len = 0;
+
+ const long long id = ReadID(pReader, pos, len);
+ if (id < 0 || (available - pos) > len)
+ return false;
+
+ if (static_cast<unsigned long>(id) != expected_id)
+ return false;
+
+ pos += len; // consume id
+
+ const long long size = ReadUInt(pReader, pos, len);
+ if (size < 0 || size > 8 || len < 1 || len > 8 || (available - pos) > len)
+ return false;
+
+ pos += len; // consume length of size of payload
+
+ val = UnserializeUInt(pReader, pos, size);
+ if (val < 0)
+ return false;
+
+ pos += size; // consume size of payload
+
+ return true;
+}
+
+bool Match(IMkvReader* pReader, long long& pos, unsigned long expected_id,
+ unsigned char*& buf, size_t& buflen) {
+ if (!pReader || pos < 0)
+ return false;
+
+ long long total = 0;
+ long long available = 0;
+
+ long status = pReader->Length(&total, &available);
+ if (status < 0 || (total >= 0 && available > total))
+ return false;
+
+ long len = 0;
+ const long long id = ReadID(pReader, pos, len);
+ if (id < 0 || (available - pos) > len)
+ return false;
+
+ if (static_cast<unsigned long>(id) != expected_id)
+ return false;
+
+ pos += len; // consume id
+
+ const long long size = ReadUInt(pReader, pos, len);
+ if (size < 0 || len <= 0 || len > 8 || (available - pos) > len)
+ return false;
+
+ unsigned long long rollover_check =
+ static_cast<unsigned long long>(pos) + len;
+ if (rollover_check > LLONG_MAX)
+ return false;
+
+ pos += len; // consume length of size of payload
+
+ rollover_check = static_cast<unsigned long long>(pos) + size;
+ if (rollover_check > LLONG_MAX)
+ return false;
+
+ if ((pos + size) > available)
+ return false;
+
+ if (size >= LONG_MAX)
+ return false;
+
+ const long buflen_ = static_cast<long>(size);
+
+ buf = SafeArrayAlloc<unsigned char>(1, buflen_);
+ if (!buf)
+ return false;
+
+ status = pReader->Read(pos, buflen_, buf);
+ if (status != 0)
+ return false;
+
+ buflen = buflen_;
+
+ pos += size; // consume size of payload
+ return true;
+}
+
+EBMLHeader::EBMLHeader() : m_docType(NULL) { Init(); }
+
+EBMLHeader::~EBMLHeader() { delete[] m_docType; }
+
+void EBMLHeader::Init() {
+ m_version = 1;
+ m_readVersion = 1;
+ m_maxIdLength = 4;
+ m_maxSizeLength = 8;
+
+ if (m_docType) {
+ delete[] m_docType;
+ m_docType = NULL;
+ }
+
+ m_docTypeVersion = 1;
+ m_docTypeReadVersion = 1;
+}
+
+long long EBMLHeader::Parse(IMkvReader* pReader, long long& pos) {
+ if (!pReader)
+ return E_FILE_FORMAT_INVALID;
+
+ long long total, available;
+
+ long status = pReader->Length(&total, &available);
+
+ if (status < 0) // error
+ return status;
+
+ pos = 0;
+
+ // Scan until we find what looks like the first byte of the EBML header.
+ const long long kMaxScanBytes = (available >= 1024) ? 1024 : available;
+ const unsigned char kEbmlByte0 = 0x1A;
+ unsigned char scan_byte = 0;
+
+ while (pos < kMaxScanBytes) {
+ status = pReader->Read(pos, 1, &scan_byte);
+
+ if (status < 0) // error
+ return status;
+ else if (status > 0)
+ return E_BUFFER_NOT_FULL;
+
+ if (scan_byte == kEbmlByte0)
+ break;
+
+ ++pos;
+ }
+
+ long len = 0;
+ const long long ebml_id = ReadID(pReader, pos, len);
+
+ if (ebml_id == E_BUFFER_NOT_FULL)
+ return E_BUFFER_NOT_FULL;
+
+ if (len != 4 || ebml_id != libwebm::kMkvEBML)
+ return E_FILE_FORMAT_INVALID;
+
+ // Move read pos forward to the EBML header size field.
+ pos += 4;
+
+ // Read length of size field.
+ long long result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return E_FILE_FORMAT_INVALID;
+ else if (result > 0) // need more data
+ return E_BUFFER_NOT_FULL;
+
+ if (len < 1 || len > 8)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((total >= 0) && ((total - pos) < len))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((available - pos) < len)
+ return pos + len; // try again later
+
+ // Read the EBML header size.
+ result = ReadUInt(pReader, pos, len);
+
+ if (result < 0) // error
+ return result;
+
+ pos += len; // consume size field
+
+ // pos now designates start of payload
+
+ if ((total >= 0) && ((total - pos) < result))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((available - pos) < result)
+ return pos + result;
+
+ const long long end = pos + result;
+
+ Init();
+
+ while (pos < end) {
+ long long id, size;
+
+ status = ParseElementHeader(pReader, pos, end, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (size == 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if (id == libwebm::kMkvEBMLVersion) {
+ m_version = UnserializeUInt(pReader, pos, size);
+
+ if (m_version <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvEBMLReadVersion) {
+ m_readVersion = UnserializeUInt(pReader, pos, size);
+
+ if (m_readVersion <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvEBMLMaxIDLength) {
+ m_maxIdLength = UnserializeUInt(pReader, pos, size);
+
+ if (m_maxIdLength <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvEBMLMaxSizeLength) {
+ m_maxSizeLength = UnserializeUInt(pReader, pos, size);
+
+ if (m_maxSizeLength <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvDocType) {
+ if (m_docType)
+ return E_FILE_FORMAT_INVALID;
+
+ status = UnserializeString(pReader, pos, size, m_docType);
+
+ if (status) // error
+ return status;
+ } else if (id == libwebm::kMkvDocTypeVersion) {
+ m_docTypeVersion = UnserializeUInt(pReader, pos, size);
+
+ if (m_docTypeVersion <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvDocTypeReadVersion) {
+ m_docTypeReadVersion = UnserializeUInt(pReader, pos, size);
+
+ if (m_docTypeReadVersion <= 0)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ pos += size;
+ }
+
+ if (pos != end)
+ return E_FILE_FORMAT_INVALID;
+
+ // Make sure DocType, DocTypeReadVersion, and DocTypeVersion are valid.
+ if (m_docType == NULL || m_docTypeReadVersion <= 0 || m_docTypeVersion <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ // Make sure EBMLMaxIDLength and EBMLMaxSizeLength are valid.
+ if (m_maxIdLength <= 0 || m_maxIdLength > 4 || m_maxSizeLength <= 0 ||
+ m_maxSizeLength > 8)
+ return E_FILE_FORMAT_INVALID;
+
+ return 0;
+}
+
+Segment::Segment(IMkvReader* pReader, long long elem_start,
+ // long long elem_size,
+ long long start, long long size)
+ : m_pReader(pReader),
+ m_element_start(elem_start),
+ // m_element_size(elem_size),
+ m_start(start),
+ m_size(size),
+ m_pos(start),
+ m_pUnknownSize(0),
+ m_pSeekHead(NULL),
+ m_pInfo(NULL),
+ m_pTracks(NULL),
+ m_pCues(NULL),
+ m_pChapters(NULL),
+ m_pTags(NULL),
+ m_clusters(NULL),
+ m_clusterCount(0),
+ m_clusterPreloadCount(0),
+ m_clusterSize(0) {}
+
+Segment::~Segment() {
+ const long count = m_clusterCount + m_clusterPreloadCount;
+
+ Cluster** i = m_clusters;
+ Cluster** j = m_clusters + count;
+
+ while (i != j) {
+ Cluster* const p = *i++;
+ delete p;
+ }
+
+ delete[] m_clusters;
+
+ delete m_pTracks;
+ delete m_pInfo;
+ delete m_pCues;
+ delete m_pChapters;
+ delete m_pTags;
+ delete m_pSeekHead;
+}
+
+long long Segment::CreateInstance(IMkvReader* pReader, long long pos,
+ Segment*& pSegment) {
+ if (pReader == NULL || pos < 0)
+ return E_PARSE_FAILED;
+
+ pSegment = NULL;
+
+ long long total, available;
+
+ const long status = pReader->Length(&total, &available);
+
+ if (status < 0) // error
+ return status;
+
+ if (available < 0)
+ return -1;
+
+ if ((total >= 0) && (available > total))
+ return -1;
+
+ // I would assume that in practice this loop would execute
+ // exactly once, but we allow for other elements (e.g. Void)
+ // to immediately follow the EBML header. This is fine for
+ // the source filter case (since the entire file is available),
+ // but in the splitter case over a network we should probably
+ // just give up early. We could for example decide only to
+ // execute this loop a maximum of, say, 10 times.
+ // TODO:
+ // There is an implied "give up early" by only parsing up
+ // to the available limit. We do do that, but only if the
+ // total file size is unknown. We could decide to always
+ // use what's available as our limit (irrespective of whether
+ // we happen to know the total file length). This would have
+ // as its sense "parse this much of the file before giving up",
+ // which a slightly different sense from "try to parse up to
+ // 10 EMBL elements before giving up".
+
+ for (;;) {
+ if ((total >= 0) && (pos >= total))
+ return E_FILE_FORMAT_INVALID;
+
+ // Read ID
+ long len;
+ long long result = GetUIntLength(pReader, pos, len);
+
+ if (result) // error, or too few available bytes
+ return result;
+
+ if ((total >= 0) && ((pos + len) > total))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > available)
+ return pos + len;
+
+ const long long idpos = pos;
+ const long long id = ReadID(pReader, pos, len);
+
+ if (id < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume ID
+
+ // Read Size
+
+ result = GetUIntLength(pReader, pos, len);
+
+ if (result) // error, or too few available bytes
+ return result;
+
+ if ((total >= 0) && ((pos + len) > total))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > available)
+ return pos + len;
+
+ long long size = ReadUInt(pReader, pos, len);
+
+ if (size < 0) // error
+ return size;
+
+ pos += len; // consume length of size of element
+
+ // Pos now points to start of payload
+
+ // Handle "unknown size" for live streaming of webm files.
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if (id == libwebm::kMkvSegment) {
+ if (size == unknown_size)
+ size = -1;
+
+ else if (total < 0)
+ size = -1;
+
+ else if ((pos + size) > total)
+ size = -1;
+
+ pSegment = new (std::nothrow) Segment(pReader, idpos, pos, size);
+ if (pSegment == NULL)
+ return E_PARSE_FAILED;
+
+ return 0; // success
+ }
+
+ if (size == unknown_size)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((total >= 0) && ((pos + size) > total))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + size) > available)
+ return pos + size;
+
+ pos += size; // consume payload
+ }
+}
+
+long long Segment::ParseHeaders() {
+ // Outermost (level 0) segment object has been constructed,
+ // and pos designates start of payload. We need to find the
+ // inner (level 1) elements.
+ long long total, available;
+
+ const int status = m_pReader->Length(&total, &available);
+
+ if (status < 0) // error
+ return status;
+
+ if (total > 0 && available > total)
+ return E_FILE_FORMAT_INVALID;
+
+ const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+ if ((segment_stop >= 0 && total >= 0 && segment_stop > total) ||
+ (segment_stop >= 0 && m_pos > segment_stop)) {
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ for (;;) {
+ if ((total >= 0) && (m_pos >= total))
+ break;
+
+ if ((segment_stop >= 0) && (m_pos >= segment_stop))
+ break;
+
+ long long pos = m_pos;
+ const long long element_start = pos;
+
+ // Avoid rolling over pos when very close to LLONG_MAX.
+ unsigned long long rollover_check = pos + 1ULL;
+ if (rollover_check > LLONG_MAX)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + 1) > available)
+ return (pos + 1);
+
+ long len;
+ long long result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return result;
+
+ if (result > 0) {
+ // MkvReader doesn't have enough data to satisfy this read attempt.
+ return (pos + 1);
+ }
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > available)
+ return pos + len;
+
+ const long long idpos = pos;
+ const long long id = ReadID(m_pReader, idpos, len);
+
+ if (id < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if (id == libwebm::kMkvCluster)
+ break;
+
+ pos += len; // consume ID
+
+ if ((pos + 1) > available)
+ return (pos + 1);
+
+ // Read Size
+ result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return result;
+
+ if (result > 0) {
+ // MkvReader doesn't have enough data to satisfy this read attempt.
+ return (pos + 1);
+ }
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > available)
+ return pos + len;
+
+ const long long size = ReadUInt(m_pReader, pos, len);
+
+ if (size < 0 || len < 1 || len > 8) {
+ // TODO(tomfinegan): ReadUInt should return an error when len is < 1 or
+ // len > 8 is true instead of checking this _everywhere_.
+ return size;
+ }
+
+ pos += len; // consume length of size of element
+
+ // Avoid rolling over pos when very close to LLONG_MAX.
+ rollover_check = static_cast<unsigned long long>(pos) + size;
+ if (rollover_check > LLONG_MAX)
+ return E_FILE_FORMAT_INVALID;
+
+ const long long element_size = size + pos - element_start;
+
+ // Pos now points to start of payload
+
+ if ((segment_stop >= 0) && ((pos + size) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ // We read EBML elements either in total or nothing at all.
+
+ if ((pos + size) > available)
+ return pos + size;
+
+ if (id == libwebm::kMkvInfo) {
+ if (m_pInfo)
+ return E_FILE_FORMAT_INVALID;
+
+ m_pInfo = new (std::nothrow)
+ SegmentInfo(this, pos, size, element_start, element_size);
+
+ if (m_pInfo == NULL)
+ return -1;
+
+ const long status = m_pInfo->Parse();
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvTracks) {
+ if (m_pTracks)
+ return E_FILE_FORMAT_INVALID;
+
+ m_pTracks = new (std::nothrow)
+ Tracks(this, pos, size, element_start, element_size);
+
+ if (m_pTracks == NULL)
+ return -1;
+
+ const long status = m_pTracks->Parse();
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvCues) {
+ if (m_pCues == NULL) {
+ m_pCues = new (std::nothrow)
+ Cues(this, pos, size, element_start, element_size);
+
+ if (m_pCues == NULL)
+ return -1;
+ }
+ } else if (id == libwebm::kMkvSeekHead) {
+ if (m_pSeekHead == NULL) {
+ m_pSeekHead = new (std::nothrow)
+ SeekHead(this, pos, size, element_start, element_size);
+
+ if (m_pSeekHead == NULL)
+ return -1;
+
+ const long status = m_pSeekHead->Parse();
+
+ if (status)
+ return status;
+ }
+ } else if (id == libwebm::kMkvChapters) {
+ if (m_pChapters == NULL) {
+ m_pChapters = new (std::nothrow)
+ Chapters(this, pos, size, element_start, element_size);
+
+ if (m_pChapters == NULL)
+ return -1;
+
+ const long status = m_pChapters->Parse();
+
+ if (status)
+ return status;
+ }
+ } else if (id == libwebm::kMkvTags) {
+ if (m_pTags == NULL) {
+ m_pTags = new (std::nothrow)
+ Tags(this, pos, size, element_start, element_size);
+
+ if (m_pTags == NULL)
+ return -1;
+
+ const long status = m_pTags->Parse();
+
+ if (status)
+ return status;
+ }
+ }
+
+ m_pos = pos + size; // consume payload
+ }
+
+ if (segment_stop >= 0 && m_pos > segment_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if (m_pInfo == NULL) // TODO: liberalize this behavior
+ return E_FILE_FORMAT_INVALID;
+
+ if (m_pTracks == NULL)
+ return E_FILE_FORMAT_INVALID;
+
+ return 0; // success
+}
+
+long Segment::LoadCluster(long long& pos, long& len) {
+ for (;;) {
+ const long result = DoLoadCluster(pos, len);
+
+ if (result <= 1)
+ return result;
+ }
+}
+
+long Segment::DoLoadCluster(long long& pos, long& len) {
+ if (m_pos < 0)
+ return DoLoadClusterUnknownSize(pos, len);
+
+ long long total, avail;
+
+ long status = m_pReader->Length(&total, &avail);
+
+ if (status < 0) // error
+ return status;
+
+ if (total >= 0 && avail > total)
+ return E_FILE_FORMAT_INVALID;
+
+ const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+ long long cluster_off = -1; // offset relative to start of segment
+ long long cluster_size = -1; // size of cluster payload
+
+ for (;;) {
+ if ((total >= 0) && (m_pos >= total))
+ return 1; // no more clusters
+
+ if ((segment_stop >= 0) && (m_pos >= segment_stop))
+ return 1; // no more clusters
+
+ pos = m_pos;
+
+ // Read ID
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0)
+ return E_BUFFER_NOT_FULL;
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long idpos = pos;
+ const long long id = ReadID(m_pReader, idpos, len);
+
+ if (id < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume ID
+
+ // Read Size
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0)
+ return E_BUFFER_NOT_FULL;
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(m_pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(size);
+
+ pos += len; // consume length of size of element
+
+ // pos now points to start of payload
+
+ if (size == 0) {
+ // Missing element payload: move on.
+ m_pos = pos;
+ continue;
+ }
+
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if ((segment_stop >= 0) && (size != unknown_size) &&
+ ((pos + size) > segment_stop)) {
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (id == libwebm::kMkvCues) {
+ if (size == unknown_size) {
+ // Cues element of unknown size: Not supported.
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (m_pCues == NULL) {
+ const long long element_size = (pos - idpos) + size;
+
+ m_pCues = new (std::nothrow) Cues(this, pos, size, idpos, element_size);
+ if (m_pCues == NULL)
+ return -1;
+ }
+
+ m_pos = pos + size; // consume payload
+ continue;
+ }
+
+ if (id != libwebm::kMkvCluster) {
+ // Besides the Segment, Libwebm allows only cluster elements of unknown
+ // size. Fail the parse upon encountering a non-cluster element reporting
+ // unknown size.
+ if (size == unknown_size)
+ return E_FILE_FORMAT_INVALID;
+
+ m_pos = pos + size; // consume payload
+ continue;
+ }
+
+ // We have a cluster.
+
+ cluster_off = idpos - m_start; // relative pos
+
+ if (size != unknown_size)
+ cluster_size = size;
+
+ break;
+ }
+
+ if (cluster_off < 0) {
+ // No cluster, die.
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ long long pos_;
+ long len_;
+
+ status = Cluster::HasBlockEntries(this, cluster_off, pos_, len_);
+
+ if (status < 0) { // error, or underflow
+ pos = pos_;
+ len = len_;
+
+ return status;
+ }
+
+ // status == 0 means "no block entries found"
+ // status > 0 means "found at least one block entry"
+
+ // TODO:
+ // The issue here is that the segment increments its own
+ // pos ptr past the most recent cluster parsed, and then
+ // starts from there to parse the next cluster. If we
+ // don't know the size of the current cluster, then we
+ // must either parse its payload (as we do below), looking
+ // for the cluster (or cues) ID to terminate the parse.
+ // This isn't really what we want: rather, we really need
+ // a way to create the curr cluster object immediately.
+ // The pity is that cluster::parse can determine its own
+ // boundary, and we largely duplicate that same logic here.
+ //
+ // Maybe we need to get rid of our look-ahead preloading
+ // in source::parse???
+ //
+ // As we're parsing the blocks in the curr cluster
+ //(in cluster::parse), we should have some way to signal
+ // to the segment that we have determined the boundary,
+ // so it can adjust its own segment::m_pos member.
+ //
+ // The problem is that we're asserting in asyncreadinit,
+ // because we adjust the pos down to the curr seek pos,
+ // and the resulting adjusted len is > 2GB. I'm suspicious
+ // that this is even correct, but even if it is, we can't
+ // be loading that much data in the cache anyway.
+
+ const long idx = m_clusterCount;
+
+ if (m_clusterPreloadCount > 0) {
+ if (idx >= m_clusterSize)
+ return E_FILE_FORMAT_INVALID;
+
+ Cluster* const pCluster = m_clusters[idx];
+ if (pCluster == NULL || pCluster->m_index >= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ const long long off = pCluster->GetPosition();
+ if (off < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if (off == cluster_off) { // preloaded already
+ if (status == 0) // no entries found
+ return E_FILE_FORMAT_INVALID;
+
+ if (cluster_size >= 0)
+ pos += cluster_size;
+ else {
+ const long long element_size = pCluster->GetElementSize();
+
+ if (element_size <= 0)
+ return E_FILE_FORMAT_INVALID; // TODO: handle this case
+
+ pos = pCluster->m_element_start + element_size;
+ }
+
+ pCluster->m_index = idx; // move from preloaded to loaded
+ ++m_clusterCount;
+ --m_clusterPreloadCount;
+
+ m_pos = pos; // consume payload
+ if (segment_stop >= 0 && m_pos > segment_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ return 0; // success
+ }
+ }
+
+ if (status == 0) { // no entries found
+ if (cluster_size >= 0)
+ pos += cluster_size;
+
+ if ((total >= 0) && (pos >= total)) {
+ m_pos = total;
+ return 1; // no more clusters
+ }
+
+ if ((segment_stop >= 0) && (pos >= segment_stop)) {
+ m_pos = segment_stop;
+ return 1; // no more clusters
+ }
+
+ m_pos = pos;
+ return 2; // try again
+ }
+
+ // status > 0 means we have an entry
+
+ Cluster* const pCluster = Cluster::Create(this, idx, cluster_off);
+ if (pCluster == NULL)
+ return -1;
+
+ if (!AppendCluster(pCluster)) {
+ delete pCluster;
+ return -1;
+ }
+
+ if (cluster_size >= 0) {
+ pos += cluster_size;
+
+ m_pos = pos;
+
+ if (segment_stop > 0 && m_pos > segment_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ return 0;
+ }
+
+ m_pUnknownSize = pCluster;
+ m_pos = -pos;
+
+ return 0; // partial success, since we have a new cluster
+
+ // status == 0 means "no block entries found"
+ // pos designates start of payload
+ // m_pos has NOT been adjusted yet (in case we need to come back here)
+}
+
+long Segment::DoLoadClusterUnknownSize(long long& pos, long& len) {
+ if (m_pos >= 0 || m_pUnknownSize == NULL)
+ return E_PARSE_FAILED;
+
+ const long status = m_pUnknownSize->Parse(pos, len);
+
+ if (status < 0) // error or underflow
+ return status;
+
+ if (status == 0) // parsed a block
+ return 2; // continue parsing
+
+ const long long start = m_pUnknownSize->m_element_start;
+ const long long size = m_pUnknownSize->GetElementSize();
+
+ if (size < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ pos = start + size;
+ m_pos = pos;
+
+ m_pUnknownSize = 0;
+
+ return 2; // continue parsing
+}
+
+bool Segment::AppendCluster(Cluster* pCluster) {
+ if (pCluster == NULL || pCluster->m_index < 0)
+ return false;
+
+ const long count = m_clusterCount + m_clusterPreloadCount;
+
+ long& size = m_clusterSize;
+ const long idx = pCluster->m_index;
+
+ if (size < count || idx != m_clusterCount)
+ return false;
+
+ if (count >= size) {
+ const long n = (size <= 0) ? 2048 : 2 * size;
+
+ Cluster** const qq = new (std::nothrow) Cluster*[n];
+ if (qq == NULL)
+ return false;
+
+ Cluster** q = qq;
+ Cluster** p = m_clusters;
+ Cluster** const pp = p + count;
+
+ while (p != pp)
+ *q++ = *p++;
+
+ delete[] m_clusters;
+
+ m_clusters = qq;
+ size = n;
+ }
+
+ if (m_clusterPreloadCount > 0) {
+ Cluster** const p = m_clusters + m_clusterCount;
+ if (*p == NULL || (*p)->m_index >= 0)
+ return false;
+
+ Cluster** q = p + m_clusterPreloadCount;
+ if (q >= (m_clusters + size))
+ return false;
+
+ for (;;) {
+ Cluster** const qq = q - 1;
+ if ((*qq)->m_index >= 0)
+ return false;
+
+ *q = *qq;
+ q = qq;
+
+ if (q == p)
+ break;
+ }
+ }
+
+ m_clusters[idx] = pCluster;
+ ++m_clusterCount;
+ return true;
+}
+
+bool Segment::PreloadCluster(Cluster* pCluster, ptrdiff_t idx) {
+ if (pCluster == NULL || pCluster->m_index >= 0 || idx < m_clusterCount)
+ return false;
+
+ const long count = m_clusterCount + m_clusterPreloadCount;
+
+ long& size = m_clusterSize;
+ if (size < count)
+ return false;
+
+ if (count >= size) {
+ const long n = (size <= 0) ? 2048 : 2 * size;
+
+ Cluster** const qq = new (std::nothrow) Cluster*[n];
+ if (qq == NULL)
+ return false;
+ Cluster** q = qq;
+
+ Cluster** p = m_clusters;
+ Cluster** const pp = p + count;
+
+ while (p != pp)
+ *q++ = *p++;
+
+ delete[] m_clusters;
+
+ m_clusters = qq;
+ size = n;
+ }
+
+ if (m_clusters == NULL)
+ return false;
+
+ Cluster** const p = m_clusters + idx;
+
+ Cluster** q = m_clusters + count;
+ if (q < p || q >= (m_clusters + size))
+ return false;
+
+ while (q > p) {
+ Cluster** const qq = q - 1;
+
+ if ((*qq)->m_index >= 0)
+ return false;
+
+ *q = *qq;
+ q = qq;
+ }
+
+ m_clusters[idx] = pCluster;
+ ++m_clusterPreloadCount;
+ return true;
+}
+
+long Segment::Load() {
+ if (m_clusters != NULL || m_clusterSize != 0 || m_clusterCount != 0)
+ return E_PARSE_FAILED;
+
+ // Outermost (level 0) segment object has been constructed,
+ // and pos designates start of payload. We need to find the
+ // inner (level 1) elements.
+
+ const long long header_status = ParseHeaders();
+
+ if (header_status < 0) // error
+ return static_cast<long>(header_status);
+
+ if (header_status > 0) // underflow
+ return E_BUFFER_NOT_FULL;
+
+ if (m_pInfo == NULL || m_pTracks == NULL)
+ return E_FILE_FORMAT_INVALID;
+
+ for (;;) {
+ const long status = LoadCluster();
+
+ if (status < 0) // error
+ return status;
+
+ if (status >= 1) // no more clusters
+ return 0;
+ }
+}
+
+SeekHead::Entry::Entry() : id(0), pos(0), element_start(0), element_size(0) {}
+
+SeekHead::SeekHead(Segment* pSegment, long long start, long long size_,
+ long long element_start, long long element_size)
+ : m_pSegment(pSegment),
+ m_start(start),
+ m_size(size_),
+ m_element_start(element_start),
+ m_element_size(element_size),
+ m_entries(0),
+ m_entry_count(0),
+ m_void_elements(0),
+ m_void_element_count(0) {}
+
+SeekHead::~SeekHead() {
+ delete[] m_entries;
+ delete[] m_void_elements;
+}
+
+long SeekHead::Parse() {
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ long long pos = m_start;
+ const long long stop = m_start + m_size;
+
+ // first count the seek head entries
+
+ int entry_count = 0;
+ int void_element_count = 0;
+
+ while (pos < stop) {
+ long long id, size;
+
+ const long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (id == libwebm::kMkvSeek)
+ ++entry_count;
+ else if (id == libwebm::kMkvVoid)
+ ++void_element_count;
+
+ pos += size; // consume payload
+
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if (entry_count > 0) {
+ m_entries = new (std::nothrow) Entry[entry_count];
+
+ if (m_entries == NULL)
+ return -1;
+ }
+
+ if (void_element_count > 0) {
+ m_void_elements = new (std::nothrow) VoidElement[void_element_count];
+
+ if (m_void_elements == NULL)
+ return -1;
+ }
+
+ // now parse the entries and void elements
+
+ Entry* pEntry = m_entries;
+ VoidElement* pVoidElement = m_void_elements;
+
+ pos = m_start;
+
+ while (pos < stop) {
+ const long long idpos = pos;
+
+ long long id, size;
+
+ const long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (id == libwebm::kMkvSeek && entry_count > 0) {
+ if (ParseEntry(pReader, pos, size, pEntry)) {
+ Entry& e = *pEntry++;
+
+ e.element_start = idpos;
+ e.element_size = (pos + size) - idpos;
+ }
+ } else if (id == libwebm::kMkvVoid && void_element_count > 0) {
+ VoidElement& e = *pVoidElement++;
+
+ e.element_start = idpos;
+ e.element_size = (pos + size) - idpos;
+ }
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ ptrdiff_t count_ = ptrdiff_t(pEntry - m_entries);
+ assert(count_ >= 0);
+ assert(count_ <= entry_count);
+
+ m_entry_count = static_cast<int>(count_);
+
+ count_ = ptrdiff_t(pVoidElement - m_void_elements);
+ assert(count_ >= 0);
+ assert(count_ <= void_element_count);
+
+ m_void_element_count = static_cast<int>(count_);
+
+ return 0;
+}
+
+int SeekHead::GetCount() const { return m_entry_count; }
+
+const SeekHead::Entry* SeekHead::GetEntry(int idx) const {
+ if (idx < 0)
+ return 0;
+
+ if (idx >= m_entry_count)
+ return 0;
+
+ return m_entries + idx;
+}
+
+int SeekHead::GetVoidElementCount() const { return m_void_element_count; }
+
+const SeekHead::VoidElement* SeekHead::GetVoidElement(int idx) const {
+ if (idx < 0)
+ return 0;
+
+ if (idx >= m_void_element_count)
+ return 0;
+
+ return m_void_elements + idx;
+}
+
+long Segment::ParseCues(long long off, long long& pos, long& len) {
+ if (m_pCues)
+ return 0; // success
+
+ if (off < 0)
+ return -1;
+
+ long long total, avail;
+
+ const int status = m_pReader->Length(&total, &avail);
+
+ if (status < 0) // error
+ return status;
+
+ assert((total < 0) || (avail <= total));
+
+ pos = m_start + off;
+
+ if ((total < 0) || (pos >= total))
+ return 1; // don't bother parsing cues
+
+ const long long element_start = pos;
+ const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // underflow (weird)
+ {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long idpos = pos;
+
+ const long long id = ReadID(m_pReader, idpos, len);
+
+ if (id != libwebm::kMkvCues)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume ID
+ assert((segment_stop < 0) || (pos <= segment_stop));
+
+ // Read Size
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // underflow (weird)
+ {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(m_pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(size);
+
+ if (size == 0) // weird, although technically not illegal
+ return 1; // done
+
+ pos += len; // consume length of size of element
+ assert((segment_stop < 0) || (pos <= segment_stop));
+
+ // Pos now points to start of payload
+
+ const long long element_stop = pos + size;
+
+ if ((segment_stop >= 0) && (element_stop > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((total >= 0) && (element_stop > total))
+ return 1; // don't bother parsing anymore
+
+ len = static_cast<long>(size);
+
+ if (element_stop > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long element_size = element_stop - element_start;
+
+ m_pCues =
+ new (std::nothrow) Cues(this, pos, size, element_start, element_size);
+ if (m_pCues == NULL)
+ return -1;
+
+ return 0; // success
+}
+
+bool SeekHead::ParseEntry(IMkvReader* pReader, long long start, long long size_,
+ Entry* pEntry) {
+ if (size_ <= 0)
+ return false;
+
+ long long pos = start;
+ const long long stop = start + size_;
+
+ long len;
+
+ // parse the container for the level-1 element ID
+
+ const long long seekIdId = ReadID(pReader, pos, len);
+ if (seekIdId < 0)
+ return false;
+
+ if (seekIdId != libwebm::kMkvSeekID)
+ return false;
+
+ if ((pos + len) > stop)
+ return false;
+
+ pos += len; // consume SeekID id
+
+ const long long seekIdSize = ReadUInt(pReader, pos, len);
+
+ if (seekIdSize <= 0)
+ return false;
+
+ if ((pos + len) > stop)
+ return false;
+
+ pos += len; // consume size of field
+
+ if ((pos + seekIdSize) > stop)
+ return false;
+
+ pEntry->id = ReadID(pReader, pos, len); // payload
+
+ if (pEntry->id <= 0)
+ return false;
+
+ if (len != seekIdSize)
+ return false;
+
+ pos += seekIdSize; // consume SeekID payload
+
+ const long long seekPosId = ReadID(pReader, pos, len);
+
+ if (seekPosId != libwebm::kMkvSeekPosition)
+ return false;
+
+ if ((pos + len) > stop)
+ return false;
+
+ pos += len; // consume id
+
+ const long long seekPosSize = ReadUInt(pReader, pos, len);
+
+ if (seekPosSize <= 0)
+ return false;
+
+ if ((pos + len) > stop)
+ return false;
+
+ pos += len; // consume size
+
+ if ((pos + seekPosSize) > stop)
+ return false;
+
+ pEntry->pos = UnserializeUInt(pReader, pos, seekPosSize);
+
+ if (pEntry->pos < 0)
+ return false;
+
+ pos += seekPosSize; // consume payload
+
+ if (pos != stop)
+ return false;
+
+ return true;
+}
+
+Cues::Cues(Segment* pSegment, long long start_, long long size_,
+ long long element_start, long long element_size)
+ : m_pSegment(pSegment),
+ m_start(start_),
+ m_size(size_),
+ m_element_start(element_start),
+ m_element_size(element_size),
+ m_cue_points(NULL),
+ m_count(0),
+ m_preload_count(0),
+ m_pos(start_) {}
+
+Cues::~Cues() {
+ const long n = m_count + m_preload_count;
+
+ CuePoint** p = m_cue_points;
+ CuePoint** const q = p + n;
+
+ while (p != q) {
+ CuePoint* const pCP = *p++;
+ assert(pCP);
+
+ delete pCP;
+ }
+
+ delete[] m_cue_points;
+}
+
+long Cues::GetCount() const {
+ if (m_cue_points == NULL)
+ return -1;
+
+ return m_count; // TODO: really ignore preload count?
+}
+
+bool Cues::DoneParsing() const {
+ const long long stop = m_start + m_size;
+ return (m_pos >= stop);
+}
+
+bool Cues::Init() const {
+ if (m_cue_points)
+ return true;
+
+ if (m_count != 0 || m_preload_count != 0)
+ return false;
+
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ const long long stop = m_start + m_size;
+ long long pos = m_start;
+
+ long cue_points_size = 0;
+
+ while (pos < stop) {
+ const long long idpos = pos;
+
+ long len;
+
+ const long long id = ReadID(pReader, pos, len);
+ if (id < 0 || (pos + len) > stop) {
+ return false;
+ }
+
+ pos += len; // consume ID
+
+ const long long size = ReadUInt(pReader, pos, len);
+ if (size < 0 || (pos + len > stop)) {
+ return false;
+ }
+
+ pos += len; // consume Size field
+ if (pos + size > stop) {
+ return false;
+ }
+
+ if (id == libwebm::kMkvCuePoint) {
+ if (!PreloadCuePoint(cue_points_size, idpos))
+ return false;
+ }
+
+ pos += size; // skip payload
+ }
+ return true;
+}
+
+bool Cues::PreloadCuePoint(long& cue_points_size, long long pos) const {
+ if (m_count != 0)
+ return false;
+
+ if (m_preload_count >= cue_points_size) {
+ const long n = (cue_points_size <= 0) ? 2048 : 2 * cue_points_size;
+
+ CuePoint** const qq = new (std::nothrow) CuePoint*[n];
+ if (qq == NULL)
+ return false;
+
+ CuePoint** q = qq; // beginning of target
+
+ CuePoint** p = m_cue_points; // beginning of source
+ CuePoint** const pp = p + m_preload_count; // end of source
+
+ while (p != pp)
+ *q++ = *p++;
+
+ delete[] m_cue_points;
+
+ m_cue_points = qq;
+ cue_points_size = n;
+ }
+
+ CuePoint* const pCP = new (std::nothrow) CuePoint(m_preload_count, pos);
+ if (pCP == NULL)
+ return false;
+
+ m_cue_points[m_preload_count++] = pCP;
+ return true;
+}
+
+bool Cues::LoadCuePoint() const {
+ const long long stop = m_start + m_size;
+
+ if (m_pos >= stop)
+ return false; // nothing else to do
+
+ if (!Init()) {
+ m_pos = stop;
+ return false;
+ }
+
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ while (m_pos < stop) {
+ const long long idpos = m_pos;
+
+ long len;
+
+ const long long id = ReadID(pReader, m_pos, len);
+ if (id < 0 || (m_pos + len) > stop)
+ return false;
+
+ m_pos += len; // consume ID
+
+ const long long size = ReadUInt(pReader, m_pos, len);
+ if (size < 0 || (m_pos + len) > stop)
+ return false;
+
+ m_pos += len; // consume Size field
+ if ((m_pos + size) > stop)
+ return false;
+
+ if (id != libwebm::kMkvCuePoint) {
+ m_pos += size; // consume payload
+ if (m_pos > stop)
+ return false;
+
+ continue;
+ }
+
+ if (m_preload_count < 1)
+ return false;
+
+ CuePoint* const pCP = m_cue_points[m_count];
+ if (!pCP || (pCP->GetTimeCode() < 0 && (-pCP->GetTimeCode() != idpos)))
+ return false;
+
+ if (!pCP->Load(pReader)) {
+ m_pos = stop;
+ return false;
+ }
+ ++m_count;
+ --m_preload_count;
+
+ m_pos += size; // consume payload
+ if (m_pos > stop)
+ return false;
+
+ return true; // yes, we loaded a cue point
+ }
+
+ return false; // no, we did not load a cue point
+}
+
+bool Cues::Find(long long time_ns, const Track* pTrack, const CuePoint*& pCP,
+ const CuePoint::TrackPosition*& pTP) const {
+ if (time_ns < 0 || pTrack == NULL || m_cue_points == NULL || m_count == 0)
+ return false;
+
+ CuePoint** const ii = m_cue_points;
+ CuePoint** i = ii;
+
+ CuePoint** const jj = ii + m_count;
+ CuePoint** j = jj;
+
+ pCP = *i;
+ if (pCP == NULL)
+ return false;
+
+ if (time_ns <= pCP->GetTime(m_pSegment)) {
+ pTP = pCP->Find(pTrack);
+ return (pTP != NULL);
+ }
+
+ while (i < j) {
+ // INVARIANT:
+ //[ii, i) <= time_ns
+ //[i, j) ?
+ //[j, jj) > time_ns
+
+ CuePoint** const k = i + (j - i) / 2;
+ if (k >= jj)
+ return false;
+
+ CuePoint* const pCP = *k;
+ if (pCP == NULL)
+ return false;
+
+ const long long t = pCP->GetTime(m_pSegment);
+
+ if (t <= time_ns)
+ i = k + 1;
+ else
+ j = k;
+
+ if (i > j)
+ return false;
+ }
+
+ if (i != j || i > jj || i <= ii)
+ return false;
+
+ pCP = *--i;
+
+ if (pCP == NULL || pCP->GetTime(m_pSegment) > time_ns)
+ return false;
+
+ // TODO: here and elsewhere, it's probably not correct to search
+ // for the cue point with this time, and then search for a matching
+ // track. In principle, the matching track could be on some earlier
+ // cue point, and with our current algorithm, we'd miss it. To make
+ // this bullet-proof, we'd need to create a secondary structure,
+ // with a list of cue points that apply to a track, and then search
+ // that track-based structure for a matching cue point.
+
+ pTP = pCP->Find(pTrack);
+ return (pTP != NULL);
+}
+
+const CuePoint* Cues::GetFirst() const {
+ if (m_cue_points == NULL || m_count == 0)
+ return NULL;
+
+ CuePoint* const* const pp = m_cue_points;
+ if (pp == NULL)
+ return NULL;
+
+ CuePoint* const pCP = pp[0];
+ if (pCP == NULL || pCP->GetTimeCode() < 0)
+ return NULL;
+
+ return pCP;
+}
+
+const CuePoint* Cues::GetLast() const {
+ if (m_cue_points == NULL || m_count <= 0)
+ return NULL;
+
+ const long index = m_count - 1;
+
+ CuePoint* const* const pp = m_cue_points;
+ if (pp == NULL)
+ return NULL;
+
+ CuePoint* const pCP = pp[index];
+ if (pCP == NULL || pCP->GetTimeCode() < 0)
+ return NULL;
+
+ return pCP;
+}
+
+const CuePoint* Cues::GetNext(const CuePoint* pCurr) const {
+ if (pCurr == NULL || pCurr->GetTimeCode() < 0 || m_cue_points == NULL ||
+ m_count < 1) {
+ return NULL;
+ }
+
+ long index = pCurr->m_index;
+ if (index >= m_count)
+ return NULL;
+
+ CuePoint* const* const pp = m_cue_points;
+ if (pp == NULL || pp[index] != pCurr)
+ return NULL;
+
+ ++index;
+
+ if (index >= m_count)
+ return NULL;
+
+ CuePoint* const pNext = pp[index];
+
+ if (pNext == NULL || pNext->GetTimeCode() < 0)
+ return NULL;
+
+ return pNext;
+}
+
+const BlockEntry* Cues::GetBlock(const CuePoint* pCP,
+ const CuePoint::TrackPosition* pTP) const {
+ if (pCP == NULL || pTP == NULL)
+ return NULL;
+
+ return m_pSegment->GetBlock(*pCP, *pTP);
+}
+
+const BlockEntry* Segment::GetBlock(const CuePoint& cp,
+ const CuePoint::TrackPosition& tp) {
+ Cluster** const ii = m_clusters;
+ Cluster** i = ii;
+
+ const long count = m_clusterCount + m_clusterPreloadCount;
+
+ Cluster** const jj = ii + count;
+ Cluster** j = jj;
+
+ while (i < j) {
+ // INVARIANT:
+ //[ii, i) < pTP->m_pos
+ //[i, j) ?
+ //[j, jj) > pTP->m_pos
+
+ Cluster** const k = i + (j - i) / 2;
+ assert(k < jj);
+
+ Cluster* const pCluster = *k;
+ assert(pCluster);
+
+ // const long long pos_ = pCluster->m_pos;
+ // assert(pos_);
+ // const long long pos = pos_ * ((pos_ < 0) ? -1 : 1);
+
+ const long long pos = pCluster->GetPosition();
+ assert(pos >= 0);
+
+ if (pos < tp.m_pos)
+ i = k + 1;
+ else if (pos > tp.m_pos)
+ j = k;
+ else
+ return pCluster->GetEntry(cp, tp);
+ }
+
+ assert(i == j);
+ // assert(Cluster::HasBlockEntries(this, tp.m_pos));
+
+ Cluster* const pCluster = Cluster::Create(this, -1, tp.m_pos); //, -1);
+ if (pCluster == NULL)
+ return NULL;
+
+ const ptrdiff_t idx = i - m_clusters;
+
+ if (!PreloadCluster(pCluster, idx)) {
+ delete pCluster;
+ return NULL;
+ }
+ assert(m_clusters);
+ assert(m_clusterPreloadCount > 0);
+ assert(m_clusters[idx] == pCluster);
+
+ return pCluster->GetEntry(cp, tp);
+}
+
+const Cluster* Segment::FindOrPreloadCluster(long long requested_pos) {
+ if (requested_pos < 0)
+ return 0;
+
+ Cluster** const ii = m_clusters;
+ Cluster** i = ii;
+
+ const long count = m_clusterCount + m_clusterPreloadCount;
+
+ Cluster** const jj = ii + count;
+ Cluster** j = jj;
+
+ while (i < j) {
+ // INVARIANT:
+ //[ii, i) < pTP->m_pos
+ //[i, j) ?
+ //[j, jj) > pTP->m_pos
+
+ Cluster** const k = i + (j - i) / 2;
+ assert(k < jj);
+
+ Cluster* const pCluster = *k;
+ assert(pCluster);
+
+ // const long long pos_ = pCluster->m_pos;
+ // assert(pos_);
+ // const long long pos = pos_ * ((pos_ < 0) ? -1 : 1);
+
+ const long long pos = pCluster->GetPosition();
+ assert(pos >= 0);
+
+ if (pos < requested_pos)
+ i = k + 1;
+ else if (pos > requested_pos)
+ j = k;
+ else
+ return pCluster;
+ }
+
+ assert(i == j);
+ // assert(Cluster::HasBlockEntries(this, tp.m_pos));
+
+ Cluster* const pCluster = Cluster::Create(this, -1, requested_pos);
+ if (pCluster == NULL)
+ return NULL;
+
+ const ptrdiff_t idx = i - m_clusters;
+
+ if (!PreloadCluster(pCluster, idx)) {
+ delete pCluster;
+ return NULL;
+ }
+ assert(m_clusters);
+ assert(m_clusterPreloadCount > 0);
+ assert(m_clusters[idx] == pCluster);
+
+ return pCluster;
+}
+
+CuePoint::CuePoint(long idx, long long pos)
+ : m_element_start(0),
+ m_element_size(0),
+ m_index(idx),
+ m_timecode(-1 * pos),
+ m_track_positions(NULL),
+ m_track_positions_count(0) {
+ assert(pos > 0);
+}
+
+CuePoint::~CuePoint() { delete[] m_track_positions; }
+
+bool CuePoint::Load(IMkvReader* pReader) {
+ // odbgstream os;
+ // os << "CuePoint::Load(begin): timecode=" << m_timecode << endl;
+
+ if (m_timecode >= 0) // already loaded
+ return true;
+
+ assert(m_track_positions == NULL);
+ assert(m_track_positions_count == 0);
+
+ long long pos_ = -m_timecode;
+ const long long element_start = pos_;
+
+ long long stop;
+
+ {
+ long len;
+
+ const long long id = ReadID(pReader, pos_, len);
+ if (id != libwebm::kMkvCuePoint)
+ return false;
+
+ pos_ += len; // consume ID
+
+ const long long size = ReadUInt(pReader, pos_, len);
+ assert(size >= 0);
+
+ pos_ += len; // consume Size field
+ // pos_ now points to start of payload
+
+ stop = pos_ + size;
+ }
+
+ const long long element_size = stop - element_start;
+
+ long long pos = pos_;
+
+ // First count number of track positions
+
+ while (pos < stop) {
+ long len;
+
+ const long long id = ReadID(pReader, pos, len);
+ if ((id < 0) || (pos + len > stop)) {
+ return false;
+ }
+
+ pos += len; // consume ID
+
+ const long long size = ReadUInt(pReader, pos, len);
+ if ((size < 0) || (pos + len > stop)) {
+ return false;
+ }
+
+ pos += len; // consume Size field
+ if ((pos + size) > stop) {
+ return false;
+ }
+
+ if (id == libwebm::kMkvCueTime)
+ m_timecode = UnserializeUInt(pReader, pos, size);
+
+ else if (id == libwebm::kMkvCueTrackPositions)
+ ++m_track_positions_count;
+
+ pos += size; // consume payload
+ }
+
+ if (m_timecode < 0 || m_track_positions_count <= 0) {
+ return false;
+ }
+
+ // os << "CuePoint::Load(cont'd): idpos=" << idpos
+ // << " timecode=" << m_timecode
+ // << endl;
+
+ m_track_positions = new (std::nothrow) TrackPosition[m_track_positions_count];
+ if (m_track_positions == NULL)
+ return false;
+
+ // Now parse track positions
+
+ TrackPosition* p = m_track_positions;
+ pos = pos_;
+
+ while (pos < stop) {
+ long len;
+
+ const long long id = ReadID(pReader, pos, len);
+ if (id < 0 || (pos + len) > stop)
+ return false;
+
+ pos += len; // consume ID
+
+ const long long size = ReadUInt(pReader, pos, len);
+ assert(size >= 0);
+ assert((pos + len) <= stop);
+
+ pos += len; // consume Size field
+ assert((pos + size) <= stop);
+
+ if (id == libwebm::kMkvCueTrackPositions) {
+ TrackPosition& tp = *p++;
+ if (!tp.Parse(pReader, pos, size)) {
+ return false;
+ }
+ }
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return false;
+ }
+
+ assert(size_t(p - m_track_positions) == m_track_positions_count);
+
+ m_element_start = element_start;
+ m_element_size = element_size;
+
+ return true;
+}
+
+bool CuePoint::TrackPosition::Parse(IMkvReader* pReader, long long start_,
+ long long size_) {
+ const long long stop = start_ + size_;
+ long long pos = start_;
+
+ m_track = -1;
+ m_pos = -1;
+ m_block = 1; // default
+
+ while (pos < stop) {
+ long len;
+
+ const long long id = ReadID(pReader, pos, len);
+ if ((id < 0) || ((pos + len) > stop)) {
+ return false;
+ }
+
+ pos += len; // consume ID
+
+ const long long size = ReadUInt(pReader, pos, len);
+ if ((size < 0) || ((pos + len) > stop)) {
+ return false;
+ }
+
+ pos += len; // consume Size field
+ if ((pos + size) > stop) {
+ return false;
+ }
+
+ if (id == libwebm::kMkvCueTrack)
+ m_track = UnserializeUInt(pReader, pos, size);
+ else if (id == libwebm::kMkvCueClusterPosition)
+ m_pos = UnserializeUInt(pReader, pos, size);
+ else if (id == libwebm::kMkvCueBlockNumber)
+ m_block = UnserializeUInt(pReader, pos, size);
+
+ pos += size; // consume payload
+ }
+
+ if ((m_pos < 0) || (m_track <= 0)) {
+ return false;
+ }
+
+ return true;
+}
+
+const CuePoint::TrackPosition* CuePoint::Find(const Track* pTrack) const {
+ if (pTrack == NULL) {
+ return NULL;
+ }
+
+ const long long n = pTrack->GetNumber();
+
+ const TrackPosition* i = m_track_positions;
+ const TrackPosition* const j = i + m_track_positions_count;
+
+ while (i != j) {
+ const TrackPosition& p = *i++;
+
+ if (p.m_track == n)
+ return &p;
+ }
+
+ return NULL; // no matching track number found
+}
+
+long long CuePoint::GetTimeCode() const { return m_timecode; }
+
+long long CuePoint::GetTime(const Segment* pSegment) const {
+ assert(pSegment);
+ assert(m_timecode >= 0);
+
+ const SegmentInfo* const pInfo = pSegment->GetInfo();
+ assert(pInfo);
+
+ const long long scale = pInfo->GetTimeCodeScale();
+ assert(scale >= 1);
+
+ const long long time = scale * m_timecode;
+
+ return time;
+}
+
+bool Segment::DoneParsing() const {
+ if (m_size < 0) {
+ long long total, avail;
+
+ const int status = m_pReader->Length(&total, &avail);
+
+ if (status < 0) // error
+ return true; // must assume done
+
+ if (total < 0)
+ return false; // assume live stream
+
+ return (m_pos >= total);
+ }
+
+ const long long stop = m_start + m_size;
+
+ return (m_pos >= stop);
+}
+
+const Cluster* Segment::GetFirst() const {
+ if ((m_clusters == NULL) || (m_clusterCount <= 0))
+ return &m_eos;
+
+ Cluster* const pCluster = m_clusters[0];
+ assert(pCluster);
+
+ return pCluster;
+}
+
+const Cluster* Segment::GetLast() const {
+ if ((m_clusters == NULL) || (m_clusterCount <= 0))
+ return &m_eos;
+
+ const long idx = m_clusterCount - 1;
+
+ Cluster* const pCluster = m_clusters[idx];
+ assert(pCluster);
+
+ return pCluster;
+}
+
+unsigned long Segment::GetCount() const { return m_clusterCount; }
+
+const Cluster* Segment::GetNext(const Cluster* pCurr) {
+ assert(pCurr);
+ assert(pCurr != &m_eos);
+ assert(m_clusters);
+
+ long idx = pCurr->m_index;
+
+ if (idx >= 0) {
+ assert(m_clusterCount > 0);
+ assert(idx < m_clusterCount);
+ assert(pCurr == m_clusters[idx]);
+
+ ++idx;
+
+ if (idx >= m_clusterCount)
+ return &m_eos; // caller will LoadCluster as desired
+
+ Cluster* const pNext = m_clusters[idx];
+ assert(pNext);
+ assert(pNext->m_index >= 0);
+ assert(pNext->m_index == idx);
+
+ return pNext;
+ }
+
+ assert(m_clusterPreloadCount > 0);
+
+ long long pos = pCurr->m_element_start;
+
+ assert(m_size >= 0); // TODO
+ const long long stop = m_start + m_size; // end of segment
+
+ {
+ long len;
+
+ long long result = GetUIntLength(m_pReader, pos, len);
+ assert(result == 0);
+ assert((pos + len) <= stop); // TODO
+ if (result != 0)
+ return NULL;
+
+ const long long id = ReadID(m_pReader, pos, len);
+ if (id != libwebm::kMkvCluster)
+ return NULL;
+
+ pos += len; // consume ID
+
+ // Read Size
+ result = GetUIntLength(m_pReader, pos, len);
+ assert(result == 0); // TODO
+ assert((pos + len) <= stop); // TODO
+
+ const long long size = ReadUInt(m_pReader, pos, len);
+ assert(size > 0); // TODO
+ // assert((pCurr->m_size <= 0) || (pCurr->m_size == size));
+
+ pos += len; // consume length of size of element
+ assert((pos + size) <= stop); // TODO
+
+ // Pos now points to start of payload
+
+ pos += size; // consume payload
+ }
+
+ long long off_next = 0;
+
+ while (pos < stop) {
+ long len;
+
+ long long result = GetUIntLength(m_pReader, pos, len);
+ assert(result == 0);
+ assert((pos + len) <= stop); // TODO
+ if (result != 0)
+ return NULL;
+
+ const long long idpos = pos; // pos of next (potential) cluster
+
+ const long long id = ReadID(m_pReader, idpos, len);
+ if (id < 0)
+ return NULL;
+
+ pos += len; // consume ID
+
+ // Read Size
+ result = GetUIntLength(m_pReader, pos, len);
+ assert(result == 0); // TODO
+ assert((pos + len) <= stop); // TODO
+
+ const long long size = ReadUInt(m_pReader, pos, len);
+ assert(size >= 0); // TODO
+
+ pos += len; // consume length of size of element
+ assert((pos + size) <= stop); // TODO
+
+ // Pos now points to start of payload
+
+ if (size == 0) // weird
+ continue;
+
+ if (id == libwebm::kMkvCluster) {
+ const long long off_next_ = idpos - m_start;
+
+ long long pos_;
+ long len_;
+
+ const long status = Cluster::HasBlockEntries(this, off_next_, pos_, len_);
+
+ assert(status >= 0);
+
+ if (status > 0) {
+ off_next = off_next_;
+ break;
+ }
+ }
+
+ pos += size; // consume payload
+ }
+
+ if (off_next <= 0)
+ return 0;
+
+ Cluster** const ii = m_clusters + m_clusterCount;
+ Cluster** i = ii;
+
+ Cluster** const jj = ii + m_clusterPreloadCount;
+ Cluster** j = jj;
+
+ while (i < j) {
+ // INVARIANT:
+ //[0, i) < pos_next
+ //[i, j) ?
+ //[j, jj) > pos_next
+
+ Cluster** const k = i + (j - i) / 2;
+ assert(k < jj);
+
+ Cluster* const pNext = *k;
+ assert(pNext);
+ assert(pNext->m_index < 0);
+
+ // const long long pos_ = pNext->m_pos;
+ // assert(pos_);
+ // pos = pos_ * ((pos_ < 0) ? -1 : 1);
+
+ pos = pNext->GetPosition();
+
+ if (pos < off_next)
+ i = k + 1;
+ else if (pos > off_next)
+ j = k;
+ else
+ return pNext;
+ }
+
+ assert(i == j);
+
+ Cluster* const pNext = Cluster::Create(this, -1, off_next);
+ if (pNext == NULL)
+ return NULL;
+
+ const ptrdiff_t idx_next = i - m_clusters; // insertion position
+
+ if (!PreloadCluster(pNext, idx_next)) {
+ delete pNext;
+ return NULL;
+ }
+ assert(m_clusters);
+ assert(idx_next < m_clusterSize);
+ assert(m_clusters[idx_next] == pNext);
+
+ return pNext;
+}
+
+long Segment::ParseNext(const Cluster* pCurr, const Cluster*& pResult,
+ long long& pos, long& len) {
+ assert(pCurr);
+ assert(!pCurr->EOS());
+ assert(m_clusters);
+
+ pResult = 0;
+
+ if (pCurr->m_index >= 0) { // loaded (not merely preloaded)
+ assert(m_clusters[pCurr->m_index] == pCurr);
+
+ const long next_idx = pCurr->m_index + 1;
+
+ if (next_idx < m_clusterCount) {
+ pResult = m_clusters[next_idx];
+ return 0; // success
+ }
+
+ // curr cluster is last among loaded
+
+ const long result = LoadCluster(pos, len);
+
+ if (result < 0) // error or underflow
+ return result;
+
+ if (result > 0) // no more clusters
+ {
+ // pResult = &m_eos;
+ return 1;
+ }
+
+ pResult = GetLast();
+ return 0; // success
+ }
+
+ assert(m_pos > 0);
+
+ long long total, avail;
+
+ long status = m_pReader->Length(&total, &avail);
+
+ if (status < 0) // error
+ return status;
+
+ assert((total < 0) || (avail <= total));
+
+ const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+ // interrogate curr cluster
+
+ pos = pCurr->m_element_start;
+
+ if (pCurr->m_element_size >= 0)
+ pos += pCurr->m_element_size;
+ else {
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long id = ReadUInt(m_pReader, pos, len);
+
+ if (id != libwebm::kMkvCluster)
+ return -1;
+
+ pos += len; // consume ID
+
+ // Read Size
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(m_pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(size);
+
+ pos += len; // consume size field
+
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if (size == unknown_size) // TODO: should never happen
+ return E_FILE_FORMAT_INVALID; // TODO: resolve this
+
+ // assert((pCurr->m_size <= 0) || (pCurr->m_size == size));
+
+ if ((segment_stop >= 0) && ((pos + size) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ // Pos now points to start of payload
+
+ pos += size; // consume payload (that is, the current cluster)
+ if (segment_stop >= 0 && pos > segment_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ // By consuming the payload, we are assuming that the curr
+ // cluster isn't interesting. That is, we don't bother checking
+ // whether the payload of the curr cluster is less than what
+ // happens to be available (obtained via IMkvReader::Length).
+ // Presumably the caller has already dispensed with the current
+ // cluster, and really does want the next cluster.
+ }
+
+ // pos now points to just beyond the last fully-loaded cluster
+
+ for (;;) {
+ const long status = DoParseNext(pResult, pos, len);
+
+ if (status <= 1)
+ return status;
+ }
+}
+
+long Segment::DoParseNext(const Cluster*& pResult, long long& pos, long& len) {
+ long long total, avail;
+
+ long status = m_pReader->Length(&total, &avail);
+
+ if (status < 0) // error
+ return status;
+
+ assert((total < 0) || (avail <= total));
+
+ const long long segment_stop = (m_size < 0) ? -1 : m_start + m_size;
+
+ // Parse next cluster. This is strictly a parsing activity.
+ // Creation of a new cluster object happens later, after the
+ // parsing is done.
+
+ long long off_next = 0;
+ long long cluster_size = -1;
+
+ for (;;) {
+ if ((total >= 0) && (pos >= total))
+ return 1; // EOF
+
+ if ((segment_stop >= 0) && (pos >= segment_stop))
+ return 1; // EOF
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long idpos = pos; // absolute
+ const long long idoff = pos - m_start; // relative
+
+ const long long id = ReadID(m_pReader, idpos, len); // absolute
+
+ if (id < 0) // error
+ return static_cast<long>(id);
+
+ if (id == 0) // weird
+ return -1; // generic error
+
+ pos += len; // consume ID
+
+ // Read Size
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(m_pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(size);
+
+ pos += len; // consume length of size of element
+
+ // Pos now points to start of payload
+
+ if (size == 0) // weird
+ continue;
+
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if ((segment_stop >= 0) && (size != unknown_size) &&
+ ((pos + size) > segment_stop)) {
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (id == libwebm::kMkvCues) {
+ if (size == unknown_size)
+ return E_FILE_FORMAT_INVALID;
+
+ const long long element_stop = pos + size;
+
+ if ((segment_stop >= 0) && (element_stop > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ const long long element_start = idpos;
+ const long long element_size = element_stop - element_start;
+
+ if (m_pCues == NULL) {
+ m_pCues = new (std::nothrow)
+ Cues(this, pos, size, element_start, element_size);
+ if (m_pCues == NULL)
+ return false;
+ }
+
+ pos += size; // consume payload
+ if (segment_stop >= 0 && pos > segment_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ continue;
+ }
+
+ if (id != libwebm::kMkvCluster) { // not a Cluster ID
+ if (size == unknown_size)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += size; // consume payload
+ if (segment_stop >= 0 && pos > segment_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ continue;
+ }
+
+ // We have a cluster.
+ off_next = idoff;
+
+ if (size != unknown_size)
+ cluster_size = size;
+
+ break;
+ }
+
+ assert(off_next > 0); // have cluster
+
+ // We have parsed the next cluster.
+ // We have not created a cluster object yet. What we need
+ // to do now is determine whether it has already be preloaded
+ //(in which case, an object for this cluster has already been
+ // created), and if not, create a new cluster object.
+
+ Cluster** const ii = m_clusters + m_clusterCount;
+ Cluster** i = ii;
+
+ Cluster** const jj = ii + m_clusterPreloadCount;
+ Cluster** j = jj;
+
+ while (i < j) {
+ // INVARIANT:
+ //[0, i) < pos_next
+ //[i, j) ?
+ //[j, jj) > pos_next
+
+ Cluster** const k = i + (j - i) / 2;
+ assert(k < jj);
+
+ const Cluster* const pNext = *k;
+ assert(pNext);
+ assert(pNext->m_index < 0);
+
+ pos = pNext->GetPosition();
+ assert(pos >= 0);
+
+ if (pos < off_next)
+ i = k + 1;
+ else if (pos > off_next)
+ j = k;
+ else {
+ pResult = pNext;
+ return 0; // success
+ }
+ }
+
+ assert(i == j);
+
+ long long pos_;
+ long len_;
+
+ status = Cluster::HasBlockEntries(this, off_next, pos_, len_);
+
+ if (status < 0) { // error or underflow
+ pos = pos_;
+ len = len_;
+
+ return status;
+ }
+
+ if (status > 0) { // means "found at least one block entry"
+ Cluster* const pNext = Cluster::Create(this,
+ -1, // preloaded
+ off_next);
+ if (pNext == NULL)
+ return -1;
+
+ const ptrdiff_t idx_next = i - m_clusters; // insertion position
+
+ if (!PreloadCluster(pNext, idx_next)) {
+ delete pNext;
+ return -1;
+ }
+ assert(m_clusters);
+ assert(idx_next < m_clusterSize);
+ assert(m_clusters[idx_next] == pNext);
+
+ pResult = pNext;
+ return 0; // success
+ }
+
+ // status == 0 means "no block entries found"
+
+ if (cluster_size < 0) { // unknown size
+ const long long payload_pos = pos; // absolute pos of cluster payload
+
+ for (;;) { // determine cluster size
+ if ((total >= 0) && (pos >= total))
+ break;
+
+ if ((segment_stop >= 0) && (pos >= segment_stop))
+ break; // no more clusters
+
+ // Read ID
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long idpos = pos;
+ const long long id = ReadID(m_pReader, idpos, len);
+
+ if (id < 0) // error (or underflow)
+ return static_cast<long>(id);
+
+ // This is the distinguished set of ID's we use to determine
+ // that we have exhausted the sub-element's inside the cluster
+ // whose ID we parsed earlier.
+
+ if (id == libwebm::kMkvCluster || id == libwebm::kMkvCues)
+ break;
+
+ pos += len; // consume ID (of sub-element)
+
+ // Read Size
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(m_pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(m_pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(size);
+
+ pos += len; // consume size field of element
+
+ // pos now points to start of sub-element's payload
+
+ if (size == 0) // weird
+ continue;
+
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if (size == unknown_size)
+ return E_FILE_FORMAT_INVALID; // not allowed for sub-elements
+
+ if ((segment_stop >= 0) && ((pos + size) > segment_stop)) // weird
+ return E_FILE_FORMAT_INVALID;
+
+ pos += size; // consume payload of sub-element
+ if (segment_stop >= 0 && pos > segment_stop)
+ return E_FILE_FORMAT_INVALID;
+ } // determine cluster size
+
+ cluster_size = pos - payload_pos;
+ assert(cluster_size >= 0); // TODO: handle cluster_size = 0
+
+ pos = payload_pos; // reset and re-parse original cluster
+ }
+
+ pos += cluster_size; // consume payload
+ if (segment_stop >= 0 && pos > segment_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ return 2; // try to find a cluster that follows next
+}
+
+const Cluster* Segment::FindCluster(long long time_ns) const {
+ if ((m_clusters == NULL) || (m_clusterCount <= 0))
+ return &m_eos;
+
+ {
+ Cluster* const pCluster = m_clusters[0];
+ assert(pCluster);
+ assert(pCluster->m_index == 0);
+
+ if (time_ns <= pCluster->GetTime())
+ return pCluster;
+ }
+
+ // Binary search of cluster array
+
+ long i = 0;
+ long j = m_clusterCount;
+
+ while (i < j) {
+ // INVARIANT:
+ //[0, i) <= time_ns
+ //[i, j) ?
+ //[j, m_clusterCount) > time_ns
+
+ const long k = i + (j - i) / 2;
+ assert(k < m_clusterCount);
+
+ Cluster* const pCluster = m_clusters[k];
+ assert(pCluster);
+ assert(pCluster->m_index == k);
+
+ const long long t = pCluster->GetTime();
+
+ if (t <= time_ns)
+ i = k + 1;
+ else
+ j = k;
+
+ assert(i <= j);
+ }
+
+ assert(i == j);
+ assert(i > 0);
+ assert(i <= m_clusterCount);
+
+ const long k = i - 1;
+
+ Cluster* const pCluster = m_clusters[k];
+ assert(pCluster);
+ assert(pCluster->m_index == k);
+ assert(pCluster->GetTime() <= time_ns);
+
+ return pCluster;
+}
+
+const Tracks* Segment::GetTracks() const { return m_pTracks; }
+const SegmentInfo* Segment::GetInfo() const { return m_pInfo; }
+const Cues* Segment::GetCues() const { return m_pCues; }
+const Chapters* Segment::GetChapters() const { return m_pChapters; }
+const Tags* Segment::GetTags() const { return m_pTags; }
+const SeekHead* Segment::GetSeekHead() const { return m_pSeekHead; }
+
+long long Segment::GetDuration() const {
+ assert(m_pInfo);
+ return m_pInfo->GetDuration();
+}
+
+Chapters::Chapters(Segment* pSegment, long long payload_start,
+ long long payload_size, long long element_start,
+ long long element_size)
+ : m_pSegment(pSegment),
+ m_start(payload_start),
+ m_size(payload_size),
+ m_element_start(element_start),
+ m_element_size(element_size),
+ m_editions(NULL),
+ m_editions_size(0),
+ m_editions_count(0) {}
+
+Chapters::~Chapters() {
+ while (m_editions_count > 0) {
+ Edition& e = m_editions[--m_editions_count];
+ e.Clear();
+ }
+ delete[] m_editions;
+}
+
+long Chapters::Parse() {
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ long long pos = m_start; // payload start
+ const long long stop = pos + m_size; // payload stop
+
+ while (pos < stop) {
+ long long id, size;
+
+ long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (size == 0) // weird
+ continue;
+
+ if (id == libwebm::kMkvEditionEntry) {
+ status = ParseEdition(pos, size);
+
+ if (status < 0) // error
+ return status;
+ }
+
+ pos += size;
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+ return 0;
+}
+
+int Chapters::GetEditionCount() const { return m_editions_count; }
+
+const Chapters::Edition* Chapters::GetEdition(int idx) const {
+ if (idx < 0)
+ return NULL;
+
+ if (idx >= m_editions_count)
+ return NULL;
+
+ return m_editions + idx;
+}
+
+bool Chapters::ExpandEditionsArray() {
+ if (m_editions_size > m_editions_count)
+ return true; // nothing else to do
+
+ const int size = (m_editions_size == 0) ? 1 : 2 * m_editions_size;
+
+ Edition* const editions = new (std::nothrow) Edition[size];
+
+ if (editions == NULL)
+ return false;
+
+ for (int idx = 0; idx < m_editions_count; ++idx) {
+ m_editions[idx].ShallowCopy(editions[idx]);
+ }
+
+ delete[] m_editions;
+ m_editions = editions;
+
+ m_editions_size = size;
+ return true;
+}
+
+long Chapters::ParseEdition(long long pos, long long size) {
+ if (!ExpandEditionsArray())
+ return -1;
+
+ Edition& e = m_editions[m_editions_count++];
+ e.Init();
+
+ return e.Parse(m_pSegment->m_pReader, pos, size);
+}
+
+Chapters::Edition::Edition() {}
+
+Chapters::Edition::~Edition() {}
+
+int Chapters::Edition::GetAtomCount() const { return m_atoms_count; }
+
+const Chapters::Atom* Chapters::Edition::GetAtom(int index) const {
+ if (index < 0)
+ return NULL;
+
+ if (index >= m_atoms_count)
+ return NULL;
+
+ return m_atoms + index;
+}
+
+void Chapters::Edition::Init() {
+ m_atoms = NULL;
+ m_atoms_size = 0;
+ m_atoms_count = 0;
+}
+
+void Chapters::Edition::ShallowCopy(Edition& rhs) const {
+ rhs.m_atoms = m_atoms;
+ rhs.m_atoms_size = m_atoms_size;
+ rhs.m_atoms_count = m_atoms_count;
+}
+
+void Chapters::Edition::Clear() {
+ while (m_atoms_count > 0) {
+ Atom& a = m_atoms[--m_atoms_count];
+ a.Clear();
+ }
+
+ delete[] m_atoms;
+ m_atoms = NULL;
+
+ m_atoms_size = 0;
+}
+
+long Chapters::Edition::Parse(IMkvReader* pReader, long long pos,
+ long long size) {
+ const long long stop = pos + size;
+
+ while (pos < stop) {
+ long long id, size;
+
+ long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (size == 0)
+ continue;
+
+ if (id == libwebm::kMkvChapterAtom) {
+ status = ParseAtom(pReader, pos, size);
+
+ if (status < 0) // error
+ return status;
+ }
+
+ pos += size;
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+ return 0;
+}
+
+long Chapters::Edition::ParseAtom(IMkvReader* pReader, long long pos,
+ long long size) {
+ if (!ExpandAtomsArray())
+ return -1;
+
+ Atom& a = m_atoms[m_atoms_count++];
+ a.Init();
+
+ return a.Parse(pReader, pos, size);
+}
+
+bool Chapters::Edition::ExpandAtomsArray() {
+ if (m_atoms_size > m_atoms_count)
+ return true; // nothing else to do
+
+ const int size = (m_atoms_size == 0) ? 1 : 2 * m_atoms_size;
+
+ Atom* const atoms = new (std::nothrow) Atom[size];
+
+ if (atoms == NULL)
+ return false;
+
+ for (int idx = 0; idx < m_atoms_count; ++idx) {
+ m_atoms[idx].ShallowCopy(atoms[idx]);
+ }
+
+ delete[] m_atoms;
+ m_atoms = atoms;
+
+ m_atoms_size = size;
+ return true;
+}
+
+Chapters::Atom::Atom() {}
+
+Chapters::Atom::~Atom() {}
+
+unsigned long long Chapters::Atom::GetUID() const { return m_uid; }
+
+const char* Chapters::Atom::GetStringUID() const { return m_string_uid; }
+
+long long Chapters::Atom::GetStartTimecode() const { return m_start_timecode; }
+
+long long Chapters::Atom::GetStopTimecode() const { return m_stop_timecode; }
+
+long long Chapters::Atom::GetStartTime(const Chapters* pChapters) const {
+ return GetTime(pChapters, m_start_timecode);
+}
+
+long long Chapters::Atom::GetStopTime(const Chapters* pChapters) const {
+ return GetTime(pChapters, m_stop_timecode);
+}
+
+int Chapters::Atom::GetDisplayCount() const { return m_displays_count; }
+
+const Chapters::Display* Chapters::Atom::GetDisplay(int index) const {
+ if (index < 0)
+ return NULL;
+
+ if (index >= m_displays_count)
+ return NULL;
+
+ return m_displays + index;
+}
+
+void Chapters::Atom::Init() {
+ m_string_uid = NULL;
+ m_uid = 0;
+ m_start_timecode = -1;
+ m_stop_timecode = -1;
+
+ m_displays = NULL;
+ m_displays_size = 0;
+ m_displays_count = 0;
+}
+
+void Chapters::Atom::ShallowCopy(Atom& rhs) const {
+ rhs.m_string_uid = m_string_uid;
+ rhs.m_uid = m_uid;
+ rhs.m_start_timecode = m_start_timecode;
+ rhs.m_stop_timecode = m_stop_timecode;
+
+ rhs.m_displays = m_displays;
+ rhs.m_displays_size = m_displays_size;
+ rhs.m_displays_count = m_displays_count;
+}
+
+void Chapters::Atom::Clear() {
+ delete[] m_string_uid;
+ m_string_uid = NULL;
+
+ while (m_displays_count > 0) {
+ Display& d = m_displays[--m_displays_count];
+ d.Clear();
+ }
+
+ delete[] m_displays;
+ m_displays = NULL;
+
+ m_displays_size = 0;
+}
+
+long Chapters::Atom::Parse(IMkvReader* pReader, long long pos, long long size) {
+ const long long stop = pos + size;
+
+ while (pos < stop) {
+ long long id, size;
+
+ long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (size == 0) // 0 length payload, skip.
+ continue;
+
+ if (id == libwebm::kMkvChapterDisplay) {
+ status = ParseDisplay(pReader, pos, size);
+
+ if (status < 0) // error
+ return status;
+ } else if (id == libwebm::kMkvChapterStringUID) {
+ status = UnserializeString(pReader, pos, size, m_string_uid);
+
+ if (status < 0) // error
+ return status;
+ } else if (id == libwebm::kMkvChapterUID) {
+ long long val;
+ status = UnserializeInt(pReader, pos, size, val);
+
+ if (status < 0) // error
+ return status;
+
+ m_uid = static_cast<unsigned long long>(val);
+ } else if (id == libwebm::kMkvChapterTimeStart) {
+ const long long val = UnserializeUInt(pReader, pos, size);
+
+ if (val < 0) // error
+ return static_cast<long>(val);
+
+ m_start_timecode = val;
+ } else if (id == libwebm::kMkvChapterTimeEnd) {
+ const long long val = UnserializeUInt(pReader, pos, size);
+
+ if (val < 0) // error
+ return static_cast<long>(val);
+
+ m_stop_timecode = val;
+ }
+
+ pos += size;
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+ return 0;
+}
+
+long long Chapters::Atom::GetTime(const Chapters* pChapters,
+ long long timecode) {
+ if (pChapters == NULL)
+ return -1;
+
+ Segment* const pSegment = pChapters->m_pSegment;
+
+ if (pSegment == NULL) // weird
+ return -1;
+
+ const SegmentInfo* const pInfo = pSegment->GetInfo();
+
+ if (pInfo == NULL)
+ return -1;
+
+ const long long timecode_scale = pInfo->GetTimeCodeScale();
+
+ if (timecode_scale < 1) // weird
+ return -1;
+
+ if (timecode < 0)
+ return -1;
+
+ const long long result = timecode_scale * timecode;
+
+ return result;
+}
+
+long Chapters::Atom::ParseDisplay(IMkvReader* pReader, long long pos,
+ long long size) {
+ if (!ExpandDisplaysArray())
+ return -1;
+
+ Display& d = m_displays[m_displays_count++];
+ d.Init();
+
+ return d.Parse(pReader, pos, size);
+}
+
+bool Chapters::Atom::ExpandDisplaysArray() {
+ if (m_displays_size > m_displays_count)
+ return true; // nothing else to do
+
+ const int size = (m_displays_size == 0) ? 1 : 2 * m_displays_size;
+
+ Display* const displays = new (std::nothrow) Display[size];
+
+ if (displays == NULL)
+ return false;
+
+ for (int idx = 0; idx < m_displays_count; ++idx) {
+ m_displays[idx].ShallowCopy(displays[idx]);
+ }
+
+ delete[] m_displays;
+ m_displays = displays;
+
+ m_displays_size = size;
+ return true;
+}
+
+Chapters::Display::Display() {}
+
+Chapters::Display::~Display() {}
+
+const char* Chapters::Display::GetString() const { return m_string; }
+
+const char* Chapters::Display::GetLanguage() const { return m_language; }
+
+const char* Chapters::Display::GetCountry() const { return m_country; }
+
+void Chapters::Display::Init() {
+ m_string = NULL;
+ m_language = NULL;
+ m_country = NULL;
+}
+
+void Chapters::Display::ShallowCopy(Display& rhs) const {
+ rhs.m_string = m_string;
+ rhs.m_language = m_language;
+ rhs.m_country = m_country;
+}
+
+void Chapters::Display::Clear() {
+ delete[] m_string;
+ m_string = NULL;
+
+ delete[] m_language;
+ m_language = NULL;
+
+ delete[] m_country;
+ m_country = NULL;
+}
+
+long Chapters::Display::Parse(IMkvReader* pReader, long long pos,
+ long long size) {
+ const long long stop = pos + size;
+
+ while (pos < stop) {
+ long long id, size;
+
+ long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (size == 0) // No payload.
+ continue;
+
+ if (id == libwebm::kMkvChapString) {
+ status = UnserializeString(pReader, pos, size, m_string);
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvChapLanguage) {
+ status = UnserializeString(pReader, pos, size, m_language);
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvChapCountry) {
+ status = UnserializeString(pReader, pos, size, m_country);
+
+ if (status)
+ return status;
+ }
+
+ pos += size;
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+ return 0;
+}
+
+Tags::Tags(Segment* pSegment, long long payload_start, long long payload_size,
+ long long element_start, long long element_size)
+ : m_pSegment(pSegment),
+ m_start(payload_start),
+ m_size(payload_size),
+ m_element_start(element_start),
+ m_element_size(element_size),
+ m_tags(NULL),
+ m_tags_size(0),
+ m_tags_count(0) {}
+
+Tags::~Tags() {
+ while (m_tags_count > 0) {
+ Tag& t = m_tags[--m_tags_count];
+ t.Clear();
+ }
+ delete[] m_tags;
+}
+
+long Tags::Parse() {
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ long long pos = m_start; // payload start
+ const long long stop = pos + m_size; // payload stop
+
+ while (pos < stop) {
+ long long id, size;
+
+ long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0)
+ return status;
+
+ if (size == 0) // 0 length tag, read another
+ continue;
+
+ if (id == libwebm::kMkvTag) {
+ status = ParseTag(pos, size);
+
+ if (status < 0)
+ return status;
+ }
+
+ pos += size;
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ return 0;
+}
+
+int Tags::GetTagCount() const { return m_tags_count; }
+
+const Tags::Tag* Tags::GetTag(int idx) const {
+ if (idx < 0)
+ return NULL;
+
+ if (idx >= m_tags_count)
+ return NULL;
+
+ return m_tags + idx;
+}
+
+bool Tags::ExpandTagsArray() {
+ if (m_tags_size > m_tags_count)
+ return true; // nothing else to do
+
+ const int size = (m_tags_size == 0) ? 1 : 2 * m_tags_size;
+
+ Tag* const tags = new (std::nothrow) Tag[size];
+
+ if (tags == NULL)
+ return false;
+
+ for (int idx = 0; idx < m_tags_count; ++idx) {
+ m_tags[idx].ShallowCopy(tags[idx]);
+ }
+
+ delete[] m_tags;
+ m_tags = tags;
+
+ m_tags_size = size;
+ return true;
+}
+
+long Tags::ParseTag(long long pos, long long size) {
+ if (!ExpandTagsArray())
+ return -1;
+
+ Tag& t = m_tags[m_tags_count++];
+ t.Init();
+
+ return t.Parse(m_pSegment->m_pReader, pos, size);
+}
+
+Tags::Tag::Tag() {}
+
+Tags::Tag::~Tag() {}
+
+int Tags::Tag::GetSimpleTagCount() const { return m_simple_tags_count; }
+
+const Tags::SimpleTag* Tags::Tag::GetSimpleTag(int index) const {
+ if (index < 0)
+ return NULL;
+
+ if (index >= m_simple_tags_count)
+ return NULL;
+
+ return m_simple_tags + index;
+}
+
+void Tags::Tag::Init() {
+ m_simple_tags = NULL;
+ m_simple_tags_size = 0;
+ m_simple_tags_count = 0;
+}
+
+void Tags::Tag::ShallowCopy(Tag& rhs) const {
+ rhs.m_simple_tags = m_simple_tags;
+ rhs.m_simple_tags_size = m_simple_tags_size;
+ rhs.m_simple_tags_count = m_simple_tags_count;
+}
+
+void Tags::Tag::Clear() {
+ while (m_simple_tags_count > 0) {
+ SimpleTag& d = m_simple_tags[--m_simple_tags_count];
+ d.Clear();
+ }
+
+ delete[] m_simple_tags;
+ m_simple_tags = NULL;
+
+ m_simple_tags_size = 0;
+}
+
+long Tags::Tag::Parse(IMkvReader* pReader, long long pos, long long size) {
+ const long long stop = pos + size;
+
+ while (pos < stop) {
+ long long id, size;
+
+ long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0)
+ return status;
+
+ if (size == 0) // 0 length tag, read another
+ continue;
+
+ if (id == libwebm::kMkvSimpleTag) {
+ status = ParseSimpleTag(pReader, pos, size);
+
+ if (status < 0)
+ return status;
+ }
+
+ pos += size;
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+ return 0;
+}
+
+long Tags::Tag::ParseSimpleTag(IMkvReader* pReader, long long pos,
+ long long size) {
+ if (!ExpandSimpleTagsArray())
+ return -1;
+
+ SimpleTag& st = m_simple_tags[m_simple_tags_count++];
+ st.Init();
+
+ return st.Parse(pReader, pos, size);
+}
+
+bool Tags::Tag::ExpandSimpleTagsArray() {
+ if (m_simple_tags_size > m_simple_tags_count)
+ return true; // nothing else to do
+
+ const int size = (m_simple_tags_size == 0) ? 1 : 2 * m_simple_tags_size;
+
+ SimpleTag* const displays = new (std::nothrow) SimpleTag[size];
+
+ if (displays == NULL)
+ return false;
+
+ for (int idx = 0; idx < m_simple_tags_count; ++idx) {
+ m_simple_tags[idx].ShallowCopy(displays[idx]);
+ }
+
+ delete[] m_simple_tags;
+ m_simple_tags = displays;
+
+ m_simple_tags_size = size;
+ return true;
+}
+
+Tags::SimpleTag::SimpleTag() {}
+
+Tags::SimpleTag::~SimpleTag() {}
+
+const char* Tags::SimpleTag::GetTagName() const { return m_tag_name; }
+
+const char* Tags::SimpleTag::GetTagString() const { return m_tag_string; }
+
+void Tags::SimpleTag::Init() {
+ m_tag_name = NULL;
+ m_tag_string = NULL;
+}
+
+void Tags::SimpleTag::ShallowCopy(SimpleTag& rhs) const {
+ rhs.m_tag_name = m_tag_name;
+ rhs.m_tag_string = m_tag_string;
+}
+
+void Tags::SimpleTag::Clear() {
+ delete[] m_tag_name;
+ m_tag_name = NULL;
+
+ delete[] m_tag_string;
+ m_tag_string = NULL;
+}
+
+long Tags::SimpleTag::Parse(IMkvReader* pReader, long long pos,
+ long long size) {
+ const long long stop = pos + size;
+
+ while (pos < stop) {
+ long long id, size;
+
+ long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (size == 0) // weird
+ continue;
+
+ if (id == libwebm::kMkvTagName) {
+ status = UnserializeString(pReader, pos, size, m_tag_name);
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvTagString) {
+ status = UnserializeString(pReader, pos, size, m_tag_string);
+
+ if (status)
+ return status;
+ }
+
+ pos += size;
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+ return 0;
+}
+
+SegmentInfo::SegmentInfo(Segment* pSegment, long long start, long long size_,
+ long long element_start, long long element_size)
+ : m_pSegment(pSegment),
+ m_start(start),
+ m_size(size_),
+ m_element_start(element_start),
+ m_element_size(element_size),
+ m_pMuxingAppAsUTF8(NULL),
+ m_pWritingAppAsUTF8(NULL),
+ m_pTitleAsUTF8(NULL) {}
+
+SegmentInfo::~SegmentInfo() {
+ delete[] m_pMuxingAppAsUTF8;
+ m_pMuxingAppAsUTF8 = NULL;
+
+ delete[] m_pWritingAppAsUTF8;
+ m_pWritingAppAsUTF8 = NULL;
+
+ delete[] m_pTitleAsUTF8;
+ m_pTitleAsUTF8 = NULL;
+}
+
+long SegmentInfo::Parse() {
+ assert(m_pMuxingAppAsUTF8 == NULL);
+ assert(m_pWritingAppAsUTF8 == NULL);
+ assert(m_pTitleAsUTF8 == NULL);
+
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ long long pos = m_start;
+ const long long stop = m_start + m_size;
+
+ m_timecodeScale = 1000000;
+ m_duration = -1;
+
+ while (pos < stop) {
+ long long id, size;
+
+ const long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (id == libwebm::kMkvTimecodeScale) {
+ m_timecodeScale = UnserializeUInt(pReader, pos, size);
+
+ if (m_timecodeScale <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvDuration) {
+ const long status = UnserializeFloat(pReader, pos, size, m_duration);
+
+ if (status < 0)
+ return status;
+
+ if (m_duration < 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvMuxingApp) {
+ const long status =
+ UnserializeString(pReader, pos, size, m_pMuxingAppAsUTF8);
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvWritingApp) {
+ const long status =
+ UnserializeString(pReader, pos, size, m_pWritingAppAsUTF8);
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvTitle) {
+ const long status = UnserializeString(pReader, pos, size, m_pTitleAsUTF8);
+
+ if (status)
+ return status;
+ }
+
+ pos += size;
+
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ const double rollover_check = m_duration * m_timecodeScale;
+ if (rollover_check > static_cast<double>(LLONG_MAX))
+ return E_FILE_FORMAT_INVALID;
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ return 0;
+}
+
+long long SegmentInfo::GetTimeCodeScale() const { return m_timecodeScale; }
+
+long long SegmentInfo::GetDuration() const {
+ if (m_duration < 0)
+ return -1;
+
+ assert(m_timecodeScale >= 1);
+
+ const double dd = double(m_duration) * double(m_timecodeScale);
+ const long long d = static_cast<long long>(dd);
+
+ return d;
+}
+
+const char* SegmentInfo::GetMuxingAppAsUTF8() const {
+ return m_pMuxingAppAsUTF8;
+}
+
+const char* SegmentInfo::GetWritingAppAsUTF8() const {
+ return m_pWritingAppAsUTF8;
+}
+
+const char* SegmentInfo::GetTitleAsUTF8() const { return m_pTitleAsUTF8; }
+
+///////////////////////////////////////////////////////////////
+// ContentEncoding element
+ContentEncoding::ContentCompression::ContentCompression()
+ : algo(0), settings(NULL), settings_len(0) {}
+
+ContentEncoding::ContentCompression::~ContentCompression() {
+ delete[] settings;
+}
+
+ContentEncoding::ContentEncryption::ContentEncryption()
+ : algo(0),
+ key_id(NULL),
+ key_id_len(0),
+ signature(NULL),
+ signature_len(0),
+ sig_key_id(NULL),
+ sig_key_id_len(0),
+ sig_algo(0),
+ sig_hash_algo(0) {}
+
+ContentEncoding::ContentEncryption::~ContentEncryption() {
+ delete[] key_id;
+ delete[] signature;
+ delete[] sig_key_id;
+}
+
+ContentEncoding::ContentEncoding()
+ : compression_entries_(NULL),
+ compression_entries_end_(NULL),
+ encryption_entries_(NULL),
+ encryption_entries_end_(NULL),
+ encoding_order_(0),
+ encoding_scope_(1),
+ encoding_type_(0) {}
+
+ContentEncoding::~ContentEncoding() {
+ ContentCompression** comp_i = compression_entries_;
+ ContentCompression** const comp_j = compression_entries_end_;
+
+ while (comp_i != comp_j) {
+ ContentCompression* const comp = *comp_i++;
+ delete comp;
+ }
+
+ delete[] compression_entries_;
+
+ ContentEncryption** enc_i = encryption_entries_;
+ ContentEncryption** const enc_j = encryption_entries_end_;
+
+ while (enc_i != enc_j) {
+ ContentEncryption* const enc = *enc_i++;
+ delete enc;
+ }
+
+ delete[] encryption_entries_;
+}
+
+const ContentEncoding::ContentCompression*
+ContentEncoding::GetCompressionByIndex(unsigned long idx) const {
+ const ptrdiff_t count = compression_entries_end_ - compression_entries_;
+ assert(count >= 0);
+
+ if (idx >= static_cast<unsigned long>(count))
+ return NULL;
+
+ return compression_entries_[idx];
+}
+
+unsigned long ContentEncoding::GetCompressionCount() const {
+ const ptrdiff_t count = compression_entries_end_ - compression_entries_;
+ assert(count >= 0);
+
+ return static_cast<unsigned long>(count);
+}
+
+const ContentEncoding::ContentEncryption* ContentEncoding::GetEncryptionByIndex(
+ unsigned long idx) const {
+ const ptrdiff_t count = encryption_entries_end_ - encryption_entries_;
+ assert(count >= 0);
+
+ if (idx >= static_cast<unsigned long>(count))
+ return NULL;
+
+ return encryption_entries_[idx];
+}
+
+unsigned long ContentEncoding::GetEncryptionCount() const {
+ const ptrdiff_t count = encryption_entries_end_ - encryption_entries_;
+ assert(count >= 0);
+
+ return static_cast<unsigned long>(count);
+}
+
+long ContentEncoding::ParseContentEncAESSettingsEntry(
+ long long start, long long size, IMkvReader* pReader,
+ ContentEncAESSettings* aes) {
+ assert(pReader);
+ assert(aes);
+
+ long long pos = start;
+ const long long stop = start + size;
+
+ while (pos < stop) {
+ long long id, size;
+ const long status = ParseElementHeader(pReader, pos, stop, id, size);
+ if (status < 0) // error
+ return status;
+
+ if (id == libwebm::kMkvAESSettingsCipherMode) {
+ aes->cipher_mode = UnserializeUInt(pReader, pos, size);
+ if (aes->cipher_mode != 1)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ return 0;
+}
+
+long ContentEncoding::ParseContentEncodingEntry(long long start, long long size,
+ IMkvReader* pReader) {
+ assert(pReader);
+
+ long long pos = start;
+ const long long stop = start + size;
+
+ // Count ContentCompression and ContentEncryption elements.
+ int compression_count = 0;
+ int encryption_count = 0;
+
+ while (pos < stop) {
+ long long id, size;
+ const long status = ParseElementHeader(pReader, pos, stop, id, size);
+ if (status < 0) // error
+ return status;
+
+ if (id == libwebm::kMkvContentCompression)
+ ++compression_count;
+
+ if (id == libwebm::kMkvContentEncryption)
+ ++encryption_count;
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (compression_count <= 0 && encryption_count <= 0)
+ return -1;
+
+ if (compression_count > 0) {
+ compression_entries_ =
+ new (std::nothrow) ContentCompression*[compression_count];
+ if (!compression_entries_)
+ return -1;
+ compression_entries_end_ = compression_entries_;
+ }
+
+ if (encryption_count > 0) {
+ encryption_entries_ =
+ new (std::nothrow) ContentEncryption*[encryption_count];
+ if (!encryption_entries_) {
+ delete[] compression_entries_;
+ compression_entries_ = NULL;
+ return -1;
+ }
+ encryption_entries_end_ = encryption_entries_;
+ }
+
+ pos = start;
+ while (pos < stop) {
+ long long id, size;
+ long status = ParseElementHeader(pReader, pos, stop, id, size);
+ if (status < 0) // error
+ return status;
+
+ if (id == libwebm::kMkvContentEncodingOrder) {
+ encoding_order_ = UnserializeUInt(pReader, pos, size);
+ } else if (id == libwebm::kMkvContentEncodingScope) {
+ encoding_scope_ = UnserializeUInt(pReader, pos, size);
+ if (encoding_scope_ < 1)
+ return -1;
+ } else if (id == libwebm::kMkvContentEncodingType) {
+ encoding_type_ = UnserializeUInt(pReader, pos, size);
+ } else if (id == libwebm::kMkvContentCompression) {
+ ContentCompression* const compression =
+ new (std::nothrow) ContentCompression();
+ if (!compression)
+ return -1;
+
+ status = ParseCompressionEntry(pos, size, pReader, compression);
+ if (status) {
+ delete compression;
+ return status;
+ }
+ assert(compression_count > 0);
+ *compression_entries_end_++ = compression;
+ } else if (id == libwebm::kMkvContentEncryption) {
+ ContentEncryption* const encryption =
+ new (std::nothrow) ContentEncryption();
+ if (!encryption)
+ return -1;
+
+ status = ParseEncryptionEntry(pos, size, pReader, encryption);
+ if (status) {
+ delete encryption;
+ return status;
+ }
+ assert(encryption_count > 0);
+ *encryption_entries_end_++ = encryption;
+ }
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+ return 0;
+}
+
+long ContentEncoding::ParseCompressionEntry(long long start, long long size,
+ IMkvReader* pReader,
+ ContentCompression* compression) {
+ assert(pReader);
+ assert(compression);
+
+ long long pos = start;
+ const long long stop = start + size;
+
+ bool valid = false;
+
+ while (pos < stop) {
+ long long id, size;
+ const long status = ParseElementHeader(pReader, pos, stop, id, size);
+ if (status < 0) // error
+ return status;
+
+ if (id == libwebm::kMkvContentCompAlgo) {
+ long long algo = UnserializeUInt(pReader, pos, size);
+ if (algo < 0)
+ return E_FILE_FORMAT_INVALID;
+ compression->algo = algo;
+ valid = true;
+ } else if (id == libwebm::kMkvContentCompSettings) {
+ if (size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ const size_t buflen = static_cast<size_t>(size);
+ unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen);
+ if (buf == NULL)
+ return -1;
+
+ const int read_status =
+ pReader->Read(pos, static_cast<long>(buflen), buf);
+ if (read_status) {
+ delete[] buf;
+ return status;
+ }
+
+ // There should be only one settings element per content compression.
+ if (compression->settings != NULL) {
+ delete[] buf;
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ compression->settings = buf;
+ compression->settings_len = buflen;
+ }
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ // ContentCompAlgo is mandatory
+ if (!valid)
+ return E_FILE_FORMAT_INVALID;
+
+ return 0;
+}
+
+long ContentEncoding::ParseEncryptionEntry(long long start, long long size,
+ IMkvReader* pReader,
+ ContentEncryption* encryption) {
+ assert(pReader);
+ assert(encryption);
+
+ long long pos = start;
+ const long long stop = start + size;
+
+ while (pos < stop) {
+ long long id, size;
+ const long status = ParseElementHeader(pReader, pos, stop, id, size);
+ if (status < 0) // error
+ return status;
+
+ if (id == libwebm::kMkvContentEncAlgo) {
+ encryption->algo = UnserializeUInt(pReader, pos, size);
+ if (encryption->algo != 5)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvContentEncKeyID) {
+ delete[] encryption->key_id;
+ encryption->key_id = NULL;
+ encryption->key_id_len = 0;
+
+ if (size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ const size_t buflen = static_cast<size_t>(size);
+ unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen);
+ if (buf == NULL)
+ return -1;
+
+ const int read_status =
+ pReader->Read(pos, static_cast<long>(buflen), buf);
+ if (read_status) {
+ delete[] buf;
+ return status;
+ }
+
+ encryption->key_id = buf;
+ encryption->key_id_len = buflen;
+ } else if (id == libwebm::kMkvContentSignature) {
+ delete[] encryption->signature;
+ encryption->signature = NULL;
+ encryption->signature_len = 0;
+
+ if (size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ const size_t buflen = static_cast<size_t>(size);
+ unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen);
+ if (buf == NULL)
+ return -1;
+
+ const int read_status =
+ pReader->Read(pos, static_cast<long>(buflen), buf);
+ if (read_status) {
+ delete[] buf;
+ return status;
+ }
+
+ encryption->signature = buf;
+ encryption->signature_len = buflen;
+ } else if (id == libwebm::kMkvContentSigKeyID) {
+ delete[] encryption->sig_key_id;
+ encryption->sig_key_id = NULL;
+ encryption->sig_key_id_len = 0;
+
+ if (size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ const size_t buflen = static_cast<size_t>(size);
+ unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen);
+ if (buf == NULL)
+ return -1;
+
+ const int read_status =
+ pReader->Read(pos, static_cast<long>(buflen), buf);
+ if (read_status) {
+ delete[] buf;
+ return status;
+ }
+
+ encryption->sig_key_id = buf;
+ encryption->sig_key_id_len = buflen;
+ } else if (id == libwebm::kMkvContentSigAlgo) {
+ encryption->sig_algo = UnserializeUInt(pReader, pos, size);
+ } else if (id == libwebm::kMkvContentSigHashAlgo) {
+ encryption->sig_hash_algo = UnserializeUInt(pReader, pos, size);
+ } else if (id == libwebm::kMkvContentEncAESSettings) {
+ const long status = ParseContentEncAESSettingsEntry(
+ pos, size, pReader, &encryption->aes_settings);
+ if (status)
+ return status;
+ }
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ return 0;
+}
+
+Track::Track(Segment* pSegment, long long element_start, long long element_size)
+ : m_pSegment(pSegment),
+ m_element_start(element_start),
+ m_element_size(element_size),
+ content_encoding_entries_(NULL),
+ content_encoding_entries_end_(NULL) {}
+
+Track::~Track() {
+ Info& info = const_cast<Info&>(m_info);
+ info.Clear();
+
+ ContentEncoding** i = content_encoding_entries_;
+ ContentEncoding** const j = content_encoding_entries_end_;
+
+ while (i != j) {
+ ContentEncoding* const encoding = *i++;
+ delete encoding;
+ }
+
+ delete[] content_encoding_entries_;
+}
+
+long Track::Create(Segment* pSegment, const Info& info, long long element_start,
+ long long element_size, Track*& pResult) {
+ if (pResult)
+ return -1;
+
+ Track* const pTrack =
+ new (std::nothrow) Track(pSegment, element_start, element_size);
+
+ if (pTrack == NULL)
+ return -1; // generic error
+
+ const int status = info.Copy(pTrack->m_info);
+
+ if (status) { // error
+ delete pTrack;
+ return status;
+ }
+
+ pResult = pTrack;
+ return 0; // success
+}
+
+Track::Info::Info()
+ : uid(0),
+ defaultDuration(0),
+ codecDelay(0),
+ seekPreRoll(0),
+ nameAsUTF8(NULL),
+ language(NULL),
+ codecId(NULL),
+ codecNameAsUTF8(NULL),
+ codecPrivate(NULL),
+ codecPrivateSize(0),
+ lacing(false) {}
+
+Track::Info::~Info() { Clear(); }
+
+void Track::Info::Clear() {
+ delete[] nameAsUTF8;
+ nameAsUTF8 = NULL;
+
+ delete[] language;
+ language = NULL;
+
+ delete[] codecId;
+ codecId = NULL;
+
+ delete[] codecPrivate;
+ codecPrivate = NULL;
+ codecPrivateSize = 0;
+
+ delete[] codecNameAsUTF8;
+ codecNameAsUTF8 = NULL;
+}
+
+int Track::Info::CopyStr(char* Info::*str, Info& dst_) const {
+ if (str == static_cast<char * Info::*>(NULL))
+ return -1;
+
+ char*& dst = dst_.*str;
+
+ if (dst) // should be NULL already
+ return -1;
+
+ const char* const src = this->*str;
+
+ if (src == NULL)
+ return 0;
+
+ const size_t len = strlen(src);
+
+ dst = SafeArrayAlloc<char>(1, len + 1);
+
+ if (dst == NULL)
+ return -1;
+
+ strcpy(dst, src);
+
+ return 0;
+}
+
+int Track::Info::Copy(Info& dst) const {
+ if (&dst == this)
+ return 0;
+
+ dst.type = type;
+ dst.number = number;
+ dst.defaultDuration = defaultDuration;
+ dst.codecDelay = codecDelay;
+ dst.seekPreRoll = seekPreRoll;
+ dst.uid = uid;
+ dst.lacing = lacing;
+ dst.settings = settings;
+
+ // We now copy the string member variables from src to dst.
+ // This involves memory allocation so in principle the operation
+ // can fail (indeed, that's why we have Info::Copy), so we must
+ // report this to the caller. An error return from this function
+ // therefore implies that the copy was only partially successful.
+
+ if (int status = CopyStr(&Info::nameAsUTF8, dst))
+ return status;
+
+ if (int status = CopyStr(&Info::language, dst))
+ return status;
+
+ if (int status = CopyStr(&Info::codecId, dst))
+ return status;
+
+ if (int status = CopyStr(&Info::codecNameAsUTF8, dst))
+ return status;
+
+ if (codecPrivateSize > 0) {
+ if (codecPrivate == NULL)
+ return -1;
+
+ if (dst.codecPrivate)
+ return -1;
+
+ if (dst.codecPrivateSize != 0)
+ return -1;
+
+ dst.codecPrivate = SafeArrayAlloc<unsigned char>(1, codecPrivateSize);
+
+ if (dst.codecPrivate == NULL)
+ return -1;
+
+ memcpy(dst.codecPrivate, codecPrivate, codecPrivateSize);
+ dst.codecPrivateSize = codecPrivateSize;
+ }
+
+ return 0;
+}
+
+const BlockEntry* Track::GetEOS() const { return &m_eos; }
+
+long Track::GetType() const { return m_info.type; }
+
+long Track::GetNumber() const { return m_info.number; }
+
+unsigned long long Track::GetUid() const { return m_info.uid; }
+
+const char* Track::GetNameAsUTF8() const { return m_info.nameAsUTF8; }
+
+const char* Track::GetLanguage() const { return m_info.language; }
+
+const char* Track::GetCodecNameAsUTF8() const { return m_info.codecNameAsUTF8; }
+
+const char* Track::GetCodecId() const { return m_info.codecId; }
+
+const unsigned char* Track::GetCodecPrivate(size_t& size) const {
+ size = m_info.codecPrivateSize;
+ return m_info.codecPrivate;
+}
+
+bool Track::GetLacing() const { return m_info.lacing; }
+
+unsigned long long Track::GetDefaultDuration() const {
+ return m_info.defaultDuration;
+}
+
+unsigned long long Track::GetCodecDelay() const { return m_info.codecDelay; }
+
+unsigned long long Track::GetSeekPreRoll() const { return m_info.seekPreRoll; }
+
+long Track::GetFirst(const BlockEntry*& pBlockEntry) const {
+ const Cluster* pCluster = m_pSegment->GetFirst();
+
+ for (int i = 0;;) {
+ if (pCluster == NULL) {
+ pBlockEntry = GetEOS();
+ return 1;
+ }
+
+ if (pCluster->EOS()) {
+ if (m_pSegment->DoneParsing()) {
+ pBlockEntry = GetEOS();
+ return 1;
+ }
+
+ pBlockEntry = 0;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long status = pCluster->GetFirst(pBlockEntry);
+
+ if (status < 0) // error
+ return status;
+
+ if (pBlockEntry == 0) { // empty cluster
+ pCluster = m_pSegment->GetNext(pCluster);
+ continue;
+ }
+
+ for (;;) {
+ const Block* const pBlock = pBlockEntry->GetBlock();
+ assert(pBlock);
+
+ const long long tn = pBlock->GetTrackNumber();
+
+ if ((tn == m_info.number) && VetEntry(pBlockEntry))
+ return 0;
+
+ const BlockEntry* pNextEntry;
+
+ status = pCluster->GetNext(pBlockEntry, pNextEntry);
+
+ if (status < 0) // error
+ return status;
+
+ if (pNextEntry == 0)
+ break;
+
+ pBlockEntry = pNextEntry;
+ }
+
+ ++i;
+
+ if (i >= 100)
+ break;
+
+ pCluster = m_pSegment->GetNext(pCluster);
+ }
+
+ // NOTE: if we get here, it means that we didn't find a block with
+ // a matching track number. We interpret that as an error (which
+ // might be too conservative).
+
+ pBlockEntry = GetEOS(); // so we can return a non-NULL value
+ return 1;
+}
+
+long Track::GetNext(const BlockEntry* pCurrEntry,
+ const BlockEntry*& pNextEntry) const {
+ assert(pCurrEntry);
+ assert(!pCurrEntry->EOS()); //?
+
+ const Block* const pCurrBlock = pCurrEntry->GetBlock();
+ assert(pCurrBlock && pCurrBlock->GetTrackNumber() == m_info.number);
+ if (!pCurrBlock || pCurrBlock->GetTrackNumber() != m_info.number)
+ return -1;
+
+ const Cluster* pCluster = pCurrEntry->GetCluster();
+ assert(pCluster);
+ assert(!pCluster->EOS());
+
+ long status = pCluster->GetNext(pCurrEntry, pNextEntry);
+
+ if (status < 0) // error
+ return status;
+
+ for (int i = 0;;) {
+ while (pNextEntry) {
+ const Block* const pNextBlock = pNextEntry->GetBlock();
+ assert(pNextBlock);
+
+ if (pNextBlock->GetTrackNumber() == m_info.number)
+ return 0;
+
+ pCurrEntry = pNextEntry;
+
+ status = pCluster->GetNext(pCurrEntry, pNextEntry);
+
+ if (status < 0) // error
+ return status;
+ }
+
+ pCluster = m_pSegment->GetNext(pCluster);
+
+ if (pCluster == NULL) {
+ pNextEntry = GetEOS();
+ return 1;
+ }
+
+ if (pCluster->EOS()) {
+ if (m_pSegment->DoneParsing()) {
+ pNextEntry = GetEOS();
+ return 1;
+ }
+
+ // TODO: there is a potential O(n^2) problem here: we tell the
+ // caller to (pre)load another cluster, which he does, but then he
+ // calls GetNext again, which repeats the same search. This is
+ // a pathological case, since the only way it can happen is if
+ // there exists a long sequence of clusters none of which contain a
+ // block from this track. One way around this problem is for the
+ // caller to be smarter when he loads another cluster: don't call
+ // us back until you have a cluster that contains a block from this
+ // track. (Of course, that's not cheap either, since our caller
+ // would have to scan the each cluster as it's loaded, so that
+ // would just push back the problem.)
+
+ pNextEntry = NULL;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ status = pCluster->GetFirst(pNextEntry);
+
+ if (status < 0) // error
+ return status;
+
+ if (pNextEntry == NULL) // empty cluster
+ continue;
+
+ ++i;
+
+ if (i >= 100)
+ break;
+ }
+
+ // NOTE: if we get here, it means that we didn't find a block with
+ // a matching track number after lots of searching, so we give
+ // up trying.
+
+ pNextEntry = GetEOS(); // so we can return a non-NULL value
+ return 1;
+}
+
+bool Track::VetEntry(const BlockEntry* pBlockEntry) const {
+ assert(pBlockEntry);
+ const Block* const pBlock = pBlockEntry->GetBlock();
+ assert(pBlock);
+ assert(pBlock->GetTrackNumber() == m_info.number);
+ if (!pBlock || pBlock->GetTrackNumber() != m_info.number)
+ return false;
+
+ // This function is used during a seek to determine whether the
+ // frame is a valid seek target. This default function simply
+ // returns true, which means all frames are valid seek targets.
+ // It gets overridden by the VideoTrack class, because only video
+ // keyframes can be used as seek target.
+
+ return true;
+}
+
+long Track::Seek(long long time_ns, const BlockEntry*& pResult) const {
+ const long status = GetFirst(pResult);
+
+ if (status < 0) // buffer underflow, etc
+ return status;
+
+ assert(pResult);
+
+ if (pResult->EOS())
+ return 0;
+
+ const Cluster* pCluster = pResult->GetCluster();
+ assert(pCluster);
+ assert(pCluster->GetIndex() >= 0);
+
+ if (time_ns <= pResult->GetBlock()->GetTime(pCluster))
+ return 0;
+
+ Cluster** const clusters = m_pSegment->m_clusters;
+ assert(clusters);
+
+ const long count = m_pSegment->GetCount(); // loaded only, not preloaded
+ assert(count > 0);
+
+ Cluster** const i = clusters + pCluster->GetIndex();
+ assert(i);
+ assert(*i == pCluster);
+ assert(pCluster->GetTime() <= time_ns);
+
+ Cluster** const j = clusters + count;
+
+ Cluster** lo = i;
+ Cluster** hi = j;
+
+ while (lo < hi) {
+ // INVARIANT:
+ //[i, lo) <= time_ns
+ //[lo, hi) ?
+ //[hi, j) > time_ns
+
+ Cluster** const mid = lo + (hi - lo) / 2;
+ assert(mid < hi);
+
+ pCluster = *mid;
+ assert(pCluster);
+ assert(pCluster->GetIndex() >= 0);
+ assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters));
+
+ const long long t = pCluster->GetTime();
+
+ if (t <= time_ns)
+ lo = mid + 1;
+ else
+ hi = mid;
+
+ assert(lo <= hi);
+ }
+
+ assert(lo == hi);
+ assert(lo > i);
+ assert(lo <= j);
+
+ while (lo > i) {
+ pCluster = *--lo;
+ assert(pCluster);
+ assert(pCluster->GetTime() <= time_ns);
+
+ pResult = pCluster->GetEntry(this);
+
+ if ((pResult != 0) && !pResult->EOS())
+ return 0;
+
+ // landed on empty cluster (no entries)
+ }
+
+ pResult = GetEOS(); // weird
+ return 0;
+}
+
+const ContentEncoding* Track::GetContentEncodingByIndex(
+ unsigned long idx) const {
+ const ptrdiff_t count =
+ content_encoding_entries_end_ - content_encoding_entries_;
+ assert(count >= 0);
+
+ if (idx >= static_cast<unsigned long>(count))
+ return NULL;
+
+ return content_encoding_entries_[idx];
+}
+
+unsigned long Track::GetContentEncodingCount() const {
+ const ptrdiff_t count =
+ content_encoding_entries_end_ - content_encoding_entries_;
+ assert(count >= 0);
+
+ return static_cast<unsigned long>(count);
+}
+
+long Track::ParseContentEncodingsEntry(long long start, long long size) {
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+ assert(pReader);
+
+ long long pos = start;
+ const long long stop = start + size;
+
+ // Count ContentEncoding elements.
+ int count = 0;
+ while (pos < stop) {
+ long long id, size;
+ const long status = ParseElementHeader(pReader, pos, stop, id, size);
+ if (status < 0) // error
+ return status;
+
+ // pos now designates start of element
+ if (id == libwebm::kMkvContentEncoding)
+ ++count;
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (count <= 0)
+ return -1;
+
+ content_encoding_entries_ = new (std::nothrow) ContentEncoding*[count];
+ if (!content_encoding_entries_)
+ return -1;
+
+ content_encoding_entries_end_ = content_encoding_entries_;
+
+ pos = start;
+ while (pos < stop) {
+ long long id, size;
+ long status = ParseElementHeader(pReader, pos, stop, id, size);
+ if (status < 0) // error
+ return status;
+
+ // pos now designates start of element
+ if (id == libwebm::kMkvContentEncoding) {
+ ContentEncoding* const content_encoding =
+ new (std::nothrow) ContentEncoding();
+ if (!content_encoding)
+ return -1;
+
+ status = content_encoding->ParseContentEncodingEntry(pos, size, pReader);
+ if (status) {
+ delete content_encoding;
+ return status;
+ }
+
+ *content_encoding_entries_end_++ = content_encoding;
+ }
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ return 0;
+}
+
+Track::EOSBlock::EOSBlock() : BlockEntry(NULL, LONG_MIN) {}
+
+BlockEntry::Kind Track::EOSBlock::GetKind() const { return kBlockEOS; }
+
+const Block* Track::EOSBlock::GetBlock() const { return NULL; }
+
+bool PrimaryChromaticity::Parse(IMkvReader* reader, long long read_pos,
+ long long value_size, bool is_x,
+ PrimaryChromaticity** chromaticity) {
+ if (!reader)
+ return false;
+
+ if (!*chromaticity)
+ *chromaticity = new PrimaryChromaticity();
+
+ if (!*chromaticity)
+ return false;
+
+ PrimaryChromaticity* pc = *chromaticity;
+ float* value = is_x ? &pc->x : &pc->y;
+
+ double parser_value = 0;
+ const long long parse_status =
+ UnserializeFloat(reader, read_pos, value_size, parser_value);
+
+ // Valid range is [0, 1]. Make sure the double is representable as a float
+ // before casting.
+ if (parse_status < 0 || parser_value < 0.0 || parser_value > 1.0 ||
+ (parser_value > 0.0 && parser_value < FLT_MIN))
+ return false;
+
+ *value = static_cast<float>(parser_value);
+
+ return true;
+}
+
+bool MasteringMetadata::Parse(IMkvReader* reader, long long mm_start,
+ long long mm_size, MasteringMetadata** mm) {
+ if (!reader || *mm)
+ return false;
+
+ std::unique_ptr<MasteringMetadata> mm_ptr(new MasteringMetadata());
+ if (!mm_ptr.get())
+ return false;
+
+ const long long mm_end = mm_start + mm_size;
+ long long read_pos = mm_start;
+
+ while (read_pos < mm_end) {
+ long long child_id = 0;
+ long long child_size = 0;
+
+ const long long status =
+ ParseElementHeader(reader, read_pos, mm_end, child_id, child_size);
+ if (status < 0)
+ return false;
+
+ if (child_id == libwebm::kMkvLuminanceMax) {
+ double value = 0;
+ const long long value_parse_status =
+ UnserializeFloat(reader, read_pos, child_size, value);
+ if (value < -FLT_MAX || value > FLT_MAX ||
+ (value > 0.0 && value < FLT_MIN)) {
+ return false;
+ }
+ mm_ptr->luminance_max = static_cast<float>(value);
+ if (value_parse_status < 0 || mm_ptr->luminance_max < 0.0 ||
+ mm_ptr->luminance_max > 9999.99) {
+ return false;
+ }
+ } else if (child_id == libwebm::kMkvLuminanceMin) {
+ double value = 0;
+ const long long value_parse_status =
+ UnserializeFloat(reader, read_pos, child_size, value);
+ if (value < -FLT_MAX || value > FLT_MAX ||
+ (value > 0.0 && value < FLT_MIN)) {
+ return false;
+ }
+ mm_ptr->luminance_min = static_cast<float>(value);
+ if (value_parse_status < 0 || mm_ptr->luminance_min < 0.0 ||
+ mm_ptr->luminance_min > 999.9999) {
+ return false;
+ }
+ } else {
+ bool is_x = false;
+ PrimaryChromaticity** chromaticity;
+ switch (child_id) {
+ case libwebm::kMkvPrimaryRChromaticityX:
+ case libwebm::kMkvPrimaryRChromaticityY:
+ is_x = child_id == libwebm::kMkvPrimaryRChromaticityX;
+ chromaticity = &mm_ptr->r;
+ break;
+ case libwebm::kMkvPrimaryGChromaticityX:
+ case libwebm::kMkvPrimaryGChromaticityY:
+ is_x = child_id == libwebm::kMkvPrimaryGChromaticityX;
+ chromaticity = &mm_ptr->g;
+ break;
+ case libwebm::kMkvPrimaryBChromaticityX:
+ case libwebm::kMkvPrimaryBChromaticityY:
+ is_x = child_id == libwebm::kMkvPrimaryBChromaticityX;
+ chromaticity = &mm_ptr->b;
+ break;
+ case libwebm::kMkvWhitePointChromaticityX:
+ case libwebm::kMkvWhitePointChromaticityY:
+ is_x = child_id == libwebm::kMkvWhitePointChromaticityX;
+ chromaticity = &mm_ptr->white_point;
+ break;
+ default:
+ return false;
+ }
+ const bool value_parse_status = PrimaryChromaticity::Parse(
+ reader, read_pos, child_size, is_x, chromaticity);
+ if (!value_parse_status)
+ return false;
+ }
+
+ read_pos += child_size;
+ if (read_pos > mm_end)
+ return false;
+ }
+
+ *mm = mm_ptr.release();
+ return true;
+}
+
+bool Colour::Parse(IMkvReader* reader, long long colour_start,
+ long long colour_size, Colour** colour) {
+ if (!reader || *colour)
+ return false;
+
+ std::unique_ptr<Colour> colour_ptr(new Colour());
+ if (!colour_ptr.get())
+ return false;
+
+ const long long colour_end = colour_start + colour_size;
+ long long read_pos = colour_start;
+
+ while (read_pos < colour_end) {
+ long long child_id = 0;
+ long long child_size = 0;
+
+ const long status =
+ ParseElementHeader(reader, read_pos, colour_end, child_id, child_size);
+ if (status < 0)
+ return false;
+
+ if (child_id == libwebm::kMkvMatrixCoefficients) {
+ colour_ptr->matrix_coefficients =
+ UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->matrix_coefficients < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvBitsPerChannel) {
+ colour_ptr->bits_per_channel =
+ UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->bits_per_channel < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvChromaSubsamplingHorz) {
+ colour_ptr->chroma_subsampling_horz =
+ UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->chroma_subsampling_horz < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvChromaSubsamplingVert) {
+ colour_ptr->chroma_subsampling_vert =
+ UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->chroma_subsampling_vert < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvCbSubsamplingHorz) {
+ colour_ptr->cb_subsampling_horz =
+ UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->cb_subsampling_horz < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvCbSubsamplingVert) {
+ colour_ptr->cb_subsampling_vert =
+ UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->cb_subsampling_vert < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvChromaSitingHorz) {
+ colour_ptr->chroma_siting_horz =
+ UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->chroma_siting_horz < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvChromaSitingVert) {
+ colour_ptr->chroma_siting_vert =
+ UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->chroma_siting_vert < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvRange) {
+ colour_ptr->range = UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->range < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvTransferCharacteristics) {
+ colour_ptr->transfer_characteristics =
+ UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->transfer_characteristics < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvPrimaries) {
+ colour_ptr->primaries = UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->primaries < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvMaxCLL) {
+ colour_ptr->max_cll = UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->max_cll < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvMaxFALL) {
+ colour_ptr->max_fall = UnserializeUInt(reader, read_pos, child_size);
+ if (colour_ptr->max_fall < 0)
+ return false;
+ } else if (child_id == libwebm::kMkvMasteringMetadata) {
+ if (!MasteringMetadata::Parse(reader, read_pos, child_size,
+ &colour_ptr->mastering_metadata))
+ return false;
+ } else {
+ return false;
+ }
+
+ read_pos += child_size;
+ if (read_pos > colour_end)
+ return false;
+ }
+ *colour = colour_ptr.release();
+ return true;
+}
+
+bool Projection::Parse(IMkvReader* reader, long long start, long long size,
+ Projection** projection) {
+ if (!reader || *projection)
+ return false;
+
+ std::unique_ptr<Projection> projection_ptr(new Projection());
+ if (!projection_ptr.get())
+ return false;
+
+ const long long end = start + size;
+ long long read_pos = start;
+
+ while (read_pos < end) {
+ long long child_id = 0;
+ long long child_size = 0;
+
+ const long long status =
+ ParseElementHeader(reader, read_pos, end, child_id, child_size);
+ if (status < 0)
+ return false;
+
+ if (child_id == libwebm::kMkvProjectionType) {
+ long long projection_type = kTypeNotPresent;
+ projection_type = UnserializeUInt(reader, read_pos, child_size);
+ if (projection_type < 0)
+ return false;
+
+ projection_ptr->type = static_cast<ProjectionType>(projection_type);
+ } else if (child_id == libwebm::kMkvProjectionPrivate) {
+ if (projection_ptr->private_data != NULL)
+ return false;
+ unsigned char* data = SafeArrayAlloc<unsigned char>(1, child_size);
+
+ if (data == NULL)
+ return false;
+
+ const int status =
+ reader->Read(read_pos, static_cast<long>(child_size), data);
+
+ if (status) {
+ delete[] data;
+ return false;
+ }
+
+ projection_ptr->private_data = data;
+ projection_ptr->private_data_length = static_cast<size_t>(child_size);
+ } else {
+ double value = 0;
+ const long long value_parse_status =
+ UnserializeFloat(reader, read_pos, child_size, value);
+ // Make sure value is representable as a float before casting.
+ if (value_parse_status < 0 || value < -FLT_MAX || value > FLT_MAX ||
+ (value > 0.0 && value < FLT_MIN)) {
+ return false;
+ }
+
+ switch (child_id) {
+ case libwebm::kMkvProjectionPoseYaw:
+ projection_ptr->pose_yaw = static_cast<float>(value);
+ break;
+ case libwebm::kMkvProjectionPosePitch:
+ projection_ptr->pose_pitch = static_cast<float>(value);
+ break;
+ case libwebm::kMkvProjectionPoseRoll:
+ projection_ptr->pose_roll = static_cast<float>(value);
+ break;
+ default:
+ return false;
+ }
+ }
+
+ read_pos += child_size;
+ if (read_pos > end)
+ return false;
+ }
+
+ *projection = projection_ptr.release();
+ return true;
+}
+
+VideoTrack::VideoTrack(Segment* pSegment, long long element_start,
+ long long element_size)
+ : Track(pSegment, element_start, element_size),
+ m_colour_space(NULL),
+ m_colour(NULL),
+ m_projection(NULL) {}
+
+VideoTrack::~VideoTrack() {
+ delete[] m_colour_space;
+ delete m_colour;
+ delete m_projection;
+}
+
+long VideoTrack::Parse(Segment* pSegment, const Info& info,
+ long long element_start, long long element_size,
+ VideoTrack*& pResult) {
+ if (pResult)
+ return -1;
+
+ if (info.type != Track::kVideo)
+ return -1;
+
+ long long width = 0;
+ long long height = 0;
+ long long display_width = 0;
+ long long display_height = 0;
+ long long display_unit = 0;
+ long long stereo_mode = 0;
+
+ double rate = 0.0;
+ std::unique_ptr<char[]> colour_space_ptr;
+
+ IMkvReader* const pReader = pSegment->m_pReader;
+
+ const Settings& s = info.settings;
+ assert(s.start >= 0);
+ assert(s.size >= 0);
+
+ long long pos = s.start;
+ assert(pos >= 0);
+
+ const long long stop = pos + s.size;
+
+ std::unique_ptr<Colour> colour_ptr;
+ std::unique_ptr<Projection> projection_ptr;
+
+ while (pos < stop) {
+ long long id, size;
+
+ const long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (id == libwebm::kMkvPixelWidth) {
+ width = UnserializeUInt(pReader, pos, size);
+
+ if (width <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvPixelHeight) {
+ height = UnserializeUInt(pReader, pos, size);
+
+ if (height <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvDisplayWidth) {
+ display_width = UnserializeUInt(pReader, pos, size);
+
+ if (display_width <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvDisplayHeight) {
+ display_height = UnserializeUInt(pReader, pos, size);
+
+ if (display_height <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvDisplayUnit) {
+ display_unit = UnserializeUInt(pReader, pos, size);
+
+ if (display_unit < 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvStereoMode) {
+ stereo_mode = UnserializeUInt(pReader, pos, size);
+
+ if (stereo_mode < 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvFrameRate) {
+ const long status = UnserializeFloat(pReader, pos, size, rate);
+
+ if (status < 0)
+ return status;
+
+ if (rate <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvColour) {
+ Colour* colour = NULL;
+ if (!Colour::Parse(pReader, pos, size, &colour)) {
+ return E_FILE_FORMAT_INVALID;
+ } else {
+ colour_ptr.reset(colour);
+ }
+ } else if (id == libwebm::kMkvProjection) {
+ Projection* projection = NULL;
+ if (!Projection::Parse(pReader, pos, size, &projection)) {
+ return E_FILE_FORMAT_INVALID;
+ } else {
+ projection_ptr.reset(projection);
+ }
+ } else if (id == libwebm::kMkvColourSpace) {
+ char* colour_space = NULL;
+ const long status = UnserializeString(pReader, pos, size, colour_space);
+ if (status < 0)
+ return status;
+ colour_space_ptr.reset(colour_space);
+ }
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ VideoTrack* const pTrack =
+ new (std::nothrow) VideoTrack(pSegment, element_start, element_size);
+
+ if (pTrack == NULL)
+ return -1; // generic error
+
+ const int status = info.Copy(pTrack->m_info);
+
+ if (status) { // error
+ delete pTrack;
+ return status;
+ }
+
+ pTrack->m_width = width;
+ pTrack->m_height = height;
+ pTrack->m_display_width = display_width;
+ pTrack->m_display_height = display_height;
+ pTrack->m_display_unit = display_unit;
+ pTrack->m_stereo_mode = stereo_mode;
+ pTrack->m_rate = rate;
+ pTrack->m_colour = colour_ptr.release();
+ pTrack->m_colour_space = colour_space_ptr.release();
+ pTrack->m_projection = projection_ptr.release();
+
+ pResult = pTrack;
+ return 0; // success
+}
+
+bool VideoTrack::VetEntry(const BlockEntry* pBlockEntry) const {
+ return Track::VetEntry(pBlockEntry) && pBlockEntry->GetBlock()->IsKey();
+}
+
+long VideoTrack::Seek(long long time_ns, const BlockEntry*& pResult) const {
+ const long status = GetFirst(pResult);
+
+ if (status < 0) // buffer underflow, etc
+ return status;
+
+ assert(pResult);
+
+ if (pResult->EOS())
+ return 0;
+
+ const Cluster* pCluster = pResult->GetCluster();
+ assert(pCluster);
+ assert(pCluster->GetIndex() >= 0);
+
+ if (time_ns <= pResult->GetBlock()->GetTime(pCluster))
+ return 0;
+
+ Cluster** const clusters = m_pSegment->m_clusters;
+ assert(clusters);
+
+ const long count = m_pSegment->GetCount(); // loaded only, not pre-loaded
+ assert(count > 0);
+
+ Cluster** const i = clusters + pCluster->GetIndex();
+ assert(i);
+ assert(*i == pCluster);
+ assert(pCluster->GetTime() <= time_ns);
+
+ Cluster** const j = clusters + count;
+
+ Cluster** lo = i;
+ Cluster** hi = j;
+
+ while (lo < hi) {
+ // INVARIANT:
+ //[i, lo) <= time_ns
+ //[lo, hi) ?
+ //[hi, j) > time_ns
+
+ Cluster** const mid = lo + (hi - lo) / 2;
+ assert(mid < hi);
+
+ pCluster = *mid;
+ assert(pCluster);
+ assert(pCluster->GetIndex() >= 0);
+ assert(pCluster->GetIndex() == long(mid - m_pSegment->m_clusters));
+
+ const long long t = pCluster->GetTime();
+
+ if (t <= time_ns)
+ lo = mid + 1;
+ else
+ hi = mid;
+
+ assert(lo <= hi);
+ }
+
+ assert(lo == hi);
+ assert(lo > i);
+ assert(lo <= j);
+
+ pCluster = *--lo;
+ assert(pCluster);
+ assert(pCluster->GetTime() <= time_ns);
+
+ pResult = pCluster->GetEntry(this, time_ns);
+
+ if ((pResult != 0) && !pResult->EOS()) // found a keyframe
+ return 0;
+
+ while (lo != i) {
+ pCluster = *--lo;
+ assert(pCluster);
+ assert(pCluster->GetTime() <= time_ns);
+
+ pResult = pCluster->GetEntry(this, time_ns);
+
+ if ((pResult != 0) && !pResult->EOS())
+ return 0;
+ }
+
+ // weird: we're on the first cluster, but no keyframe found
+ // should never happen but we must return something anyway
+
+ pResult = GetEOS();
+ return 0;
+}
+
+Colour* VideoTrack::GetColour() const { return m_colour; }
+
+Projection* VideoTrack::GetProjection() const { return m_projection; }
+
+long long VideoTrack::GetWidth() const { return m_width; }
+
+long long VideoTrack::GetHeight() const { return m_height; }
+
+long long VideoTrack::GetDisplayWidth() const {
+ return m_display_width > 0 ? m_display_width : GetWidth();
+}
+
+long long VideoTrack::GetDisplayHeight() const {
+ return m_display_height > 0 ? m_display_height : GetHeight();
+}
+
+long long VideoTrack::GetDisplayUnit() const { return m_display_unit; }
+
+long long VideoTrack::GetStereoMode() const { return m_stereo_mode; }
+
+double VideoTrack::GetFrameRate() const { return m_rate; }
+
+AudioTrack::AudioTrack(Segment* pSegment, long long element_start,
+ long long element_size)
+ : Track(pSegment, element_start, element_size) {}
+
+long AudioTrack::Parse(Segment* pSegment, const Info& info,
+ long long element_start, long long element_size,
+ AudioTrack*& pResult) {
+ if (pResult)
+ return -1;
+
+ if (info.type != Track::kAudio)
+ return -1;
+
+ IMkvReader* const pReader = pSegment->m_pReader;
+
+ const Settings& s = info.settings;
+ assert(s.start >= 0);
+ assert(s.size >= 0);
+
+ long long pos = s.start;
+ assert(pos >= 0);
+
+ const long long stop = pos + s.size;
+
+ double rate = 8000.0; // MKV default
+ long long channels = 1;
+ long long bit_depth = 0;
+
+ while (pos < stop) {
+ long long id, size;
+
+ long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (id == libwebm::kMkvSamplingFrequency) {
+ status = UnserializeFloat(pReader, pos, size, rate);
+
+ if (status < 0)
+ return status;
+
+ if (rate <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvChannels) {
+ channels = UnserializeUInt(pReader, pos, size);
+
+ if (channels <= 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvBitDepth) {
+ bit_depth = UnserializeUInt(pReader, pos, size);
+
+ if (bit_depth <= 0)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ AudioTrack* const pTrack =
+ new (std::nothrow) AudioTrack(pSegment, element_start, element_size);
+
+ if (pTrack == NULL)
+ return -1; // generic error
+
+ const int status = info.Copy(pTrack->m_info);
+
+ if (status) {
+ delete pTrack;
+ return status;
+ }
+
+ pTrack->m_rate = rate;
+ pTrack->m_channels = channels;
+ pTrack->m_bitDepth = bit_depth;
+
+ pResult = pTrack;
+ return 0; // success
+}
+
+double AudioTrack::GetSamplingRate() const { return m_rate; }
+
+long long AudioTrack::GetChannels() const { return m_channels; }
+
+long long AudioTrack::GetBitDepth() const { return m_bitDepth; }
+
+Tracks::Tracks(Segment* pSegment, long long start, long long size_,
+ long long element_start, long long element_size)
+ : m_pSegment(pSegment),
+ m_start(start),
+ m_size(size_),
+ m_element_start(element_start),
+ m_element_size(element_size),
+ m_trackEntries(NULL),
+ m_trackEntriesEnd(NULL) {}
+
+long Tracks::Parse() {
+ assert(m_trackEntries == NULL);
+ assert(m_trackEntriesEnd == NULL);
+
+ const long long stop = m_start + m_size;
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ int count = 0;
+ long long pos = m_start;
+
+ while (pos < stop) {
+ long long id, size;
+
+ const long status = ParseElementHeader(pReader, pos, stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (size == 0) // weird
+ continue;
+
+ if (id == libwebm::kMkvTrackEntry)
+ ++count;
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if (count <= 0)
+ return 0; // success
+
+ m_trackEntries = new (std::nothrow) Track*[count];
+
+ if (m_trackEntries == NULL)
+ return -1;
+
+ m_trackEntriesEnd = m_trackEntries;
+
+ pos = m_start;
+
+ while (pos < stop) {
+ const long long element_start = pos;
+
+ long long id, payload_size;
+
+ const long status =
+ ParseElementHeader(pReader, pos, stop, id, payload_size);
+
+ if (status < 0) // error
+ return status;
+
+ if (payload_size == 0) // weird
+ continue;
+
+ const long long payload_stop = pos + payload_size;
+ assert(payload_stop <= stop); // checked in ParseElement
+
+ const long long element_size = payload_stop - element_start;
+
+ if (id == libwebm::kMkvTrackEntry) {
+ Track*& pTrack = *m_trackEntriesEnd;
+ pTrack = NULL;
+
+ const long status = ParseTrackEntry(pos, payload_size, element_start,
+ element_size, pTrack);
+ if (status)
+ return status;
+
+ if (pTrack)
+ ++m_trackEntriesEnd;
+ }
+
+ pos = payload_stop;
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ return 0; // success
+}
+
+unsigned long Tracks::GetTracksCount() const {
+ const ptrdiff_t result = m_trackEntriesEnd - m_trackEntries;
+ assert(result >= 0);
+
+ return static_cast<unsigned long>(result);
+}
+
+long Tracks::ParseTrackEntry(long long track_start, long long track_size,
+ long long element_start, long long element_size,
+ Track*& pResult) const {
+ if (pResult)
+ return -1;
+
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ long long pos = track_start;
+ const long long track_stop = track_start + track_size;
+
+ Track::Info info;
+
+ info.type = 0;
+ info.number = 0;
+ info.uid = 0;
+ info.defaultDuration = 0;
+
+ Track::Settings v;
+ v.start = -1;
+ v.size = -1;
+
+ Track::Settings a;
+ a.start = -1;
+ a.size = -1;
+
+ Track::Settings e; // content_encodings_settings;
+ e.start = -1;
+ e.size = -1;
+
+ long long lacing = 1; // default is true
+
+ while (pos < track_stop) {
+ long long id, size;
+
+ const long status = ParseElementHeader(pReader, pos, track_stop, id, size);
+
+ if (status < 0) // error
+ return status;
+
+ if (size < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ const long long start = pos;
+
+ if (id == libwebm::kMkvVideo) {
+ v.start = start;
+ v.size = size;
+ } else if (id == libwebm::kMkvAudio) {
+ a.start = start;
+ a.size = size;
+ } else if (id == libwebm::kMkvContentEncodings) {
+ e.start = start;
+ e.size = size;
+ } else if (id == libwebm::kMkvTrackUID) {
+ if (size > 8)
+ return E_FILE_FORMAT_INVALID;
+
+ info.uid = 0;
+
+ long long pos_ = start;
+ const long long pos_end = start + size;
+
+ while (pos_ != pos_end) {
+ unsigned char b;
+
+ const int status = pReader->Read(pos_, 1, &b);
+
+ if (status)
+ return status;
+
+ info.uid <<= 8;
+ info.uid |= b;
+
+ ++pos_;
+ }
+ } else if (id == libwebm::kMkvTrackNumber) {
+ const long long num = UnserializeUInt(pReader, pos, size);
+
+ if ((num <= 0) || (num > 127))
+ return E_FILE_FORMAT_INVALID;
+
+ info.number = static_cast<long>(num);
+ } else if (id == libwebm::kMkvTrackType) {
+ const long long type = UnserializeUInt(pReader, pos, size);
+
+ if ((type <= 0) || (type > 254))
+ return E_FILE_FORMAT_INVALID;
+
+ info.type = static_cast<long>(type);
+ } else if (id == libwebm::kMkvName) {
+ const long status =
+ UnserializeString(pReader, pos, size, info.nameAsUTF8);
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvLanguage) {
+ const long status = UnserializeString(pReader, pos, size, info.language);
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvDefaultDuration) {
+ const long long duration = UnserializeUInt(pReader, pos, size);
+
+ if (duration < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ info.defaultDuration = static_cast<unsigned long long>(duration);
+ } else if (id == libwebm::kMkvCodecID) {
+ const long status = UnserializeString(pReader, pos, size, info.codecId);
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvFlagLacing) {
+ lacing = UnserializeUInt(pReader, pos, size);
+
+ if ((lacing < 0) || (lacing > 1))
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvCodecPrivate) {
+ delete[] info.codecPrivate;
+ info.codecPrivate = NULL;
+ info.codecPrivateSize = 0;
+
+ const size_t buflen = static_cast<size_t>(size);
+
+ if (buflen) {
+ unsigned char* buf = SafeArrayAlloc<unsigned char>(1, buflen);
+
+ if (buf == NULL)
+ return -1;
+
+ const int status = pReader->Read(pos, static_cast<long>(buflen), buf);
+
+ if (status) {
+ delete[] buf;
+ return status;
+ }
+
+ info.codecPrivate = buf;
+ info.codecPrivateSize = buflen;
+ }
+ } else if (id == libwebm::kMkvCodecName) {
+ const long status =
+ UnserializeString(pReader, pos, size, info.codecNameAsUTF8);
+
+ if (status)
+ return status;
+ } else if (id == libwebm::kMkvCodecDelay) {
+ info.codecDelay = UnserializeUInt(pReader, pos, size);
+ } else if (id == libwebm::kMkvSeekPreRoll) {
+ info.seekPreRoll = UnserializeUInt(pReader, pos, size);
+ }
+
+ pos += size; // consume payload
+ if (pos > track_stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != track_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if (info.number <= 0) // not specified
+ return E_FILE_FORMAT_INVALID;
+
+ if (GetTrackByNumber(info.number))
+ return E_FILE_FORMAT_INVALID;
+
+ if (info.type <= 0) // not specified
+ return E_FILE_FORMAT_INVALID;
+
+ info.lacing = (lacing > 0) ? true : false;
+
+ if (info.type == Track::kVideo) {
+ if (v.start < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if (a.start >= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ info.settings = v;
+
+ VideoTrack* pTrack = NULL;
+
+ const long status = VideoTrack::Parse(m_pSegment, info, element_start,
+ element_size, pTrack);
+
+ if (status)
+ return status;
+
+ pResult = pTrack;
+ assert(pResult);
+
+ if (e.start >= 0)
+ pResult->ParseContentEncodingsEntry(e.start, e.size);
+ } else if (info.type == Track::kAudio) {
+ if (a.start < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if (v.start >= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ info.settings = a;
+
+ AudioTrack* pTrack = NULL;
+
+ const long status = AudioTrack::Parse(m_pSegment, info, element_start,
+ element_size, pTrack);
+
+ if (status)
+ return status;
+
+ pResult = pTrack;
+ assert(pResult);
+
+ if (e.start >= 0)
+ pResult->ParseContentEncodingsEntry(e.start, e.size);
+ } else {
+ // neither video nor audio - probably metadata or subtitles
+
+ if (a.start >= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if (v.start >= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if (info.type == Track::kMetadata && e.start >= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ info.settings.start = -1;
+ info.settings.size = 0;
+
+ Track* pTrack = NULL;
+
+ const long status =
+ Track::Create(m_pSegment, info, element_start, element_size, pTrack);
+
+ if (status)
+ return status;
+
+ pResult = pTrack;
+ assert(pResult);
+ }
+
+ return 0; // success
+}
+
+Tracks::~Tracks() {
+ Track** i = m_trackEntries;
+ Track** const j = m_trackEntriesEnd;
+
+ while (i != j) {
+ Track* const pTrack = *i++;
+ delete pTrack;
+ }
+
+ delete[] m_trackEntries;
+}
+
+const Track* Tracks::GetTrackByNumber(long tn) const {
+ if (tn < 0)
+ return NULL;
+
+ Track** i = m_trackEntries;
+ Track** const j = m_trackEntriesEnd;
+
+ while (i != j) {
+ Track* const pTrack = *i++;
+
+ if (pTrack == NULL)
+ continue;
+
+ if (tn == pTrack->GetNumber())
+ return pTrack;
+ }
+
+ return NULL; // not found
+}
+
+const Track* Tracks::GetTrackByIndex(unsigned long idx) const {
+ const ptrdiff_t count = m_trackEntriesEnd - m_trackEntries;
+
+ if (idx >= static_cast<unsigned long>(count))
+ return NULL;
+
+ return m_trackEntries[idx];
+}
+
+long Cluster::Load(long long& pos, long& len) const {
+ if (m_pSegment == NULL)
+ return E_PARSE_FAILED;
+
+ if (m_timecode >= 0) // at least partially loaded
+ return 0;
+
+ if (m_pos != m_element_start || m_element_size >= 0)
+ return E_PARSE_FAILED;
+
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+ long long total, avail;
+ const int status = pReader->Length(&total, &avail);
+
+ if (status < 0) // error
+ return status;
+
+ if (total >= 0 && (avail > total || m_pos > total))
+ return E_FILE_FORMAT_INVALID;
+
+ pos = m_pos;
+
+ long long cluster_size = -1;
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error or underflow
+ return static_cast<long>(result);
+
+ if (result > 0)
+ return E_BUFFER_NOT_FULL;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long id_ = ReadID(pReader, pos, len);
+
+ if (id_ < 0) // error
+ return static_cast<long>(id_);
+
+ if (id_ != libwebm::kMkvCluster)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume id
+
+ // read cluster size
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0)
+ return E_BUFFER_NOT_FULL;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(cluster_size);
+
+ if (size == 0)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume length of size of element
+
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if (size != unknown_size)
+ cluster_size = size;
+
+ // pos points to start of payload
+ long long timecode = -1;
+ long long new_pos = -1;
+ bool bBlock = false;
+
+ long long cluster_stop = (cluster_size < 0) ? -1 : pos + cluster_size;
+
+ for (;;) {
+ if ((cluster_stop >= 0) && (pos >= cluster_stop))
+ break;
+
+ // Parse ID
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0)
+ return E_BUFFER_NOT_FULL;
+
+ if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long id = ReadID(pReader, pos, len);
+
+ if (id < 0) // error
+ return static_cast<long>(id);
+
+ if (id == 0)
+ return E_FILE_FORMAT_INVALID;
+
+ // This is the distinguished set of ID's we use to determine
+ // that we have exhausted the sub-element's inside the cluster
+ // whose ID we parsed earlier.
+
+ if (id == libwebm::kMkvCluster)
+ break;
+
+ if (id == libwebm::kMkvCues)
+ break;
+
+ pos += len; // consume ID field
+
+ // Parse Size
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0)
+ return E_BUFFER_NOT_FULL;
+
+ if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(size);
+
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if (size == unknown_size)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume size field
+
+ if ((cluster_stop >= 0) && (pos > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ // pos now points to start of payload
+
+ if (size == 0)
+ continue;
+
+ if ((cluster_stop >= 0) && ((pos + size) > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if (id == libwebm::kMkvTimecode) {
+ len = static_cast<long>(size);
+
+ if ((pos + size) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ timecode = UnserializeUInt(pReader, pos, size);
+
+ if (timecode < 0) // error (or underflow)
+ return static_cast<long>(timecode);
+
+ new_pos = pos + size;
+
+ if (bBlock)
+ break;
+ } else if (id == libwebm::kMkvBlockGroup) {
+ bBlock = true;
+ break;
+ } else if (id == libwebm::kMkvSimpleBlock) {
+ bBlock = true;
+ break;
+ }
+
+ pos += size; // consume payload
+ if (cluster_stop >= 0 && pos > cluster_stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (cluster_stop >= 0 && pos > cluster_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if (timecode < 0) // no timecode found
+ return E_FILE_FORMAT_INVALID;
+
+ if (!bBlock)
+ return E_FILE_FORMAT_INVALID;
+
+ m_pos = new_pos; // designates position just beyond timecode payload
+ m_timecode = timecode; // m_timecode >= 0 means we're partially loaded
+
+ if (cluster_size >= 0)
+ m_element_size = cluster_stop - m_element_start;
+
+ return 0;
+}
+
+long Cluster::Parse(long long& pos, long& len) const {
+ long status = Load(pos, len);
+
+ if (status < 0)
+ return status;
+
+ if (m_pos < m_element_start || m_timecode < 0)
+ return E_PARSE_FAILED;
+
+ const long long cluster_stop =
+ (m_element_size < 0) ? -1 : m_element_start + m_element_size;
+
+ if ((cluster_stop >= 0) && (m_pos >= cluster_stop))
+ return 1; // nothing else to do
+
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ long long total, avail;
+
+ status = pReader->Length(&total, &avail);
+
+ if (status < 0) // error
+ return status;
+
+ if (total >= 0 && avail > total)
+ return E_FILE_FORMAT_INVALID;
+
+ pos = m_pos;
+
+ for (;;) {
+ if ((cluster_stop >= 0) && (pos >= cluster_stop))
+ break;
+
+ if ((total >= 0) && (pos >= total)) {
+ if (m_element_size < 0)
+ m_element_size = pos - m_element_start;
+
+ break;
+ }
+
+ // Parse ID
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0)
+ return E_BUFFER_NOT_FULL;
+
+ if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long id = ReadID(pReader, pos, len);
+
+ if (id < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ // This is the distinguished set of ID's we use to determine
+ // that we have exhausted the sub-element's inside the cluster
+ // whose ID we parsed earlier.
+
+ if ((id == libwebm::kMkvCluster) || (id == libwebm::kMkvCues)) {
+ if (m_element_size < 0)
+ m_element_size = pos - m_element_start;
+
+ break;
+ }
+
+ pos += len; // consume ID field
+
+ // Parse Size
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0)
+ return E_BUFFER_NOT_FULL;
+
+ if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(size);
+
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if (size == unknown_size)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume size field
+
+ if ((cluster_stop >= 0) && (pos > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ // pos now points to start of payload
+
+ if (size == 0)
+ continue;
+
+ // const long long block_start = pos;
+ const long long block_stop = pos + size;
+
+ if (cluster_stop >= 0) {
+ if (block_stop > cluster_stop) {
+ if (id == libwebm::kMkvBlockGroup || id == libwebm::kMkvSimpleBlock) {
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ pos = cluster_stop;
+ break;
+ }
+ } else if ((total >= 0) && (block_stop > total)) {
+ m_element_size = total - m_element_start;
+ pos = total;
+ break;
+ } else if (block_stop > avail) {
+ len = static_cast<long>(size);
+ return E_BUFFER_NOT_FULL;
+ }
+
+ Cluster* const this_ = const_cast<Cluster*>(this);
+
+ if (id == libwebm::kMkvBlockGroup)
+ return this_->ParseBlockGroup(size, pos, len);
+
+ if (id == libwebm::kMkvSimpleBlock)
+ return this_->ParseSimpleBlock(size, pos, len);
+
+ pos += size; // consume payload
+ if (cluster_stop >= 0 && pos > cluster_stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (m_element_size < 1)
+ return E_FILE_FORMAT_INVALID;
+
+ m_pos = pos;
+ if (cluster_stop >= 0 && m_pos > cluster_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if (m_entries_count > 0) {
+ const long idx = m_entries_count - 1;
+
+ const BlockEntry* const pLast = m_entries[idx];
+ if (pLast == NULL)
+ return E_PARSE_FAILED;
+
+ const Block* const pBlock = pLast->GetBlock();
+ if (pBlock == NULL)
+ return E_PARSE_FAILED;
+
+ const long long start = pBlock->m_start;
+
+ if ((total >= 0) && (start > total))
+ return E_PARSE_FAILED; // defend against trucated stream
+
+ const long long size = pBlock->m_size;
+
+ const long long stop = start + size;
+ if (cluster_stop >= 0 && stop > cluster_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((total >= 0) && (stop > total))
+ return E_PARSE_FAILED; // defend against trucated stream
+ }
+
+ return 1; // no more entries
+}
+
+long Cluster::ParseSimpleBlock(long long block_size, long long& pos,
+ long& len) {
+ const long long block_start = pos;
+ const long long block_stop = pos + block_size;
+
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ long long total, avail;
+
+ long status = pReader->Length(&total, &avail);
+
+ if (status < 0) // error
+ return status;
+
+ assert((total < 0) || (avail <= total));
+
+ // parse track number
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((pos + len) > block_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long track = ReadUInt(pReader, pos, len);
+
+ if (track < 0) // error
+ return static_cast<long>(track);
+
+ if (track == 0)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume track number
+
+ if ((pos + 2) > block_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + 2) > avail) {
+ len = 2;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ pos += 2; // consume timecode
+
+ if ((pos + 1) > block_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ unsigned char flags;
+
+ status = pReader->Read(pos, 1, &flags);
+
+ if (status < 0) { // error or underflow
+ len = 1;
+ return status;
+ }
+
+ ++pos; // consume flags byte
+ assert(pos <= avail);
+
+ if (pos >= block_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ const int lacing = int(flags & 0x06) >> 1;
+
+ if ((lacing != 0) && (block_stop > avail)) {
+ len = static_cast<long>(block_stop - pos);
+ return E_BUFFER_NOT_FULL;
+ }
+
+ status = CreateBlock(libwebm::kMkvSimpleBlock, block_start, block_size,
+ 0); // DiscardPadding
+
+ if (status != 0)
+ return status;
+
+ m_pos = block_stop;
+
+ return 0; // success
+}
+
+long Cluster::ParseBlockGroup(long long payload_size, long long& pos,
+ long& len) {
+ const long long payload_start = pos;
+ const long long payload_stop = pos + payload_size;
+
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ long long total, avail;
+
+ long status = pReader->Length(&total, &avail);
+
+ if (status < 0) // error
+ return status;
+
+ assert((total < 0) || (avail <= total));
+
+ if ((total >= 0) && (payload_stop > total))
+ return E_FILE_FORMAT_INVALID;
+
+ if (payload_stop > avail) {
+ len = static_cast<long>(payload_size);
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long discard_padding = 0;
+
+ while (pos < payload_stop) {
+ // parse sub-block element ID
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((pos + len) > payload_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long id = ReadID(pReader, pos, len);
+
+ if (id < 0) // error
+ return static_cast<long>(id);
+
+ if (id == 0) // not a valid ID
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume ID field
+
+ // Parse Size
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((pos + len) > payload_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(size);
+
+ pos += len; // consume size field
+
+ // pos now points to start of sub-block group payload
+
+ if (pos > payload_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if (size == 0) // weird
+ continue;
+
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if (size == unknown_size)
+ return E_FILE_FORMAT_INVALID;
+
+ if (id == libwebm::kMkvDiscardPadding) {
+ status = UnserializeInt(pReader, pos, size, discard_padding);
+
+ if (status < 0) // error
+ return status;
+ }
+
+ if (id != libwebm::kMkvBlock) {
+ pos += size; // consume sub-part of block group
+
+ if (pos > payload_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ continue;
+ }
+
+ const long long block_stop = pos + size;
+
+ if (block_stop > payload_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ // parse track number
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((pos + len) > block_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long track = ReadUInt(pReader, pos, len);
+
+ if (track < 0) // error
+ return static_cast<long>(track);
+
+ if (track == 0)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume track number
+
+ if ((pos + 2) > block_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + 2) > avail) {
+ len = 2;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ pos += 2; // consume timecode
+
+ if ((pos + 1) > block_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ unsigned char flags;
+
+ status = pReader->Read(pos, 1, &flags);
+
+ if (status < 0) { // error or underflow
+ len = 1;
+ return status;
+ }
+
+ ++pos; // consume flags byte
+ assert(pos <= avail);
+
+ if (pos >= block_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ const int lacing = int(flags & 0x06) >> 1;
+
+ if ((lacing != 0) && (block_stop > avail)) {
+ len = static_cast<long>(block_stop - pos);
+ return E_BUFFER_NOT_FULL;
+ }
+
+ pos = block_stop; // consume block-part of block group
+ if (pos > payload_stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ if (pos != payload_stop)
+ return E_FILE_FORMAT_INVALID;
+
+ status = CreateBlock(libwebm::kMkvBlockGroup, payload_start, payload_size,
+ discard_padding);
+ if (status != 0)
+ return status;
+
+ m_pos = payload_stop;
+
+ return 0; // success
+}
+
+long Cluster::GetEntry(long index, const mkvparser::BlockEntry*& pEntry) const {
+ assert(m_pos >= m_element_start);
+
+ pEntry = NULL;
+
+ if (index < 0)
+ return -1; // generic error
+
+ if (m_entries_count < 0)
+ return E_BUFFER_NOT_FULL;
+
+ assert(m_entries);
+ assert(m_entries_size > 0);
+ assert(m_entries_count <= m_entries_size);
+
+ if (index < m_entries_count) {
+ pEntry = m_entries[index];
+ assert(pEntry);
+
+ return 1; // found entry
+ }
+
+ if (m_element_size < 0) // we don't know cluster end yet
+ return E_BUFFER_NOT_FULL; // underflow
+
+ const long long element_stop = m_element_start + m_element_size;
+
+ if (m_pos >= element_stop)
+ return 0; // nothing left to parse
+
+ return E_BUFFER_NOT_FULL; // underflow, since more remains to be parsed
+}
+
+Cluster* Cluster::Create(Segment* pSegment, long idx, long long off) {
+ if (!pSegment || off < 0)
+ return NULL;
+
+ const long long element_start = pSegment->m_start + off;
+
+ Cluster* const pCluster =
+ new (std::nothrow) Cluster(pSegment, idx, element_start);
+
+ return pCluster;
+}
+
+Cluster::Cluster()
+ : m_pSegment(NULL),
+ m_element_start(0),
+ m_index(0),
+ m_pos(0),
+ m_element_size(0),
+ m_timecode(0),
+ m_entries(NULL),
+ m_entries_size(0),
+ m_entries_count(0) // means "no entries"
+{}
+
+Cluster::Cluster(Segment* pSegment, long idx, long long element_start
+ /* long long element_size */)
+ : m_pSegment(pSegment),
+ m_element_start(element_start),
+ m_index(idx),
+ m_pos(element_start),
+ m_element_size(-1 /* element_size */),
+ m_timecode(-1),
+ m_entries(NULL),
+ m_entries_size(0),
+ m_entries_count(-1) // means "has not been parsed yet"
+{}
+
+Cluster::~Cluster() {
+ if (m_entries_count <= 0) {
+ delete[] m_entries;
+ return;
+ }
+
+ BlockEntry** i = m_entries;
+ BlockEntry** const j = m_entries + m_entries_count;
+
+ while (i != j) {
+ BlockEntry* p = *i++;
+ assert(p);
+
+ delete p;
+ }
+
+ delete[] m_entries;
+}
+
+bool Cluster::EOS() const { return (m_pSegment == NULL); }
+
+long Cluster::GetIndex() const { return m_index; }
+
+long long Cluster::GetPosition() const {
+ const long long pos = m_element_start - m_pSegment->m_start;
+ assert(pos >= 0);
+
+ return pos;
+}
+
+long long Cluster::GetElementSize() const { return m_element_size; }
+
+long Cluster::HasBlockEntries(
+ const Segment* pSegment,
+ long long off, // relative to start of segment payload
+ long long& pos, long& len) {
+ assert(pSegment);
+ assert(off >= 0); // relative to segment
+
+ IMkvReader* const pReader = pSegment->m_pReader;
+
+ long long total, avail;
+
+ long status = pReader->Length(&total, &avail);
+
+ if (status < 0) // error
+ return status;
+
+ assert((total < 0) || (avail <= total));
+
+ pos = pSegment->m_start + off; // absolute
+
+ if ((total >= 0) && (pos >= total))
+ return 0; // we don't even have a complete cluster
+
+ const long long segment_stop =
+ (pSegment->m_size < 0) ? -1 : pSegment->m_start + pSegment->m_size;
+
+ long long cluster_stop = -1; // interpreted later to mean "unknown size"
+
+ {
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // need more data
+ return E_BUFFER_NOT_FULL;
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((total >= 0) && ((pos + len) > total))
+ return 0;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long id = ReadID(pReader, pos, len);
+
+ if (id < 0) // error
+ return static_cast<long>(id);
+
+ if (id != libwebm::kMkvCluster)
+ return E_PARSE_FAILED;
+
+ pos += len; // consume Cluster ID field
+
+ // read size field
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // weird
+ return E_BUFFER_NOT_FULL;
+
+ if ((segment_stop >= 0) && ((pos + len) > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((total >= 0) && ((pos + len) > total))
+ return 0;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(size);
+
+ if (size == 0)
+ return 0; // cluster does not have entries
+
+ pos += len; // consume size field
+
+ // pos now points to start of payload
+
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if (size != unknown_size) {
+ cluster_stop = pos + size;
+ assert(cluster_stop >= 0);
+
+ if ((segment_stop >= 0) && (cluster_stop > segment_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((total >= 0) && (cluster_stop > total))
+ // return E_FILE_FORMAT_INVALID; //too conservative
+ return 0; // cluster does not have any entries
+ }
+ }
+
+ for (;;) {
+ if ((cluster_stop >= 0) && (pos >= cluster_stop))
+ return 0; // no entries detected
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ long long result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // need more data
+ return E_BUFFER_NOT_FULL;
+
+ if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long id = ReadID(pReader, pos, len);
+
+ if (id < 0) // error
+ return static_cast<long>(id);
+
+ // This is the distinguished set of ID's we use to determine
+ // that we have exhausted the sub-element's inside the cluster
+ // whose ID we parsed earlier.
+
+ if (id == libwebm::kMkvCluster)
+ return 0; // no entries found
+
+ if (id == libwebm::kMkvCues)
+ return 0; // no entries found
+
+ pos += len; // consume id field
+
+ if ((cluster_stop >= 0) && (pos >= cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ // read size field
+
+ if ((pos + 1) > avail) {
+ len = 1;
+ return E_BUFFER_NOT_FULL;
+ }
+
+ result = GetUIntLength(pReader, pos, len);
+
+ if (result < 0) // error
+ return static_cast<long>(result);
+
+ if (result > 0) // underflow
+ return E_BUFFER_NOT_FULL;
+
+ if ((cluster_stop >= 0) && ((pos + len) > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > avail)
+ return E_BUFFER_NOT_FULL;
+
+ const long long size = ReadUInt(pReader, pos, len);
+
+ if (size < 0) // error
+ return static_cast<long>(size);
+
+ pos += len; // consume size field
+
+ // pos now points to start of payload
+
+ if ((cluster_stop >= 0) && (pos > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if (size == 0) // weird
+ continue;
+
+ const long long unknown_size = (1LL << (7 * len)) - 1;
+
+ if (size == unknown_size)
+ return E_FILE_FORMAT_INVALID; // not supported inside cluster
+
+ if ((cluster_stop >= 0) && ((pos + size) > cluster_stop))
+ return E_FILE_FORMAT_INVALID;
+
+ if (id == libwebm::kMkvBlockGroup)
+ return 1; // have at least one entry
+
+ if (id == libwebm::kMkvSimpleBlock)
+ return 1; // have at least one entry
+
+ pos += size; // consume payload
+ if (cluster_stop >= 0 && pos > cluster_stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+}
+
+long long Cluster::GetTimeCode() const {
+ long long pos;
+ long len;
+
+ const long status = Load(pos, len);
+
+ if (status < 0) // error
+ return status;
+
+ return m_timecode;
+}
+
+long long Cluster::GetTime() const {
+ const long long tc = GetTimeCode();
+
+ if (tc < 0)
+ return tc;
+
+ const SegmentInfo* const pInfo = m_pSegment->GetInfo();
+ assert(pInfo);
+
+ const long long scale = pInfo->GetTimeCodeScale();
+ assert(scale >= 1);
+
+ const long long t = m_timecode * scale;
+
+ return t;
+}
+
+long long Cluster::GetFirstTime() const {
+ const BlockEntry* pEntry;
+
+ const long status = GetFirst(pEntry);
+
+ if (status < 0) // error
+ return status;
+
+ if (pEntry == NULL) // empty cluster
+ return GetTime();
+
+ const Block* const pBlock = pEntry->GetBlock();
+ assert(pBlock);
+
+ return pBlock->GetTime(this);
+}
+
+long long Cluster::GetLastTime() const {
+ const BlockEntry* pEntry;
+
+ const long status = GetLast(pEntry);
+
+ if (status < 0) // error
+ return status;
+
+ if (pEntry == NULL) // empty cluster
+ return GetTime();
+
+ const Block* const pBlock = pEntry->GetBlock();
+ assert(pBlock);
+
+ return pBlock->GetTime(this);
+}
+
+long Cluster::CreateBlock(long long id,
+ long long pos, // absolute pos of payload
+ long long size, long long discard_padding) {
+ if (id != libwebm::kMkvBlockGroup && id != libwebm::kMkvSimpleBlock)
+ return E_PARSE_FAILED;
+
+ if (m_entries_count < 0) { // haven't parsed anything yet
+ assert(m_entries == NULL);
+ assert(m_entries_size == 0);
+
+ m_entries_size = 1024;
+ m_entries = new (std::nothrow) BlockEntry*[m_entries_size];
+ if (m_entries == NULL)
+ return -1;
+
+ m_entries_count = 0;
+ } else {
+ assert(m_entries);
+ assert(m_entries_size > 0);
+ assert(m_entries_count <= m_entries_size);
+
+ if (m_entries_count >= m_entries_size) {
+ const long entries_size = 2 * m_entries_size;
+
+ BlockEntry** const entries = new (std::nothrow) BlockEntry*[entries_size];
+ if (entries == NULL)
+ return -1;
+
+ BlockEntry** src = m_entries;
+ BlockEntry** const src_end = src + m_entries_count;
+
+ BlockEntry** dst = entries;
+
+ while (src != src_end)
+ *dst++ = *src++;
+
+ delete[] m_entries;
+
+ m_entries = entries;
+ m_entries_size = entries_size;
+ }
+ }
+
+ if (id == libwebm::kMkvBlockGroup)
+ return CreateBlockGroup(pos, size, discard_padding);
+ else
+ return CreateSimpleBlock(pos, size);
+}
+
+long Cluster::CreateBlockGroup(long long start_offset, long long size,
+ long long discard_padding) {
+ assert(m_entries);
+ assert(m_entries_size > 0);
+ assert(m_entries_count >= 0);
+ assert(m_entries_count < m_entries_size);
+
+ IMkvReader* const pReader = m_pSegment->m_pReader;
+
+ long long pos = start_offset;
+ const long long stop = start_offset + size;
+
+ // For WebM files, there is a bias towards previous reference times
+ //(in order to support alt-ref frames, which refer back to the previous
+ // keyframe). Normally a 0 value is not possible, but here we tenatively
+ // allow 0 as the value of a reference frame, with the interpretation
+ // that this is a "previous" reference time.
+
+ long long prev = 1; // nonce
+ long long next = 0; // nonce
+ long long duration = -1; // really, this is unsigned
+
+ long long bpos = -1;
+ long long bsize = -1;
+
+ while (pos < stop) {
+ long len;
+ const long long id = ReadID(pReader, pos, len);
+ if (id < 0 || (pos + len) > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume ID
+
+ const long long size = ReadUInt(pReader, pos, len);
+ assert(size >= 0); // TODO
+ assert((pos + len) <= stop);
+
+ pos += len; // consume size
+
+ if (id == libwebm::kMkvBlock) {
+ if (bpos < 0) { // Block ID
+ bpos = pos;
+ bsize = size;
+ }
+ } else if (id == libwebm::kMkvBlockDuration) {
+ if (size > 8)
+ return E_FILE_FORMAT_INVALID;
+
+ duration = UnserializeUInt(pReader, pos, size);
+
+ if (duration < 0)
+ return E_FILE_FORMAT_INVALID;
+ } else if (id == libwebm::kMkvReferenceBlock) {
+ if (size > 8 || size <= 0)
+ return E_FILE_FORMAT_INVALID;
+ const long size_ = static_cast<long>(size);
+
+ long long time;
+
+ long status = UnserializeInt(pReader, pos, size_, time);
+ assert(status == 0);
+ if (status != 0)
+ return -1;
+
+ if (time <= 0) // see note above
+ prev = time;
+ else
+ next = time;
+ }
+
+ pos += size; // consume payload
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+ if (bpos < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+ assert(bsize >= 0);
+
+ const long idx = m_entries_count;
+
+ BlockEntry** const ppEntry = m_entries + idx;
+ BlockEntry*& pEntry = *ppEntry;
+
+ pEntry = new (std::nothrow)
+ BlockGroup(this, idx, bpos, bsize, prev, next, duration, discard_padding);
+
+ if (pEntry == NULL)
+ return -1; // generic error
+
+ BlockGroup* const p = static_cast<BlockGroup*>(pEntry);
+
+ const long status = p->Parse();
+
+ if (status == 0) { // success
+ ++m_entries_count;
+ return 0;
+ }
+
+ delete pEntry;
+ pEntry = 0;
+
+ return status;
+}
+
+long Cluster::CreateSimpleBlock(long long st, long long sz) {
+ assert(m_entries);
+ assert(m_entries_size > 0);
+ assert(m_entries_count >= 0);
+ assert(m_entries_count < m_entries_size);
+
+ const long idx = m_entries_count;
+
+ BlockEntry** const ppEntry = m_entries + idx;
+ BlockEntry*& pEntry = *ppEntry;
+
+ pEntry = new (std::nothrow) SimpleBlock(this, idx, st, sz);
+
+ if (pEntry == NULL)
+ return -1; // generic error
+
+ SimpleBlock* const p = static_cast<SimpleBlock*>(pEntry);
+
+ const long status = p->Parse();
+
+ if (status == 0) {
+ ++m_entries_count;
+ return 0;
+ }
+
+ delete pEntry;
+ pEntry = 0;
+
+ return status;
+}
+
+long Cluster::GetFirst(const BlockEntry*& pFirst) const {
+ if (m_entries_count <= 0) {
+ long long pos;
+ long len;
+
+ const long status = Parse(pos, len);
+
+ if (status < 0) { // error
+ pFirst = NULL;
+ return status;
+ }
+
+ if (m_entries_count <= 0) { // empty cluster
+ pFirst = NULL;
+ return 0;
+ }
+ }
+
+ assert(m_entries);
+
+ pFirst = m_entries[0];
+ assert(pFirst);
+
+ return 0; // success
+}
+
+long Cluster::GetLast(const BlockEntry*& pLast) const {
+ for (;;) {
+ long long pos;
+ long len;
+
+ const long status = Parse(pos, len);
+
+ if (status < 0) { // error
+ pLast = NULL;
+ return status;
+ }
+
+ if (status > 0) // no new block
+ break;
+ }
+
+ if (m_entries_count <= 0) {
+ pLast = NULL;
+ return 0;
+ }
+
+ assert(m_entries);
+
+ const long idx = m_entries_count - 1;
+
+ pLast = m_entries[idx];
+ assert(pLast);
+
+ return 0;
+}
+
+long Cluster::GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const {
+ assert(pCurr);
+ assert(m_entries);
+ assert(m_entries_count > 0);
+
+ size_t idx = pCurr->GetIndex();
+ assert(idx < size_t(m_entries_count));
+ assert(m_entries[idx] == pCurr);
+
+ ++idx;
+
+ if (idx >= size_t(m_entries_count)) {
+ long long pos;
+ long len;
+
+ const long status = Parse(pos, len);
+
+ if (status < 0) { // error
+ pNext = NULL;
+ return status;
+ }
+
+ if (status > 0) {
+ pNext = NULL;
+ return 0;
+ }
+
+ assert(m_entries);
+ assert(m_entries_count > 0);
+ assert(idx < size_t(m_entries_count));
+ }
+
+ pNext = m_entries[idx];
+ assert(pNext);
+
+ return 0;
+}
+
+long Cluster::GetEntryCount() const { return m_entries_count; }
+
+const BlockEntry* Cluster::GetEntry(const Track* pTrack,
+ long long time_ns) const {
+ assert(pTrack);
+
+ if (m_pSegment == NULL) // this is the special EOS cluster
+ return pTrack->GetEOS();
+
+ const BlockEntry* pResult = pTrack->GetEOS();
+
+ long index = 0;
+
+ for (;;) {
+ if (index >= m_entries_count) {
+ long long pos;
+ long len;
+
+ const long status = Parse(pos, len);
+ assert(status >= 0);
+
+ if (status > 0) // completely parsed, and no more entries
+ return pResult;
+
+ if (status < 0) // should never happen
+ return 0;
+
+ assert(m_entries);
+ assert(index < m_entries_count);
+ }
+
+ const BlockEntry* const pEntry = m_entries[index];
+ assert(pEntry);
+ assert(!pEntry->EOS());
+
+ const Block* const pBlock = pEntry->GetBlock();
+ assert(pBlock);
+
+ if (pBlock->GetTrackNumber() != pTrack->GetNumber()) {
+ ++index;
+ continue;
+ }
+
+ if (pTrack->VetEntry(pEntry)) {
+ if (time_ns < 0) // just want first candidate block
+ return pEntry;
+
+ const long long ns = pBlock->GetTime(this);
+
+ if (ns > time_ns)
+ return pResult;
+
+ pResult = pEntry; // have a candidate
+ } else if (time_ns >= 0) {
+ const long long ns = pBlock->GetTime(this);
+
+ if (ns > time_ns)
+ return pResult;
+ }
+
+ ++index;
+ }
+}
+
+const BlockEntry* Cluster::GetEntry(const CuePoint& cp,
+ const CuePoint::TrackPosition& tp) const {
+ assert(m_pSegment);
+ const long long tc = cp.GetTimeCode();
+
+ if (tp.m_block > 0) {
+ const long block = static_cast<long>(tp.m_block);
+ const long index = block - 1;
+
+ while (index >= m_entries_count) {
+ long long pos;
+ long len;
+
+ const long status = Parse(pos, len);
+
+ if (status < 0) // TODO: can this happen?
+ return NULL;
+
+ if (status > 0) // nothing remains to be parsed
+ return NULL;
+ }
+
+ const BlockEntry* const pEntry = m_entries[index];
+ assert(pEntry);
+ assert(!pEntry->EOS());
+
+ const Block* const pBlock = pEntry->GetBlock();
+ assert(pBlock);
+
+ if ((pBlock->GetTrackNumber() == tp.m_track) &&
+ (pBlock->GetTimeCode(this) == tc)) {
+ return pEntry;
+ }
+ }
+
+ long index = 0;
+
+ for (;;) {
+ if (index >= m_entries_count) {
+ long long pos;
+ long len;
+
+ const long status = Parse(pos, len);
+
+ if (status < 0) // TODO: can this happen?
+ return NULL;
+
+ if (status > 0) // nothing remains to be parsed
+ return NULL;
+
+ assert(m_entries);
+ assert(index < m_entries_count);
+ }
+
+ const BlockEntry* const pEntry = m_entries[index];
+ assert(pEntry);
+ assert(!pEntry->EOS());
+
+ const Block* const pBlock = pEntry->GetBlock();
+ assert(pBlock);
+
+ if (pBlock->GetTrackNumber() != tp.m_track) {
+ ++index;
+ continue;
+ }
+
+ const long long tc_ = pBlock->GetTimeCode(this);
+
+ if (tc_ < tc) {
+ ++index;
+ continue;
+ }
+
+ if (tc_ > tc)
+ return NULL;
+
+ const Tracks* const pTracks = m_pSegment->GetTracks();
+ assert(pTracks);
+
+ const long tn = static_cast<long>(tp.m_track);
+ const Track* const pTrack = pTracks->GetTrackByNumber(tn);
+
+ if (pTrack == NULL)
+ return NULL;
+
+ const long long type = pTrack->GetType();
+
+ if (type == 2) // audio
+ return pEntry;
+
+ if (type != 1) // not video
+ return NULL;
+
+ if (!pBlock->IsKey())
+ return NULL;
+
+ return pEntry;
+ }
+}
+
+BlockEntry::BlockEntry(Cluster* p, long idx) : m_pCluster(p), m_index(idx) {}
+BlockEntry::~BlockEntry() {}
+const Cluster* BlockEntry::GetCluster() const { return m_pCluster; }
+long BlockEntry::GetIndex() const { return m_index; }
+
+SimpleBlock::SimpleBlock(Cluster* pCluster, long idx, long long start,
+ long long size)
+ : BlockEntry(pCluster, idx), m_block(start, size, 0) {}
+
+long SimpleBlock::Parse() { return m_block.Parse(m_pCluster); }
+BlockEntry::Kind SimpleBlock::GetKind() const { return kBlockSimple; }
+const Block* SimpleBlock::GetBlock() const { return &m_block; }
+
+BlockGroup::BlockGroup(Cluster* pCluster, long idx, long long block_start,
+ long long block_size, long long prev, long long next,
+ long long duration, long long discard_padding)
+ : BlockEntry(pCluster, idx),
+ m_block(block_start, block_size, discard_padding),
+ m_prev(prev),
+ m_next(next),
+ m_duration(duration) {}
+
+long BlockGroup::Parse() {
+ const long status = m_block.Parse(m_pCluster);
+
+ if (status)
+ return status;
+
+ m_block.SetKey((m_prev > 0) && (m_next <= 0));
+
+ return 0;
+}
+
+BlockEntry::Kind BlockGroup::GetKind() const { return kBlockGroup; }
+const Block* BlockGroup::GetBlock() const { return &m_block; }
+long long BlockGroup::GetPrevTimeCode() const { return m_prev; }
+long long BlockGroup::GetNextTimeCode() const { return m_next; }
+long long BlockGroup::GetDurationTimeCode() const { return m_duration; }
+
+Block::Block(long long start, long long size_, long long discard_padding)
+ : m_start(start),
+ m_size(size_),
+ m_track(0),
+ m_timecode(-1),
+ m_flags(0),
+ m_frames(NULL),
+ m_frame_count(-1),
+ m_discard_padding(discard_padding) {}
+
+Block::~Block() { delete[] m_frames; }
+
+long Block::Parse(const Cluster* pCluster) {
+ if (pCluster == NULL)
+ return -1;
+
+ if (pCluster->m_pSegment == NULL)
+ return -1;
+
+ assert(m_start >= 0);
+ assert(m_size >= 0);
+ assert(m_track <= 0);
+ assert(m_frames == NULL);
+ assert(m_frame_count <= 0);
+
+ long long pos = m_start;
+ const long long stop = m_start + m_size;
+
+ long len;
+
+ IMkvReader* const pReader = pCluster->m_pSegment->m_pReader;
+
+ m_track = ReadUInt(pReader, pos, len);
+
+ if (m_track <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume track number
+
+ if ((stop - pos) < 2)
+ return E_FILE_FORMAT_INVALID;
+
+ long status;
+ long long value;
+
+ status = UnserializeInt(pReader, pos, 2, value);
+
+ if (status)
+ return E_FILE_FORMAT_INVALID;
+
+ if (value < SHRT_MIN)
+ return E_FILE_FORMAT_INVALID;
+
+ if (value > SHRT_MAX)
+ return E_FILE_FORMAT_INVALID;
+
+ m_timecode = static_cast<short>(value);
+
+ pos += 2;
+
+ if ((stop - pos) <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ status = pReader->Read(pos, 1, &m_flags);
+
+ if (status)
+ return E_FILE_FORMAT_INVALID;
+
+ const int lacing = int(m_flags & 0x06) >> 1;
+
+ ++pos; // consume flags byte
+
+ if (lacing == 0) { // no lacing
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ m_frame_count = 1;
+ m_frames = new (std::nothrow) Frame[m_frame_count];
+ if (m_frames == NULL)
+ return -1;
+
+ Frame& f = m_frames[0];
+ f.pos = pos;
+
+ const long long frame_size = stop - pos;
+
+ if (frame_size > LONG_MAX || frame_size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ f.len = static_cast<long>(frame_size);
+
+ return 0; // success
+ }
+
+ if (pos >= stop)
+ return E_FILE_FORMAT_INVALID;
+
+ unsigned char biased_count;
+
+ status = pReader->Read(pos, 1, &biased_count);
+
+ if (status)
+ return E_FILE_FORMAT_INVALID;
+
+ ++pos; // consume frame count
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ m_frame_count = int(biased_count) + 1;
+
+ m_frames = new (std::nothrow) Frame[m_frame_count];
+ if (m_frames == NULL)
+ return -1;
+
+ if (!m_frames)
+ return E_FILE_FORMAT_INVALID;
+
+ if (lacing == 1) { // Xiph
+ Frame* pf = m_frames;
+ Frame* const pf_end = pf + m_frame_count;
+
+ long long size = 0;
+ int frame_count = m_frame_count;
+
+ while (frame_count > 1) {
+ long frame_size = 0;
+
+ for (;;) {
+ unsigned char val;
+
+ if (pos >= stop)
+ return E_FILE_FORMAT_INVALID;
+
+ status = pReader->Read(pos, 1, &val);
+
+ if (status)
+ return E_FILE_FORMAT_INVALID;
+
+ ++pos; // consume xiph size byte
+
+ frame_size += val;
+
+ if (val < 255)
+ break;
+ }
+
+ Frame& f = *pf++;
+ assert(pf < pf_end);
+ if (pf >= pf_end)
+ return E_FILE_FORMAT_INVALID;
+
+ f.pos = 0; // patch later
+
+ if (frame_size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ f.len = frame_size;
+ size += frame_size; // contribution of this frame
+
+ --frame_count;
+ }
+
+ if (pf >= pf_end || pos > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ {
+ Frame& f = *pf++;
+
+ if (pf != pf_end)
+ return E_FILE_FORMAT_INVALID;
+
+ f.pos = 0; // patch later
+
+ const long long total_size = stop - pos;
+
+ if (total_size < size)
+ return E_FILE_FORMAT_INVALID;
+
+ const long long frame_size = total_size - size;
+
+ if (frame_size > LONG_MAX || frame_size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ f.len = static_cast<long>(frame_size);
+ }
+
+ pf = m_frames;
+ while (pf != pf_end) {
+ Frame& f = *pf++;
+ assert((pos + f.len) <= stop);
+
+ if ((pos + f.len) > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ f.pos = pos;
+ pos += f.len;
+ }
+
+ assert(pos == stop);
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ } else if (lacing == 2) { // fixed-size lacing
+ if (pos >= stop)
+ return E_FILE_FORMAT_INVALID;
+
+ const long long total_size = stop - pos;
+
+ if ((total_size % m_frame_count) != 0)
+ return E_FILE_FORMAT_INVALID;
+
+ const long long frame_size = total_size / m_frame_count;
+
+ if (frame_size > LONG_MAX || frame_size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ Frame* pf = m_frames;
+ Frame* const pf_end = pf + m_frame_count;
+
+ while (pf != pf_end) {
+ assert((pos + frame_size) <= stop);
+ if ((pos + frame_size) > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ Frame& f = *pf++;
+
+ f.pos = pos;
+ f.len = static_cast<long>(frame_size);
+
+ pos += frame_size;
+ }
+
+ assert(pos == stop);
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+
+ } else {
+ assert(lacing == 3); // EBML lacing
+
+ if (pos >= stop)
+ return E_FILE_FORMAT_INVALID;
+
+ long long size = 0;
+ int frame_count = m_frame_count;
+
+ long long frame_size = ReadUInt(pReader, pos, len);
+
+ if (frame_size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if (frame_size > LONG_MAX)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume length of size of first frame
+
+ if ((pos + frame_size) > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ Frame* pf = m_frames;
+ Frame* const pf_end = pf + m_frame_count;
+
+ {
+ Frame& curr = *pf;
+
+ curr.pos = 0; // patch later
+
+ curr.len = static_cast<long>(frame_size);
+ size += curr.len; // contribution of this frame
+ }
+
+ --frame_count;
+
+ while (frame_count > 1) {
+ if (pos >= stop)
+ return E_FILE_FORMAT_INVALID;
+
+ assert(pf < pf_end);
+ if (pf >= pf_end)
+ return E_FILE_FORMAT_INVALID;
+
+ const Frame& prev = *pf++;
+ assert(prev.len == frame_size);
+ if (prev.len != frame_size)
+ return E_FILE_FORMAT_INVALID;
+
+ assert(pf < pf_end);
+ if (pf >= pf_end)
+ return E_FILE_FORMAT_INVALID;
+
+ Frame& curr = *pf;
+
+ curr.pos = 0; // patch later
+
+ const long long delta_size_ = ReadUInt(pReader, pos, len);
+
+ if (delta_size_ < 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if ((pos + len) > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ pos += len; // consume length of (delta) size
+ if (pos > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ const long exp = 7 * len - 1;
+ const long long bias = (1LL << exp) - 1LL;
+ const long long delta_size = delta_size_ - bias;
+
+ frame_size += delta_size;
+
+ if (frame_size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ if (frame_size > LONG_MAX)
+ return E_FILE_FORMAT_INVALID;
+
+ curr.len = static_cast<long>(frame_size);
+ // Check if size + curr.len could overflow.
+ if (size > LLONG_MAX - curr.len) {
+ return E_FILE_FORMAT_INVALID;
+ }
+ size += curr.len; // contribution of this frame
+
+ --frame_count;
+ }
+
+ // parse last frame
+ if (frame_count > 0) {
+ if (pos > stop || pf >= pf_end)
+ return E_FILE_FORMAT_INVALID;
+
+ const Frame& prev = *pf++;
+ assert(prev.len == frame_size);
+ if (prev.len != frame_size)
+ return E_FILE_FORMAT_INVALID;
+
+ if (pf >= pf_end)
+ return E_FILE_FORMAT_INVALID;
+
+ Frame& curr = *pf++;
+ if (pf != pf_end)
+ return E_FILE_FORMAT_INVALID;
+
+ curr.pos = 0; // patch later
+
+ const long long total_size = stop - pos;
+
+ if (total_size < size)
+ return E_FILE_FORMAT_INVALID;
+
+ frame_size = total_size - size;
+
+ if (frame_size > LONG_MAX || frame_size <= 0)
+ return E_FILE_FORMAT_INVALID;
+
+ curr.len = static_cast<long>(frame_size);
+ }
+
+ pf = m_frames;
+ while (pf != pf_end) {
+ Frame& f = *pf++;
+ if ((pos + f.len) > stop)
+ return E_FILE_FORMAT_INVALID;
+
+ f.pos = pos;
+ pos += f.len;
+ }
+
+ if (pos != stop)
+ return E_FILE_FORMAT_INVALID;
+ }
+
+ return 0; // success
+}
+
+long long Block::GetTimeCode(const Cluster* pCluster) const {
+ if (pCluster == 0)
+ return m_timecode;
+
+ const long long tc0 = pCluster->GetTimeCode();
+ assert(tc0 >= 0);
+
+ // Check if tc0 + m_timecode would overflow.
+ if (tc0 < 0 || LLONG_MAX - tc0 < m_timecode) {
+ return -1;
+ }
+
+ const long long tc = tc0 + m_timecode;
+
+ return tc; // unscaled timecode units
+}
+
+long long Block::GetTime(const Cluster* pCluster) const {
+ assert(pCluster);
+
+ const long long tc = GetTimeCode(pCluster);
+
+ const Segment* const pSegment = pCluster->m_pSegment;
+ const SegmentInfo* const pInfo = pSegment->GetInfo();
+ assert(pInfo);
+
+ const long long scale = pInfo->GetTimeCodeScale();
+ assert(scale >= 1);
+
+ // Check if tc * scale could overflow.
+ if (tc != 0 && scale > LLONG_MAX / tc) {
+ return -1;
+ }
+ const long long ns = tc * scale;
+
+ return ns;
+}
+
+long long Block::GetTrackNumber() const { return m_track; }
+
+bool Block::IsKey() const {
+ return ((m_flags & static_cast<unsigned char>(1 << 7)) != 0);
+}
+
+void Block::SetKey(bool bKey) {
+ if (bKey)
+ m_flags |= static_cast<unsigned char>(1 << 7);
+ else
+ m_flags &= 0x7F;
+}
+
+bool Block::IsInvisible() const { return bool(int(m_flags & 0x08) != 0); }
+
+Block::Lacing Block::GetLacing() const {
+ const int value = int(m_flags & 0x06) >> 1;
+ return static_cast<Lacing>(value);
+}
+
+int Block::GetFrameCount() const { return m_frame_count; }
+
+const Block::Frame& Block::GetFrame(int idx) const {
+ assert(idx >= 0);
+ assert(idx < m_frame_count);
+
+ const Frame& f = m_frames[idx];
+ assert(f.pos > 0);
+ assert(f.len > 0);
+
+ return f;
+}
+
+long Block::Frame::Read(IMkvReader* pReader, unsigned char* buf) const {
+ assert(pReader);
+ assert(buf);
+
+ const long status = pReader->Read(pos, len, buf);
+ return status;
+}
+
+long long Block::GetDiscardPadding() const { return m_discard_padding; }
+
+} // namespace mkvparser
diff --git a/mkvparser/mkvparser.h b/mkvparser/mkvparser.h
new file mode 100644
index 0000000..848d01f
--- /dev/null
+++ b/mkvparser/mkvparser.h
@@ -0,0 +1,1147 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef MKVPARSER_MKVPARSER_H_
+#define MKVPARSER_MKVPARSER_H_
+
+#include <cstddef>
+
+namespace mkvparser {
+
+const int E_PARSE_FAILED = -1;
+const int E_FILE_FORMAT_INVALID = -2;
+const int E_BUFFER_NOT_FULL = -3;
+
+class IMkvReader {
+ public:
+ virtual int Read(long long pos, long len, unsigned char* buf) = 0;
+ virtual int Length(long long* total, long long* available) = 0;
+
+ protected:
+ virtual ~IMkvReader() {}
+};
+
+template <typename Type>
+Type* SafeArrayAlloc(unsigned long long num_elements,
+ unsigned long long element_size);
+long long GetUIntLength(IMkvReader*, long long, long&);
+long long ReadUInt(IMkvReader*, long long, long&);
+long long ReadID(IMkvReader* pReader, long long pos, long& len);
+long long UnserializeUInt(IMkvReader*, long long pos, long long size);
+
+long UnserializeFloat(IMkvReader*, long long pos, long long size, double&);
+long UnserializeInt(IMkvReader*, long long pos, long long size,
+ long long& result);
+
+long UnserializeString(IMkvReader*, long long pos, long long size, char*& str);
+
+long ParseElementHeader(IMkvReader* pReader,
+ long long& pos, // consume id and size fields
+ long long stop, // if you know size of element's parent
+ long long& id, long long& size);
+
+bool Match(IMkvReader*, long long&, unsigned long, long long&);
+bool Match(IMkvReader*, long long&, unsigned long, unsigned char*&, size_t&);
+
+void GetVersion(int& major, int& minor, int& build, int& revision);
+
+struct EBMLHeader {
+ EBMLHeader();
+ ~EBMLHeader();
+ long long m_version;
+ long long m_readVersion;
+ long long m_maxIdLength;
+ long long m_maxSizeLength;
+ char* m_docType;
+ long long m_docTypeVersion;
+ long long m_docTypeReadVersion;
+
+ long long Parse(IMkvReader*, long long&);
+ void Init();
+};
+
+class Segment;
+class Track;
+class Cluster;
+
+class Block {
+ Block(const Block&);
+ Block& operator=(const Block&);
+
+ public:
+ const long long m_start;
+ const long long m_size;
+
+ Block(long long start, long long size, long long discard_padding);
+ ~Block();
+
+ long Parse(const Cluster*);
+
+ long long GetTrackNumber() const;
+ long long GetTimeCode(const Cluster*) const; // absolute, but not scaled
+ long long GetTime(const Cluster*) const; // absolute, and scaled (ns)
+ bool IsKey() const;
+ void SetKey(bool);
+ bool IsInvisible() const;
+
+ enum Lacing { kLacingNone, kLacingXiph, kLacingFixed, kLacingEbml };
+ Lacing GetLacing() const;
+
+ int GetFrameCount() const; // to index frames: [0, count)
+
+ struct Frame {
+ long long pos; // absolute offset
+ long len;
+
+ long Read(IMkvReader*, unsigned char*) const;
+ };
+
+ const Frame& GetFrame(int frame_index) const;
+
+ long long GetDiscardPadding() const;
+
+ private:
+ long long m_track; // Track::Number()
+ short m_timecode; // relative to cluster
+ unsigned char m_flags;
+
+ Frame* m_frames;
+ int m_frame_count;
+
+ protected:
+ const long long m_discard_padding;
+};
+
+class BlockEntry {
+ BlockEntry(const BlockEntry&);
+ BlockEntry& operator=(const BlockEntry&);
+
+ protected:
+ BlockEntry(Cluster*, long index);
+
+ public:
+ virtual ~BlockEntry();
+
+ bool EOS() const { return (GetKind() == kBlockEOS); }
+ const Cluster* GetCluster() const;
+ long GetIndex() const;
+ virtual const Block* GetBlock() const = 0;
+
+ enum Kind { kBlockEOS, kBlockSimple, kBlockGroup };
+ virtual Kind GetKind() const = 0;
+
+ protected:
+ Cluster* const m_pCluster;
+ const long m_index;
+};
+
+class SimpleBlock : public BlockEntry {
+ SimpleBlock(const SimpleBlock&);
+ SimpleBlock& operator=(const SimpleBlock&);
+
+ public:
+ SimpleBlock(Cluster*, long index, long long start, long long size);
+ long Parse();
+
+ Kind GetKind() const;
+ const Block* GetBlock() const;
+
+ protected:
+ Block m_block;
+};
+
+class BlockGroup : public BlockEntry {
+ BlockGroup(const BlockGroup&);
+ BlockGroup& operator=(const BlockGroup&);
+
+ public:
+ BlockGroup(Cluster*, long index,
+ long long block_start, // absolute pos of block's payload
+ long long block_size, // size of block's payload
+ long long prev, long long next, long long duration,
+ long long discard_padding);
+
+ long Parse();
+
+ Kind GetKind() const;
+ const Block* GetBlock() const;
+
+ long long GetPrevTimeCode() const; // relative to block's time
+ long long GetNextTimeCode() const; // as above
+ long long GetDurationTimeCode() const;
+
+ private:
+ Block m_block;
+ const long long m_prev;
+ const long long m_next;
+ const long long m_duration;
+};
+
+///////////////////////////////////////////////////////////////
+// ContentEncoding element
+// Elements used to describe if the track data has been encrypted or
+// compressed with zlib or header stripping.
+class ContentEncoding {
+ public:
+ enum { kCTR = 1 };
+
+ ContentEncoding();
+ ~ContentEncoding();
+
+ // ContentCompression element names
+ struct ContentCompression {
+ ContentCompression();
+ ~ContentCompression();
+
+ unsigned long long algo;
+ unsigned char* settings;
+ long long settings_len;
+ };
+
+ // ContentEncAESSettings element names
+ struct ContentEncAESSettings {
+ ContentEncAESSettings() : cipher_mode(kCTR) {}
+ ~ContentEncAESSettings() {}
+
+ unsigned long long cipher_mode;
+ };
+
+ // ContentEncryption element names
+ struct ContentEncryption {
+ ContentEncryption();
+ ~ContentEncryption();
+
+ unsigned long long algo;
+ unsigned char* key_id;
+ long long key_id_len;
+ unsigned char* signature;
+ long long signature_len;
+ unsigned char* sig_key_id;
+ long long sig_key_id_len;
+ unsigned long long sig_algo;
+ unsigned long long sig_hash_algo;
+
+ ContentEncAESSettings aes_settings;
+ };
+
+ // Returns ContentCompression represented by |idx|. Returns NULL if |idx|
+ // is out of bounds.
+ const ContentCompression* GetCompressionByIndex(unsigned long idx) const;
+
+ // Returns number of ContentCompression elements in this ContentEncoding
+ // element.
+ unsigned long GetCompressionCount() const;
+
+ // Parses the ContentCompression element from |pReader|. |start| is the
+ // starting offset of the ContentCompression payload. |size| is the size in
+ // bytes of the ContentCompression payload. |compression| is where the parsed
+ // values will be stored.
+ long ParseCompressionEntry(long long start, long long size,
+ IMkvReader* pReader,
+ ContentCompression* compression);
+
+ // Returns ContentEncryption represented by |idx|. Returns NULL if |idx|
+ // is out of bounds.
+ const ContentEncryption* GetEncryptionByIndex(unsigned long idx) const;
+
+ // Returns number of ContentEncryption elements in this ContentEncoding
+ // element.
+ unsigned long GetEncryptionCount() const;
+
+ // Parses the ContentEncAESSettings element from |pReader|. |start| is the
+ // starting offset of the ContentEncAESSettings payload. |size| is the
+ // size in bytes of the ContentEncAESSettings payload. |encryption| is
+ // where the parsed values will be stored.
+ long ParseContentEncAESSettingsEntry(long long start, long long size,
+ IMkvReader* pReader,
+ ContentEncAESSettings* aes);
+
+ // Parses the ContentEncoding element from |pReader|. |start| is the
+ // starting offset of the ContentEncoding payload. |size| is the size in
+ // bytes of the ContentEncoding payload. Returns true on success.
+ long ParseContentEncodingEntry(long long start, long long size,
+ IMkvReader* pReader);
+
+ // Parses the ContentEncryption element from |pReader|. |start| is the
+ // starting offset of the ContentEncryption payload. |size| is the size in
+ // bytes of the ContentEncryption payload. |encryption| is where the parsed
+ // values will be stored.
+ long ParseEncryptionEntry(long long start, long long size,
+ IMkvReader* pReader, ContentEncryption* encryption);
+
+ unsigned long long encoding_order() const { return encoding_order_; }
+ unsigned long long encoding_scope() const { return encoding_scope_; }
+ unsigned long long encoding_type() const { return encoding_type_; }
+
+ private:
+ // Member variables for list of ContentCompression elements.
+ ContentCompression** compression_entries_;
+ ContentCompression** compression_entries_end_;
+
+ // Member variables for list of ContentEncryption elements.
+ ContentEncryption** encryption_entries_;
+ ContentEncryption** encryption_entries_end_;
+
+ // ContentEncoding element names
+ unsigned long long encoding_order_;
+ unsigned long long encoding_scope_;
+ unsigned long long encoding_type_;
+
+ // LIBWEBM_DISALLOW_COPY_AND_ASSIGN(ContentEncoding);
+ ContentEncoding(const ContentEncoding&);
+ ContentEncoding& operator=(const ContentEncoding&);
+};
+
+class Track {
+ Track(const Track&);
+ Track& operator=(const Track&);
+
+ public:
+ class Info;
+ static long Create(Segment*, const Info&, long long element_start,
+ long long element_size, Track*&);
+
+ enum Type { kVideo = 1, kAudio = 2, kSubtitle = 0x11, kMetadata = 0x21 };
+
+ Segment* const m_pSegment;
+ const long long m_element_start;
+ const long long m_element_size;
+ virtual ~Track();
+
+ long GetType() const;
+ long GetNumber() const;
+ unsigned long long GetUid() const;
+ const char* GetNameAsUTF8() const;
+ const char* GetLanguage() const;
+ const char* GetCodecNameAsUTF8() const;
+ const char* GetCodecId() const;
+ const unsigned char* GetCodecPrivate(size_t&) const;
+ bool GetLacing() const;
+ unsigned long long GetDefaultDuration() const;
+ unsigned long long GetCodecDelay() const;
+ unsigned long long GetSeekPreRoll() const;
+
+ const BlockEntry* GetEOS() const;
+
+ struct Settings {
+ long long start;
+ long long size;
+ };
+
+ class Info {
+ public:
+ Info();
+ ~Info();
+ int Copy(Info&) const;
+ void Clear();
+ long type;
+ long number;
+ unsigned long long uid;
+ unsigned long long defaultDuration;
+ unsigned long long codecDelay;
+ unsigned long long seekPreRoll;
+ char* nameAsUTF8;
+ char* language;
+ char* codecId;
+ char* codecNameAsUTF8;
+ unsigned char* codecPrivate;
+ size_t codecPrivateSize;
+ bool lacing;
+ Settings settings;
+
+ private:
+ Info(const Info&);
+ Info& operator=(const Info&);
+ int CopyStr(char* Info::*str, Info&) const;
+ };
+
+ long GetFirst(const BlockEntry*&) const;
+ long GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const;
+ virtual bool VetEntry(const BlockEntry*) const;
+ virtual long Seek(long long time_ns, const BlockEntry*&) const;
+
+ const ContentEncoding* GetContentEncodingByIndex(unsigned long idx) const;
+ unsigned long GetContentEncodingCount() const;
+
+ long ParseContentEncodingsEntry(long long start, long long size);
+
+ protected:
+ Track(Segment*, long long element_start, long long element_size);
+
+ Info m_info;
+
+ class EOSBlock : public BlockEntry {
+ public:
+ EOSBlock();
+
+ Kind GetKind() const;
+ const Block* GetBlock() const;
+ };
+
+ EOSBlock m_eos;
+
+ private:
+ ContentEncoding** content_encoding_entries_;
+ ContentEncoding** content_encoding_entries_end_;
+};
+
+struct PrimaryChromaticity {
+ PrimaryChromaticity() : x(0), y(0) {}
+ ~PrimaryChromaticity() {}
+ static bool Parse(IMkvReader* reader, long long read_pos,
+ long long value_size, bool is_x,
+ PrimaryChromaticity** chromaticity);
+ float x;
+ float y;
+};
+
+struct MasteringMetadata {
+ static const float kValueNotPresent;
+
+ MasteringMetadata()
+ : r(NULL),
+ g(NULL),
+ b(NULL),
+ white_point(NULL),
+ luminance_max(kValueNotPresent),
+ luminance_min(kValueNotPresent) {}
+ ~MasteringMetadata() {
+ delete r;
+ delete g;
+ delete b;
+ delete white_point;
+ }
+
+ static bool Parse(IMkvReader* reader, long long element_start,
+ long long element_size,
+ MasteringMetadata** mastering_metadata);
+
+ PrimaryChromaticity* r;
+ PrimaryChromaticity* g;
+ PrimaryChromaticity* b;
+ PrimaryChromaticity* white_point;
+ float luminance_max;
+ float luminance_min;
+};
+
+struct Colour {
+ static const long long kValueNotPresent;
+
+ // Unless otherwise noted all values assigned upon construction are the
+ // equivalent of unspecified/default.
+ Colour()
+ : matrix_coefficients(kValueNotPresent),
+ bits_per_channel(kValueNotPresent),
+ chroma_subsampling_horz(kValueNotPresent),
+ chroma_subsampling_vert(kValueNotPresent),
+ cb_subsampling_horz(kValueNotPresent),
+ cb_subsampling_vert(kValueNotPresent),
+ chroma_siting_horz(kValueNotPresent),
+ chroma_siting_vert(kValueNotPresent),
+ range(kValueNotPresent),
+ transfer_characteristics(kValueNotPresent),
+ primaries(kValueNotPresent),
+ max_cll(kValueNotPresent),
+ max_fall(kValueNotPresent),
+ mastering_metadata(NULL) {}
+ ~Colour() {
+ delete mastering_metadata;
+ mastering_metadata = NULL;
+ }
+
+ static bool Parse(IMkvReader* reader, long long element_start,
+ long long element_size, Colour** colour);
+
+ long long matrix_coefficients;
+ long long bits_per_channel;
+ long long chroma_subsampling_horz;
+ long long chroma_subsampling_vert;
+ long long cb_subsampling_horz;
+ long long cb_subsampling_vert;
+ long long chroma_siting_horz;
+ long long chroma_siting_vert;
+ long long range;
+ long long transfer_characteristics;
+ long long primaries;
+ long long max_cll;
+ long long max_fall;
+
+ MasteringMetadata* mastering_metadata;
+};
+
+struct Projection {
+ enum ProjectionType {
+ kTypeNotPresent = -1,
+ kRectangular = 0,
+ kEquirectangular = 1,
+ kCubeMap = 2,
+ kMesh = 3,
+ };
+ static const float kValueNotPresent;
+ Projection()
+ : type(kTypeNotPresent),
+ private_data(NULL),
+ private_data_length(0),
+ pose_yaw(kValueNotPresent),
+ pose_pitch(kValueNotPresent),
+ pose_roll(kValueNotPresent) {}
+ ~Projection() { delete[] private_data; }
+ static bool Parse(IMkvReader* reader, long long element_start,
+ long long element_size, Projection** projection);
+
+ ProjectionType type;
+ unsigned char* private_data;
+ size_t private_data_length;
+ float pose_yaw;
+ float pose_pitch;
+ float pose_roll;
+};
+
+class VideoTrack : public Track {
+ VideoTrack(const VideoTrack&);
+ VideoTrack& operator=(const VideoTrack&);
+
+ VideoTrack(Segment*, long long element_start, long long element_size);
+
+ public:
+ virtual ~VideoTrack();
+ static long Parse(Segment*, const Info&, long long element_start,
+ long long element_size, VideoTrack*&);
+
+ long long GetWidth() const;
+ long long GetHeight() const;
+ long long GetDisplayWidth() const;
+ long long GetDisplayHeight() const;
+ long long GetDisplayUnit() const;
+ long long GetStereoMode() const;
+ double GetFrameRate() const;
+
+ bool VetEntry(const BlockEntry*) const;
+ long Seek(long long time_ns, const BlockEntry*&) const;
+
+ Colour* GetColour() const;
+
+ Projection* GetProjection() const;
+
+ const char* GetColourSpace() const { return m_colour_space; }
+
+ private:
+ long long m_width;
+ long long m_height;
+ long long m_display_width;
+ long long m_display_height;
+ long long m_display_unit;
+ long long m_stereo_mode;
+ char* m_colour_space;
+ double m_rate;
+
+ Colour* m_colour;
+ Projection* m_projection;
+};
+
+class AudioTrack : public Track {
+ AudioTrack(const AudioTrack&);
+ AudioTrack& operator=(const AudioTrack&);
+
+ AudioTrack(Segment*, long long element_start, long long element_size);
+
+ public:
+ static long Parse(Segment*, const Info&, long long element_start,
+ long long element_size, AudioTrack*&);
+
+ double GetSamplingRate() const;
+ long long GetChannels() const;
+ long long GetBitDepth() const;
+
+ private:
+ double m_rate;
+ long long m_channels;
+ long long m_bitDepth;
+};
+
+class Tracks {
+ Tracks(const Tracks&);
+ Tracks& operator=(const Tracks&);
+
+ public:
+ Segment* const m_pSegment;
+ const long long m_start;
+ const long long m_size;
+ const long long m_element_start;
+ const long long m_element_size;
+
+ Tracks(Segment*, long long start, long long size, long long element_start,
+ long long element_size);
+
+ ~Tracks();
+
+ long Parse();
+
+ unsigned long GetTracksCount() const;
+
+ const Track* GetTrackByNumber(long tn) const;
+ const Track* GetTrackByIndex(unsigned long idx) const;
+
+ private:
+ Track** m_trackEntries;
+ Track** m_trackEntriesEnd;
+
+ long ParseTrackEntry(long long payload_start, long long payload_size,
+ long long element_start, long long element_size,
+ Track*&) const;
+};
+
+class Chapters {
+ Chapters(const Chapters&);
+ Chapters& operator=(const Chapters&);
+
+ public:
+ Segment* const m_pSegment;
+ const long long m_start;
+ const long long m_size;
+ const long long m_element_start;
+ const long long m_element_size;
+
+ Chapters(Segment*, long long payload_start, long long payload_size,
+ long long element_start, long long element_size);
+
+ ~Chapters();
+
+ long Parse();
+
+ class Atom;
+ class Edition;
+
+ class Display {
+ friend class Atom;
+ Display();
+ Display(const Display&);
+ ~Display();
+ Display& operator=(const Display&);
+
+ public:
+ const char* GetString() const;
+ const char* GetLanguage() const;
+ const char* GetCountry() const;
+
+ private:
+ void Init();
+ void ShallowCopy(Display&) const;
+ void Clear();
+ long Parse(IMkvReader*, long long pos, long long size);
+
+ char* m_string;
+ char* m_language;
+ char* m_country;
+ };
+
+ class Atom {
+ friend class Edition;
+ Atom();
+ Atom(const Atom&);
+ ~Atom();
+ Atom& operator=(const Atom&);
+
+ public:
+ unsigned long long GetUID() const;
+ const char* GetStringUID() const;
+
+ long long GetStartTimecode() const;
+ long long GetStopTimecode() const;
+
+ long long GetStartTime(const Chapters*) const;
+ long long GetStopTime(const Chapters*) const;
+
+ int GetDisplayCount() const;
+ const Display* GetDisplay(int index) const;
+
+ private:
+ void Init();
+ void ShallowCopy(Atom&) const;
+ void Clear();
+ long Parse(IMkvReader*, long long pos, long long size);
+ static long long GetTime(const Chapters*, long long timecode);
+
+ long ParseDisplay(IMkvReader*, long long pos, long long size);
+ bool ExpandDisplaysArray();
+
+ char* m_string_uid;
+ unsigned long long m_uid;
+ long long m_start_timecode;
+ long long m_stop_timecode;
+
+ Display* m_displays;
+ int m_displays_size;
+ int m_displays_count;
+ };
+
+ class Edition {
+ friend class Chapters;
+ Edition();
+ Edition(const Edition&);
+ ~Edition();
+ Edition& operator=(const Edition&);
+
+ public:
+ int GetAtomCount() const;
+ const Atom* GetAtom(int index) const;
+
+ private:
+ void Init();
+ void ShallowCopy(Edition&) const;
+ void Clear();
+ long Parse(IMkvReader*, long long pos, long long size);
+
+ long ParseAtom(IMkvReader*, long long pos, long long size);
+ bool ExpandAtomsArray();
+
+ Atom* m_atoms;
+ int m_atoms_size;
+ int m_atoms_count;
+ };
+
+ int GetEditionCount() const;
+ const Edition* GetEdition(int index) const;
+
+ private:
+ long ParseEdition(long long pos, long long size);
+ bool ExpandEditionsArray();
+
+ Edition* m_editions;
+ int m_editions_size;
+ int m_editions_count;
+};
+
+class Tags {
+ Tags(const Tags&);
+ Tags& operator=(const Tags&);
+
+ public:
+ Segment* const m_pSegment;
+ const long long m_start;
+ const long long m_size;
+ const long long m_element_start;
+ const long long m_element_size;
+
+ Tags(Segment*, long long payload_start, long long payload_size,
+ long long element_start, long long element_size);
+
+ ~Tags();
+
+ long Parse();
+
+ class Tag;
+ class SimpleTag;
+
+ class SimpleTag {
+ friend class Tag;
+ SimpleTag();
+ SimpleTag(const SimpleTag&);
+ ~SimpleTag();
+ SimpleTag& operator=(const SimpleTag&);
+
+ public:
+ const char* GetTagName() const;
+ const char* GetTagString() const;
+
+ private:
+ void Init();
+ void ShallowCopy(SimpleTag&) const;
+ void Clear();
+ long Parse(IMkvReader*, long long pos, long long size);
+
+ char* m_tag_name;
+ char* m_tag_string;
+ };
+
+ class Tag {
+ friend class Tags;
+ Tag();
+ Tag(const Tag&);
+ ~Tag();
+ Tag& operator=(const Tag&);
+
+ public:
+ int GetSimpleTagCount() const;
+ const SimpleTag* GetSimpleTag(int index) const;
+
+ private:
+ void Init();
+ void ShallowCopy(Tag&) const;
+ void Clear();
+ long Parse(IMkvReader*, long long pos, long long size);
+
+ long ParseSimpleTag(IMkvReader*, long long pos, long long size);
+ bool ExpandSimpleTagsArray();
+
+ SimpleTag* m_simple_tags;
+ int m_simple_tags_size;
+ int m_simple_tags_count;
+ };
+
+ int GetTagCount() const;
+ const Tag* GetTag(int index) const;
+
+ private:
+ long ParseTag(long long pos, long long size);
+ bool ExpandTagsArray();
+
+ Tag* m_tags;
+ int m_tags_size;
+ int m_tags_count;
+};
+
+class SegmentInfo {
+ SegmentInfo(const SegmentInfo&);
+ SegmentInfo& operator=(const SegmentInfo&);
+
+ public:
+ Segment* const m_pSegment;
+ const long long m_start;
+ const long long m_size;
+ const long long m_element_start;
+ const long long m_element_size;
+
+ SegmentInfo(Segment*, long long start, long long size,
+ long long element_start, long long element_size);
+
+ ~SegmentInfo();
+
+ long Parse();
+
+ long long GetTimeCodeScale() const;
+ long long GetDuration() const; // scaled
+ const char* GetMuxingAppAsUTF8() const;
+ const char* GetWritingAppAsUTF8() const;
+ const char* GetTitleAsUTF8() const;
+
+ private:
+ long long m_timecodeScale;
+ double m_duration;
+ char* m_pMuxingAppAsUTF8;
+ char* m_pWritingAppAsUTF8;
+ char* m_pTitleAsUTF8;
+};
+
+class SeekHead {
+ SeekHead(const SeekHead&);
+ SeekHead& operator=(const SeekHead&);
+
+ public:
+ Segment* const m_pSegment;
+ const long long m_start;
+ const long long m_size;
+ const long long m_element_start;
+ const long long m_element_size;
+
+ SeekHead(Segment*, long long start, long long size, long long element_start,
+ long long element_size);
+
+ ~SeekHead();
+
+ long Parse();
+
+ struct Entry {
+ Entry();
+
+ // the SeekHead entry payload
+ long long id;
+ long long pos;
+
+ // absolute pos of SeekEntry ID
+ long long element_start;
+
+ // SeekEntry ID size + size size + payload
+ long long element_size;
+ };
+
+ int GetCount() const;
+ const Entry* GetEntry(int idx) const;
+
+ struct VoidElement {
+ // absolute pos of Void ID
+ long long element_start;
+
+ // ID size + size size + payload size
+ long long element_size;
+ };
+
+ int GetVoidElementCount() const;
+ const VoidElement* GetVoidElement(int idx) const;
+
+ private:
+ Entry* m_entries;
+ int m_entry_count;
+
+ VoidElement* m_void_elements;
+ int m_void_element_count;
+
+ static bool ParseEntry(IMkvReader*,
+ long long pos, // payload
+ long long size, Entry*);
+};
+
+class Cues;
+class CuePoint {
+ friend class Cues;
+
+ CuePoint(long, long long);
+ ~CuePoint();
+
+ CuePoint(const CuePoint&);
+ CuePoint& operator=(const CuePoint&);
+
+ public:
+ long long m_element_start;
+ long long m_element_size;
+
+ bool Load(IMkvReader*);
+
+ long long GetTimeCode() const; // absolute but unscaled
+ long long GetTime(const Segment*) const; // absolute and scaled (ns units)
+
+ struct TrackPosition {
+ long long m_track;
+ long long m_pos; // of cluster
+ long long m_block;
+ // codec_state //defaults to 0
+ // reference = clusters containing req'd referenced blocks
+ // reftime = timecode of the referenced block
+
+ bool Parse(IMkvReader*, long long, long long);
+ };
+
+ const TrackPosition* Find(const Track*) const;
+
+ private:
+ const long m_index;
+ long long m_timecode;
+ TrackPosition* m_track_positions;
+ size_t m_track_positions_count;
+};
+
+class Cues {
+ friend class Segment;
+
+ Cues(Segment*, long long start, long long size, long long element_start,
+ long long element_size);
+ ~Cues();
+
+ Cues(const Cues&);
+ Cues& operator=(const Cues&);
+
+ public:
+ Segment* const m_pSegment;
+ const long long m_start;
+ const long long m_size;
+ const long long m_element_start;
+ const long long m_element_size;
+
+ bool Find( // lower bound of time_ns
+ long long time_ns, const Track*, const CuePoint*&,
+ const CuePoint::TrackPosition*&) const;
+
+ const CuePoint* GetFirst() const;
+ const CuePoint* GetLast() const;
+ const CuePoint* GetNext(const CuePoint*) const;
+
+ const BlockEntry* GetBlock(const CuePoint*,
+ const CuePoint::TrackPosition*) const;
+
+ bool LoadCuePoint() const;
+ long GetCount() const; // loaded only
+ // long GetTotal() const; //loaded + preloaded
+ bool DoneParsing() const;
+
+ private:
+ bool Init() const;
+ bool PreloadCuePoint(long&, long long) const;
+
+ mutable CuePoint** m_cue_points;
+ mutable long m_count;
+ mutable long m_preload_count;
+ mutable long long m_pos;
+};
+
+class Cluster {
+ friend class Segment;
+
+ Cluster(const Cluster&);
+ Cluster& operator=(const Cluster&);
+
+ public:
+ Segment* const m_pSegment;
+
+ public:
+ static Cluster* Create(Segment*,
+ long index, // index in segment
+ long long off); // offset relative to segment
+ // long long element_size);
+
+ Cluster(); // EndOfStream
+ ~Cluster();
+
+ bool EOS() const;
+
+ long long GetTimeCode() const; // absolute, but not scaled
+ long long GetTime() const; // absolute, and scaled (nanosecond units)
+ long long GetFirstTime() const; // time (ns) of first (earliest) block
+ long long GetLastTime() const; // time (ns) of last (latest) block
+
+ long GetFirst(const BlockEntry*&) const;
+ long GetLast(const BlockEntry*&) const;
+ long GetNext(const BlockEntry* curr, const BlockEntry*& next) const;
+
+ const BlockEntry* GetEntry(const Track*, long long ns = -1) const;
+ const BlockEntry* GetEntry(const CuePoint&,
+ const CuePoint::TrackPosition&) const;
+ // const BlockEntry* GetMaxKey(const VideoTrack*) const;
+
+ // static bool HasBlockEntries(const Segment*, long long);
+
+ static long HasBlockEntries(const Segment*, long long idoff, long long& pos,
+ long& size);
+
+ long GetEntryCount() const;
+
+ long Load(long long& pos, long& size) const;
+
+ long Parse(long long& pos, long& size) const;
+ long GetEntry(long index, const mkvparser::BlockEntry*&) const;
+
+ protected:
+ Cluster(Segment*, long index, long long element_start);
+ // long long element_size);
+
+ public:
+ const long long m_element_start;
+ long long GetPosition() const; // offset relative to segment
+
+ long GetIndex() const;
+ long long GetElementSize() const;
+ // long long GetPayloadSize() const;
+
+ // long long Unparsed() const;
+
+ private:
+ long m_index;
+ mutable long long m_pos;
+ // mutable long long m_size;
+ mutable long long m_element_size;
+ mutable long long m_timecode;
+ mutable BlockEntry** m_entries;
+ mutable long m_entries_size;
+ mutable long m_entries_count;
+
+ long ParseSimpleBlock(long long, long long&, long&);
+ long ParseBlockGroup(long long, long long&, long&);
+
+ long CreateBlock(long long id, long long pos, long long size,
+ long long discard_padding);
+ long CreateBlockGroup(long long start_offset, long long size,
+ long long discard_padding);
+ long CreateSimpleBlock(long long, long long);
+};
+
+class Segment {
+ friend class Cues;
+ friend class Track;
+ friend class VideoTrack;
+
+ Segment(const Segment&);
+ Segment& operator=(const Segment&);
+
+ private:
+ Segment(IMkvReader*, long long elem_start,
+ // long long elem_size,
+ long long pos, long long size);
+
+ public:
+ IMkvReader* const m_pReader;
+ const long long m_element_start;
+ // const long long m_element_size;
+ const long long m_start; // posn of segment payload
+ const long long m_size; // size of segment payload
+ Cluster m_eos; // TODO: make private?
+
+ static long long CreateInstance(IMkvReader*, long long, Segment*&);
+ ~Segment();
+
+ long Load(); // loads headers and all clusters
+
+ // for incremental loading
+ // long long Unparsed() const;
+ bool DoneParsing() const;
+ long long ParseHeaders(); // stops when first cluster is found
+ // long FindNextCluster(long long& pos, long& size) const;
+ long LoadCluster(long long& pos, long& size); // load one cluster
+ long LoadCluster();
+
+ long ParseNext(const Cluster* pCurr, const Cluster*& pNext, long long& pos,
+ long& size);
+
+ const SeekHead* GetSeekHead() const;
+ const Tracks* GetTracks() const;
+ const SegmentInfo* GetInfo() const;
+ const Cues* GetCues() const;
+ const Chapters* GetChapters() const;
+ const Tags* GetTags() const;
+
+ long long GetDuration() const;
+
+ unsigned long GetCount() const;
+ const Cluster* GetFirst() const;
+ const Cluster* GetLast() const;
+ const Cluster* GetNext(const Cluster*);
+
+ const Cluster* FindCluster(long long time_nanoseconds) const;
+ // const BlockEntry* Seek(long long time_nanoseconds, const Track*) const;
+
+ const Cluster* FindOrPreloadCluster(long long pos);
+
+ long ParseCues(long long cues_off, // offset relative to start of segment
+ long long& parse_pos, long& parse_len);
+
+ private:
+ long long m_pos; // absolute file posn; what has been consumed so far
+ Cluster* m_pUnknownSize;
+
+ SeekHead* m_pSeekHead;
+ SegmentInfo* m_pInfo;
+ Tracks* m_pTracks;
+ Cues* m_pCues;
+ Chapters* m_pChapters;
+ Tags* m_pTags;
+ Cluster** m_clusters;
+ long m_clusterCount; // number of entries for which m_index >= 0
+ long m_clusterPreloadCount; // number of entries for which m_index < 0
+ long m_clusterSize; // array size
+
+ long DoLoadCluster(long long&, long&);
+ long DoLoadClusterUnknownSize(long long&, long&);
+ long DoParseNext(const Cluster*&, long long&, long&);
+
+ bool AppendCluster(Cluster*);
+ bool PreloadCluster(Cluster*, ptrdiff_t);
+
+ // void ParseSeekHead(long long pos, long long size);
+ // void ParseSeekEntry(long long pos, long long size);
+ // void ParseCues(long long);
+
+ const BlockEntry* GetBlock(const CuePoint&, const CuePoint::TrackPosition&);
+};
+
+} // namespace mkvparser
+
+inline long mkvparser::Segment::LoadCluster() {
+ long long pos;
+ long size;
+
+ return LoadCluster(pos, size);
+}
+
+#endif // MKVPARSER_MKVPARSER_H_
diff --git a/mkvparser/mkvreader.cc b/mkvparser/mkvreader.cc
new file mode 100644
index 0000000..9d19c1b
--- /dev/null
+++ b/mkvparser/mkvreader.cc
@@ -0,0 +1,135 @@
+// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "mkvparser/mkvreader.h"
+
+#include <sys/types.h>
+
+#include <cassert>
+
+namespace mkvparser {
+
+MkvReader::MkvReader() : m_file(NULL), reader_owns_file_(true) {}
+
+MkvReader::MkvReader(FILE* fp) : m_file(fp), reader_owns_file_(false) {
+ GetFileSize();
+}
+
+MkvReader::~MkvReader() {
+ if (reader_owns_file_)
+ Close();
+ m_file = NULL;
+}
+
+int MkvReader::Open(const char* fileName) {
+ if (fileName == NULL)
+ return -1;
+
+ if (m_file)
+ return -1;
+
+#ifdef _MSC_VER
+ const errno_t e = fopen_s(&m_file, fileName, "rb");
+
+ if (e)
+ return -1; // error
+#else
+ m_file = fopen(fileName, "rb");
+
+ if (m_file == NULL)
+ return -1;
+#endif
+ return !GetFileSize();
+}
+
+bool MkvReader::GetFileSize() {
+ if (m_file == NULL)
+ return false;
+#ifdef _MSC_VER
+ int status = _fseeki64(m_file, 0L, SEEK_END);
+
+ if (status)
+ return false; // error
+
+ m_length = _ftelli64(m_file);
+#else
+ fseek(m_file, 0L, SEEK_END);
+ m_length = ftell(m_file);
+#endif
+ assert(m_length >= 0);
+
+ if (m_length < 0)
+ return false;
+
+#ifdef _MSC_VER
+ status = _fseeki64(m_file, 0L, SEEK_SET);
+
+ if (status)
+ return false; // error
+#else
+ fseek(m_file, 0L, SEEK_SET);
+#endif
+
+ return true;
+}
+
+void MkvReader::Close() {
+ if (m_file != NULL) {
+ fclose(m_file);
+ m_file = NULL;
+ }
+}
+
+int MkvReader::Length(long long* total, long long* available) {
+ if (m_file == NULL)
+ return -1;
+
+ if (total)
+ *total = m_length;
+
+ if (available)
+ *available = m_length;
+
+ return 0;
+}
+
+int MkvReader::Read(long long offset, long len, unsigned char* buffer) {
+ if (m_file == NULL)
+ return -1;
+
+ if (offset < 0)
+ return -1;
+
+ if (len < 0)
+ return -1;
+
+ if (len == 0)
+ return 0;
+
+ if (offset >= m_length)
+ return -1;
+
+#ifdef _MSC_VER
+ const int status = _fseeki64(m_file, offset, SEEK_SET);
+
+ if (status)
+ return -1; // error
+#elif defined(_WIN32)
+ fseeko64(m_file, static_cast<off_t>(offset), SEEK_SET);
+#else
+ fseeko(m_file, static_cast<off_t>(offset), SEEK_SET);
+#endif
+
+ const size_t size = fread(buffer, 1, len, m_file);
+
+ if (size < size_t(len))
+ return -1; // error
+
+ return 0; // success
+}
+
+} // namespace mkvparser
diff --git a/mkvparser/mkvreader.h b/mkvparser/mkvreader.h
new file mode 100644
index 0000000..9831ecf
--- /dev/null
+++ b/mkvparser/mkvreader.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef MKVPARSER_MKVREADER_H_
+#define MKVPARSER_MKVREADER_H_
+
+#include <cstdio>
+
+#include "mkvparser/mkvparser.h"
+
+namespace mkvparser {
+
+class MkvReader : public IMkvReader {
+ public:
+ MkvReader();
+ explicit MkvReader(FILE* fp);
+ virtual ~MkvReader();
+
+ int Open(const char*);
+ void Close();
+
+ virtual int Read(long long position, long length, unsigned char* buffer);
+ virtual int Length(long long* total, long long* available);
+
+ private:
+ MkvReader(const MkvReader&);
+ MkvReader& operator=(const MkvReader&);
+
+ // Determines the size of the file. This is called either by the constructor
+ // or by the Open function depending on file ownership. Returns true on
+ // success.
+ bool GetFileSize();
+
+ long long m_length;
+ FILE* m_file;
+ bool reader_owns_file_;
+};
+
+} // namespace mkvparser
+
+#endif // MKVPARSER_MKVREADER_H_
diff --git a/mkvparser_sample.cc b/mkvparser_sample.cc
new file mode 100644
index 0000000..f285c3e
--- /dev/null
+++ b/mkvparser_sample.cc
@@ -0,0 +1,459 @@
+// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+//
+// This sample application demonstrates how to use the Matroska parser
+// library, which allows clients to handle a Matroska format file.
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+#include <new>
+
+#include "mkvparser/mkvparser.h"
+#include "mkvparser/mkvreader.h"
+
+namespace {
+const wchar_t* utf8towcs(const char* str) {
+ if (str == NULL)
+ return NULL;
+
+ // TODO: this probably requires that the locale be
+ // configured somehow:
+
+ const size_t size = mbstowcs(NULL, str, 0);
+
+ if (size == 0 || size == static_cast<size_t>(-1))
+ return NULL;
+
+ wchar_t* const val = new (std::nothrow) wchar_t[size + 1];
+ if (val == NULL)
+ return NULL;
+
+ mbstowcs(val, str, size);
+ val[size] = L'\0';
+
+ return val;
+}
+
+bool InputHasCues(const mkvparser::Segment* const segment) {
+ const mkvparser::Cues* const cues = segment->GetCues();
+ if (cues == NULL)
+ return false;
+
+ while (!cues->DoneParsing())
+ cues->LoadCuePoint();
+
+ const mkvparser::CuePoint* const cue_point = cues->GetFirst();
+ if (cue_point == NULL)
+ return false;
+
+ return true;
+}
+
+bool MasteringMetadataValuePresent(double value) {
+ return value != mkvparser::MasteringMetadata::kValueNotPresent;
+}
+
+bool ColourValuePresent(long long value) {
+ return value != mkvparser::Colour::kValueNotPresent;
+}
+} // namespace
+
+int main(int argc, char* argv[]) {
+ if (argc == 1) {
+ printf("Mkv Parser Sample Application\n");
+ printf(" Usage: %s <input file> \n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ mkvparser::MkvReader reader;
+
+ if (reader.Open(argv[1])) {
+ printf("\n Filename is invalid or error while opening.\n");
+ return EXIT_FAILURE;
+ }
+
+ int maj, min, build, rev;
+
+ mkvparser::GetVersion(maj, min, build, rev);
+ printf("\t\t libwebm version: %d.%d.%d.%d\n", maj, min, build, rev);
+
+ long long pos = 0;
+
+ mkvparser::EBMLHeader ebmlHeader;
+
+ long long ret = ebmlHeader.Parse(&reader, pos);
+ if (ret < 0) {
+ printf("\n EBMLHeader::Parse() failed.");
+ return EXIT_FAILURE;
+ }
+
+ printf("\t\t\t EBML Header\n");
+ printf("\t\tEBML Version\t\t: %lld\n", ebmlHeader.m_version);
+ printf("\t\tEBML MaxIDLength\t: %lld\n", ebmlHeader.m_maxIdLength);
+ printf("\t\tEBML MaxSizeLength\t: %lld\n", ebmlHeader.m_maxSizeLength);
+ printf("\t\tDoc Type\t\t: %s\n", ebmlHeader.m_docType);
+ printf("\t\tPos\t\t\t: %lld\n", pos);
+
+ typedef mkvparser::Segment seg_t;
+ seg_t* pSegment_;
+
+ ret = seg_t::CreateInstance(&reader, pos, pSegment_);
+ if (ret) {
+ printf("\n Segment::CreateInstance() failed.");
+ return EXIT_FAILURE;
+ }
+
+ const std::unique_ptr<seg_t> pSegment(pSegment_);
+
+ ret = pSegment->Load();
+ if (ret < 0) {
+ printf("\n Segment::Load() failed.");
+ return EXIT_FAILURE;
+ }
+
+ const mkvparser::SegmentInfo* const pSegmentInfo = pSegment->GetInfo();
+ if (pSegmentInfo == NULL) {
+ printf("\n Segment::GetInfo() failed.");
+ return EXIT_FAILURE;
+ }
+
+ const long long timeCodeScale = pSegmentInfo->GetTimeCodeScale();
+ const long long duration_ns = pSegmentInfo->GetDuration();
+
+ const char* const pTitle_ = pSegmentInfo->GetTitleAsUTF8();
+ const wchar_t* const pTitle = utf8towcs(pTitle_);
+
+ const char* const pMuxingApp_ = pSegmentInfo->GetMuxingAppAsUTF8();
+ const wchar_t* const pMuxingApp = utf8towcs(pMuxingApp_);
+
+ const char* const pWritingApp_ = pSegmentInfo->GetWritingAppAsUTF8();
+ const wchar_t* const pWritingApp = utf8towcs(pWritingApp_);
+
+ printf("\n");
+ printf("\t\t\t Segment Info\n");
+ printf("\t\tTimeCodeScale\t\t: %lld \n", timeCodeScale);
+ printf("\t\tDuration\t\t: %lld\n", duration_ns);
+
+ const double duration_sec = double(duration_ns) / 1000000000;
+ printf("\t\tDuration(secs)\t\t: %7.3lf\n", duration_sec);
+
+ if (pTitle == NULL)
+ printf("\t\tTrack Name\t\t: NULL\n");
+ else {
+ printf("\t\tTrack Name\t\t: %ls\n", pTitle);
+ delete[] pTitle;
+ }
+
+ if (pMuxingApp == NULL)
+ printf("\t\tMuxing App\t\t: NULL\n");
+ else {
+ printf("\t\tMuxing App\t\t: %ls\n", pMuxingApp);
+ delete[] pMuxingApp;
+ }
+
+ if (pWritingApp == NULL)
+ printf("\t\tWriting App\t\t: NULL\n");
+ else {
+ printf("\t\tWriting App\t\t: %ls\n", pWritingApp);
+ delete[] pWritingApp;
+ }
+
+ // pos of segment payload
+ printf("\t\tPosition(Segment)\t: %lld\n", pSegment->m_start);
+
+ // size of segment payload
+ printf("\t\tSize(Segment)\t\t: %lld\n", pSegment->m_size);
+
+ const mkvparser::Tracks* pTracks = pSegment->GetTracks();
+
+ unsigned long track_num = 0;
+ const unsigned long num_tracks = pTracks->GetTracksCount();
+
+ printf("\n\t\t\t Track Info\n");
+
+ while (track_num != num_tracks) {
+ const mkvparser::Track* const pTrack =
+ pTracks->GetTrackByIndex(track_num++);
+
+ if (pTrack == NULL)
+ continue;
+
+ const long trackType = pTrack->GetType();
+ const long trackNumber = pTrack->GetNumber();
+ const unsigned long long trackUid = pTrack->GetUid();
+ const wchar_t* const pTrackName = utf8towcs(pTrack->GetNameAsUTF8());
+
+ printf("\t\tTrack Type\t\t: %ld\n", trackType);
+ printf("\t\tTrack Number\t\t: %ld\n", trackNumber);
+ printf("\t\tTrack Uid\t\t: %lld\n", trackUid);
+
+ if (pTrackName == NULL)
+ printf("\t\tTrack Name\t\t: NULL\n");
+ else {
+ printf("\t\tTrack Name\t\t: %ls \n", pTrackName);
+ delete[] pTrackName;
+ }
+
+ const char* const pCodecId = pTrack->GetCodecId();
+
+ if (pCodecId == NULL)
+ printf("\t\tCodec Id\t\t: NULL\n");
+ else
+ printf("\t\tCodec Id\t\t: %s\n", pCodecId);
+
+ size_t codec_private_size = 0;
+ if (pTrack->GetCodecPrivate(codec_private_size)) {
+ printf("\t\tCodec private length: %u bytes\n",
+ static_cast<unsigned int>(codec_private_size));
+ }
+
+ const char* const pCodecName_ = pTrack->GetCodecNameAsUTF8();
+ const wchar_t* const pCodecName = utf8towcs(pCodecName_);
+
+ if (pCodecName == NULL)
+ printf("\t\tCodec Name\t\t: NULL\n");
+ else {
+ printf("\t\tCodec Name\t\t: %ls\n", pCodecName);
+ delete[] pCodecName;
+ }
+
+ if (trackType == mkvparser::Track::kVideo) {
+ const mkvparser::VideoTrack* const pVideoTrack =
+ static_cast<const mkvparser::VideoTrack*>(pTrack);
+
+ const long long width = pVideoTrack->GetWidth();
+ printf("\t\tVideo Width\t\t: %lld\n", width);
+
+ const long long height = pVideoTrack->GetHeight();
+ printf("\t\tVideo Height\t\t: %lld\n", height);
+
+ const double rate = pVideoTrack->GetFrameRate();
+ printf("\t\tVideo Rate\t\t: %f\n", rate);
+
+ const mkvparser::Colour* const colour = pVideoTrack->GetColour();
+ if (colour) {
+ printf("\t\tVideo Colour:\n");
+ if (ColourValuePresent(colour->matrix_coefficients))
+ printf("\t\t\tMatrixCoefficients: %lld\n",
+ colour->matrix_coefficients);
+ if (ColourValuePresent(colour->bits_per_channel))
+ printf("\t\t\tBitsPerChannel: %lld\n", colour->bits_per_channel);
+ if (ColourValuePresent(colour->chroma_subsampling_horz))
+ printf("\t\t\tChromaSubsamplingHorz: %lld\n",
+ colour->chroma_subsampling_horz);
+ if (ColourValuePresent(colour->chroma_subsampling_vert))
+ printf("\t\t\tChromaSubsamplingVert: %lld\n",
+ colour->chroma_subsampling_vert);
+ if (ColourValuePresent(colour->cb_subsampling_horz))
+ printf("\t\t\tCbSubsamplingHorz: %lld\n",
+ colour->cb_subsampling_horz);
+ if (ColourValuePresent(colour->cb_subsampling_vert))
+ printf("\t\t\tCbSubsamplingVert: %lld\n",
+ colour->cb_subsampling_vert);
+ if (ColourValuePresent(colour->chroma_siting_horz))
+ printf("\t\t\tChromaSitingHorz: %lld\n", colour->chroma_siting_horz);
+ if (ColourValuePresent(colour->chroma_siting_vert))
+ printf("\t\t\tChromaSitingVert: %lld\n", colour->chroma_siting_vert);
+ if (ColourValuePresent(colour->range))
+ printf("\t\t\tRange: %lld\n", colour->range);
+ if (ColourValuePresent(colour->transfer_characteristics))
+ printf("\t\t\tTransferCharacteristics: %lld\n",
+ colour->transfer_characteristics);
+ if (ColourValuePresent(colour->primaries))
+ printf("\t\t\tPrimaries: %lld\n", colour->primaries);
+ if (ColourValuePresent(colour->max_cll))
+ printf("\t\t\tMaxCLL: %lld\n", colour->max_cll);
+ if (ColourValuePresent(colour->max_fall))
+ printf("\t\t\tMaxFALL: %lld\n", colour->max_fall);
+ if (colour->mastering_metadata) {
+ const mkvparser::MasteringMetadata* const mm =
+ colour->mastering_metadata;
+ printf("\t\t\tMastering Metadata:\n");
+ if (MasteringMetadataValuePresent(mm->luminance_max))
+ printf("\t\t\t\tLuminanceMax: %f\n", mm->luminance_max);
+ if (MasteringMetadataValuePresent(mm->luminance_min))
+ printf("\t\t\t\tLuminanceMin: %f\n", mm->luminance_min);
+ if (mm->r) {
+ printf("\t\t\t\t\tPrimaryRChromaticityX: %f\n", mm->r->x);
+ printf("\t\t\t\t\tPrimaryRChromaticityY: %f\n", mm->r->y);
+ }
+ if (mm->g) {
+ printf("\t\t\t\t\tPrimaryGChromaticityX: %f\n", mm->g->x);
+ printf("\t\t\t\t\tPrimaryGChromaticityY: %f\n", mm->g->y);
+ }
+ if (mm->b) {
+ printf("\t\t\t\t\tPrimaryBChromaticityX: %f\n", mm->b->x);
+ printf("\t\t\t\t\tPrimaryBChromaticityY: %f\n", mm->b->y);
+ }
+ if (mm->white_point) {
+ printf("\t\t\t\t\tWhitePointChromaticityX: %f\n",
+ mm->white_point->x);
+ printf("\t\t\t\t\tWhitePointChromaticityY: %f\n",
+ mm->white_point->y);
+ }
+ }
+ }
+
+ const mkvparser::Projection* const projection =
+ pVideoTrack->GetProjection();
+ if (projection) {
+ printf("\t\tVideo Projection:\n");
+ if (projection->type != mkvparser::Projection::kTypeNotPresent)
+ printf("\t\t\tProjectionType: %d\n",
+ static_cast<int>(projection->type));
+ if (projection->private_data) {
+ printf("\t\t\tProjectionPrivate: %u bytes\n",
+ static_cast<unsigned int>(projection->private_data_length));
+ }
+ if (projection->pose_yaw != mkvparser::Projection::kValueNotPresent)
+ printf("\t\t\tProjectionPoseYaw: %f\n", projection->pose_yaw);
+ if (projection->pose_pitch != mkvparser::Projection::kValueNotPresent)
+ printf("\t\t\tProjectionPosePitch: %f\n", projection->pose_pitch);
+ if (projection->pose_roll != mkvparser::Projection::kValueNotPresent)
+ printf("\t\t\tProjectionPosePitch: %f\n", projection->pose_roll);
+ }
+ }
+
+ if (trackType == mkvparser::Track::kAudio) {
+ const mkvparser::AudioTrack* const pAudioTrack =
+ static_cast<const mkvparser::AudioTrack*>(pTrack);
+
+ const long long channels = pAudioTrack->GetChannels();
+ printf("\t\tAudio Channels\t\t: %lld\n", channels);
+
+ const long long bitDepth = pAudioTrack->GetBitDepth();
+ printf("\t\tAudio BitDepth\t\t: %lld\n", bitDepth);
+
+ const double sampleRate = pAudioTrack->GetSamplingRate();
+ printf("\t\tAddio Sample Rate\t: %.3f\n", sampleRate);
+
+ const long long codecDelay = pAudioTrack->GetCodecDelay();
+ printf("\t\tAudio Codec Delay\t\t: %lld\n", codecDelay);
+
+ const long long seekPreRoll = pAudioTrack->GetSeekPreRoll();
+ printf("\t\tAudio Seek Pre Roll\t\t: %lld\n", seekPreRoll);
+ }
+ }
+
+ printf("\n\n\t\t\t Cluster Info\n");
+ const unsigned long clusterCount = pSegment->GetCount();
+
+ printf("\t\tCluster Count\t: %ld\n\n", clusterCount);
+
+ if (clusterCount == 0) {
+ printf("\t\tSegment has no clusters.\n");
+ return EXIT_FAILURE;
+ }
+
+ const mkvparser::Cluster* pCluster = pSegment->GetFirst();
+
+ while (pCluster != NULL && !pCluster->EOS()) {
+ const long long timeCode = pCluster->GetTimeCode();
+ printf("\t\tCluster Time Code\t: %lld\n", timeCode);
+
+ const long long time_ns = pCluster->GetTime();
+ printf("\t\tCluster Time (ns)\t: %lld\n", time_ns);
+
+ const mkvparser::BlockEntry* pBlockEntry;
+
+ long status = pCluster->GetFirst(pBlockEntry);
+
+ if (status < 0) // error
+ {
+ printf("\t\tError parsing first block of cluster\n");
+ fflush(stdout);
+ return EXIT_FAILURE;
+ }
+
+ while (pBlockEntry != NULL && !pBlockEntry->EOS()) {
+ const mkvparser::Block* const pBlock = pBlockEntry->GetBlock();
+ const long long trackNum = pBlock->GetTrackNumber();
+ const unsigned long tn = static_cast<unsigned long>(trackNum);
+ const mkvparser::Track* const pTrack = pTracks->GetTrackByNumber(tn);
+
+ if (pTrack == NULL)
+ printf("\t\t\tBlock\t\t:UNKNOWN TRACK TYPE\n");
+ else {
+ const long long trackType = pTrack->GetType();
+ const int frameCount = pBlock->GetFrameCount();
+ const long long time_ns = pBlock->GetTime(pCluster);
+ const long long discard_padding = pBlock->GetDiscardPadding();
+
+ printf("\t\t\tBlock\t\t:%s,%s,%15lld,%lld\n",
+ (trackType == mkvparser::Track::kVideo) ? "V" : "A",
+ pBlock->IsKey() ? "I" : "P", time_ns, discard_padding);
+
+ for (int i = 0; i < frameCount; ++i) {
+ const mkvparser::Block::Frame& theFrame = pBlock->GetFrame(i);
+ const long size = theFrame.len;
+ const long long offset = theFrame.pos;
+ printf("\t\t\t %15ld,%15llx\n", size, offset);
+ }
+ }
+
+ status = pCluster->GetNext(pBlockEntry, pBlockEntry);
+
+ if (status < 0) {
+ printf("\t\t\tError parsing next block of cluster\n");
+ fflush(stdout);
+ return EXIT_FAILURE;
+ }
+ }
+
+ pCluster = pSegment->GetNext(pCluster);
+ }
+
+ if (InputHasCues(pSegment.get())) {
+ // Walk them.
+ const mkvparser::Cues* const cues = pSegment->GetCues();
+ const mkvparser::CuePoint* cue = cues->GetFirst();
+ int cue_point_num = 1;
+
+ printf("\t\tCues\n");
+ do {
+ for (track_num = 0; track_num < num_tracks; ++track_num) {
+ const mkvparser::Track* const track =
+ pTracks->GetTrackByIndex(track_num);
+ const mkvparser::CuePoint::TrackPosition* const track_pos =
+ cue->Find(track);
+
+ if (track_pos != NULL) {
+ const char track_type =
+ (track->GetType() == mkvparser::Track::kVideo) ? 'V' : 'A';
+ printf(
+ "\t\t\tCue Point %4d Track %3lu(%c) Time %14lld "
+ "Block %4lld Pos %8llx\n",
+ cue_point_num, track->GetNumber(), track_type,
+ cue->GetTime(pSegment.get()), track_pos->m_block,
+ track_pos->m_pos);
+ }
+ }
+
+ cue = cues->GetNext(cue);
+ ++cue_point_num;
+ } while (cue != NULL);
+ }
+
+ const mkvparser::Tags* const tags = pSegment->GetTags();
+ if (tags && tags->GetTagCount() > 0) {
+ printf("\t\tTags\n");
+ for (int i = 0; i < tags->GetTagCount(); ++i) {
+ const mkvparser::Tags::Tag* const tag = tags->GetTag(i);
+ printf("\t\t\tTag\n");
+ for (int j = 0; j < tag->GetSimpleTagCount(); j++) {
+ const mkvparser::Tags::SimpleTag* const simple_tag =
+ tag->GetSimpleTag(j);
+ printf("\t\t\t\tSimple Tag \"%s\" Value \"%s\"\n",
+ simple_tag->GetTagName(), simple_tag->GetTagString());
+ }
+ }
+ }
+
+ fflush(stdout);
+ return EXIT_SUCCESS;
+}
diff --git a/mkvreader.hpp b/mkvreader.hpp
new file mode 100644
index 0000000..d115872
--- /dev/null
+++ b/mkvreader.hpp
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_MKVREADER_HPP_
+#define LIBWEBM_MKVREADER_HPP_
+
+// This file is a wrapper for the file included immediately after this comment.
+// New projects should not include this file: include the file included below.
+#include "mkvparser/mkvreader.h"
+
+#endif // LIBWEBM_MKVREADER_HPP_
diff --git a/mkvwriter.hpp b/mkvwriter.hpp
new file mode 100644
index 0000000..9927b3e
--- /dev/null
+++ b/mkvwriter.hpp
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_MKVWRITER_HPP_
+#define LIBWEBM_MKVWRITER_HPP_
+
+// This file is a wrapper for the file included immediately after this comment.
+// New projects should not include this file: include the file included below.
+#include "mkvmuxer/mkvwriter.h"
+
+#endif // LIBWEBM_MKVWRITER_HPP_
diff --git a/sample_muxer_metadata.cc b/sample_muxer_metadata.cc
new file mode 100644
index 0000000..6eadcfd
--- /dev/null
+++ b/sample_muxer_metadata.cc
@@ -0,0 +1,391 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+//
+// This sample application demonstrates how to use the matroska parser
+// library, which allows clients to handle a matroska format file.
+
+#include "sample_muxer_metadata.h"
+
+#include <cstdio>
+#include <string>
+
+#include "mkvmuxer/mkvmuxer.h"
+#include "webvtt/vttreader.h"
+
+SampleMuxerMetadata::SampleMuxerMetadata() : segment_(NULL) {}
+
+bool SampleMuxerMetadata::Init(mkvmuxer::Segment* segment) {
+ if (segment == NULL || segment_ != NULL)
+ return false;
+
+ segment_ = segment;
+ return true;
+}
+
+bool SampleMuxerMetadata::Load(const char* file, Kind kind) {
+ if (kind == kChapters)
+ return LoadChapters(file);
+
+ uint64_t track_num;
+
+ if (!AddTrack(kind, &track_num)) {
+ printf("Unable to add track for WebVTT file \"%s\"\n", file);
+ return false;
+ }
+
+ return Parse(file, kind, track_num);
+}
+
+bool SampleMuxerMetadata::AddChapters() {
+ typedef cue_list_t::const_iterator iter_t;
+ iter_t i = chapter_cues_.begin();
+ const iter_t j = chapter_cues_.end();
+
+ while (i != j) {
+ const cue_t& chapter = *i++;
+
+ if (!AddChapter(chapter))
+ return false;
+ }
+
+ return true;
+}
+
+bool SampleMuxerMetadata::Write(int64_t time_ns) {
+ typedef cues_set_t::iterator iter_t;
+
+ iter_t i = cues_set_.begin();
+ const iter_t j = cues_set_.end();
+
+ while (i != j) {
+ const cues_set_t::value_type& v = *i;
+
+ if (time_ns >= 0 && v > time_ns)
+ return true; // nothing else to do just yet
+
+ if (!v.Write(segment_)) {
+ printf("\nCould not add metadata.\n");
+ return false; // error
+ }
+
+ cues_set_.erase(i++);
+ }
+
+ return true;
+}
+
+bool SampleMuxerMetadata::LoadChapters(const char* file) {
+ if (!chapter_cues_.empty()) {
+ printf("Support for more than one chapters file is not yet implemented\n");
+ return false;
+ }
+
+ cue_list_t cues;
+
+ if (!ParseChapters(file, &cues))
+ return false;
+
+ // TODO(matthewjheaney): support more than one chapters file
+ chapter_cues_.swap(cues);
+
+ return true;
+}
+
+bool SampleMuxerMetadata::ParseChapters(const char* file,
+ cue_list_t* cues_ptr) {
+ cue_list_t& cues = *cues_ptr;
+ cues.clear();
+
+ libwebvtt::VttReader r;
+ int e = r.Open(file);
+
+ if (e) {
+ printf("Unable to open WebVTT file: \"%s\"\n", file);
+ return false;
+ }
+
+ libwebvtt::Parser p(&r);
+ e = p.Init();
+
+ if (e < 0) { // error
+ printf("Error parsing WebVTT file: \"%s\"\n", file);
+ return false;
+ }
+
+ libwebvtt::Time t;
+ t.hours = -1;
+
+ for (;;) {
+ cue_t c;
+ e = p.Parse(&c);
+
+ if (e < 0) { // error
+ printf("Error parsing WebVTT file: \"%s\"\n", file);
+ return false;
+ }
+
+ if (e > 0) // EOF
+ return true;
+
+ if (c.start_time < t) {
+ printf("bad WebVTT cue timestamp (out-of-order)\n");
+ return false;
+ }
+
+ if (c.stop_time < c.start_time) {
+ printf("bad WebVTT cue timestamp (stop < start)\n");
+ return false;
+ }
+
+ t = c.start_time;
+ cues.push_back(c);
+ }
+}
+
+bool SampleMuxerMetadata::AddChapter(const cue_t& cue) {
+ // TODO(matthewjheaney): support language and country
+
+ mkvmuxer::Chapter* const chapter = segment_->AddChapter();
+
+ if (chapter == NULL) {
+ printf("Unable to add chapter\n");
+ return false;
+ }
+
+ if (cue.identifier.empty()) {
+ chapter->set_id(NULL);
+ } else {
+ const char* const id = cue.identifier.c_str();
+ if (!chapter->set_id(id)) {
+ printf("Unable to set chapter id\n");
+ return false;
+ }
+ }
+
+ typedef libwebvtt::presentation_t time_ms_t;
+ const time_ms_t start_time_ms = cue.start_time.presentation();
+ const time_ms_t stop_time_ms = cue.stop_time.presentation();
+
+ enum { kNsPerMs = 1000000 };
+ const uint64_t start_time_ns = start_time_ms * kNsPerMs;
+ const uint64_t stop_time_ns = stop_time_ms * kNsPerMs;
+
+ chapter->set_time(*segment_, start_time_ns, stop_time_ns);
+
+ typedef libwebvtt::Cue::payload_t::const_iterator iter_t;
+ iter_t i = cue.payload.begin();
+ const iter_t j = cue.payload.end();
+
+ std::string title;
+
+ for (;;) {
+ title += *i++;
+
+ if (i == j)
+ break;
+
+ enum { kLF = '\x0A' };
+ title += kLF;
+ }
+
+ if (!chapter->add_string(title.c_str(), NULL, NULL)) {
+ printf("Unable to set chapter title\n");
+ return false;
+ }
+
+ return true;
+}
+
+bool SampleMuxerMetadata::AddTrack(Kind kind, uint64_t* track_num) {
+ *track_num = 0;
+
+ // Track number value 0 means "let muxer choose track number"
+ mkvmuxer::Track* const track = segment_->AddTrack(0);
+
+ if (track == NULL) // error
+ return false;
+
+ // Return the track number value chosen by the muxer
+ *track_num = track->number();
+
+ int type;
+ const char* codec_id;
+
+ switch (kind) {
+ case kSubtitles:
+ type = 0x11;
+ codec_id = "D_WEBVTT/SUBTITLES";
+ break;
+
+ case kCaptions:
+ type = 0x11;
+ codec_id = "D_WEBVTT/CAPTIONS";
+ break;
+
+ case kDescriptions:
+ type = 0x21;
+ codec_id = "D_WEBVTT/DESCRIPTIONS";
+ break;
+
+ case kMetadata:
+ type = 0x21;
+ codec_id = "D_WEBVTT/METADATA";
+ break;
+
+ default:
+ return false;
+ }
+
+ track->set_type(type);
+ track->set_codec_id(codec_id);
+
+ // TODO(matthewjheaney): set name and language
+
+ return true;
+}
+
+bool SampleMuxerMetadata::Parse(const char* file, Kind /* kind */,
+ uint64_t track_num) {
+ libwebvtt::VttReader r;
+ int e = r.Open(file);
+
+ if (e) {
+ printf("Unable to open WebVTT file: \"%s\"\n", file);
+ return false;
+ }
+
+ libwebvtt::Parser p(&r);
+
+ e = p.Init();
+
+ if (e < 0) { // error
+ printf("Error parsing WebVTT file: \"%s\"\n", file);
+ return false;
+ }
+
+ SortableCue cue;
+ cue.track_num = track_num;
+
+ libwebvtt::Time t;
+ t.hours = -1;
+
+ for (;;) {
+ cue_t& c = cue.cue;
+ e = p.Parse(&c);
+
+ if (e < 0) { // error
+ printf("Error parsing WebVTT file: \"%s\"\n", file);
+ return false;
+ }
+
+ if (e > 0) // EOF
+ return true;
+
+ if (c.start_time >= t) {
+ t = c.start_time;
+ } else {
+ printf("bad WebVTT cue timestamp (out-of-order)\n");
+ return false;
+ }
+
+ if (c.stop_time < c.start_time) {
+ printf("bad WebVTT cue timestamp (stop < start)\n");
+ return false;
+ }
+
+ cues_set_.insert(cue);
+ }
+}
+
+void SampleMuxerMetadata::MakeFrame(const cue_t& c, std::string* pf) {
+ pf->clear();
+ WriteCueIdentifier(c.identifier, pf);
+ WriteCueSettings(c.settings, pf);
+ WriteCuePayload(c.payload, pf);
+}
+
+void SampleMuxerMetadata::WriteCueIdentifier(const std::string& identifier,
+ std::string* pf) {
+ pf->append(identifier);
+ pf->push_back('\x0A'); // LF
+}
+
+void SampleMuxerMetadata::WriteCueSettings(const cue_t::settings_t& settings,
+ std::string* pf) {
+ if (settings.empty()) {
+ pf->push_back('\x0A'); // LF
+ return;
+ }
+
+ typedef cue_t::settings_t::const_iterator iter_t;
+
+ iter_t i = settings.begin();
+ const iter_t j = settings.end();
+
+ for (;;) {
+ const libwebvtt::Setting& setting = *i++;
+
+ pf->append(setting.name);
+ pf->push_back(':');
+ pf->append(setting.value);
+
+ if (i == j)
+ break;
+
+ pf->push_back(' '); // separate settings with whitespace
+ }
+
+ pf->push_back('\x0A'); // LF
+}
+
+void SampleMuxerMetadata::WriteCuePayload(const cue_t::payload_t& payload,
+ std::string* pf) {
+ typedef cue_t::payload_t::const_iterator iter_t;
+
+ iter_t i = payload.begin();
+ const iter_t j = payload.end();
+
+ while (i != j) {
+ const std::string& line = *i++;
+ pf->append(line);
+ pf->push_back('\x0A'); // LF
+ }
+}
+
+bool SampleMuxerMetadata::SortableCue::Write(mkvmuxer::Segment* segment) const {
+ // Cue start time expressed in milliseconds
+ const int64_t start_ms = cue.start_time.presentation();
+
+ // Cue start time expressed in nanoseconds (MKV time)
+ const int64_t start_ns = start_ms * 1000000;
+
+ // Cue stop time expressed in milliseconds
+ const int64_t stop_ms = cue.stop_time.presentation();
+
+ // Cue stop time expressed in nanonseconds
+ const int64_t stop_ns = stop_ms * 1000000;
+
+ // Metadata blocks always specify the block duration.
+ const int64_t duration_ns = stop_ns - start_ns;
+
+ std::string frame;
+ MakeFrame(cue, &frame);
+
+ typedef const uint8_t* data_t;
+ const data_t buf = reinterpret_cast<data_t>(frame.data());
+ const uint64_t len = frame.length();
+
+ mkvmuxer::Frame muxer_frame;
+ if (!muxer_frame.Init(buf, len))
+ return 0;
+ muxer_frame.set_track_number(track_num);
+ muxer_frame.set_timestamp(start_ns);
+ muxer_frame.set_duration(duration_ns);
+ muxer_frame.set_is_key(true); // All metadata frames are keyframes.
+ return segment->AddGenericFrame(&muxer_frame);
+} \ No newline at end of file
diff --git a/sample_muxer_metadata.h b/sample_muxer_metadata.h
new file mode 100644
index 0000000..d76ccf5
--- /dev/null
+++ b/sample_muxer_metadata.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef SAMPLE_MUXER_METADATA_H_ // NOLINT
+#define SAMPLE_MUXER_METADATA_H_
+
+#include <stdint.h>
+
+#include <list>
+#include <set>
+#include <string>
+
+#include "webvtt/webvttparser.h"
+
+namespace mkvmuxer {
+class Chapter;
+class Frame;
+class Segment;
+class Track;
+} // namespace mkvmuxer
+
+class SampleMuxerMetadata {
+ public:
+ enum Kind { kSubtitles, kCaptions, kDescriptions, kMetadata, kChapters };
+
+ SampleMuxerMetadata();
+
+ // Bind this metadata object to the muxer instance. Returns false
+ // if segment equals NULL, or Init has already been called.
+ bool Init(mkvmuxer::Segment* segment);
+
+ // Parse the WebVTT file |filename| having the indicated |kind|, and
+ // create a corresponding track (or chapters element) in the
+ // segment. Returns false on error.
+ bool Load(const char* filename, Kind kind);
+
+ bool AddChapters();
+
+ // Write any WebVTT cues whose time is less or equal to |time_ns| as
+ // a metadata block in its corresponding track. If |time_ns| is
+ // negative, write all remaining cues. Returns false on error.
+ bool Write(int64_t time_ns);
+
+ private:
+ typedef libwebvtt::Cue cue_t;
+
+ // Used to sort cues as they are loaded.
+ struct SortableCue {
+ bool operator>(int64_t time_ns) const {
+ // Cue start time expressed in milliseconds
+ const int64_t start_ms = cue.start_time.presentation();
+
+ // Cue start time expressed in nanoseconds (MKV time)
+ const int64_t start_ns = start_ms * 1000000;
+
+ return (start_ns > time_ns);
+ }
+
+ bool operator<(const SortableCue& rhs) const {
+ if (cue.start_time < rhs.cue.start_time)
+ return true;
+
+ if (cue.start_time > rhs.cue.start_time)
+ return false;
+
+ return (track_num < rhs.track_num);
+ }
+
+ // Write this cue as a metablock to |segment|. Returns false on
+ // error.
+ bool Write(mkvmuxer::Segment* segment) const;
+
+ uint64_t track_num;
+ cue_t cue;
+ };
+
+ typedef std::multiset<SortableCue> cues_set_t;
+ typedef std::list<cue_t> cue_list_t;
+
+ // Parse the WebVTT cues in the named |file|, returning false on
+ // error. We handle chapters as a special case, because they are
+ // stored in their own, dedicated level-1 element.
+ bool LoadChapters(const char* file);
+
+ // Parse the WebVTT chapters in |file| to populate |cues|. Returns
+ // false on error.
+ static bool ParseChapters(const char* file, cue_list_t* cues);
+
+ // Adds WebVTT cue |chapter| to the chapters element of the output
+ // file's segment element. Returns false on error.
+ bool AddChapter(const cue_t& chapter);
+
+ // Add a metadata track to the segment having the indicated |kind|,
+ // returning the |track_num| that has been chosen for this track.
+ // Returns false on error.
+ bool AddTrack(Kind kind, uint64_t* track_num);
+
+ // Parse the WebVTT |file| having the indicated |kind| and
+ // |track_num|, adding each parsed cue to cues set. Returns false
+ // on error.
+ bool Parse(const char* file, Kind kind, uint64_t track_num);
+
+ // Converts a WebVTT cue to a Matroska metadata block.
+ static void MakeFrame(const cue_t& cue, std::string* frame);
+
+ // Populate the cue identifier part of the metadata block.
+ static void WriteCueIdentifier(const std::string& identifier,
+ std::string* frame);
+
+ // Populate the cue settings part of the metadata block.
+ static void WriteCueSettings(const cue_t::settings_t& settings,
+ std::string* frame);
+
+ // Populate the payload part of the metadata block.
+ static void WriteCuePayload(const cue_t::payload_t& payload,
+ std::string* frame);
+
+ mkvmuxer::Segment* segment_;
+
+ // Set of cues ordered by time and then by track number.
+ cues_set_t cues_set_;
+
+ // The cues that will be used to populate the Chapters level-1
+ // element of the output file.
+ cue_list_t chapter_cues_;
+
+ // Disable copy ctor and copy assign.
+ SampleMuxerMetadata(const SampleMuxerMetadata&);
+ SampleMuxerMetadata& operator=(const SampleMuxerMetadata&);
+};
+
+#endif // SAMPLE_MUXER_METADATA_H_ // NOLINT
diff --git a/testing/mkvmuxer_tests.cc b/testing/mkvmuxer_tests.cc
new file mode 100644
index 0000000..3374058
--- /dev/null
+++ b/testing/mkvmuxer_tests.cc
@@ -0,0 +1,1010 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include <stdint.h>
+
+#include <array>
+#include <cstdio>
+#include <cstring>
+#include <iomanip>
+#include <memory>
+#include <ostream>
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "common/file_util.h"
+#include "common/libwebm_util.h"
+#include "mkvmuxer/mkvmuxer.h"
+#include "mkvmuxer/mkvwriter.h"
+#include "mkvparser/mkvreader.h"
+#include "testing/test_util.h"
+
+using mkvmuxer::AudioTrack;
+using mkvmuxer::Chapter;
+using mkvmuxer::Frame;
+using mkvmuxer::MkvWriter;
+using mkvmuxer::Segment;
+using mkvmuxer::SegmentInfo;
+using mkvmuxer::Tag;
+using mkvmuxer::Track;
+using mkvmuxer::VideoTrack;
+
+namespace test {
+
+// Base class containing boiler plate stuff.
+class MuxerTest : public testing::Test {
+ public:
+ MuxerTest() { Init(); }
+
+ ~MuxerTest() { CloseWriter(); }
+
+ // Simple init function for use by constructor. Calls made here to allow use
+ // of ASSERT_* macros-- this is necessary here because all failures in Init()
+ // are fatal, but the ASSERT_* gtest macros cannot be used in a constructor.
+ void Init() {
+ ASSERT_TRUE(GetTestDataDir().length() > 0);
+ filename_ = libwebm::GetTempFileName();
+ ASSERT_GT(filename_.length(), 0u);
+ temp_file_ = libwebm::FilePtr(std::fopen(filename_.c_str(), "wb"),
+ libwebm::FILEDeleter());
+ ASSERT_TRUE(temp_file_.get() != nullptr);
+ writer_.reset(new MkvWriter(temp_file_.get()));
+ is_writer_open_ = true;
+ memset(dummy_data_, 0, kFrameLength);
+ }
+
+ void AddDummyFrameAndFinalize(int track_number) {
+ EXPECT_TRUE(segment_.AddFrame(&dummy_data_[0], kFrameLength, track_number,
+ 0, false));
+ EXPECT_TRUE(segment_.Finalize());
+ }
+
+ void AddVideoTrack() {
+ const int vid_track = static_cast<int>(
+ segment_.AddVideoTrack(kWidth, kHeight, kVideoTrackNumber));
+ ASSERT_EQ(kVideoTrackNumber, vid_track);
+ VideoTrack* const video =
+ dynamic_cast<VideoTrack*>(segment_.GetTrackByNumber(vid_track));
+ ASSERT_TRUE(video != NULL);
+ video->set_uid(kVideoTrackNumber);
+ }
+
+ void AddAudioTrack() {
+ const int aud_track = static_cast<int>(
+ segment_.AddAudioTrack(kSampleRate, kChannels, kAudioTrackNumber));
+ ASSERT_EQ(kAudioTrackNumber, aud_track);
+ AudioTrack* const audio =
+ dynamic_cast<AudioTrack*>(segment_.GetTrackByNumber(aud_track));
+ ASSERT_TRUE(audio != NULL);
+ audio->set_uid(kAudioTrackNumber);
+ audio->set_codec_id(kOpusCodecId);
+ }
+
+ void CloseWriter() {
+ if (is_writer_open_)
+ writer_->Close();
+ is_writer_open_ = false;
+ }
+
+ bool SegmentInit(bool output_cues, bool accurate_cluster_duration,
+ bool fixed_size_cluster_timecode) {
+ if (!segment_.Init(writer_.get()))
+ return false;
+ SegmentInfo* const info = segment_.GetSegmentInfo();
+ info->set_writing_app(kAppString);
+ info->set_muxing_app(kAppString);
+ segment_.OutputCues(output_cues);
+ segment_.AccurateClusterDuration(accurate_cluster_duration);
+ segment_.UseFixedSizeClusterTimecode(fixed_size_cluster_timecode);
+ return true;
+ }
+
+ protected:
+ virtual void TearDown() {
+ remove(filename_.c_str());
+ testing::Test::TearDown();
+ }
+
+ std::unique_ptr<MkvWriter> writer_;
+ bool is_writer_open_ = false;
+ Segment segment_;
+ std::string filename_;
+ libwebm::FilePtr temp_file_;
+ std::uint8_t dummy_data_[kFrameLength];
+};
+
+TEST_F(MuxerTest, SegmentInfo) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ SegmentInfo* const info = segment_.GetSegmentInfo();
+ info->set_timecode_scale(kTimeCodeScale);
+ info->set_duration(2.345);
+ EXPECT_STREQ(kAppString, info->muxing_app());
+ EXPECT_STREQ(kAppString, info->writing_app());
+ EXPECT_EQ(static_cast<uint64_t>(kTimeCodeScale), info->timecode_scale());
+ EXPECT_DOUBLE_EQ(2.345, info->duration());
+ AddVideoTrack();
+
+ AddDummyFrameAndFinalize(kVideoTrackNumber);
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("segment_info.webm"), filename_));
+}
+
+TEST_F(MuxerTest, AddTracks) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+
+ // Add a Video Track
+ AddVideoTrack();
+ VideoTrack* const video =
+ dynamic_cast<VideoTrack*>(segment_.GetTrackByNumber(kVideoTrackNumber));
+ ASSERT_TRUE(video != NULL);
+ EXPECT_EQ(static_cast<uint64_t>(kWidth), video->width());
+ EXPECT_EQ(static_cast<uint64_t>(kHeight), video->height());
+ video->set_name(kTrackName);
+ video->set_display_width(kWidth - 10);
+ video->set_display_height(kHeight - 10);
+ video->set_frame_rate(0.5);
+ EXPECT_STREQ(kTrackName, video->name());
+ const uint64_t kDisplayWidth = kWidth - 10;
+ EXPECT_EQ(kDisplayWidth, video->display_width());
+ const uint64_t kDisplayHeight = kHeight - 10;
+ EXPECT_EQ(kDisplayHeight, video->display_height());
+ EXPECT_DOUBLE_EQ(0.5, video->frame_rate());
+ EXPECT_EQ(static_cast<uint64_t>(kVideoTrackNumber), video->uid());
+
+ // Add an Audio Track
+ const int aud_track = static_cast<int>(
+ segment_.AddAudioTrack(kSampleRate, kChannels, kAudioTrackNumber));
+ EXPECT_EQ(kAudioTrackNumber, aud_track);
+ AudioTrack* const audio =
+ dynamic_cast<AudioTrack*>(segment_.GetTrackByNumber(aud_track));
+ EXPECT_EQ(kSampleRate, audio->sample_rate());
+ EXPECT_EQ(static_cast<uint64_t>(kChannels), audio->channels());
+ ASSERT_TRUE(audio != NULL);
+ audio->set_name(kTrackName);
+ audio->set_bit_depth(kBitDepth);
+ audio->set_uid(kAudioTrackNumber);
+ EXPECT_STREQ(kTrackName, audio->name());
+ EXPECT_EQ(static_cast<uint64_t>(kBitDepth), audio->bit_depth());
+ EXPECT_EQ(static_cast<uint64_t>(kAudioTrackNumber), audio->uid());
+
+ AddDummyFrameAndFinalize(kVideoTrackNumber);
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("tracks.webm"), filename_));
+}
+
+TEST_F(MuxerTest, AddChapters) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+
+ // Add a Chapter
+ Chapter* chapter = segment_.AddChapter();
+ EXPECT_TRUE(chapter->set_id("unit_test"));
+ chapter->set_time(segment_, 0, 1000000000);
+ EXPECT_TRUE(chapter->add_string("unit_test", "english", "us"));
+ chapter->set_uid(1);
+
+ AddDummyFrameAndFinalize(kVideoTrackNumber);
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("chapters.webm"), filename_));
+}
+
+TEST_F(MuxerTest, SimpleBlock) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+
+ // Valid Frame
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0,
+ false));
+
+ // Valid Frame
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 2000000, false));
+
+ // Invalid Frame - Non monotonically increasing timestamp
+ EXPECT_FALSE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 1, false));
+
+ // Invalid Frame - Null pointer
+ EXPECT_FALSE(segment_.AddFrame(NULL, 0, kVideoTrackNumber, 8000000, false));
+
+ // Invalid Frame - Invalid track number
+ EXPECT_FALSE(segment_.AddFrame(NULL, 0, kInvalidTrackNumber, 8000000, false));
+
+ segment_.Finalize();
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("simple_block.webm"), filename_));
+}
+
+TEST_F(MuxerTest, SimpleBlockWithAddGenericFrame) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+
+ Frame frame;
+ frame.Init(dummy_data_, kFrameLength);
+ frame.set_track_number(kVideoTrackNumber);
+ frame.set_is_key(false);
+
+ // Valid Frame
+ frame.set_timestamp(0);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+
+ // Valid Frame
+ frame.set_timestamp(2000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+
+ // Invalid Frame - Non monotonically increasing timestamp
+ frame.set_timestamp(1);
+ EXPECT_FALSE(segment_.AddGenericFrame(&frame));
+
+ // Invalid Frame - Invalid track number
+ frame.set_track_number(kInvalidTrackNumber);
+ frame.set_timestamp(8000000);
+ EXPECT_FALSE(segment_.AddGenericFrame(&frame));
+
+ segment_.Finalize();
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("simple_block.webm"), filename_));
+}
+
+TEST_F(MuxerTest, MetadataBlock) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ Track* const track = segment_.AddTrack(kMetadataTrackNumber);
+ track->set_uid(kMetadataTrackNumber);
+ track->set_type(kMetadataTrackType);
+ track->set_codec_id(kMetadataCodecId);
+
+ // Valid Frame
+ EXPECT_TRUE(segment_.AddMetadata(dummy_data_, kFrameLength,
+ kMetadataTrackNumber, 0, 2000000));
+
+ // Valid Frame
+ EXPECT_TRUE(segment_.AddMetadata(dummy_data_, kFrameLength,
+ kMetadataTrackNumber, 2000000, 6000000));
+
+ // Invalid Frame - Non monotonically increasing timestamp
+ EXPECT_FALSE(segment_.AddMetadata(dummy_data_, kFrameLength,
+ kMetadataTrackNumber, 1, 2000000));
+
+ // Invalid Frame - Null pointer
+ EXPECT_FALSE(segment_.AddMetadata(NULL, 0, kMetadataTrackNumber, 0, 8000000));
+
+ // Invalid Frame - Invalid track number
+ EXPECT_FALSE(segment_.AddMetadata(NULL, 0, kInvalidTrackNumber, 0, 8000000));
+
+ segment_.Finalize();
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("metadata_block.webm"), filename_));
+}
+
+TEST_F(MuxerTest, TrackType) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ Track* const track = segment_.AddTrack(kMetadataTrackNumber);
+ track->set_uid(kMetadataTrackNumber);
+ track->set_codec_id(kMetadataCodecId);
+
+ // Invalid Frame - Incomplete track information (Track Type not set).
+ EXPECT_FALSE(segment_.AddMetadata(dummy_data_, kFrameLength,
+ kMetadataTrackNumber, 0, 2000000));
+
+ track->set_type(kMetadataTrackType);
+
+ // Valid Frame
+ EXPECT_TRUE(segment_.AddMetadata(dummy_data_, kFrameLength,
+ kMetadataTrackNumber, 0, 2000000));
+
+ segment_.Finalize();
+ CloseWriter();
+}
+
+TEST_F(MuxerTest, BlockWithAdditional) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+
+ // Valid Frame
+ EXPECT_TRUE(segment_.AddFrameWithAdditional(dummy_data_, kFrameLength,
+ dummy_data_, kFrameLength, 1,
+ kVideoTrackNumber, 0, true));
+
+ // Valid Frame
+ EXPECT_TRUE(segment_.AddFrameWithAdditional(
+ dummy_data_, kFrameLength, dummy_data_, kFrameLength, 1,
+ kVideoTrackNumber, 2000000, false));
+
+ // Invalid Frame - Non monotonically increasing timestamp
+ EXPECT_FALSE(segment_.AddFrameWithAdditional(dummy_data_, kFrameLength,
+ dummy_data_, kFrameLength, 1,
+ kVideoTrackNumber, 1, false));
+
+ // Invalid Frame - Null frame pointer
+ EXPECT_FALSE(
+ segment_.AddFrameWithAdditional(NULL, 0, dummy_data_, kFrameLength, 1,
+ kVideoTrackNumber, 3000000, false));
+
+ // Invalid Frame - Null additional pointer
+ EXPECT_FALSE(segment_.AddFrameWithAdditional(dummy_data_, kFrameLength, NULL,
+ 0, 1, kVideoTrackNumber, 4000000,
+ false));
+
+ // Invalid Frame - Invalid track number
+ EXPECT_FALSE(segment_.AddFrameWithAdditional(
+ dummy_data_, kFrameLength, dummy_data_, kFrameLength, 1,
+ kInvalidTrackNumber, 8000000, false));
+
+ segment_.Finalize();
+ CloseWriter();
+
+ EXPECT_TRUE(
+ CompareFiles(GetTestFilePath("block_with_additional.webm"), filename_));
+}
+
+TEST_F(MuxerTest, BlockAdditionalWithAddGenericFrame) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+
+ Frame frame;
+ frame.Init(dummy_data_, kFrameLength);
+ frame.AddAdditionalData(dummy_data_, kFrameLength, 1);
+ frame.set_track_number(kVideoTrackNumber);
+ frame.set_is_key(true);
+
+ // Valid Frame
+ frame.set_timestamp(0);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+
+ // Valid Frame
+ frame.set_timestamp(2000000);
+ frame.set_is_key(false);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+
+ // Invalid Frame - Non monotonically increasing timestamp
+ frame.set_timestamp(1);
+ EXPECT_FALSE(segment_.AddGenericFrame(&frame));
+
+ // Invalid Frame - Invalid track number
+ frame.set_track_number(kInvalidTrackNumber);
+ frame.set_timestamp(4000000);
+ EXPECT_FALSE(segment_.AddGenericFrame(&frame));
+
+ segment_.Finalize();
+ CloseWriter();
+
+ EXPECT_TRUE(
+ CompareFiles(GetTestFilePath("block_with_additional.webm"), filename_));
+}
+
+TEST_F(MuxerTest, SegmentDurationComputation) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+
+ Frame frame;
+ frame.Init(dummy_data_, kFrameLength);
+ frame.set_track_number(kVideoTrackNumber);
+ frame.set_timestamp(0);
+ frame.set_is_key(false);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ frame.set_timestamp(2000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ frame.set_timestamp(4000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ frame.set_timestamp(6000000);
+ frame.set_duration(2000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ segment_.Finalize();
+
+ // SegmentInfo's duration is in timecode scale
+ EXPECT_EQ(8, segment_.GetSegmentInfo()->duration());
+
+ CloseWriter();
+
+ EXPECT_TRUE(
+ CompareFiles(GetTestFilePath("segment_duration.webm"), filename_));
+}
+
+TEST_F(MuxerTest, SetSegmentDuration) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0,
+ false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 2000000, false));
+
+ segment_.set_duration(10500.0);
+ segment_.Finalize();
+ CloseWriter();
+
+ EXPECT_TRUE(
+ CompareFiles(GetTestFilePath("set_segment_duration.webm"), filename_));
+}
+
+TEST_F(MuxerTest, ForceNewCluster) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0,
+ false));
+ segment_.ForceNewClusterOnNextFrame();
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 2000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 4000000, false));
+ segment_.ForceNewClusterOnNextFrame();
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 6000000, false));
+ segment_.Finalize();
+
+ CloseWriter();
+
+ EXPECT_TRUE(
+ CompareFiles(GetTestFilePath("force_new_cluster.webm"), filename_));
+}
+
+TEST_F(MuxerTest, OutputCues) {
+ EXPECT_TRUE(SegmentInit(true, false, false));
+ AddVideoTrack();
+
+ EXPECT_TRUE(
+ segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, true));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 2000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 4000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 6000000, true));
+ EXPECT_TRUE(segment_.AddCuePoint(4000000, kVideoTrackNumber));
+ segment_.Finalize();
+
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("output_cues.webm"), filename_));
+}
+
+TEST_F(MuxerTest, CuesBeforeClusters) {
+ EXPECT_TRUE(SegmentInit(true, false, false));
+ AddVideoTrack();
+
+ EXPECT_TRUE(
+ segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0, true));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 2000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 4000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 6000000, true));
+ segment_.Finalize();
+ CloseWriter();
+#ifdef _MSC_VER
+ // Close the output file: the MS run time won't allow mkvparser::MkvReader
+ // to open a file for reading when it's still open for writing.
+ temp_file_.reset();
+#endif
+ mkvparser::MkvReader reader;
+ ASSERT_EQ(0, reader.Open(filename_.c_str()));
+ MkvWriter cues_writer;
+ std::string cues_filename = libwebm::GetTempFileName();
+ ASSERT_GT(cues_filename.length(), 0u);
+ cues_writer.Open(cues_filename.c_str());
+ EXPECT_TRUE(segment_.CopyAndMoveCuesBeforeClusters(&reader, &cues_writer));
+ reader.Close();
+ cues_writer.Close();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("cues_before_clusters.webm"),
+ cues_filename));
+ MkvParser parser;
+ ASSERT_TRUE(ParseMkvFileReleaseParser(cues_filename, &parser));
+ int64_t cues_offset = 0;
+ ASSERT_TRUE(HasCuePoints(parser.segment, &cues_offset));
+ ASSERT_GT(cues_offset, 0);
+ ASSERT_TRUE(ValidateCues(parser.segment, parser.reader));
+ remove(cues_filename.c_str());
+}
+
+TEST_F(MuxerTest, MaxClusterSize) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+ const uint64_t kMaxClusterSize = 20;
+ segment_.set_max_cluster_size(kMaxClusterSize);
+ EXPECT_EQ(kMaxClusterSize, segment_.max_cluster_size());
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, 1, kVideoTrackNumber, 0, false));
+ EXPECT_TRUE(
+ segment_.AddFrame(dummy_data_, 1, kVideoTrackNumber, 2000000, false));
+ EXPECT_TRUE(
+ segment_.AddFrame(dummy_data_, 1, kVideoTrackNumber, 4000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 6000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 8000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 9000000, false));
+ segment_.Finalize();
+
+ CloseWriter();
+
+ EXPECT_TRUE(
+ CompareFiles(GetTestFilePath("max_cluster_size.webm"), filename_));
+}
+
+TEST_F(MuxerTest, MaxClusterDuration) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+ const uint64_t kMaxClusterDuration = 4000000;
+ segment_.set_max_cluster_duration(kMaxClusterDuration);
+
+ EXPECT_EQ(kMaxClusterDuration, segment_.max_cluster_duration());
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0,
+ false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 2000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 4000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 6000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 8000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 9000000, false));
+ segment_.Finalize();
+
+ CloseWriter();
+
+ EXPECT_TRUE(
+ CompareFiles(GetTestFilePath("max_cluster_duration.webm"), filename_));
+}
+
+TEST_F(MuxerTest, SetCuesTrackNumber) {
+ const uint64_t kTrackNumber = 10;
+ EXPECT_TRUE(SegmentInit(true, false, false));
+ const uint64_t vid_track =
+ segment_.AddVideoTrack(kWidth, kHeight, kTrackNumber);
+ EXPECT_EQ(kTrackNumber, vid_track);
+ segment_.GetTrackByNumber(vid_track)->set_uid(kVideoTrackNumber);
+ EXPECT_TRUE(segment_.CuesTrack(vid_track));
+
+ EXPECT_EQ(vid_track, segment_.cues_track());
+ EXPECT_TRUE(
+ segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber, 0, true));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber,
+ 2000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber,
+ 4000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber,
+ 6000000, true));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber,
+ 8000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kTrackNumber,
+ 9000000, false));
+ segment_.Finalize();
+
+ CloseWriter();
+
+ EXPECT_TRUE(
+ CompareFiles(GetTestFilePath("set_cues_track_number.webm"), filename_));
+}
+
+TEST_F(MuxerTest, BlockWithDiscardPadding) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddAudioTrack();
+
+ int timecode = 1000;
+ // 12810000 == 0xc37710, should be 0-extended to avoid changing the sign.
+ // The next two should be written as 1 byte.
+ std::array<int, 3> values = {{12810000, 127, -128}};
+ for (const std::int64_t discard_padding : values) {
+ EXPECT_TRUE(segment_.AddFrameWithDiscardPadding(
+ dummy_data_, kFrameLength, discard_padding, kAudioTrackNumber, timecode,
+ true))
+ << "discard_padding: " << discard_padding;
+ timecode += 1000;
+ }
+
+ segment_.Finalize();
+
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("discard_padding.webm"), filename_));
+}
+
+TEST_F(MuxerTest, AccurateClusterDuration) {
+ EXPECT_TRUE(SegmentInit(false, true, false));
+ AddVideoTrack();
+
+ Frame frame;
+ frame.Init(dummy_data_, kFrameLength);
+ frame.set_track_number(kVideoTrackNumber);
+ frame.set_timestamp(0);
+ frame.set_is_key(true);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ segment_.ForceNewClusterOnNextFrame();
+ frame.set_timestamp(2000000);
+ frame.set_is_key(false);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ frame.set_timestamp(4000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ segment_.ForceNewClusterOnNextFrame();
+ frame.set_timestamp(6000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ segment_.Finalize();
+
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("accurate_cluster_duration.webm"),
+ filename_));
+}
+
+// Tests AccurateClusterDuration flag with the duration of the very last block
+// of the file set explicitly.
+TEST_F(MuxerTest, AccurateClusterDurationExplicitLastFrameDuration) {
+ EXPECT_TRUE(SegmentInit(false, true, false));
+ AddVideoTrack();
+
+ Frame frame;
+ frame.Init(dummy_data_, kFrameLength);
+ frame.set_track_number(kVideoTrackNumber);
+ frame.set_timestamp(0);
+ frame.set_is_key(true);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ segment_.ForceNewClusterOnNextFrame();
+ frame.set_timestamp(2000000);
+ frame.set_is_key(false);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ frame.set_timestamp(4000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ segment_.ForceNewClusterOnNextFrame();
+ frame.set_timestamp(6000000);
+ frame.set_duration(2000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ segment_.Finalize();
+
+ // SegmentInfo's duration is in timecode scale
+ EXPECT_EQ(8, segment_.GetSegmentInfo()->duration());
+
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(
+ GetTestFilePath("accurate_cluster_duration_last_frame.webm"), filename_));
+}
+
+TEST_F(MuxerTest, AccurateClusterDurationTwoTracks) {
+ EXPECT_TRUE(SegmentInit(false, true, false));
+ AddVideoTrack();
+ AddAudioTrack();
+
+ Frame video_frame;
+ video_frame.Init(dummy_data_, kFrameLength);
+ video_frame.set_track_number(kVideoTrackNumber);
+ Frame audio_frame;
+ audio_frame.Init(dummy_data_, kFrameLength);
+ audio_frame.set_track_number(kAudioTrackNumber);
+ std::array<std::uint64_t, 2> cluster_timestamps = {{0, 40000000}};
+ for (const std::uint64_t cluster_timestamp : cluster_timestamps) {
+ // Add video and audio frames with timestamp 0.
+ video_frame.set_timestamp(cluster_timestamp);
+ video_frame.set_is_key(true);
+ EXPECT_TRUE(segment_.AddGenericFrame(&video_frame));
+ audio_frame.set_timestamp(cluster_timestamp);
+ audio_frame.set_is_key(true);
+ EXPECT_TRUE(segment_.AddGenericFrame(&audio_frame));
+
+ // Add 3 consecutive audio frames.
+ std::array<std::uint64_t, 3> audio_timestamps = {
+ {10000000, 20000000, 30000000}};
+ for (const std::uint64_t audio_timestamp : audio_timestamps) {
+ audio_frame.set_timestamp(cluster_timestamp + audio_timestamp);
+ // Explicitly set duration for the very last audio frame.
+ if (cluster_timestamp == 40000000 && audio_timestamp == 30000000) {
+ audio_frame.set_duration(10000000);
+ }
+ EXPECT_TRUE(segment_.AddGenericFrame(&audio_frame));
+ }
+
+ // Add a video frame with timestamp 33ms.
+ video_frame.set_is_key(false);
+ // Explicitly set duration for the very last video frame.
+ if (cluster_timestamp == 40000000) {
+ video_frame.set_duration(7000000);
+ }
+ video_frame.set_timestamp(cluster_timestamp + 33000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&video_frame));
+ segment_.ForceNewClusterOnNextFrame();
+ }
+ segment_.Finalize();
+
+ // SegmentInfo's duration is in timecode scale
+ EXPECT_EQ(80, segment_.GetSegmentInfo()->duration());
+
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(
+ GetTestFilePath("accurate_cluster_duration_two_tracks.webm"), filename_));
+}
+
+TEST_F(MuxerTest, AccurateClusterDurationWithoutFinalizingCluster) {
+ EXPECT_TRUE(SegmentInit(false, true, false));
+ AddVideoTrack();
+
+ // Add a couple of frames and then bail out without finalizing the Segment
+ // (and thereby not finalizing the Cluster). The expectation here is that
+ // there shouldn't be any leaks. The test will fail under valgrind if there's
+ // a leak.
+ Frame video_frame;
+ video_frame.Init(dummy_data_, kFrameLength);
+ video_frame.set_track_number(kVideoTrackNumber);
+ video_frame.set_timestamp(0);
+ video_frame.set_is_key(true);
+ EXPECT_TRUE(segment_.AddGenericFrame(&video_frame));
+ video_frame.set_timestamp(33000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&video_frame));
+
+ CloseWriter();
+}
+
+TEST_F(MuxerTest, UseFixedSizeClusterTimecode) {
+ EXPECT_TRUE(SegmentInit(false, false, true));
+ AddVideoTrack();
+
+ Frame frame;
+ frame.Init(dummy_data_, kFrameLength);
+ frame.set_track_number(kVideoTrackNumber);
+ frame.set_timestamp(0);
+ frame.set_is_key(true);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ segment_.ForceNewClusterOnNextFrame();
+ frame.set_timestamp(2000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ segment_.ForceNewClusterOnNextFrame();
+ frame.set_timestamp(4000000);
+ EXPECT_TRUE(segment_.AddGenericFrame(&frame));
+ segment_.Finalize();
+
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("fixed_size_cluster_timecode.webm"),
+ filename_));
+}
+
+TEST_F(MuxerTest, DocTypeWebm) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+ Track* const vid_track = segment_.GetTrackByNumber(kVideoTrackNumber);
+ vid_track->set_codec_id(kVP9CodecId);
+ AddDummyFrameAndFinalize(kVideoTrackNumber);
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("webm_doctype.webm"), filename_));
+}
+
+TEST_F(MuxerTest, DocTypeMatroska) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+ Track* const vid_track = segment_.GetTrackByNumber(kVideoTrackNumber);
+ vid_track->set_codec_id("V_SOMETHING_NOT_IN_WEBM");
+ AddDummyFrameAndFinalize(kVideoTrackNumber);
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("matroska_doctype.mkv"), filename_));
+}
+
+TEST_F(MuxerTest, Colour) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+
+ mkvmuxer::PrimaryChromaticity muxer_pc(.1, .2);
+ mkvmuxer::MasteringMetadata muxer_mm;
+ muxer_mm.set_luminance_min(30.0);
+ muxer_mm.set_luminance_max(40.0);
+ ASSERT_TRUE(
+ muxer_mm.SetChromaticity(&muxer_pc, &muxer_pc, &muxer_pc, &muxer_pc));
+
+ mkvmuxer::Colour muxer_colour;
+ muxer_colour.set_matrix_coefficients(mkvmuxer::Colour::kGbr);
+ muxer_colour.set_bits_per_channel(1);
+ muxer_colour.set_chroma_subsampling_horz(2);
+ muxer_colour.set_chroma_subsampling_vert(3);
+ muxer_colour.set_cb_subsampling_horz(4);
+ muxer_colour.set_cb_subsampling_vert(5);
+ muxer_colour.set_chroma_siting_horz(mkvmuxer::Colour::kLeftCollocated);
+ muxer_colour.set_chroma_siting_vert(mkvmuxer::Colour::kTopCollocated);
+ muxer_colour.set_range(mkvmuxer::Colour::kFullRange);
+ muxer_colour.set_transfer_characteristics(mkvmuxer::Colour::kLog);
+ muxer_colour.set_primaries(mkvmuxer::Colour::kSmpteSt4281P);
+ muxer_colour.set_max_cll(11);
+ muxer_colour.set_max_fall(12);
+ ASSERT_TRUE(muxer_colour.SetMasteringMetadata(muxer_mm));
+
+ VideoTrack* const video_track =
+ dynamic_cast<VideoTrack*>(segment_.GetTrackByNumber(kVideoTrackNumber));
+ ASSERT_TRUE(video_track != nullptr);
+ ASSERT_TRUE(video_track->SetColour(muxer_colour));
+ ASSERT_NO_FATAL_FAILURE(AddDummyFrameAndFinalize(kVideoTrackNumber));
+
+ MkvParser parser;
+ ASSERT_TRUE(ParseMkvFileReleaseParser(filename_, &parser));
+
+ const mkvparser::VideoTrack* const parser_track =
+ static_cast<const mkvparser::VideoTrack*>(
+ parser.segment->GetTracks()->GetTrackByIndex(0));
+ const mkvparser::Colour* parser_colour = parser_track->GetColour();
+ ASSERT_TRUE(parser_colour != nullptr);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.matrix_coefficients()),
+ parser_colour->matrix_coefficients);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.bits_per_channel()),
+ parser_colour->bits_per_channel);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.chroma_subsampling_horz()),
+ parser_colour->chroma_subsampling_horz);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.chroma_subsampling_vert()),
+ parser_colour->chroma_subsampling_vert);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.cb_subsampling_horz()),
+ parser_colour->cb_subsampling_horz);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.cb_subsampling_vert()),
+ parser_colour->cb_subsampling_vert);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.chroma_siting_horz()),
+ parser_colour->chroma_siting_horz);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.chroma_siting_vert()),
+ parser_colour->chroma_siting_vert);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.range()), parser_colour->range);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.transfer_characteristics()),
+ parser_colour->transfer_characteristics);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.primaries()),
+ parser_colour->primaries);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.max_cll()),
+ parser_colour->max_cll);
+ EXPECT_EQ(static_cast<long long>(muxer_colour.max_fall()),
+ parser_colour->max_fall);
+
+ const mkvparser::MasteringMetadata* const parser_mm =
+ parser_colour->mastering_metadata;
+ ASSERT_TRUE(parser_mm != nullptr);
+ EXPECT_FLOAT_EQ(muxer_mm.luminance_min(), parser_mm->luminance_min);
+ EXPECT_FLOAT_EQ(muxer_mm.luminance_max(), parser_mm->luminance_max);
+ EXPECT_FLOAT_EQ(muxer_mm.r()->x(), parser_mm->r->x);
+ EXPECT_FLOAT_EQ(muxer_mm.r()->y(), parser_mm->r->y);
+ EXPECT_FLOAT_EQ(muxer_mm.g()->x(), parser_mm->g->x);
+ EXPECT_FLOAT_EQ(muxer_mm.g()->y(), parser_mm->g->y);
+ EXPECT_FLOAT_EQ(muxer_mm.b()->x(), parser_mm->b->x);
+ EXPECT_FLOAT_EQ(muxer_mm.b()->y(), parser_mm->b->y);
+ EXPECT_FLOAT_EQ(muxer_mm.white_point()->x(), parser_mm->white_point->x);
+ EXPECT_FLOAT_EQ(muxer_mm.white_point()->y(), parser_mm->white_point->y);
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("colour.webm"), filename_));
+}
+
+TEST_F(MuxerTest, ColourPartial) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+ mkvmuxer::Colour muxer_colour;
+ muxer_colour.set_matrix_coefficients(
+ mkvmuxer::Colour::kBt2020NonConstantLuminance);
+
+ VideoTrack* const video_track =
+ dynamic_cast<VideoTrack*>(segment_.GetTrackByNumber(kVideoTrackNumber));
+ ASSERT_TRUE(video_track != nullptr);
+ ASSERT_TRUE(video_track->SetColour(muxer_colour));
+ ASSERT_NO_FATAL_FAILURE(AddDummyFrameAndFinalize(kVideoTrackNumber));
+
+ MkvParser parser;
+ ASSERT_TRUE(ParseMkvFileReleaseParser(filename_, &parser));
+
+ const mkvparser::VideoTrack* const parser_track =
+ static_cast<const mkvparser::VideoTrack*>(
+ parser.segment->GetTracks()->GetTrackByIndex(0));
+ const mkvparser::Colour* parser_colour = parser_track->GetColour();
+ EXPECT_EQ(static_cast<long long>(muxer_colour.matrix_coefficients()),
+ parser_colour->matrix_coefficients);
+}
+
+TEST_F(MuxerTest, Projection) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+
+ mkvmuxer::Projection muxer_proj;
+ muxer_proj.set_type(mkvmuxer::Projection::kRectangular);
+ muxer_proj.set_pose_yaw(1);
+ muxer_proj.set_pose_pitch(2);
+ muxer_proj.set_pose_roll(3);
+ const uint8_t muxer_proj_private[1] = {4};
+ const uint64_t muxer_proj_private_length = 1;
+ ASSERT_TRUE(muxer_proj.SetProjectionPrivate(&muxer_proj_private[0],
+ muxer_proj_private_length));
+
+ VideoTrack* const video_track =
+ static_cast<VideoTrack*>(segment_.GetTrackByNumber(kVideoTrackNumber));
+ ASSERT_TRUE(video_track != nullptr);
+ ASSERT_TRUE(video_track->SetProjection(muxer_proj));
+ ASSERT_NO_FATAL_FAILURE(AddDummyFrameAndFinalize(kVideoTrackNumber));
+
+ MkvParser parser;
+ ASSERT_TRUE(ParseMkvFileReleaseParser(filename_, &parser));
+
+ const mkvparser::VideoTrack* const parser_track =
+ static_cast<const mkvparser::VideoTrack*>(
+ parser.segment->GetTracks()->GetTrackByIndex(0));
+
+ const mkvparser::Projection* const parser_proj =
+ parser_track->GetProjection();
+ ASSERT_TRUE(parser_proj != nullptr);
+ EXPECT_FLOAT_EQ(muxer_proj.pose_yaw(), parser_proj->pose_yaw);
+ EXPECT_FLOAT_EQ(muxer_proj.pose_pitch(), parser_proj->pose_pitch);
+ EXPECT_FLOAT_EQ(muxer_proj.pose_roll(), parser_proj->pose_roll);
+ ASSERT_TRUE(parser_proj->private_data != nullptr);
+ EXPECT_EQ(static_cast<size_t>(muxer_proj.private_data_length()),
+ parser_proj->private_data_length);
+
+ EXPECT_EQ(muxer_proj.private_data()[0], parser_proj->private_data[0]);
+ typedef mkvparser::Projection::ProjectionType ParserProjType;
+ EXPECT_EQ(static_cast<ParserProjType>(muxer_proj.type()), parser_proj->type);
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("projection.webm"), filename_));
+}
+
+TEST_F(MuxerTest, EstimateDuration) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ segment_.set_estimate_file_duration(true);
+ AddVideoTrack();
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0,
+ false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 2000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 4000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 6000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 8000000, false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 9000000, false));
+ segment_.Finalize();
+
+ CloseWriter();
+
+ EXPECT_TRUE(
+ CompareFiles(GetTestFilePath("estimate_duration.webm"), filename_));
+}
+
+TEST_F(MuxerTest, SetPixelWidthPixelHeight) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+ VideoTrack* const video_track =
+ static_cast<VideoTrack*>(segment_.GetTrackByNumber(kVideoTrackNumber));
+ ASSERT_TRUE(video_track != nullptr);
+ video_track->set_pixel_width(500);
+ video_track->set_pixel_height(650);
+
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0,
+ false));
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber,
+ 2000000, false));
+
+ segment_.Finalize();
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("set_pixelwidth_pixelheight.webm"),
+ filename_));
+}
+
+TEST_F(MuxerTest, LongTagString) {
+ EXPECT_TRUE(SegmentInit(false, false, false));
+ AddVideoTrack();
+ Tag* const tag = segment_.AddTag();
+ // 160 needs two bytes when varint encoded.
+ const std::string dummy_string(160, '0');
+ tag->add_simple_tag("long_tag", dummy_string.c_str());
+
+ EXPECT_TRUE(segment_.AddFrame(dummy_data_, kFrameLength, kVideoTrackNumber, 0,
+ false));
+
+ segment_.Finalize();
+ CloseWriter();
+
+ EXPECT_TRUE(CompareFiles(GetTestFilePath("long_tag_string.webm"), filename_));
+}
+
+} // namespace test
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/testing/mkvparser_tests.cc b/testing/mkvparser_tests.cc
new file mode 100644
index 0000000..9c26a1e
--- /dev/null
+++ b/testing/mkvparser_tests.cc
@@ -0,0 +1,823 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "gtest/gtest.h"
+
+#include <array>
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <iomanip>
+#include <string>
+
+#include "common/hdr_util.h"
+#include "mkvparser/mkvparser.h"
+#include "mkvparser/mkvreader.h"
+#include "testing/test_util.h"
+
+using mkvparser::AudioTrack;
+using mkvparser::Block;
+using mkvparser::BlockEntry;
+using mkvparser::BlockGroup;
+using mkvparser::Cluster;
+using mkvparser::CuePoint;
+using mkvparser::Cues;
+using mkvparser::MkvReader;
+using mkvparser::Segment;
+using mkvparser::SegmentInfo;
+using mkvparser::Track;
+using mkvparser::Tracks;
+using mkvparser::VideoTrack;
+
+namespace test {
+
+// Base class containing boiler plate stuff.
+class ParserTest : public testing::Test {
+ public:
+ ParserTest() : is_reader_open_(false), segment_(NULL) {
+ memset(dummy_data_, -1, kFrameLength);
+ memset(gold_frame_, 0, kFrameLength);
+ }
+
+ virtual ~ParserTest() {
+ CloseReader();
+ if (segment_ != NULL) {
+ delete segment_;
+ segment_ = NULL;
+ }
+ }
+
+ void CloseReader() {
+ if (is_reader_open_) {
+ reader_.Close();
+ }
+ is_reader_open_ = false;
+ }
+
+ bool CreateAndLoadSegment(const std::string& filename,
+ int expected_doc_type_ver) {
+ filename_ = GetTestFilePath(filename);
+ if (reader_.Open(filename_.c_str())) {
+ return false;
+ }
+ is_reader_open_ = true;
+ pos_ = 0;
+ mkvparser::EBMLHeader ebml_header;
+ ebml_header.Parse(&reader_, pos_);
+ EXPECT_EQ(1, ebml_header.m_version);
+ EXPECT_EQ(1, ebml_header.m_readVersion);
+ EXPECT_STREQ("webm", ebml_header.m_docType);
+ EXPECT_EQ(expected_doc_type_ver, ebml_header.m_docTypeVersion);
+ EXPECT_EQ(2, ebml_header.m_docTypeReadVersion);
+
+ if (mkvparser::Segment::CreateInstance(&reader_, pos_, segment_)) {
+ return false;
+ }
+ return !HasFailure() && segment_->Load() >= 0;
+ }
+
+ bool CreateAndLoadSegment(const std::string& filename) {
+ return CreateAndLoadSegment(filename, 4);
+ }
+
+ void CreateSegmentNoHeaderChecks(const std::string& filename) {
+ filename_ = GetTestFilePath(filename);
+ ASSERT_NE(0u, filename_.length());
+ ASSERT_EQ(0, reader_.Open(filename_.c_str()));
+ mkvparser::EBMLHeader ebml_header;
+ ASSERT_EQ(0, ebml_header.Parse(&reader_, pos_));
+ ASSERT_EQ(0, mkvparser::Segment::CreateInstance(&reader_, pos_, segment_));
+ }
+
+ void CompareBlockContents(const Cluster* const cluster,
+ const Block* const block, std::uint64_t timestamp,
+ int track_number, bool is_key, int frame_count) {
+ ASSERT_TRUE(block != NULL);
+ EXPECT_EQ(track_number, block->GetTrackNumber());
+ EXPECT_EQ(static_cast<long long>(timestamp), block->GetTime(cluster));
+ EXPECT_EQ(is_key, block->IsKey());
+ EXPECT_EQ(frame_count, block->GetFrameCount());
+ const Block::Frame& frame = block->GetFrame(0);
+ EXPECT_EQ(kFrameLength, frame.len);
+ std::memset(dummy_data_, -1, kFrameLength);
+ frame.Read(&reader_, dummy_data_);
+ EXPECT_EQ(0, std::memcmp(gold_frame_, dummy_data_, kFrameLength));
+ }
+
+ void CompareCuePointContents(const Track* const track,
+ const CuePoint* const cue_point,
+ std::uint64_t timestamp, int track_number,
+ std::uint64_t pos) {
+ ASSERT_TRUE(cue_point != NULL);
+ EXPECT_EQ(static_cast<long long>(timestamp), cue_point->GetTime(segment_));
+ const CuePoint::TrackPosition* const track_position =
+ cue_point->Find(track);
+ EXPECT_EQ(track_number, track_position->m_track);
+ EXPECT_EQ(static_cast<long long>(pos), track_position->m_pos);
+ }
+
+ protected:
+ MkvReader reader_;
+ bool is_reader_open_;
+ Segment* segment_;
+ std::string filename_;
+ long long pos_;
+ std::uint8_t dummy_data_[kFrameLength];
+ std::uint8_t gold_frame_[kFrameLength];
+};
+
+TEST_F(ParserTest, SegmentInfo) {
+ ASSERT_TRUE(CreateAndLoadSegment("segment_info.webm"));
+ const SegmentInfo* const info = segment_->GetInfo();
+ EXPECT_EQ(kTimeCodeScale, info->GetTimeCodeScale());
+ EXPECT_STREQ(kAppString, info->GetMuxingAppAsUTF8());
+ EXPECT_STREQ(kAppString, info->GetWritingAppAsUTF8());
+}
+
+TEST_F(ParserTest, TrackEntries) {
+ ASSERT_TRUE(CreateAndLoadSegment("tracks.webm"));
+ const Tracks* const tracks = segment_->GetTracks();
+ const unsigned int kTracksCount = 2;
+ EXPECT_EQ(kTracksCount, tracks->GetTracksCount());
+ for (int i = 0; i < 2; ++i) {
+ const Track* const track = tracks->GetTrackByIndex(i);
+ ASSERT_TRUE(track != NULL);
+ EXPECT_STREQ(kTrackName, track->GetNameAsUTF8());
+ if (track->GetType() == Track::kVideo) {
+ const VideoTrack* const video_track =
+ dynamic_cast<const VideoTrack*>(track);
+ EXPECT_EQ(kWidth, static_cast<int>(video_track->GetWidth()));
+ EXPECT_EQ(kHeight, static_cast<int>(video_track->GetHeight()));
+ EXPECT_STREQ(kVP8CodecId, video_track->GetCodecId());
+ EXPECT_DOUBLE_EQ(kVideoFrameRate, video_track->GetFrameRate());
+ const unsigned int kTrackUid = 1;
+ EXPECT_EQ(kTrackUid, video_track->GetUid());
+ } else if (track->GetType() == Track::kAudio) {
+ const AudioTrack* const audio_track =
+ dynamic_cast<const AudioTrack*>(track);
+ EXPECT_EQ(kSampleRate, audio_track->GetSamplingRate());
+ EXPECT_EQ(kChannels, audio_track->GetChannels());
+ EXPECT_EQ(kBitDepth, audio_track->GetBitDepth());
+ EXPECT_STREQ(kVorbisCodecId, audio_track->GetCodecId());
+ const unsigned int kTrackUid = 2;
+ EXPECT_EQ(kTrackUid, audio_track->GetUid());
+ }
+ }
+}
+
+TEST_F(ParserTest, SimpleBlock) {
+ ASSERT_TRUE(CreateAndLoadSegment("simple_block.webm"));
+ const unsigned int kTracksCount = 1;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+
+ // Get the cluster
+ const Cluster* cluster = segment_->GetFirst();
+ ASSERT_TRUE(cluster != NULL);
+ EXPECT_FALSE(cluster->EOS());
+
+ // Get the first block
+ const BlockEntry* block_entry;
+ EXPECT_EQ(0, cluster->GetFirst(block_entry));
+ ASSERT_TRUE(block_entry != NULL);
+ EXPECT_FALSE(block_entry->EOS());
+ CompareBlockContents(cluster, block_entry->GetBlock(), 0, kVideoTrackNumber,
+ false, 1);
+
+ // Get the second block
+ EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry));
+ ASSERT_TRUE(block_entry != NULL);
+ EXPECT_FALSE(block_entry->EOS());
+ CompareBlockContents(cluster, block_entry->GetBlock(), 2000000,
+ kVideoTrackNumber, false, 1);
+
+ // End of Stream
+ EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry));
+ ASSERT_EQ(NULL, block_entry);
+ cluster = segment_->GetNext(cluster);
+ EXPECT_TRUE(cluster->EOS());
+}
+
+TEST_F(ParserTest, MultipleClusters) {
+ ASSERT_TRUE(CreateAndLoadSegment("force_new_cluster.webm"));
+ const unsigned int kTracksCount = 1;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+
+ // Get the first cluster
+ const Cluster* cluster = segment_->GetFirst();
+ ASSERT_TRUE(cluster != NULL);
+ EXPECT_FALSE(cluster->EOS());
+
+ // Get the first block
+ const BlockEntry* block_entry;
+ EXPECT_EQ(0, cluster->GetFirst(block_entry));
+ ASSERT_TRUE(block_entry != NULL);
+ EXPECT_FALSE(block_entry->EOS());
+ CompareBlockContents(cluster, block_entry->GetBlock(), 0, kVideoTrackNumber,
+ false, 1);
+
+ // Get the second cluster
+ EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry));
+ EXPECT_EQ(NULL, block_entry);
+ cluster = segment_->GetNext(cluster);
+ ASSERT_TRUE(cluster != NULL);
+ EXPECT_FALSE(cluster->EOS());
+
+ // Get the second block
+ EXPECT_EQ(0, cluster->GetFirst(block_entry));
+ ASSERT_TRUE(block_entry != NULL);
+ EXPECT_FALSE(block_entry->EOS());
+ CompareBlockContents(cluster, block_entry->GetBlock(), 2000000,
+ kVideoTrackNumber, false, 1);
+
+ // Get the third block
+ EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry));
+ ASSERT_TRUE(block_entry != NULL);
+ EXPECT_FALSE(block_entry->EOS());
+ CompareBlockContents(cluster, block_entry->GetBlock(), 4000000,
+ kVideoTrackNumber, false, 1);
+
+ // Get the third cluster
+ EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry));
+ EXPECT_EQ(NULL, block_entry);
+ cluster = segment_->GetNext(cluster);
+ ASSERT_TRUE(cluster != NULL);
+ EXPECT_FALSE(cluster->EOS());
+
+ // Get the fourth block
+ EXPECT_EQ(0, cluster->GetFirst(block_entry));
+ ASSERT_TRUE(block_entry != NULL);
+ EXPECT_FALSE(block_entry->EOS());
+ CompareBlockContents(cluster, block_entry->GetBlock(), 6000000,
+ kVideoTrackNumber, false, 1);
+
+ // End of Stream
+ EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry));
+ EXPECT_EQ(NULL, block_entry);
+ cluster = segment_->GetNext(cluster);
+ EXPECT_TRUE(cluster->EOS());
+}
+
+TEST_F(ParserTest, BlockGroup) {
+ ASSERT_TRUE(CreateAndLoadSegment("metadata_block.webm"));
+ const unsigned int kTracksCount = 1;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+
+ // Get the cluster
+ const Cluster* cluster = segment_->GetFirst();
+ ASSERT_TRUE(cluster != NULL);
+ EXPECT_FALSE(cluster->EOS());
+
+ // Get the first block
+ const BlockEntry* block_entry;
+ EXPECT_EQ(0, cluster->GetFirst(block_entry));
+ ASSERT_TRUE(block_entry != NULL);
+ EXPECT_FALSE(block_entry->EOS());
+ EXPECT_EQ(BlockEntry::Kind::kBlockGroup, block_entry->GetKind());
+ const BlockGroup* block_group = static_cast<const BlockGroup*>(block_entry);
+ EXPECT_EQ(2, block_group->GetDurationTimeCode());
+ CompareBlockContents(cluster, block_group->GetBlock(), 0,
+ kMetadataTrackNumber, true, 1);
+
+ // Get the second block
+ EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry));
+ ASSERT_TRUE(block_entry != NULL);
+ EXPECT_FALSE(block_entry->EOS());
+ EXPECT_EQ(BlockEntry::Kind::kBlockGroup, block_entry->GetKind());
+ block_group = static_cast<const BlockGroup*>(block_entry);
+ EXPECT_EQ(6, block_group->GetDurationTimeCode());
+ CompareBlockContents(cluster, block_group->GetBlock(), 2000000,
+ kMetadataTrackNumber, true, 1);
+
+ // End of Stream
+ EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry));
+ EXPECT_EQ(NULL, block_entry);
+ cluster = segment_->GetNext(cluster);
+ EXPECT_TRUE(cluster->EOS());
+}
+
+TEST_F(ParserTest, Cues) {
+ ASSERT_TRUE(CreateAndLoadSegment("output_cues.webm"));
+ const unsigned int kTracksCount = 1;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+
+ const Track* const track = segment_->GetTracks()->GetTrackByIndex(0);
+ const Cues* const cues = segment_->GetCues();
+ ASSERT_TRUE(cues != NULL);
+ while (!cues->DoneParsing()) {
+ cues->LoadCuePoint();
+ }
+ EXPECT_EQ(3, cues->GetCount());
+
+ // Get first Cue Point
+ const CuePoint* cue_point = cues->GetFirst();
+ CompareCuePointContents(track, cue_point, 0, kVideoTrackNumber, 206);
+
+ // Get second Cue Point
+ cue_point = cues->GetNext(cue_point);
+ CompareCuePointContents(track, cue_point, 6000000, kVideoTrackNumber, 269);
+
+ // Get third (also last) Cue Point
+ cue_point = cues->GetNext(cue_point);
+ const CuePoint* last_cue_point = cues->GetLast();
+ EXPECT_TRUE(cue_point == last_cue_point);
+ CompareCuePointContents(track, cue_point, 4000000, kVideoTrackNumber, 269);
+
+ EXPECT_TRUE(ValidateCues(segment_, &reader_));
+}
+
+TEST_F(ParserTest, CuesBeforeClusters) {
+ ASSERT_TRUE(CreateAndLoadSegment("cues_before_clusters.webm"));
+ const unsigned int kTracksCount = 1;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+
+ const Track* const track = segment_->GetTracks()->GetTrackByIndex(0);
+ const Cues* const cues = segment_->GetCues();
+ ASSERT_TRUE(cues != NULL);
+ while (!cues->DoneParsing()) {
+ cues->LoadCuePoint();
+ }
+ EXPECT_EQ(2, cues->GetCount());
+
+ // Get first Cue Point
+ const CuePoint* cue_point = cues->GetFirst();
+ CompareCuePointContents(track, cue_point, 0, kVideoTrackNumber, 238);
+
+ // Get second (also last) Cue Point
+ cue_point = cues->GetNext(cue_point);
+ const CuePoint* last_cue_point = cues->GetLast();
+ EXPECT_TRUE(cue_point == last_cue_point);
+ CompareCuePointContents(track, cue_point, 6000000, kVideoTrackNumber, 301);
+
+ EXPECT_TRUE(ValidateCues(segment_, &reader_));
+}
+
+TEST_F(ParserTest, CuesTrackNumber) {
+ ASSERT_TRUE(CreateAndLoadSegment("set_cues_track_number.webm"));
+ const unsigned int kTracksCount = 1;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+
+ const Track* const track = segment_->GetTracks()->GetTrackByIndex(0);
+ const Cues* const cues = segment_->GetCues();
+ ASSERT_TRUE(cues != NULL);
+ while (!cues->DoneParsing()) {
+ cues->LoadCuePoint();
+ }
+ EXPECT_EQ(2, cues->GetCount());
+
+ // Get first Cue Point
+ const CuePoint* cue_point = cues->GetFirst();
+ CompareCuePointContents(track, cue_point, 0, 10, 206);
+
+ // Get second (also last) Cue Point
+ cue_point = cues->GetNext(cue_point);
+ const CuePoint* last_cue_point = cues->GetLast();
+ EXPECT_TRUE(cue_point == last_cue_point);
+ CompareCuePointContents(track, cue_point, 6000000, 10, 269);
+
+ EXPECT_TRUE(ValidateCues(segment_, &reader_));
+}
+
+TEST_F(ParserTest, Opus) {
+ ASSERT_TRUE(CreateAndLoadSegment("bbb_480p_vp9_opus_1second.webm", 4));
+ const unsigned int kTracksCount = 2;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+
+ // --------------------------------------------------------------------------
+ // Track Header validation.
+ const Tracks* const tracks = segment_->GetTracks();
+ EXPECT_EQ(kTracksCount, tracks->GetTracksCount());
+ for (int i = 0; i < 2; ++i) {
+ const Track* const track = tracks->GetTrackByIndex(i);
+ ASSERT_TRUE(track != NULL);
+
+ EXPECT_EQ(NULL, track->GetNameAsUTF8());
+ EXPECT_STREQ("und", track->GetLanguage());
+ EXPECT_EQ(i + 1, track->GetNumber());
+ EXPECT_FALSE(track->GetLacing());
+
+ if (track->GetType() == Track::kVideo) {
+ const VideoTrack* const video_track =
+ dynamic_cast<const VideoTrack*>(track);
+ EXPECT_EQ(854, static_cast<int>(video_track->GetWidth()));
+ EXPECT_EQ(480, static_cast<int>(video_track->GetHeight()));
+ EXPECT_STREQ(kVP9CodecId, video_track->GetCodecId());
+ EXPECT_DOUBLE_EQ(0., video_track->GetFrameRate());
+ EXPECT_EQ(41666666,
+ static_cast<int>(video_track->GetDefaultDuration())); // 24.000
+ const unsigned int kVideoUid = kVideoTrackNumber;
+ EXPECT_EQ(kVideoUid, video_track->GetUid());
+ const unsigned int kCodecDelay = 0;
+ EXPECT_EQ(kCodecDelay, video_track->GetCodecDelay());
+ const unsigned int kSeekPreRoll = 0;
+ EXPECT_EQ(kSeekPreRoll, video_track->GetSeekPreRoll());
+
+ size_t video_codec_private_size;
+ EXPECT_EQ(NULL, video_track->GetCodecPrivate(video_codec_private_size));
+ const unsigned int kPrivateSize = 0;
+ EXPECT_EQ(kPrivateSize, video_codec_private_size);
+ } else if (track->GetType() == Track::kAudio) {
+ const AudioTrack* const audio_track =
+ dynamic_cast<const AudioTrack*>(track);
+ EXPECT_EQ(48000, audio_track->GetSamplingRate());
+ EXPECT_EQ(6, audio_track->GetChannels());
+ EXPECT_EQ(32, audio_track->GetBitDepth());
+ EXPECT_STREQ(kOpusCodecId, audio_track->GetCodecId());
+ EXPECT_EQ(kAudioTrackNumber, static_cast<int>(audio_track->GetUid()));
+ const unsigned int kDefaultDuration = 0;
+ EXPECT_EQ(kDefaultDuration, audio_track->GetDefaultDuration());
+ EXPECT_EQ(kOpusCodecDelay, audio_track->GetCodecDelay());
+ EXPECT_EQ(kOpusSeekPreroll, audio_track->GetSeekPreRoll());
+
+ size_t audio_codec_private_size;
+ EXPECT_TRUE(audio_track->GetCodecPrivate(audio_codec_private_size) !=
+ NULL);
+ EXPECT_GE(audio_codec_private_size, kOpusPrivateDataSizeMinimum);
+ }
+ }
+
+ // --------------------------------------------------------------------------
+ // Parse the file to do block-level validation.
+ const Cluster* cluster = segment_->GetFirst();
+ ASSERT_TRUE(cluster != NULL);
+ EXPECT_FALSE(cluster->EOS());
+
+ for (; cluster != NULL && !cluster->EOS();
+ cluster = segment_->GetNext(cluster)) {
+ // Get the first block
+ const BlockEntry* block_entry;
+ EXPECT_EQ(0, cluster->GetFirst(block_entry));
+ ASSERT_TRUE(block_entry != NULL);
+ EXPECT_FALSE(block_entry->EOS());
+
+ while (block_entry != NULL && !block_entry->EOS()) {
+ const Block* const block = block_entry->GetBlock();
+ ASSERT_TRUE(block != NULL);
+ EXPECT_FALSE(block->IsInvisible());
+ EXPECT_EQ(Block::kLacingNone, block->GetLacing());
+
+ const std::uint32_t track_number =
+ static_cast<std::uint32_t>(block->GetTrackNumber());
+ const Track* const track = tracks->GetTrackByNumber(track_number);
+ ASSERT_TRUE(track != NULL);
+ EXPECT_EQ(track->GetNumber(), block->GetTrackNumber());
+ const unsigned int kContentEncodingCount = 0;
+ EXPECT_EQ(kContentEncodingCount,
+ track->GetContentEncodingCount()); // no encryption
+
+ const std::int64_t track_type = track->GetType();
+ EXPECT_TRUE(track_type == Track::kVideo || track_type == Track::kAudio);
+ if (track_type == Track::kVideo) {
+ EXPECT_EQ(BlockEntry::kBlockSimple, block_entry->GetKind());
+ EXPECT_EQ(0, block->GetDiscardPadding());
+ } else {
+ EXPECT_TRUE(block->IsKey());
+ const std::int64_t kLastAudioTimecode = 1001;
+ const std::int64_t timecode = block->GetTimeCode(cluster);
+ // Only the final Opus block should have discard padding.
+ if (timecode == kLastAudioTimecode) {
+ EXPECT_EQ(BlockEntry::kBlockGroup, block_entry->GetKind());
+ EXPECT_EQ(13500000, block->GetDiscardPadding());
+ } else {
+ EXPECT_EQ(BlockEntry::kBlockSimple, block_entry->GetKind());
+ EXPECT_EQ(0, block->GetDiscardPadding());
+ }
+ }
+
+ const int frame_count = block->GetFrameCount();
+ const Block::Frame& frame = block->GetFrame(0);
+ EXPECT_EQ(1, frame_count);
+ EXPECT_GT(frame.len, 0);
+
+ EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry));
+ }
+ }
+
+ ASSERT_TRUE(cluster != NULL);
+ EXPECT_TRUE(cluster->EOS());
+}
+
+TEST_F(ParserTest, DiscardPadding) {
+ // Test an artificial file with some extreme DiscardPadding values.
+ const std::string file = "discard_padding.webm";
+ ASSERT_TRUE(CreateAndLoadSegment(file, 4));
+ const unsigned int kTracksCount = 1;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+
+ // --------------------------------------------------------------------------
+ // Track Header validation.
+ const Tracks* const tracks = segment_->GetTracks();
+ EXPECT_EQ(kTracksCount, tracks->GetTracksCount());
+ const Track* const track = tracks->GetTrackByIndex(0);
+ ASSERT_TRUE(track != NULL);
+
+ EXPECT_STREQ(NULL, track->GetNameAsUTF8());
+ EXPECT_EQ(NULL, track->GetLanguage());
+ EXPECT_EQ(kAudioTrackNumber, track->GetNumber());
+ EXPECT_TRUE(track->GetLacing());
+
+ EXPECT_EQ(Track::kAudio, track->GetType());
+ const AudioTrack* const audio_track = dynamic_cast<const AudioTrack*>(track);
+ EXPECT_EQ(30, audio_track->GetSamplingRate());
+ EXPECT_EQ(2, audio_track->GetChannels());
+ EXPECT_STREQ(kOpusCodecId, audio_track->GetCodecId());
+ EXPECT_EQ(kAudioTrackNumber, static_cast<int>(audio_track->GetUid()));
+ const unsigned int kDefaultDuration = 0;
+ EXPECT_EQ(kDefaultDuration, audio_track->GetDefaultDuration());
+ const unsigned int kCodecDelay = 0;
+ EXPECT_EQ(kCodecDelay, audio_track->GetCodecDelay());
+ const unsigned int kSeekPreRoll = 0;
+ EXPECT_EQ(kSeekPreRoll, audio_track->GetSeekPreRoll());
+
+ size_t audio_codec_private_size;
+ EXPECT_EQ(NULL, audio_track->GetCodecPrivate(audio_codec_private_size));
+ const unsigned int kPrivateSize = 0;
+ EXPECT_EQ(kPrivateSize, audio_codec_private_size);
+
+ // --------------------------------------------------------------------------
+ // Parse the file to do block-level validation.
+ const Cluster* cluster = segment_->GetFirst();
+ ASSERT_TRUE(cluster != NULL);
+ EXPECT_FALSE(cluster->EOS());
+ const unsigned int kSegmentCount = 1;
+ EXPECT_EQ(kSegmentCount, segment_->GetCount());
+
+ // Get the first block
+ const BlockEntry* block_entry;
+ EXPECT_EQ(0, cluster->GetFirst(block_entry));
+ ASSERT_TRUE(block_entry != NULL);
+ EXPECT_FALSE(block_entry->EOS());
+
+ const std::array<int, 3> discard_padding = {{12810000, 127, -128}};
+ int index = 0;
+ while (block_entry != NULL && !block_entry->EOS()) {
+ const Block* const block = block_entry->GetBlock();
+ ASSERT_TRUE(block != NULL);
+ EXPECT_FALSE(block->IsInvisible());
+ EXPECT_EQ(Block::kLacingNone, block->GetLacing());
+
+ const std::uint32_t track_number =
+ static_cast<std::uint32_t>(block->GetTrackNumber());
+ const Track* const track = tracks->GetTrackByNumber(track_number);
+ ASSERT_TRUE(track != NULL);
+ EXPECT_EQ(track->GetNumber(), block->GetTrackNumber());
+ const unsigned int kContentEncodingCount = 0;
+ EXPECT_EQ(kContentEncodingCount,
+ track->GetContentEncodingCount()); // no encryption
+
+ const std::int64_t track_type = track->GetType();
+ EXPECT_EQ(Track::kAudio, track_type);
+ EXPECT_TRUE(block->IsKey());
+
+ // All blocks have DiscardPadding.
+ EXPECT_EQ(BlockEntry::kBlockGroup, block_entry->GetKind());
+ ASSERT_LT(index, static_cast<int>(discard_padding.size()));
+ EXPECT_EQ(discard_padding[index], block->GetDiscardPadding());
+ ++index;
+
+ const int frame_count = block->GetFrameCount();
+ const Block::Frame& frame = block->GetFrame(0);
+ EXPECT_EQ(1, frame_count);
+ EXPECT_GT(frame.len, 0);
+
+ EXPECT_EQ(0, cluster->GetNext(block_entry, block_entry));
+ }
+
+ cluster = segment_->GetNext(cluster);
+ ASSERT_TRUE(cluster != NULL);
+ EXPECT_TRUE(cluster->EOS());
+}
+
+TEST_F(ParserTest, StereoModeParsedCorrectly) {
+ ASSERT_TRUE(CreateAndLoadSegment("test_stereo_left_right.webm"));
+ const unsigned int kTracksCount = 1;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+
+ const VideoTrack* const video_track = dynamic_cast<const VideoTrack*>(
+ segment_->GetTracks()->GetTrackByIndex(0));
+
+ EXPECT_EQ(1, video_track->GetStereoMode());
+ EXPECT_EQ(256, video_track->GetWidth());
+ EXPECT_EQ(144, video_track->GetHeight());
+ EXPECT_EQ(128, video_track->GetDisplayWidth());
+ EXPECT_EQ(144, video_track->GetDisplayHeight());
+}
+
+TEST_F(ParserTest, CanParseColour) {
+ ASSERT_TRUE(CreateAndLoadSegment("colour.webm"));
+ const unsigned int kTracksCount = 1;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+ const VideoTrack* const video_track = dynamic_cast<const VideoTrack*>(
+ segment_->GetTracks()->GetTrackByIndex(0));
+
+ const mkvparser::Colour* const colour = video_track->GetColour();
+ ASSERT_TRUE(colour != nullptr);
+ EXPECT_EQ(0u, colour->matrix_coefficients);
+ EXPECT_EQ(1u, colour->bits_per_channel);
+ EXPECT_EQ(2u, colour->chroma_subsampling_horz);
+ EXPECT_EQ(3u, colour->chroma_subsampling_vert);
+ EXPECT_EQ(4u, colour->cb_subsampling_horz);
+ EXPECT_EQ(5u, colour->cb_subsampling_vert);
+ EXPECT_EQ(1u, colour->chroma_siting_horz);
+ EXPECT_EQ(1u, colour->chroma_siting_vert);
+ EXPECT_EQ(2u, colour->range);
+ EXPECT_EQ(9u, colour->transfer_characteristics);
+ EXPECT_EQ(10u, colour->primaries);
+ EXPECT_EQ(11u, colour->max_cll);
+ EXPECT_EQ(12u, colour->max_fall);
+
+ const mkvparser::MasteringMetadata* const mm =
+ video_track->GetColour()->mastering_metadata;
+ ASSERT_TRUE(mm != nullptr);
+ ASSERT_TRUE(mm->r != nullptr);
+ ASSERT_TRUE(mm->g != nullptr);
+ ASSERT_TRUE(mm->b != nullptr);
+ ASSERT_TRUE(mm->white_point != nullptr);
+ EXPECT_FLOAT_EQ(.1, mm->r->x);
+ EXPECT_FLOAT_EQ(.2, mm->r->y);
+ EXPECT_FLOAT_EQ(.1, mm->g->x);
+ EXPECT_FLOAT_EQ(.2, mm->g->y);
+ EXPECT_FLOAT_EQ(.1, mm->b->x);
+ EXPECT_FLOAT_EQ(.2, mm->b->y);
+ EXPECT_FLOAT_EQ(.1, mm->white_point->x);
+ EXPECT_FLOAT_EQ(.2, mm->white_point->y);
+ EXPECT_FLOAT_EQ(30.0, mm->luminance_min);
+ EXPECT_FLOAT_EQ(40.0, mm->luminance_max);
+}
+
+TEST_F(ParserTest, CanParseProjection) {
+ ASSERT_TRUE(CreateAndLoadSegment("projection.webm"));
+ const unsigned int kTracksCount = 1;
+ EXPECT_EQ(kTracksCount, segment_->GetTracks()->GetTracksCount());
+ const VideoTrack* const video_track =
+ static_cast<const VideoTrack*>(segment_->GetTracks()->GetTrackByIndex(0));
+
+ const mkvparser::Projection* const projection = video_track->GetProjection();
+ ASSERT_TRUE(projection != nullptr);
+ EXPECT_EQ(mkvparser::Projection::kRectangular, projection->type);
+ EXPECT_FLOAT_EQ(1, projection->pose_yaw);
+ EXPECT_FLOAT_EQ(2, projection->pose_pitch);
+ EXPECT_FLOAT_EQ(3, projection->pose_roll);
+ EXPECT_EQ(1u, projection->private_data_length);
+ ASSERT_TRUE(projection->private_data != nullptr);
+ EXPECT_EQ(4u, projection->private_data[0]);
+}
+
+TEST_F(ParserTest, Vp9CodecLevelTest) {
+ const int kCodecPrivateLength = 3;
+ const uint8_t good_codec_private_level[kCodecPrivateLength] = {2, 1, 11};
+ libwebm::Vp9CodecFeatures features;
+ EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&good_codec_private_level[0],
+ kCodecPrivateLength, &features));
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.profile);
+ EXPECT_EQ(11, features.level);
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.bit_depth);
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent,
+ features.chroma_subsampling);
+}
+
+TEST_F(ParserTest, Vp9CodecProfileTest) {
+ const int kCodecPrivateLength = 3;
+ const uint8_t good_codec_private_profile[kCodecPrivateLength] = {1, 1, 1};
+ libwebm::Vp9CodecFeatures features;
+ EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&good_codec_private_profile[0],
+ kCodecPrivateLength, &features));
+ EXPECT_EQ(1, features.profile);
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.level);
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.bit_depth);
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent,
+ features.chroma_subsampling);
+}
+
+TEST_F(ParserTest, Vp9CodecBitDepthTest) {
+ const int kCodecPrivateLength = 3;
+ const uint8_t good_codec_private_profile[kCodecPrivateLength] = {3, 1, 8};
+ libwebm::Vp9CodecFeatures features;
+ EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&good_codec_private_profile[0],
+ kCodecPrivateLength, &features));
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.profile);
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.level);
+ EXPECT_EQ(8, features.bit_depth);
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent,
+ features.chroma_subsampling);
+}
+
+TEST_F(ParserTest, Vp9CodecChromaSubsamplingTest) {
+ const int kCodecPrivateLength = 3;
+ const uint8_t good_codec_private_profile[kCodecPrivateLength] = {4, 1, 0};
+ libwebm::Vp9CodecFeatures features;
+ EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&good_codec_private_profile[0],
+ kCodecPrivateLength, &features));
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.profile);
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.level);
+ EXPECT_EQ(libwebm::Vp9CodecFeatures::kValueNotPresent, features.bit_depth);
+ EXPECT_EQ(0, features.chroma_subsampling);
+}
+
+TEST_F(ParserTest, Vp9CodecProfileLevelTest) {
+ const int kCodecPrivateLength = 6;
+ const uint8_t codec_private[kCodecPrivateLength] = {1, 1, 1, 2, 1, 11};
+ libwebm::Vp9CodecFeatures features;
+ EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&codec_private[0],
+ kCodecPrivateLength, &features));
+ EXPECT_EQ(1, features.profile);
+ EXPECT_EQ(11, features.level);
+}
+
+TEST_F(ParserTest, Vp9CodecAllTest) {
+ const int kCodecPrivateLength = 12;
+ const uint8_t codec_private[kCodecPrivateLength] = {1, 1, 1, 2, 1, 11,
+ 3, 1, 8, 4, 1, 0};
+ libwebm::Vp9CodecFeatures features;
+ EXPECT_TRUE(libwebm::ParseVpxCodecPrivate(&codec_private[0],
+ kCodecPrivateLength, &features));
+ EXPECT_EQ(1, features.profile);
+ EXPECT_EQ(11, features.level);
+ EXPECT_EQ(8, features.bit_depth);
+ EXPECT_EQ(0, features.chroma_subsampling);
+}
+
+TEST_F(ParserTest, Vp9CodecPrivateBadTest) {
+ const int kCodecPrivateLength = 3;
+ libwebm::Vp9CodecFeatures features;
+ // Test invalid codec private data; all of these should return false.
+ const uint8_t bad_codec_private[kCodecPrivateLength] = {0, 0, 0};
+ EXPECT_FALSE(
+ libwebm::ParseVpxCodecPrivate(NULL, kCodecPrivateLength, &features));
+ EXPECT_FALSE(
+ libwebm::ParseVpxCodecPrivate(&bad_codec_private[0], 0, &features));
+ EXPECT_FALSE(libwebm::ParseVpxCodecPrivate(&bad_codec_private[0],
+ kCodecPrivateLength, &features));
+ const uint8_t good_codec_private_level[kCodecPrivateLength] = {2, 1, 11};
+
+ // Test parse of codec private chunks, but lie about length.
+ EXPECT_FALSE(
+ libwebm::ParseVpxCodecPrivate(&bad_codec_private[0], 0, &features));
+ EXPECT_FALSE(libwebm::ParseVpxCodecPrivate(&good_codec_private_level[0], 0,
+ &features));
+ EXPECT_FALSE(libwebm::ParseVpxCodecPrivate(&good_codec_private_level[0],
+ kCodecPrivateLength, NULL));
+}
+
+TEST_F(ParserTest, InvalidTruncatedChapterString) {
+ ASSERT_NO_FATAL_FAILURE(CreateSegmentNoHeaderChecks(
+ "invalid/chapters_truncated_chapter_string.mkv"));
+ EXPECT_EQ(mkvparser::E_PARSE_FAILED, segment_->Load());
+}
+
+TEST_F(ParserTest, InvalidTruncatedChapterString2) {
+ ASSERT_NO_FATAL_FAILURE(CreateSegmentNoHeaderChecks(
+ "invalid/chapters_truncated_chapter_string_2.mkv"));
+ EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID, segment_->Load());
+}
+
+TEST_F(ParserTest, InvalidFixedLacingSize) {
+ ASSERT_NO_FATAL_FAILURE(
+ CreateSegmentNoHeaderChecks("invalid/fixed_lacing_bad_lace_size.mkv"));
+ ASSERT_EQ(0, segment_->Load());
+ const mkvparser::BlockEntry* block_entry = NULL;
+ EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID,
+ segment_->GetFirst()->GetFirst(block_entry));
+}
+
+TEST_F(ParserTest, InvalidBlockEndsBeyondCluster) {
+ ASSERT_NO_FATAL_FAILURE(
+ CreateSegmentNoHeaderChecks("invalid/block_ends_beyond_cluster.mkv"));
+ ASSERT_EQ(0, segment_->Load());
+ const mkvparser::BlockEntry* block_entry = NULL;
+ EXPECT_EQ(0, segment_->GetFirst()->GetFirst(block_entry));
+ EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID,
+ segment_->GetFirst()->GetNext(block_entry, block_entry));
+}
+
+TEST_F(ParserTest, InvalidBlockGroupBlockEndsBlockGroup) {
+ ASSERT_NO_FATAL_FAILURE(CreateSegmentNoHeaderChecks(
+ "invalid/blockgroup_block_ends_beyond_blockgroup.mkv"));
+ ASSERT_EQ(0, segment_->Load());
+ const mkvparser::BlockEntry* block_entry = NULL;
+ EXPECT_EQ(0, segment_->GetFirst()->GetFirst(block_entry));
+ EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID,
+ segment_->GetFirst()->GetNext(block_entry, block_entry));
+}
+
+TEST_F(ParserTest, InvalidProjectionFloatOverflow) {
+ ASSERT_NO_FATAL_FAILURE(
+ CreateSegmentNoHeaderChecks("invalid/projection_float_overflow.webm"));
+ EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID, segment_->Load());
+}
+
+TEST_F(ParserTest, InvalidPrimaryChromaticityParseFail) {
+ ASSERT_NO_FATAL_FAILURE(CreateSegmentNoHeaderChecks(
+ "invalid/primarychromaticity_fieldtoolarge.webm"));
+ EXPECT_EQ(mkvparser::E_FILE_FORMAT_INVALID, segment_->Load());
+}
+
+} // namespace test
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/testing/test_util.cc b/testing/test_util.cc
new file mode 100644
index 0000000..8789b33
--- /dev/null
+++ b/testing/test_util.cc
@@ -0,0 +1,215 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "testing/test_util.h"
+
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <ios>
+#include <string>
+
+#include "common/libwebm_util.h"
+#include "common/webmids.h"
+
+#include "mkvparser/mkvparser.h"
+#include "mkvparser/mkvreader.h"
+
+namespace test {
+
+std::string GetTestDataDir() {
+ const char* test_data_path = std::getenv("LIBWEBM_TEST_DATA_PATH");
+ return test_data_path ? std::string(test_data_path) : std::string();
+}
+
+std::string GetTestFilePath(const std::string& name) {
+ const std::string libwebm_testdata_dir = GetTestDataDir();
+ return libwebm_testdata_dir + "/" + name;
+}
+
+bool CompareFiles(const std::string& file1, const std::string& file2) {
+ const std::size_t kBlockSize = 4096;
+ std::uint8_t buf1[kBlockSize] = {0};
+ std::uint8_t buf2[kBlockSize] = {0};
+
+ libwebm::FilePtr f1 =
+ libwebm::FilePtr(std::fopen(file1.c_str(), "rb"), libwebm::FILEDeleter());
+ libwebm::FilePtr f2 =
+ libwebm::FilePtr(std::fopen(file2.c_str(), "rb"), libwebm::FILEDeleter());
+
+ if (!f1.get() || !f2.get()) {
+ // Files cannot match if one or both couldn't be opened.
+ return false;
+ }
+
+ do {
+ const std::size_t r1 = std::fread(buf1, 1, kBlockSize, f1.get());
+ const std::size_t r2 = std::fread(buf2, 1, kBlockSize, f2.get());
+
+ // TODO(fgalligan): Add output of which byte differs.
+ if (r1 != r2 || std::memcmp(buf1, buf2, r1)) {
+ return 0; // Files are not equal
+ }
+ } while (!std::feof(f1.get()) && !std::feof(f2.get()));
+
+ return std::feof(f1.get()) && std::feof(f2.get());
+}
+
+bool HasCuePoints(const mkvparser::Segment* segment,
+ std::int64_t* cues_offset) {
+ if (!segment || !cues_offset) {
+ return false;
+ }
+ using mkvparser::SeekHead;
+ const SeekHead* const seek_head = segment->GetSeekHead();
+ if (!seek_head) {
+ return false;
+ }
+
+ std::int64_t offset = 0;
+ for (int i = 0; i < seek_head->GetCount(); ++i) {
+ const SeekHead::Entry* const entry = seek_head->GetEntry(i);
+ if (entry->id == libwebm::kMkvCues) {
+ offset = entry->pos;
+ }
+ }
+
+ if (offset <= 0) {
+ // No Cues found.
+ return false;
+ }
+
+ *cues_offset = offset;
+ return true;
+}
+
+bool ValidateCues(mkvparser::Segment* segment, mkvparser::IMkvReader* reader) {
+ if (!segment) {
+ return false;
+ }
+
+ std::int64_t cues_offset = 0;
+ if (!HasCuePoints(segment, &cues_offset)) {
+ // No cues to validate, everything is OK.
+ return true;
+ }
+
+ // Parse Cues.
+ long long cues_pos = 0; // NOLINT
+ long cues_len = 0; // NOLINT
+ if (segment->ParseCues(cues_offset, cues_pos, cues_len)) {
+ return false;
+ }
+
+ // Get a pointer to the video track if it exists. Otherwise, we assume
+ // that Cues are based on the first track (which is true for all our test
+ // files).
+ const mkvparser::Tracks* const tracks = segment->GetTracks();
+ const mkvparser::Track* cues_track = tracks->GetTrackByIndex(0);
+ for (int i = 1; i < static_cast<int>(tracks->GetTracksCount()); ++i) {
+ const mkvparser::Track* const track = tracks->GetTrackByIndex(i);
+ if (track->GetType() == mkvparser::Track::kVideo) {
+ cues_track = track;
+ break;
+ }
+ }
+
+ // Iterate through Cues and verify if they are pointing to the correct
+ // Cluster position.
+ const mkvparser::Cues* const cues = segment->GetCues();
+ const mkvparser::CuePoint* cue_point = NULL;
+ while (cues->LoadCuePoint()) {
+ if (!cue_point) {
+ cue_point = cues->GetFirst();
+ } else {
+ cue_point = cues->GetNext(cue_point);
+ }
+ const mkvparser::CuePoint::TrackPosition* const track_position =
+ cue_point->Find(cues_track);
+ const long long cluster_pos = track_position->m_pos + // NOLINT
+ segment->m_start;
+
+ // If a cluster does not begin at |cluster_pos|, then the file is
+ // incorrect.
+ long length; // NOLINT
+ const std::int64_t id = mkvparser::ReadID(reader, cluster_pos, length);
+ if (id != libwebm::kMkvCluster) {
+ return false;
+ }
+ }
+ return true;
+}
+
+MkvParser::~MkvParser() {
+ delete segment;
+ delete reader;
+}
+
+bool ParseMkvFileReleaseParser(const std::string& webm_file,
+ MkvParser* parser_out) {
+ parser_out->reader = new (std::nothrow) mkvparser::MkvReader;
+ mkvparser::MkvReader& reader = *parser_out->reader;
+ if (!parser_out->reader || reader.Open(webm_file.c_str()) < 0) {
+ return false;
+ }
+
+ long long pos = 0; // NOLINT
+ mkvparser::EBMLHeader ebml_header;
+ if (ebml_header.Parse(&reader, pos)) {
+ return false;
+ }
+
+ using mkvparser::Segment;
+ Segment* segment_ptr = nullptr;
+ if (Segment::CreateInstance(&reader, pos, segment_ptr)) {
+ return false;
+ }
+
+ std::unique_ptr<Segment> segment(segment_ptr);
+ long result;
+ if ((result = segment->Load()) < 0) {
+ return false;
+ }
+
+ const mkvparser::Cluster* cluster = segment->GetFirst();
+ if (!cluster || cluster->EOS()) {
+ return false;
+ }
+
+ while (cluster && cluster->EOS() == false) {
+ if (cluster->GetTimeCode() < 0) {
+ return false;
+ }
+
+ const mkvparser::BlockEntry* block = nullptr;
+ if (cluster->GetFirst(block) < 0) {
+ return false;
+ }
+
+ while (block != NULL && block->EOS() == false) {
+ if (cluster->GetNext(block, block) < 0) {
+ return false;
+ }
+ }
+
+ cluster = segment->GetNext(cluster);
+ }
+
+ parser_out->segment = segment.release();
+ return true;
+}
+
+bool ParseMkvFile(const std::string& webm_file) {
+ MkvParser parser;
+ const bool result = ParseMkvFileReleaseParser(webm_file, &parser);
+ delete parser.segment;
+ delete parser.reader;
+ return result;
+}
+
+} // namespace test
diff --git a/testing/test_util.h b/testing/test_util.h
new file mode 100644
index 0000000..5f85ec7
--- /dev/null
+++ b/testing/test_util.h
@@ -0,0 +1,88 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_TESTING_TEST_UTIL_H_
+#define LIBWEBM_TESTING_TEST_UTIL_H_
+
+#include <stdint.h>
+
+#include <cstddef>
+#include <string>
+
+namespace mkvparser {
+class IMkvReader;
+class MkvReader;
+class Segment;
+} // namespace mkvparser
+
+namespace test {
+
+// constants for muxer and parser tests
+const char kAppString[] = "mkvmuxer_unit_tests";
+const char kOpusCodecId[] = "A_OPUS";
+const char kVorbisCodecId[] = "A_VORBIS";
+const int kAudioTrackNumber = 2;
+const int kBitDepth = 2;
+const int kChannels = 2;
+const double kDuration = 2.345;
+const int kFrameLength = 10;
+const int kHeight = 180;
+const int kInvalidTrackNumber = 100;
+const std::uint64_t kOpusCodecDelay = 6500000;
+const std::size_t kOpusPrivateDataSizeMinimum = 19;
+const std::uint64_t kOpusSeekPreroll = 80000000;
+const char kMetadataCodecId[] = "D_WEBVTT/METADATA";
+const int kMetadataTrackNumber = 3;
+const int kMetadataTrackType = 0x21;
+const int kSampleRate = 30;
+const int kTimeCodeScale = 1000;
+const char kTrackName[] = "unit_test";
+const char kVP8CodecId[] = "V_VP8";
+const char kVP9CodecId[] = "V_VP9";
+const double kVideoFrameRate = 0.5;
+const int kVideoTrackNumber = 1;
+const int kWidth = 320;
+
+// Returns the path to the test data directory by reading and returning the
+// contents the LIBWEBM_TESTDATA_DIR environment variable.
+std::string GetTestDataDir();
+
+// Returns the absolute path to the file of |name| in LIBWEBM_TESTDATA_DIR.
+std::string GetTestFilePath(const std::string& name);
+
+// Byte-wise comparison of two files |file1| and |file2|. Returns true if the
+// files match exactly, false otherwise.
+bool CompareFiles(const std::string& file1, const std::string& file2);
+
+// Returns true and sets |cues_offset| to the cues location within the MKV file
+// parsed by |segment| when the MKV file has cue points.
+bool HasCuePoints(const mkvparser::Segment* segment, std::int64_t* cues_offset);
+
+// Validates cue points. Assumes caller has already called Load() on |segment|.
+// Returns true when:
+// All cue points point at clusters, OR
+// Data parsed by |segment| has no cue points.
+bool ValidateCues(mkvparser::Segment* segment, mkvparser::IMkvReader* reader);
+
+// Parses |webm_file| using mkvparser and returns true when file parses
+// successfully (all clusters and blocks can be successfully walked). Second
+// variant allows further interaction with the parsed file via transferring
+// ownership of the mkvparser Segment and MkvReader to the caller via
+// |parser_out|.
+struct MkvParser {
+ MkvParser() = default;
+ ~MkvParser();
+ mkvparser::Segment* segment = nullptr;
+ mkvparser::MkvReader* reader = nullptr;
+};
+bool ParseMkvFile(const std::string& webm_file);
+bool ParseMkvFileReleaseParser(const std::string& webm_file,
+ MkvParser* parser_out);
+
+} // namespace test
+
+#endif // LIBWEBM_TESTING_TEST_UTIL_H_ \ No newline at end of file
diff --git a/testing/testdata/accurate_cluster_duration.webm b/testing/testdata/accurate_cluster_duration.webm
new file mode 100644
index 0000000..fffe83d
--- /dev/null
+++ b/testing/testdata/accurate_cluster_duration.webm
Binary files differ
diff --git a/testing/testdata/accurate_cluster_duration_last_frame.webm b/testing/testdata/accurate_cluster_duration_last_frame.webm
new file mode 100644
index 0000000..8104bfc
--- /dev/null
+++ b/testing/testdata/accurate_cluster_duration_last_frame.webm
Binary files differ
diff --git a/testing/testdata/accurate_cluster_duration_two_tracks.webm b/testing/testdata/accurate_cluster_duration_two_tracks.webm
new file mode 100644
index 0000000..cf69859
--- /dev/null
+++ b/testing/testdata/accurate_cluster_duration_two_tracks.webm
Binary files differ
diff --git a/testing/testdata/bbb_480p_vp9_opus_1second.webm b/testing/testdata/bbb_480p_vp9_opus_1second.webm
new file mode 100644
index 0000000..c3657a7
--- /dev/null
+++ b/testing/testdata/bbb_480p_vp9_opus_1second.webm
Binary files differ
diff --git a/testing/testdata/block_with_additional.webm b/testing/testdata/block_with_additional.webm
new file mode 100644
index 0000000..2f1b22c
--- /dev/null
+++ b/testing/testdata/block_with_additional.webm
Binary files differ
diff --git a/testing/testdata/chapters.webm b/testing/testdata/chapters.webm
new file mode 100644
index 0000000..af0c5fe
--- /dev/null
+++ b/testing/testdata/chapters.webm
Binary files differ
diff --git a/testing/testdata/colour.webm b/testing/testdata/colour.webm
new file mode 100644
index 0000000..580cb50
--- /dev/null
+++ b/testing/testdata/colour.webm
Binary files differ
diff --git a/testing/testdata/cues_before_clusters.webm b/testing/testdata/cues_before_clusters.webm
new file mode 100644
index 0000000..c634b17
--- /dev/null
+++ b/testing/testdata/cues_before_clusters.webm
Binary files differ
diff --git a/testing/testdata/discard_padding.webm b/testing/testdata/discard_padding.webm
new file mode 100644
index 0000000..a573226
--- /dev/null
+++ b/testing/testdata/discard_padding.webm
Binary files differ
diff --git a/testing/testdata/estimate_duration.webm b/testing/testdata/estimate_duration.webm
new file mode 100644
index 0000000..c754741
--- /dev/null
+++ b/testing/testdata/estimate_duration.webm
Binary files differ
diff --git a/testing/testdata/fixed_size_cluster_timecode.webm b/testing/testdata/fixed_size_cluster_timecode.webm
new file mode 100644
index 0000000..c6f1cc6
--- /dev/null
+++ b/testing/testdata/fixed_size_cluster_timecode.webm
Binary files differ
diff --git a/testing/testdata/force_new_cluster.webm b/testing/testdata/force_new_cluster.webm
new file mode 100644
index 0000000..f762b07
--- /dev/null
+++ b/testing/testdata/force_new_cluster.webm
Binary files differ
diff --git a/testing/testdata/invalid/README.libwebm b/testing/testdata/invalid/README.libwebm
new file mode 100644
index 0000000..f9e9413
--- /dev/null
+++ b/testing/testdata/invalid/README.libwebm
@@ -0,0 +1,24 @@
+Why the files in this directory are considered invalid:
+
+block_ends_beyond_cluster.mkv -
+ File containing a single cluster with two simple blocks. One valid, and the
+ second reporting a size that would cause the block to end far beyond the end
+ of its parent cluster.
+
+blockgroup_block_ends_beyond_blockgroup.mkv -
+ File containing a single cluster and two blockgroups. The first blockgroup is
+ valid. The second blockgroup contains a block reporting a size that spans well
+ past the block and the end of the file.
+
+chapters_truncated_chapter_string.mkv -
+ File with a Chapters element that ends with a ChapterAtom whose ChapterDisplay
+ element contains a truncated ChapterString.
+
+chapters_truncated_chapter_string_2.mkv -
+ Nearly identical to chapters_truncated_chapter_string.mkv, but with a void
+ element and a partial cluster. Causes mkvparser to fail in a slightly
+ different manner.
+
+fixed_lacing_bad_lace_size.mkv -
+ File containing a BlockGroup with fixed lacing, but reports a total laced size
+ that is not evenly divisible by the number of laced frames.
diff --git a/testing/testdata/invalid/block_ends_beyond_cluster.mkv b/testing/testdata/invalid/block_ends_beyond_cluster.mkv
new file mode 100644
index 0000000..3035c8c
--- /dev/null
+++ b/testing/testdata/invalid/block_ends_beyond_cluster.mkv
Binary files differ
diff --git a/testing/testdata/invalid/blockgroup_block_ends_beyond_blockgroup.mkv b/testing/testdata/invalid/blockgroup_block_ends_beyond_blockgroup.mkv
new file mode 100644
index 0000000..da9aca9
--- /dev/null
+++ b/testing/testdata/invalid/blockgroup_block_ends_beyond_blockgroup.mkv
Binary files differ
diff --git a/testing/testdata/invalid/chapters_truncated_chapter_string.mkv b/testing/testdata/invalid/chapters_truncated_chapter_string.mkv
new file mode 100644
index 0000000..9883e77
--- /dev/null
+++ b/testing/testdata/invalid/chapters_truncated_chapter_string.mkv
Binary files differ
diff --git a/testing/testdata/invalid/chapters_truncated_chapter_string_2.mkv b/testing/testdata/invalid/chapters_truncated_chapter_string_2.mkv
new file mode 100644
index 0000000..37f0403
--- /dev/null
+++ b/testing/testdata/invalid/chapters_truncated_chapter_string_2.mkv
Binary files differ
diff --git a/testing/testdata/invalid/fixed_lacing_bad_lace_size.mkv b/testing/testdata/invalid/fixed_lacing_bad_lace_size.mkv
new file mode 100644
index 0000000..bbe2025
--- /dev/null
+++ b/testing/testdata/invalid/fixed_lacing_bad_lace_size.mkv
Binary files differ
diff --git a/testing/testdata/invalid/invalid_vp9_bitstream-bug_1416.webm b/testing/testdata/invalid/invalid_vp9_bitstream-bug_1416.webm
new file mode 100644
index 0000000..ac76dce
--- /dev/null
+++ b/testing/testdata/invalid/invalid_vp9_bitstream-bug_1416.webm
Binary files differ
diff --git a/testing/testdata/invalid/invalid_vp9_bitstream-bug_1417.webm b/testing/testdata/invalid/invalid_vp9_bitstream-bug_1417.webm
new file mode 100644
index 0000000..0cbd724
--- /dev/null
+++ b/testing/testdata/invalid/invalid_vp9_bitstream-bug_1417.webm
Binary files differ
diff --git a/testing/testdata/invalid/primarychromaticity_fieldtoolarge.webm b/testing/testdata/invalid/primarychromaticity_fieldtoolarge.webm
new file mode 100644
index 0000000..c432425
--- /dev/null
+++ b/testing/testdata/invalid/primarychromaticity_fieldtoolarge.webm
@@ -0,0 +1 @@
+EߣŸB00B00B00B00B‚„webmB00B00S€g0000000»‹00000000000»Œ000000000000ì¼000000000000000000000000000000000000000000000000000000000000€“0000000000000000000W0“0000000000000000000T®k@ž®@›×0s00ƒ†…00000à@‡°‚00º0U°ýU¸0U¹0Uº0U»0U¼0U½0UÐÆUÙ„0000UÚ„0000UÑ„0000UÒ000000000000000000000000000000000000000000000000000000000000000000000000000 \ No newline at end of file
diff --git a/testing/testdata/invalid/projection_float_overflow.webm b/testing/testdata/invalid/projection_float_overflow.webm
new file mode 100644
index 0000000..46f6208
--- /dev/null
+++ b/testing/testdata/invalid/projection_float_overflow.webm
Binary files differ
diff --git a/testing/testdata/long_tag_string.webm b/testing/testdata/long_tag_string.webm
new file mode 100644
index 0000000..e8d6653
--- /dev/null
+++ b/testing/testdata/long_tag_string.webm
Binary files differ
diff --git a/testing/testdata/matroska_doctype.mkv b/testing/testdata/matroska_doctype.mkv
new file mode 100644
index 0000000..5652785
--- /dev/null
+++ b/testing/testdata/matroska_doctype.mkv
Binary files differ
diff --git a/testing/testdata/max_cluster_duration.webm b/testing/testdata/max_cluster_duration.webm
new file mode 100644
index 0000000..9caad9c
--- /dev/null
+++ b/testing/testdata/max_cluster_duration.webm
Binary files differ
diff --git a/testing/testdata/max_cluster_size.webm b/testing/testdata/max_cluster_size.webm
new file mode 100644
index 0000000..df87536
--- /dev/null
+++ b/testing/testdata/max_cluster_size.webm
Binary files differ
diff --git a/testing/testdata/metadata_block.webm b/testing/testdata/metadata_block.webm
new file mode 100644
index 0000000..a9c686e
--- /dev/null
+++ b/testing/testdata/metadata_block.webm
Binary files differ
diff --git a/testing/testdata/output_cues.webm b/testing/testdata/output_cues.webm
new file mode 100644
index 0000000..49e2126
--- /dev/null
+++ b/testing/testdata/output_cues.webm
Binary files differ
diff --git a/testing/testdata/projection.webm b/testing/testdata/projection.webm
new file mode 100644
index 0000000..0f13e8a
--- /dev/null
+++ b/testing/testdata/projection.webm
Binary files differ
diff --git a/testing/testdata/segment_duration.webm b/testing/testdata/segment_duration.webm
new file mode 100644
index 0000000..47cfb6d
--- /dev/null
+++ b/testing/testdata/segment_duration.webm
Binary files differ
diff --git a/testing/testdata/segment_info.webm b/testing/testdata/segment_info.webm
new file mode 100644
index 0000000..0fde8f0
--- /dev/null
+++ b/testing/testdata/segment_info.webm
Binary files differ
diff --git a/testing/testdata/set_cues_track_number.webm b/testing/testdata/set_cues_track_number.webm
new file mode 100644
index 0000000..82a2b38
--- /dev/null
+++ b/testing/testdata/set_cues_track_number.webm
Binary files differ
diff --git a/testing/testdata/set_pixelwidth_pixelheight.webm b/testing/testdata/set_pixelwidth_pixelheight.webm
new file mode 100644
index 0000000..e81c8bc
--- /dev/null
+++ b/testing/testdata/set_pixelwidth_pixelheight.webm
Binary files differ
diff --git a/testing/testdata/set_segment_duration.webm b/testing/testdata/set_segment_duration.webm
new file mode 100644
index 0000000..100684b
--- /dev/null
+++ b/testing/testdata/set_segment_duration.webm
Binary files differ
diff --git a/testing/testdata/simple_block.webm b/testing/testdata/simple_block.webm
new file mode 100644
index 0000000..c311e68
--- /dev/null
+++ b/testing/testdata/simple_block.webm
Binary files differ
diff --git a/testing/testdata/test_stereo_left_right.webm b/testing/testdata/test_stereo_left_right.webm
new file mode 100644
index 0000000..8c717dc
--- /dev/null
+++ b/testing/testdata/test_stereo_left_right.webm
Binary files differ
diff --git a/testing/testdata/tracks.webm b/testing/testdata/tracks.webm
new file mode 100644
index 0000000..8aa9274
--- /dev/null
+++ b/testing/testdata/tracks.webm
Binary files differ
diff --git a/testing/testdata/webm_doctype.webm b/testing/testdata/webm_doctype.webm
new file mode 100644
index 0000000..4269030
--- /dev/null
+++ b/testing/testdata/webm_doctype.webm
Binary files differ
diff --git a/testing/video_frame_tests.cc b/testing/video_frame_tests.cc
new file mode 100644
index 0000000..4e5b397
--- /dev/null
+++ b/testing/video_frame_tests.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "common/video_frame.h"
+
+#include "gtest/gtest.h"
+
+namespace {
+const libwebm::VideoFrame::Codec kCodec = libwebm::VideoFrame::kVP8;
+const std::int64_t kPts = 12345;
+const std::size_t kSize = 1;
+const std::size_t kEmptySize = 0;
+
+TEST(VideoFrameTests, DefaultsTest) {
+ libwebm::VideoFrame frame;
+ EXPECT_EQ(kEmptySize, frame.buffer().capacity);
+ EXPECT_EQ(kEmptySize, frame.buffer().length);
+ EXPECT_EQ(nullptr, frame.buffer().data.get());
+ EXPECT_FALSE(frame.keyframe());
+ EXPECT_EQ(0, frame.nanosecond_pts());
+ EXPECT_EQ(libwebm::VideoFrame::kVP9, frame.codec());
+}
+
+TEST(VideoFrameTests, SizeTest) {
+ libwebm::VideoFrame frame;
+ EXPECT_TRUE(frame.Init(kSize));
+
+ // Buffer inits empty, length should be 0, aka |kEmpty|.
+ EXPECT_GT(kSize, frame.buffer().length);
+ EXPECT_EQ(kEmptySize, frame.buffer().length);
+
+ // Capacity should be equal to |kSize|.
+ EXPECT_EQ(kSize, frame.buffer().capacity);
+ EXPECT_FALSE(frame.SetBufferLength(kSize + 1));
+
+ // Write a byte into the buffer via the raw data pointer, update length, and
+ // verify expected behavior.
+ uint8_t* write_ptr = reinterpret_cast<uint8_t*>(frame.buffer().data.get());
+ *write_ptr = 0xFF;
+ EXPECT_TRUE(frame.SetBufferLength(1));
+ EXPECT_EQ(frame.buffer().length, frame.buffer().capacity);
+}
+
+TEST(VideoFrameTests, OverloadsTest) {
+ const bool kKeyframe = true;
+
+ // Test VideoFrame::VideoFrame(bool keyframe, int64_t nano_pts, Codec c).
+ libwebm::VideoFrame keyframe(kKeyframe, kPts, kCodec);
+ EXPECT_EQ(kKeyframe, keyframe.keyframe());
+ EXPECT_EQ(kPts, keyframe.nanosecond_pts());
+ EXPECT_EQ(kCodec, keyframe.codec());
+ EXPECT_EQ(kEmptySize, keyframe.buffer().capacity);
+ EXPECT_EQ(kEmptySize, keyframe.buffer().length);
+ EXPECT_EQ(nullptr, keyframe.buffer().data.get());
+
+ // Test VideoFrame::Init(std::size_t length).
+ EXPECT_TRUE(keyframe.Init(kSize));
+ EXPECT_EQ(kKeyframe, keyframe.keyframe());
+ EXPECT_EQ(kPts, keyframe.nanosecond_pts());
+ EXPECT_EQ(kCodec, keyframe.codec());
+ EXPECT_NE(nullptr, keyframe.buffer().data.get());
+
+ // Test VideoFrame::Init(size_t length, int64_t nano_pts, Codec c).
+ EXPECT_TRUE(keyframe.Init(kSize, kPts + 1, libwebm::VideoFrame::kVP9));
+ EXPECT_EQ(kSize, keyframe.buffer().capacity);
+ EXPECT_GT(kSize, keyframe.buffer().length);
+ EXPECT_NE(kPts, keyframe.nanosecond_pts());
+ EXPECT_NE(kCodec, keyframe.codec());
+}
+
+} // namespace
diff --git a/vttdemux.cc b/vttdemux.cc
new file mode 100644
index 0000000..186783b
--- /dev/null
+++ b/vttdemux.cc
@@ -0,0 +1,1004 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "mkvparser/mkvparser.h"
+#include "mkvparser/mkvreader.h"
+#include "webvtt/webvttparser.h"
+
+using std::string;
+
+namespace libwebm {
+namespace vttdemux {
+
+typedef long long mkvtime_t; // NOLINT
+typedef long long mkvpos_t; // NOLINT
+typedef std::unique_ptr<mkvparser::Segment> segment_ptr_t;
+
+// WebVTT metadata tracks have a type (encoded in the CodecID for the track).
+// We use |type| to synthesize a filename for the out-of-band WebVTT |file|.
+struct MetadataInfo {
+ enum Type { kSubtitles, kCaptions, kDescriptions, kMetadata, kChapters } type;
+ FILE* file;
+};
+
+// We use a map, indexed by track number, to collect information about
+// each track in the input file.
+typedef std::map<long, MetadataInfo> metadata_map_t; // NOLINT
+
+// The distinguished key value we use to store the chapters
+// information in the metadata map.
+enum { kChaptersKey = 0 };
+
+// The data from the original WebVTT Cue is stored as a WebM block.
+// The FrameParser is used to parse the lines of text out from the
+// block, in order to reconstruct the original WebVTT Cue.
+class FrameParser : public libwebvtt::LineReader {
+ public:
+ // Bind the FrameParser instance to a WebM block.
+ explicit FrameParser(const mkvparser::BlockGroup* block_group);
+ virtual ~FrameParser();
+
+ // The Webm block (group) to which this instance is bound. We
+ // treat the payload of the block as a stream of characters.
+ const mkvparser::BlockGroup* const block_group_;
+
+ protected:
+ // Read the next character from the character stream (the payload
+ // of the WebM block). We increment the stream pointer |pos_| as
+ // each character from the stream is consumed.
+ virtual int GetChar(char* c);
+
+ // End-of-line handling requires that we put a character back into
+ // the stream. Here we need only decrement the stream pointer |pos_|
+ // to unconsume the character.
+ virtual void UngetChar(char c);
+
+ // The current position in the character stream (the payload of the block).
+ mkvpos_t pos_;
+
+ // The position of the end of the character stream. When the current
+ // position |pos_| equals the end position |pos_end_|, the entire
+ // stream (block payload) has been consumed and end-of-stream is indicated.
+ mkvpos_t pos_end_;
+
+ private:
+ // Disable copy ctor and copy assign
+ FrameParser(const FrameParser&);
+ FrameParser& operator=(const FrameParser&);
+};
+
+// The data from the original WebVTT Cue is stored as an MKV Chapters
+// Atom element (the cue payload is stored as a Display sub-element).
+// The ChapterAtomParser is used to parse the lines of text out from
+// the String sub-element of the Display element (though it would be
+// admittedly odd if there were more than one line).
+class ChapterAtomParser : public libwebvtt::LineReader {
+ public:
+ explicit ChapterAtomParser(const mkvparser::Chapters::Display* display);
+ virtual ~ChapterAtomParser();
+
+ const mkvparser::Chapters::Display* const display_;
+
+ protected:
+ // Read the next character from the character stream (the title
+ // member of the atom's display). We increment the stream pointer
+ // |str_| as each character from the stream is consumed.
+ virtual int GetChar(char* c);
+
+ // End-of-line handling requires that we put a character back into
+ // the stream. Here we need only decrement the stream pointer |str_|
+ // to unconsume the character.
+ virtual void UngetChar(char c);
+
+ // The current position in the character stream (the title of the
+ // atom's display).
+ const char* str_;
+
+ // The position of the end of the character stream. When the current
+ // position |str_| equals the end position |str_end_|, the entire
+ // stream (title of the display) has been consumed and end-of-stream
+ // is indicated.
+ const char* str_end_;
+
+ private:
+ ChapterAtomParser(const ChapterAtomParser&);
+ ChapterAtomParser& operator=(const ChapterAtomParser&);
+};
+
+// Parse the EBML header of the WebM input file, to determine whether we
+// actually have a WebM file. Returns false if this is not a WebM file.
+bool ParseHeader(mkvparser::IMkvReader* reader, mkvpos_t* pos);
+
+// Parse the Segment of the input file and load all of its clusters.
+// Returns false if there was an error parsing the file.
+bool ParseSegment(mkvparser::IMkvReader* reader, mkvpos_t pos,
+ segment_ptr_t* segment);
+
+// If |segment| has a Chapters element (in which case, there will be a
+// corresponding entry in |metadata_map|), convert the MKV chapters to
+// WebVTT chapter cues and write them to the output file. Returns
+// false on error.
+bool WriteChaptersFile(const metadata_map_t& metadata_map,
+ const mkvparser::Segment* segment);
+
+// Convert an MKV Chapters Atom to a WebVTT cue and write it to the
+// output |file|. Returns false on error.
+bool WriteChaptersCue(FILE* file, const mkvparser::Chapters* chapters,
+ const mkvparser::Chapters::Atom* atom,
+ const mkvparser::Chapters::Display* display);
+
+// Write the Cue Identifier line of the WebVTT cue, if it's present.
+// Returns false on error.
+bool WriteChaptersCueIdentifier(FILE* file,
+ const mkvparser::Chapters::Atom* atom);
+
+// Use the timecodes from the chapters |atom| to write just the
+// timings line of the WebVTT cue. Returns false on error.
+bool WriteChaptersCueTimings(FILE* file, const mkvparser::Chapters* chapters,
+ const mkvparser::Chapters::Atom* atom);
+
+// Parse the String sub-element of the |display| and write the payload
+// of the WebVTT cue. Returns false on error.
+bool WriteChaptersCuePayload(FILE* file,
+ const mkvparser::Chapters::Display* display);
+
+// Iterate over the tracks of the input file (and any chapters
+// element) and cache information about each metadata track.
+void BuildMap(const mkvparser::Segment* segment, metadata_map_t* metadata_map);
+
+// For each track listed in the cache, synthesize its output filename
+// and open a file handle that designates the out-of-band file.
+// Returns false if we were unable to open an output file for a track.
+bool OpenFiles(metadata_map_t* metadata_map, const char* filename);
+
+// Close the file handle for each track in the cache.
+void CloseFiles(metadata_map_t* metadata_map);
+
+// Iterate over the clusters of the input file, and write a WebVTT cue
+// for each metadata block. Returns false if processing of a cluster
+// failed.
+bool WriteFiles(const metadata_map_t& m, mkvparser::Segment* s);
+
+// Write the WebVTT header for each track in the cache. We do this
+// immediately before writing the actual WebVTT cues. Returns false
+// if the write failed.
+bool InitializeFiles(const metadata_map_t& metadata_map);
+
+// Iterate over the blocks of the |cluster|, writing a WebVTT cue to
+// its associated output file for each block of metadata. Returns
+// false if processing a block failed, or there was a parse error.
+bool ProcessCluster(const metadata_map_t& metadata_map,
+ const mkvparser::Cluster* cluster);
+
+// Look up this track number in the cache, and if found (meaning this
+// is a metadata track), write a WebVTT cue to the associated output
+// file. Returns false if writing the WebVTT cue failed.
+bool ProcessBlockEntry(const metadata_map_t& metadata_map,
+ const mkvparser::BlockEntry* block_entry);
+
+// Parse the lines of text from the |block_group| to reconstruct the
+// original WebVTT cue, and write it to the associated output |file|.
+// Returns false if there was an error writing to the output file.
+bool WriteCue(FILE* file, const mkvparser::BlockGroup* block_group);
+
+// Consume a line of text from the character stream, and if the line
+// is not empty write the cue identifier to the associated output
+// file. Returns false if there was an error writing to the file.
+bool WriteCueIdentifier(FILE* f, FrameParser* parser);
+
+// Consume a line of text from the character stream (which holds any
+// cue settings) and write the cue timings line for this cue to the
+// associated output file. Returns false if there was an error
+// writing to the file.
+bool WriteCueTimings(FILE* f, FrameParser* parser);
+
+// Write the timestamp (representating either the start time or stop
+// time of the cue) to the output file. Returns false if there was an
+// error writing to the file.
+bool WriteCueTime(FILE* f, mkvtime_t time_ns);
+
+// Consume the remaining lines of text from the character stream
+// (these lines are the actual payload of the WebVTT cue), and write
+// them to the associated output file. Returns false if there was an
+// error writing to the file.
+bool WriteCuePayload(FILE* f, FrameParser* parser);
+} // namespace vttdemux
+
+namespace vttdemux {
+
+FrameParser::FrameParser(const mkvparser::BlockGroup* block_group)
+ : block_group_(block_group) {
+ const mkvparser::Block* const block = block_group->GetBlock();
+ const mkvparser::Block::Frame& f = block->GetFrame(0);
+
+ // The beginning and end of the character stream corresponds to the
+ // position of this block's frame within the WebM input file.
+
+ pos_ = f.pos;
+ pos_end_ = f.pos + f.len;
+}
+
+FrameParser::~FrameParser() {}
+
+int FrameParser::GetChar(char* c) {
+ if (pos_ >= pos_end_) // end-of-stream
+ return 1; // per the semantics of libwebvtt::Reader::GetChar
+
+ const mkvparser::Cluster* const cluster = block_group_->GetCluster();
+ const mkvparser::Segment* const segment = cluster->m_pSegment;
+ mkvparser::IMkvReader* const reader = segment->m_pReader;
+
+ unsigned char* const buf = reinterpret_cast<unsigned char*>(c);
+ const int result = reader->Read(pos_, 1, buf);
+
+ if (result < 0) // error
+ return -1;
+
+ ++pos_; // consume this character in the stream
+ return 0;
+}
+
+void FrameParser::UngetChar(char /* c */) {
+ // All we need to do here is decrement the position in the stream.
+ // The next time GetChar is called the same character will be
+ // re-read from the input file.
+ --pos_;
+}
+
+ChapterAtomParser::ChapterAtomParser(
+ const mkvparser::Chapters::Display* display)
+ : display_(display) {
+ str_ = display->GetString();
+ if (str_ == NULL)
+ return;
+ const size_t len = strlen(str_);
+ str_end_ = str_ + len;
+}
+
+ChapterAtomParser::~ChapterAtomParser() {}
+
+int ChapterAtomParser::GetChar(char* c) {
+ if (str_ == NULL || str_ >= str_end_) // end-of-stream
+ return 1; // per the semantics of libwebvtt::Reader::GetChar
+
+ *c = *str_++; // consume this character in the stream
+ return 0;
+}
+
+void ChapterAtomParser::UngetChar(char /* c */) {
+ // All we need to do here is decrement the position in the stream.
+ // The next time GetChar is called the same character will be
+ // re-read from the input file.
+ --str_;
+}
+
+} // namespace vttdemux
+
+bool vttdemux::ParseHeader(mkvparser::IMkvReader* reader, mkvpos_t* pos) {
+ mkvparser::EBMLHeader h;
+ const mkvpos_t status = h.Parse(reader, *pos);
+
+ if (status) {
+ printf("error parsing EBML header\n");
+ return false;
+ }
+
+ if (h.m_docType == NULL || strcmp(h.m_docType, "webm") != 0) {
+ printf("bad doctype\n");
+ return false;
+ }
+
+ return true; // success
+}
+
+bool vttdemux::ParseSegment(mkvparser::IMkvReader* reader, mkvpos_t pos,
+ segment_ptr_t* segment_ptr) {
+ // We first create the segment object.
+
+ mkvparser::Segment* p;
+ const mkvpos_t create = mkvparser::Segment::CreateInstance(reader, pos, p);
+
+ if (create) {
+ printf("error parsing segment element\n");
+ return false;
+ }
+
+ segment_ptr->reset(p);
+
+ // Now parse all of the segment's sub-elements, in toto.
+
+ const long status = p->Load(); // NOLINT
+
+ if (status < 0) {
+ printf("error loading segment\n");
+ return false;
+ }
+
+ return true;
+}
+
+void vttdemux::BuildMap(const mkvparser::Segment* segment,
+ metadata_map_t* map_ptr) {
+ metadata_map_t& m = *map_ptr;
+ m.clear();
+
+ if (segment->GetChapters()) {
+ MetadataInfo info;
+ info.file = NULL;
+ info.type = MetadataInfo::kChapters;
+
+ m[kChaptersKey] = info;
+ }
+
+ const mkvparser::Tracks* const tt = segment->GetTracks();
+ if (tt == NULL)
+ return;
+
+ const long tc = tt->GetTracksCount(); // NOLINT
+ if (tc <= 0)
+ return;
+
+ // Iterate over the tracks in the intput file. We determine whether
+ // a track holds metadata by inspecting its CodecID.
+
+ for (long idx = 0; idx < tc; ++idx) { // NOLINT
+ const mkvparser::Track* const t = tt->GetTrackByIndex(idx);
+
+ if (t == NULL) // weird
+ continue;
+
+ const long tn = t->GetNumber(); // NOLINT
+
+ if (tn <= 0) // weird
+ continue;
+
+ const char* const codec_id = t->GetCodecId();
+
+ if (codec_id == NULL) // weird
+ continue;
+
+ MetadataInfo info;
+ info.file = NULL;
+
+ if (strcmp(codec_id, "D_WEBVTT/SUBTITLES") == 0) {
+ info.type = MetadataInfo::kSubtitles;
+ } else if (strcmp(codec_id, "D_WEBVTT/CAPTIONS") == 0) {
+ info.type = MetadataInfo::kCaptions;
+ } else if (strcmp(codec_id, "D_WEBVTT/DESCRIPTIONS") == 0) {
+ info.type = MetadataInfo::kDescriptions;
+ } else if (strcmp(codec_id, "D_WEBVTT/METADATA") == 0) {
+ info.type = MetadataInfo::kMetadata;
+ } else {
+ continue;
+ }
+
+ m[tn] = info; // create an entry in the cache for this track
+ }
+}
+
+bool vttdemux::OpenFiles(metadata_map_t* metadata_map, const char* filename) {
+ if (metadata_map == NULL || metadata_map->empty())
+ return false;
+
+ if (filename == NULL)
+ return false;
+
+ // Find the position of the filename extension. We synthesize the
+ // output filename from the directory path and basename of the input
+ // filename.
+
+ const char* const ext = strrchr(filename, '.');
+
+ if (ext == NULL) // TODO(matthewjheaney): liberalize?
+ return false;
+
+ // Remember whether a track of this type has already been seen (the
+ // map key) by keeping a count (the map item). We quality the
+ // output filename with the track number if there is more than one
+ // track having a given type.
+
+ std::map<MetadataInfo::Type, int> exists;
+
+ typedef metadata_map_t::iterator iter_t;
+
+ metadata_map_t& m = *metadata_map;
+ const iter_t ii = m.begin();
+ const iter_t j = m.end();
+
+ // Make a first pass over the cache to determine whether there is
+ // more than one track corresponding to a given metadata type.
+
+ iter_t i = ii;
+ while (i != j) {
+ const metadata_map_t::value_type& v = *i++;
+ const MetadataInfo& info = v.second;
+ const MetadataInfo::Type type = info.type;
+ ++exists[type];
+ }
+
+ // Make a second pass over the cache, synthesizing the filename of
+ // each output file (from the input file basename, the input track
+ // metadata type, and its track number if necessary), and then
+ // opening a WebVTT output file having that filename.
+
+ i = ii;
+ while (i != j) {
+ metadata_map_t::value_type& v = *i++;
+ MetadataInfo& info = v.second;
+ const MetadataInfo::Type type = info.type;
+
+ // Start with the basename of the input file.
+
+ string name(filename, ext);
+
+ // Next append the metadata kind.
+
+ switch (type) {
+ case MetadataInfo::kSubtitles:
+ name += "_SUBTITLES";
+ break;
+
+ case MetadataInfo::kCaptions:
+ name += "_CAPTIONS";
+ break;
+
+ case MetadataInfo::kDescriptions:
+ name += "_DESCRIPTIONS";
+ break;
+
+ case MetadataInfo::kMetadata:
+ name += "_METADATA";
+ break;
+
+ case MetadataInfo::kChapters:
+ name += "_CHAPTERS";
+ break;
+
+ default:
+ return false;
+ }
+
+ // If there is more than one metadata track having a given type
+ // (the WebVTT-in-WebM spec doesn't preclude this), then qualify
+ // the output filename with the input track number.
+
+ if (exists[type] > 1) {
+ enum { kLen = 33 };
+ char str[kLen]; // max 126 tracks, so only 4 chars really needed
+#ifndef _MSC_VER
+ snprintf(str, kLen, "%ld", v.first); // track number
+#else
+ _snprintf_s(str, sizeof(str), kLen, "%ld", v.first); // track number
+#endif
+ name += str;
+ }
+
+ // Finally append the output filename extension.
+
+ name += ".vtt";
+
+ // We have synthesized the full output filename, so attempt to
+ // open the WebVTT output file.
+
+ info.file = fopen(name.c_str(), "wb");
+ const bool success = (info.file != NULL);
+
+ if (!success) {
+ printf("unable to open output file %s\n", name.c_str());
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void vttdemux::CloseFiles(metadata_map_t* metadata_map) {
+ if (metadata_map == NULL)
+ return;
+
+ metadata_map_t& m = *metadata_map;
+
+ typedef metadata_map_t::iterator iter_t;
+
+ iter_t i = m.begin();
+ const iter_t j = m.end();
+
+ // Gracefully close each output file, to ensure all output gets
+ // propertly flushed.
+
+ while (i != j) {
+ metadata_map_t::value_type& v = *i++;
+ MetadataInfo& info = v.second;
+
+ if (info.file != NULL) {
+ fclose(info.file);
+ info.file = NULL;
+ }
+ }
+}
+
+bool vttdemux::WriteFiles(const metadata_map_t& m, mkvparser::Segment* s) {
+ // First write the WebVTT header.
+
+ InitializeFiles(m);
+
+ if (!WriteChaptersFile(m, s))
+ return false;
+
+ // Now iterate over the clusters, writing the WebVTT cue as we parse
+ // each metadata block.
+
+ const mkvparser::Cluster* cluster = s->GetFirst();
+
+ while (cluster != NULL && !cluster->EOS()) {
+ if (!ProcessCluster(m, cluster))
+ return false;
+
+ cluster = s->GetNext(cluster);
+ }
+
+ return true;
+}
+
+bool vttdemux::InitializeFiles(const metadata_map_t& m) {
+ // Write the WebVTT header for each output file in the cache.
+
+ typedef metadata_map_t::const_iterator iter_t;
+ iter_t i = m.begin();
+ const iter_t j = m.end();
+
+ while (i != j) {
+ const metadata_map_t::value_type& v = *i++;
+ const MetadataInfo& info = v.second;
+ FILE* const f = info.file;
+
+ if (fputs("WEBVTT\n", f) < 0) {
+ printf("unable to initialize output file\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool vttdemux::WriteChaptersFile(const metadata_map_t& m,
+ const mkvparser::Segment* s) {
+ const metadata_map_t::const_iterator info_iter = m.find(kChaptersKey);
+ if (info_iter == m.end()) // no chapters, so nothing to do
+ return true;
+
+ const mkvparser::Chapters* const chapters = s->GetChapters();
+ if (chapters == NULL) // weird
+ return true;
+
+ const MetadataInfo& info = info_iter->second;
+ FILE* const file = info.file;
+
+ const int edition_count = chapters->GetEditionCount();
+
+ if (edition_count <= 0) // weird
+ return true; // nothing to do
+
+ if (edition_count > 1) {
+ // TODO(matthewjheaney): figure what to do here
+ printf("more than one chapter edition detected\n");
+ return false;
+ }
+
+ const mkvparser::Chapters::Edition* const edition = chapters->GetEdition(0);
+
+ const int atom_count = edition->GetAtomCount();
+
+ for (int idx = 0; idx < atom_count; ++idx) {
+ const mkvparser::Chapters::Atom* const atom = edition->GetAtom(idx);
+ const int display_count = atom->GetDisplayCount();
+
+ if (display_count <= 0)
+ continue;
+
+ if (display_count > 1) {
+ // TODO(matthewjheaney): handle case of multiple languages
+ printf("more than 1 display in atom detected\n");
+ return false;
+ }
+
+ const mkvparser::Chapters::Display* const display = atom->GetDisplay(0);
+
+ if (const char* language = display->GetLanguage()) {
+ if (strcmp(language, "eng") != 0) {
+ // TODO(matthewjheaney): handle case of multiple languages.
+
+ // We must create a separate webvtt file for each language.
+ // This isn't a simple problem (which is why we defer it for
+ // now), because there's nothing in the header that tells us
+ // what languages we have as cues. We must parse the displays
+ // of each atom to determine that.
+
+ // One solution is to make two passes over the input data.
+ // First parse the displays, creating an in-memory cache of
+ // all the chapter cues, sorted according to their language.
+ // After we have read all of the chapter atoms from the input
+ // file, we can then write separate output files for each
+ // language.
+
+ printf("only English-language chapter cues are supported\n");
+ return false;
+ }
+ }
+
+ if (!WriteChaptersCue(file, chapters, atom, display))
+ return false;
+ }
+
+ return true;
+}
+
+bool vttdemux::WriteChaptersCue(FILE* f, const mkvparser::Chapters* chapters,
+ const mkvparser::Chapters::Atom* atom,
+ const mkvparser::Chapters::Display* display) {
+ // We start a new cue by writing a cue separator (an empty line)
+ // into the stream.
+
+ if (fputc('\n', f) < 0)
+ return false;
+
+ // A WebVTT Cue comprises 3 things: a cue identifier, followed by
+ // the cue timings, followed by the payload of the cue. We write
+ // each part of the cue in sequence.
+
+ if (!WriteChaptersCueIdentifier(f, atom))
+ return false;
+
+ if (!WriteChaptersCueTimings(f, chapters, atom))
+ return false;
+
+ if (!WriteChaptersCuePayload(f, display))
+ return false;
+
+ return true;
+}
+
+bool vttdemux::WriteChaptersCueIdentifier(
+ FILE* f, const mkvparser::Chapters::Atom* atom) {
+ const char* const identifier = atom->GetStringUID();
+
+ if (identifier == NULL)
+ return true; // nothing else to do
+
+ if (fprintf(f, "%s\n", identifier) < 0)
+ return false;
+
+ return true;
+}
+
+bool vttdemux::WriteChaptersCueTimings(FILE* f,
+ const mkvparser::Chapters* chapters,
+ const mkvparser::Chapters::Atom* atom) {
+ const mkvtime_t start_ns = atom->GetStartTime(chapters);
+
+ if (start_ns < 0)
+ return false;
+
+ const mkvtime_t stop_ns = atom->GetStopTime(chapters);
+
+ if (stop_ns < 0)
+ return false;
+
+ if (!WriteCueTime(f, start_ns))
+ return false;
+
+ if (fputs(" --> ", f) < 0)
+ return false;
+
+ if (!WriteCueTime(f, stop_ns))
+ return false;
+
+ if (fputc('\n', f) < 0)
+ return false;
+
+ return true;
+}
+
+bool vttdemux::WriteChaptersCuePayload(
+ FILE* f, const mkvparser::Chapters::Display* display) {
+ // Bind a Chapter parser object to the display, which allows us to
+ // extract each line of text from the title-part of the display.
+ ChapterAtomParser parser(display);
+
+ int count = 0; // count of lines of payload text written to output file
+ for (string line;;) {
+ const int e = parser.GetLine(&line);
+
+ if (e < 0) // error (only -- we allow EOS here)
+ return false;
+
+ if (line.empty()) // TODO(matthewjheaney): retain this check?
+ break;
+
+ if (fprintf(f, "%s\n", line.c_str()) < 0)
+ return false;
+
+ ++count;
+ }
+
+ if (count <= 0) // WebVTT cue requires non-empty payload
+ return false;
+
+ return true;
+}
+
+bool vttdemux::ProcessCluster(const metadata_map_t& m,
+ const mkvparser::Cluster* c) {
+ // Visit the blocks in this cluster, writing a WebVTT cue for each
+ // metadata block.
+
+ const mkvparser::BlockEntry* block_entry;
+
+ long result = c->GetFirst(block_entry); // NOLINT
+ if (result < 0) {
+ printf("bad cluster (unable to get first block)\n");
+ return false;
+ }
+
+ while (block_entry != NULL && !block_entry->EOS()) {
+ if (!ProcessBlockEntry(m, block_entry))
+ return false;
+
+ result = c->GetNext(block_entry, block_entry);
+ if (result < 0) { // error
+ printf("bad cluster (unable to get next block)\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool vttdemux::ProcessBlockEntry(const metadata_map_t& m,
+ const mkvparser::BlockEntry* block_entry) {
+ // If the track number for this block is in the cache, then we have
+ // a metadata block, so write the WebVTT cue to the output file.
+
+ const mkvparser::Block* const block = block_entry->GetBlock();
+ const long long tn = block->GetTrackNumber(); // NOLINT
+
+ typedef metadata_map_t::const_iterator iter_t;
+ const iter_t i = m.find(static_cast<metadata_map_t::key_type>(tn));
+
+ if (i == m.end()) // not a metadata track
+ return true; // nothing else to do
+
+ if (block_entry->GetKind() != mkvparser::BlockEntry::kBlockGroup)
+ return false; // weird
+
+ typedef mkvparser::BlockGroup BG;
+ const BG* const block_group = static_cast<const BG*>(block_entry);
+
+ const MetadataInfo& info = i->second;
+ FILE* const f = info.file;
+
+ return WriteCue(f, block_group);
+}
+
+bool vttdemux::WriteCue(FILE* f, const mkvparser::BlockGroup* block_group) {
+ // Bind a FrameParser object to the block, which allows us to
+ // extract each line of text from the payload of the block.
+ FrameParser parser(block_group);
+
+ // We start a new cue by writing a cue separator (an empty line)
+ // into the stream.
+
+ if (fputc('\n', f) < 0)
+ return false;
+
+ // A WebVTT Cue comprises 3 things: a cue identifier, followed by
+ // the cue timings, followed by the payload of the cue. We write
+ // each part of the cue in sequence.
+
+ if (!WriteCueIdentifier(f, &parser))
+ return false;
+
+ if (!WriteCueTimings(f, &parser))
+ return false;
+
+ if (!WriteCuePayload(f, &parser))
+ return false;
+
+ return true;
+}
+
+bool vttdemux::WriteCueIdentifier(FILE* f, FrameParser* parser) {
+ string line;
+ int e = parser->GetLine(&line);
+
+ if (e) // error or EOS
+ return false;
+
+ // If the cue identifier line is empty, this means that the original
+ // WebVTT cue did not have a cue identifier, so we don't bother
+ // writing an extra line terminator to the output file (though doing
+ // so would be harmless).
+
+ if (!line.empty()) {
+ if (fputs(line.c_str(), f) < 0)
+ return false;
+
+ if (fputc('\n', f) < 0)
+ return false;
+ }
+
+ return true;
+}
+
+bool vttdemux::WriteCueTimings(FILE* f, FrameParser* parser) {
+ const mkvparser::BlockGroup* const block_group = parser->block_group_;
+ const mkvparser::Cluster* const cluster = block_group->GetCluster();
+ const mkvparser::Block* const block = block_group->GetBlock();
+
+ // A WebVTT Cue "timings" line comprises two parts: the start and
+ // stop time for this cue, followed by the (optional) cue settings,
+ // such as orientation of the rendered text or its size. Only the
+ // settings part of the cue timings line is stored in the WebM
+ // block. We reconstruct the start and stop times of the WebVTT cue
+ // from the timestamp and duration of the WebM block.
+
+ const mkvtime_t start_ns = block->GetTime(cluster);
+
+ if (!WriteCueTime(f, start_ns))
+ return false;
+
+ if (fputs(" --> ", f) < 0)
+ return false;
+
+ const mkvtime_t duration_timecode = block_group->GetDurationTimeCode();
+
+ if (duration_timecode < 0)
+ return false;
+
+ const mkvparser::Segment* const segment = cluster->m_pSegment;
+ const mkvparser::SegmentInfo* const info = segment->GetInfo();
+
+ if (info == NULL)
+ return false;
+
+ const mkvtime_t timecode_scale = info->GetTimeCodeScale();
+
+ if (timecode_scale <= 0)
+ return false;
+
+ const mkvtime_t duration_ns = duration_timecode * timecode_scale;
+ const mkvtime_t stop_ns = start_ns + duration_ns;
+
+ if (!WriteCueTime(f, stop_ns))
+ return false;
+
+ string line;
+ int e = parser->GetLine(&line);
+
+ if (e) // error or EOS
+ return false;
+
+ if (!line.empty()) {
+ if (fputc(' ', f) < 0)
+ return false;
+
+ if (fputs(line.c_str(), f) < 0)
+ return false;
+ }
+
+ if (fputc('\n', f) < 0)
+ return false;
+
+ return true;
+}
+
+bool vttdemux::WriteCueTime(FILE* f, mkvtime_t time_ns) {
+ mkvtime_t ms = time_ns / 1000000; // WebVTT time has millisecond resolution
+
+ mkvtime_t sec = ms / 1000;
+ ms -= sec * 1000;
+
+ mkvtime_t min = sec / 60;
+ sec -= 60 * min;
+
+ mkvtime_t hr = min / 60;
+ min -= 60 * hr;
+
+ if (hr > 0) {
+ if (fprintf(f, "%02lld:", hr) < 0)
+ return false;
+ }
+
+ if (fprintf(f, "%02lld:%02lld.%03lld", min, sec, ms) < 0)
+ return false;
+
+ return true;
+}
+
+bool vttdemux::WriteCuePayload(FILE* f, FrameParser* parser) {
+ int count = 0; // count of lines of payload text written to output file
+ for (string line;;) {
+ const int e = parser->GetLine(&line);
+
+ if (e < 0) // error (only -- we allow EOS here)
+ return false;
+
+ if (line.empty()) // TODO(matthewjheaney): retain this check?
+ break;
+
+ if (fprintf(f, "%s\n", line.c_str()) < 0)
+ return false;
+
+ ++count;
+ }
+
+ if (count <= 0) // WebVTT cue requires non-empty payload
+ return false;
+
+ return true;
+}
+
+} // namespace libwebm
+
+int main(int argc, const char* argv[]) {
+ if (argc != 2) {
+ printf("usage: vttdemux <webmfile>\n");
+ return EXIT_SUCCESS;
+ }
+
+ const char* const filename = argv[1];
+ mkvparser::MkvReader reader;
+
+ int e = reader.Open(filename);
+
+ if (e) { // error
+ printf("unable to open file\n");
+ return EXIT_FAILURE;
+ }
+
+ libwebm::vttdemux::mkvpos_t pos;
+
+ if (!libwebm::vttdemux::ParseHeader(&reader, &pos))
+ return EXIT_FAILURE;
+
+ libwebm::vttdemux::segment_ptr_t segment_ptr;
+
+ if (!libwebm::vttdemux::ParseSegment(&reader, pos, &segment_ptr))
+ return EXIT_FAILURE;
+
+ libwebm::vttdemux::metadata_map_t metadata_map;
+
+ BuildMap(segment_ptr.get(), &metadata_map);
+
+ if (metadata_map.empty()) {
+ printf("no WebVTT metadata found\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!OpenFiles(&metadata_map, filename)) {
+ CloseFiles(&metadata_map); // nothing to flush, so not strictly necessary
+ return EXIT_FAILURE;
+ }
+
+ if (!WriteFiles(metadata_map, segment_ptr.get())) {
+ CloseFiles(&metadata_map); // might as well flush what we do have
+ return EXIT_FAILURE;
+ }
+
+ CloseFiles(&metadata_map);
+
+ return EXIT_SUCCESS;
+}
diff --git a/vttreader.h b/vttreader.h
new file mode 100644
index 0000000..2e7cc4b
--- /dev/null
+++ b/vttreader.h
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_VTTREADER_H_
+#define LIBWEBM_VTTREADER_H_
+
+// This file is a wrapper for the file included immediately after this comment.
+// New projects should not include this file: include the file included below.
+#include "webvtt/vttreader.h"
+
+#endif // LIBWEBM_VTTREADER_H_
diff --git a/webm_info.cc b/webm_info.cc
new file mode 100644
index 0000000..fdf4759
--- /dev/null
+++ b/webm_info.cc
@@ -0,0 +1,1329 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include <inttypes.h>
+#include <stdint.h>
+
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <queue>
+#include <string>
+#include <vector>
+
+#include "common/hdr_util.h"
+#include "common/indent.h"
+#include "common/vp9_header_parser.h"
+#include "common/vp9_level_stats.h"
+#include "common/webm_constants.h"
+#include "common/webm_endian.h"
+
+#include "mkvparser/mkvparser.h"
+#include "mkvparser/mkvreader.h"
+
+namespace {
+
+using libwebm::Indent;
+using libwebm::kNanosecondsPerSecond;
+using libwebm::kNanosecondsPerSecondi;
+using mkvparser::ContentEncoding;
+using std::string;
+using std::wstring;
+
+const char VERSION_STRING[] = "1.0.4.5";
+
+struct Options {
+ Options();
+
+ // Returns true if |value| matches -|option| or -no|option|.
+ static bool MatchesBooleanOption(const string& option, const string& value);
+
+ // Set all of the member variables to |value|.
+ void SetAll(bool value);
+
+ bool output_video;
+ bool output_audio;
+ bool output_size;
+ bool output_offset;
+ bool output_seconds;
+ bool output_ebml_header;
+ bool output_segment;
+ bool output_seekhead;
+ bool output_segment_info;
+ bool output_tracks;
+ bool output_clusters;
+ bool output_blocks;
+ bool output_codec_info;
+ bool output_clusters_size;
+ bool output_encrypted_info;
+ bool output_cues;
+ bool output_frame_stats;
+ bool output_vp9_level;
+};
+
+Options::Options()
+ : output_video(true),
+ output_audio(true),
+ output_size(false),
+ output_offset(false),
+ output_seconds(true),
+ output_ebml_header(true),
+ output_segment(true),
+ output_seekhead(false),
+ output_segment_info(true),
+ output_tracks(true),
+ output_clusters(false),
+ output_blocks(false),
+ output_codec_info(false),
+ output_clusters_size(false),
+ output_encrypted_info(false),
+ output_cues(false),
+ output_frame_stats(false),
+ output_vp9_level(false) {}
+
+void Options::SetAll(bool value) {
+ output_video = value;
+ output_audio = value;
+ output_size = value;
+ output_offset = value;
+ output_ebml_header = value;
+ output_seconds = value;
+ output_segment = value;
+ output_segment_info = value;
+ output_tracks = value;
+ output_clusters = value;
+ output_blocks = value;
+ output_codec_info = value;
+ output_clusters_size = value;
+ output_encrypted_info = value;
+ output_cues = value;
+ output_frame_stats = value;
+ output_vp9_level = value;
+}
+
+bool Options::MatchesBooleanOption(const string& option, const string& value) {
+ const string opt = "-" + option;
+ const string noopt = "-no" + option;
+ return value == opt || value == noopt;
+}
+
+struct FrameStats {
+ FrameStats()
+ : frames(0),
+ displayed_frames(0),
+ first_altref(true),
+ frames_since_last_altref(0),
+ minimum_altref_distance(std::numeric_limits<int>::max()),
+ min_altref_end_ns(0),
+ max_window_size(0),
+ max_window_end_ns(0) {}
+
+ int frames;
+ int displayed_frames;
+
+ bool first_altref;
+ int frames_since_last_altref;
+ int minimum_altref_distance;
+ int64_t min_altref_end_ns;
+
+ std::queue<int64_t> window;
+ int64_t max_window_size;
+ int64_t max_window_end_ns;
+};
+
+void Usage() {
+ printf("Usage: webm_info [options] -i input\n");
+ printf("\n");
+ printf("Main options:\n");
+ printf(" -h | -? show help\n");
+ printf(" -v show version\n");
+ printf(" -all Enable all output options.\n");
+ printf(" -video Output video tracks (true)\n");
+ printf(" -audio Output audio tracks (true)\n");
+ printf(" -size Output element sizes (false)\n");
+ printf(" -offset Output element offsets (false)\n");
+ printf(" -times_seconds Output times as seconds (true)\n");
+ printf(" -ebml_header Output EBML header (true)\n");
+ printf(" -segment Output Segment (true)\n");
+ printf(" -seekhead Output SeekHead (false)\n");
+ printf(" -segment_info Output SegmentInfo (true)\n");
+ printf(" -tracks Output Tracks (true)\n");
+ printf(" -clusters Output Clusters (false)\n");
+ printf(" -blocks Output Blocks (false)\n");
+ printf(" -codec_info Output video codec information (false)\n");
+ printf(" -clusters_size Output Total Clusters size (false)\n");
+ printf(" -encrypted_info Output encrypted frame info (false)\n");
+ printf(" -cues Output Cues entries (false)\n");
+ printf(" -frame_stats Output frame stats (VP9)(false)\n");
+ printf(" -vp9_level Output VP9 level(false)\n");
+ printf("\nOutput options may be negated by prefixing 'no'.\n");
+}
+
+// TODO(fgalligan): Add support for non-ascii.
+wstring UTF8ToWideString(const char* str) {
+ wstring wstr;
+
+ if (str == NULL)
+ return wstr;
+
+ string temp_str(str, strlen(str));
+ wstr.assign(temp_str.begin(), temp_str.end());
+
+ return wstr;
+}
+
+string ToString(const char* str) { return string((str == NULL) ? "" : str); }
+
+void OutputEBMLHeader(const mkvparser::EBMLHeader& ebml, FILE* o,
+ Indent* indent) {
+ fprintf(o, "EBML Header:\n");
+ indent->Adjust(libwebm::kIncreaseIndent);
+ fprintf(o, "%sEBMLVersion : %lld\n", indent->indent_str().c_str(),
+ ebml.m_version);
+ fprintf(o, "%sEBMLReadVersion : %lld\n", indent->indent_str().c_str(),
+ ebml.m_readVersion);
+ fprintf(o, "%sEBMLMaxIDLength : %lld\n", indent->indent_str().c_str(),
+ ebml.m_maxIdLength);
+ fprintf(o, "%sEBMLMaxSizeLength : %lld\n", indent->indent_str().c_str(),
+ ebml.m_maxSizeLength);
+ fprintf(o, "%sDoc Type : %s\n", indent->indent_str().c_str(),
+ ebml.m_docType);
+ fprintf(o, "%sDocTypeVersion : %lld\n", indent->indent_str().c_str(),
+ ebml.m_docTypeVersion);
+ fprintf(o, "%sDocTypeReadVersion: %lld\n", indent->indent_str().c_str(),
+ ebml.m_docTypeReadVersion);
+ indent->Adjust(libwebm::kDecreaseIndent);
+}
+
+void OutputSegment(const mkvparser::Segment& segment, const Options& options,
+ FILE* o) {
+ fprintf(o, "Segment:");
+ if (options.output_offset)
+ fprintf(o, " @: %lld", segment.m_element_start);
+ if (options.output_size)
+ fprintf(o, " size: %lld",
+ segment.m_size + segment.m_start - segment.m_element_start);
+ fprintf(o, "\n");
+}
+
+bool OutputSeekHead(const mkvparser::Segment& segment, const Options& options,
+ FILE* o, Indent* indent) {
+ const mkvparser::SeekHead* const seekhead = segment.GetSeekHead();
+ if (!seekhead) {
+ // SeekHeads are optional.
+ return true;
+ }
+
+ fprintf(o, "%sSeekHead:", indent->indent_str().c_str());
+ if (options.output_offset)
+ fprintf(o, " @: %lld", seekhead->m_element_start);
+ if (options.output_size)
+ fprintf(o, " size: %lld", seekhead->m_element_size);
+ fprintf(o, "\n");
+
+ indent->Adjust(libwebm::kIncreaseIndent);
+
+ for (int i = 0; i < seekhead->GetCount(); ++i) {
+ const mkvparser::SeekHead::Entry* const entry = seekhead->GetEntry(i);
+ if (!entry) {
+ fprintf(stderr, "Error retrieving SeekHead entry #%d\n", i);
+ return false;
+ }
+
+ fprintf(o, "%sEntry[%d]", indent->indent_str().c_str(), i);
+ if (options.output_offset)
+ fprintf(o, " @: %lld", entry->element_start);
+ if (options.output_size)
+ fprintf(o, " size: %lld", entry->element_size);
+ fprintf(o, "\n");
+
+ indent->Adjust(libwebm::kIncreaseIndent);
+ std::string entry_indent = indent->indent_str();
+ // TODO(jzern): 1) known ids could be stringified. 2) ids could be
+ // reencoded to EBML for ease of lookup.
+ fprintf(o, "%sSeek ID : %llx\n", entry_indent.c_str(), entry->id);
+ fprintf(o, "%sSeek position : %lld\n", entry_indent.c_str(), entry->pos);
+ indent->Adjust(libwebm::kDecreaseIndent);
+ }
+
+ for (int i = 0; i < seekhead->GetVoidElementCount(); ++i) {
+ const mkvparser::SeekHead::VoidElement* const entry =
+ seekhead->GetVoidElement(i);
+ if (!entry) {
+ fprintf(stderr, "Error retrieving SeekHead void element #%d\n", i);
+ return false;
+ }
+
+ fprintf(o, "%sVoid element[%d]", indent->indent_str().c_str(), i);
+ if (options.output_offset)
+ fprintf(o, " @: %lld", entry->element_start);
+ if (options.output_size)
+ fprintf(o, " size: %lld", entry->element_size);
+ fprintf(o, "\n");
+ }
+
+ indent->Adjust(libwebm::kDecreaseIndent);
+ return true;
+}
+
+bool OutputSegmentInfo(const mkvparser::Segment& segment,
+ const Options& options, FILE* o, Indent* indent) {
+ const mkvparser::SegmentInfo* const segment_info = segment.GetInfo();
+ if (!segment_info) {
+ fprintf(stderr, "SegmentInfo was NULL.\n");
+ return false;
+ }
+
+ const int64_t timecode_scale = segment_info->GetTimeCodeScale();
+ const int64_t duration_ns = segment_info->GetDuration();
+ const wstring title = UTF8ToWideString(segment_info->GetTitleAsUTF8());
+ const wstring muxing_app =
+ UTF8ToWideString(segment_info->GetMuxingAppAsUTF8());
+ const wstring writing_app =
+ UTF8ToWideString(segment_info->GetWritingAppAsUTF8());
+
+ fprintf(o, "%sSegmentInfo:", indent->indent_str().c_str());
+ if (options.output_offset)
+ fprintf(o, " @: %lld", segment_info->m_element_start);
+ if (options.output_size)
+ fprintf(o, " size: %lld", segment_info->m_element_size);
+ fprintf(o, "\n");
+
+ indent->Adjust(libwebm::kIncreaseIndent);
+ fprintf(o, "%sTimecodeScale : %" PRId64 " \n", indent->indent_str().c_str(),
+ timecode_scale);
+ if (options.output_seconds)
+ fprintf(o, "%sDuration(secs): %g\n", indent->indent_str().c_str(),
+ duration_ns / kNanosecondsPerSecond);
+ else
+ fprintf(o, "%sDuration(nano): %" PRId64 "\n", indent->indent_str().c_str(),
+ duration_ns);
+
+ if (!title.empty())
+ fprintf(o, "%sTitle : %ls\n", indent->indent_str().c_str(),
+ title.c_str());
+ if (!muxing_app.empty())
+ fprintf(o, "%sMuxingApp : %ls\n", indent->indent_str().c_str(),
+ muxing_app.c_str());
+ if (!writing_app.empty())
+ fprintf(o, "%sWritingApp : %ls\n", indent->indent_str().c_str(),
+ writing_app.c_str());
+ indent->Adjust(libwebm::kDecreaseIndent);
+ return true;
+}
+
+bool OutputTracks(const mkvparser::Segment& segment, const Options& options,
+ FILE* o, Indent* indent) {
+ const mkvparser::Tracks* const tracks = segment.GetTracks();
+ if (!tracks) {
+ fprintf(stderr, "Tracks was NULL.\n");
+ return false;
+ }
+
+ fprintf(o, "%sTracks:", indent->indent_str().c_str());
+ if (options.output_offset)
+ fprintf(o, " @: %lld", tracks->m_element_start);
+ if (options.output_size)
+ fprintf(o, " size: %lld", tracks->m_element_size);
+ fprintf(o, "\n");
+
+ unsigned int i = 0;
+ const unsigned long j = tracks->GetTracksCount();
+ while (i != j) {
+ const mkvparser::Track* const track = tracks->GetTrackByIndex(i++);
+ if (track == NULL)
+ continue;
+
+ indent->Adjust(libwebm::kIncreaseIndent);
+ fprintf(o, "%sTrack:", indent->indent_str().c_str());
+ if (options.output_offset)
+ fprintf(o, " @: %lld", track->m_element_start);
+ if (options.output_size)
+ fprintf(o, " size: %lld", track->m_element_size);
+ fprintf(o, "\n");
+
+ const int64_t track_type = track->GetType();
+ const int64_t track_number = track->GetNumber();
+ const wstring track_name = UTF8ToWideString(track->GetNameAsUTF8());
+
+ indent->Adjust(libwebm::kIncreaseIndent);
+ fprintf(o, "%sTrackType : %" PRId64 "\n", indent->indent_str().c_str(),
+ track_type);
+ fprintf(o, "%sTrackNumber : %" PRId64 "\n", indent->indent_str().c_str(),
+ track_number);
+ if (!track_name.empty())
+ fprintf(o, "%sName : %ls\n", indent->indent_str().c_str(),
+ track_name.c_str());
+
+ const char* const codec_id = track->GetCodecId();
+ if (codec_id)
+ fprintf(o, "%sCodecID : %s\n", indent->indent_str().c_str(),
+ codec_id);
+
+ const wstring codec_name = UTF8ToWideString(track->GetCodecNameAsUTF8());
+ if (!codec_name.empty())
+ fprintf(o, "%sCodecName : %ls\n", indent->indent_str().c_str(),
+ codec_name.c_str());
+
+ size_t private_size;
+ const unsigned char* const private_data =
+ track->GetCodecPrivate(private_size);
+ if (private_data) {
+ fprintf(o, "%sPrivateData(size): %d\n", indent->indent_str().c_str(),
+ static_cast<int>(private_size));
+
+ if (track_type == mkvparser::Track::kVideo) {
+ const std::string codec_id = ToString(track->GetCodecId());
+ const std::string v_vp9 = "V_VP9";
+ if (codec_id == v_vp9) {
+ libwebm::Vp9CodecFeatures features;
+ if (!libwebm::ParseVpxCodecPrivate(private_data,
+ static_cast<int32_t>(private_size),
+ &features)) {
+ fprintf(stderr, "Error parsing VpxCodecPrivate.\n");
+ return false;
+ }
+ if (features.profile != -1)
+ fprintf(o, "%sVP9 profile : %d\n",
+ indent->indent_str().c_str(), features.profile);
+ if (features.level != -1)
+ fprintf(o, "%sVP9 level : %d\n",
+ indent->indent_str().c_str(), features.level);
+ if (features.bit_depth != -1)
+ fprintf(o, "%sVP9 bit_depth : %d\n",
+ indent->indent_str().c_str(), features.bit_depth);
+ if (features.chroma_subsampling != -1)
+ fprintf(o, "%sVP9 chroma subsampling : %d\n",
+ indent->indent_str().c_str(), features.chroma_subsampling);
+ }
+ }
+ }
+
+ const uint64_t default_duration = track->GetDefaultDuration();
+ if (default_duration > 0)
+ fprintf(o, "%sDefaultDuration: %" PRIu64 "\n",
+ indent->indent_str().c_str(), default_duration);
+
+ if (track->GetContentEncodingCount() > 0) {
+ // Only check the first content encoding.
+ const ContentEncoding* const encoding =
+ track->GetContentEncodingByIndex(0);
+ if (!encoding) {
+ printf("Could not get first ContentEncoding.\n");
+ return false;
+ }
+
+ fprintf(o, "%sContentEncodingOrder : %lld\n",
+ indent->indent_str().c_str(), encoding->encoding_order());
+ fprintf(o, "%sContentEncodingScope : %lld\n",
+ indent->indent_str().c_str(), encoding->encoding_scope());
+ fprintf(o, "%sContentEncodingType : %lld\n",
+ indent->indent_str().c_str(), encoding->encoding_type());
+
+ if (encoding->GetEncryptionCount() > 0) {
+ // Only check the first encryption.
+ const ContentEncoding::ContentEncryption* const encryption =
+ encoding->GetEncryptionByIndex(0);
+ if (!encryption) {
+ printf("Could not get first ContentEncryption.\n");
+ return false;
+ }
+
+ fprintf(o, "%sContentEncAlgo : %lld\n",
+ indent->indent_str().c_str(), encryption->algo);
+
+ if (encryption->key_id_len > 0) {
+ fprintf(o, "%sContentEncKeyID : ", indent->indent_str().c_str());
+ for (int k = 0; k < encryption->key_id_len; ++k) {
+ fprintf(o, "0x%02x, ", encryption->key_id[k]);
+ }
+ fprintf(o, "\n");
+ }
+
+ if (encryption->signature_len > 0) {
+ fprintf(o, "%sContentSignature : 0x",
+ indent->indent_str().c_str());
+ for (int k = 0; k < encryption->signature_len; ++k) {
+ fprintf(o, "%x", encryption->signature[k]);
+ }
+ fprintf(o, "\n");
+ }
+
+ if (encryption->sig_key_id_len > 0) {
+ fprintf(o, "%sContentSigKeyID : 0x",
+ indent->indent_str().c_str());
+ for (int k = 0; k < encryption->sig_key_id_len; ++k) {
+ fprintf(o, "%x", encryption->sig_key_id[k]);
+ }
+ fprintf(o, "\n");
+ }
+
+ fprintf(o, "%sContentSigAlgo : %lld\n",
+ indent->indent_str().c_str(), encryption->sig_algo);
+ fprintf(o, "%sContentSigHashAlgo : %lld\n",
+ indent->indent_str().c_str(), encryption->sig_hash_algo);
+
+ const ContentEncoding::ContentEncAESSettings& aes =
+ encryption->aes_settings;
+ fprintf(o, "%sCipherMode : %lld\n",
+ indent->indent_str().c_str(), aes.cipher_mode);
+ }
+ }
+
+ if (track_type == mkvparser::Track::kVideo) {
+ const mkvparser::VideoTrack* const video_track =
+ static_cast<const mkvparser::VideoTrack*>(track);
+ const int64_t width = video_track->GetWidth();
+ const int64_t height = video_track->GetHeight();
+ const int64_t display_width = video_track->GetDisplayWidth();
+ const int64_t display_height = video_track->GetDisplayHeight();
+ const int64_t display_unit = video_track->GetDisplayUnit();
+ const double frame_rate = video_track->GetFrameRate();
+ fprintf(o, "%sPixelWidth : %" PRId64 "\n", indent->indent_str().c_str(),
+ width);
+ fprintf(o, "%sPixelHeight : %" PRId64 "\n", indent->indent_str().c_str(),
+ height);
+ if (frame_rate > 0.0)
+ fprintf(o, "%sFrameRate : %g\n", indent->indent_str().c_str(),
+ video_track->GetFrameRate());
+ if (display_unit > 0 || display_width != width ||
+ display_height != height) {
+ fprintf(o, "%sDisplayWidth : %" PRId64 "\n",
+ indent->indent_str().c_str(), display_width);
+ fprintf(o, "%sDisplayHeight : %" PRId64 "\n",
+ indent->indent_str().c_str(), display_height);
+ fprintf(o, "%sDisplayUnit : %" PRId64 "\n",
+ indent->indent_str().c_str(), display_unit);
+ }
+
+ const mkvparser::Colour* const colour = video_track->GetColour();
+ if (colour) {
+ // TODO(fgalligan): Add support for Colour's address and size.
+ fprintf(o, "%sColour:\n", indent->indent_str().c_str());
+ indent->Adjust(libwebm::kIncreaseIndent);
+
+ const int64_t matrix_coefficients = colour->matrix_coefficients;
+ const int64_t bits_per_channel = colour->bits_per_channel;
+ const int64_t chroma_subsampling_horz = colour->chroma_subsampling_horz;
+ const int64_t chroma_subsampling_vert = colour->chroma_subsampling_vert;
+ const int64_t cb_subsampling_horz = colour->cb_subsampling_horz;
+ const int64_t cb_subsampling_vert = colour->cb_subsampling_vert;
+ const int64_t chroma_siting_horz = colour->chroma_siting_horz;
+ const int64_t chroma_siting_vert = colour->chroma_siting_vert;
+ const int64_t range = colour->range;
+ const int64_t transfer_characteristics =
+ colour->transfer_characteristics;
+ const int64_t primaries = colour->primaries;
+ const int64_t max_cll = colour->max_cll;
+ const int64_t max_fall = colour->max_fall;
+ if (matrix_coefficients != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sMatrixCoefficients : %" PRId64 "\n",
+ indent->indent_str().c_str(), matrix_coefficients);
+ if (bits_per_channel != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sBitsPerChannel : %" PRId64 "\n",
+ indent->indent_str().c_str(), bits_per_channel);
+ if (chroma_subsampling_horz != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sChromaSubsamplingHorz : %" PRId64 "\n",
+ indent->indent_str().c_str(), chroma_subsampling_horz);
+ if (chroma_subsampling_vert != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sChromaSubsamplingVert : %" PRId64 "\n",
+ indent->indent_str().c_str(), chroma_subsampling_vert);
+ if (cb_subsampling_horz != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sCbSubsamplingHorz : %" PRId64 "\n",
+ indent->indent_str().c_str(), cb_subsampling_horz);
+ if (cb_subsampling_vert != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sCbSubsamplingVert : %" PRId64 "\n",
+ indent->indent_str().c_str(), cb_subsampling_vert);
+ if (chroma_siting_horz != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sChromaSitingHorz : %" PRId64 "\n",
+ indent->indent_str().c_str(), chroma_siting_horz);
+ if (chroma_siting_vert != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sChromaSitingVert : %" PRId64 "\n",
+ indent->indent_str().c_str(), chroma_siting_vert);
+ if (range != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sRange : %" PRId64 "\n",
+ indent->indent_str().c_str(), range);
+ if (transfer_characteristics != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sTransferCharacteristics : %" PRId64 "\n",
+ indent->indent_str().c_str(), transfer_characteristics);
+ if (primaries != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sPrimaries : %" PRId64 "\n",
+ indent->indent_str().c_str(), primaries);
+ if (max_cll != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sMaxCLL : %" PRId64 "\n",
+ indent->indent_str().c_str(), max_cll);
+ if (max_fall != mkvparser::Colour::kValueNotPresent)
+ fprintf(o, "%sMaxFALL : %" PRId64 "\n",
+ indent->indent_str().c_str(), max_fall);
+
+ const mkvparser::MasteringMetadata* const metadata =
+ colour->mastering_metadata;
+ if (metadata) {
+ // TODO(fgalligan): Add support for MasteringMetadata's address and
+ // size.
+ fprintf(o, "%sMasteringMetadata:\n", indent->indent_str().c_str());
+ indent->Adjust(libwebm::kIncreaseIndent);
+
+ const mkvparser::PrimaryChromaticity* const red = metadata->r;
+ const mkvparser::PrimaryChromaticity* const green = metadata->g;
+ const mkvparser::PrimaryChromaticity* const blue = metadata->b;
+ const mkvparser::PrimaryChromaticity* const white =
+ metadata->white_point;
+ const float max = metadata->luminance_max;
+ const float min = metadata->luminance_min;
+ if (red) {
+ fprintf(o, "%sPrimaryRChromaticityX : %g\n",
+ indent->indent_str().c_str(), red->x);
+ fprintf(o, "%sPrimaryRChromaticityY : %g\n",
+ indent->indent_str().c_str(), red->y);
+ }
+ if (green) {
+ fprintf(o, "%sPrimaryGChromaticityX : %g\n",
+ indent->indent_str().c_str(), green->x);
+ fprintf(o, "%sPrimaryGChromaticityY : %g\n",
+ indent->indent_str().c_str(), green->y);
+ }
+ if (blue) {
+ fprintf(o, "%sPrimaryBChromaticityX : %g\n",
+ indent->indent_str().c_str(), blue->x);
+ fprintf(o, "%sPrimaryBChromaticityY : %g\n",
+ indent->indent_str().c_str(), blue->y);
+ }
+ if (white) {
+ fprintf(o, "%sWhitePointChromaticityX : %g\n",
+ indent->indent_str().c_str(), white->x);
+ fprintf(o, "%sWhitePointChromaticityY : %g\n",
+ indent->indent_str().c_str(), white->y);
+ }
+ if (max != mkvparser::MasteringMetadata::kValueNotPresent)
+ fprintf(o, "%sLuminanceMax : %g\n",
+ indent->indent_str().c_str(), max);
+ if (min != mkvparser::MasteringMetadata::kValueNotPresent)
+ fprintf(o, "%sLuminanceMin : %g\n",
+ indent->indent_str().c_str(), min);
+ indent->Adjust(libwebm::kDecreaseIndent);
+ }
+ indent->Adjust(libwebm::kDecreaseIndent);
+ }
+
+ const mkvparser::Projection* const projection =
+ video_track->GetProjection();
+ if (projection) {
+ fprintf(o, "%sProjection:\n", indent->indent_str().c_str());
+ indent->Adjust(libwebm::kIncreaseIndent);
+
+ const int projection_type = static_cast<int>(projection->type);
+ const int kTypeNotPresent =
+ static_cast<int>(mkvparser::Projection::kTypeNotPresent);
+ const float kValueNotPresent = mkvparser::Projection::kValueNotPresent;
+ if (projection_type != kTypeNotPresent)
+ fprintf(o, "%sProjectionType : %d\n",
+ indent->indent_str().c_str(), projection_type);
+ if (projection->private_data)
+ fprintf(o, "%sProjectionPrivate(size) : %d\n",
+ indent->indent_str().c_str(),
+ static_cast<int>(projection->private_data_length));
+ if (projection->pose_yaw != kValueNotPresent)
+ fprintf(o, "%sProjectionPoseYaw : %g\n",
+ indent->indent_str().c_str(), projection->pose_yaw);
+ if (projection->pose_pitch != kValueNotPresent)
+ fprintf(o, "%sProjectionPosePitch : %g\n",
+ indent->indent_str().c_str(), projection->pose_pitch);
+ if (projection->pose_roll != kValueNotPresent)
+ fprintf(o, "%sProjectionPoseRoll : %g\n",
+ indent->indent_str().c_str(), projection->pose_roll);
+ indent->Adjust(libwebm::kDecreaseIndent);
+ }
+ } else if (track_type == mkvparser::Track::kAudio) {
+ const mkvparser::AudioTrack* const audio_track =
+ static_cast<const mkvparser::AudioTrack*>(track);
+ const int64_t channels = audio_track->GetChannels();
+ const int64_t bit_depth = audio_track->GetBitDepth();
+ const uint64_t codec_delay = audio_track->GetCodecDelay();
+ const uint64_t seek_preroll = audio_track->GetSeekPreRoll();
+ fprintf(o, "%sChannels : %" PRId64 "\n",
+ indent->indent_str().c_str(), channels);
+ if (bit_depth > 0)
+ fprintf(o, "%sBitDepth : %" PRId64 "\n",
+ indent->indent_str().c_str(), bit_depth);
+ fprintf(o, "%sSamplingFrequency: %g\n", indent->indent_str().c_str(),
+ audio_track->GetSamplingRate());
+ if (codec_delay)
+ fprintf(o, "%sCodecDelay : %" PRIu64 "\n",
+ indent->indent_str().c_str(), codec_delay);
+ if (seek_preroll)
+ fprintf(o, "%sSeekPreRoll : %" PRIu64 "\n",
+ indent->indent_str().c_str(), seek_preroll);
+ }
+ indent->Adjust(libwebm::kDecreaseIndent * 2);
+ }
+
+ return true;
+}
+
+// libvpx reference: vp9/vp9_dx_iface.c
+void ParseSuperframeIndex(const uint8_t* data, size_t data_sz,
+ uint32_t sizes[8], int* count) {
+ const uint8_t marker = data[data_sz - 1];
+ *count = 0;
+
+ if ((marker & 0xe0) == 0xc0) {
+ const int frames = (marker & 0x7) + 1;
+ const int mag = ((marker >> 3) & 0x3) + 1;
+ const size_t index_sz = 2 + mag * frames;
+
+ if (data_sz >= index_sz && data[data_sz - index_sz] == marker) {
+ // found a valid superframe index
+ const uint8_t* x = data + data_sz - index_sz + 1;
+
+ for (int i = 0; i < frames; ++i) {
+ uint32_t this_sz = 0;
+
+ for (int j = 0; j < mag; ++j) {
+ this_sz |= (*x++) << (j * 8);
+ }
+ sizes[i] = this_sz;
+ }
+ *count = frames;
+ }
+ }
+}
+
+void PrintVP9Info(const uint8_t* data, int size, FILE* o, int64_t time_ns,
+ FrameStats* stats, vp9_parser::Vp9HeaderParser* parser,
+ vp9_parser::Vp9LevelStats* level_stats) {
+ if (size < 1)
+ return;
+
+ uint32_t sizes[8];
+ int i = 0, count = 0;
+ ParseSuperframeIndex(data, size, sizes, &count);
+
+ // Remove all frames that are less than window size.
+ while (!stats->window.empty() &&
+ stats->window.front() < (time_ns - (kNanosecondsPerSecondi - 1)))
+ stats->window.pop();
+
+ do {
+ const size_t frame_length = (count > 0) ? sizes[i] : size;
+ if (frame_length > static_cast<size_t>(std::numeric_limits<int>::max()) ||
+ static_cast<int>(frame_length) > size) {
+ fprintf(o, " invalid VP9 frame size (%u)\n",
+ static_cast<uint32_t>(frame_length));
+ return;
+ }
+ if (!parser->ParseUncompressedHeader(data, frame_length))
+ return;
+ level_stats->AddFrame(*parser, time_ns);
+
+ // const int frame_marker = (data[0] >> 6) & 0x3;
+ const int version = parser->profile();
+ const int key = parser->key();
+ const int altref_frame = parser->altref();
+ const int error_resilient_mode = parser->error_resilient_mode();
+ const int row_tiles = parser->row_tiles();
+ const int column_tiles = parser->column_tiles();
+ const int frame_parallel_mode = parser->frame_parallel_mode();
+
+ if (key &&
+ !(size >= 4 && data[1] == 0x49 && data[2] == 0x83 && data[3] == 0x42)) {
+ fprintf(o, " invalid VP9 signature");
+ return;
+ }
+
+ stats->window.push(time_ns);
+ ++stats->frames;
+
+ if (altref_frame) {
+ const int delta_altref = stats->frames_since_last_altref;
+ if (stats->first_altref) {
+ stats->first_altref = false;
+ } else if (delta_altref < stats->minimum_altref_distance) {
+ stats->minimum_altref_distance = delta_altref;
+ stats->min_altref_end_ns = time_ns;
+ }
+ stats->frames_since_last_altref = 0;
+ } else {
+ ++stats->frames_since_last_altref;
+ ++stats->displayed_frames;
+ }
+
+ if (count > 0) {
+ fprintf(o, " packed [%d]: {", i);
+ }
+
+ fprintf(o, " key:%d v:%d altref:%d errm:%d rt:%d ct:%d fpm:%d", key,
+ version, altref_frame, error_resilient_mode, row_tiles,
+ column_tiles, frame_parallel_mode);
+
+ if (key && size > 4) {
+ fprintf(o, " cs:%d", parser->color_space());
+ }
+
+ if (count > 0) {
+ fprintf(o, " size: %u }", sizes[i]);
+ data += sizes[i];
+ size -= sizes[i];
+ }
+ ++i;
+ } while (i < count);
+
+ if (stats->max_window_size < static_cast<int64_t>(stats->window.size())) {
+ stats->max_window_size = stats->window.size();
+ stats->max_window_end_ns = time_ns;
+ }
+}
+
+void PrintVP8Info(const uint8_t* data, int size, FILE* o) {
+ if (size < 3)
+ return;
+
+ const uint32_t bits = data[0] | (data[1] << 8) | (data[2] << 16);
+ const int key = !(bits & 0x1);
+ const int altref_frame = !((bits >> 4) & 0x1);
+ const int version = (bits >> 1) & 0x7;
+ const int partition_length = (bits >> 5) & 0x7FFFF;
+ if (key &&
+ !(size >= 6 && data[3] == 0x9d && data[4] == 0x01 && data[5] == 0x2a)) {
+ fprintf(o, " invalid VP8 signature");
+ return;
+ }
+ fprintf(o, " key:%d v:%d altref:%d partition_length:%d", key, version,
+ altref_frame, partition_length);
+}
+
+// Prints the partition offsets of the sub-sample encryption. |data| must point
+// to an encrypted frame just after the signal byte. Returns the number of
+// bytes read from the sub-sample partition information.
+int PrintSubSampleEncryption(const uint8_t* data, int size, FILE* o) {
+ int read_end = sizeof(uint64_t);
+
+ // Skip past IV.
+ if (size < read_end)
+ return 0;
+ data += sizeof(uint64_t);
+
+ // Read number of partitions.
+ read_end += sizeof(uint8_t);
+ if (size < read_end)
+ return 0;
+ const int num_partitions = data[0];
+ data += sizeof(uint8_t);
+
+ // Read partitions.
+ for (int i = 0; i < num_partitions; ++i) {
+ read_end += sizeof(uint32_t);
+ if (size < read_end)
+ return 0;
+ uint32_t partition_offset;
+ memcpy(&partition_offset, data, sizeof(partition_offset));
+ partition_offset = libwebm::bigendian_to_host(partition_offset);
+ fprintf(o, " off[%d]:%u", i, partition_offset);
+ data += sizeof(uint32_t);
+ }
+
+ return read_end;
+}
+
+bool OutputCluster(const mkvparser::Cluster& cluster,
+ const mkvparser::Tracks& tracks, const Options& options,
+ FILE* o, mkvparser::MkvReader* reader, Indent* indent,
+ int64_t* clusters_size, FrameStats* stats,
+ vp9_parser::Vp9HeaderParser* parser,
+ vp9_parser::Vp9LevelStats* level_stats) {
+ if (clusters_size) {
+ // Load the Cluster.
+ const mkvparser::BlockEntry* block_entry;
+ long status = cluster.GetFirst(block_entry);
+ if (status) {
+ fprintf(stderr, "Could not get first Block of Cluster.\n");
+ return false;
+ }
+
+ *clusters_size += cluster.GetElementSize();
+ }
+
+ if (options.output_clusters) {
+ const int64_t time_ns = cluster.GetTime();
+ const int64_t duration_ns = cluster.GetLastTime() - cluster.GetFirstTime();
+
+ fprintf(o, "%sCluster:", indent->indent_str().c_str());
+ if (options.output_offset)
+ fprintf(o, " @: %lld", cluster.m_element_start);
+ if (options.output_size)
+ fprintf(o, " size: %lld", cluster.GetElementSize());
+ fprintf(o, "\n");
+ indent->Adjust(libwebm::kIncreaseIndent);
+ if (options.output_seconds)
+ fprintf(o, "%sTimecode (sec) : %g\n", indent->indent_str().c_str(),
+ time_ns / kNanosecondsPerSecond);
+ else
+ fprintf(o, "%sTimecode (nano): %" PRId64 "\n",
+ indent->indent_str().c_str(), time_ns);
+ if (options.output_seconds)
+ fprintf(o, "%sDuration (sec) : %g\n", indent->indent_str().c_str(),
+ duration_ns / kNanosecondsPerSecond);
+ else
+ fprintf(o, "%sDuration (nano): %" PRId64 "\n",
+ indent->indent_str().c_str(), duration_ns);
+
+ fprintf(o, "%s# Blocks : %ld\n", indent->indent_str().c_str(),
+ cluster.GetEntryCount());
+ }
+
+ if (options.output_blocks) {
+ const mkvparser::BlockEntry* block_entry;
+ long status = cluster.GetFirst(block_entry);
+ if (status) {
+ fprintf(stderr, "Could not get first Block of Cluster.\n");
+ return false;
+ }
+
+ std::vector<unsigned char> vector_data;
+ while (block_entry != NULL && !block_entry->EOS()) {
+ const mkvparser::Block* const block = block_entry->GetBlock();
+ if (!block) {
+ fprintf(stderr, "Could not getblock entry.\n");
+ return false;
+ }
+
+ const unsigned int track_number =
+ static_cast<unsigned int>(block->GetTrackNumber());
+ const mkvparser::Track* track = tracks.GetTrackByNumber(track_number);
+ if (!track) {
+ fprintf(stderr, "Could not get Track.\n");
+ return false;
+ }
+
+ const int64_t track_type = track->GetType();
+ if ((track_type == mkvparser::Track::kVideo && options.output_video) ||
+ (track_type == mkvparser::Track::kAudio && options.output_audio)) {
+ const int64_t time_ns = block->GetTime(&cluster);
+ const bool is_key = block->IsKey();
+
+ if (block_entry->GetKind() == mkvparser::BlockEntry::kBlockGroup) {
+ fprintf(o, "%sBlockGroup:\n", indent->indent_str().c_str());
+ indent->Adjust(libwebm::kIncreaseIndent);
+ }
+
+ fprintf(o, "%sBlock: type:%s frame:%s", indent->indent_str().c_str(),
+ track_type == mkvparser::Track::kVideo ? "V" : "A",
+ is_key ? "I" : "P");
+ if (options.output_seconds)
+ fprintf(o, " secs:%5g", time_ns / kNanosecondsPerSecond);
+ else
+ fprintf(o, " nano:%10" PRId64, time_ns);
+
+ if (options.output_offset)
+ fprintf(o, " @_payload: %lld", block->m_start);
+ if (options.output_size)
+ fprintf(o, " size_payload: %lld", block->m_size);
+
+ const uint8_t KEncryptedBit = 0x1;
+ const uint8_t kSubSampleBit = 0x2;
+ const int kSignalByteSize = 1;
+ bool encrypted_stream = false;
+ if (options.output_encrypted_info) {
+ if (track->GetContentEncodingCount() > 0) {
+ // Only check the first content encoding.
+ const ContentEncoding* const encoding =
+ track->GetContentEncodingByIndex(0);
+ if (encoding) {
+ if (encoding->GetEncryptionCount() > 0) {
+ const ContentEncoding::ContentEncryption* const encryption =
+ encoding->GetEncryptionByIndex(0);
+ if (encryption) {
+ const ContentEncoding::ContentEncAESSettings& aes =
+ encryption->aes_settings;
+ if (aes.cipher_mode == 1) {
+ encrypted_stream = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (encrypted_stream) {
+ const mkvparser::Block::Frame& frame = block->GetFrame(0);
+ if (frame.len > static_cast<int>(vector_data.size())) {
+ vector_data.resize(frame.len + 1024);
+ }
+
+ unsigned char* data = &vector_data[0];
+ if (frame.Read(reader, data) < 0) {
+ fprintf(stderr, "Could not read frame.\n");
+ return false;
+ }
+
+ const bool encrypted_frame = !!(data[0] & KEncryptedBit);
+ const bool sub_sample_encrypt = !!(data[0] & kSubSampleBit);
+ fprintf(o, " enc: %d", encrypted_frame ? 1 : 0);
+ fprintf(o, " sub: %d", sub_sample_encrypt ? 1 : 0);
+
+ if (encrypted_frame) {
+ uint64_t iv;
+ memcpy(&iv, data + kSignalByteSize, sizeof(iv));
+ fprintf(o, " iv: %" PRIx64, iv);
+ }
+ }
+ }
+
+ if (options.output_codec_info) {
+ const int frame_count = block->GetFrameCount();
+
+ if (frame_count > 1) {
+ fprintf(o, "\n");
+ indent->Adjust(libwebm::kIncreaseIndent);
+ }
+
+ for (int i = 0; i < frame_count; ++i) {
+ if (track_type == mkvparser::Track::kVideo) {
+ const mkvparser::Block::Frame& frame = block->GetFrame(i);
+ if (frame.len > static_cast<int>(vector_data.size())) {
+ vector_data.resize(frame.len + 1024);
+ }
+
+ unsigned char* data = &vector_data[0];
+ if (frame.Read(reader, data) < 0) {
+ fprintf(stderr, "Could not read frame.\n");
+ return false;
+ }
+
+ if (frame_count > 1)
+ fprintf(o, "\n%sVP8 data :", indent->indent_str().c_str());
+
+ bool encrypted_frame = false;
+ bool sub_sample_encrypt = false;
+ int frame_size = static_cast<int>(frame.len);
+
+ int frame_offset = 0;
+ if (encrypted_stream) {
+ if (data[0] & KEncryptedBit) {
+ encrypted_frame = true;
+ if (data[0] & kSubSampleBit) {
+ sub_sample_encrypt = true;
+ data += kSignalByteSize;
+ frame_size -= kSignalByteSize;
+ frame_offset =
+ PrintSubSampleEncryption(data, frame_size, o);
+ }
+ } else {
+ frame_offset = kSignalByteSize;
+ }
+ }
+
+ if (!encrypted_frame || sub_sample_encrypt) {
+ data += frame_offset;
+ frame_size -= frame_offset;
+
+ const string codec_id = ToString(track->GetCodecId());
+ if (codec_id == "V_VP8") {
+ PrintVP8Info(data, frame_size, o);
+ } else if (codec_id == "V_VP9") {
+ PrintVP9Info(data, frame_size, o, time_ns, stats, parser,
+ level_stats);
+ }
+ }
+ }
+ }
+
+ if (frame_count > 1)
+ indent->Adjust(libwebm::kDecreaseIndent);
+ }
+
+ if (block_entry->GetKind() == mkvparser::BlockEntry::kBlockGroup) {
+ const int64_t discard_padding = block->GetDiscardPadding();
+ if (discard_padding != 0) {
+ fprintf(o, "\n%sDiscardPadding: %10" PRId64,
+ indent->indent_str().c_str(), discard_padding);
+ }
+ indent->Adjust(libwebm::kDecreaseIndent);
+ }
+
+ fprintf(o, "\n");
+ }
+
+ status = cluster.GetNext(block_entry, block_entry);
+ if (status) {
+ printf("\n Could not get next block of cluster.\n");
+ return false;
+ }
+ }
+ }
+
+ if (options.output_clusters)
+ indent->Adjust(libwebm::kDecreaseIndent);
+
+ return true;
+}
+
+bool OutputCues(const mkvparser::Segment& segment,
+ const mkvparser::Tracks& tracks, const Options& options,
+ FILE* o, Indent* indent) {
+ const mkvparser::Cues* const cues = segment.GetCues();
+ if (cues == NULL)
+ return true;
+
+ // Load all of the cue points.
+ while (!cues->DoneParsing())
+ cues->LoadCuePoint();
+
+ // Confirm that the input has cue points.
+ const mkvparser::CuePoint* const first_cue = cues->GetFirst();
+ if (first_cue == NULL) {
+ fprintf(o, "%sNo cue points.\n", indent->indent_str().c_str());
+ return true;
+ }
+
+ // Input has cue points, dump them:
+ fprintf(o, "%sCues:", indent->indent_str().c_str());
+ if (options.output_offset)
+ fprintf(o, " @:%lld", cues->m_element_start);
+ if (options.output_size)
+ fprintf(o, " size:%lld", cues->m_element_size);
+ fprintf(o, "\n");
+
+ const mkvparser::CuePoint* cue_point = first_cue;
+ int cue_point_num = 1;
+ const int num_tracks = static_cast<int>(tracks.GetTracksCount());
+ indent->Adjust(libwebm::kIncreaseIndent);
+
+ do {
+ for (int track_num = 0; track_num < num_tracks; ++track_num) {
+ const mkvparser::Track* const track = tracks.GetTrackByIndex(track_num);
+ const mkvparser::CuePoint::TrackPosition* const track_pos =
+ cue_point->Find(track);
+
+ if (track_pos != NULL) {
+ const char track_type =
+ (track->GetType() == mkvparser::Track::kVideo) ? 'V' : 'A';
+ fprintf(o, "%sCue Point:%d type:%c track:%d",
+ indent->indent_str().c_str(), cue_point_num, track_type,
+ static_cast<int>(track->GetNumber()));
+
+ if (options.output_seconds) {
+ fprintf(o, " secs:%g",
+ cue_point->GetTime(&segment) / kNanosecondsPerSecond);
+ } else {
+ fprintf(o, " nano:%lld", cue_point->GetTime(&segment));
+ }
+
+ if (options.output_blocks)
+ fprintf(o, " block:%lld", track_pos->m_block);
+
+ if (options.output_offset)
+ fprintf(o, " @:%lld", track_pos->m_pos);
+
+ fprintf(o, "\n");
+ }
+ }
+
+ cue_point = cues->GetNext(cue_point);
+ ++cue_point_num;
+ } while (cue_point != NULL);
+
+ indent->Adjust(libwebm::kDecreaseIndent);
+ return true;
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ string input;
+ Options options;
+
+ const int argc_check = argc - 1;
+ for (int i = 1; i < argc; ++i) {
+ if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i])) {
+ Usage();
+ return EXIT_SUCCESS;
+ } else if (!strcmp("-v", argv[i])) {
+ printf("version: %s\n", VERSION_STRING);
+ } else if (!strcmp("-i", argv[i]) && i < argc_check) {
+ input = argv[++i];
+ } else if (!strcmp("-all", argv[i])) {
+ options.SetAll(true);
+ } else if (Options::MatchesBooleanOption("video", argv[i])) {
+ options.output_video = !strcmp("-video", argv[i]);
+ } else if (Options::MatchesBooleanOption("audio", argv[i])) {
+ options.output_audio = !strcmp("-audio", argv[i]);
+ } else if (Options::MatchesBooleanOption("size", argv[i])) {
+ options.output_size = !strcmp("-size", argv[i]);
+ } else if (Options::MatchesBooleanOption("offset", argv[i])) {
+ options.output_offset = !strcmp("-offset", argv[i]);
+ } else if (Options::MatchesBooleanOption("times_seconds", argv[i])) {
+ options.output_seconds = !strcmp("-times_seconds", argv[i]);
+ } else if (Options::MatchesBooleanOption("ebml_header", argv[i])) {
+ options.output_ebml_header = !strcmp("-ebml_header", argv[i]);
+ } else if (Options::MatchesBooleanOption("segment", argv[i])) {
+ options.output_segment = !strcmp("-segment", argv[i]);
+ } else if (Options::MatchesBooleanOption("seekhead", argv[i])) {
+ options.output_seekhead = !strcmp("-seekhead", argv[i]);
+ } else if (Options::MatchesBooleanOption("segment_info", argv[i])) {
+ options.output_segment_info = !strcmp("-segment_info", argv[i]);
+ } else if (Options::MatchesBooleanOption("tracks", argv[i])) {
+ options.output_tracks = !strcmp("-tracks", argv[i]);
+ } else if (Options::MatchesBooleanOption("clusters", argv[i])) {
+ options.output_clusters = !strcmp("-clusters", argv[i]);
+ } else if (Options::MatchesBooleanOption("blocks", argv[i])) {
+ options.output_blocks = !strcmp("-blocks", argv[i]);
+ } else if (Options::MatchesBooleanOption("codec_info", argv[i])) {
+ options.output_codec_info = !strcmp("-codec_info", argv[i]);
+ } else if (Options::MatchesBooleanOption("clusters_size", argv[i])) {
+ options.output_clusters_size = !strcmp("-clusters_size", argv[i]);
+ } else if (Options::MatchesBooleanOption("encrypted_info", argv[i])) {
+ options.output_encrypted_info = !strcmp("-encrypted_info", argv[i]);
+ } else if (Options::MatchesBooleanOption("cues", argv[i])) {
+ options.output_cues = !strcmp("-cues", argv[i]);
+ } else if (Options::MatchesBooleanOption("frame_stats", argv[i])) {
+ options.output_frame_stats = !strcmp("-frame_stats", argv[i]);
+ } else if (Options::MatchesBooleanOption("vp9_level", argv[i])) {
+ options.output_vp9_level = !strcmp("-vp9_level", argv[i]);
+ }
+ }
+
+ if (argc < 3 || input.empty()) {
+ Usage();
+ return EXIT_FAILURE;
+ }
+
+ std::unique_ptr<mkvparser::MkvReader> reader(
+ new (std::nothrow) mkvparser::MkvReader()); // NOLINT
+ if (reader->Open(input.c_str())) {
+ fprintf(stderr, "Error opening file:%s\n", input.c_str());
+ return EXIT_FAILURE;
+ }
+
+ long long int pos = 0;
+ std::unique_ptr<mkvparser::EBMLHeader> ebml_header(
+ new (std::nothrow) mkvparser::EBMLHeader()); // NOLINT
+ if (ebml_header->Parse(reader.get(), pos) < 0) {
+ fprintf(stderr, "Error parsing EBML header.\n");
+ return EXIT_FAILURE;
+ }
+
+ Indent indent(0);
+ FILE* out = stdout;
+
+ if (options.output_ebml_header)
+ OutputEBMLHeader(*ebml_header.get(), out, &indent);
+
+ mkvparser::Segment* temp_segment;
+ if (mkvparser::Segment::CreateInstance(reader.get(), pos, temp_segment)) {
+ fprintf(stderr, "Segment::CreateInstance() failed.\n");
+ return EXIT_FAILURE;
+ }
+ std::unique_ptr<mkvparser::Segment> segment(temp_segment);
+
+ if (segment->Load() < 0) {
+ fprintf(stderr, "Segment::Load() failed.\n");
+ return EXIT_FAILURE;
+ }
+
+ if (options.output_segment) {
+ OutputSegment(*(segment.get()), options, out);
+ indent.Adjust(libwebm::kIncreaseIndent);
+ }
+
+ if (options.output_seekhead)
+ if (!OutputSeekHead(*(segment.get()), options, out, &indent))
+ return EXIT_FAILURE;
+
+ if (options.output_segment_info)
+ if (!OutputSegmentInfo(*(segment.get()), options, out, &indent))
+ return EXIT_FAILURE;
+
+ if (options.output_tracks)
+ if (!OutputTracks(*(segment.get()), options, out, &indent))
+ return EXIT_FAILURE;
+
+ const mkvparser::Tracks* const tracks = segment->GetTracks();
+ if (!tracks) {
+ fprintf(stderr, "Could not get Tracks.\n");
+ return EXIT_FAILURE;
+ }
+
+ // If Cues are before the clusters output them first.
+ if (options.output_cues) {
+ const mkvparser::Cluster* cluster = segment->GetFirst();
+ const mkvparser::Cues* const cues = segment->GetCues();
+ if (cluster != NULL && cues != NULL) {
+ if (cues->m_element_start < cluster->m_element_start) {
+ if (!OutputCues(*segment, *tracks, options, out, &indent)) {
+ return EXIT_FAILURE;
+ }
+ options.output_cues = false;
+ }
+ }
+ }
+
+ if (options.output_clusters)
+ fprintf(out, "%sClusters (count):%ld\n", indent.indent_str().c_str(),
+ segment->GetCount());
+
+ int64_t clusters_size = 0;
+ FrameStats stats;
+ vp9_parser::Vp9HeaderParser parser;
+ vp9_parser::Vp9LevelStats level_stats;
+ const mkvparser::Cluster* cluster = segment->GetFirst();
+ while (cluster != NULL && !cluster->EOS()) {
+ if (!OutputCluster(*cluster, *tracks, options, out, reader.get(), &indent,
+ &clusters_size, &stats, &parser, &level_stats))
+ return EXIT_FAILURE;
+ cluster = segment->GetNext(cluster);
+ }
+
+ if (options.output_clusters_size)
+ fprintf(out, "%sClusters (size):%" PRId64 "\n", indent.indent_str().c_str(),
+ clusters_size);
+
+ if (options.output_cues)
+ if (!OutputCues(*segment, *tracks, options, out, &indent))
+ return EXIT_FAILURE;
+
+ // TODO(fgalligan): Add support for VP8.
+ if (options.output_frame_stats &&
+ stats.minimum_altref_distance != std::numeric_limits<int>::max()) {
+ const double actual_fps =
+ stats.frames /
+ (segment->GetInfo()->GetDuration() / kNanosecondsPerSecond);
+ const double displayed_fps =
+ stats.displayed_frames /
+ (segment->GetInfo()->GetDuration() / kNanosecondsPerSecond);
+ fprintf(out, "\nActual fps:%g Displayed fps:%g\n", actual_fps,
+ displayed_fps);
+
+ fprintf(out, "Minimum Altref Distance:%d at:%g seconds\n",
+ stats.minimum_altref_distance,
+ stats.min_altref_end_ns / kNanosecondsPerSecond);
+
+ // TODO(fgalligan): Add support for window duration other than 1 second.
+ const double sec_end = stats.max_window_end_ns / kNanosecondsPerSecond;
+ const double sec_start =
+ stats.max_window_end_ns > kNanosecondsPerSecondi ? sec_end - 1.0 : 0.0;
+ fprintf(out, "Maximum Window:%g-%g seconds Window fps:%" PRId64 "\n",
+ sec_start, sec_end, stats.max_window_size);
+ }
+
+ if (options.output_vp9_level) {
+ level_stats.set_duration(segment->GetInfo()->GetDuration());
+ const vp9_parser::Vp9Level level = level_stats.GetLevel();
+ fprintf(out, "VP9 Level:%d\n", level);
+ fprintf(
+ out,
+ "mlsr:%" PRId64 " mlps:%" PRId64 " mlpb:%" PRId64
+ " abr:%g mcs:%g cr:%g mct:%d"
+ " mad:%d mrf:%d\n",
+ level_stats.GetMaxLumaSampleRate(), level_stats.GetMaxLumaPictureSize(),
+ level_stats.GetMaxLumaPictureBreadth(), level_stats.GetAverageBitRate(),
+ level_stats.GetMaxCpbSize(), level_stats.GetCompressionRatio(),
+ level_stats.GetMaxColumnTiles(), level_stats.GetMinimumAltrefDistance(),
+ level_stats.GetMaxReferenceFrames());
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/webm_parser/README.md b/webm_parser/README.md
new file mode 100644
index 0000000..f3d37ff
--- /dev/null
+++ b/webm_parser/README.md
@@ -0,0 +1,325 @@
+# WebM Parser {#mainpage}
+
+# Introduction
+
+This WebM parser is a C++11-based parser that aims to be a safe and complete
+parser for WebM. It supports all WebM elements (from the old deprecated ones to
+the newest ones like `Colour`), including recursive elements like `ChapterAtom`
+and `SimpleTag`. It supports incremental parsing; parsing may be stopped at any
+point and resumed later as needed. It also supports starting at an arbitrary
+WebM element, so parsing need not start from the beginning of the file.
+
+The parser (`WebmParser`) works by being fed input data from a data source (an
+instance of `Reader`) that represents a WebM file. The parser will parse the
+WebM data into various data structures that represent the encoded WebM elements,
+and then call corresponding `Callback` event methods as the data structures are
+parsed.
+
+# Building
+
+CMake support has been added to the root libwebm `CMakeLists.txt` file. Simply
+enable the `ENABLE_WEBM_PARSER` feature if using the interactive CMake builder,
+or alternatively pass the `-DENABLE_WEBM_PARSER:BOOL=ON` flag from the command
+line. By default, this parser is not enabled when building libwebm, so you must
+explicitly enable it.
+
+Alternatively, the following illustrates the minimal commands necessary to
+compile the code into a static library without CMake:
+
+```.sh
+c++ -Iinclude -I. -std=c++11 -c src/*.cc
+ar rcs libwebm.a *.o
+```
+
+# Using the parser
+
+There are 3 basic components in the parser that are used: `Reader`, `Callback`,
+and `WebmParser`.
+
+## `Reader`
+
+The `Reader` interface acts as a data source for the parser. You may subclass it
+and implement your own data source if you wish. Alternatively, use the
+`FileReader`, `IstreamReader`, or `BufferReader` if you wish to read from a
+`FILE*`, `std::istream`, or `std::vector<std::uint8_t>`, respectively.
+
+The parser supports `Reader` implementations that do short reads. If
+`Reader::Skip()` or `Reader::Read()` do a partial read (returning
+`Status::kOkPartial`), the parser will call them again in an attempt to read
+more data. If no data is available, the `Reader` may return some other status
+(like `Status::kWouldBlock`) to indicate that no data is available. In this
+situation, the parser will stop parsing and return the status it received.
+Parsing may be resumed later when more data is available.
+
+When the `Reader` has reached the end of the WebM document and no more data is
+available, it should return `Status::kEndOfFile`. This will cause parsing to
+stop. If the file ends at a valid location (that is, there aren't any elements
+that have specified a size that indicates the file ended prematurely), the
+parser will translate `Status::kEndOfFile` into `Status::kOkCompleted` and
+return it. If the file ends prematurely, the parser will return
+`Status::kEndOfFile` to indicate that.
+
+Note that if the WebM file contains elements that have an unknown size (or a
+seek has been performed and the parser doesn't know the size of the root
+element(s)), and the parser is parsing them and hits end-of-file, the parser may
+still call `Reader::Read()`/`Reader::Skip()` multiple times (even though they've
+already reported `Status::kEndOfFile`) as nested parsers terminate parsing.
+Because of this, `Reader::Read()`/`Reader::Skip()` implementations should be
+able to handle being called multiple times after the file's end has been
+reached, and they should consistently return `Status::kEndOfFile`.
+
+The three provided readers (`FileReader`, `IstreamReader`, and `BufferReader`)
+are blocking implementations (they won't return `Status::kWouldBlock`), so if
+you're using them the parser will run until it entirely consumes all their data
+(unless, of course, you request the parser to stop via `Callback`... see the
+next section).
+
+## `Callback`
+
+As the parser progresses through the file, it builds objects (see
+`webm/dom_types.h`) that represent parsed data structures. The parser then
+notifies the `Callback` implementation as objects complete parsing. For some
+data structures (like frames or Void elements), the parser notifies the
+`Callback` and requests it to consume the data directly from the `Reader` (this
+is done for structures that can be large/frequent binary blobs in order to allow
+you to read the data directly into the object/type of your choice, rather than
+just reading them into a `std::vector<std::uint8_t>` and making you copy it into
+a different object if you wanted to work with something other than
+`std::vector<std::uint8_t>`).
+
+The parser was designed to parse the data into objects that are small enough
+that the `Callback` can be quickly and frequently notified as soon as the object
+is ready, but large enough that the objects received by the `Callback` are still
+useful. Having `Callback` events for every tiny integer/float/string/etc.
+element would require too much assembly and work to be useful to most users, and
+pasing the file into a single DOM tree (or a small handful of large conglomerate
+structures) would unnecessarily delay video playback or consume too much memory
+on smaller devices.
+
+The parser may call the following methods while nearly anywhere in the file:
+
+- `Callback::OnElementBegin()`: This is called for every element that the
+ parser encounters. This is primarily useful if you want to skip some
+ elements or build a map of every element in the file.
+- `Callback::OnUnknownElement()`: This is called when an element is either not
+ a valid/recognized WebM element, or it is a WebM element but is improperly
+ nested (e.g. an EBMLVersion element inside of a Segment element). The parser
+ doesn't know how to handle the element; it could just skip it but instead
+ defers to the `Callback` to decide how it should be handled. The default
+ implementation just skips the element.
+- `Callback::OnVoid()`: Void elements can appear anywhere in any master
+ element. This method will be called to handle the Void element.
+
+The parser may call the following methods in the proper nesting order, as shown
+in the list. A `*Begin()` method will always be matched up with its
+corresponding `*End()` method (unless a seek has been performed). The parser
+will only call the methods in the proper nesting order as specified in the WebM
+DOM. For example, `Callback::OnEbml()` will never be called in between
+`Callback::OnSegmentBegin()`/`Callback::OnSegmentEnd()` (since the EBML element
+is not a child of the Segment element), and `Callback::OnTrackEntry()` will only
+ever be called in between
+`Callback::OnSegmentBegin()`/`Callback::OnSegmentEnd()` (since the TrackEntry
+element is a (grand-)child of the Segment element and must be contained by a
+Segment element). `Callback::OnFrame()` is listed twice because it will be
+called to handle frames contained in both SimpleBlock and Block elements.
+
+- `Callback::OnEbml()`
+- `Callback::OnSegmentBegin()`
+ - `Callback::OnSeek()`
+ - `Callback::OnInfo()`
+ - `Callback::OnClusterBegin()`
+ - `Callback::OnSimpleBlockBegin()`
+ - `Callback::OnFrame()`
+ - `Callback::OnSimpleBlockEnd()`
+ - `Callback::OnBlockGroupBegin()`
+ - `Callback::OnBlockBegin()`
+ - `Callback::OnFrame()`
+ - `Callback::OnBlockEnd()`
+ - `Callback::OnBlockGroupEnd()`
+ - `Callback::OnClusterEnd()`
+ - `Callback::OnTrackEntry()`
+ - `Callback::OnCuePoint()`
+ - `Callback::OnEditionEntry()`
+ - `Callback::OnTag()`
+- `Callback::OnSegmentEnd()`
+
+Only `Callback::OnFrame()` (and no other `Callback` methods) will be called in
+between `Callback::OnSimpleBlockBegin()`/`Callback::OnSimpleBlockEnd()` or
+`Callback::OnBlockBegin()`/`Callback::OnBlockEnd()`, since the SimpleBlock and
+Block elements are not master elements only contain frames.
+
+Note that seeking into the middle of the file may cause the parser to skip some
+`*Begin()` methods. For example, if a seek is performed to a SimpleBlock
+element, `Callback::OnSegmentBegin()` and `Callback::OnClusterBegin()` will not
+be called. In this situation, the full sequence of callback events would be
+(assuming the file ended after the SimpleBlock):
+`Callback::OnSimpleBlockBegin()`, `Callback::OnFrame()` (for every frame in the
+SimpleBlock), `Callback::OnSimpleBlockEnd()`, `Callback::OnClusterEnd()`, and
+`Callback::OnSegmentEnd()`. Since the Cluster and Segment elements were skipped,
+the `Cluster` DOM object may have some members marked as absent, and the
+`*End()` events for the Cluster and Segment elements will have metadata with
+unknown header position, header length, and body size (see `kUnknownHeaderSize`,
+`kUnknownElementSize`, and `kUnknownElementPosition`).
+
+When a `Callback` method has completed, it should return `Status::kOkCompleted`
+to allow parsing to continue. If you would like parsing to stop, return any
+other status code (except `Status::kEndOfFile`, since that's treated somewhat
+specially and is intended for `Reader`s to use), which the parser will return.
+If you return a non-parsing-error status code (.e.g. `Status::kOkPartial`,
+`Status::kWouldBlock`, etc. or your own status code with a value > 0), parsing
+may be resumed again. When parsing is resumed, the parser will call the same
+callback method again (and once again, you may return `Status::kOkCompleted` to
+let parsing continue or some other value to stop parsing).
+
+You may subclass the `Callback` element and override methods which you are
+interested in receiving events for. By default, methods taking an `Action`
+parameter will set it to `Action::kRead` so the entire file is parsed. The
+`Callback::OnFrame()` method will just skip over the frame bytes by default.
+
+## `WebmParser`
+
+The actual parsing work is done with `WebmParser`. Simply construct a
+`WebmParser` and call `WebmParser::Feed()` (providing it a `Callback` and
+`Reader` instance) to parse a file. It will return `Status::kOkCompleted` when
+the entire file has been successfully parsed. `WebmParser::Feed()` doesn't store
+any internal references to the `Callback` or `Reader`.
+
+If you wish to start parsing from the middle of a file, call
+`WebmParser::DidSeek()` before calling `WebmParser::Feed()` to prepare the
+parser to receive data starting at an arbitrary point in the file. When seeking,
+you should seek to the beginning of a WebM element; seeking to a location that
+is not the start of a WebM element (e.g. seeking to a frame, rather than its
+containing SimpleBlock/Block element) will cause parsing to fail. Calling
+`WebmParser::DidSeek()` will reset the state of the parser and clear any
+internal errors, so a `WebmParser` instance may be reused (even if it has
+previously failed to parse a file).
+
+## Building your program
+
+The following program is a small program that completely parses a file from
+stdin:
+
+```.cc
+#include <webm/callback.h>
+#include <webm/file_reader.h>
+#include <webm/webm_parser.h>
+
+int main() {
+ webm::Callback callback;
+ webm::FileReader reader(std::freopen(nullptr, "rb", stdin));
+ webm::WebmParser parser;
+ parser.Feed(&callback, &reader);
+}
+```
+
+It completely parses the input file, but we need to make a new class that
+derives from `Callback` if we want to receive any parsing events. So if we
+change it to:
+
+```.cc
+#include <iomanip>
+#include <iostream>
+
+#include <webm/callback.h>
+#include <webm/file_reader.h>
+#include <webm/status.h>
+#include <webm/webm_parser.h>
+
+class MyCallback : public webm::Callback {
+ public:
+ webm::Status OnElementBegin(const webm::ElementMetadata& metadata,
+ webm::Action* action) override {
+ std::cout << "Element ID = 0x"
+ << std::hex << static_cast<std::uint32_t>(metadata.id);
+ std::cout << std::dec; // Reset to decimal mode.
+ std::cout << " at position ";
+ if (metadata.position == webm::kUnknownElementPosition) {
+ // The position will only be unknown if we've done a seek. But since we
+ // aren't seeking in this demo, this will never be the case. However, this
+ // if-statement is included for completeness.
+ std::cout << "<unknown>";
+ } else {
+ std::cout << metadata.position;
+ }
+ std::cout << " with header size ";
+ if (metadata.header_size == webm::kUnknownHeaderSize) {
+ // The header size will only be unknown if we've done a seek. But since we
+ // aren't seeking in this demo, this will never be the case. However, this
+ // if-statement is included for completeness.
+ std::cout << "<unknown>";
+ } else {
+ std::cout << metadata.header_size;
+ }
+ std::cout << " and body size ";
+ if (metadata.size == webm::kUnknownElementSize) {
+ // WebM master elements may have an unknown size, though this is rare.
+ std::cout << "<unknown>";
+ } else {
+ std::cout << metadata.size;
+ }
+ std::cout << '\n';
+
+ *action = webm::Action::kRead;
+ return webm::Status(webm::Status::kOkCompleted);
+ }
+};
+
+int main() {
+ MyCallback callback;
+ webm::FileReader reader(std::freopen(nullptr, "rb", stdin));
+ webm::WebmParser parser;
+ webm::Status status = parser.Feed(&callback, &reader);
+ if (status.completed_ok()) {
+ std::cout << "Parsing successfully completed\n";
+ } else {
+ std::cout << "Parsing failed with status code: " << status.code << '\n';
+ }
+}
+```
+
+This will output information about every element in the entire file: it's ID,
+position, header size, and body size. The status of the parse is also checked
+and reported.
+
+For a more complete example, see `demo/demo.cc`, which parses an entire file and
+prints out all of its information. That example overrides every `Callback`
+method to show exactly what information is available while parsing and how to
+access it. The example is verbose, but that's primarily due to pretty-printing
+and string formatting operations.
+
+When compiling your program, add the `include` directory to your compiler's
+header search paths and link to the compiled library. Be sure your compiler has
+C++11 mode enabled (`-std=c++11` in clang++ or g++).
+
+# Testing
+
+Unit tests are located in the `tests` directory. Google Test and Google Mock are
+used as testing frameworks. Building and running the tests will be supported in
+the upcoming CMake scripts, but they can currently be built and run by manually
+compiling them (and linking to Google Test and Google Mock).
+
+# Fuzzing
+
+The parser has been fuzzed with [AFL](http://lcamtuf.coredump.cx/afl/) and
+[libFuzzer](http://llvm.org/docs/LibFuzzer.html). If you wish to fuzz the parser
+with AFL or libFuzzer but don't want to write an executable that exercises the
+parsing API, you may use `fuzzing/webm_fuzzer.cc`.
+
+When compiling for fuzzing, define the macro
+`WEBM_FUZZER_BYTE_ELEMENT_SIZE_LIMIT` to be some integer in order to limit the
+maximum size of ASCII/UTF-8/binary elements. It's too easy for the fuzzer to
+generate elements that claim to have a ridiculously massive size, which will
+cause allocations to fail or the program to allocate too much memory. AFL will
+terminate the process if it allocates too much memory (by default, 50 MB), and
+the [Address Sanitizer doesn't throw `std::bad_alloc` when an allocation fails]
+(https://github.com/google/sanitizers/issues/295). Defining
+`WEBM_FUZZER_BYTE_ELEMENT_SIZE_LIMIT` to a low number (say, 1024) will cause the
+ASCII/UTF-8/binary element parsers to return `Status::kNotEnoughMemory` if the
+element's size exceeds `WEBM_FUZZER_BYTE_ELEMENT_SIZE_LIMIT`, which will avoid
+false positives when fuzzing. The parser expects `std::string` and `std::vector`
+to throw `std::bad_alloc` when an allocation fails, which doesn't necessarily
+happen due to the fuzzers' limitations.
+
+You may also define the macro `WEBM_FUZZER_SEEK_FIRST` to have
+`fuzzing/webm_fuzzer.cc` call `WebmParser::DidSeek()` before doing any parsing.
+This will test the seeking code paths.
diff --git a/webm_parser/demo/demo.cc b/webm_parser/demo/demo.cc
new file mode 100644
index 0000000..47ecbb0
--- /dev/null
+++ b/webm_parser/demo/demo.cc
@@ -0,0 +1,1161 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include <cstdio>
+#include <cstdlib>
+#include <iomanip>
+#include <iostream>
+
+#include "webm/callback.h"
+#include "webm/file_reader.h"
+#include "webm/status.h"
+#include "webm/webm_parser.h"
+
+// We use pretty much everything in the webm namespace. Just pull
+// it all in.
+using namespace webm; // NOLINT
+
+template <typename T>
+std::ostream& PrintUnknownEnumValue(std::ostream& os, T value) {
+ return os << std::to_string(static_cast<std::uint64_t>(value)) << " (?)";
+}
+
+// Overloads for operator<< for pretty printing enums.
+std::ostream& operator<<(std::ostream& os, Id id) {
+ switch (id) {
+ case Id::kEbml:
+ return os << "EBML";
+ case Id::kEbmlVersion:
+ return os << "EBMLVersion";
+ case Id::kEbmlReadVersion:
+ return os << "EBMLReadVersion";
+ case Id::kEbmlMaxIdLength:
+ return os << "EBMLMaxIDLength";
+ case Id::kEbmlMaxSizeLength:
+ return os << "EBMLMaxSizeLength";
+ case Id::kDocType:
+ return os << "DocType";
+ case Id::kDocTypeVersion:
+ return os << "DocTypeVersion";
+ case Id::kDocTypeReadVersion:
+ return os << "DocTypeReadVersion";
+ case Id::kVoid:
+ return os << "Void";
+ case Id::kSegment:
+ return os << "Segment";
+ case Id::kSeekHead:
+ return os << "SeekHead";
+ case Id::kSeek:
+ return os << "Seek";
+ case Id::kSeekId:
+ return os << "SeekID";
+ case Id::kSeekPosition:
+ return os << "SeekPosition";
+ case Id::kInfo:
+ return os << "Info";
+ case Id::kTimecodeScale:
+ return os << "TimecodeScale";
+ case Id::kDuration:
+ return os << "Duration";
+ case Id::kDateUtc:
+ return os << "DateUTC";
+ case Id::kTitle:
+ return os << "Title";
+ case Id::kMuxingApp:
+ return os << "MuxingApp";
+ case Id::kWritingApp:
+ return os << "WritingApp";
+ case Id::kCluster:
+ return os << "Cluster";
+ case Id::kTimecode:
+ return os << "Timecode";
+ case Id::kPrevSize:
+ return os << "PrevSize";
+ case Id::kSimpleBlock:
+ return os << "SimpleBlock";
+ case Id::kBlockGroup:
+ return os << "BlockGroup";
+ case Id::kBlock:
+ return os << "Block";
+ case Id::kBlockVirtual:
+ return os << "BlockVirtual";
+ case Id::kBlockAdditions:
+ return os << "BlockAdditions";
+ case Id::kBlockMore:
+ return os << "BlockMore";
+ case Id::kBlockAddId:
+ return os << "BlockAddID";
+ case Id::kBlockAdditional:
+ return os << "BlockAdditional";
+ case Id::kBlockDuration:
+ return os << "BlockDuration";
+ case Id::kReferenceBlock:
+ return os << "ReferenceBlock";
+ case Id::kDiscardPadding:
+ return os << "DiscardPadding";
+ case Id::kSlices:
+ return os << "Slices";
+ case Id::kTimeSlice:
+ return os << "TimeSlice";
+ case Id::kLaceNumber:
+ return os << "LaceNumber";
+ case Id::kTracks:
+ return os << "Tracks";
+ case Id::kTrackEntry:
+ return os << "TrackEntry";
+ case Id::kTrackNumber:
+ return os << "TrackNumber";
+ case Id::kTrackUid:
+ return os << "TrackUID";
+ case Id::kTrackType:
+ return os << "TrackType";
+ case Id::kFlagEnabled:
+ return os << "FlagEnabled";
+ case Id::kFlagDefault:
+ return os << "FlagDefault";
+ case Id::kFlagForced:
+ return os << "FlagForced";
+ case Id::kFlagLacing:
+ return os << "FlagLacing";
+ case Id::kDefaultDuration:
+ return os << "DefaultDuration";
+ case Id::kName:
+ return os << "Name";
+ case Id::kLanguage:
+ return os << "Language";
+ case Id::kCodecId:
+ return os << "CodecID";
+ case Id::kCodecPrivate:
+ return os << "CodecPrivate";
+ case Id::kCodecName:
+ return os << "CodecName";
+ case Id::kCodecDelay:
+ return os << "CodecDelay";
+ case Id::kSeekPreRoll:
+ return os << "SeekPreRoll";
+ case Id::kVideo:
+ return os << "Video";
+ case Id::kFlagInterlaced:
+ return os << "FlagInterlaced";
+ case Id::kStereoMode:
+ return os << "StereoMode";
+ case Id::kAlphaMode:
+ return os << "AlphaMode";
+ case Id::kPixelWidth:
+ return os << "PixelWidth";
+ case Id::kPixelHeight:
+ return os << "PixelHeight";
+ case Id::kPixelCropBottom:
+ return os << "PixelCropBottom";
+ case Id::kPixelCropTop:
+ return os << "PixelCropTop";
+ case Id::kPixelCropLeft:
+ return os << "PixelCropLeft";
+ case Id::kPixelCropRight:
+ return os << "PixelCropRight";
+ case Id::kDisplayWidth:
+ return os << "DisplayWidth";
+ case Id::kDisplayHeight:
+ return os << "DisplayHeight";
+ case Id::kDisplayUnit:
+ return os << "DisplayUnit";
+ case Id::kAspectRatioType:
+ return os << "AspectRatioType";
+ case Id::kFrameRate:
+ return os << "FrameRate";
+ case Id::kColour:
+ return os << "Colour";
+ case Id::kMatrixCoefficients:
+ return os << "MatrixCoefficients";
+ case Id::kBitsPerChannel:
+ return os << "BitsPerChannel";
+ case Id::kChromaSubsamplingHorz:
+ return os << "ChromaSubsamplingHorz";
+ case Id::kChromaSubsamplingVert:
+ return os << "ChromaSubsamplingVert";
+ case Id::kCbSubsamplingHorz:
+ return os << "CbSubsamplingHorz";
+ case Id::kCbSubsamplingVert:
+ return os << "CbSubsamplingVert";
+ case Id::kChromaSitingHorz:
+ return os << "ChromaSitingHorz";
+ case Id::kChromaSitingVert:
+ return os << "ChromaSitingVert";
+ case Id::kRange:
+ return os << "Range";
+ case Id::kTransferCharacteristics:
+ return os << "TransferCharacteristics";
+ case Id::kPrimaries:
+ return os << "Primaries";
+ case Id::kMaxCll:
+ return os << "MaxCLL";
+ case Id::kMaxFall:
+ return os << "MaxFALL";
+ case Id::kMasteringMetadata:
+ return os << "MasteringMetadata";
+ case Id::kPrimaryRChromaticityX:
+ return os << "PrimaryRChromaticityX";
+ case Id::kPrimaryRChromaticityY:
+ return os << "PrimaryRChromaticityY";
+ case Id::kPrimaryGChromaticityX:
+ return os << "PrimaryGChromaticityX";
+ case Id::kPrimaryGChromaticityY:
+ return os << "PrimaryGChromaticityY";
+ case Id::kPrimaryBChromaticityX:
+ return os << "PrimaryBChromaticityX";
+ case Id::kPrimaryBChromaticityY:
+ return os << "PrimaryBChromaticityY";
+ case Id::kWhitePointChromaticityX:
+ return os << "WhitePointChromaticityX";
+ case Id::kWhitePointChromaticityY:
+ return os << "WhitePointChromaticityY";
+ case Id::kLuminanceMax:
+ return os << "LuminanceMax";
+ case Id::kLuminanceMin:
+ return os << "LuminanceMin";
+ case Id::kProjection:
+ return os << "Projection";
+ case Id::kProjectionType:
+ return os << "kProjectionType";
+ case Id::kProjectionPrivate:
+ return os << "kProjectionPrivate";
+ case Id::kProjectionPoseYaw:
+ return os << "kProjectionPoseYaw";
+ case Id::kProjectionPosePitch:
+ return os << "kProjectionPosePitch";
+ case Id::kProjectionPoseRoll:
+ return os << "ProjectionPoseRoll";
+ case Id::kAudio:
+ return os << "Audio";
+ case Id::kSamplingFrequency:
+ return os << "SamplingFrequency";
+ case Id::kOutputSamplingFrequency:
+ return os << "OutputSamplingFrequency";
+ case Id::kChannels:
+ return os << "Channels";
+ case Id::kBitDepth:
+ return os << "BitDepth";
+ case Id::kContentEncodings:
+ return os << "ContentEncodings";
+ case Id::kContentEncoding:
+ return os << "ContentEncoding";
+ case Id::kContentEncodingOrder:
+ return os << "ContentEncodingOrder";
+ case Id::kContentEncodingScope:
+ return os << "ContentEncodingScope";
+ case Id::kContentEncodingType:
+ return os << "ContentEncodingType";
+ case Id::kContentEncryption:
+ return os << "ContentEncryption";
+ case Id::kContentEncAlgo:
+ return os << "ContentEncAlgo";
+ case Id::kContentEncKeyId:
+ return os << "ContentEncKeyID";
+ case Id::kContentEncAesSettings:
+ return os << "ContentEncAESSettings";
+ case Id::kAesSettingsCipherMode:
+ return os << "AESSettingsCipherMode";
+ case Id::kCues:
+ return os << "Cues";
+ case Id::kCuePoint:
+ return os << "CuePoint";
+ case Id::kCueTime:
+ return os << "CueTime";
+ case Id::kCueTrackPositions:
+ return os << "CueTrackPositions";
+ case Id::kCueTrack:
+ return os << "CueTrack";
+ case Id::kCueClusterPosition:
+ return os << "CueClusterPosition";
+ case Id::kCueRelativePosition:
+ return os << "CueRelativePosition";
+ case Id::kCueDuration:
+ return os << "CueDuration";
+ case Id::kCueBlockNumber:
+ return os << "CueBlockNumber";
+ case Id::kChapters:
+ return os << "Chapters";
+ case Id::kEditionEntry:
+ return os << "EditionEntry";
+ case Id::kChapterAtom:
+ return os << "ChapterAtom";
+ case Id::kChapterUid:
+ return os << "ChapterUID";
+ case Id::kChapterStringUid:
+ return os << "ChapterStringUID";
+ case Id::kChapterTimeStart:
+ return os << "ChapterTimeStart";
+ case Id::kChapterTimeEnd:
+ return os << "ChapterTimeEnd";
+ case Id::kChapterDisplay:
+ return os << "ChapterDisplay";
+ case Id::kChapString:
+ return os << "ChapString";
+ case Id::kChapLanguage:
+ return os << "ChapLanguage";
+ case Id::kChapCountry:
+ return os << "ChapCountry";
+ case Id::kTags:
+ return os << "Tags";
+ case Id::kTag:
+ return os << "Tag";
+ case Id::kTargets:
+ return os << "Targets";
+ case Id::kTargetTypeValue:
+ return os << "TargetTypeValue";
+ case Id::kTargetType:
+ return os << "TargetType";
+ case Id::kTagTrackUid:
+ return os << "TagTrackUID";
+ case Id::kSimpleTag:
+ return os << "SimpleTag";
+ case Id::kTagName:
+ return os << "TagName";
+ case Id::kTagLanguage:
+ return os << "TagLanguage";
+ case Id::kTagDefault:
+ return os << "TagDefault";
+ case Id::kTagString:
+ return os << "TagString";
+ case Id::kTagBinary:
+ return os << "TagBinary";
+ default:
+ return PrintUnknownEnumValue(os, id);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, Lacing value) {
+ switch (value) {
+ case Lacing::kNone:
+ return os << "0 (none)";
+ case Lacing::kXiph:
+ return os << "2 (Xiph)";
+ case Lacing::kFixed:
+ return os << "4 (fixed)";
+ case Lacing::kEbml:
+ return os << "6 (EBML)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, MatrixCoefficients value) {
+ switch (value) {
+ case MatrixCoefficients::kRgb:
+ return os << "0 (identity, RGB/XYZ)";
+ case MatrixCoefficients::kBt709:
+ return os << "1 (Rec. ITU-R BT.709-5)";
+ case MatrixCoefficients::kUnspecified:
+ return os << "2 (unspecified)";
+ case MatrixCoefficients::kFcc:
+ return os << "4 (US FCC)";
+ case MatrixCoefficients::kBt470Bg:
+ return os << "5 (Rec. ITU-R BT.470-6 System B, G)";
+ case MatrixCoefficients::kSmpte170M:
+ return os << "6 (SMPTE 170M)";
+ case MatrixCoefficients::kSmpte240M:
+ return os << "7 (SMPTE 240M)";
+ case MatrixCoefficients::kYCgCo:
+ return os << "8 (YCgCo)";
+ case MatrixCoefficients::kBt2020NonconstantLuminance:
+ return os << "9 (Rec. ITU-R BT.2020, non-constant luma)";
+ case MatrixCoefficients::kBt2020ConstantLuminance:
+ return os << "10 (Rec. ITU-R BT.2020 , constant luma)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, Range value) {
+ switch (value) {
+ case Range::kUnspecified:
+ return os << "0 (unspecified)";
+ case Range::kBroadcast:
+ return os << "1 (broadcast)";
+ case Range::kFull:
+ return os << "2 (full)";
+ case Range::kDerived:
+ return os << "3 (defined by MatrixCoefficients/TransferCharacteristics)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, TransferCharacteristics value) {
+ switch (value) {
+ case TransferCharacteristics::kBt709:
+ return os << "1 (Rec. ITU-R BT.709-6)";
+ case TransferCharacteristics::kUnspecified:
+ return os << "2 (unspecified)";
+ case TransferCharacteristics::kGamma22curve:
+ return os << "4 (gamma 2.2, Rec. ITU‑R BT.470‑6 System M)";
+ case TransferCharacteristics::kGamma28curve:
+ return os << "5 (gamma 2.8, Rec. ITU‑R BT.470-6 System B, G)";
+ case TransferCharacteristics::kSmpte170M:
+ return os << "6 (SMPTE 170M)";
+ case TransferCharacteristics::kSmpte240M:
+ return os << "7 (SMPTE 240M)";
+ case TransferCharacteristics::kLinear:
+ return os << "8 (linear)";
+ case TransferCharacteristics::kLog:
+ return os << "9 (log, 100:1 range)";
+ case TransferCharacteristics::kLogSqrt:
+ return os << "10 (log, 316.2:1 range)";
+ case TransferCharacteristics::kIec6196624:
+ return os << "11 (IEC 61966-2-4)";
+ case TransferCharacteristics::kBt1361ExtendedColourGamut:
+ return os << "12 (Rec. ITU-R BT.1361, extended colour gamut)";
+ case TransferCharacteristics::kIec6196621:
+ return os << "13 (IEC 61966-2-1, sRGB or sYCC)";
+ case TransferCharacteristics::k10BitBt2020:
+ return os << "14 (Rec. ITU-R BT.2020-2, 10-bit)";
+ case TransferCharacteristics::k12BitBt2020:
+ return os << "15 (Rec. ITU-R BT.2020-2, 12-bit)";
+ case TransferCharacteristics::kSmpteSt2084:
+ return os << "16 (SMPTE ST 2084)";
+ case TransferCharacteristics::kSmpteSt4281:
+ return os << "17 (SMPTE ST 428-1)";
+ case TransferCharacteristics::kAribStdB67Hlg:
+ return os << "18 (ARIB STD-B67/Rec. ITU-R BT.[HDR-TV] HLG)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, Primaries value) {
+ switch (value) {
+ case Primaries::kBt709:
+ return os << "1 (Rec. ITU‑R BT.709-6)";
+ case Primaries::kUnspecified:
+ return os << "2 (unspecified)";
+ case Primaries::kBt470M:
+ return os << "4 (Rec. ITU‑R BT.470‑6 System M)";
+ case Primaries::kBt470Bg:
+ return os << "5 (Rec. ITU‑R BT.470‑6 System B, G)";
+ case Primaries::kSmpte170M:
+ return os << "6 (SMPTE 170M)";
+ case Primaries::kSmpte240M:
+ return os << "7 (SMPTE 240M)";
+ case Primaries::kFilm:
+ return os << "8 (generic film)";
+ case Primaries::kBt2020:
+ return os << "9 (Rec. ITU-R BT.2020-2)";
+ case Primaries::kSmpteSt4281:
+ return os << "10 (SMPTE ST 428-1)";
+ case Primaries::kJedecP22Phosphors:
+ return os << "22 (EBU Tech. 3213-E/JEDEC P22 phosphors)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, ProjectionType value) {
+ switch (value) {
+ case ProjectionType::kRectangular:
+ return os << "0 (rectangular)";
+ case ProjectionType::kEquirectangular:
+ return os << "1 (equirectangular)";
+ case ProjectionType::kCubeMap:
+ return os << "2 (cube map)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, FlagInterlaced value) {
+ switch (value) {
+ case FlagInterlaced::kUnspecified:
+ return os << "0 (unspecified)";
+ case FlagInterlaced::kInterlaced:
+ return os << "1 (interlaced)";
+ case FlagInterlaced::kProgressive:
+ return os << "2 (progressive)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, StereoMode value) {
+ switch (value) {
+ case StereoMode::kMono:
+ return os << "0 (mono)";
+ case StereoMode::kSideBySideLeftFirst:
+ return os << "1 (side-by-side, left eye first)";
+ case StereoMode::kTopBottomRightFirst:
+ return os << "2 (top-bottom, right eye first)";
+ case StereoMode::kTopBottomLeftFirst:
+ return os << "3 (top-bottom, left eye first)";
+ case StereoMode::kCheckboardRightFirst:
+ return os << "4 (checkboard, right eye first)";
+ case StereoMode::kCheckboardLeftFirst:
+ return os << "5 (checkboard, left eye first)";
+ case StereoMode::kRowInterleavedRightFirst:
+ return os << "6 (row interleaved, right eye first)";
+ case StereoMode::kRowInterleavedLeftFirst:
+ return os << "7 (row interleaved, left eye first)";
+ case StereoMode::kColumnInterleavedRightFirst:
+ return os << "8 (column interleaved, right eye first)";
+ case StereoMode::kColumnInterleavedLeftFirst:
+ return os << "9 (column interleaved, left eye first)";
+ case StereoMode::kAnaglyphCyanRed:
+ return os << "10 (anaglyph, cyan/red)";
+ case StereoMode::kSideBySideRightFirst:
+ return os << "11 (side-by-side, right eye first)";
+ case StereoMode::kAnaglyphGreenMagenta:
+ return os << "12 (anaglyph, green/magenta)";
+ case StereoMode::kBlockLacedLeftFirst:
+ return os << "13 (block laced, left eye first)";
+ case StereoMode::kBlockLacedRightFirst:
+ return os << "14 (block laced, right eye first)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, DisplayUnit value) {
+ switch (value) {
+ case DisplayUnit::kPixels:
+ return os << "0 (pixels)";
+ case DisplayUnit::kCentimeters:
+ return os << "1 (centimeters)";
+ case DisplayUnit::kInches:
+ return os << "2 (inches)";
+ case DisplayUnit::kDisplayAspectRatio:
+ return os << "3 (display aspect ratio)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, AspectRatioType value) {
+ switch (value) {
+ case AspectRatioType::kFreeResizing:
+ return os << "0 (free resizing)";
+ case AspectRatioType::kKeep:
+ return os << "1 (keep aspect ratio)";
+ case AspectRatioType::kFixed:
+ return os << "2 (fixed)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, AesSettingsCipherMode value) {
+ switch (value) {
+ case AesSettingsCipherMode::kCtr:
+ return os << "1 (CTR)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, ContentEncAlgo value) {
+ switch (value) {
+ case ContentEncAlgo::kOnlySigned:
+ return os << "0 (only signed, not encrypted)";
+ case ContentEncAlgo::kDes:
+ return os << "1 (DES)";
+ case ContentEncAlgo::k3Des:
+ return os << "2 (3DES)";
+ case ContentEncAlgo::kTwofish:
+ return os << "3 (Twofish)";
+ case ContentEncAlgo::kBlowfish:
+ return os << "4 (Blowfish)";
+ case ContentEncAlgo::kAes:
+ return os << "5 (AES)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, ContentEncodingType value) {
+ switch (value) {
+ case ContentEncodingType::kCompression:
+ return os << "0 (compression)";
+ case ContentEncodingType::kEncryption:
+ return os << "1 (encryption)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, TrackType value) {
+ switch (value) {
+ case TrackType::kVideo:
+ return os << "1 (video)";
+ case TrackType::kAudio:
+ return os << "2 (audio)";
+ case TrackType::kComplex:
+ return os << "3 (complex)";
+ case TrackType::kLogo:
+ return os << "16 (logo)";
+ case TrackType::kSubtitle:
+ return os << "17 (subtitle)";
+ case TrackType::kButtons:
+ return os << "18 (buttons)";
+ case TrackType::kControl:
+ return os << "32 (control)";
+ default:
+ return PrintUnknownEnumValue(os, value);
+ }
+}
+
+// For binary elements, just print out its size.
+std::ostream& operator<<(std::ostream& os,
+ const std::vector<std::uint8_t>& value) {
+ return os << '<' << value.size() << " bytes>";
+}
+
+class DemoCallback : public Callback {
+ public:
+ int indent = 0;
+ int spaces_per_indent = 2;
+
+ void PrintElementMetadata(const std::string& name,
+ const ElementMetadata& metadata) {
+ // Since we aren't doing any seeking in this demo, we don't have to worry
+ // about kUnknownHeaderSize or kUnknownElementPosition when adding the
+ // position and sizes.
+ const std::uint64_t header_start = metadata.position;
+ const std::uint64_t header_end = header_start + metadata.header_size;
+ const std::uint64_t body_start = header_end;
+ std::cout << std::string(indent * spaces_per_indent, ' ') << name;
+ // The ContentEncAESSettings element has the longest name (out of all other
+ // master elements) at 21 characters. It's also the deepest master element
+ // at a level of 6. Insert enough whitespace so there's room for it.
+ std::cout << std::string(21 + 6 * spaces_per_indent -
+ indent * spaces_per_indent - name.size(),
+ ' ')
+ << " header: [" << header_start << ", " << header_end
+ << ") body: [" << body_start << ", ";
+ if (metadata.size != kUnknownElementSize) {
+ const std::uint64_t body_end = body_start + metadata.size;
+ std::cout << body_end;
+ } else {
+ std::cout << '?';
+ }
+ std::cout << ")\n";
+ }
+
+ template <typename T>
+ void PrintMandatoryElement(const std::string& name,
+ const Element<T>& element) {
+ std::cout << std::string(indent * spaces_per_indent, ' ') << name;
+ if (!element.is_present()) {
+ std::cout << " (implicit)";
+ }
+ std::cout << ": " << element.value() << '\n';
+ }
+
+ template <typename T>
+ void PrintMandatoryElement(const std::string& name,
+ const std::vector<Element<T>>& elements) {
+ for (const Element<T>& element : elements) {
+ PrintMandatoryElement(name, element);
+ }
+ }
+
+ template <typename T>
+ void PrintOptionalElement(const std::string& name,
+ const Element<T>& element) {
+ if (element.is_present()) {
+ std::cout << std::string(indent * spaces_per_indent, ' ') << name << ": "
+ << element.value() << '\n';
+ }
+ }
+
+ template <typename T>
+ void PrintOptionalElement(const std::string& name,
+ const std::vector<Element<T>>& elements) {
+ for (const Element<T>& element : elements) {
+ PrintOptionalElement(name, element);
+ }
+ }
+
+ void PrintMasterElement(const BlockAdditions& block_additions) {
+ PrintMasterElement("BlockMore", block_additions.block_mores);
+ }
+
+ void PrintMasterElement(const BlockMore& block_more) {
+ PrintMandatoryElement("BlockAddID", block_more.id);
+ PrintMandatoryElement("BlockAdditional", block_more.data);
+ }
+
+ void PrintMasterElement(const Slices& slices) {
+ PrintMasterElement("TimeSlice", slices.slices);
+ }
+
+ void PrintMasterElement(const TimeSlice& time_slice) {
+ PrintOptionalElement("LaceNumber", time_slice.lace_number);
+ }
+
+ void PrintMasterElement(const Video& video) {
+ PrintMandatoryElement("FlagInterlaced", video.interlaced);
+ PrintOptionalElement("StereoMode", video.stereo_mode);
+ PrintOptionalElement("AlphaMode", video.alpha_mode);
+ PrintMandatoryElement("PixelWidth", video.pixel_width);
+ PrintMandatoryElement("PixelHeight", video.pixel_height);
+ PrintOptionalElement("PixelCropBottom", video.pixel_crop_bottom);
+ PrintOptionalElement("PixelCropTop", video.pixel_crop_top);
+ PrintOptionalElement("PixelCropLeft", video.pixel_crop_left);
+ PrintOptionalElement("PixelCropRight", video.pixel_crop_right);
+ PrintOptionalElement("DisplayWidth", video.display_width);
+ PrintOptionalElement("DisplayHeight", video.display_height);
+ PrintOptionalElement("DisplayUnit", video.display_unit);
+ PrintOptionalElement("AspectRatioType", video.aspect_ratio_type);
+ PrintOptionalElement("FrameRate", video.frame_rate);
+ PrintMasterElement("Colour", video.colour);
+ PrintMasterElement("Projection", video.projection);
+ }
+
+ void PrintMasterElement(const Colour& colour) {
+ PrintOptionalElement("MatrixCoefficients", colour.matrix_coefficients);
+ PrintOptionalElement("BitsPerChannel", colour.bits_per_channel);
+ PrintOptionalElement("ChromaSubsamplingHorz", colour.chroma_subsampling_x);
+ PrintOptionalElement("ChromaSubsamplingVert", colour.chroma_subsampling_y);
+ PrintOptionalElement("CbSubsamplingHorz", colour.cb_subsampling_x);
+ PrintOptionalElement("CbSubsamplingVert", colour.cb_subsampling_y);
+ PrintOptionalElement("ChromaSitingHorz", colour.chroma_siting_x);
+ PrintOptionalElement("ChromaSitingVert", colour.chroma_siting_y);
+ PrintOptionalElement("Range", colour.range);
+ PrintOptionalElement("TransferCharacteristics",
+ colour.transfer_characteristics);
+ PrintOptionalElement("Primaries", colour.primaries);
+ PrintOptionalElement("MaxCLL", colour.max_cll);
+ PrintOptionalElement("MaxFALL", colour.max_fall);
+ PrintMasterElement("MasteringMetadata", colour.mastering_metadata);
+ }
+
+ void PrintMasterElement(const MasteringMetadata& mastering_metadata) {
+ PrintOptionalElement("PrimaryRChromaticityX",
+ mastering_metadata.primary_r_chromaticity_x);
+ PrintOptionalElement("PrimaryRChromaticityY",
+ mastering_metadata.primary_r_chromaticity_y);
+ PrintOptionalElement("PrimaryGChromaticityX",
+ mastering_metadata.primary_g_chromaticity_x);
+ PrintOptionalElement("PrimaryGChromaticityY",
+ mastering_metadata.primary_g_chromaticity_y);
+ PrintOptionalElement("PrimaryBChromaticityX",
+ mastering_metadata.primary_b_chromaticity_x);
+ PrintOptionalElement("PrimaryBChromaticityY",
+ mastering_metadata.primary_b_chromaticity_y);
+ PrintOptionalElement("WhitePointChromaticityX",
+ mastering_metadata.white_point_chromaticity_x);
+ PrintOptionalElement("WhitePointChromaticityY",
+ mastering_metadata.white_point_chromaticity_y);
+ PrintOptionalElement("LuminanceMax", mastering_metadata.luminance_max);
+ PrintOptionalElement("LuminanceMin", mastering_metadata.luminance_min);
+ }
+
+ void PrintMasterElement(const Projection& projection) {
+ PrintMandatoryElement("ProjectionType", projection.type);
+ PrintOptionalElement("ProjectionPrivate", projection.projection_private);
+ PrintMandatoryElement("ProjectionPoseYaw", projection.pose_yaw);
+ PrintMandatoryElement("ProjectionPosePitch", projection.pose_pitch);
+ PrintMandatoryElement("ProjectionPoseRoll", projection.pose_roll);
+ }
+
+ void PrintMasterElement(const Audio& audio) {
+ PrintMandatoryElement("SamplingFrequency", audio.sampling_frequency);
+ PrintOptionalElement("OutputSamplingFrequency", audio.output_frequency);
+ PrintMandatoryElement("Channels", audio.channels);
+ PrintOptionalElement("BitDepth", audio.bit_depth);
+ }
+
+ void PrintMasterElement(const ContentEncodings& content_encodings) {
+ PrintMasterElement("ContentEncoding", content_encodings.encodings);
+ }
+
+ void PrintMasterElement(const ContentEncoding& content_encoding) {
+ PrintMandatoryElement("ContentEncodingOrder", content_encoding.order);
+ PrintMandatoryElement("ContentEncodingScope", content_encoding.scope);
+ PrintMandatoryElement("ContentEncodingType", content_encoding.type);
+ PrintMasterElement("ContentEncryption", content_encoding.encryption);
+ }
+
+ void PrintMasterElement(const ContentEncryption& content_encryption) {
+ PrintOptionalElement("ContentEncAlgo", content_encryption.algorithm);
+ PrintOptionalElement("ContentEncKeyID", content_encryption.key_id);
+ PrintMasterElement("ContentEncAESSettings",
+ content_encryption.aes_settings);
+ }
+
+ void PrintMasterElement(
+ const ContentEncAesSettings& content_enc_aes_settings) {
+ PrintMandatoryElement("AESSettingsCipherMode",
+ content_enc_aes_settings.aes_settings_cipher_mode);
+ }
+
+ void PrintMasterElement(const CueTrackPositions& cue_track_positions) {
+ PrintMandatoryElement("CueTrack", cue_track_positions.track);
+ PrintMandatoryElement("CueClusterPosition",
+ cue_track_positions.cluster_position);
+ PrintOptionalElement("CueRelativePosition",
+ cue_track_positions.relative_position);
+ PrintOptionalElement("CueDuration", cue_track_positions.duration);
+ PrintOptionalElement("CueBlockNumber", cue_track_positions.block_number);
+ }
+
+ void PrintMasterElement(const ChapterAtom& chapter_atom) {
+ PrintMandatoryElement("ChapterUID", chapter_atom.uid);
+ PrintOptionalElement("ChapterStringUID", chapter_atom.string_uid);
+ PrintMandatoryElement("ChapterTimeStart", chapter_atom.time_start);
+ PrintOptionalElement("ChapterTimeEnd", chapter_atom.time_end);
+ PrintMasterElement("ChapterDisplay", chapter_atom.displays);
+ PrintMasterElement("ChapterAtom", chapter_atom.atoms);
+ }
+
+ void PrintMasterElement(const ChapterDisplay& chapter_display) {
+ PrintMandatoryElement("ChapString", chapter_display.string);
+ PrintMandatoryElement("ChapLanguage", chapter_display.languages);
+ PrintOptionalElement("ChapCountry", chapter_display.countries);
+ }
+
+ void PrintMasterElement(const Targets& targets) {
+ PrintOptionalElement("TargetTypeValue", targets.type_value);
+ PrintOptionalElement("TargetType", targets.type);
+ PrintMandatoryElement("TagTrackUID", targets.track_uids);
+ }
+
+ void PrintMasterElement(const SimpleTag& simple_tag) {
+ PrintMandatoryElement("TagName", simple_tag.name);
+ PrintMandatoryElement("TagLanguage", simple_tag.language);
+ PrintMandatoryElement("TagDefault", simple_tag.is_default);
+ PrintOptionalElement("TagString", simple_tag.string);
+ PrintOptionalElement("TagBinary", simple_tag.binary);
+ PrintMasterElement("SimpleTag", simple_tag.tags);
+ }
+
+ // When printing a master element that's wrapped in Element<>, peel off the
+ // Element<> wrapper and print the underlying master element if it's present.
+ template <typename T>
+ void PrintMasterElement(const std::string& name, const Element<T>& element) {
+ if (element.is_present()) {
+ std::cout << std::string(indent * spaces_per_indent, ' ') << name << "\n";
+ ++indent;
+ PrintMasterElement(element.value());
+ --indent;
+ }
+ }
+
+ template <typename T>
+ void PrintMasterElement(const std::string& name,
+ const std::vector<Element<T>>& elements) {
+ for (const Element<T>& element : elements) {
+ PrintMasterElement(name, element);
+ }
+ }
+
+ template <typename T>
+ void PrintValue(const std::string& name, const T& value) {
+ std::cout << std::string(indent * spaces_per_indent, ' ') << name << ": "
+ << value << '\n';
+ }
+
+ Status OnElementBegin(const ElementMetadata& metadata,
+ Action* action) override {
+ // Print out metadata for some level 1 elements that don't have explicit
+ // callbacks.
+ switch (metadata.id) {
+ case Id::kSeekHead:
+ indent = 1;
+ PrintElementMetadata("SeekHead", metadata);
+ break;
+ case Id::kTracks:
+ indent = 1;
+ PrintElementMetadata("Tracks", metadata);
+ break;
+ case Id::kCues:
+ indent = 1;
+ PrintElementMetadata("Cues", metadata);
+ break;
+ case Id::kChapters:
+ indent = 1;
+ PrintElementMetadata("Chapters", metadata);
+ break;
+ case Id::kTags:
+ indent = 1;
+ PrintElementMetadata("Tags", metadata);
+ break;
+ default:
+ break;
+ }
+
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnUnknownElement(const ElementMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining) override {
+ // Output unknown elements without any indentation because we aren't
+ // tracking which element contains them.
+ int original_indent = indent;
+ indent = 0;
+ PrintElementMetadata("UNKNOWN_ELEMENT!", metadata);
+ indent = original_indent;
+ // The base class's implementation will just skip the element via
+ // Reader::Skip().
+ return Callback::OnUnknownElement(metadata, reader, bytes_remaining);
+ }
+
+ Status OnEbml(const ElementMetadata& metadata, const Ebml& ebml) override {
+ indent = 0;
+ PrintElementMetadata("EBML", metadata);
+ indent = 1;
+ PrintMandatoryElement("EBMLVersion", ebml.ebml_version);
+ PrintMandatoryElement("EBMLReadVersion", ebml.ebml_read_version);
+ PrintMandatoryElement("EBMLMaxIDLength", ebml.ebml_max_id_length);
+ PrintMandatoryElement("EBMLMaxSizeLength", ebml.ebml_max_size_length);
+ PrintMandatoryElement("DocType", ebml.doc_type);
+ PrintMandatoryElement("DocTypeVersion", ebml.doc_type_version);
+ PrintMandatoryElement("DocTypeReadVersion", ebml.doc_type_read_version);
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnVoid(const ElementMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining) override {
+ // Output Void elements without any indentation because we aren't tracking
+ // which element contains them.
+ int original_indent = indent;
+ indent = 0;
+ PrintElementMetadata("Void", metadata);
+ indent = original_indent;
+ // The base class's implementation will just skip the element via
+ // Reader::Skip().
+ return Callback::OnVoid(metadata, reader, bytes_remaining);
+ }
+
+ Status OnSegmentBegin(const ElementMetadata& metadata,
+ Action* action) override {
+ indent = 0;
+ PrintElementMetadata("Segment", metadata);
+ indent = 1;
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnSeek(const ElementMetadata& metadata, const Seek& seek) override {
+ indent = 2;
+ PrintElementMetadata("Seek", metadata);
+ indent = 3;
+ PrintMandatoryElement("SeekID", seek.id);
+ PrintMandatoryElement("SeekPosition", seek.position);
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnInfo(const ElementMetadata& metadata, const Info& info) override {
+ indent = 1;
+ PrintElementMetadata("Info", metadata);
+ indent = 2;
+ PrintMandatoryElement("TimecodeScale", info.timecode_scale);
+ PrintOptionalElement("Duration", info.duration);
+ PrintOptionalElement("DateUTC", info.date_utc);
+ PrintOptionalElement("Title", info.title);
+ PrintOptionalElement("MuxingApp", info.muxing_app);
+ PrintOptionalElement("WritingApp", info.writing_app);
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnClusterBegin(const ElementMetadata& metadata, const Cluster& cluster,
+ Action* action) override {
+ indent = 1;
+ PrintElementMetadata("Cluster", metadata);
+ // A properly muxed file will have Timecode and PrevSize first before any
+ // SimpleBlock or BlockGroups. The parser takes advantage of this and delays
+ // calling OnClusterBegin() until it hits the first SimpleBlock or
+ // BlockGroup child (or the Cluster ends if it's empty). It's possible for
+ // an improperly muxed file to have Timecode or PrevSize after the first
+ // block, in which case they'll be absent here and may be accessed in
+ // OnClusterEnd() when the Cluster and all its children have been fully
+ // parsed. In this demo we assume the file has been properly muxed.
+ indent = 2;
+ PrintMandatoryElement("Timecode", cluster.timecode);
+ PrintOptionalElement("PrevSize", cluster.previous_size);
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnSimpleBlockBegin(const ElementMetadata& metadata,
+ const SimpleBlock& simple_block,
+ Action* action) override {
+ indent = 2;
+ PrintElementMetadata("SimpleBlock", metadata);
+ indent = 3;
+ PrintValue("track number", simple_block.track_number);
+ PrintValue("frames", simple_block.num_frames);
+ PrintValue("timecode", simple_block.timecode);
+ PrintValue("lacing", simple_block.lacing);
+ std::string flags = (simple_block.is_visible) ? "visible" : "invisible";
+ if (simple_block.is_key_frame)
+ flags += ", key frame";
+ if (simple_block.is_discardable)
+ flags += ", discardable";
+ PrintValue("flags", flags);
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnSimpleBlockEnd(const ElementMetadata& /* metadata */,
+ const SimpleBlock& /* simple_block */) override {
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnBlockGroupBegin(const ElementMetadata& metadata,
+ Action* action) override {
+ indent = 2;
+ PrintElementMetadata("BlockGroup", metadata);
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnBlockBegin(const ElementMetadata& metadata, const Block& block,
+ Action* action) override {
+ indent = 3;
+ PrintElementMetadata("Block", metadata);
+ indent = 4;
+ PrintValue("track number", block.track_number);
+ PrintValue("frames", block.num_frames);
+ PrintValue("timecode", block.timecode);
+ PrintValue("lacing", block.lacing);
+ PrintValue("flags", (block.is_visible) ? "visible" : "invisible");
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnBlockEnd(const ElementMetadata& /* metadata */,
+ const Block& /* block */) override {
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnBlockGroupEnd(const ElementMetadata& /* metadata */,
+ const BlockGroup& block_group) override {
+ if (block_group.virtual_block.is_present()) {
+ std::cout << std::string(indent * spaces_per_indent, ' ')
+ << "BlockVirtual\n";
+ indent = 4;
+ PrintValue("track number",
+ block_group.virtual_block.value().track_number);
+ PrintValue("timecode", block_group.virtual_block.value().timecode);
+ }
+ indent = 3;
+ PrintMasterElement("BlockAdditions", block_group.additions);
+ PrintOptionalElement("BlockDuration", block_group.duration);
+ PrintOptionalElement("ReferenceBlock", block_group.references);
+ PrintOptionalElement("DiscardPadding", block_group.discard_padding);
+ PrintMasterElement("Slices", block_group.slices);
+ // BlockGroup::block has been set, but we've already printed it in
+ // OnBlockBegin().
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnFrame(const FrameMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining) override {
+ PrintValue("frame byte range",
+ '[' + std::to_string(metadata.position) + ", " +
+ std::to_string(metadata.position + metadata.size) + ')');
+ // The base class's implementation will just skip the frame via
+ // Reader::Skip().
+ return Callback::OnFrame(metadata, reader, bytes_remaining);
+ }
+
+ Status OnClusterEnd(const ElementMetadata& /* metadata */,
+ const Cluster& /* cluster */) override {
+ // The Cluster and all its children have been fully parsed at this point. If
+ // the file wasn't properly muxed and Timecode or PrevSize were missing in
+ // OnClusterBegin(), they'll be set here (if the Cluster contained them). In
+ // this demo we already handled them, though.
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnTrackEntry(const ElementMetadata& metadata,
+ const TrackEntry& track_entry) override {
+ indent = 2;
+ PrintElementMetadata("TrackEntry", metadata);
+ indent = 3;
+ PrintMandatoryElement("TrackNumber", track_entry.track_number);
+ PrintMandatoryElement("TrackUID", track_entry.track_uid);
+ PrintMandatoryElement("TrackType", track_entry.track_type);
+ PrintMandatoryElement("FlagEnabled", track_entry.is_enabled);
+ PrintMandatoryElement("FlagDefault", track_entry.is_default);
+ PrintMandatoryElement("FlagForced", track_entry.is_forced);
+ PrintMandatoryElement("FlagLacing", track_entry.uses_lacing);
+ PrintOptionalElement("DefaultDuration", track_entry.default_duration);
+ PrintOptionalElement("Name", track_entry.name);
+ PrintOptionalElement("Language", track_entry.language);
+ PrintMandatoryElement("CodecID", track_entry.codec_id);
+ PrintOptionalElement("CodecPrivate", track_entry.codec_private);
+ PrintOptionalElement("CodecName", track_entry.codec_name);
+ PrintOptionalElement("CodecDelay", track_entry.codec_delay);
+ PrintMandatoryElement("SeekPreRoll", track_entry.seek_pre_roll);
+ PrintMasterElement("Video", track_entry.video);
+ PrintMasterElement("Audio", track_entry.audio);
+ PrintMasterElement("ContentEncodings", track_entry.content_encodings);
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnCuePoint(const ElementMetadata& metadata,
+ const CuePoint& cue_point) override {
+ indent = 2;
+ PrintElementMetadata("CuePoint", metadata);
+ indent = 3;
+ PrintMandatoryElement("CueTime", cue_point.time);
+ PrintMasterElement("CueTrackPositions", cue_point.cue_track_positions);
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnEditionEntry(const ElementMetadata& metadata,
+ const EditionEntry& edition_entry) override {
+ indent = 2;
+ PrintElementMetadata("EditionEntry", metadata);
+ indent = 3;
+ PrintMasterElement("ChapterAtom", edition_entry.atoms);
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnTag(const ElementMetadata& metadata, const Tag& tag) override {
+ indent = 2;
+ PrintElementMetadata("Tag", metadata);
+ indent = 3;
+ PrintMasterElement("Targets", tag.targets);
+ PrintMasterElement("SimpleTag", tag.tags);
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnSegmentEnd(const ElementMetadata& /* metadata */) override {
+ return Status(Status::kOkCompleted);
+ }
+};
+
+int main(int argc, char* argv[]) {
+ if ((argc != 1 && argc != 2) ||
+ (argc == 2 && argv[1] == std::string("--help"))) {
+ std::cerr << "Usage:\n"
+ << argv[0] << " [path-to-webm-file]\n\n"
+ << "Prints info for the WebM file specified in the command line. "
+ "If no file is\n"
+ << "specified, stdin is used as input.\n";
+ return EXIT_FAILURE;
+ }
+
+ FILE* file = (argc == 2) ? std::fopen(argv[1], "rb")
+ : std::freopen(nullptr, "rb", stdin);
+ if (!file) {
+ std::cerr << "File cannot be opened\n";
+ return EXIT_FAILURE;
+ }
+
+ FileReader reader(file);
+ DemoCallback callback;
+ WebmParser parser;
+ Status status = parser.Feed(&callback, &reader);
+ if (!status.completed_ok()) {
+ std::cerr << "Parsing error; status code: " << status.code << '\n';
+ return EXIT_FAILURE;
+ }
+
+ return 0;
+}
diff --git a/webm_parser/doxygen.config b/webm_parser/doxygen.config
new file mode 100644
index 0000000..f2aae8a
--- /dev/null
+++ b/webm_parser/doxygen.config
@@ -0,0 +1,319 @@
+# Doxyfile 1.8.11
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+DOXYFILE_ENCODING = UTF-8
+PROJECT_NAME = "WebM Parser"
+PROJECT_NUMBER =
+PROJECT_BRIEF =
+PROJECT_LOGO =
+OUTPUT_DIRECTORY =
+CREATE_SUBDIRS = NO
+ALLOW_UNICODE_NAMES = NO
+OUTPUT_LANGUAGE = English
+BRIEF_MEMBER_DESC = YES
+REPEAT_BRIEF = YES
+ABBREVIATE_BRIEF =
+ALWAYS_DETAILED_SEC = NO
+INLINE_INHERITED_MEMB = NO
+FULL_PATH_NAMES = YES
+STRIP_FROM_PATH =
+STRIP_FROM_INC_PATH =
+SHORT_NAMES = NO
+JAVADOC_AUTOBRIEF = YES
+QT_AUTOBRIEF = NO
+MULTILINE_CPP_IS_BRIEF = NO
+INHERIT_DOCS = YES
+SEPARATE_MEMBER_PAGES = NO
+TAB_SIZE = 2
+ALIASES = MatroskaID{1}="[\1](https://www.webmproject.org/docs/container/#\1) ([Matroska definition](https://matroska.org/technical/specs/index.html#\1))" \
+ WebMID{1}="[\1](https://www.webmproject.org/docs/container/#\1)" \
+ WebMTable{7}="| Type | Level | Mandatory | Multiple | Recursive | Value range | Default value |\n| ---- | ----- | --------- | -------- | --------- | ----------- | ------------- |\n| \1 | \2 | \3 | \4 | \5 | \6 | \7 |\n"
+TCL_SUBST =
+OPTIMIZE_OUTPUT_FOR_C = NO
+OPTIMIZE_OUTPUT_JAVA = NO
+OPTIMIZE_FOR_FORTRAN = NO
+OPTIMIZE_OUTPUT_VHDL = NO
+EXTENSION_MAPPING =
+MARKDOWN_SUPPORT = YES
+AUTOLINK_SUPPORT = YES
+BUILTIN_STL_SUPPORT = NO
+CPP_CLI_SUPPORT = NO
+SIP_SUPPORT = NO
+IDL_PROPERTY_SUPPORT = YES
+DISTRIBUTE_GROUP_DOC = NO
+GROUP_NESTED_COMPOUNDS = NO
+SUBGROUPING = YES
+INLINE_GROUPED_CLASSES = NO
+INLINE_SIMPLE_STRUCTS = NO
+TYPEDEF_HIDES_STRUCT = NO
+LOOKUP_CACHE_SIZE = 0
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+EXTRACT_ALL = NO
+EXTRACT_PRIVATE = NO
+EXTRACT_PACKAGE = NO
+EXTRACT_STATIC = NO
+EXTRACT_LOCAL_CLASSES = YES
+EXTRACT_LOCAL_METHODS = NO
+EXTRACT_ANON_NSPACES = NO
+HIDE_UNDOC_MEMBERS = NO
+HIDE_UNDOC_CLASSES = NO
+HIDE_FRIEND_COMPOUNDS = NO
+HIDE_IN_BODY_DOCS = NO
+INTERNAL_DOCS = NO
+CASE_SENSE_NAMES = NO
+HIDE_SCOPE_NAMES = NO
+HIDE_COMPOUND_REFERENCE= NO
+SHOW_INCLUDE_FILES = YES
+SHOW_GROUPED_MEMB_INC = NO
+FORCE_LOCAL_INCLUDES = NO
+INLINE_INFO = YES
+SORT_MEMBER_DOCS = YES
+SORT_BRIEF_DOCS = NO
+SORT_MEMBERS_CTORS_1ST = NO
+SORT_GROUP_NAMES = NO
+SORT_BY_SCOPE_NAME = NO
+STRICT_PROTO_MATCHING = NO
+GENERATE_TODOLIST = YES
+GENERATE_TESTLIST = YES
+GENERATE_BUGLIST = YES
+GENERATE_DEPRECATEDLIST= YES
+ENABLED_SECTIONS =
+MAX_INITIALIZER_LINES = 30
+SHOW_USED_FILES = YES
+SHOW_FILES = YES
+SHOW_NAMESPACES = YES
+FILE_VERSION_FILTER =
+LAYOUT_FILE =
+CITE_BIB_FILES =
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+QUIET = NO
+WARNINGS = YES
+WARN_IF_UNDOCUMENTED = YES
+WARN_IF_DOC_ERROR = YES
+WARN_NO_PARAMDOC = NO
+WARN_AS_ERROR = NO
+WARN_FORMAT = "$file:$line: $text"
+WARN_LOGFILE =
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+INPUT = README.md include/webm
+INPUT_ENCODING = UTF-8
+FILE_PATTERNS =
+RECURSIVE = NO
+EXCLUDE =
+EXCLUDE_SYMLINKS = NO
+EXCLUDE_PATTERNS =
+EXCLUDE_SYMBOLS =
+EXAMPLE_PATH =
+EXAMPLE_PATTERNS =
+EXAMPLE_RECURSIVE = NO
+IMAGE_PATH =
+INPUT_FILTER =
+FILTER_PATTERNS =
+FILTER_SOURCE_FILES = NO
+FILTER_SOURCE_PATTERNS =
+USE_MDFILE_AS_MAINPAGE = README.md
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+SOURCE_BROWSER = NO
+INLINE_SOURCES = NO
+STRIP_CODE_COMMENTS = YES
+REFERENCED_BY_RELATION = NO
+REFERENCES_RELATION = NO
+REFERENCES_LINK_SOURCE = YES
+SOURCE_TOOLTIPS = YES
+USE_HTAGS = NO
+VERBATIM_HEADERS = YES
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+ALPHABETICAL_INDEX = YES
+COLS_IN_ALPHA_INDEX = 5
+IGNORE_PREFIX =
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+GENERATE_HTML = YES
+HTML_OUTPUT = html
+HTML_FILE_EXTENSION = .html
+HTML_HEADER =
+HTML_FOOTER =
+HTML_STYLESHEET =
+HTML_EXTRA_STYLESHEET =
+HTML_EXTRA_FILES =
+HTML_COLORSTYLE_HUE = 220
+HTML_COLORSTYLE_SAT = 100
+HTML_COLORSTYLE_GAMMA = 80
+HTML_TIMESTAMP = NO
+HTML_DYNAMIC_SECTIONS = NO
+HTML_INDEX_NUM_ENTRIES = 100
+GENERATE_DOCSET = NO
+DOCSET_FEEDNAME = "Doxygen generated docs"
+DOCSET_BUNDLE_ID = org.doxygen.Project
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+DOCSET_PUBLISHER_NAME = Publisher
+GENERATE_HTMLHELP = NO
+CHM_FILE =
+HHC_LOCATION =
+GENERATE_CHI = NO
+CHM_INDEX_ENCODING =
+BINARY_TOC = NO
+TOC_EXPAND = NO
+GENERATE_QHP = NO
+QCH_FILE =
+QHP_NAMESPACE = org.doxygen.Project
+QHP_VIRTUAL_FOLDER = doc
+QHP_CUST_FILTER_NAME =
+QHP_CUST_FILTER_ATTRS =
+QHP_SECT_FILTER_ATTRS =
+QHG_LOCATION =
+GENERATE_ECLIPSEHELP = NO
+ECLIPSE_DOC_ID = org.doxygen.Project
+DISABLE_INDEX = NO
+GENERATE_TREEVIEW = NO
+ENUM_VALUES_PER_LINE = 4
+TREEVIEW_WIDTH = 250
+EXT_LINKS_IN_WINDOW = NO
+FORMULA_FONTSIZE = 10
+FORMULA_TRANSPARENT = YES
+USE_MATHJAX = NO
+MATHJAX_FORMAT = HTML-CSS
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+MATHJAX_EXTENSIONS =
+MATHJAX_CODEFILE =
+SEARCHENGINE = YES
+SERVER_BASED_SEARCH = NO
+EXTERNAL_SEARCH = NO
+SEARCHENGINE_URL =
+SEARCHDATA_FILE = searchdata.xml
+EXTERNAL_SEARCH_ID =
+EXTRA_SEARCH_MAPPINGS =
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+GENERATE_LATEX = YES
+LATEX_OUTPUT = latex
+LATEX_CMD_NAME = latex
+MAKEINDEX_CMD_NAME = makeindex
+COMPACT_LATEX = NO
+PAPER_TYPE = a4
+EXTRA_PACKAGES =
+LATEX_HEADER =
+LATEX_FOOTER =
+LATEX_EXTRA_STYLESHEET =
+LATEX_EXTRA_FILES =
+PDF_HYPERLINKS = YES
+USE_PDFLATEX = YES
+LATEX_BATCHMODE = NO
+LATEX_HIDE_INDICES = NO
+LATEX_SOURCE_CODE = NO
+LATEX_BIB_STYLE = plain
+LATEX_TIMESTAMP = NO
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+GENERATE_RTF = NO
+RTF_OUTPUT = rtf
+COMPACT_RTF = NO
+RTF_HYPERLINKS = NO
+RTF_STYLESHEET_FILE =
+RTF_EXTENSIONS_FILE =
+RTF_SOURCE_CODE = NO
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+GENERATE_MAN = NO
+MAN_OUTPUT = man
+MAN_EXTENSION = .3
+MAN_SUBDIR =
+MAN_LINKS = NO
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+GENERATE_XML = NO
+XML_OUTPUT = xml
+XML_PROGRAMLISTING = YES
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+GENERATE_DOCBOOK = NO
+DOCBOOK_OUTPUT = docbook
+DOCBOOK_PROGRAMLISTING = NO
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+GENERATE_AUTOGEN_DEF = NO
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+GENERATE_PERLMOD = NO
+PERLMOD_LATEX = NO
+PERLMOD_PRETTY = YES
+PERLMOD_MAKEVAR_PREFIX =
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+ENABLE_PREPROCESSING = YES
+MACRO_EXPANSION = NO
+EXPAND_ONLY_PREDEF = NO
+SEARCH_INCLUDES = YES
+INCLUDE_PATH =
+INCLUDE_FILE_PATTERNS =
+PREDEFINED =
+EXPAND_AS_DEFINED =
+SKIP_FUNCTION_MACROS = YES
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+TAGFILES =
+GENERATE_TAGFILE =
+ALLEXTERNALS = NO
+EXTERNAL_GROUPS = YES
+EXTERNAL_PAGES = YES
+PERL_PATH = /usr/bin/perl
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+CLASS_DIAGRAMS = YES
+MSCGEN_PATH =
+DIA_PATH =
+HIDE_UNDOC_RELATIONS = YES
+HAVE_DOT = NO
+DOT_NUM_THREADS = 0
+DOT_FONTNAME = Helvetica
+DOT_FONTSIZE = 10
+DOT_FONTPATH =
+CLASS_GRAPH = YES
+COLLABORATION_GRAPH = YES
+GROUP_GRAPHS = YES
+UML_LOOK = NO
+UML_LIMIT_NUM_FIELDS = 10
+TEMPLATE_RELATIONS = NO
+INCLUDE_GRAPH = YES
+INCLUDED_BY_GRAPH = YES
+CALL_GRAPH = NO
+CALLER_GRAPH = NO
+GRAPHICAL_HIERARCHY = YES
+DIRECTORY_GRAPH = YES
+DOT_IMAGE_FORMAT = png
+INTERACTIVE_SVG = NO
+DOT_PATH =
+DOTFILE_DIRS =
+MSCFILE_DIRS =
+DIAFILE_DIRS =
+PLANTUML_JAR_PATH =
+PLANTUML_INCLUDE_PATH =
+DOT_GRAPH_MAX_NODES = 50
+MAX_DOT_GRAPH_DEPTH = 0
+DOT_TRANSPARENT = NO
+DOT_MULTI_TARGETS = NO
+GENERATE_LEGEND = YES
+DOT_CLEANUP = YES
diff --git a/webm_parser/fuzzing/corpus/00805c2543756a5fd85652d03bfbbd2eb6192ca5 b/webm_parser/fuzzing/corpus/00805c2543756a5fd85652d03bfbbd2eb6192ca5
new file mode 100644
index 0000000..d78507e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/00805c2543756a5fd85652d03bfbbd2eb6192ca5
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šm€‡b@„P1S€g”T®k®m€Šb@‡P5„Gáÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/00d120eb143bb02c48d7c863e5826d2ad1a6da4b b/webm_parser/fuzzing/corpus/00d120eb143bb02c48d7c863e5826d2ad1a6da4b
new file mode 100644
index 0000000..2d4403c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/00d120eb143bb02c48d7c863e5826d2ad1a6da4b
@@ -0,0 +1 @@
+S€g‹T®k†®„c¢! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/018dee8285e9e20ca3996bb2dc0284b5c57ba75a b/webm_parser/fuzzing/corpus/018dee8285e9e20ca3996bb2dc0284b5c57ba75a
new file mode 100644
index 0000000..2920522
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/018dee8285e9e20ca3996bb2dc0284b5c57ba75a
@@ -0,0 +1 @@
+S€g…C¶u€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/020edb59637c1e6439f19aa3a5a9d50c3377dbe9 b/webm_parser/fuzzing/corpus/020edb59637c1e6439f19aa3a5a9d50c3377dbe9
new file mode 100644
index 0000000..c194606
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/020edb59637c1e6439f19aa3a5a9d50c3377dbe9
@@ -0,0 +1 @@
+S€g“T®kŽ®Œm€‰b@†P3ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/0247ce2b1a71752a3af11e1065ca90afa0df9d30 b/webm_parser/fuzzing/corpus/0247ce2b1a71752a3af11e1065ca90afa0df9d30
new file mode 100644
index 0000000..42b8525
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0247ce2b1a71752a3af11e1065ca90afa0df9d30
@@ -0,0 +1 @@
+S€gC¶u‹ ‰Ž‡è…̃ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/028b19f7d79f5da7a2af13a0c1e2d13f7eaf24cd b/webm_parser/fuzzing/corpus/028b19f7d79f5da7a2af13a0c1e2d13f7eaf24cd
new file mode 100644
index 0000000..863b42b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/028b19f7d79f5da7a2af13a0c1e2d13f7eaf24cd
@@ -0,0 +1 @@
+S€gTÿk†à®ˆTº„ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/029ab55b16df41881f8de2351205201da334550a b/webm_parser/fuzzing/corpus/029ab55b16df41881f8de2351205201da334550a
new file mode 100644
index 0000000..27523d4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/029ab55b16df41881f8de2351205201da334550a
@@ -0,0 +1 @@
+S€gŽC§p‰E¹†¶„VT! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/029be5e90913b19cf5890559cf3f98aa909f0a84 b/webm_parser/fuzzing/corpus/029be5e90913b19cf5890559cf3f98aa909f0a84
new file mode 100644
index 0000000..75abefb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/029be5e90913b19cf5890559cf3f98aa909f0a84
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/02fb96539b84bbd12de84ff05cbc9dc3faa96b7e b/webm_parser/fuzzing/corpus/02fb96539b84bbd12de84ff05cbc9dc3faa96b7e
new file mode 100644
index 0000000..f6d3738
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/02fb96539b84bbd12de84ff05cbc9dc3faa96b7e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/0349f5632d21faa36b85520ad0b524d561f5e13f b/webm_parser/fuzzing/corpus/0349f5632d21faa36b85520ad0b524d561f5e13f
new file mode 100644
index 0000000..f3df0a4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0349f5632d21faa36b85520ad0b524d561f5e13f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/036fc9daf7fb1b4274dd668cfd883248ebbad967 b/webm_parser/fuzzing/corpus/036fc9daf7fb1b4274dd668cfd883248ebbad967
new file mode 100644
index 0000000..5d97ec8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/036fc9daf7fb1b4274dd668cfd883248ebbad967
@@ -0,0 +1 @@
+S€gS»kŠ»ƒ³»ƒ³ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/037a4edc18e475ec81081e47277cbf51f1316680 b/webm_parser/fuzzing/corpus/037a4edc18e475ec81081e47277cbf51f1316680
new file mode 100644
index 0000000..3109c31
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/037a4edc18e475ec81081e47277cbf51f1316680
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/0481dad9a7d0e6fab0c703bba9b3268db96c6793 b/webm_parser/fuzzing/corpus/0481dad9a7d0e6fab0c703bba9b3268db96c6793
new file mode 100644
index 0000000..45d388a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0481dad9a7d0e6fab0c703bba9b3268db96c6793
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/04dc2407e7142f5618aa5105377925b0b0ed544b b/webm_parser/fuzzing/corpus/04dc2407e7142f5618aa5105377925b0b0ed544b
new file mode 100644
index 0000000..fa4ff2f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/04dc2407e7142f5618aa5105377925b0b0ed544b
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/04fa2f34ff4a4406d136e5aaba5debe7d8129a06 b/webm_parser/fuzzing/corpus/04fa2f34ff4a4406d136e5aaba5debe7d8129a06
new file mode 100644
index 0000000..81b5bdd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/04fa2f34ff4a4406d136e5aaba5debe7d8129a06
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/054fd0041ad81cfad0a85e3c59195485492a4493 b/webm_parser/fuzzing/corpus/054fd0041ad81cfad0a85e3c59195485492a4493
new file mode 100644
index 0000000..e5ddccf
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/054fd0041ad81cfad0a85e3c59195485492a4493
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/056b83ab2457979ea021e7118ab847eba265df15 b/webm_parser/fuzzing/corpus/056b83ab2457979ea021e7118ab847eba265df15
new file mode 100644
index 0000000..ef730c6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/056b83ab2457979ea021e7118ab847eba265df15
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/058326151c1d4a490964d495d35adcf15178030f b/webm_parser/fuzzing/corpus/058326151c1d4a490964d495d35adcf15178030f
new file mode 100644
index 0000000..e6d60cc
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/058326151c1d4a490964d495d35adcf15178030f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/05847b5be0eb200d6a6b340c939c7a654b55003e b/webm_parser/fuzzing/corpus/05847b5be0eb200d6a6b340c939c7a654b55003e
new file mode 100644
index 0000000..c928212
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/05847b5be0eb200d6a6b340c939c7a654b55003e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/05b600ae9a9072ac2865247e69ed0736a0c58316 b/webm_parser/fuzzing/corpus/05b600ae9a9072ac2865247e69ed0736a0c58316
new file mode 100644
index 0000000..a39b94c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/05b600ae9a9072ac2865247e69ed0736a0c58316
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/05cf976698b55df1fcd03bc07446bb9283dad9d7 b/webm_parser/fuzzing/corpus/05cf976698b55df1fcd03bc07446bb9283dad9d7
new file mode 100644
index 0000000..effefb8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/05cf976698b55df1fcd03bc07446bb9283dad9d7
@@ -0,0 +1 @@
+S€gTÃg˜ss•gÈ’E£€Dz€D„€D‡€D…€gÈ€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/05d4dfda5e264fffda243f2991a86e96141f579a b/webm_parser/fuzzing/corpus/05d4dfda5e264fffda243f2991a86e96141f579a
new file mode 100644
index 0000000..3a30835
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/05d4dfda5e264fffda243f2991a86e96141f579a
@@ -0,0 +1 @@
+S€g”T®k®m€Šb@‡P5„Gâ!S€g›T®k–®”m€‘ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/061217fc0b6af0ec3851d9728e03ed9b30c702d5 b/webm_parser/fuzzing/corpus/061217fc0b6af0ec3851d9728e03ed9b30c702d5
new file mode 100644
index 0000000..d2dbb4d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/061217fc0b6af0ec3851d9728e03ed9b30c702d5
@@ -0,0 +1 @@
+u¢€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/077b53a7163e51c48e8cdaf4209bb837823ffba9 b/webm_parser/fuzzing/corpus/077b53a7163e51c48e8cdaf4209bb837823ffba9
new file mode 100644
index 0000000..f0a787c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/077b53a7163e51c48e8cdaf4209bb837823ffba9
@@ -0,0 +1 @@
+Tð€ð€€€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/07c3ade9713892bb75db2d93b48ef40b262a54e6 b/webm_parser/fuzzing/corpus/07c3ade9713892bb75db2d93b48ef40b262a54e6
new file mode 100644
index 0000000..12d7edf
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/07c3ade9713892bb75db2d93b48ef40b262a54e6
@@ -0,0 +1 @@
+S€gŒS»k‡»…·ƒñÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/07eaf3c7437032f60c905f6f8e3dfc193491b602 b/webm_parser/fuzzing/corpus/07eaf3c7437032f60c905f6f8e3dfc193491b602
new file mode 100644
index 0000000..5686078
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/07eaf3c7437032f60c905f6f8e3dfc193491b602
@@ -0,0 +1 @@
+S€g“C¶uŽ ŒŽŠìƒÌ•ƒèÌ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/07f67b922b503354b2aebcdcc4ab7b6d3150b048 b/webm_parser/fuzzing/corpus/07f67b922b503354b2aebcdcc4ab7b6d3150b048
new file mode 100644
index 0000000..532f5d9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/07f67b922b503354b2aebcdcc4ab7b6d3150b048
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/08af8d91bb21835c50330e997d973cac8ff67766 b/webm_parser/fuzzing/corpus/08af8d91bb21835c50330e997d973cac8ff67766
new file mode 100644
index 0000000..7fab9be
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/08af8d91bb21835c50330e997d973cac8ff67766
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/0a18f05bb16402756202160225aec9c5a654f1ae b/webm_parser/fuzzing/corpus/0a18f05bb16402756202160225aec9c5a654f1ae
new file mode 100644
index 0000000..11f4d8c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0a18f05bb16402756202160225aec9c5a654f1ae
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/0b60823983971ee17a2590678f0fc0762c21bb73 b/webm_parser/fuzzing/corpus/0b60823983971ee17a2590678f0fc0762c21bb73
new file mode 100644
index 0000000..14fc6de
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0b60823983971ee17a2590678f0fc0762c21bb73
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/0bae7f0976af0f75974047b5f2cf4c9645412066 b/webm_parser/fuzzing/corpus/0bae7f0976af0f75974047b5f2cf4c9645412066
new file mode 100644
index 0000000..beeec12
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0bae7f0976af0f75974047b5f2cf4c9645412066
@@ -0,0 +1 @@
+S€gœC§p—E¹”¶’Ä€aT€€€€€€€€€€€€€ƒèÌ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/0c1862b4065eefab2535ecc6951295e38069a82e b/webm_parser/fuzzing/corpus/0c1862b4065eefab2535ecc6951295e38069a82e
new file mode 100644
index 0000000..95342c8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0c1862b4065eefab2535ecc6951295e38069a82e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/0c2f51d5ffc69e69680bf3d6edb91d76c353ad14 b/webm_parser/fuzzing/corpus/0c2f51d5ffc69e69680bf3d6edb91d76c353ad14
new file mode 100644
index 0000000..f0ad094
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0c2f51d5ffc69e69680bf3d6edb91d76c353ad14
@@ -0,0 +1 @@
+S€g€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/0c3af72d69f18103383c9cd41a7f2676a9d28a40 b/webm_parser/fuzzing/corpus/0c3af72d69f18103383c9cd41a7f2676a9d28a40
new file mode 100644
index 0000000..74f0550
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0c3af72d69f18103383c9cd41a7f2676a9d28a40
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/0c43df7fc9d06187249187583c3c082520701289 b/webm_parser/fuzzing/corpus/0c43df7fc9d06187249187583c3c082520701289
new file mode 100644
index 0000000..c0033c4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0c43df7fc9d06187249187583c3c082520701289
@@ -0,0 +1 @@
+S€gŒC¶u‡ …ûƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/0cadf5ecf58a394560a1f6db72a2e21264445d13 b/webm_parser/fuzzing/corpus/0cadf5ecf58a394560a1f6db72a2e21264445d13
new file mode 100644
index 0000000..e3e6491
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0cadf5ecf58a394560a1f6db72a2e21264445d13
@@ -0,0 +1 @@
+S€g™T®k”®’m€b@ŒP5‰á€Gç€Gç€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/0cd91a4e7bec912beb3b47a685b89d5be24d0046 b/webm_parser/fuzzing/corpus/0cd91a4e7bec912beb3b47a685b89d5be24d0046
new file mode 100644
index 0000000..33d3668
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0cd91a4e7bec912beb3b47a685b89d5be24d0046
@@ -0,0 +1 @@
+S€gŽS»k‰»‡·…²ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/0d0da60f91f9489af113d8484d9b6871622523d5 b/webm_parser/fuzzing/corpus/0d0da60f91f9489af113d8484d9b6871622523d5
new file mode 100644
index 0000000..14e966c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0d0da60f91f9489af113d8484d9b6871622523d5
@@ -0,0 +1 @@
+S€g”T®i®m€‚b@‡P5„Gâ!S€gŠT®k…®ƒ¹ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/0d30a4f88e53de1ce4bf1ec724016f2f4c11bc9b b/webm_parser/fuzzing/corpus/0d30a4f88e53de1ce4bf1ec724016f2f4c11bc9b
new file mode 100644
index 0000000..2de1dab
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0d30a4f88e53de1ce4bf1ec724016f2f4c11bc9b
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/0d6b3b1d024e7aa73fbc58b157ab53df048a2f2c b/webm_parser/fuzzing/corpus/0d6b3b1d024e7aa73fbc58b157ab53df048a2f2c
new file mode 100644
index 0000000..1a5dc0e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0d6b3b1d024e7aa73fbc58b157ab53df048a2f2c
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/0d94310cfd8a9acdbc7fb82ad9c73cdf3f64f926 b/webm_parser/fuzzing/corpus/0d94310cfd8a9acdbc7fb82ad9c73cdf3f64f926
new file mode 100644
index 0000000..676e417
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0d94310cfd8a9acdbc7fb82ad9c73cdf3f64f926
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/0f5741483be8f4f6eddcb70ea418fb0c08da9443 b/webm_parser/fuzzing/corpus/0f5741483be8f4f6eddcb70ea418fb0c08da9443
new file mode 100644
index 0000000..34838b0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/0f5741483be8f4f6eddcb70ea418fb0c08da9443
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/109e125f729f84d69c3e3a614123547a094534ad b/webm_parser/fuzzing/corpus/109e125f729f84d69c3e3a614123547a094534ad
new file mode 100644
index 0000000..841b067
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/109e125f729f84d69c3e3a614123547a094534ad
@@ -0,0 +1 @@
+S€g”C¶u u¡Š¦ƒî¦ƒî \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/10d257a4a314e20644fbc469cdebafe16c7f46a8 b/webm_parser/fuzzing/corpus/10d257a4a314e20644fbc469cdebafe16c7f46a8
new file mode 100644
index 0000000..edb0d4f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/10d257a4a314e20644fbc469cdebafe16c7f46a8
@@ -0,0 +1 @@
+S€gTÃgŠss‡gÈ„D„ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/10d951f88995a2176878501a2633b9bb4822ff96 b/webm_parser/fuzzing/corpus/10d951f88995a2176878501a2633b9bb4822ff96
new file mode 100644
index 0000000..ca0a094
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/10d951f88995a2176878501a2633b9bb4822ff96
@@ -0,0 +1 @@
+!EߣˆB‚@webmS€g£M›t€I©f€C¶u€TS€gŒT®k‡®…®k€Sჟ»k€C§p€TÃg€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/111b8f8318a269bbe0f7d9f1cebfdc1cb43c982b b/webm_parser/fuzzing/corpus/111b8f8318a269bbe0f7d9f1cebfdc1cb43c982b
new file mode 100644
index 0000000..a612d8b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/111b8f8318a269bbe0f7d9f1cebfdc1cb43c982b
@@ -0,0 +1 @@
+(Á \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/11c122ce1f7d993f809a4eb5db17c738f279a707 b/webm_parser/fuzzing/corpus/11c122ce1f7d993f809a4eb5db17c738f279a707
new file mode 100644
index 0000000..064086e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/11c122ce1f7d993f809a4eb5db17c738f279a707
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/11e288617056809208561f2a9112fd0664d378d2 b/webm_parser/fuzzing/corpus/11e288617056809208561f2a9112fd0664d378d2
new file mode 100644
index 0000000..9e2b9d3
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/11e288617056809208561f2a9112fd0664d378d2
@@ -0,0 +1 @@
+S€g”T®k®m€Šb@‡P5„Gâ!S€gŠT®k…®ƒ¹ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/124fde9cfe7157773d8febcbb0829914bf4d17d3 b/webm_parser/fuzzing/corpus/124fde9cfe7157773d8febcbb0829914bf4d17d3
new file mode 100644
index 0000000..1a1a2df
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/124fde9cfe7157773d8febcbb0829914bf4d17d3
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/1386cc740c80ede6dfea5f3bb1d4fe1501ab5e91 b/webm_parser/fuzzing/corpus/1386cc740c80ede6dfea5f3bb1d4fe1501ab5e91
new file mode 100644
index 0000000..f1b13fc
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1386cc740c80ede6dfea5f3bb1d4fe1501ab5e91
@@ -0,0 +1 @@
+S€gŒT®k‡®…%†ˆ! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/13dd373ccb0c534402c6f7c4a0bdd723a26bc2c1 b/webm_parser/fuzzing/corpus/13dd373ccb0c534402c6f7c4a0bdd723a26bc2c1
new file mode 100644
index 0000000..5fea349
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/13dd373ccb0c534402c6f7c4a0bdd723a26bc2c1
@@ -0,0 +1 @@
+S€g”T®k®m€Šb@‡P5„Gá \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/13fc3d2b32d789c84be6349bb585c308289b4c3e b/webm_parser/fuzzing/corpus/13fc3d2b32d789c84be6349bb585c308289b4c3e
new file mode 100644
index 0000000..94ff769
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/13fc3d2b32d789c84be6349bb585c308289b4c3e
@@ -0,0 +1 @@
+S€g‘TÃgŒss‰gȆD„ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/148357130d1e5ac4059ad2bb6c63d78e2523f7d2 b/webm_parser/fuzzing/corpus/148357130d1e5ac4059ad2bb6c63d78e2523f7d2
new file mode 100644
index 0000000..23129b8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/148357130d1e5ac4059ad2bb6c63d78e2523f7d2
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/15366a3aafe2590c2e3183c088dde4cc100cb956 b/webm_parser/fuzzing/corpus/15366a3aafe2590c2e3183c088dde4cc100cb956
new file mode 100644
index 0000000..acb4e59
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/15366a3aafe2590c2e3183c088dde4cc100cb956
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶³sÄVTA‘@…@B€@”@C¶@sĶ@sĶ@sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/15feeb939fa90b25f57622a0abd9155ca88ad08b b/webm_parser/fuzzing/corpus/15feeb939fa90b25f57622a0abd9155ca88ad08b
new file mode 100644
index 0000000..e8c0a65
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/15feeb939fa90b25f57622a0abd9155ca88ad08b
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/16282b78a2018eb78544316554a92fe1003859e3 b/webm_parser/fuzzing/corpus/16282b78a2018eb78544316554a92fe1003859e3
new file mode 100644
index 0000000..4d9e8d5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/16282b78a2018eb78544316554a92fe1003859e3
@@ -0,0 +1 @@
+S€g”T®k®m€Šb@‡P5„Gâ! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1717628a6d6ea868febec5fb196edcf4b9eb284d b/webm_parser/fuzzing/corpus/1717628a6d6ea868febec5fb196edcf4b9eb284d
new file mode 100644
index 0000000..a495a9d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1717628a6d6ea868febec5fb196edcf4b9eb284d
@@ -0,0 +1 @@
+S€gŒT®k‡®…ჟÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/17921b1e28600e7e3faf67fc68b397ecab4e2a52 b/webm_parser/fuzzing/corpus/17921b1e28600e7e3faf67fc68b397ecab4e2a52
new file mode 100644
index 0000000..a351f0e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/17921b1e28600e7e3faf67fc68b397ecab4e2a52
@@ -0,0 +1 @@
+S€gC§pŠE¹‡¶…€ƒ…! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/17b8276355dd2368647b7756431820f5275cc3e2 b/webm_parser/fuzzing/corpus/17b8276355dd2368647b7756431820f5275cc3e2
new file mode 100644
index 0000000..0aeaa9e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/17b8276355dd2368647b7756431820f5275cc3e2
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/17d0aece97973ab23a467486b177ea9722e1b90b b/webm_parser/fuzzing/corpus/17d0aece97973ab23a467486b177ea9722e1b90b
new file mode 100644
index 0000000..447e515
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/17d0aece97973ab23a467486b177ea9722e1b90b
@@ -0,0 +1 @@
+S€g¼C§p·E¹´¶“|°…@hello€€€€€€€€€…@B€@…@C¶@ sÄ€€€€€€„€–€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/17e80cf8c247d17acad56c88750da34893fcb4fd b/webm_parser/fuzzing/corpus/17e80cf8c247d17acad56c88750da34893fcb4fd
new file mode 100644
index 0000000..23ff05e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/17e80cf8c247d17acad56c88750da34893fcb4fd
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/18040e106688eb1d54323927a403439ecfde6626 b/webm_parser/fuzzing/corpus/18040e106688eb1d54323927a403439ecfde6626
new file mode 100644
index 0000000..14b59f5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/18040e106688eb1d54323927a403439ecfde6626
@@ -0,0 +1 @@
+S€g”T®k®m€Šb@‡P5„Gâ!S€g”T®k®m€Šb@‡P5„Gâ!S€g›T®k–®”m€;S€g›T®k–®”m€‘ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1813261b3141faa01431c82e06292485516d3327 b/webm_parser/fuzzing/corpus/1813261b3141faa01431c82e06292485516d3327
new file mode 100644
index 0000000..063dacd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1813261b3141faa01431c82e06292485516d3327
@@ -0,0 +1 @@
+S€g¼C§p·E¹´¶²€ …@helloC|…lang0C~…area0C}…lang€…S|1C–T®k‘®m€Œb@‰P2€ÿ†G*lang2C~ƒGè…€ara1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/184ee2343d4b9f62c69383692829fd852ad8855d b/webm_parser/fuzzing/corpus/184ee2343d4b9f62c69383692829fd852ad8855d
new file mode 100644
index 0000000..10ba100
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/184ee2343d4b9f62c69383692829fd852ad8855d
@@ -0,0 +1 @@
+Tª€g‰C¶u„ ‚¢€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/186035a2f5c09ccd6b7d15de46f8561c98d8897d b/webm_parser/fuzzing/corpus/186035a2f5c09ccd6b7d15de46f8561c98d8897d
new file mode 100644
index 0000000..fdcc0a0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/186035a2f5c09ccd6b7d15de46f8561c98d8897d
@@ -0,0 +1 @@
+S€gC§pˆE¹…¶ƒ‘ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/186292900ad9d43881b71765158c32d78d80c8f3 b/webm_parser/fuzzing/corpus/186292900ad9d43881b71765158c32d78d80c8f3
new file mode 100644
index 0000000..35ada6f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/186292900ad9d43881b71765158c32d78d80c8f3
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/18fd762b91406d37b85a7b342a91434a15f17290 b/webm_parser/fuzzing/corpus/18fd762b91406d37b85a7b342a91434a15f17290
new file mode 100644
index 0000000..26c44c9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/18fd762b91406d37b85a7b342a91434a15f17290
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/19334eee05eeb18c549498e7ca2e792a2a4e04f3 b/webm_parser/fuzzing/corpus/19334eee05eeb18c549498e7ca2e792a2a4e04f3
new file mode 100644
index 0000000..cc25e67
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/19334eee05eeb18c549498e7ca2e792a2a4e04f3
@@ -0,0 +1 @@
+S€gC§p‹E¹ˆ¶†€„C| \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1a9a5df35779dd6e9e1de171b9bb0d316d2b64a5 b/webm_parser/fuzzing/corpus/1a9a5df35779dd6e9e1de171b9bb0d316d2b64a5
new file mode 100644
index 0000000..7293974
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1a9a5df35779dd6e9e1de171b9bb0d316d2b64a5
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šàˆ#ƒã„@Û \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1ab3d30f60743c2a1d3043773aae3a04f83c07c0 b/webm_parser/fuzzing/corpus/1ab3d30f60743c2a1d3043773aae3a04f83c07c0
new file mode 100644
index 0000000..9682a0f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1ab3d30f60743c2a1d3043773aae3a04f83c07c0
@@ -0,0 +1 @@
+Sg€„IŒû@D‰ÉÛ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1ad84ed46f3fda305150bac93958a5a390e23a67 b/webm_parser/fuzzing/corpus/1ad84ed46f3fda305150bac93958a5a390e23a67
new file mode 100644
index 0000000..7e0f66f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1ad84ed46f3fda305150bac93958a5a390e23a67
@@ -0,0 +1 @@
+S€g¼C§p·E¹´¶²€°…@helloC|…lang0C|…area0C|…lang1C|…lang2C|…area1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1b40a997150aa03c23ecc6efe445a2d7c3dd8368 b/webm_parser/fuzzing/corpus/1b40a997150aa03c23ecc6efe445a2d7c3dd8368
new file mode 100644
index 0000000..b5d9ab5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1b40a997150aa03c23ecc6efe445a2d7c3dd8368
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/1b6b3bab9032cd420f350b6bb252942484f6b527 b/webm_parser/fuzzing/corpus/1b6b3bab9032cd420f350b6bb252942484f6b527
new file mode 100644
index 0000000..5275f03
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1b6b3bab9032cd420f350b6bb252942484f6b527
@@ -0,0 +1 @@
+bdT°gŒT®‡®…ᇟ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1b7765cb05c94581461ffcd38d38b7b272d8e9d6 b/webm_parser/fuzzing/corpus/1b7765cb05c94581461ffcd38d38b7b272d8e9d6
new file mode 100644
index 0000000..980ae84
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1b7765cb05c94581461ffcd38d38b7b272d8e9d6
@@ -0,0 +1 @@
+S€gT®kˆ®†à„T² \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1b89e3fe0cbd4c2291a74bf21969a9d9d851cbf1 b/webm_parser/fuzzing/corpus/1b89e3fe0cbd4c2291a74bf21969a9d9d851cbf1
new file mode 100644
index 0000000..24ed722
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1b89e3fe0cbd4c2291a74bf21969a9d9d851cbf1
@@ -0,0 +1 @@
+ã \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1b95d4da08fe949a60f63cd59213d42d30dbd381 b/webm_parser/fuzzing/corpus/1b95d4da08fe949a60f63cd59213d42d30dbd381
new file mode 100644
index 0000000..1326dfe
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1b95d4da08fe949a60f63cd59213d42d30dbd381
@@ -0,0 +1 @@
+S€gŒS»k‡»…·ƒ² \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1bac1200e05bb3269d019903241791c917a12bd0 b/webm_parser/fuzzing/corpus/1bac1200e05bb3269d019903241791c917a12bd0
new file mode 100644
index 0000000..a930622
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1bac1200e05bb3269d019903241791c917a12bd0
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/1bb8f5f81b7f6c1d58e1f7bb13fa561fe1a146bc b/webm_parser/fuzzing/corpus/1bb8f5f81b7f6c1d58e1f7bb13fa561fe1a146bc
new file mode 100644
index 0000000..b3204bc
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1bb8f5f81b7f6c1d58e1f7bb13fa561fe1a146bc
@@ -0,0 +1 @@
+S€g‘C¶uŒ Šu¡‡¦…îƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1c56068c6dd17e9a824db6da78d64f933267a8c0 b/webm_parser/fuzzing/corpus/1c56068c6dd17e9a824db6da78d64f933267a8c0
new file mode 100644
index 0000000..bd3b28e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1c56068c6dd17e9a824db6da78d64f933267a8c0
@@ -0,0 +1 @@
+S€gTÃgŠss‡cÀ„hgÈ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1c8db8b9d88dd3483b6f81e4224c4f985046e6ac b/webm_parser/fuzzing/corpus/1c8db8b9d88dd3483b6f81e4224c4f985046e6ac
new file mode 100644
index 0000000..57d9844
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1c8db8b9d88dd3483b6f81e4224c4f985046e6ac
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/1d29a77924602a79c0f546535a885f59cbbbc405 b/webm_parser/fuzzing/corpus/1d29a77924602a79c0f546535a885f59cbbbc405
new file mode 100644
index 0000000..3baa1ce
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1d29a77924602a79c0f546535a885f59cbbbc405
@@ -0,0 +1 @@
+S€g£I©f€I©f€I©f€C¶u€T®k€)S»k€ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1d75a65733da627e5c401625bff522eadf098315 b/webm_parser/fuzzing/corpus/1d75a65733da627e5c401625bff522eadf098315
new file mode 100644
index 0000000..5b6200c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1d75a65733da627e5c401625bff522eadf098315
@@ -0,0 +1 @@
+S€gT®kˆ®†à„S¸ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/1df9507cc2a54a369646d6d34d846d3fcc172479 b/webm_parser/fuzzing/corpus/1df9507cc2a54a369646d6d34d846d3fcc172479
new file mode 100644
index 0000000..0826b42
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1df9507cc2a54a369646d6d34d846d3fcc172479
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/1e7a571be5aa542c3dfec1223d2e089a3075ce1f b/webm_parser/fuzzing/corpus/1e7a571be5aa542c3dfec1223d2e089a3075ce1f
new file mode 100644
index 0000000..4feebdf
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/1e7a571be5aa542c3dfec1223d2e089a3075ce1f
@@ -0,0 +1 @@
+S€gŒT®k‡®…ჟ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/20a6b040258fbfa09bb37c6fc07106b2e43bada7 b/webm_parser/fuzzing/corpus/20a6b040258fbfa09bb37c6fc07106b2e43bada7
new file mode 100644
index 0000000..fcae8ab
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/20a6b040258fbfa09bb37c6fc07106b2e43bada7
@@ -0,0 +1 @@
+S€gŠS»k…»ƒ³ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/20e8ca854d3c0c375dc943142a04ee2260f0d1fd b/webm_parser/fuzzing/corpus/20e8ca854d3c0c375dc943142a04ee2260f0d1fd
new file mode 100644
index 0000000..dbaa571
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/20e8ca854d3c0c375dc943142a04ee2260f0d1fd
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/212ee7d21a8cb25249644cab4f959db111f3a3c0 b/webm_parser/fuzzing/corpus/212ee7d21a8cb25249644cab4f959db111f3a3c0
new file mode 100644
index 0000000..0474655
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/212ee7d21a8cb25249644cab4f959db111f3a3c0
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/224ce7b415f7950118880fce0594734aef489fec b/webm_parser/fuzzing/corpus/224ce7b415f7950118880fce0594734aef489fec
new file mode 100644
index 0000000..35aedfe
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/224ce7b415f7950118880fce0594734aef489fec
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/226e70c1beddace83862791a6555c80475640bd0 b/webm_parser/fuzzing/corpus/226e70c1beddace83862791a6555c80475640bd0
new file mode 100644
index 0000000..028ff9d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/226e70c1beddace83862791a6555c80475640bd0
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/233d53e3eb21b6ea6feebd6e59e3ef888a840f49 b/webm_parser/fuzzing/corpus/233d53e3eb21b6ea6feebd6e59e3ef888a840f49
new file mode 100644
index 0000000..ff5a962
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/233d53e3eb21b6ea6feebd6e59e3ef888a840f49
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/237f7aee90f206d3a6b138cc908b8921b544e190 b/webm_parser/fuzzing/corpus/237f7aee90f206d3a6b138cc908b8921b544e190
new file mode 100644
index 0000000..60d1eaa
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/237f7aee90f206d3a6b138cc908b8921b544e190
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/23e1ac0f77f1283cf6e9fa044df3ec51bff27fd4 b/webm_parser/fuzzing/corpus/23e1ac0f77f1283cf6e9fa044df3ec51bff27fd4
new file mode 100644
index 0000000..b17cd82
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/23e1ac0f77f1283cf6e9fa044df3ec51bff27fd4
@@ -0,0 +1 @@
+S€gŠS»k…»ƒ³ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/23fcee6c71a8f5a22447df688724c0250fb77a70 b/webm_parser/fuzzing/corpus/23fcee6c71a8f5a22447df688724c0250fb77a70
new file mode 100644
index 0000000..6580bec
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/23fcee6c71a8f5a22447df688724c0250fb77a70
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/2459eb855d8c6ebac13cb74d996565d78130f4dd b/webm_parser/fuzzing/corpus/2459eb855d8c6ebac13cb74d996565d78130f4dd
new file mode 100644
index 0000000..2ad814f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2459eb855d8c6ebac13cb74d996565d78130f4dd
@@ -0,0 +1 @@
+EߣˆB‚@webmS€g©C¶u¤ç@«@£…¾Eß#Z \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/259188e5fb0c09046df96f6d565c59e0d146f198 b/webm_parser/fuzzing/corpus/259188e5fb0c09046df96f6d565c59e0d146f198
new file mode 100644
index 0000000..c69951e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/259188e5fb0c09046df96f6d565c59e0d146f198
@@ -0,0 +1 @@
+S€gŠT®k…®ƒœ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/26f07f8e28e7521ed282fe5c3938c1ae225816b9 b/webm_parser/fuzzing/corpus/26f07f8e28e7521ed282fe5c3938c1ae225816b9
new file mode 100644
index 0000000..d592c6e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/26f07f8e28e7521ed282fe5c3938c1ae225816b9
@@ -0,0 +1 @@
+STŠggÀss‡g„D…! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/276425d65d58453d03a3444c9f6662d08d449af7 b/webm_parser/fuzzing/corpus/276425d65d58453d03a3444c9f6662d08d449af7
new file mode 100644
index 0000000..3d99d50
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/276425d65d58453d03a3444c9f6662d08d449af7
@@ -0,0 +1 @@
+S€g”TÃgssŒcÀ‰hÊ€cÊ€cÅ€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/27d62874ec87a2552e7c842da65de113aa69f7aa b/webm_parser/fuzzing/corpus/27d62874ec87a2552e7c842da65de113aa69f7aa
new file mode 100644
index 0000000..f83242a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/27d62874ec87a2552e7c842da65de113aa69f7aa
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/281b259280ada5d07b07a22cbe9a78c7b0fba94b b/webm_parser/fuzzing/corpus/281b259280ada5d07b07a22cbe9a78c7b0fba94b
new file mode 100644
index 0000000..d59afe9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/281b259280ada5d07b07a22cbe9a78c7b0fba94b
@@ -0,0 +1 @@
+S€g”C¶u u¡Š¦ƒî¦ƒî \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/299ed12b98673c6c4adbcf886cb70db8adde6aa1 b/webm_parser/fuzzing/corpus/299ed12b98673c6c4adbcf886cb70db8adde6aa1
new file mode 100644
index 0000000..2afc785
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/299ed12b98673c6c4adbcf886cb70db8adde6aa1
@@ -0,0 +1 @@
+S€g–T®k‘®m€Œb@‰P3†GçƒGè€S€gg—T®kÀ®m€b@ŠP3‡Gç„Gè \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/2a40feb7480d0b31c36d5626761e948d0ae52792 b/webm_parser/fuzzing/corpus/2a40feb7480d0b31c36d5626761e948d0ae52792
new file mode 100644
index 0000000..6828d16
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2a40feb7480d0b31c36d5626761e948d0ae52792
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/2a6d7e2b829ed28307b551eda0d96f1834bff899 b/webm_parser/fuzzing/corpus/2a6d7e2b829ed28307b551eda0d96f1834bff899
new file mode 100644
index 0000000..6556c80
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2a6d7e2b829ed28307b551eda0d96f1834bff899
@@ -0,0 +1 @@
+S€g¼C§p·E¹´¶²€°…@helltC|…lang0C~…area0C|…lang1C|…lang2C~…a1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/2a8dda90aa286175b5c683b57fae1dc7e6ac1e7e b/webm_parser/fuzzing/corpus/2a8dda90aa286175b5c683b57fae1dc7e6ac1e7e
new file mode 100644
index 0000000..f453884
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2a8dda90aa286175b5c683b57fae1dc7e6ac1e7e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/2a9588e6fab82016b545462cff2ed014c0345551 b/webm_parser/fuzzing/corpus/2a9588e6fab82016b545462cff2ed014c0345551
new file mode 100644
index 0000000..0140636
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2a9588e6fab82016b545462cff2ed014c0345551
@@ -0,0 +1 @@
+S€gT®kˆ®†à„T°ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/2b1d57a8e8fa7164c9ba00957c9486010754560c b/webm_parser/fuzzing/corpus/2b1d57a8e8fa7164c9ba00957c9486010754560c
new file mode 100644
index 0000000..298598f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2b1d57a8e8fa7164c9ba00957c9486010754560c
@@ -0,0 +1 @@
+S€g–T®k‘®m€Œb@‰P5†GçƒGè€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/2b475d1a8f2fe4fbba92e1424f0ea96d95b12fb7 b/webm_parser/fuzzing/corpus/2b475d1a8f2fe4fbba92e1424f0ea96d95b12fb7
new file mode 100644
index 0000000..7e8be98
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2b475d1a8f2fe4fbba92e1424f0ea96d95b12fb7
@@ -0,0 +1 @@
+S€g˜T®k“®‘m€Žb@„P1b@€P1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/2bb80c6d0e2eadd73018eb2a8cae37d41602ee79 b/webm_parser/fuzzing/corpus/2bb80c6d0e2eadd73018eb2a8cae37d41602ee79
new file mode 100644
index 0000000..8f07d6c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2bb80c6d0e2eadd73018eb2a8cae37d41602ee79
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/2c1389f882e256e260e37e8a67af7e32d0f4e0fc b/webm_parser/fuzzing/corpus/2c1389f882e256e260e37e8a67af7e32d0f4e0fc
new file mode 100644
index 0000000..6710408
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2c1389f882e256e260e37e8a67af7e32d0f4e0fc
@@ -0,0 +1 @@
+µÿÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/2c1f94c76e4eec607cce5311323620f349912e73 b/webm_parser/fuzzing/corpus/2c1f94c76e4eec607cce5311323620f349912e73
new file mode 100644
index 0000000..26995d2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2c1f94c76e4eec607cce5311323620f349912e73
@@ -0,0 +1 @@
+S€gŽS»k‰»‡·…ðƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/2c962c7fbceaacf8247ac9b70c8eeb1f814e435b b/webm_parser/fuzzing/corpus/2c962c7fbceaacf8247ac9b70c8eeb1f814e435b
new file mode 100644
index 0000000..856cb5b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2c962c7fbceaacf8247ac9b70c8eeb1f814e435b
@@ -0,0 +1 @@
+S€gŒM›t‡M»„S¬ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/2c9aaabacf3a3b48dec4a85767cc5d38a1736aff b/webm_parser/fuzzing/corpus/2c9aaabacf3a3b48dec4a85767cc5d38a1736aff
new file mode 100644
index 0000000..28edff6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2c9aaabacf3a3b48dec4a85767cc5d38a1736aff
@@ -0,0 +1 @@
+S€gC¶uˆ †Ž„y‚Ì€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/2cf086299f983d0afc7f95c5e0c86b657448af33 b/webm_parser/fuzzing/corpus/2cf086299f983d0afc7f95c5e0c86b657448af33
new file mode 100644
index 0000000..13f1826
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2cf086299f983d0afc7f95c5e0c86b657448af33
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/2d0d96b95e9a3316d1ff0ef019ada509bbde4c9d b/webm_parser/fuzzing/corpus/2d0d96b95e9a3316d1ff0ef019ada509bbde4c9d
new file mode 100644
index 0000000..adcb9e1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2d0d96b95e9a3316d1ff0ef019ada509bbde4c9d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/2d414d5dfd20055393df3208009840da9cfabca9 b/webm_parser/fuzzing/corpus/2d414d5dfd20055393df3208009840da9cfabca9
new file mode 100644
index 0000000..8e5d23f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2d414d5dfd20055393df3208009840da9cfabca9
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/2df967edcc00ac5c8e00037f1a43680600af5cba b/webm_parser/fuzzing/corpus/2df967edcc00ac5c8e00037f1a43680600af5cba
new file mode 100644
index 0000000..fb0e9b1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2df967edcc00ac5c8e00037f1a43680600af5cba
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/2e6b09f1eca05ce2bba920fb78f9bca2a883fda0 b/webm_parser/fuzzing/corpus/2e6b09f1eca05ce2bba920fb78f9bca2a883fda0
new file mode 100644
index 0000000..2a3a9d3
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2e6b09f1eca05ce2bba920fb78f9bca2a883fda0
@@ -0,0 +1 @@
+Eߣ„B‚! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/2e8bde3549723e13849b604f4deedd51c71dfef5 b/webm_parser/fuzzing/corpus/2e8bde3549723e13849b604f4deedd51c71dfef5
new file mode 100644
index 0000000..16bf112
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2e8bde3549723e13849b604f4deedd51c71dfef5
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/2f6e92a71918d01c16762d5ca59b328f1341d326 b/webm_parser/fuzzing/corpus/2f6e92a71918d01c16762d5ca59b328f1341d326
new file mode 100644
index 0000000..3393c8d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/2f6e92a71918d01c16762d5ca59b328f1341d326
@@ -0,0 +1 @@
+S€g»k \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/306e2cf9aebe012cb0769b1b2a6ea68af2e8ed44 b/webm_parser/fuzzing/corpus/306e2cf9aebe012cb0769b1b2a6ea68af2e8ed44
new file mode 100644
index 0000000..60a0f48
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/306e2cf9aebe012cb0769b1b2a6ea68af2e8ed44
@@ -0,0 +1 @@
+áEߣˆB‚@webmS€g£*M'›t€I©f€C¶u€TSkk»€®€C§p€TÃg€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/30f7348d35de0c47d2044736cb115972b800569d b/webm_parser/fuzzing/corpus/30f7348d35de0c47d2044736cb115972b800569d
new file mode 100644
index 0000000..d992dbe
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/30f7348d35de0c47d2044736cb115972b800569d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/310798b8d94a3a2fe649a4c871458b4d74fdcf32 b/webm_parser/fuzzing/corpus/310798b8d94a3a2fe649a4c871458b4d74fdcf32
new file mode 100644
index 0000000..e3c32aa
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/310798b8d94a3a2fe649a4c871458b4d74fdcf32
@@ -0,0 +1 @@
+S€gC§pˆE¹…¶ƒ’ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/31ea606a9859bb29d7f98162d254348021e0d932 b/webm_parser/fuzzing/corpus/31ea606a9859bb29d7f98162d254348021e0d932
new file mode 100644
index 0000000..038f1a9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/31ea606a9859bb29d7f98162d254348021e0d932
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶³sÄVTA‘@’@€@…@B€@…@C¶@sĶ@€ÿÿÿ¶@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3230fc31ab8d408c484aa28bbe36431f71101243 b/webm_parser/fuzzing/corpus/3230fc31ab8d408c484aa28bbe36431f71101243
new file mode 100644
index 0000000..cad0aa0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3230fc31ab8d408c484aa28bbe36431f71101243
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/328b7c92996e480d1f11efe847c3be4b5dc0f8eb b/webm_parser/fuzzing/corpus/328b7c92996e480d1f11efe847c3be4b5dc0f8eb
new file mode 100644
index 0000000..b229bcb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/328b7c92996e480d1f11efe847c3be4b5dc0f8eb
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/32e58bb3d00863115f33707e0c8af722036676fd b/webm_parser/fuzzing/corpus/32e58bb3d00863115f33707e0c8af722036676fd
new file mode 100644
index 0000000..caf3c09
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/32e58bb3d00863115f33707e0c8af722036676fd
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/32fc8547b6145c502d98e5e0c296ebd05badfdb9 b/webm_parser/fuzzing/corpus/32fc8547b6145c502d98e5e0c296ebd05badfdb9
new file mode 100644
index 0000000..67c94c8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/32fc8547b6145c502d98e5e0c296ebd05badfdb9
@@ -0,0 +1 @@
+S€g˜T®k“®‘m€Žb@„P1b@€P1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/334cddba889265a1263feb77a2afdc454ca54f5e b/webm_parser/fuzzing/corpus/334cddba889265a1263feb77a2afdc454ca54f5e
new file mode 100644
index 0000000..e37d039
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/334cddba889265a1263feb77a2afdc454ca54f5e
@@ -0,0 +1 @@
+S€gŒI©f‡*×±ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/33c9f9990890d2baf0c30d74b34b486082782413 b/webm_parser/fuzzing/corpus/33c9f9990890d2baf0c30d74b34b486082782413
new file mode 100644
index 0000000..9a1abea
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/33c9f9990890d2baf0c30d74b34b486082782413
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/341f3e8689e57eeab4234fcdd6d2b8f800b9ab34 b/webm_parser/fuzzing/corpus/341f3e8689e57eeab4234fcdd6d2b8f800b9ab34
new file mode 100644
index 0000000..090bf59
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/341f3e8689e57eeab4234fcdd6d2b8f800b9ab34
@@ -0,0 +1 @@
+S€g“C¶uŽ ŒŽŠè€Ìè€Ì \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3457e356eac79bf1c30b59940532d360dba2c1a9 b/webm_parser/fuzzing/corpus/3457e356eac79bf1c30b59940532d360dba2c1a9
new file mode 100644
index 0000000..3f813fb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3457e356eac79bf1c30b59940532d360dba2c1a9
@@ -0,0 +1 @@
+S€gC¶uŠ ˆu¡…¦ƒîÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/351185a29bdbc7bf0d584479001fb47c32c61e5b b/webm_parser/fuzzing/corpus/351185a29bdbc7bf0d584479001fb47c32c61e5b
new file mode 100644
index 0000000..6033e1c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/351185a29bdbc7bf0d584479001fb47c32c61e5b
@@ -0,0 +1 @@
+!EߣˆB‚@webmS€g£M›t€I©f€C¶u€T®k€S»k€C§p€TÃg€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/35ae0d43c6bfc5f4b45f16877157832fddafce77 b/webm_parser/fuzzing/corpus/35ae0d43c6bfc5f4b45f16877157832fddafce77
new file mode 100644
index 0000000..7e7e562
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/35ae0d43c6bfc5f4b45f16877157832fddafce77
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/35b2f81c573b15304cb9b13f00008b460da78942 b/webm_parser/fuzzing/corpus/35b2f81c573b15304cb9b13f00008b460da78942
new file mode 100644
index 0000000..85f4be2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/35b2f81c573b15304cb9b13f00008b460da78942
@@ -0,0 +1 @@
+S€gˆC¶uƒ«ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/35d9256a4d7ef3ee54384616178af3a8092d0981 b/webm_parser/fuzzing/corpus/35d9256a4d7ef3ee54384616178af3a8092d0981
new file mode 100644
index 0000000..42f1817
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/35d9256a4d7ef3ee54384616178af3a8092d0981
@@ -0,0 +1 @@
+S€gˆTÃgƒss€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/36352efd680a747f0f2c93d760559d69399a4102 b/webm_parser/fuzzing/corpus/36352efd680a747f0f2c93d760559d69399a4102
new file mode 100644
index 0000000..d438e9c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/36352efd680a747f0f2c93d760559d69399a4102
@@ -0,0 +1 @@
+S€gŠC¶u… ƒ›ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3685ffbcfe28b166ced0a783b012a446f9cfea46 b/webm_parser/fuzzing/corpus/3685ffbcfe28b166ced0a783b012a446f9cfea46
new file mode 100644
index 0000000..fe6660b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3685ffbcfe28b166ced0a783b012a446f9cfea46
@@ -0,0 +1 @@
+S€gŒS»k‡»…·ƒ÷ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/36def8b2661197d594756f116a750822ae1d65d5 b/webm_parser/fuzzing/corpus/36def8b2661197d594756f116a750822ae1d65d5
new file mode 100644
index 0000000..50d822a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/36def8b2661197d594756f116a750822ae1d65d5
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šm€‡b@„P3 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/371950308853fb3b1b3a940c0598bfdfeb1d0f9c b/webm_parser/fuzzing/corpus/371950308853fb3b1b3a940c0598bfdfeb1d0f9c
new file mode 100644
index 0000000..d12faf8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/371950308853fb3b1b3a940c0598bfdfeb1d0f9c
@@ -0,0 +1 @@
+S€g‹C¶u† „u¢ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3765e9174971dbac9cfeb7e485ca61dc4941f7a9 b/webm_parser/fuzzing/corpus/3765e9174971dbac9cfeb7e485ca61dc4941f7a9
new file mode 100644
index 0000000..4c306d9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3765e9174971dbac9cfeb7e485ca61dc4941f7a9
@@ -0,0 +1 @@
+S€gžT®k™®—m€”b@‘P5ŽGç‹Gèˆ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/37cae036ce7ff5d0671f32d3757579f248609c9c b/webm_parser/fuzzing/corpus/37cae036ce7ff5d0671f32d3757579f248609c9c
new file mode 100644
index 0000000..9183f01
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/37cae036ce7ff5d0671f32d3757579f248609c9c
@@ -0,0 +1 @@
+S€g”T®k®m€Šb*‡P0„Gâ!S€gŠT®k…®ƒ¹ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/38460ea06ee847ff3d5733415c299b44b0866e58 b/webm_parser/fuzzing/corpus/38460ea06ee847ff3d5733415c299b44b0866e58
new file mode 100644
index 0000000..6456fa5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/38460ea06ee847ff3d5733415c299b44b0866e58
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶žsÄVTA‘@’@€@…@B€@…@C¶@sĶ@€ÿÿÿ¶@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3865de2fec3f353513d86b28e43bf936d4707f97 b/webm_parser/fuzzing/corpus/3865de2fec3f353513d86b28e43bf936d4707f97
new file mode 100644
index 0000000..f2150f6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3865de2fec3f353513d86b28e43bf936d4707f97
@@ -0,0 +1 @@
+€gS€g½C§p¸E¹µ¶³sÄVTA‘ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3867b18060c307e0d04e0098f195d699e4b3294f b/webm_parser/fuzzing/corpus/3867b18060c307e0d04e0098f195d699e4b3294f
new file mode 100644
index 0000000..f0cfd0a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3867b18060c307e0d04e0098f195d699e4b3294f
@@ -0,0 +1 @@
+S€g»TÃg¶ss³gțȄ#‡dgÈ„]‡egÈ„D‡dgÈ„]‡egÈ„D‡gÈ€D‡dgÈ€g \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/391920720c3609b7c1f7b51162ce3ec99329a0b4 b/webm_parser/fuzzing/corpus/391920720c3609b7c1f7b51162ce3ec99329a0b4
new file mode 100644
index 0000000..fb057c8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/391920720c3609b7c1f7b51162ce3ec99329a0b4
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/395027454c7d5babff9544414c04a39d712eed1c b/webm_parser/fuzzing/corpus/395027454c7d5babff9544414c04a39d712eed1c
new file mode 100644
index 0000000..e34ef6f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/395027454c7d5babff9544414c04a39d712eed1c
@@ -0,0 +1 @@
+€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ²€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ²€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶ÿ¶€¶€¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€D¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€II
diff --git a/webm_parser/fuzzing/corpus/3a258c9ed48e634ed8d495f07caf21dbc6978205 b/webm_parser/fuzzing/corpus/3a258c9ed48e634ed8d495f07caf21dbc6978205
new file mode 100644
index 0000000..a14b82b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3a258c9ed48e634ed8d495f07caf21dbc6978205
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/3a397485197e6b99c77158132eaf211389615fe5 b/webm_parser/fuzzing/corpus/3a397485197e6b99c77158132eaf211389615fe5
new file mode 100644
index 0000000..f8697b8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3a397485197e6b99c77158132eaf211389615fe5
@@ -0,0 +1 @@
+S€gŠT®k…®ƒˆÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3a66b34706b686fe0fa8ca8f10c829758b2ebb07 b/webm_parser/fuzzing/corpus/3a66b34706b686fe0fa8ca8f10c829758b2ebb07
new file mode 100644
index 0000000..5062337
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3a66b34706b686fe0fa8ca8f10c829758b2ebb07
@@ -0,0 +1 @@
+S€g˜T®k“®‘m€Žb@„P1b€P1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3ba666ad891fa8009dfc961aa3c215bbfd2d81d2 b/webm_parser/fuzzing/corpus/3ba666ad891fa8009dfc961aa3c215bbfd2d81d2
new file mode 100644
index 0000000..227e447
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3ba666ad891fa8009dfc961aa3c215bbfd2d81d2
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/3ba9769c7c23cc3c80542ad123a371cf4f69e858 b/webm_parser/fuzzing/corpus/3ba9769c7c23cc3c80542ad123a371cf4f69e858
new file mode 100644
index 0000000..d6bae74
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3ba9769c7c23cc3c80542ad123a371cf4f69e858
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/3beb4432302433e303a9ccaf34637dd3a910028e b/webm_parser/fuzzing/corpus/3beb4432302433e303a9ccaf34637dd3a910028e
new file mode 100644
index 0000000..63ae212
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3beb4432302433e303a9ccaf34637dd3a910028e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/3c6cdebc450d20a58150a9c71335b45a6a5ee0e5 b/webm_parser/fuzzing/corpus/3c6cdebc450d20a58150a9c71335b45a6a5ee0e5
new file mode 100644
index 0000000..b92a4ed
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3c6cdebc450d20a58150a9c71335b45a6a5ee0e5
@@ -0,0 +1 @@
+S€gS¸ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3c867362eb6a20f573a890db87749fce3c719d14 b/webm_parser/fuzzing/corpus/3c867362eb6a20f573a890db87749fce3c719d14
new file mode 100644
index 0000000..8175642
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3c867362eb6a20f573a890db87749fce3c719d14
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/3cd69ddcd34bd874e3abade696f95c949c17198d b/webm_parser/fuzzing/corpus/3cd69ddcd34bd874e3abade696f95c949c17198d
new file mode 100644
index 0000000..298c393
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3cd69ddcd34bd874e3abade696f95c949c17198d
@@ -0,0 +1 @@
+S€gŒM›t‡M»„S«! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3de495ebac4b80064b79844f3a9453270caec251 b/webm_parser/fuzzing/corpus/3de495ebac4b80064b79844f3a9453270caec251
new file mode 100644
index 0000000..c4fb5a4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3de495ebac4b80064b79844f3a9453270caec251
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/3e04542bcc462ded0025b53371f709d8fcd25c21 b/webm_parser/fuzzing/corpus/3e04542bcc462ded0025b53371f709d8fcd25c21
new file mode 100644
index 0000000..8089b46
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3e04542bcc462ded0025b53371f709d8fcd25c21
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/3e94196e55a9214083bda470772d71aedc17eb5f b/webm_parser/fuzzing/corpus/3e94196e55a9214083bda470772d71aedc17eb5f
new file mode 100644
index 0000000..18f0b8f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3e94196e55a9214083bda470772d71aedc17eb5f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/3ec4ac00bb967a335b5caae0ae51488f2fda6583 b/webm_parser/fuzzing/corpus/3ec4ac00bb967a335b5caae0ae51488f2fda6583
new file mode 100644
index 0000000..b8043e8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3ec4ac00bb967a335b5caae0ae51488f2fda6583
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/3eec38c0ac3a96856d3210a0357857e70b07a9cb b/webm_parser/fuzzing/corpus/3eec38c0ac3a96856d3210a0357857e70b07a9cb
new file mode 100644
index 0000000..25f0273
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3eec38c0ac3a96856d3210a0357857e70b07a9cb
@@ -0,0 +1 @@
+S€g™TÃg‰ss†cÀ€cÀ€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3ef82ba46238fa46b89148a230d23e741ba5f1da b/webm_parser/fuzzing/corpus/3ef82ba46238fa46b89148a230d23e741ba5f1da
new file mode 100644
index 0000000..134a456
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3ef82ba46238fa46b89148a230d23e741ba5f1da
@@ -0,0 +1 @@
+S€g˜T®k“®‘m€b@„P1b@€b@€P1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3fbd43f81888ca60e5bfe49b47e23cb115d5cd7d b/webm_parser/fuzzing/corpus/3fbd43f81888ca60e5bfe49b47e23cb115d5cd7d
new file mode 100644
index 0000000..75b0d29
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3fbd43f81888ca60e5bfe49b47e23cb115d5cd7d
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶¬sÄV…@B€@…@C¶@sĶ@sä¶@sĶ@sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3fe56e5b06653e8a37c99da190ff8d44d14bfc06 b/webm_parser/fuzzing/corpus/3fe56e5b06653e8a37c99da190ff8d44d14bfc06
new file mode 100644
index 0000000..7a3c719
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3fe56e5b06653e8a37c99da190ff8d44d14bfc06
@@ -0,0 +1 @@
+S€G™T®k”®’m€bŒP‰S€g“C¶uŽ ŒŽŠèƒÌá€èGƒÌâ€Gç€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/3febffec3be8b1f4bcb76a01c1d713600d8e4adb b/webm_parser/fuzzing/corpus/3febffec3be8b1f4bcb76a01c1d713600d8e4adb
new file mode 100644
index 0000000..5f0a13d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/3febffec3be8b1f4bcb76a01c1d713600d8e4adb
@@ -0,0 +1 @@
+S€gŠC§p…E¹‚¶€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/43380e5a9dd29ca847e9b5bfba12a0a95d94d0da b/webm_parser/fuzzing/corpus/43380e5a9dd29ca847e9b5bfba12a0a95d94d0da
new file mode 100644
index 0000000..1c630fc
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/43380e5a9dd29ca847e9b5bfba12a0a95d94d0da
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/435d3ff6ddc43ac582e740ead969397fd2bfc7f5 b/webm_parser/fuzzing/corpus/435d3ff6ddc43ac582e740ead969397fd2bfc7f5
new file mode 100644
index 0000000..8d8a6f4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/435d3ff6ddc43ac582e740ead969397fd2bfc7f5
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/448120a37132488f48ab6c46a146c995948958cc b/webm_parser/fuzzing/corpus/448120a37132488f48ab6c46a146c995948958cc
new file mode 100644
index 0000000..ff3b629
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/448120a37132488f48ab6c46a146c995948958cc
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šm€‡b@„P1S€g”T®k®m€Šb@€Ð5„Gáÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/46db501d3e9a8808118333740a22bf9c808af239 b/webm_parser/fuzzing/corpus/46db501d3e9a8808118333740a22bf9c808af239
new file mode 100644
index 0000000..a6c8ea7
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/46db501d3e9a8808118333740a22bf9c808af239
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/47a2ca2b47aa1993cb655b77a0273ff139dd2280 b/webm_parser/fuzzing/corpus/47a2ca2b47aa1993cb655b77a0273ff139dd2280
new file mode 100644
index 0000000..21fea1b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/47a2ca2b47aa1993cb655b77a0273ff139dd2280
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/490576c8f6c235e1ed921c4932bac028070c806a b/webm_parser/fuzzing/corpus/490576c8f6c235e1ed921c4932bac028070c806a
new file mode 100644
index 0000000..c83a83e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/490576c8f6c235e1ed921c4932bac028070c806a
@@ -0,0 +1 @@
+S€g‘C¶uŒ Š¡‡¦…îƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/4930b86bf0dfb02836b0319d21aa4a63a8dd894d b/webm_parser/fuzzing/corpus/4930b86bf0dfb02836b0319d21aa4a63a8dd894d
new file mode 100644
index 0000000..3bc24db
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4930b86bf0dfb02836b0319d21aa4a63a8dd894d
@@ -0,0 +1 @@
+S€gŠC¶u… ƒûÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/49667ff0b09901223117e74d2506bc37a5ae1580 b/webm_parser/fuzzing/corpus/49667ff0b09901223117e74d2506bc37a5ae1580
new file mode 100644
index 0000000..a7c53d4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/49667ff0b09901223117e74d2506bc37a5ae1580
@@ -0,0 +1 @@
+S€gŠT®k…®ƒ× \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/49906adf7313330e29be20d51fb36e9bdf1939c3 b/webm_parser/fuzzing/corpus/49906adf7313330e29be20d51fb36e9bdf1939c3
new file mode 100644
index 0000000..713aeb8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/49906adf7313330e29be20d51fb36e9bdf1939c3
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/49c0e3eed81c1827e755c70c9c6d9606e7f54b22 b/webm_parser/fuzzing/corpus/49c0e3eed81c1827e755c70c9c6d9606e7f54b22
new file mode 100644
index 0000000..cc061ed
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/49c0e3eed81c1827e755c70c9c6d9606e7f54b22
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/4a3e77f5bc061fc8121ef7ebd2b8e3ebb0bdd609 b/webm_parser/fuzzing/corpus/4a3e77f5bc061fc8121ef7ebd2b8e3ebb0bdd609
new file mode 100644
index 0000000..336e6fb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4a3e77f5bc061fc8121ef7ebd2b8e3ebb0bdd609
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶³sÄVTA‘@’@€@…@B€€€€j€€€€€€€€€€€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/4a454e01ef09b175f3864b654cfc1104a850e871 b/webm_parser/fuzzing/corpus/4a454e01ef09b175f3864b654cfc1104a850e871
new file mode 100644
index 0000000..f57bbfd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4a454e01ef09b175f3864b654cfc1104a850e871
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/4acd9df9a7ee34e48c6c80777159b089f417a69a b/webm_parser/fuzzing/corpus/4acd9df9a7ee34e48c6c80777159b089f417a69a
new file mode 100644
index 0000000..1df1e83
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4acd9df9a7ee34e48c6c80777159b089f417a69a
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/4af5564129d7b4c9c4736a3e511926c29348a4a4 b/webm_parser/fuzzing/corpus/4af5564129d7b4c9c4736a3e511926c29348a4a4
new file mode 100644
index 0000000..93af2db
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4af5564129d7b4c9c4736a3e511926c29348a4a4
@@ -0,0 +1 @@
+S€g™T®k”®’m€b@ŒP5‰Gá€â€Gç€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/4b3957bcbe5975399e34485cf0e5229169ab67b9 b/webm_parser/fuzzing/corpus/4b3957bcbe5975399e34485cf0e5229169ab67b9
new file mode 100644
index 0000000..974197f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4b3957bcbe5975399e34485cf0e5229169ab67b9
@@ -0,0 +1 @@
+S€gTÃgŠss‡cÀ„cÊ! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/4c9e40aa8a439a500f073cf5a1e060a16ad12a6f b/webm_parser/fuzzing/corpus/4c9e40aa8a439a500f073cf5a1e060a16ad12a6f
new file mode 100644
index 0000000..2d31d44
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4c9e40aa8a439a500f073cf5a1e060a16ad12a6f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/4cb5ff0e26f5bd0bb1fca846b4843db69095f18b b/webm_parser/fuzzing/corpus/4cb5ff0e26f5bd0bb1fca846b4843db69095f18b
new file mode 100644
index 0000000..db5373d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4cb5ff0e26f5bd0bb1fca846b4843db69095f18b
@@ -0,0 +1 @@
+S€gT®kˆ®†à„T³ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/4ce80549344cf5b0432e39e7b5382407f0763e8c b/webm_parser/fuzzing/corpus/4ce80549344cf5b0432e39e7b5382407f0763e8c
new file mode 100644
index 0000000..742ac60
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4ce80549344cf5b0432e39e7b5382407f0763e8c
@@ -0,0 +1 @@
+S€®ŽT”gk®m€Šb@‡P5„Gá \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/4d10ae0032f04fa01148a54fae6838c44e4bb5bd b/webm_parser/fuzzing/corpus/4d10ae0032f04fa01148a54fae6838c44e4bb5bd
new file mode 100644
index 0000000..97f34c6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4d10ae0032f04fa01148a54fae6838c44e4bb5bd
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶sÄV…@B€@…@C¶@sĶ@sä¶@sĶ@ sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/4db2ded939a16fb1907dc2bad7acf67a64e38c0c b/webm_parser/fuzzing/corpus/4db2ded939a16fb1907dc2bad7acf67a64e38c0c
new file mode 100644
index 0000000..36747bd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4db2ded939a16fb1907dc2bad7acf67a64e38c0c
@@ -0,0 +1 @@
+S€gˆC¶uƒç \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/4dc3a9a9477fb452ba7a4026bbc01d4cdd3d4990 b/webm_parser/fuzzing/corpus/4dc3a9a9477fb452ba7a4026bbc01d4cdd3d4990
new file mode 100644
index 0000000..6422ec9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4dc3a9a9477fb452ba7a4026bbc01d4cdd3d4990
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/4e1fb006a2268fa5fc70ca6b241bbc71faa6d7ff b/webm_parser/fuzzing/corpus/4e1fb006a2268fa5fc70ca6b241bbc71faa6d7ff
new file mode 100644
index 0000000..1469145
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4e1fb006a2268fa5fc70ca6b241bbc71faa6d7ff
@@ -0,0 +1 @@
+S€g‚À \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/4e871bbc5089f9550533641ddf6850f8722dfc36 b/webm_parser/fuzzing/corpus/4e871bbc5089f9550533641ddf6850f8722dfc36
new file mode 100644
index 0000000..5967f9e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4e871bbc5089f9550533641ddf6850f8722dfc36
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/4eeb4507868732d3705d1ab10f6a31f70f579556 b/webm_parser/fuzzing/corpus/4eeb4507868732d3705d1ab10f6a31f70f579556
new file mode 100644
index 0000000..652484d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4eeb4507868732d3705d1ab10f6a31f70f579556
@@ -0,0 +1 @@
+S€g“C¶uŽ ŒŽŠèƒÌèƒÌS€gC¶uˆ †Ž„è‚΀ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/4f21f3584c99e97453ebe513ed44c86530fbebba b/webm_parser/fuzzing/corpus/4f21f3584c99e97453ebe513ed44c86530fbebba
new file mode 100644
index 0000000..b37544d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/4f21f3584c99e97453ebe513ed44c86530fbebba
@@ -0,0 +1 @@
+S€g¼C§p‹E¹ˆ¶†€„C|›C§p‹E¹ˆ¶†€„C|! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5103160695cd56d281faa45d9267013488cc7bde b/webm_parser/fuzzing/corpus/5103160695cd56d281faa45d9267013488cc7bde
new file mode 100644
index 0000000..2a707f7
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5103160695cd56d281faa45d9267013488cc7bde
@@ -0,0 +1 @@
+S€gT®kŠ®ƒ×®ƒ× \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/521103054e395afd0f9612b47c71cfa0e62151b9 b/webm_parser/fuzzing/corpus/521103054e395afd0f9612b47c71cfa0e62151b9
new file mode 100644
index 0000000..5849044
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/521103054e395afd0f9612b47c71cfa0e62151b9
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/523511e33c8788ebb1b703d7c5ad12b5da03964e b/webm_parser/fuzzing/corpus/523511e33c8788ebb1b703d7c5ad12b5da03964e
new file mode 100644
index 0000000..99e7799
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/523511e33c8788ebb1b703d7c5ad12b5da03964e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/525aedb7462311393cd01939e4734d01db48535b b/webm_parser/fuzzing/corpus/525aedb7462311393cd01939e4734d01db48535b
new file mode 100644
index 0000000..a68bdd6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/525aedb7462311393cd01939e4734d01db48535b
@@ -0,0 +1 @@
+V»UT³ÿM \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/53809012a8d5cfee839c3d0961b7fdc9468329a7 b/webm_parser/fuzzing/corpus/53809012a8d5cfee839c3d0961b7fdc9468329a7
new file mode 100644
index 0000000..50eef72
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/53809012a8d5cfee839c3d0961b7fdc9468329a7
@@ -0,0 +1 @@
+S€g»TÃg¶ss³È©È„#‡dgÈ„]‡egÈ„D‡dgÈ„]‡egÈ„D‡gÈ€D‡dgÈ€g \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/53c01d820d125e57b28410ca5b3a9c72c18d4102 b/webm_parser/fuzzing/corpus/53c01d820d125e57b28410ca5b3a9c72c18d4102
new file mode 100644
index 0000000..8f78c01
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/53c01d820d125e57b28410ca5b3a9c72c18d4102
@@ -0,0 +1 @@
+S€g‘C¶uŒ Šûˆþܺ˜vT2 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/53c27071f53df6b8d2e5c419edf95b7792b278e7 b/webm_parser/fuzzing/corpus/53c27071f53df6b8d2e5c419edf95b7792b278e7
new file mode 100644
index 0000000..87d69ed
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/53c27071f53df6b8d2e5c419edf95b7792b278e7
@@ -0,0 +1 @@
+€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶:€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶„¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€wÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶¶€¶ÿ¶€m¶ÿ¶€¶ÿ¶¶®®Ý€¶ÿ¶€¶ÿ¶€¶€µ¶€¶Œÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€å¶€¶€¶ÿ¶€Eߣ¶€¶À¶ÿ¶€m¶ÿ¶€¶ÿ¶¶®®Ý€¶ÿ¶€¶ÿ¶€¶€µ¶€¶Œÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/53d5307f2e7852b183839fe93c20647004d151ec b/webm_parser/fuzzing/corpus/53d5307f2e7852b183839fe93c20647004d151ec
new file mode 100644
index 0000000..acecedb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/53d5307f2e7852b183839fe93c20647004d151ec
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/5466c59c9c0eff34800e19cc92faf07469865ea9 b/webm_parser/fuzzing/corpus/5466c59c9c0eff34800e19cc92faf07469865ea9
new file mode 100644
index 0000000..2b86b64
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5466c59c9c0eff34800e19cc92faf07469865ea9
@@ -0,0 +1 @@
+VT¢€€ˆƒ€€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/54bf8d6f1f846243b6f4de81605779b0acb78406 b/webm_parser/fuzzing/corpus/54bf8d6f1f846243b6f4de81605779b0acb78406
new file mode 100644
index 0000000..9b4a32e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/54bf8d6f1f846243b6f4de81605779b0acb78406
@@ -0,0 +1 @@
+€€€€€€€€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/54fd1711209fb1c0781092374132c66e79e2241b b/webm_parser/fuzzing/corpus/54fd1711209fb1c0781092374132c66e79e2241b
new file mode 100644
index 0000000..7937c68
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/54fd1711209fb1c0781092374132c66e79e2241b
@@ -0,0 +1 @@
+g \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5506797514c0b214f5faa9764a9e149cd1df9d28 b/webm_parser/fuzzing/corpus/5506797514c0b214f5faa9764a9e149cd1df9d28
new file mode 100644
index 0000000..bfc5004
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5506797514c0b214f5faa9764a9e149cd1df9d28
@@ -0,0 +1 @@
+S€gŒT®k‡®…׃ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5566c6d599866f5e523c54e6575d7b9e557a2539 b/webm_parser/fuzzing/corpus/5566c6d599866f5e523c54e6575d7b9e557a2539
new file mode 100644
index 0000000..8578677
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5566c6d599866f5e523c54e6575d7b9e557a2539
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/55c3d55780427eb00bde252348db1c2ede22526c b/webm_parser/fuzzing/corpus/55c3d55780427eb00bde252348db1c2ede22526c
new file mode 100644
index 0000000..25c7cc7
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/55c3d55780427eb00bde252348db1c2ede22526c
@@ -0,0 +1 @@
+€
diff --git a/webm_parser/fuzzing/corpus/5600abaefe804c50a353bbfdd0f9e10ffc102f81 b/webm_parser/fuzzing/corpus/5600abaefe804c50a353bbfdd0f9e10ffc102f81
new file mode 100644
index 0000000..2b5e073
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5600abaefe804c50a353bbfdd0f9e10ffc102f81
@@ -0,0 +1 @@
+S¸S€g‘TkŒ®Šm€‡b@„P1S€g”T®k®m€Šb@€Ð5„Gáÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5611ddd40488973ee6c64c45ad57d6eefe6da100 b/webm_parser/fuzzing/corpus/5611ddd40488973ee6c64c45ad57d6eefe6da100
new file mode 100644
index 0000000..34d12c7
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5611ddd40488973ee6c64c45ad57d6eefe6da100
@@ -0,0 +1 @@
+S€gŠI©f…*×±ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/56b0ddd4e44492b45ebb7e9438b0a5556c244bfb b/webm_parser/fuzzing/corpus/56b0ddd4e44492b45ebb7e9438b0a5556c244bfb
new file mode 100644
index 0000000..2da35a1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/56b0ddd4e44492b45ebb7e9438b0a5556c244bfb
@@ -0,0 +1 @@
+S€g‹S»k†»„·€·€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5729395b452092152b06fbb8976e9bf1fda06439 b/webm_parser/fuzzing/corpus/5729395b452092152b06fbb8976e9bf1fda06439
new file mode 100644
index 0000000..df3e05b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5729395b452092152b06fbb8976e9bf1fda06439
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/57cce6fac7c0992eb338e0e538d3e0ee3fe7f1cd b/webm_parser/fuzzing/corpus/57cce6fac7c0992eb338e0e538d3e0ee3fe7f1cd
new file mode 100644
index 0000000..ab69784
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/57cce6fac7c0992eb338e0e538d3e0ee3fe7f1cd
@@ -0,0 +1 @@
+ñEߣˆB‚@webmS€g£*M'›t€I©f€C¶u€ÔSkk»€®€C§p€TÃg€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5822672f068da89546838004ae59552e12e132f7 b/webm_parser/fuzzing/corpus/5822672f068da89546838004ae59552e12e132f7
new file mode 100644
index 0000000..b5b45fe
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5822672f068da89546838004ae59552e12e132f7
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/59ce13e9a22e88dac879841256ff66f02cd2f8cc b/webm_parser/fuzzing/corpus/59ce13e9a22e88dac879841256ff66f02cd2f8cc
new file mode 100644
index 0000000..628eda0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/59ce13e9a22e88dac879841256ff66f02cd2f8cc
@@ -0,0 +1 @@
+S€gC¶u‹ ‰u¡†¦„€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5a020527fc80b0de64e279fc44635687e014a9cf b/webm_parser/fuzzing/corpus/5a020527fc80b0de64e279fc44635687e014a9cf
new file mode 100644
index 0000000..50c33bd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5a020527fc80b0de64e279fc44635687e014a9cf
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/5a645b30948850f8efb7bdd11944bdf3112c7bfe b/webm_parser/fuzzing/corpus/5a645b30948850f8efb7bdd11944bdf3112c7bfe
new file mode 100644
index 0000000..2d85ba9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5a645b30948850f8efb7bdd11944bdf3112c7bfe
@@ -0,0 +1 @@
+S€gTÃgŠss‡cÀ„cÅÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5b2f727461cde3c02ffdfb300adb0b4a09734911 b/webm_parser/fuzzing/corpus/5b2f727461cde3c02ffdfb300adb0b4a09734911
new file mode 100644
index 0000000..7416715
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5b2f727461cde3c02ffdfb300adb0b4a09734911
@@ -0,0 +1 @@
+S€gT®kŠ®ˆà†S¸ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5b5531d859ab5bbb034345bbaa2852e9d8a1e1ef b/webm_parser/fuzzing/corpus/5b5531d859ab5bbb034345bbaa2852e9d8a1e1ef
new file mode 100644
index 0000000..5b800b8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5b5531d859ab5bbb034345bbaa2852e9d8a1e1ef
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šm€‡b@„P1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5b980cb94e35769ec92a140ca00d34977e7da5ec b/webm_parser/fuzzing/corpus/5b980cb94e35769ec92a140ca00d34977e7da5ec
new file mode 100644
index 0000000..180ef6b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5b980cb94e35769ec92a140ca00d34977e7da5ec
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/5bfec2fe795176646fa0babecf97c04045bd4814 b/webm_parser/fuzzing/corpus/5bfec2fe795176646fa0babecf97c04045bd4814
new file mode 100644
index 0000000..9e406d3
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5bfec2fe795176646fa0babecf97c04045bd4814
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/5c28d94242e9a5ba7e7324e59e5dd12f3e31e51c b/webm_parser/fuzzing/corpus/5c28d94242e9a5ba7e7324e59e5dd12f3e31e51c
new file mode 100644
index 0000000..68b4ebc
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5c28d94242e9a5ba7e7324e59e5dd12f3e31e51c
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/5c67728ab04ba3dfe4b2636cf609f9617c4a5974 b/webm_parser/fuzzing/corpus/5c67728ab04ba3dfe4b2636cf609f9617c4a5974
new file mode 100644
index 0000000..f14a6f6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5c67728ab04ba3dfe4b2636cf609f9617c4a5974
@@ -0,0 +1 @@
+S€g”T®k®m€Šb@‡P0„Gâ!S€gŠT®k…®ƒ¹ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5cd36053e269c97f5359b0a075d80bf1e23b5f47 b/webm_parser/fuzzing/corpus/5cd36053e269c97f5359b0a075d80bf1e23b5f47
new file mode 100644
index 0000000..985bba4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5cd36053e269c97f5359b0a075d80bf1e23b5f47
@@ -0,0 +1 @@
+S€gŒC¶u‡ …u¡‚¦€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/5d6f99c2afbf68a1b15a17e07945c11db8579e5f b/webm_parser/fuzzing/corpus/5d6f99c2afbf68a1b15a17e07945c11db8579e5f
new file mode 100644
index 0000000..85a0c93
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5d6f99c2afbf68a1b15a17e07945c11db8579e5f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/5dd149b1b8509f456c16c4ac6dac3c7d6aa217ed b/webm_parser/fuzzing/corpus/5dd149b1b8509f456c16c4ac6dac3c7d6aa217ed
new file mode 100644
index 0000000..102660d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5dd149b1b8509f456c16c4ac6dac3c7d6aa217ed
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/5e84011c6b092e9f0d6ba60f12fc0fae95a622ca b/webm_parser/fuzzing/corpus/5e84011c6b092e9f0d6ba60f12fc0fae95a622ca
new file mode 100644
index 0000000..d4df4df
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5e84011c6b092e9f0d6ba60f12fc0fae95a622ca
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/5f56766993de2b8c2db87116090af546e1add3b0 b/webm_parser/fuzzing/corpus/5f56766993de2b8c2db87116090af546e1add3b0
new file mode 100644
index 0000000..c90364d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/5f56766993de2b8c2db87116090af546e1add3b0
@@ -0,0 +1 @@
+S€gŽS»k‰»‡·…÷ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/60571c8be9bee3b13428613df66ff36cf54309ec b/webm_parser/fuzzing/corpus/60571c8be9bee3b13428613df66ff36cf54309ec
new file mode 100644
index 0000000..bfd35dc
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/60571c8be9bee3b13428613df66ff36cf54309ec
@@ -0,0 +1 @@
+S€g”TÃgssŒcÀ‰hÊ€cÊ€cÅ€S€gTÃgŠss‡cÀ„cÊ! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/60ac245f262ee1ff177d351a4fe246060fb4538b b/webm_parser/fuzzing/corpus/60ac245f262ee1ff177d351a4fe246060fb4538b
new file mode 100644
index 0000000..21219e9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/60ac245f262ee1ff177d351a4fe246060fb4538b
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/6120aafb0db1715400e47d0a61fb9f6d8665a736 b/webm_parser/fuzzing/corpus/6120aafb0db1715400e47d0a61fb9f6d8665a736
new file mode 100644
index 0000000..2fc4e39
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6120aafb0db1715400e47d0a61fb9f6d8665a736
@@ -0,0 +1 @@
+S€gŠC¶u…çƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/61953a8f7a9f0613020d297a6d350abeeaf6e58c b/webm_parser/fuzzing/corpus/61953a8f7a9f0613020d297a6d350abeeaf6e58c
new file mode 100644
index 0000000..478128c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/61953a8f7a9f0613020d297a6d350abeeaf6e58c
@@ -0,0 +1 @@
+S€g“C¶uŽ ŒŽŠèƒÌèƒÌ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/61c0a7fef46c0c1b081b1c2b40835805e5989b54 b/webm_parser/fuzzing/corpus/61c0a7fef46c0c1b081b1c2b40835805e5989b54
new file mode 100644
index 0000000..b5ac3f6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/61c0a7fef46c0c1b081b1c2b40835805e5989b54
@@ -0,0 +1 @@
+ä \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/620d85870206e88db0bc3ee978c24c0bbc1821ba b/webm_parser/fuzzing/corpus/620d85870206e88db0bc3ee978c24c0bbc1821ba
new file mode 100644
index 0000000..4749f9c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/620d85870206e88db0bc3ee978c24c0bbc1821ba
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/621131d8e28a67a6b4fb03c611f4375873d69678 b/webm_parser/fuzzing/corpus/621131d8e28a67a6b4fb03c611f4375873d69678
new file mode 100644
index 0000000..d037403
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/621131d8e28a67a6b4fb03c611f4375873d69678
@@ -0,0 +1 @@
+S€gŽC¶u‰ ‡Ž…èƒÌ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/624cbd42e3ebf35a3a0ba55b3b4969b99054f84d b/webm_parser/fuzzing/corpus/624cbd42e3ebf35a3a0ba55b3b4969b99054f84d
new file mode 100644
index 0000000..0b518b9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/624cbd42e3ebf35a3a0ba55b3b4969b99054f84d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/6290103200631075988aa3566d8d8922599f8b6d b/webm_parser/fuzzing/corpus/6290103200631075988aa3566d8d8922599f8b6d
new file mode 100644
index 0000000..52f240d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6290103200631075988aa3566d8d8922599f8b6d
@@ -0,0 +1 @@
+€ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/62a329f65143f1571dc4a2fbb256800c8ce4d89f b/webm_parser/fuzzing/corpus/62a329f65143f1571dc4a2fbb256800c8ce4d89f
new file mode 100644
index 0000000..eca1ba9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/62a329f65143f1571dc4a2fbb256800c8ce4d89f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/62f8a53cfb4dd625dcb31ee2ba258db3916a3bd0 b/webm_parser/fuzzing/corpus/62f8a53cfb4dd625dcb31ee2ba258db3916a3bd0
new file mode 100644
index 0000000..a3a36dc
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/62f8a53cfb4dd625dcb31ee2ba258db3916a3bd0
@@ -0,0 +1 @@
+S€gTÃg˜ss•gÈ’E£€gÈ€D„€D‡€D…€gÈ€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6318dc574b3ad7291fee7e01bfeaa674eb0feb48 b/webm_parser/fuzzing/corpus/6318dc574b3ad7291fee7e01bfeaa674eb0feb48
new file mode 100644
index 0000000..baa7b64
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6318dc574b3ad7291fee7e01bfeaa674eb0feb48
@@ -0,0 +1 @@
+S€g˜T®k“®‘m€Žb@„P1b@„P1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6457a079debb9b532255e3e6ddf276b29808b49d b/webm_parser/fuzzing/corpus/6457a079debb9b532255e3e6ddf276b29808b49d
new file mode 100644
index 0000000..640d4a1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6457a079debb9b532255e3e6ddf276b29808b49d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/646061a4526506a0b3c62629f4f77b6072c8f9c3 b/webm_parser/fuzzing/corpus/646061a4526506a0b3c62629f4f77b6072c8f9c3
new file mode 100644
index 0000000..beb9e5b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/646061a4526506a0b3c62629f4f77b6072c8f9c3
@@ -0,0 +1 @@
+EߣžB†‡ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6538d0517865c8832ee9bec87609b7bd4facb025 b/webm_parser/fuzzing/corpus/6538d0517865c8832ee9bec87609b7bd4facb025
new file mode 100644
index 0000000..7230e70
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6538d0517865c8832ee9bec87609b7bd4facb025
@@ -0,0 +1 @@
+€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ„€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶¶¶ÿÿ€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶Š¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI
diff --git a/webm_parser/fuzzing/corpus/653de28e2f085334e30ec1c964540b432a3cfbc6 b/webm_parser/fuzzing/corpus/653de28e2f085334e30ec1c964540b432a3cfbc6
new file mode 100644
index 0000000..5748705
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/653de28e2f085334e30ec1c964540b432a3cfbc6
@@ -0,0 +1 @@
+S€gC§p‹E¹ˆ¶†€„C|!S€gŽC§p‰E¹†¶„VT \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/657c9c42bf3e1c79a6dcb9071bf123367dc37734 b/webm_parser/fuzzing/corpus/657c9c42bf3e1c79a6dcb9071bf123367dc37734
new file mode 100644
index 0000000..8638bce
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/657c9c42bf3e1c79a6dcb9071bf123367dc37734
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/65ebba0df08a5484686905e99631a4ead845c08b b/webm_parser/fuzzing/corpus/65ebba0df08a5484686905e99631a4ead845c08b
new file mode 100644
index 0000000..e04df80
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/65ebba0df08a5484686905e99631a4ead845c08b
@@ -0,0 +1 @@
+S€gŽM›t‰M»†S¬ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/66dd27c22fe09842ac4c4f51fdef4bea500b6a70 b/webm_parser/fuzzing/corpus/66dd27c22fe09842ac4c4f51fdef4bea500b6a70
new file mode 100644
index 0000000..2e5c016
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/66dd27c22fe09842ac4c4f51fdef4bea500b6a70
@@ -0,0 +1 @@
+EߣÿB‚è \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/66e25df2bd43c840337a49e0cb5b386e9d290d80 b/webm_parser/fuzzing/corpus/66e25df2bd43c840337a49e0cb5b386e9d290d80
new file mode 100644
index 0000000..88bbf1f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/66e25df2bd43c840337a49e0cb5b386e9d290d80
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/6714df1ac1c54d4c30add212454e9513c6880767 b/webm_parser/fuzzing/corpus/6714df1ac1c54d4c30add212454e9513c6880767
new file mode 100644
index 0000000..20b7616
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6714df1ac1c54d4c30add212454e9513c6880767
@@ -0,0 +1 @@
+S€gŒT®k‡®…àƒ° \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/68a6f9c2f62e9f57667de9563b5d612ec2f2c829 b/webm_parser/fuzzing/corpus/68a6f9c2f62e9f57667de9563b5d612ec2f2c829
new file mode 100644
index 0000000..d8309ea
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/68a6f9c2f62e9f57667de9563b5d612ec2f2c829
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/69089b76cc62fe00615702bdd1f339e259194d42 b/webm_parser/fuzzing/corpus/69089b76cc62fe00615702bdd1f339e259194d42
new file mode 100644
index 0000000..f925002
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/69089b76cc62fe00615702bdd1f339e259194d42
@@ -0,0 +1 @@
+S€gŽT®k‰®‡à…šƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/69d8622d24c7315f344c424297986170f7ef2243 b/webm_parser/fuzzing/corpus/69d8622d24c7315f344c424297986170f7ef2243
new file mode 100644
index 0000000..6182f76
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/69d8622d24c7315f344c424297986170f7ef2243
@@ -0,0 +1 @@
+S€g“C¶uŽ ŒŽŠèƒÌèƒÌS€gC¶uˆ †Ž„è‚Ì€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6a700d958cb3a7565d5a6b74540486874d52b4c7 b/webm_parser/fuzzing/corpus/6a700d958cb3a7565d5a6b74540486874d52b4c7
new file mode 100644
index 0000000..3c4a4a4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6a700d958cb3a7565d5a6b74540486874d52b4c7
@@ -0,0 +1 @@
+S€g¼C§p·E¹´¶²€ …@helloC|…lang0C~…area0C|…lang€…S|1C–T®k‘®m€Œb@‰P2€ÿ†G*lang2C~ƒGè…€area1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6a852150c5c2a12d325e78056687a8ea97b25fe7 b/webm_parser/fuzzing/corpus/6a852150c5c2a12d325e78056687a8ea97b25fe7
new file mode 100644
index 0000000..ca14558
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6a852150c5c2a12d325e78056687a8ea97b25fe7
@@ -0,0 +1 @@
+S€g‡T®k‚®€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6ac8c9f754d5ff3c0cd3f3f585de99dd73944bbe b/webm_parser/fuzzing/corpus/6ac8c9f754d5ff3c0cd3f3f585de99dd73944bbe
new file mode 100644
index 0000000..a7d88f0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6ac8c9f754d5ff3c0cd3f3f585de99dd73944bbe
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/6ace27a48f12990e2169cc099007aa5fa11962d2 b/webm_parser/fuzzing/corpus/6ace27a48f12990e2169cc099007aa5fa11962d2
new file mode 100644
index 0000000..f544930
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6ace27a48f12990e2169cc099007aa5fa11962d2
@@ -0,0 +1 @@
+S€g™T®k”®’m€b@ŒT2€P2€P3P5€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6b10397dc3bff2ccf57be4fe3ff9d8d1acb8c76e b/webm_parser/fuzzing/corpus/6b10397dc3bff2ccf57be4fe3ff9d8d1acb8c76e
new file mode 100644
index 0000000..db9ed48
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6b10397dc3bff2ccf57be4fe3ff9d8d1acb8c76e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/6c3f1d96c06b853551f685dde821142928f55acf b/webm_parser/fuzzing/corpus/6c3f1d96c06b853551f685dde821142928f55acf
new file mode 100644
index 0000000..91f4e81
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6c3f1d96c06b853551f685dde821142928f55acf
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/6c8de4d4bbb5294154c3b140d43836757b9f08ac b/webm_parser/fuzzing/corpus/6c8de4d4bbb5294154c3b140d43836757b9f08ac
new file mode 100644
index 0000000..8aefea2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6c8de4d4bbb5294154c3b140d43836757b9f08ac
@@ -0,0 +1 @@
+S€g‰I©f„{©! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6cf122ee328af5cf6f18d5f9546b0fe761951b8d b/webm_parser/fuzzing/corpus/6cf122ee328af5cf6f18d5f9546b0fe761951b8d
new file mode 100644
index 0000000..5634639
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6cf122ee328af5cf6f18d5f9546b0fe761951b8d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/6cfbbc25caa217e22fd7ca3e92d2051c5f661bdb b/webm_parser/fuzzing/corpus/6cfbbc25caa217e22fd7ca3e92d2051c5f661bdb
new file mode 100644
index 0000000..bc69d35
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6cfbbc25caa217e22fd7ca3e92d2051c5f661bdb
@@ -0,0 +1 @@
+S€gC¶uˆ †Ž„è‚Ì€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6d703bc900e74d1a576bebcccb63e3b2701ae86d b/webm_parser/fuzzing/corpus/6d703bc900e74d1a576bebcccb63e3b2701ae86d
new file mode 100644
index 0000000..e0bdcf2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6d703bc900e74d1a576bebcccb63e3b2701ae86d
@@ -0,0 +1 @@
+S€g˜T®k“®‘m€Žb@„P1b@€b@€P1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6e1afa81dfcd6833705d84b45881f085c19b2211 b/webm_parser/fuzzing/corpus/6e1afa81dfcd6833705d84b45881f085c19b2211
new file mode 100644
index 0000000..670dd7c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6e1afa81dfcd6833705d84b45881f085c19b2211
@@ -0,0 +1 @@
+S€gC¶uŠ ˆu¡…¦ƒ¥! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6e244ec3b81e0d2f8b5810854859e1b8140422d9 b/webm_parser/fuzzing/corpus/6e244ec3b81e0d2f8b5810854859e1b8140422d9
new file mode 100644
index 0000000..2e7c1a4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6e244ec3b81e0d2f8b5810854859e1b8140422d9
@@ -0,0 +1 @@
+S€gûû \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6e323b4c733df90d55c754e099fb5a13bd9701cb b/webm_parser/fuzzing/corpus/6e323b4c733df90d55c754e099fb5a13bd9701cb
new file mode 100644
index 0000000..89e0e79
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6e323b4c733df90d55c754e099fb5a13bd9701cb
@@ -0,0 +1 @@
+S€gT®kŠ®ˆà†T°ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6e36720f3f55dc0cd5d585c6770572964719b5b8 b/webm_parser/fuzzing/corpus/6e36720f3f55dc0cd5d585c6770572964719b5b8
new file mode 100644
index 0000000..376c75b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6e36720f3f55dc0cd5d585c6770572964719b5b8
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/6e8384ab115ac92bb7787121cfb03582e2d72585 b/webm_parser/fuzzing/corpus/6e8384ab115ac92bb7787121cfb03582e2d72585
new file mode 100644
index 0000000..7f90100
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6e8384ab115ac92bb7787121cfb03582e2d72585
@@ -0,0 +1 @@
+S€g¼C§p·E¹´¶ž|°…@hello€€€€€€€€€…@B€@…@C¶@ sÄ€€€€€€„€–€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6ebf8b74dca1657f5f7ffadeccd629984473dcb4 b/webm_parser/fuzzing/corpus/6ebf8b74dca1657f5f7ffadeccd629984473dcb4
new file mode 100644
index 0000000..0bf3c1b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6ebf8b74dca1657f5f7ffadeccd629984473dcb4
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/6ec18dcca0d820fff04c03c3a1bce37dd8ada41d b/webm_parser/fuzzing/corpus/6ec18dcca0d820fff04c03c3a1bce37dd8ada41d
new file mode 100644
index 0000000..c10fff0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6ec18dcca0d820fff04c03c3a1bce37dd8ada41d
@@ -0,0 +1 @@
+S€gT®k‹®‰á‡xµ„@ÉÛ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6eceebb8f46545393d217ab0111ff7dd29bc99f7 b/webm_parser/fuzzing/corpus/6eceebb8f46545393d217ab0111ff7dd29bc99f7
new file mode 100644
index 0000000..27952aa
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6eceebb8f46545393d217ab0111ff7dd29bc99f7
@@ -0,0 +1 @@
+S€g–T®k‘®m€Œb@‰P5€†GçƒGè€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6ee071f654fd6b7b25725a8bdd880bb6836e8c81 b/webm_parser/fuzzing/corpus/6ee071f654fd6b7b25725a8bdd880bb6836e8c81
new file mode 100644
index 0000000..fc5bea2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6ee071f654fd6b7b25725a8bdd880bb6836e8c81
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/6f1088b3d1f4cdfbc37d9b8cfc1ff4c3bde2f205 b/webm_parser/fuzzing/corpus/6f1088b3d1f4cdfbc37d9b8cfc1ff4c3bde2f205
new file mode 100644
index 0000000..9936eea
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6f1088b3d1f4cdfbc37d9b8cfc1ff4c3bde2f205
@@ -0,0 +1 @@
+EߣˆB‚@webmS€g©C¶u¤ç@«@£…¾Eß# \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6f79e36e880664c9b3b610140d2485fb6a6e5039 b/webm_parser/fuzzing/corpus/6f79e36e880664c9b3b610140d2485fb6a6e5039
new file mode 100644
index 0000000..e646c0d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6f79e36e880664c9b3b610140d2485fb6a6e5039
@@ -0,0 +1 @@
+€g€g=C§p¸Ess¶³sÄVTA‘ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/6fbea59066b2b97fa45259aa87c40c4310070019 b/webm_parser/fuzzing/corpus/6fbea59066b2b97fa45259aa87c40c4310070019
new file mode 100644
index 0000000..6b33dfd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/6fbea59066b2b97fa45259aa87c40c4310070019
@@ -0,0 +1 @@
+EߣˆB‚@webmS€g©C¶u¤ç@!« \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/7056b9c294f0cff7b4ace611a19363b2fc096bb3 b/webm_parser/fuzzing/corpus/7056b9c294f0cff7b4ace611a19363b2fc096bb3
new file mode 100644
index 0000000..b0e999e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7056b9c294f0cff7b4ace611a19363b2fc096bb3
@@ -0,0 +1 @@
+!EߣˆB‚@webmS€g£M'›t)€I©f€OC¶u€gT®k€S»k€C§p€TÃg€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/7073dca40b911fb22f738c99aff43a10ae6c4db5 b/webm_parser/fuzzing/corpus/7073dca40b911fb22f738c99aff43a10ae6c4db5
new file mode 100644
index 0000000..383c77a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7073dca40b911fb22f738c99aff43a10ae6c4db5
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/7164d8ca3f4c3d621cc5eed7b59e3495e7b017a0 b/webm_parser/fuzzing/corpus/7164d8ca3f4c3d621cc5eed7b59e3495e7b017a0
new file mode 100644
index 0000000..7d8a5d1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7164d8ca3f4c3d621cc5eed7b59e3495e7b017a0
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/71e3e01b3cfcd4228402cbf19b5282c965f042df b/webm_parser/fuzzing/corpus/71e3e01b3cfcd4228402cbf19b5282c965f042df
new file mode 100644
index 0000000..72cc1dc
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/71e3e01b3cfcd4228402cbf19b5282c965f042df
@@ -0,0 +1 @@
+ìÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/720d36ea7f9616b1a1d47fa5b746e8657b5f973f b/webm_parser/fuzzing/corpus/720d36ea7f9616b1a1d47fa5b746e8657b5f973f
new file mode 100644
index 0000000..6de803d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/720d36ea7f9616b1a1d47fa5b746e8657b5f973f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/72ccbcbc53c7fa54247a81bd1d0e62dd0779494b b/webm_parser/fuzzing/corpus/72ccbcbc53c7fa54247a81bd1d0e62dd0779494b
new file mode 100644
index 0000000..20c7fa6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/72ccbcbc53c7fa54247a81bd1d0e62dd0779494b
@@ -0,0 +1 @@
+!EߣˆB‚@webmS€g£M'›t€I©f€C¶u€T®k€S»k€C§p€TÃg€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/736c0460f94c089c9e270857a4222a21ffd9e13f b/webm_parser/fuzzing/corpus/736c0460f94c089c9e270857a4222a21ffd9e13f
new file mode 100644
index 0000000..14a490e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/736c0460f94c089c9e270857a4222a21ffd9e13f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/745e32f46833c075c8e68552e0fbbcdd5ec52641 b/webm_parser/fuzzing/corpus/745e32f46833c075c8e68552e0fbbcdd5ec52641
new file mode 100644
index 0000000..518d182
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/745e32f46833c075c8e68552e0fbbcdd5ec52641
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/74aa7256477af2c0e2511df16376dee323f3ccb6 b/webm_parser/fuzzing/corpus/74aa7256477af2c0e2511df16376dee323f3ccb6
new file mode 100644
index 0000000..46cfdb1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/74aa7256477af2c0e2511df16376dee323f3ccb6
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/7528086eea404dccc3ee5b214befbc2a95676feb b/webm_parser/fuzzing/corpus/7528086eea404dccc3ee5b214befbc2a95676feb
new file mode 100644
index 0000000..40b7db2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7528086eea404dccc3ee5b214befbc2a95676feb
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šàˆ#ƒã„ÉÛ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/7528f9f08f80c8e85951aa1db2dc616e992c1a62 b/webm_parser/fuzzing/corpus/7528f9f08f80c8e85951aa1db2dc616e992c1a62
new file mode 100644
index 0000000..87b29be
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7528f9f08f80c8e85951aa1db2dc616e992c1a62
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/75af94f9695c375e5eb620849f0213ad41e8f205 b/webm_parser/fuzzing/corpus/75af94f9695c375e5eb620849f0213ad41e8f205
new file mode 100644
index 0000000..dcf5dc6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/75af94f9695c375e5eb620849f0213ad41e8f205
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/75e1ff2e016b7b80ecb7a6a1f4c3238a2a2ed130 b/webm_parser/fuzzing/corpus/75e1ff2e016b7b80ecb7a6a1f4c3238a2a2ed130
new file mode 100644
index 0000000..9dcbbb5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/75e1ff2e016b7b80ecb7a6a1f4c3238a2a2ed130
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/76129c3bdedafb3bda93f53266bd17bffdc30682 b/webm_parser/fuzzing/corpus/76129c3bdedafb3bda93f53266bd17bffdc30682
new file mode 100644
index 0000000..bb8e055
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/76129c3bdedafb3bda93f53266bd17bffdc30682
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/7670db89e0c3fe492c91458de219c3be34a3dcb7 b/webm_parser/fuzzing/corpus/7670db89e0c3fe492c91458de219c3be34a3dcb7
new file mode 100644
index 0000000..c57ba43
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7670db89e0c3fe492c91458de219c3be34a3dcb7
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/7673d7fdf630637c6ae2b96694a047044c1a9cb0 b/webm_parser/fuzzing/corpus/7673d7fdf630637c6ae2b96694a047044c1a9cb0
new file mode 100644
index 0000000..b0955cb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7673d7fdf630637c6ae2b96694a047044c1a9cb0
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/76903905e654ddc59acf57380bf3dc1ea136bad9 b/webm_parser/fuzzing/corpus/76903905e654ddc59acf57380bf3dc1ea136bad9
new file mode 100644
index 0000000..f652c35
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/76903905e654ddc59acf57380bf3dc1ea136bad9
@@ -0,0 +1 @@
+S€gT®kˆ®†à„T²ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/76d36e3cf3d31e0f76af08bd7ba5571f679057a1 b/webm_parser/fuzzing/corpus/76d36e3cf3d31e0f76af08bd7ba5571f679057a1
new file mode 100644
index 0000000..3f6abf8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/76d36e3cf3d31e0f76af08bd7ba5571f679057a1
@@ -0,0 +1 @@
+S€gŠC¶u… ƒû \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/775ce646f7dbf50199b8e8df85c9441b8a0a5447 b/webm_parser/fuzzing/corpus/775ce646f7dbf50199b8e8df85c9441b8a0a5447
new file mode 100644
index 0000000..3d451af
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/775ce646f7dbf50199b8e8df85c9441b8a0a5447
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/7770a6fd33ce821e60c78b0cb67a7e8c8bb74269 b/webm_parser/fuzzing/corpus/7770a6fd33ce821e60c78b0cb67a7e8c8bb74269
new file mode 100644
index 0000000..e25e77c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7770a6fd33ce821e60c78b0cb67a7e8c8bb74269
@@ -0,0 +1 @@
+S€g”C¶u u¡Š¦ƒî¦ƒîS€gC¶uŠ ˆu¡…¦ƒ¥! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/781b24e3433ba400df3ccddf56cb8fc7b1fd52ce b/webm_parser/fuzzing/corpus/781b24e3433ba400df3ccddf56cb8fc7b1fd52ce
new file mode 100644
index 0000000..536c504
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/781b24e3433ba400df3ccddf56cb8fc7b1fd52ce
@@ -0,0 +1 @@
+S€gŠI©f…*×± \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/784db9d87b31ffb040ad212e4018b30c3535ad18 b/webm_parser/fuzzing/corpus/784db9d87b31ffb040ad212e4018b30c3535ad18
new file mode 100644
index 0000000..5586a9e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/784db9d87b31ffb040ad212e4018b30c3535ad18
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/7880e2ae55d255065ad415c4c625e1b63bfd2a93 b/webm_parser/fuzzing/corpus/7880e2ae55d255065ad415c4c625e1b63bfd2a93
new file mode 100644
index 0000000..e2fb6ac
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7880e2ae55d255065ad415c4c625e1b63bfd2a93
@@ -0,0 +1 @@
+DzS€g–T®k‘®m€Œb@‰P5ÿ†GçƒGè€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/78cb3d726e4f9e8bc89b399fa514c3b600bf8e5a b/webm_parser/fuzzing/corpus/78cb3d726e4f9e8bc89b399fa514c3b600bf8e5a
new file mode 100644
index 0000000..fc42e54
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/78cb3d726e4f9e8bc89b399fa514c3b600bf8e5a
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/79ba60931988a5974328a73fe091bdf6f5992891 b/webm_parser/fuzzing/corpus/79ba60931988a5974328a73fe091bdf6f5992891
new file mode 100644
index 0000000..c6837fd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/79ba60931988a5974328a73fe091bdf6f5992891
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/79cdb8dec1ad07b389f544a511f89b347429837e b/webm_parser/fuzzing/corpus/79cdb8dec1ad07b389f544a511f89b347429837e
new file mode 100644
index 0000000..68e6b7d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/79cdb8dec1ad07b389f544a511f89b347429837e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/7ab8c6a65c39907f4afa4e2b294303aebb6b22d8 b/webm_parser/fuzzing/corpus/7ab8c6a65c39907f4afa4e2b294303aebb6b22d8
new file mode 100644
index 0000000..fddf510
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7ab8c6a65c39907f4afa4e2b294303aebb6b22d8
@@ -0,0 +1 @@
+°€€€€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/7b12a398a1860ac2f3930d5020e422aec061f177 b/webm_parser/fuzzing/corpus/7b12a398a1860ac2f3930d5020e422aec061f177
new file mode 100644
index 0000000..fed31c6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7b12a398a1860ac2f3930d5020e422aec061f177
@@ -0,0 +1 @@
+S€gŠT®k…®ƒ¹ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/7bf1682743405f3d5b3433830eadae4ea311807d b/webm_parser/fuzzing/corpus/7bf1682743405f3d5b3433830eadae4ea311807d
new file mode 100644
index 0000000..d8666eb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7bf1682743405f3d5b3433830eadae4ea311807d
@@ -0,0 +1 @@
+S€g»TÃg¶ss³gÈ©È„#‡dgÈ„]‡egÈ„D‡dgÈ„]‡egÈ„D‡gÈ„D‡dgÈg \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/7c7ef0e305f37f833708b375271d59300d120d84 b/webm_parser/fuzzing/corpus/7c7ef0e305f37f833708b375271d59300d120d84
new file mode 100644
index 0000000..c9cedc0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7c7ef0e305f37f833708b375271d59300d120d84
@@ -0,0 +1 @@
+S€gŽT®k‰®‡á…Ÿƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/7d06bd4a7de57953ef09c2e18ca67aa7ff367c76 b/webm_parser/fuzzing/corpus/7d06bd4a7de57953ef09c2e18ca67aa7ff367c76
new file mode 100644
index 0000000..6cce7cb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7d06bd4a7de57953ef09c2e18ca67aa7ff367c76
@@ -0,0 +1 @@
+S€gTÃgŠss‡gÈ„E£! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/7d31bd53bb7eb7b1f354c5f85d376390760ab16f b/webm_parser/fuzzing/corpus/7d31bd53bb7eb7b1f354c5f85d376390760ab16f
new file mode 100644
index 0000000..f356949
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7d31bd53bb7eb7b1f354c5f85d376390760ab16f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/7d67ef4d0c681655d4b4e93e848d56865630390b b/webm_parser/fuzzing/corpus/7d67ef4d0c681655d4b4e93e848d56865630390b
new file mode 100644
index 0000000..ca4a486
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7d67ef4d0c681655d4b4e93e848d56865630390b
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/7db9fe869081fdc855913dd000de0d493f5d5192 b/webm_parser/fuzzing/corpus/7db9fe869081fdc855913dd000de0d493f5d5192
new file mode 100644
index 0000000..9fa0f0a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7db9fe869081fdc855913dd000de0d493f5d5192
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/7f20c1e73451c3321c30223db91b891753ef77dd b/webm_parser/fuzzing/corpus/7f20c1e73451c3321c30223db91b891753ef77dd
new file mode 100644
index 0000000..7f0a6df
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7f20c1e73451c3321c30223db91b891753ef77dd
@@ -0,0 +1 @@
+S€g‰C¶u„ ‚¡€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/7fd4d999983c3fbf22763853e90ec10b03c70b17 b/webm_parser/fuzzing/corpus/7fd4d999983c3fbf22763853e90ec10b03c70b17
new file mode 100644
index 0000000..65e61e9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/7fd4d999983c3fbf22763853e90ec10b03c70b17
@@ -0,0 +1 @@
+S€g“M›tŽM»„S¬M»„S¬ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/80039ce7eff40359bee041d75e371b55117aad3a b/webm_parser/fuzzing/corpus/80039ce7eff40359bee041d75e371b55117aad3a
new file mode 100644
index 0000000..e62a685
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/80039ce7eff40359bee041d75e371b55117aad3a
@@ -0,0 +1 @@
+ƒ€€€€€ˆ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8104a007bc88a0a8d81ce1fd26d8b2333061e920 b/webm_parser/fuzzing/corpus/8104a007bc88a0a8d81ce1fd26d8b2333061e920
new file mode 100644
index 0000000..1e6fd37
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8104a007bc88a0a8d81ce1fd26d8b2333061e920
@@ -0,0 +1 @@
+S€gœC§p—E¹”¶’Ä€aT€}€€€€€€€€€€ƒèÌ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/81320c48ed6eea94712c5e8594c0799fbfe30d10 b/webm_parser/fuzzing/corpus/81320c48ed6eea94712c5e8594c0799fbfe30d10
new file mode 100644
index 0000000..28ac48f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/81320c48ed6eea94712c5e8594c0799fbfe30d10
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/81425ca3d0afb88cc10d412dcc9795905eacd6d9 b/webm_parser/fuzzing/corpus/81425ca3d0afb88cc10d412dcc9795905eacd6d9
new file mode 100644
index 0000000..3afdbfc
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/81425ca3d0afb88cc10d412dcc9795905eacd6d9
@@ -0,0 +1 @@
+E᜘ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/819b4bd08ae7d758990aea8ab9739f3cb97fd42c b/webm_parser/fuzzing/corpus/819b4bd08ae7d758990aea8ab9739f3cb97fd42c
new file mode 100644
index 0000000..af77557
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/819b4bd08ae7d758990aea8ab9739f3cb97fd42c
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/8206d92b710c04ce0bd706cec25fbb72014c79bf b/webm_parser/fuzzing/corpus/8206d92b710c04ce0bd706cec25fbb72014c79bf
new file mode 100644
index 0000000..a1233fd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8206d92b710c04ce0bd706cec25fbb72014c79bf
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/822926c528d16c0a9ca4c48a25651f6a0730d797 b/webm_parser/fuzzing/corpus/822926c528d16c0a9ca4c48a25651f6a0730d797
new file mode 100644
index 0000000..f4269d0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/822926c528d16c0a9ca4c48a25651f6a0730d797
@@ -0,0 +1 @@
+S€gŽC§p‰E¹†¶„VT!S€g–C§p‘E¹Ž¶@sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/82d80b6051f0750777bdc37319851741144b3671 b/webm_parser/fuzzing/corpus/82d80b6051f0750777bdc37319851741144b3671
new file mode 100644
index 0000000..43b170b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/82d80b6051f0750777bdc37319851741144b3671
@@ -0,0 +1 @@
+S€g‹C¶u† „Ž‚è€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/830c73748baaa10c2016d04b408a7f481882cd89 b/webm_parser/fuzzing/corpus/830c73748baaa10c2016d04b408a7f481882cd89
new file mode 100644
index 0000000..11b45e9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/830c73748baaa10c2016d04b408a7f481882cd89
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/8315ca46618bd0771353336407396df293e8c182 b/webm_parser/fuzzing/corpus/8315ca46618bd0771353336407396df293e8c182
new file mode 100644
index 0000000..4472080
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8315ca46618bd0771353336407396df293e8c182
@@ -0,0 +1 @@
+S€g“C¶uŽ ŒŽŠìƒÌèƒÌ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/831c152337e8b2993b8cddfde7553a8c9e64631f b/webm_parser/fuzzing/corpus/831c152337e8b2993b8cddfde7553a8c9e64631f
new file mode 100644
index 0000000..5387542
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/831c152337e8b2993b8cddfde7553a8c9e64631f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/832fad0e723027e5bd74726b1722a838183dada7 b/webm_parser/fuzzing/corpus/832fad0e723027e5bd74726b1722a838183dada7
new file mode 100644
index 0000000..3545ab8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/832fad0e723027e5bd74726b1722a838183dada7
@@ -0,0 +1 @@
+S€gTÃgŠss‡gÈ„D„ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/83b78d2ad8afd0729de45b4d9fdd765a949d8073 b/webm_parser/fuzzing/corpus/83b78d2ad8afd0729de45b4d9fdd765a949d8073
new file mode 100644
index 0000000..5cb98fe
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/83b78d2ad8afd0729de45b4d9fdd765a949d8073
@@ -0,0 +1 @@
+S€g¼C§p·E¹´¶²€ …@helloC|…lang0C~…area0C|…lang1C|C§p·E \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/84a04bf3e15345f5c992bc6732a42b95857631a7 b/webm_parser/fuzzing/corpus/84a04bf3e15345f5c992bc6732a42b95857631a7
new file mode 100644
index 0000000..e89d0d1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/84a04bf3e15345f5c992bc6732a42b95857631a7
@@ -0,0 +1 @@
+S€g“C¶uŽ ŒŽŠèƒÌèƒÌ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/84d3a72c434074ee0d810fb2357047efd23f58f6 b/webm_parser/fuzzing/corpus/84d3a72c434074ee0d810fb2357047efd23f58f6
new file mode 100644
index 0000000..e22f95b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/84d3a72c434074ee0d810fb2357047efd23f58f6
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/8659fe309298ca90f201ac7ced95b9d0de510eeb b/webm_parser/fuzzing/corpus/8659fe309298ca90f201ac7ced95b9d0de510eeb
new file mode 100644
index 0000000..77449c7
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8659fe309298ca90f201ac7ced95b9d0de510eeb
@@ -0,0 +1 @@
+S€gˆC¶uƒ« \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/866cf21064b9e20eded44e2e096fd9ce6185ce86 b/webm_parser/fuzzing/corpus/866cf21064b9e20eded44e2e096fd9ce6185ce86
new file mode 100644
index 0000000..f3a4ddd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/866cf21064b9e20eded44e2e096fd9ce6185ce86
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/8864c6c9319ef120d3accb7b70c32c8f5c1ebccb b/webm_parser/fuzzing/corpus/8864c6c9319ef120d3accb7b70c32c8f5c1ebccb
new file mode 100644
index 0000000..d6f7c00
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8864c6c9319ef120d3accb7b70c32c8f5c1ebccb
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/88f01cffe9972bf447a21034c45f6b943ea3ec07 b/webm_parser/fuzzing/corpus/88f01cffe9972bf447a21034c45f6b943ea3ec07
new file mode 100644
index 0000000..97754f4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/88f01cffe9972bf447a21034c45f6b943ea3ec07
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/88f15d430469d209b72cd098405efd15a6d18b05 b/webm_parser/fuzzing/corpus/88f15d430469d209b72cd098405efd15a6d18b05
new file mode 100644
index 0000000..adce379
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/88f15d430469d209b72cd098405efd15a6d18b05
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/8936b35f29084151fa3755d81030386940523c07 b/webm_parser/fuzzing/corpus/8936b35f29084151fa3755d81030386940523c07
new file mode 100644
index 0000000..e39670b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8936b35f29084151fa3755d81030386940523c07
@@ -0,0 +1 @@
+Eߣ„Bó \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/89a42d72ffe0b23309a9da4e15b8854c1cf939ea b/webm_parser/fuzzing/corpus/89a42d72ffe0b23309a9da4e15b8854c1cf939ea
new file mode 100644
index 0000000..af09ae1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/89a42d72ffe0b23309a9da4e15b8854c1cf939ea
@@ -0,0 +1 @@
+S€g‹S»k†»„³€·€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8a0cf07d2592231bd579e4399538d9f490e3e5b9 b/webm_parser/fuzzing/corpus/8a0cf07d2592231bd579e4399538d9f490e3e5b9
new file mode 100644
index 0000000..ce2a136
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8a0cf07d2592231bd579e4399538d9f490e3e5b9
@@ -0,0 +1 @@
+S€gŒT®k‡®…ჟ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8a5d1c05f894414aa5aaa66cf901c52dfcb119a1 b/webm_parser/fuzzing/corpus/8a5d1c05f894414aa5aaa66cf901c52dfcb119a1
new file mode 100644
index 0000000..c1bfd3b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8a5d1c05f894414aa5aaa66cf901c52dfcb119a1
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šm€‡b@„P1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8aa514b6e6fef8d045aa049fdb52fa8adcf722d4 b/webm_parser/fuzzing/corpus/8aa514b6e6fef8d045aa049fdb52fa8adcf722d4
new file mode 100644
index 0000000..7d3ecf2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8aa514b6e6fef8d045aa049fdb52fa8adcf722d4
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/8b0dcd4400fe19cf6a268af780990f47dba08189 b/webm_parser/fuzzing/corpus/8b0dcd4400fe19cf6a268af780990f47dba08189
new file mode 100644
index 0000000..2e19cb7
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8b0dcd4400fe19cf6a268af780990f47dba08189
@@ -0,0 +1 @@
+S€g»TÃg¶ss³gÈ©È„#‡dg)„]‡egÈ„D‡(gÈ„]‡egˆ„D‡gÈ€D‡dgÈg \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8c4655e6528071edd445715ff1559f399dc9d47c b/webm_parser/fuzzing/corpus/8c4655e6528071edd445715ff1559f399dc9d47c
new file mode 100644
index 0000000..fe94617
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8c4655e6528071edd445715ff1559f399dc9d47c
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/8c695cbcc43e37ce25194c4b61b3d87488b308a1 b/webm_parser/fuzzing/corpus/8c695cbcc43e37ce25194c4b61b3d87488b308a1
new file mode 100644
index 0000000..6219e9f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8c695cbcc43e37ce25194c4b61b3d87488b308a1
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šm€‡b@„P1S€g”T®k®m€Šb@‡P5€GáÿS€g™T®k”®’m€b@ŒP5€P5€Ps€P5€S€g˜T®k“®‘m€Žb@„P1b@„P1S€gT®kˆ®†à„Tº \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8c9dc3c5a839f90f29256ef8c8f1369bc7383817 b/webm_parser/fuzzing/corpus/8c9dc3c5a839f90f29256ef8c8f1369bc7383817
new file mode 100644
index 0000000..85078ca
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8c9dc3c5a839f90f29256ef8c8f1369bc7383817
@@ -0,0 +1 @@
+WA¢€€ˆƒ€€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8cdcf83eb89d7e5c87cfa1b8811c95f738747a6a b/webm_parser/fuzzing/corpus/8cdcf83eb89d7e5c87cfa1b8811c95f738747a6a
new file mode 100644
index 0000000..8db4eec
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8cdcf83eb89d7e5c87cfa1b8811c95f738747a6a
@@ -0,0 +1 @@
+S€g‰I©f„M€! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8d0c6e6b3d74233685c3a2c1793d1a7b0ae2e34d b/webm_parser/fuzzing/corpus/8d0c6e6b3d74233685c3a2c1793d1a7b0ae2e34d
new file mode 100644
index 0000000..b4ef3d5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8d0c6e6b3d74233685c3a2c1793d1a7b0ae2e34d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/8d444e8b311d158f854a03ca35d2c7865cd2c46b b/webm_parser/fuzzing/corpus/8d444e8b311d158f854a03ca35d2c7865cd2c46b
new file mode 100644
index 0000000..c6c2f15
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8d444e8b311d158f854a03ca35d2c7865cd2c46b
@@ -0,0 +1 @@
+E£Eߣ‚ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8dc289730cc03ecbbed57d04cdca538a4fd08059 b/webm_parser/fuzzing/corpus/8dc289730cc03ecbbed57d04cdca538a4fd08059
new file mode 100644
index 0000000..ed9c1ee
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8dc289730cc03ecbbed57d04cdca538a4fd08059
@@ -0,0 +1 @@
+S€g“C¶uŽ ŒŽŠèƒÌè€Ì \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8dc58cfcb396a60db921901f1b0807c4f4f37619 b/webm_parser/fuzzing/corpus/8dc58cfcb396a60db921901f1b0807c4f4f37619
new file mode 100644
index 0000000..e256d2f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8dc58cfcb396a60db921901f1b0807c4f4f37619
@@ -0,0 +1 @@
+€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI
diff --git a/webm_parser/fuzzing/corpus/8e05be75b7c14c463af277559dc8ea7841a733c8 b/webm_parser/fuzzing/corpus/8e05be75b7c14c463af277559dc8ea7841a733c8
new file mode 100644
index 0000000..caff0f1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8e05be75b7c14c463af277559dc8ea7841a733c8
@@ -0,0 +1 @@
+S€gT®kŠ®ˆá†µ„@ÉÛ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8e7eef00a7a6719e77544b57d1d98ff83a37e55a b/webm_parser/fuzzing/corpus/8e7eef00a7a6719e77544b57d1d98ff83a37e55a
new file mode 100644
index 0000000..52c1ec4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8e7eef00a7a6719e77544b57d1d98ff83a37e55a
@@ -0,0 +1 @@
+S€gŠT®k…®ƒ¹ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8ed6ef9af5555ce4ff12b23139add9621e7ad930 b/webm_parser/fuzzing/corpus/8ed6ef9af5555ce4ff12b23139add9621e7ad930
new file mode 100644
index 0000000..007a500
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8ed6ef9af5555ce4ff12b23139add9621e7ad930
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/8f39f83b71d49805a451f81a5a451f5f3d74927d b/webm_parser/fuzzing/corpus/8f39f83b71d49805a451f81a5a451f5f3d74927d
new file mode 100644
index 0000000..aea2062
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8f39f83b71d49805a451f81a5a451f5f3d74927d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/8f403efbef079f1775b63bcf1061983e4c5d2162 b/webm_parser/fuzzing/corpus/8f403efbef079f1775b63bcf1061983e4c5d2162
new file mode 100644
index 0000000..7a0e070
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8f403efbef079f1775b63bcf1061983e4c5d2162
@@ -0,0 +1 @@
+S€g”T®k®m€Šb@‡P5„Gâ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/8f8f956eaaf8f7f5e65002be888b946218f0f7ce b/webm_parser/fuzzing/corpus/8f8f956eaaf8f7f5e65002be888b946218f0f7ce
new file mode 100644
index 0000000..7ebec69
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/8f8f956eaaf8f7f5e65002be888b946218f0f7ce
@@ -0,0 +1 @@
+€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ„€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€!ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€!ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ„€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€!ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶Š¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI
diff --git a/webm_parser/fuzzing/corpus/90a03241b5cc656b7c2a4ef55664b0320fa3bd43 b/webm_parser/fuzzing/corpus/90a03241b5cc656b7c2a4ef55664b0320fa3bd43
new file mode 100644
index 0000000..ac88482
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/90a03241b5cc656b7c2a4ef55664b0320fa3bd43
@@ -0,0 +1 @@
+S€g”C¶u u¡Š¦ƒî¦€¦€î \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/925a91e7c25a43d9da8b63eba51ed66a52d833aa b/webm_parser/fuzzing/corpus/925a91e7c25a43d9da8b63eba51ed66a52d833aa
new file mode 100644
index 0000000..8eac37e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/925a91e7c25a43d9da8b63eba51ed66a52d833aa
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/92de5c217802ec24886240188dca293325eb17c9 b/webm_parser/fuzzing/corpus/92de5c217802ec24886240188dca293325eb17c9
new file mode 100644
index 0000000..8237f0b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/92de5c217802ec24886240188dca293325eb17c9
@@ -0,0 +1 @@
+S€g‹T®k†®„Sn! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/939da66e1fc8bb9854865066ee47b6ac4b933aa9 b/webm_parser/fuzzing/corpus/939da66e1fc8bb9854865066ee47b6ac4b933aa9
new file mode 100644
index 0000000..f5179b1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/939da66e1fc8bb9854865066ee47b6ac4b933aa9
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/93a91331e7c3c060f461ae1f22e2bbf4747ffe33 b/webm_parser/fuzzing/corpus/93a91331e7c3c060f461ae1f22e2bbf4747ffe33
new file mode 100644
index 0000000..1b28684
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/93a91331e7c3c060f461ae1f22e2bbf4747ffe33
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/94f6dfc499cbf2e84ffe1d69cb751ada9f2f2e3a b/webm_parser/fuzzing/corpus/94f6dfc499cbf2e84ffe1d69cb751ada9f2f2e3a
new file mode 100644
index 0000000..30a5c63
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/94f6dfc499cbf2e84ffe1d69cb751ada9f2f2e3a
@@ -0,0 +1 @@
+S€gŠC¶u… ƒ› \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/9539fef8ffb9f7c542061f1af1f3f98e9a714cc3 b/webm_parser/fuzzing/corpus/9539fef8ffb9f7c542061f1af1f3f98e9a714cc3
new file mode 100644
index 0000000..0ea9650
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9539fef8ffb9f7c542061f1af1f3f98e9a714cc3
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/953c9a616ea56067225c88fccc6423496f2501a6 b/webm_parser/fuzzing/corpus/953c9a616ea56067225c88fccc6423496f2501a6
new file mode 100644
index 0000000..249824c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/953c9a616ea56067225c88fccc6423496f2501a6
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/95a60268c555a77c1010d75bb44142af47635486 b/webm_parser/fuzzing/corpus/95a60268c555a77c1010d75bb44142af47635486
new file mode 100644
index 0000000..a95f40a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/95a60268c555a77c1010d75bb44142af47635486
@@ -0,0 +1 @@
+€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI
diff --git a/webm_parser/fuzzing/corpus/95e410025b965cf6ab2e8e2d3559efbe71c1a972 b/webm_parser/fuzzing/corpus/95e410025b965cf6ab2e8e2d3559efbe71c1a972
new file mode 100644
index 0000000..8eddf69
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/95e410025b965cf6ab2e8e2d3559efbe71c1a972
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/9671f253d1eb7cd504a5617058ce4c01a80c897e b/webm_parser/fuzzing/corpus/9671f253d1eb7cd504a5617058ce4c01a80c897e
new file mode 100644
index 0000000..f0b837c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9671f253d1eb7cd504a5617058ce4c01a80c897e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/9698d3424f1581a6bbb66c764f14c95a74b87675 b/webm_parser/fuzzing/corpus/9698d3424f1581a6bbb66c764f14c95a74b87675
new file mode 100644
index 0000000..3aedc32
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9698d3424f1581a6bbb66c764f14c95a74b87675
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/96f7f38de1f8601311733984cf51cec35500dabe b/webm_parser/fuzzing/corpus/96f7f38de1f8601311733984cf51cec35500dabe
new file mode 100644
index 0000000..1230f4c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/96f7f38de1f8601311733984cf51cec35500dabe
@@ -0,0 +1 @@
+Uª ÿM \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/970ab6333aa5ccf8c2dc14bb56814f7bb4303b94 b/webm_parser/fuzzing/corpus/970ab6333aa5ccf8c2dc14bb56814f7bb4303b94
new file mode 100644
index 0000000..cf0c884
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/970ab6333aa5ccf8c2dc14bb56814f7bb4303b94
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/98978113d2116dc4bdbb10265fa32bd95230bdb6 b/webm_parser/fuzzing/corpus/98978113d2116dc4bdbb10265fa32bd95230bdb6
new file mode 100644
index 0000000..0da5825
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/98978113d2116dc4bdbb10265fa32bd95230bdb6
@@ -0,0 +1 @@
+S€g”C¶u u¡Š¦ƒî¦ƒîS€gC¶uŠ ˆu¡…¦ƒ¥!S€g”C¶u u¡Š¦ƒî¦ƒîS€gC¶uŠ ˆu¡…¦ƒ¥! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/989e2872d2a34de543f23c5061db68212d8258f7 b/webm_parser/fuzzing/corpus/989e2872d2a34de543f23c5061db68212d8258f7
new file mode 100644
index 0000000..3430234
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/989e2872d2a34de543f23c5061db68212d8258f7
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/98ca9a00ad571c4454fce709d5405e5aca2a363c b/webm_parser/fuzzing/corpus/98ca9a00ad571c4454fce709d5405e5aca2a363c
new file mode 100644
index 0000000..938e0bc
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/98ca9a00ad571c4454fce709d5405e5aca2a363c
@@ -0,0 +1 @@
+Eߣ„Bóÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/9965361765a4151902ec04fb21d9247b3a5ed10d b/webm_parser/fuzzing/corpus/9965361765a4151902ec04fb21d9247b3a5ed10d
new file mode 100644
index 0000000..6ec73d4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9965361765a4151902ec04fb21d9247b3a5ed10d
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šàˆ#ƒã„@ÉÛ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/99d6fe94a50faa50db9d7eb38d74bc3cc8417dc1 b/webm_parser/fuzzing/corpus/99d6fe94a50faa50db9d7eb38d74bc3cc8417dc1
new file mode 100644
index 0000000..0dcb93e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/99d6fe94a50faa50db9d7eb38d74bc3cc8417dc1
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/9a6869cec3dc84f2051bfaf0ee0d3552aa221f89 b/webm_parser/fuzzing/corpus/9a6869cec3dc84f2051bfaf0ee0d3552aa221f89
new file mode 100644
index 0000000..32ad695
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9a6869cec3dc84f2051bfaf0ee0d3552aa221f89
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/9cc1f26de1e3a7df8c7c03b95ff73ce9709c85f1 b/webm_parser/fuzzing/corpus/9cc1f26de1e3a7df8c7c03b95ff73ce9709c85f1
new file mode 100644
index 0000000..5178754
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9cc1f26de1e3a7df8c7c03b95ff73ce9709c85f1
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/9cc5b552abbd551485135fa87eab739a0a784057 b/webm_parser/fuzzing/corpus/9cc5b552abbd551485135fa87eab739a0a784057
new file mode 100644
index 0000000..3a95219
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9cc5b552abbd551485135fa87eab739a0a784057
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/9cca361865a4fbff75abdbb79c1e91706780576c b/webm_parser/fuzzing/corpus/9cca361865a4fbff75abdbb79c1e91706780576c
new file mode 100644
index 0000000..6735729
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9cca361865a4fbff75abdbb79c1e91706780576c
@@ -0,0 +1 @@
+S€gŽT®k‰®‡à…°ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/9d8e99f07604d6cb05ef613d41cbfb93b2aff787 b/webm_parser/fuzzing/corpus/9d8e99f07604d6cb05ef613d41cbfb93b2aff787
new file mode 100644
index 0000000..96aa349
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9d8e99f07604d6cb05ef613d41cbfb93b2aff787
@@ -0,0 +1 @@
+S€gŒT®k‡®…àƒšÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/9ecd61eaf2681a882473247a603b9f30c2663d49 b/webm_parser/fuzzing/corpus/9ecd61eaf2681a882473247a603b9f30c2663d49
new file mode 100644
index 0000000..256c70d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9ecd61eaf2681a882473247a603b9f30c2663d49
@@ -0,0 +1 @@
+S€g“C¶uŽ ŒŽŠèÿÌèƒÌ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/9f0a3b7c0814b4f80c0745161c8769f63098981b b/webm_parser/fuzzing/corpus/9f0a3b7c0814b4f80c0745161c8769f63098981b
new file mode 100644
index 0000000..fe2403b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9f0a3b7c0814b4f80c0745161c8769f63098981b
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/9f711c29ccf3f54d44000d7ef6299585674be288 b/webm_parser/fuzzing/corpus/9f711c29ccf3f54d44000d7ef6299585674be288
new file mode 100644
index 0000000..2f17a88
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9f711c29ccf3f54d44000d7ef6299585674be288
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/9fae60819c28d4fcc88a6a1b93dcf69b4e458203 b/webm_parser/fuzzing/corpus/9fae60819c28d4fcc88a6a1b93dcf69b4e458203
new file mode 100644
index 0000000..9194d9d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/9fae60819c28d4fcc88a6a1b93dcf69b4e458203
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/a02431cf7c501a5b368c91e41283419d8fa9fb03 b/webm_parser/fuzzing/corpus/a02431cf7c501a5b368c91e41283419d8fa9fb03
new file mode 100644
index 0000000..17706ff
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a02431cf7c501a5b368c91e41283419d8fa9fb03
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/a0ac6c3c83817637bbbcb11a5106c57aa6654afb b/webm_parser/fuzzing/corpus/a0ac6c3c83817637bbbcb11a5106c57aa6654afb
new file mode 100644
index 0000000..a5d2ab4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a0ac6c3c83817637bbbcb11a5106c57aa6654afb
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/a15fdfa620d19a92d9eaa9f3f13010e53f902796 b/webm_parser/fuzzing/corpus/a15fdfa620d19a92d9eaa9f3f13010e53f902796
new file mode 100644
index 0000000..d5b108f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a15fdfa620d19a92d9eaa9f3f13010e53f902796
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶@…@B€@…XC¶@sĶ@sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a18e76ae792a054c2f6d0d01e0e78d58678b35e3 b/webm_parser/fuzzing/corpus/a18e76ae792a054c2f6d0d01e0e78d58678b35e3
new file mode 100644
index 0000000..135e70d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a18e76ae792a054c2f6d0d01e0e78d58678b35e3
@@ -0,0 +1 @@
+S€gŠT®k…®ƒ†! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a19d04f18f574e561d793ac0dfcffe2b38183eb1 b/webm_parser/fuzzing/corpus/a19d04f18f574e561d793ac0dfcffe2b38183eb1
new file mode 100644
index 0000000..cb84077
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a19d04f18f574e561d793ac0dfcffe2b38183eb1
@@ -0,0 +1 @@
+"µœŸ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a26fb85be3d2bb8a2360bb4d9533a1651bd12d99 b/webm_parser/fuzzing/corpus/a26fb85be3d2bb8a2360bb4d9533a1651bd12d99
new file mode 100644
index 0000000..6fb743b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a26fb85be3d2bb8a2360bb4d9533a1651bd12d99
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/a2e860fae30005a9e75b61f54f3d019c44090fcc b/webm_parser/fuzzing/corpus/a2e860fae30005a9e75b61f54f3d019c44090fcc
new file mode 100644
index 0000000..124e7d6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a2e860fae30005a9e75b61f54f3d019c44090fcc
@@ -0,0 +1 @@
+D…€ðVT€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a3071bfcb7b2fd3c4286ea42e1f7940754b55697 b/webm_parser/fuzzing/corpus/a3071bfcb7b2fd3c4286ea42e1f7940754b55697
new file mode 100644
index 0000000..f16be68
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a3071bfcb7b2fd3c4286ea42e1f7940754b55697
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/a4ac408fb9d6def070ad3a76312ca092863048e5 b/webm_parser/fuzzing/corpus/a4ac408fb9d6def070ad3a76312ca092863048e5
new file mode 100644
index 0000000..4e3bc51
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a4ac408fb9d6def070ad3a76312ca092863048e5
@@ -0,0 +1 @@
+Ý \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a4fafc117cbfde8c240deccc8997c7966d9109ba b/webm_parser/fuzzing/corpus/a4fafc117cbfde8c240deccc8997c7966d9109ba
new file mode 100644
index 0000000..79af6da
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a4fafc117cbfde8c240deccc8997c7966d9109ba
@@ -0,0 +1 @@
+S€gŒS»k‡»…³‚ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a59ffb5f6122e45136352585d3b53294a71346d0 b/webm_parser/fuzzing/corpus/a59ffb5f6122e45136352585d3b53294a71346d0
new file mode 100644
index 0000000..b848f43
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a59ffb5f6122e45136352585d3b53294a71346d0
@@ -0,0 +1 @@
+S€g‘C¶uŒ Š¡‡¦…îƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a5b3a3c48727c26dfd625f247069d2cdbfa031f0 b/webm_parser/fuzzing/corpus/a5b3a3c48727c26dfd625f247069d2cdbfa031f0
new file mode 100644
index 0000000..89ee740
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a5b3a3c48727c26dfd625f247069d2cdbfa031f0
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/a5c829fbcd9fd760bc55bc8ab6901b8d401b65f6 b/webm_parser/fuzzing/corpus/a5c829fbcd9fd760bc55bc8ab6901b8d401b65f6
new file mode 100644
index 0000000..13dc200
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a5c829fbcd9fd760bc55bc8ab6901b8d401b65f6
@@ -0,0 +1 @@
+S€gŠT®k…®ƒƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a5fbbce038cad4f5e0c0f97fa69ebc3601123e5c b/webm_parser/fuzzing/corpus/a5fbbce038cad4f5e0c0f97fa69ebc3601123e5c
new file mode 100644
index 0000000..ec1eabd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a5fbbce038cad4f5e0c0f97fa69ebc3601123e5c
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/a6e57b33e7a219168280e51bc98a44de40f0f9ca b/webm_parser/fuzzing/corpus/a6e57b33e7a219168280e51bc98a44de40f0f9ca
new file mode 100644
index 0000000..d8f16c2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a6e57b33e7a219168280e51bc98a44de40f0f9ca
@@ -0,0 +1 @@
+S€g‹S»k†»„·€·€S€g‹S»k†»„·€·€Eߣˆb‚@webE \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a78afe9e4e9f02a10ebadac64171ad49749a6965 b/webm_parser/fuzzing/corpus/a78afe9e4e9f02a10ebadac64171ad49749a6965
new file mode 100644
index 0000000..1b8c641
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a78afe9e4e9f02a10ebadac64171ad49749a6965
@@ -0,0 +1 @@
+S€gˆM›tƒM»€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a823a019c0b19c97a1d35722cd843109ab016c17 b/webm_parser/fuzzing/corpus/a823a019c0b19c97a1d35722cd843109ab016c17
new file mode 100644
index 0000000..eae1319
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a823a019c0b19c97a1d35722cd843109ab016c17
@@ -0,0 +1,22 @@
+Eߣ€S€g †¡C§p †šE¹ †•¶ †‘¶ †¶ †‰¶ †…¶ †¶ †}¶ †y¶ †u¶ †q¶ †m¶ †i¶ †e¶ †a¶ †]¶ †Y¶ †U¶ †Q¶ †M¶ †I¶ †E¶ †A¶ †=¶ †9¶ †5¶ †1¶ †-¶ †)¶ †%¶ †!¶ †¶ †¶ †¶ †¶ † ¶ † ¶ †¶ †¶ …ý¶ …ù¶ …õ¶ …ñ¶ …í¶ …é¶ …å¶ …ᶠ…ݶ …Ù¶ …Õ¶ …Ѷ …Ͷ …ɶ …Ŷ …Á¶ …½¶ …¹¶ …µ¶ …±¶ …­¶ …©¶ …¥¶ …¡¶ …¶ …™¶ …•¶ …‘¶ …¶ …‰¶ ……¶ …¶ …}¶ …y¶ …u¶ …q¶ …m¶ …i¶ …e¶ …a¶ …]¶ …Y¶ …U¶ …Q¶ …M¶ …I¶ …E¶ …A¶ …=¶ …9¶ …5¶ …1¶ …-¶ …)¶ …%¶ …!¶ …¶ …¶ …¶ …¶ … ¶ … ¶ …¶ …¶ „ý¶ „ù¶ „õ¶ „ñ¶ „í¶ „é¶ „å¶ „ᶠ„ݶ „Ù¶ „Õ¶ „Ѷ „Ͷ „ɶ „Ŷ „Á¶ „½¶ „¹¶ „µ¶ „±¶ „­¶ „©¶ „¥¶ „¡¶ „¶ „™¶ „•¶ „‘¶ „¶ „‰¶ „…¶ „¶ „}¶ „y¶ „u¶ „q¶ „m¶ „i¶ „e¶ „a¶ „]¶ „Y¶ „U¶ „Q¶ „M¶ „I¶ „E¶ „A¶ „=¶ „9¶ „5¶ „1¶ „-¶ „)¶ „%¶ „!¶ „¶ „¶ „¶ „¶ „ ¶ „ ¶ „¶ „¶ ƒý¶ ƒù¶ ƒõ¶ ƒñ¶ ƒí¶ ƒé¶ ƒå¶ ƒá¶ ƒÝ¶ ƒÙ¶ ƒÕ¶ ƒÑ¶ ƒÍ¶ ƒÉ¶ ƒÅ¶ ƒÁ¶ ƒ½¶ ƒ¹¶ ƒµ¶ ƒ±¶ ƒ­¶ ƒ©¶ ƒ¥¶ ƒ¡¶ ƒ¶ ƒ™¶ ƒ•¶ ƒ‘¶ ƒ¶ ƒ‰¶ ƒ…¶ ƒ¶ ƒ}¶ ƒy¶ ƒu¶ ƒq¶ ƒm¶ ƒi¶ ƒe¶ ƒa¶ ƒ]¶ ƒY¶ ƒU¶ ƒQ¶ ƒM¶ ƒI¶ ƒE¶ ƒA¶ ƒ=¶ ƒ9¶ ƒ5¶ ƒ1¶ ƒ-¶ ƒ)¶ ƒ%¶ ƒ!¶ ƒ¶ ƒ¶ ƒ¶ ƒ¶ ƒ ¶ ƒ ¶ ƒ¶ ƒ¶ ‚ý¶ ‚ù¶ ‚õ¶ ‚ñ¶ ‚í¶ ‚é¶ ‚å¶ ‚ᶠ‚ݶ ‚Ù¶ ‚Õ¶ ‚Ѷ ‚Ͷ ‚ɶ ‚Ŷ ‚Á¶ ‚½¶ ‚¹¶ ‚µ¶ ‚±¶ ‚­¶ ‚©¶ ‚¥¶ ‚¡¶ ‚¶ ‚™¶ ‚•¶ ‚‘¶ ‚¶ ‚‰¶ ‚…¶ ‚¶ ‚}¶ ‚y¶ ‚u¶ ‚q¶ ‚m¶ ‚i¶ ‚e¶ ‚a¶ ‚]¶ ‚Y¶ ‚U¶ ‚Q¶ ‚M¶ ‚I¶ ‚E¶ ‚A¶ ‚=¶ ‚9¶ ‚5¶ ‚1¶ ‚-¶ ‚)¶ ‚%¶ ‚!¶ ‚¶ ‚¶ ‚¶ ‚¶ ‚ ¶ ‚ ¶ ‚¶ ‚¶ ý¶ ù¶ õ¶ ñ¶ í¶ é¶ å¶ á¶ Ý¶ Ù¶ Õ¶ Ѷ Ͷ ɶ Ŷ Á¶ ½¶ ¹¶ µ¶ ±¶ ­¶ ©¶ ¥¶ ¡¶ ¶ ™¶ •¶ ‘¶ ¶ ‰¶ …¶ ¶ }¶ y¶ u¶ q¶ m¶ i¶ e¶ a¶ ]¶ Y¶ U¶ Q¶ M¶ I¶ E¶ A¶ =¶ 9¶ 5¶ 1¶ -¶ )¶ %¶ !¶ ¶ ¶ ¶ ¶ ¶ ¶ ¶ ¶ €ý¶ €ù¶ €õ¶ €ñ¶ €í¶ €é¶ €å¶ €á¶ €Ý¶ €Ù¶ €Õ¶ €Ñ¶ €Í¶ €É¶ €Å¶ €Á¶ €½¶ €¹¶ €µ¶ €±¶ €­¶ €©¶ €¥¶ €¡¶ €¶ €™¶ €•¶ €‘¶ €¶ €‰¶ €…¶ €¶ €}¶ €y¶ €u¶ €q¶ €m¶ €i¶ €e¶ €a¶ €]¶ €Y¶ €U¶ €Q¶ €M¶ €I¶ €E¶ €A¶ €=¶ €9¶ €5¶ €1¶ €-¶ €)¶ €%¶ €!¶ €¶ €¶ €¶ €¶ € ¶ € ¶ €¶ €¶ ý¶ ù¶ õ¶ ñ¶ í¶ é¶ å¶ ᶠݶ Ù¶ Õ¶ Ѷ Ͷ ɶ Ŷ Á¶ ½¶ ¹¶ µ¶ ±¶ ­¶ ©¶ ¥¶ ¡¶ ¶ ™¶ •¶ ‘¶ ¶ ‰¶ …¶ ¶ }¶ y¶ u¶ q¶ m¶ i¶ e¶ a¶ ]¶ Y¶ U¶ Q¶ M¶ I¶ E¶ A¶ =¶ 9¶ 5¶ 1¶ -¶ )¶ %¶ !¶ ¶ ¶ ¶ ¶  ¶  ¶ ¶ ¶ ~ý¶ ~ù¶ ~õ¶ ~ñ¶ ~í¶ ~é¶ ~å¶ ~ᶠ~ݶ ~Ù¶ ~Õ¶ ~Ѷ ~Ͷ ~ɶ ~Ŷ ~Á¶ ~½¶ ~¹¶ ~µ¶ ~±¶ ~­¶ ~©¶ ~¥¶ ~¡¶ ~¶ ~™¶ ~•¶ ~‘¶ ~¶ ~‰¶ ~…¶ ~¶ ~}¶ ~y¶ ~u¶ ~q¶ ~m¶ ~i¶ ~e¶ ~a¶ ~]¶ ~Y¶ ~U¶ ~Q¶ ~M¶ ~I¶ ~E¶ ~A¶ ~=¶ ~9¶ ~5¶ ~1¶ ~-¶ ~)¶ ~%¶ ~!¶ ~¶ ~¶ ~¶ ~¶ ~ ¶ ~ ¶ ~¶ ~¶ }ý¶ }ù¶ }õ¶ }ñ¶ }í¶ }é¶ }å¶ }ᶠ}ݶ }Ù¶ }Õ¶ }Ѷ }Ͷ }ɶ }Ŷ }Á¶ }½¶ }¹¶ }µ¶ }±¶ }­¶ }©¶ }¥¶ }¡¶ }¶ }™¶ }•¶ }‘¶ }¶ }‰¶ }…¶ }¶ }}¶ }y¶ }u¶ }q¶ }m¶ }i¶ }e¶ }a¶ }]¶ }Y¶ }U¶ }Q¶ }M¶ }I¶ }E¶ }A¶ }=¶ }9¶ }5¶ }1¶ }-¶ })¶ }%¶ }!¶ }¶ }¶ }¶ }¶ } ¶ } ¶ }¶ }¶ |ý¶ |ù¶ |õ¶ |ñ¶ |í¶ |é¶ |å¶ |ᶠ|ݶ |Ù¶ |Õ¶ |Ѷ |Ͷ |ɶ |Ŷ |Á¶ |½¶ |¹¶ |µ¶ |±¶ |­¶ |©¶ |¥¶ |¡¶ |¶ |™¶ |•¶ |‘¶ |¶ |‰¶ |…¶ |¶ |}¶ |y¶ |u¶ |q¶ |m¶ |i¶ |e¶ |a¶ |]¶ |Y¶ |U¶ |Q¶ |M¶ |I¶ |E¶ |A¶ |=¶ |9¶ |5¶ |1¶ |-¶ |)¶ |%¶ |!¶ |¶ |¶ |¶ |¶ | ¶ | ¶ |¶ |¶ {ý¶ {ù¶ {õ¶ {ñ¶ {í¶ {é¶ {å¶ {ᶠ{ݶ {Ù¶ {Õ¶ {Ѷ {Ͷ {ɶ {Ŷ {Á¶ {½¶ {¹¶ {µ¶ {±¶ {­¶ {©¶ {¥¶ {¡¶ {¶ {™¶ {•¶ {‘¶ {¶ {‰¶ {…¶ {¶ {}¶ {y¶ {u¶ {q¶ {m¶ {i¶ {e¶ {a¶ {]¶ {Y¶ {U¶ {Q¶ {M¶ {I¶ {E¶ {A¶ {=¶ {9¶ {5¶ {1¶ {-¶ {)¶ {%¶ {!¶ {¶ {¶ {¶ {¶ { ¶ { ¶ {¶ {¶ zý¶ zù¶ zõ¶ zñ¶ zí¶ zé¶ zå¶ zᶠzݶ zÙ¶ zÕ¶ zѶ zͶ zɶ zŶ zÁ¶ z½¶ z¹¶ zµ¶ z±¶ z­¶ z©¶ z¥¶ z¡¶ z¶ z™¶ z•¶ z‘¶ z¶ z‰¶ z…¶ z¶ z}¶ zy¶ zu¶ zq¶ zm¶ zi¶ ze¶ za¶ z]¶ zY¶ zU¶ zQ¶ zM¶ zI¶ zE¶ zA¶ z=¶ z9¶ z5¶ z1¶ z-¶ z)¶ z%¶ z!¶ z¶ z¶ z¶ z¶ z ¶ z ¶ z¶ z¶ yý¶ yù¶ yõ¶ yñ¶ yí¶ yé¶ yå¶ yᶠyݶ yÙ¶ yÕ¶ yѶ yͶ yɶ yŶ yÁ¶ y½¶ y¹¶ yµ¶ y±¶ y­¶ y©¶ y¥¶ y¡¶ y¶ y™¶ y•¶ y‘¶ y¶ y‰¶ y…¶ y¶ y}¶ yy¶ yu¶ yq¶ ym¶ yi¶ ye¶ ya¶ y]¶ yY¶ yU¶ yQ¶ yM¶ yI¶ yE¶ yA¶ y=¶ y9¶ y5¶ y1¶ y-¶ y)¶ y%¶ y!¶ y¶ y¶ y¶ y¶ y ¶ y ¶ y¶ y¶ xý¶ xù¶ xõ¶ xñ¶ xí¶ xé¶ xå¶ xᶠxݶ xÙ¶ xÕ¶ xѶ xͶ xɶ xŶ xÁ¶ x½¶ x¹¶ xµ¶ x±¶ x­¶ x©¶ x¥¶ x¡¶ x¶ x™¶ x•¶ x‘¶ x¶ x‰¶ x…¶ x¶ x}¶ xy¶ xu¶ xq¶ xm¶ xi¶ xe¶ xa¶ x]¶ xY¶ xU¶ xQ¶ xM¶ xI¶ xE¶ xA¶ x=¶ x9¶ x5¶ x1¶ x-¶ x)¶ x%¶ x!¶ x¶ x¶ x¶ x¶ x ¶ x ¶ x¶ x¶ wý¶ wù¶ wõ¶ wñ¶ wí¶ wé¶ wå¶ wᶠwݶ wÙ¶ wÕ¶ wѶ wͶ wɶ wŶ wÁ¶ w½¶ w¹¶ wµ¶ w±¶ w­¶ w©¶ w¥¶ w¡¶ w¶ w™¶ w•¶ w‘¶ w¶ w‰¶ w…¶ w¶ w}¶ wy¶ wu¶ wq¶ wm¶ wi¶ we¶ wa¶ w]¶ wY¶ wU¶ wQ¶ wM¶ wI¶ wE¶ wA¶ w=¶ w9¶ w5¶ w1¶ w-¶ w)¶ w%¶ w!¶ w¶ w¶ w¶ w¶ w ¶ w ¶ w¶ w¶ vý¶ vù¶ võ¶ vñ¶ ví¶ vé¶ vå¶ vᶠvݶ vÙ¶ vÕ¶ vѶ vͶ vɶ vŶ vÁ¶ v½¶ v¹¶ vµ¶ v±¶ v­¶ v©¶ v¥¶ v¡¶ v¶ v™¶ v•¶ v‘¶ v¶ v‰¶ v…¶ v¶ v}¶ vy¶ vu¶ vq¶ vm¶ vi¶ ve¶ va¶ v]¶ vY¶ vU¶ vQ¶ vM¶ vI¶ vE¶ vA¶ v=¶ v9¶ v5¶ v1¶ v-¶ v)¶ v%¶ v!¶ v¶ v¶ v¶ v¶ v ¶ v ¶ v¶ v¶ uý¶ uù¶ uõ¶ uñ¶ uí¶ ué¶ uå¶ uᶠuݶ uÙ¶ uÕ¶ uѶ uͶ uɶ uŶ uÁ¶ u½¶ u¹¶ uµ¶ u±¶ u­¶ u©¶ u¥¶ u¡¶ u¶ u™¶ u•¶ u‘¶ u¶ u‰¶ u…¶ u¶ u}¶ uy¶ uu¶ uq¶ um¶ ui¶ ue¶ ua¶ u]¶ uY¶ uU¶ uQ¶ uM¶ uI¶ uE¶ uA¶ u=¶ u9¶ u5¶ u1¶ u-¶ u)¶ u%¶ u!¶ u¶ u¶ u¶ u¶ u ¶ u ¶ u¶ u¶ tý¶ tù¶ tõ¶ tñ¶ tí¶ té¶ tå¶ tᶠtݶ tÙ¶ tÕ¶ tѶ tͶ tɶ tŶ tÁ¶ t½¶ t¹¶ tµ¶ t±¶ t­¶ t©¶ t¥¶ t¡¶ t¶ t™¶ t•¶ t‘¶ t¶ t‰¶ t…¶ t¶ t}¶ ty¶ tu¶ tq¶ tm¶ ti¶ te¶ ta¶ t]¶ tY¶ tU¶ tQ¶ tM¶ tI¶ tE¶ tA¶ t=¶ t9¶ t5¶ t1¶ t-¶ t)¶ t%¶ t!¶ t¶ t¶ t¶ t¶ t ¶ t ¶ t¶ t¶ sý¶ sù¶ sõ¶ sñ¶ sí¶ sé¶ så¶ sᶠsݶ sÙ¶ sÕ¶ sѶ sͶ sɶ sŶ sÁ¶ s½¶ s¹¶ sµ¶ s±¶ s­¶ s©¶ s¥¶ s¡¶ s¶ s™¶ s•¶ s‘¶ s¶ s‰¶ s…¶ s¶ s}¶ sy¶ su¶ sq¶ sm¶ si¶ se¶ sa¶ s]¶ sY¶ sU¶ sQ¶ sM¶ sI¶ sE¶ sA¶ s=¶ s9¶ s5¶ s1¶ s-¶ s)¶ s%¶ s!¶ s¶ s¶ s¶ s¶ s ¶ s ¶ s¶ s¶ rý¶ rù¶ rõ¶ rñ¶ rí¶ ré¶ rå¶ rᶠrݶ rÙ¶ rÕ¶ rѶ rͶ rɶ rŶ rÁ¶ r½¶ r¹¶ rµ¶ r±¶ r­¶ r©¶ r¥¶ r¡¶ r¶ r™¶ r•¶ r‘¶ r¶ r‰¶ r…¶ r¶ r}¶ ry¶ ru¶ rq¶ rm¶ ri¶ re¶ ra¶ r]¶ rY¶ rU¶ rQ¶ rM¶ rI¶ rE¶ rA¶ r=¶ r9¶ r5¶ r1¶ r-¶ r)¶ r%¶ r!¶ r¶ r¶ r¶ r¶ r ¶ r ¶ r¶ r¶ qý¶ qù¶ qõ¶ qñ¶ qí¶ qé¶ qå¶ qᶠqݶ qÙ¶ qÕ¶ qѶ qͶ qɶ qŶ qÁ¶ q½¶ q¹¶ qµ¶ q±¶ q­¶ q©¶ q¥¶ q¡¶ q¶ q™¶ q•¶ q‘¶ q¶ q‰¶ q…¶ q¶ q}¶ qy¶ qu¶ qq¶ qm¶ qi¶ qe¶ qa¶ q]¶ qY¶ qU¶ qQ¶ qM¶ qI¶ qE¶ qA¶ q=¶ q9¶ q5¶ q1¶ q-¶ q)¶ q%¶ q!¶ q¶ q¶ q¶ q¶ q ¶ q ¶ q¶ q¶ pý¶ pù¶ põ¶ pñ¶ pí¶ pé¶ på¶ pᶠpݶ pÙ¶ pÕ¶ pѶ pͶ pɶ pŶ pÁ¶ p½¶ p¹¶ pµ¶ p±¶ p­¶ p©¶ p¥¶ p¡¶ p¶ p™¶ p•¶ p‘¶ p¶ p‰¶ p…¶ p¶ p}¶ py¶ pu¶ pq¶ pm¶ pi¶ pe¶ pa¶ p]¶ pY¶ pU¶ pQ¶ pM¶ pI¶ pE¶ pA¶ p=¶ p9¶ p5¶ p1¶ p-¶ p)¶ p%¶ p!¶ p¶ p¶ p¶ p¶ p ¶ p ¶ p¶ p¶ oý¶ où¶ oõ¶ oñ¶ oí¶ oé¶ oå¶ oᶠoݶ oÙ¶ oÕ¶ oѶ oͶ oɶ oŶ oÁ¶ o½¶ o¹¶ oµ¶ o±¶ o­¶ o©¶ o¥¶ o¡¶ o¶ o™¶ o•¶ o‘¶ o¶ o‰¶ o…¶ o¶ o}¶ oy¶ ou¶ oq¶ om¶ oi¶ oe¶ oa¶ o]¶ oY¶ oU¶ oQ¶ oM¶ oI¶ oE¶ oA¶ o=¶ o9¶ o5¶ o1¶ o-¶ o)¶ o%¶ o!¶ o¶ o¶ o¶ o¶ o ¶ o ¶ o¶ o¶ ný¶ nù¶ nõ¶ nñ¶ ní¶ né¶ nå¶ nᶠnݶ nÙ¶ nÕ¶ nѶ nͶ nɶ nŶ nÁ¶ n½¶ n¹¶ nµ¶ n±¶ n­¶ n©¶ n¥¶ n¡¶ n¶ n™¶ n•¶ n‘¶ n¶ n‰¶ n…¶ n¶ n}¶ ny¶ nu¶ nq¶ nm¶ ni¶ ne¶ na¶ n]¶ nY¶ nU¶ nQ¶ nM¶ nI¶ nE¶ nA¶ n=¶ n9¶ n5¶ n1¶ n-¶ n)¶ n%¶ n!¶ n¶ n¶ n¶ n¶ n ¶ n ¶ n¶ n¶ mý¶ mù¶ mõ¶ mñ¶ mí¶ mé¶ må¶ mᶠmݶ mÙ¶ mÕ¶ mѶ mͶ mɶ mŶ mÁ¶ m½¶ m¹¶ mµ¶ m±¶ m­¶ m©¶ m¥¶ m¡¶ m¶ m™¶ m•¶ m‘¶ m¶ m‰¶ m…¶ m¶ m}¶ my¶ mu¶ mq¶ mm¶ mi¶ me¶ ma¶ m]¶ mY¶ mU¶ mQ¶ mM¶ mI¶ mE¶ mA¶ m=¶ m9¶ m5¶ m1¶ m-¶ m)¶ m%¶ m!¶ m¶ m¶ m¶ m¶ m ¶ m ¶ m¶ m¶ lý¶ lù¶ lõ¶ lñ¶ lí¶ lé¶ lå¶ lᶠlݶ lÙ¶ lÕ¶ lѶ lͶ lɶ lŶ lÁ¶ l½¶ l¹¶ lµ¶ l±¶ l­¶ l©¶ l¥¶ l¡¶ l¶ l™¶ l•¶ l‘¶ l¶ l‰¶ l…¶ l¶ l}¶ ly¶ lu¶ lq¶ lm¶ li¶ le¶ la¶ l]¶ lY¶ lU¶ lQ¶ lM¶ lI¶ lE¶ lA¶ l=¶ l9¶ l5¶ l1¶ l-¶ l)¶ l%¶ l!¶ l¶ l¶ l¶ l¶ l ¶ l ¶ l¶ l¶ ký¶ kù¶ kõ¶ kñ¶ kí¶ ké¶ kå¶ kᶠkݶ kÙ¶ kÕ¶ kѶ kͶ kɶ kŶ kÁ¶ k½¶ k¹¶ kµ¶ k±¶ k­¶ k©¶ k¥¶ k¡¶ k¶ k™¶ k•¶ k‘¶ k¶ k‰¶ k…¶ k¶ k}¶ ky¶ ku¶ kq¶ km¶ ki¶ ke¶ ka¶ k]¶ kY¶ kU¶ kQ¶ kM¶ kI¶ kE¶ kA¶ k=¶ k9¶ k5¶ k1¶ k-¶ k)¶ k%¶ k!¶ k¶ k¶ k¶ k¶ k ¶ k ¶ k¶ k¶ jý¶ jù¶ jõ¶ jñ¶ jí¶ jé¶ jå¶ jᶠjݶ jÙ¶ jÕ¶ jѶ jͶ jɶ jŶ jÁ¶ j½¶ j¹¶ jµ¶ j±¶ j­¶ j©¶ j¥¶ j¡¶ j¶ j™¶ j•¶ j‘¶ j¶ j‰¶ j…¶ j¶ j}¶ jy¶ ju¶ jq¶ jm¶ ji¶ je¶ ja¶ j]¶ jY¶ jU¶ jQ¶ jM¶ jI¶ jE¶ jA¶ j=¶ j9¶ j5¶ j1¶ j-¶ j)¶ j%¶ j!¶ j¶ j¶ j¶ j¶ j ¶ j ¶ j¶ j¶ iý¶ iù¶ iõ¶ iñ¶ ií¶ ié¶ iå¶ iᶠiݶ iÙ¶ iÕ¶ iѶ iͶ iɶ iŶ iÁ¶ i½¶ i¹¶ iµ¶ i±¶ i­¶ i©¶ i¥¶ i¡¶ i¶ i™¶ i•¶ i‘¶ i¶ i‰¶ i…¶ i¶ i}¶ iy¶ iu¶ iq¶ im¶ ii¶ ie¶ ia¶ i]¶ iY¶ iU¶ iQ¶ iM¶ iI¶ iE¶ iA¶ i=¶ i9¶ i5¶ i1¶ i-¶ i)¶ i%¶ i!¶ i¶ i¶ i¶ i¶ i ¶ i ¶ i¶ i¶ hý¶ hù¶ hõ¶ hñ¶ hí¶ hé¶ hå¶ hᶠhݶ hÙ¶ hÕ¶ hѶ hͶ hɶ hŶ hÁ¶ h½¶ h¹¶ hµ¶ h±¶ h­¶ h©¶ h¥¶ h¡¶ h¶ h™¶ h•¶ h‘¶ h¶ h‰¶ h…¶ h¶ h}¶ hy¶ hu¶ hq¶ hm¶ hi¶ he¶ ha¶ h]¶ hY¶ hU¶ hQ¶ hM¶ hI¶ hE¶ hA¶ h=¶ h9¶ h5¶ h1¶ h-¶ h)¶ h%¶ h!¶ h¶ h¶ h¶ h¶ h ¶ h ¶ h¶ h¶ gý¶ gù¶ gõ¶ gñ¶ gí¶ gé¶ gå¶ gᶠgݶ gÙ¶ gÕ¶ gѶ gͶ gɶ gŶ gÁ¶ g½¶ g¹¶ gµ¶ g±¶ g­¶ g©¶ g¥¶ g¡¶ g¶ g™¶ g•¶ g‘¶ g¶ g‰¶ g…¶ g¶ g}¶ gy¶ gu¶ gq¶ gm¶ gi¶ ge¶ ga¶ g]¶ gY¶ gU¶ gQ¶ gM¶ gI¶ gE¶ gA¶ g=¶ g9¶ g5¶ g1¶ g-¶ g)¶ g%¶ g!¶ g¶ g¶ g¶ g¶ g ¶ g ¶ g¶ g¶ fý¶ fù¶ fõ¶ fñ¶ fí¶ fé¶ få¶ fᶠfݶ fÙ¶ fÕ¶ fѶ fͶ fɶ fŶ fÁ¶ f½¶ f¹¶ fµ¶ f±¶ f­¶ f©¶ f¥¶ f¡¶ f¶ f™¶ f•¶ f‘¶ f¶ f‰¶ f…¶ f¶ f}¶ fy¶ fu¶ fq¶ fm¶ fi¶ fe¶ fa¶ f]¶ fY¶ fU¶ fQ¶ fM¶ fI¶ fE¶ fA¶ f=¶ f9¶ f5¶ f1¶ f-¶ f)¶ f%¶ f!¶ f¶ f¶ f¶ f¶ f ¶ f ¶ f¶ f¶ eý¶ eù¶ eõ¶ eñ¶ eí¶ eé¶ eå¶ eᶠeݶ eÙ¶ eÕ¶ eѶ eͶ eɶ eŶ eÁ¶ e½¶ e¹¶ eµ¶ e±¶ e­¶ e©¶ e¥¶ e¡¶ e¶ e™¶ e•¶ e‘¶ e¶ e‰¶ e…¶ e¶ e}¶ ey¶ eu¶ eq¶ em¶ ei¶ ee¶ ea¶ e]¶ eY¶ eU¶ eQ¶ eM¶ eI¶ eE¶ eA¶ e=¶ e9¶ e5¶ e1¶ e-¶ e)¶ e%¶ e!¶ e¶ e¶ e¶ e¶ e ¶ e ¶ e¶ e¶ dý¶ dù¶ dõ¶ dñ¶ dí¶ dé¶ då¶ dᶠdݶ dÙ¶ dÕ¶ dѶ dͶ dɶ dŶ dÁ¶ d½¶ d¹¶ dµ¶ d±¶ d­¶ d©¶ d¥¶ d¡¶ d¶ d™¶ d•¶ d‘¶ d¶ d‰¶ d…¶ d¶ d}¶ dy¶ du¶ dq¶ dm¶ di¶ de¶ da¶ d]¶ dY¶ dU¶ dQ¶ dM¶ dI¶ dE¶ dA¶ d=¶ d9¶ d5¶ d1¶ d-¶ d)¶ d%¶ d!¶ d¶ d¶ d¶ d¶ d ¶ d ¶ d¶ d¶ cý¶ cù¶ cõ¶ cñ¶ cí¶ cé¶ cå¶ cᶠcݶ cÙ¶ cÕ¶ cѶ cͶ cɶ cŶ cÁ¶ c½¶ c¹¶ cµ¶ c±¶ c­¶ c©¶ c¥¶ c¡¶ c¶ c™¶ c•¶ c‘¶ c¶ c‰¶ c…¶ c¶ c}¶ cy¶ cu¶ cq¶ cm¶ ci¶ ce¶ ca¶ c]¶ cY¶ cU¶ cQ¶ cM¶ cI¶ cE¶ cA¶ c=¶ c9¶ c5¶ c1¶ c-¶ c)¶ c%¶ c!¶ c¶ c¶ c¶ c¶ c ¶ c ¶ c¶ c¶ bý¶ bù¶ bõ¶ bñ¶ bí¶ bé¶ bå¶ bᶠbݶ bÙ¶ bÕ¶ bѶ bͶ bɶ bŶ bÁ¶ b½¶ b¹¶ bµ¶ b±¶ b­¶ b©¶ b¥¶ b¡¶ b¶ b™¶ b•¶ b‘¶ b¶ b‰¶ b…¶ b¶ b}¶ by¶ bu¶ bq¶ bm¶ bi¶ be¶ ba¶ b]¶ bY¶ bU¶ bQ¶ bM¶ bI¶ bE¶ bA¶ b=¶ b9¶ b5¶ b1¶ b-¶ b)¶ b%¶ b!¶ b¶ b¶ b¶ b¶ b ¶ b ¶ b¶ b¶ aý¶ aù¶ aõ¶ añ¶ aí¶ aé¶ aå¶ aᶠaݶ aÙ¶ aÕ¶ aѶ aͶ aɶ aŶ aÁ¶ a½¶ a¹¶ aµ¶ a±¶ a­¶ a©¶ a¥¶ a¡¶ a¶ a™¶ a•¶ a‘¶ a¶ a‰¶ a…¶ a¶ a}¶ ay¶ au¶ aq¶ am¶ ai¶ ae¶ aa¶ a]¶ aY¶ aU¶ aQ¶ aM¶ aI¶ aE¶ aA¶ a=¶ a9¶ a5¶ a1¶ a-¶ a)¶ a%¶ a!¶ a¶ a¶ a¶ a¶ a ¶ a ¶ a¶ a¶ `ý¶ `ù¶ `õ¶ `ñ¶ `í¶ `é¶ `å¶ `ᶠ`ݶ `Ù¶ `Õ¶ `Ѷ `Ͷ `ɶ `Ŷ `Á¶ `½¶ `¹¶ `µ¶ `±¶ `­¶ `©¶ `¥¶ `¡¶ `¶ `™¶ `•¶ `‘¶ `¶ `‰¶ `…¶ `¶ `}¶ `y¶ `u¶ `q¶ `m¶ `i¶ `e¶ `a¶ `]¶ `Y¶ `U¶ `Q¶ `M¶ `I¶ `E¶ `A¶ `=¶ `9¶ `5¶ `1¶ `-¶ `)¶ `%¶ `!¶ `¶ `¶ `¶ `¶ ` ¶ ` ¶ `¶ `¶ _ý¶ _ù¶ _õ¶ _ñ¶ _í¶ _é¶ _å¶ _ᶠ_ݶ _Ù¶ _Õ¶ _Ѷ _Ͷ _ɶ _Ŷ _Á¶ _½¶ _¹¶ _µ¶ _±¶ _­¶ _©¶ _¥¶ _¡¶ _¶ _™¶ _•¶ _‘¶ _¶ _‰¶ _…¶ _¶ _}¶ _y¶ _u¶ _q¶ _m¶ _i¶ _e¶ _a¶ _]¶ _Y¶ _U¶ _Q¶ _M¶ _I¶ _E¶ _A¶ _=¶ _9¶ _5¶ _1¶ _-¶ _)¶ _%¶ _!¶ _¶ _¶ _¶ _¶ _ ¶ _ ¶ _¶ _¶ ^ý¶ ^ù¶ ^õ¶ ^ñ¶ ^í¶ ^é¶ ^å¶ ^ᶠ^ݶ ^Ù¶ ^Õ¶ ^Ѷ ^Ͷ ^ɶ ^Ŷ ^Á¶ ^½¶ ^¹¶ ^µ¶ ^±¶ ^­¶ ^©¶ ^¥¶ ^¡¶ ^¶ ^™¶ ^•¶ ^‘¶ ^¶ ^‰¶ ^…¶ ^¶ ^}¶ ^y¶ ^u¶ ^q¶ ^m¶ ^i¶ ^e¶ ^a¶ ^]¶ ^Y¶ ^U¶ ^Q¶ ^M¶ ^I¶ ^E¶ ^A¶ ^=¶ ^9¶ ^5¶ ^1¶ ^-¶ ^)¶ ^%¶ ^!¶ ^¶ ^¶ ^¶ ^¶ ^ ¶ ^ ¶ ^¶ ^¶ ]ý¶ ]ù¶ ]õ¶ ]ñ¶ ]í¶ ]é¶ ]å¶ ]ᶠ]ݶ ]Ù¶ ]Õ¶ ]Ѷ ]Ͷ ]ɶ ]Ŷ ]Á¶ ]½¶ ]¹¶ ]µ¶ ]±¶ ]­¶ ]©¶ ]¥¶ ]¡¶ ]¶ ]™¶ ]•¶ ]‘¶ ]¶ ]‰¶ ]…¶ ]¶ ]}¶ ]y¶ ]u¶ ]q¶ ]m¶ ]i¶ ]e¶ ]a¶ ]]¶ ]Y¶ ]U¶ ]Q¶ ]M¶ ]I¶ ]E¶ ]A¶ ]=¶ ]9¶ ]5¶ ]1¶ ]-¶ ])¶ ]%¶ ]!¶ ]¶ ]¶ ]¶ ]¶ ] ¶ ] ¶ ]¶ ]¶ \ý¶ \ù¶ \õ¶ \ñ¶ \í¶ \é¶ \å¶ \ᶠ\ݶ \Ù¶ \Õ¶ \Ѷ \Ͷ \ɶ \Ŷ \Á¶ \½¶ \¹¶ \µ¶ \±¶ \­¶ \©¶ \¥¶ \¡¶ \¶ \™¶ \•¶ \‘¶ \¶ \‰¶ \…¶ \¶ \}¶ \y¶ \u¶ \q¶ \m¶ \i¶ \e¶ \a¶ \]¶ \Y¶ \U¶ \Q¶ \M¶ \I¶ \E¶ \A¶ \=¶ \9¶ \5¶ \1¶ \-¶ \)¶ \%¶ \!¶ \¶ \¶ \¶ \¶ \ ¶ \ ¶ \¶ \¶ [ý¶ [ù¶ [õ¶ [ñ¶ [í¶ [é¶ [å¶ [ᶠ[ݶ [Ù¶ [Õ¶ [Ѷ [Ͷ [ɶ [Ŷ [Á¶ [½¶ [¹¶ [µ¶ [±¶ [­¶ [©¶ [¥¶ [¡¶ [¶ [™¶ [•¶ [‘¶ [¶ [‰¶ […¶ [¶ [}¶ [y¶ [u¶ [q¶ [m¶ [i¶ [e¶ [a¶ []¶ [Y¶ [U¶ [Q¶ [M¶ [I¶ [E¶ [A¶ [=¶ [9¶ [5¶ [1¶ [-¶ [)¶ [%¶ [!¶ [¶ [¶ [¶ [¶ [ ¶ [ ¶ [¶ [¶ Zý¶ Zù¶ Zõ¶ Zñ¶ Zí¶ Zé¶ Zå¶ ZᶠZݶ ZÙ¶ ZÕ¶ ZѶ ZͶ Zɶ ZŶ ZÁ¶ Z½¶ Z¹¶ Zµ¶ Z±¶ Z­¶ Z©¶ Z¥¶ Z¡¶ Z¶ Z™¶ Z•¶ Z‘¶ Z¶ Z‰¶ Z…¶ Z¶ Z}¶ Zy¶ Zu¶ Zq¶ Zm¶ Zi¶ Ze¶ Za¶ Z]¶ ZY¶ ZU¶ ZQ¶ ZM¶ ZI¶ ZE¶ ZA¶ Z=¶ Z9¶ Z5¶ Z1¶ Z-¶ Z)¶ Z%¶ Z!¶ Z¶ Z¶ Z¶ Z¶ Z ¶ Z ¶ Z¶ Z¶ Yý¶ Yù¶ Yõ¶ Yñ¶ Yí¶ Yé¶ Yå¶ YᶠYݶ YÙ¶ YÕ¶ YѶ YͶ Yɶ YŶ YÁ¶ Y½¶ Y¹¶ Yµ¶ Y±¶ Y­¶ Y©¶ Y¥¶ Y¡¶ Y¶ Y™¶ Y•¶ Y‘¶ Y¶ Y‰¶ Y…¶ Y¶ Y}¶ Yy¶ Yu¶ Yq¶ Ym¶ Yi¶ Ye¶ Ya¶ Y]¶ YY¶ YU¶ YQ¶ YM¶ YI¶ YE¶ YA¶ Y=¶ Y9¶ Y5¶ Y1¶ Y-¶ Y)¶ Y%¶ Y!¶ Y¶ Y¶ Y¶ Y¶ Y ¶ Y ¶ Y¶ Y¶ Xý¶ Xù¶ Xõ¶ Xñ¶ Xí¶ Xé¶ Xå¶ XᶠXݶ XÙ¶ XÕ¶ XѶ XͶ Xɶ XŶ XÁ¶ X½¶ X¹¶ Xµ¶ X±¶ X­¶ X©¶ X¥¶ X¡¶ X¶ X™¶ X•¶ X‘¶ X¶ X‰¶ X…¶ X¶ X}¶ Xy¶ Xu¶ Xq¶ Xm¶ Xi¶ Xe¶ Xa¶ X]¶ XY¶ XU¶ XQ¶ XM¶ XI¶ XE¶ XA¶ X=¶ X9¶ X5¶ X1¶ X-¶ X)¶ X%¶ X!¶ X¶ X¶ X¶ X¶ X ¶ X ¶ X¶ X¶ Wý¶ Wù¶ Wõ¶ Wñ¶ Wí¶ Wé¶ Wå¶ WᶠWݶ WÙ¶ WÕ¶ WѶ WͶ Wɶ WŶ WÁ¶ W½¶ W¹¶ Wµ¶ W±¶ W­¶ W©¶ W¥¶ W¡¶ W¶ W™¶ W•¶ W‘¶ W¶ W‰¶ W…¶ W¶ W}¶ Wy¶ Wu¶ Wq¶ Wm¶ Wi¶ We¶ Wa¶ W]¶ WY¶ WU¶ WQ¶ WM¶ WI¶ WE¶ WA¶ W=¶ W9¶ W5¶ W1¶ W-¶ W)¶ W%¶ W!¶ W¶ W¶ W¶ W¶ W ¶ W ¶ W¶ W¶ Vý¶ Vù¶ Võ¶ Vñ¶ Ví¶ Vé¶ Vå¶ VᶠVݶ VÙ¶ VÕ¶ VѶ VͶ Vɶ VŶ VÁ¶ V½¶ V¹¶ Vµ¶ V±¶ V­¶ V©¶ V¥¶ V¡¶ V¶ V™¶ V•¶ V‘¶ V¶ V‰¶ V…¶ V¶ V}¶ Vy¶ Vu¶ Vq¶ Vm¶ Vi¶ Ve¶ Va¶ V]¶ VY¶ VU¶ VQ¶ VM¶ VI¶ VE¶ VA¶ V=¶ V9¶ V5¶ V1¶ V-¶ V)¶ V%¶ V!¶ V¶ V¶ V¶ V¶ V ¶ V ¶ V¶ V¶ Uý¶ Uù¶ Uõ¶ Uñ¶ Uí¶ Ué¶ Uå¶ UᶠUݶ UÙ¶ UÕ¶ UѶ UͶ Uɶ UŶ UÁ¶ U½¶ U¹¶ Uµ¶ U±¶ U­¶ U©¶ U¥¶ U¡¶ U¶ U™¶ U•¶ U‘¶ U¶ U‰¶ U…¶ U¶ U}¶ Uy¶ Uu¶ Uq¶ Um¶ Ui¶ Ue¶ Ua¶ U]¶ UY¶ UU¶ UQ¶ UM¶ UI¶ UE¶ UA¶ U=¶ U9¶ U5¶ U1¶ U-¶ U)¶ U%¶ U!¶ U¶ U¶ U¶ U¶ U ¶ U ¶ U¶ U¶ Tý¶ Tù¶ Tõ¶ Tñ¶ Tí¶ Té¶ Tå¶ TᶠTݶ TÙ¶ TÕ¶ TѶ TͶ Tɶ TŶ TÁ¶ T½¶ T¹¶ Tµ¶ T±¶ T­¶ T©¶ T¥¶ T¡¶ T¶ T™¶ T•¶ T‘¶ T¶ T‰¶ T…¶ T¶ T}¶ Ty¶ Tu¶ Tq¶ Tm¶ Ti¶ Te¶ Ta¶ T]¶ TY¶ TU¶ TQ¶ TM¶ TI¶ TE¶ TA¶ T=¶ T9¶ T5¶ T1¶ T-¶ T)¶ T%¶ T!¶ T¶ T¶ T¶ T¶ T ¶ T ¶ T¶ T¶ Sý¶ Sù¶ Sõ¶ Sñ¶ Sí¶ Sé¶ Så¶ SᶠSݶ SÙ¶ SÕ¶ SѶ SͶ Sɶ SŶ SÁ¶ S½¶ S¹¶ Sµ¶ S±¶ S­¶ S©¶ S¥¶ S¡¶ S¶ S™¶ S•¶ S‘¶ S¶ S‰¶ S…¶ S¶ S}¶ Sy¶ Su¶ Sq¶ Sm¶ Si¶ Se¶ Sa¶ S]¶ SY¶ SU¶ SQ¶ SM¶ SI¶ SE¶ SA¶ S=¶ S9¶ S5¶ S1¶ S-¶ S)¶ S%¶ S!¶ S¶ S¶ S¶ S¶ S ¶ S ¶ S¶ S¶ Rý¶ Rù¶ Rõ¶ Rñ¶ Rí¶ Ré¶ Rå¶ RᶠRݶ RÙ¶ RÕ¶ RѶ RͶ Rɶ RŶ RÁ¶ R½¶ R¹¶ Rµ¶ R±¶ R­¶ R©¶ R¥¶ R¡¶ R¶ R™¶ R•¶ R‘¶ R¶ R‰¶ R…¶ R¶ R}¶ Ry¶ Ru¶ Rq¶ Rm¶ Ri¶ Re¶ Ra¶ R]¶ RY¶ RU¶ RQ¶ RM¶ RI¶ RE¶ RA¶ R=¶ R9¶ R5¶ R1¶ R-¶ R)¶ R%¶ R!¶ R¶ R¶ R¶ R¶ R ¶ R ¶ R¶ R¶ Qý¶ Qù¶ Qõ¶ Qñ¶ Qí¶ Qé¶ Qå¶ QᶠQݶ QÙ¶ QÕ¶ QѶ QͶ Qɶ QŶ QÁ¶ Q½¶ Q¹¶ Qµ¶ Q±¶ Q­¶ Q©¶ Q¥¶ Q¡¶ Q¶ Q™¶ Q•¶ Q‘¶ Q¶ Q‰¶ Q…¶ Q¶ Q}¶ Qy¶ Qu¶ Qq¶ Qm¶ Qi¶ Qe¶ Qa¶ Q]¶ QY¶ QU¶ QQ¶ QM¶ QI¶ QE¶ QA¶ Q=¶ Q9¶ Q5¶ Q1¶ Q-¶ Q)¶ Q%¶ Q!¶ Q¶ Q¶ Q¶ Q¶ Q ¶ Q ¶ Q¶ Q¶ Pý¶ Pù¶ Põ¶ Pñ¶ Pí¶ Pé¶ På¶ PᶠPݶ PÙ¶ PÕ¶ PѶ PͶ Pɶ PŶ PÁ¶ P½¶ P¹¶ Pµ¶ P±¶ P­¶ P©¶ P¥¶ P¡¶ P¶ P™¶ P•¶ P‘¶ P¶ P‰¶ P…¶ P¶ P}¶ Py¶ Pu¶ Pq¶ Pm¶ Pi¶ Pe¶ Pa¶ P]¶ PY¶ PU¶ PQ¶ PM¶ PI¶ PE¶ PA¶ P=¶ P9¶ P5¶ P1¶ P-¶ P)¶ P%¶ P!¶ P¶ P¶ P¶ P¶ P ¶ P ¶ P¶ P¶ Oý¶ Où¶ Oõ¶ Oñ¶ Oí¶ Oé¶ Oå¶ OᶠOݶ OÙ¶ OÕ¶ OѶ OͶ Oɶ OŶ OÁ¶ O½¶ O¹¶ Oµ¶ O±¶ O­¶ O©¶ O¥¶ O¡¶ O¶ O™¶ O•¶ O‘¶ O¶ O‰¶ O…¶ O¶ O}¶ Oy¶ Ou¶ Oq¶ Om¶ Oi¶ Oe¶ Oa¶ O]¶ OY¶ OU¶ OQ¶ OM¶ OI¶ OE¶ OA¶ O=¶ O9¶ O5¶ O1¶ O-¶ O)¶ O%¶ O!¶ O¶ O¶ O¶ O¶ O ¶ O ¶ O¶ O¶ Ný¶ Nù¶ Nõ¶ Nñ¶ Ní¶ Né¶ Nå¶ NᶠNݶ NÙ¶ NÕ¶ NѶ NͶ Nɶ NŶ NÁ¶ N½¶ N¹¶ Nµ¶ N±¶ N­¶ N©¶ N¥¶ N¡¶ N¶ N™¶ N•¶ N‘¶ N¶ N‰¶ N…¶ N¶ N}¶ Ny¶ Nu¶ Nq¶ Nm¶ Ni¶ Ne¶ Na¶ N]¶ NY¶ NU¶ NQ¶ NM¶ NI¶ NE¶ NA¶ N=¶ N9¶ N5¶ N1¶ N-¶ N)¶ N%¶ N!¶ N¶ N¶ N¶ N¶ N ¶ N ¶ N¶ N¶ Mý¶ Mù¶ Mõ¶ Mñ¶ Mí¶ Mé¶ Må¶ MᶠMݶ MÙ¶ MÕ¶ MѶ MͶ Mɶ MŶ MÁ¶ M½¶ M¹¶ Mµ¶ M±¶ M­¶ M©¶ M¥¶ M¡¶ M¶ M™¶ M•¶ M‘¶ M¶ M‰¶ M…¶ M¶ M}¶ My¶ Mu¶ Mq¶ Mm¶ Mi¶ Me¶ Ma¶ M]¶ MY¶ MU¶ MQ¶ MM¶ MI¶ ME¶ MA¶ M=¶ M9¶ M5¶ M1¶ M-¶ M)¶ M%¶ M!¶ M¶ M¶ M¶ M¶ M ¶ M ¶ M¶ M¶ Lý¶ Lù¶ Lõ¶ Lñ¶ Lí¶ Lé¶ Lå¶ LᶠLݶ LÙ¶ LÕ¶ LѶ LͶ Lɶ LŶ LÁ¶ L½¶ L¹¶ Lµ¶ L±¶ L­¶ L©¶ L¥¶ L¡¶ L¶ L™¶ L•¶ L‘¶ L¶ L‰¶ L…¶ L¶ L}¶ Ly¶ Lu¶ Lq¶ Lm¶ Li¶ Le¶ La¶ L]¶ LY¶ LU¶ LQ¶ LM¶ LI¶ LE¶ LA¶ L=¶ L9¶ L5¶ L1¶ L-¶ L)¶ L%¶ L!¶ L¶ L¶ L¶ L¶ L ¶ L ¶ L¶ L¶ Ký¶ Kù¶ Kõ¶ Kñ¶ Kí¶ Ké¶ Kå¶ KᶠKݶ KÙ¶ KÕ¶ KѶ KͶ Kɶ KŶ KÁ¶ K½¶ K¹¶ Kµ¶ K±¶ K­¶ K©¶ K¥¶ K¡¶ K¶ K™¶ K•¶ K‘¶ K¶ K‰¶ K…¶ K¶ K}¶ Ky¶ Ku¶ Kq¶ Km¶ Ki¶ Ke¶ Ka¶ K]¶ KY¶ KU¶ KQ¶ KM¶ KI¶ KE¶ KA¶ K=¶ K9¶ K5¶ K1¶ K-¶ K)¶ K%¶ K!¶ K¶ K¶ K¶ K¶ K ¶ K ¶ K¶ K¶ Jý¶ Jù¶ Jõ¶ Jñ¶ Jí¶ Jé¶ Jå¶ JᶠJݶ JÙ¶ JÕ¶ JѶ JͶ Jɶ JŶ JÁ¶ J½¶ J¹¶ Jµ¶ J±¶ J­¶ J©¶ J¥¶ J¡¶ J¶ J™¶ J•¶ J‘¶ J¶ J‰¶ J…¶ J¶ J}¶ Jy¶ Ju¶ Jq¶ Jm¶ Ji¶ Je¶ Ja¶ J]¶ JY¶ JU¶ JQ¶ JM¶ JI¶ JE¶ JA¶ J=¶ J9¶ J5¶ J1¶ J-¶ J)¶ J%¶ J!¶ J¶ J¶ J¶ J¶ J ¶ J ¶ J¶ J¶ Iý¶ Iù¶ Iõ¶ Iñ¶ Ií¶ Ié¶ Iå¶ IᶠIݶ IÙ¶ IÕ¶ IѶ IͶ Iɶ IŶ IÁ¶ I½¶ I¹¶ Iµ¶ I±¶ I­¶ I©¶ I¥¶ I¡¶ I¶ I™¶ I•¶ I‘¶ I¶ I‰¶ I…¶ I¶ I}¶ Iy¶ Iu¶ Iq¶ Im¶ Ii¶ Ie¶ Ia¶ I]¶ IY¶ IU¶ IQ¶ IM¶ II¶ IE¶ IA¶ I=¶ I9¶ I5¶ I1¶ I-¶ I)¶ I%¶ I!¶ I¶ I¶ I¶ I¶ I ¶ I ¶ I¶ I¶ Hý¶ Hù¶ Hõ¶ Hñ¶ Hí¶ Hé¶ Hå¶ HᶠHݶ HÙ¶ HÕ¶ HѶ HͶ Hɶ HŶ HÁ¶ H½¶ H¹¶ Hµ¶ H±¶ H­¶ H©¶ H¥¶ H¡¶ H¶ H™¶ H•¶ H‘¶ H¶ H‰¶ H…¶ H¶ H}¶ Hy¶ Hu¶ Hq¶ Hm¶ Hi¶ He¶ Ha¶ H]¶ HY¶ HU¶ HQ¶ HM¶ HI¶ HE¶ HA¶ H=¶ H9¶ H5¶ H1¶ H-¶ H)¶ H%¶ H!¶ H¶ H¶ H¶ H¶ H ¶ H ¶ H¶ H¶ Gý¶ Gù¶ Gõ¶ Gñ¶ Gí¶ Gé¶ Gå¶ GᶠGݶ GÙ¶ GÕ¶ GѶ GͶ Gɶ GŶ GÁ¶ G½¶ G¹¶ Gµ¶ G±¶ G­¶ G©¶ G¥¶ G¡¶ G¶ G™¶ G•¶ G‘¶ G¶ G‰¶ G…¶ G¶ G}¶ Gy¶ Gu¶ Gq¶ Gm¶ Gi¶ Ge¶ Ga¶ G]¶ GY¶ GU¶ GQ¶ GM¶ GI¶ GE¶ GA¶ G=¶ G9¶ G5¶ G1¶ G-¶ G)¶ G%¶ G!¶ G¶ G¶ G¶ G¶ G ¶ G ¶ G¶ G¶ Fý¶ Fù¶ Fõ¶ Fñ¶ Fí¶ Fé¶ Få¶ FᶠFݶ FÙ¶ FÕ¶ FѶ FͶ Fɶ FŶ FÁ¶ F½¶ F¹¶ Fµ¶ F±¶ F­¶ F©¶ F¥¶ F¡¶ F¶ F™¶ F•¶ F‘¶ F¶ F‰¶ F…¶ F¶ F}¶ Fy¶ Fu¶ Fq¶ Fm¶ Fi¶ Fe¶ Fa¶ F]¶ FY¶ FU¶ FQ¶ FM¶ FI¶ FE¶ FA¶ F=¶ F9¶ F5¶ F1¶ F-¶ F)¶ F%¶ F!¶ F¶ F¶ F¶ F¶ F ¶ F ¶ F¶ F¶ Eý¶ Eù¶ Eõ¶ Eñ¶ Eí¶ Eé¶ Eå¶ EᶠEݶ EÙ¶ EÕ¶ EѶ EͶ Eɶ EŶ EÁ¶ E½¶ E¹¶ Eµ¶ E±¶ E­¶ E©¶ E¥¶ E¡¶ E¶ E™¶ E•¶ E‘¶ E¶ E‰¶ E…¶ E¶ E}¶ Ey¶ Eu¶ Eq¶ Em¶ Ei¶ Ee¶ Ea¶ E]¶ EY¶ EU¶ EQ¶ EM¶ EI¶ EE¶ EA¶ E=¶ E9¶ E5¶ E1¶ E-¶ E)¶ E%¶ E!¶ E¶ E¶ E¶ E¶ E ¶ E ¶ E¶ E¶ Dý¶ Dù¶ Dõ¶ Dñ¶ Dí¶ Dé¶ Då¶ DᶠDݶ DÙ¶ DÕ¶ DѶ DͶ Dɶ DŶ DÁ¶ D½¶ D¹¶ Dµ¶ D±¶ D­¶ D©¶ D¥¶ D¡¶ D¶ D™¶ D•¶ D‘¶ D¶ D‰¶ D…¶ D¶ D}¶ Dy¶ Du¶ Dq¶ Dm¶ Di¶ De¶ Da¶ D]¶ DY¶ DU¶ DQ¶ DM¶ DI¶ DE¶ DA¶ D=¶ D9¶ D5¶ D1¶ D-¶ D)¶ D%¶ D!¶ D¶ D¶ D¶ D¶ D ¶ D ¶ D¶ D¶ Cý¶ Cù¶ Cõ¶ Cñ¶ Cí¶ Cé¶ Cå¶ CᶠCݶ CÙ¶ CÕ¶ CѶ CͶ Cɶ CŶ CÁ¶ C½¶ C¹¶ Cµ¶ C±¶ C­¶ C©¶ C¥¶ C¡¶ C¶ C™¶ C•¶ C‘¶ C¶ C‰¶ C…¶ C¶ C}¶ Cy¶ Cu¶ Cq¶ Cm¶ Ci¶ Ce¶ Ca¶ C]¶ CY¶ CU¶ CQ¶ CM¶ CI¶ CE¶ CA¶ C=¶ C9¶ C5¶ C1¶ C-¶ C)¶ C%¶ C!¶ C¶ C¶ C¶ C¶ C ¶ C ¶ C¶ C¶ Bý¶ Bù¶ Bõ¶ Bñ¶ Bí¶ Bé¶ Bå¶ BᶠBݶ BÙ¶ BÕ¶ BѶ BͶ Bɶ BŶ BÁ¶ B½¶ B¹¶ Bµ¶ B±¶ B­¶ B©¶ B¥¶ B¡¶ B¶ B™¶ B•¶ B‘¶ B¶ B‰¶ B…¶ B¶ B}¶ By¶ Bu¶ Bq¶ Bm¶ Bi¶ Be¶ Ba¶ B]¶ BY¶ BU¶ BQ¶ BM¶ BI¶ BE¶ BA¶ B=¶ B9¶ B5¶ B1¶ B-¶ B)¶ B%¶ B!¶ B¶ B¶ B¶ B¶ B ¶ B ¶ B¶ B¶ Aý¶ Aù¶ Aõ¶ Añ¶ Aí¶ Aé¶ Aå¶ AᶠAݶ AÙ¶ AÕ¶ AѶ AͶ Aɶ AŶ AÁ¶ A½¶ A¹¶ Aµ¶ A±¶ A­¶ A©¶ A¥¶ A¡¶ A¶ A™¶ A•¶ A‘¶ A¶ A‰¶ A…¶ A¶ A}¶ Ay¶ Au¶ Aq¶ Am¶ Ai¶ Ae¶ Aa¶ A]¶ AY¶ AU¶ AQ¶ AM¶ AI¶ AE¶ AA¶ A=¶ A9¶ A5¶ A1¶ A-¶ A)¶ A%¶ A!¶ A¶ A¶ A¶ A¶ A ¶ A ¶ A¶ A¶ @ý¶ @ù¶ @õ¶ @ñ¶ @í¶ @é¶ @å¶ @ᶠ@ݶ @Ù¶ @Õ¶ @Ѷ @Ͷ @ɶ @Ŷ @Á¶ @½¶ @¹¶ @µ¶ @±¶ @­¶ @©¶ @¥¶ @¡¶ @¶ @™¶ @•¶ @‘¶ @¶ @‰¶ @…¶ @¶ @}¶ @y¶ @u¶ @q¶ @m¶ @i¶ @e¶ @a¶ @]¶ @Y¶ @U¶ @Q¶ @M¶ @I¶ @E¶ @A¶ @=¶ @9¶ @5¶ @1¶ @-¶ @)¶ @%¶ @!¶ @¶ @¶ @¶ @¶ @ ¶ @ ¶ @¶ @¶þ¶û¶ø¶õ¶ò¶ï¶ì¶é¶æ¶ã¶à¶ݶÚ¶׶Ô¶Ѷζ˶ȶŶ¶¿¶¼¶¹¶¶¶³¶°¶­¶ª¶§¶¤¶¡¶ž¶›¶˜¶•¶’¶¶Œ¶‰¶†¶ƒ¶€¶}¶z¶w¶t¶q¶n¶k¶h¶e¶b¶_¶\¶Y¶V¶S¶P¶M¶J¶G¶D¶A¶>¶;¶8¶5¶2¶/¶,¶)¶&¶#¶ ¶¶¶¶¶¶¶ ¶¶¶¶~ÿ¶~ü¶~ù¶~ö¶~ó¶~ð¶~í¶~ê¶~ç¶~ä¶~á¶~Þ¶~Û¶~ض~Õ¶~Ò¶~϶~̶~ɶ~ƶ~ö~À¶~½¶~º¶~·¶~´¶~±¶~®¶~«¶~¨¶~¥¶~¢¶~Ÿ¶~œ¶~™¶~–¶~“¶~¶~¶~Š¶~‡¶~„¶~¶~~¶~{¶~x¶~u¶~r¶~o¶~l¶~i¶~f¶~c¶~`¶~]¶~Z¶~W¶~T¶~Q¶~N¶~K¶~H¶~E¶~B¶~?¶~<¶~9¶~6¶~3¶~0¶~-¶~*¶~'¶~$¶~!¶~¶~¶~¶~¶~¶~¶~ ¶~ ¶~¶~¶~
+¶}¶}¶}¶|þ¶|û¶|ø¶|õ¶|ò¶|ï¶|ì¶|é¶|æ¶|ã¶|à¶|ݶ|Ú¶|׶|Ô¶|Ѷ|ζ|˶|ȶ|Ŷ|¶|¿¶|¼¶|¹¶|¶¶|³¶|°¶|­¶|ª¶|§¶|¤¶|¡¶|ž¶|›¶|˜¶|•¶|’¶|¶|Œ¶|‰¶|†¶|ƒ¶|€¶|}¶|z¶|w¶|t¶|q¶|n¶|k¶|h¶|e¶|b¶|_¶|\¶|Y¶|V¶|S¶|P¶|M¶|J¶|G¶|D¶|A¶|>¶|;¶|8¶|5¶|2¶|/¶|,¶|)¶|&¶|#¶| ¶|¶|¶|¶|¶|¶|¶| ¶|¶|¶|¶{ÿ¶{ü¶{ù¶{ö¶{ó¶{ð¶{í¶{ê¶{ç¶{ä¶{á¶{Þ¶{Û¶{ض{Õ¶{Ò¶{϶{̶{ɶ{ƶ{ö{À¶{½¶{º¶{·¶{´¶{±¶{®¶{«¶{¨¶{¥¶{¢¶{Ÿ¶{œ¶{™¶{–¶{“¶{¶{¶{Š¶{‡¶{„¶{¶{~¶{{¶{x¶{u¶{r¶{o¶{l¶{i¶{f¶{c¶{`¶{]¶{Z¶{W¶{T¶{Q¶{N¶{K¶{H¶{E¶{B¶{?¶{<¶{9¶{6¶{3¶{0¶{-¶{*¶{'¶{$¶{!¶{¶{¶{¶{¶{¶{¶{ ¶{ ¶{¶{¶{
+¶z¶z¶z¶yþ¶yû¶yø¶yõ¶yò¶yï¶yì¶yé¶yæ¶yã¶yà¶yݶyÚ¶y׶yÔ¶yѶyζy˶yȶyŶy¶y¿¶y¼¶y¹¶y¶¶y³¶y°¶y­¶yª¶y§¶y¤¶y¡¶yž¶y›¶y˜¶y•¶y’¶y¶yŒ¶y‰¶y†¶yƒ¶y€¶y}¶yz¶yw¶yt¶yq¶yn¶yk¶yh¶ye¶yb¶y_¶y\¶yY¶yV¶yS¶yP¶yM¶yJ¶yG¶yD¶yA¶y>¶y;¶y8¶y5¶y2¶y/¶y,¶y)¶y&¶y#¶y ¶y¶y¶y¶y¶y¶y¶y ¶y¶y¶y¶xÿ¶xü¶xù¶xö¶xó¶xð¶xí¶xê¶xç¶xä¶xá¶xÞ¶xÛ¶xضxÕ¶xÒ¶x϶x̶xɶxƶxöxÀ¶x½¶xº¶x·¶x´¶x±¶x®¶x«¶x¨¶x¥¶x¢¶xŸ¶xœ¶x™¶x–¶x“¶x¶x¶xŠ¶x‡¶x„¶x¶x~¶x{¶xx¶xu¶xr¶xo¶xl¶xi¶xf¶xc¶x`¶x]¶xZ¶xW¶xT¶xQ¶xN¶xK¶xH¶xE¶xB¶x?¶x<¶x9¶x6¶x3¶x0¶x-¶x*¶x'¶x$¶x!¶x¶x¶x¶x¶x¶x¶x ¶x ¶x¶x¶x
+¶w¶w¶w¶vþ¶vû¶vø¶võ¶vò¶vï¶vì¶vé¶væ¶vã¶và¶vݶvÚ¶v׶vÔ¶vѶvζv˶vȶvŶv¶v¿¶v¼¶v¹¶v¶¶v³¶v°¶v­¶vª¶v§¶v¤¶v¡¶vž¶v›¶v˜¶v•¶v’¶v¶vŒ¶v‰¶v†¶vƒ¶v€¶v}¶vz¶vw¶vt¶vq¶vn¶vk¶vh¶ve¶vb¶v_¶v\¶vY¶vV¶vS¶vP¶vM¶vJ¶vG¶vD¶vA¶v>¶v;¶v8¶v5¶v2¶v/¶v,¶v)¶v&¶v#¶v ¶v¶v¶v¶v¶v¶v¶v ¶v¶v¶v¶uÿ¶uü¶uù¶uö¶uó¶uð¶uí¶uê¶uç¶uä¶uá¶uÞ¶uÛ¶uضuÕ¶uÒ¶u϶u̶uɶuƶuöuÀ¶u½¶uº¶u·¶u´¶u±¶u®¶u«¶u¨¶u¥¶u¢¶uŸ¶uœ¶u™¶u–¶u“¶u¶u¶uŠ¶u‡¶u„¶u¶u~¶u{¶ux¶uu¶ur¶uo¶ul¶ui¶uf¶uc¶u`¶u]¶uZ¶uW¶uT¶uQ¶uN¶uK¶uH¶uE¶uB¶u?¶u<¶u9¶u6¶u3¶u0¶u-¶u*¶u'¶u$¶u!¶u¶u¶u¶u¶u¶u¶u ¶u ¶u¶u¶u
+¶t¶t¶t¶sþ¶sû¶sø¶sõ¶sò¶sï¶sì¶sé¶sæ¶sã¶sà¶sݶsÚ¶s׶sÔ¶sѶsζs˶sȶsŶs¶s¿¶s¼¶s¹¶s¶¶s³¶s°¶s­¶sª¶s§¶s¤¶s¡¶sž¶s›¶s˜¶s•¶s’¶s¶sŒ¶s‰¶s†¶sƒ¶s€¶s}¶sz¶sw¶st¶sq¶sn¶sk¶sh¶se¶sb¶s_¶s\¶sY¶sV¶sS¶sP¶sM¶sJ¶sG¶sD¶sA¶s>¶s;¶s8¶s5¶s2¶s/¶s,¶s)¶s&¶s#¶s ¶s¶s¶s¶s¶s¶s¶s ¶s¶s¶s¶rÿ¶rü¶rù¶rö¶ró¶rð¶rí¶rê¶rç¶rä¶rá¶rÞ¶rÛ¶rضrÕ¶rÒ¶r϶r̶rɶrƶrörÀ¶r½¶rº¶r·¶r´¶r±¶r®¶r«¶r¨¶r¥¶r¢¶rŸ¶rœ¶r™¶r–¶r“¶r¶r¶rŠ¶r‡¶r„¶r¶r~¶r{¶rx¶ru¶rr¶ro¶rl¶ri¶rf¶rc¶r`¶r]¶rZ¶rW¶rT¶rQ¶rN¶rK¶rH¶rE¶rB¶r?¶r<¶r9¶r6¶r3¶r0¶r-¶r*¶r'¶r$¶r!¶r¶r¶r¶r¶r¶r¶r ¶r ¶r¶r¶r
+¶q¶q¶q¶pþ¶pû¶pø¶põ¶pò¶pï¶pì¶pé¶pæ¶pã¶pà¶pݶpÚ¶p׶pÔ¶pѶpζp˶pȶpŶp¶p¿¶p¼¶p¹¶p¶¶p³¶p°¶p­¶pª¶p§¶p¤¶p¡¶pž¶p›¶p˜¶p•¶p’¶p¶pŒ¶p‰¶p†¶pƒ¶p€¶p}¶pz¶pw¶pt¶pq¶pn¶pk¶ph¶pe¶pb¶p_¶p\¶pY¶pV¶pS¶pP¶pM¶pJ¶pG¶pD¶pA¶p>¶p;¶p8¶p5¶p2¶p/¶p,¶p)¶p&¶p#¶p ¶p¶p¶p¶p¶p¶p¶p ¶p¶p¶p¶oÿ¶oü¶où¶oö¶oó¶oð¶oí¶oê¶oç¶oä¶oá¶oÞ¶oÛ¶oضoÕ¶oÒ¶o϶o̶oɶoƶoöoÀ¶o½¶oº¶o·¶o´¶o±¶o®¶o«¶o¨¶o¥¶o¢¶oŸ¶oœ¶o™¶o–¶o“¶o¶o¶oŠ¶o‡¶o„¶o¶o~¶o{¶ox¶ou¶or¶oo¶ol¶oi¶of¶oc¶o`¶o]¶oZ¶oW¶oT¶oQ¶oN¶oK¶oH¶oE¶oB¶o?¶o<¶o9¶o6¶o3¶o0¶o-¶o*¶o'¶o$¶o!¶o¶o¶o¶o¶o¶o¶o ¶o ¶o¶o¶o
+¶n¶n¶n¶mþ¶mû¶mø¶mõ¶mò¶mï¶mì¶mé¶mæ¶mã¶mà¶mݶmÚ¶m׶mÔ¶mѶmζm˶mȶmŶm¶m¿¶m¼¶m¹¶m¶¶m³¶m°¶m­¶mª¶m§¶m¤¶m¡¶mž¶m›¶m˜¶m•¶m’¶m¶mŒ¶m‰¶m†¶mƒ¶m€¶m}¶mz¶mw¶mt¶mq¶mn¶mk¶mh¶me¶mb¶m_¶m\¶mY¶mV¶mS¶mP¶mM¶mJ¶mG¶mD¶mA¶m>¶m;¶m8¶m5¶m2¶m/¶m,¶m)¶m&¶m#¶m ¶m¶m¶m¶m¶m¶m¶m ¶m¶m¶m¶lÿ¶lü¶lù¶lö¶ló¶lð¶lí¶lê¶lç¶lä¶lá¶lÞ¶lÛ¶lضlÕ¶lÒ¶l϶l̶lɶlƶlölÀ¶l½¶lº¶l·¶l´¶l±¶l®¶l«¶l¨¶l¥¶l¢¶lŸ¶lœ¶l™¶l–¶l“¶l¶l¶lŠ¶l‡¶l„¶l¶l~¶l{¶lx¶lu¶lr¶lo¶ll¶li¶lf¶lc¶l`¶l]¶lZ¶lW¶lT¶lQ¶lN¶lK¶lH¶lE¶lB¶l?¶l<¶l9¶l6¶l3¶l0¶l-¶l*¶l'¶l$¶l!¶l¶l¶l¶l¶l¶l¶l ¶l ¶l¶l¶l
+¶k¶k¶k¶jþ¶jû¶jø¶jõ¶jò¶jï¶jì¶jé¶jæ¶jã¶jà¶jݶjÚ¶j׶jÔ¶jѶjζj˶jȶjŶj¶j¿¶j¼¶j¹¶j¶¶j³¶j°¶j­¶jª¶j§¶j¤¶j¡¶jž¶j›¶j˜¶j•¶j’¶j¶jŒ¶j‰¶j†¶jƒ¶j€¶j}¶jz¶jw¶jt¶jq¶jn¶jk¶jh¶je¶jb¶j_¶j\¶jY¶jV¶jS¶jP¶jM¶jJ¶jG¶jD¶jA¶j>¶j;¶j8¶j5¶j2¶j/¶j,¶j)¶j&¶j#¶j ¶j¶j¶j¶j¶j¶j¶j ¶j¶j¶j¶iÿ¶iü¶iù¶iö¶ió¶ið¶ií¶iê¶iç¶iä¶iá¶iÞ¶iÛ¶iضiÕ¶iÒ¶i϶i̶iɶiƶiöiÀ¶i½¶iº¶i·¶i´¶i±¶i®¶i«¶i¨¶i¥¶i¢¶iŸ¶iœ¶i™¶i–¶i“¶i¶i¶iŠ¶i‡¶i„¶i¶i~¶i{¶ix¶iu¶ir¶io¶il¶ii¶if¶ic¶i`¶i]¶iZ¶iW¶iT¶iQ¶iN¶iK¶iH¶iE¶iB¶i?¶i<¶i9¶i6¶i3¶i0¶i-¶i*¶i'¶i$¶i!¶i¶i¶i¶i¶i¶i¶i ¶i ¶i¶i¶i
+¶h¶h¶h¶gþ¶gû¶gø¶gõ¶gò¶gï¶gì¶gé¶gæ¶gã¶gà¶gݶgÚ¶g׶gÔ¶gѶgζg˶gȶgŶg¶g¿¶g¼¶g¹¶g¶¶g³¶g°¶g­¶gª¶g§¶g¤¶g¡¶gž¶g›¶g˜¶g•¶g’¶g¶gŒ¶g‰¶g†¶gƒ¶g€¶g}¶gz¶gw¶gt¶gq¶gn¶gk¶gh¶ge¶gb¶g_¶g\¶gY¶gV¶gS¶gP¶gM¶gJ¶gG¶gD¶gA¶g>¶g;¶g8¶g5¶g2¶g/¶g,¶g)¶g&¶g#¶g ¶g¶g¶g¶g¶g¶g¶g ¶g¶g¶g¶fÿ¶fü¶fù¶fö¶fó¶fð¶fí¶fê¶fç¶fä¶fá¶fÞ¶fÛ¶fضfÕ¶fÒ¶f϶f̶fɶfƶföfÀ¶f½¶fº¶f·¶f´¶f±¶f®¶f«¶f¨¶f¥¶f¢¶fŸ¶fœ¶f™¶f–¶f“¶f¶f¶fŠ¶f‡¶f„¶f¶f~¶f{¶fx¶fu¶fr¶fo¶fl¶fi¶ff¶fc¶f`¶f]¶fZ¶fW¶fT¶fQ¶fN¶fK¶fH¶fE¶fB¶f?¶f<¶f9¶f6¶f3¶f0¶f-¶f*¶f'¶f$¶f!¶f¶f¶f¶f¶f¶f¶f ¶f ¶f¶f¶f
+¶e¶e¶e¶dþ¶dû¶dø¶dõ¶dò¶dï¶dì¶dé¶dæ¶dã¶dà¶dݶdÚ¶d׶dÔ¶dѶdζd˶dȶdŶd¶d¿¶d¼¶d¹¶d¶¶d³¶d°¶d­¶dª¶d§¶d¤¶d¡¶dž¶d›¶d˜¶d•¶d’¶d¶dŒ¶d‰¶d†¶dƒ¶d€¶d}¶dz¶dw¶dt¶dq¶dn¶dk¶dh¶de¶db¶d_¶d\¶dY¶dV¶dS¶dP¶dM¶dJ¶dG¶dD¶dA¶d>¶d;¶d8¶d5¶d2¶d/¶d,¶d)¶d&¶d#¶d ¶d¶d¶d¶d¶d¶d¶d ¶d¶d¶d¶cÿ¶cü¶cù¶cö¶có¶cð¶cí¶cê¶cç¶cä¶cá¶cÞ¶cÛ¶cضcÕ¶cÒ¶c϶c̶cɶcƶcöcÀ¶c½¶cº¶c·¶c´¶c±¶c®¶c«¶c¨¶c¥¶c¢¶cŸ¶cœ¶c™¶c–¶c“¶c¶c¶cŠ¶c‡¶c„¶c¶c~¶c{¶cx¶cu¶cr¶co¶cl¶ci¶cf¶cc¶c`¶c]¶cZ¶cW¶cT¶cQ¶cN¶cK¶cH¶cE¶cB¶c?¶c<¶c9¶c6¶c3¶c0¶c-¶c*¶c'¶c$¶c!¶c¶c¶c¶c¶c¶c¶c ¶c ¶c¶c¶c
+¶b¶b¶b¶aþ¶aû¶aø¶aõ¶aò¶aï¶aì¶aé¶aæ¶aã¶aà¶aݶaÚ¶a׶aÔ¶aѶaζa˶aȶaŶa¶a¿¶a¼¶a¹¶a¶¶a³¶a°¶a­¶aª¶a§¶a¤¶a¡¶až¶a›¶a˜¶a•¶a’¶a¶aŒ¶a‰¶a†¶aƒ¶a€¶a}¶az¶aw¶at¶aq¶an¶ak¶ah¶ae¶ab¶a_¶a\¶aY¶aV¶aS¶aP¶aM¶aJ¶aG¶aD¶aA¶a>¶a;¶a8¶a5¶a2¶a/¶a,¶a)¶a&¶a#¶a ¶a¶a¶a¶a¶a¶a¶a ¶a¶a¶a¶`ÿ¶`ü¶`ù¶`ö¶`ó¶`ð¶`í¶`ê¶`ç¶`ä¶`á¶`Þ¶`Û¶`ض`Õ¶`Ò¶`϶`̶`ɶ`ƶ`ö`À¶`½¶`º¶`·¶`´¶`±¶`®¶`«¶`¨¶`¥¶`¢¶`Ÿ¶`œ¶`™¶`–¶`“¶`¶`¶`Š¶`‡¶`„¶`¶`~¶`{¶`x¶`u¶`r¶`o¶`l¶`i¶`f¶`c¶``¶`]¶`Z¶`W¶`T¶`Q¶`N¶`K¶`H¶`E¶`B¶`?¶`<¶`9¶`6¶`3¶`0¶`-¶`*¶`'¶`$¶`!¶`¶`¶`¶`¶`¶`¶` ¶` ¶`¶`¶`
+¶_¶_¶_¶^þ¶^û¶^ø¶^õ¶^ò¶^ï¶^ì¶^é¶^æ¶^ã¶^à¶^ݶ^Ú¶^׶^Ô¶^Ѷ^ζ^˶^ȶ^Ŷ^¶^¿¶^¼¶^¹¶^¶¶^³¶^°¶^­¶^ª¶^§¶^¤¶^¡¶^ž¶^›¶^˜¶^•¶^’¶^¶^Œ¶^‰¶^†¶^ƒ¶^€¶^}¶^z¶^w¶^t¶^q¶^n¶^k¶^h¶^e¶^b¶^_¶^\¶^Y¶^V¶^S¶^P¶^M¶^J¶^G¶^D¶^A¶^>¶^;¶^8¶^5¶^2¶^/¶^,¶^)¶^&¶^#¶^ ¶^¶^¶^¶^¶^¶^¶^ ¶^¶^¶^¶]ÿ¶]ü¶]ù¶]ö¶]ó¶]ð¶]í¶]ê¶]ç¶]ä¶]á¶]Þ¶]Û¶]ض]Õ¶]Ò¶]϶]̶]ɶ]ƶ]ö]À¶]½¶]º¶]·¶]´¶]±¶]®¶]«¶]¨¶]¥¶]¢¶]Ÿ¶]œ¶]™¶]–¶]“¶]¶]¶]Š¶]‡¶]„¶]¶]~¶]{¶]x¶]u¶]r¶]o¶]l¶]i¶]f¶]c¶]`¶]]¶]Z¶]W¶]T¶]Q¶]N¶]K¶]H¶]E¶]B¶]?¶]<¶]9¶]6¶]3¶]0¶]-¶]*¶]'¶]$¶]!¶]¶]¶]¶]¶]¶]¶] ¶] ¶]¶]¶]
+¶\¶\¶\¶[þ¶[û¶[ø¶[õ¶[ò¶[ï¶[ì¶[é¶[æ¶[ã¶[à¶[ݶ[Ú¶[׶[Ô¶[Ѷ[ζ[˶[ȶ[Ŷ[¶[¿¶[¼¶[¹¶[¶¶[³¶[°¶[­¶[ª¶[§¶[¤¶[¡¶[ž¶[›¶[˜¶[•¶[’¶[¶[Œ¶[‰¶[†¶[ƒ¶[€¶[}¶[z¶[w¶[t¶[q¶[n¶[k¶[h¶[e¶[b¶[_¶[\¶[Y¶[V¶[S¶[P¶[M¶[J¶[G¶[D¶[A¶[>¶[;¶[8¶[5¶[2¶[/¶[,¶[)¶[&¶[#¶[ ¶[¶[¶[¶[¶[¶[¶[ ¶[¶[¶[¶Zÿ¶Zü¶Zù¶Zö¶Zó¶Zð¶Zí¶Zê¶Zç¶Zä¶Zá¶ZÞ¶ZÛ¶ZضZÕ¶ZÒ¶Z϶Z̶ZɶZƶZöZÀ¶Z½¶Zº¶Z·¶Z´¶Z±¶Z®¶Z«¶Z¨¶Z¥¶Z¢¶ZŸ¶Zœ¶Z™¶Z–¶Z“¶Z¶Z¶ZŠ¶Z‡¶Z„¶Z¶Z~¶Z{¶Zx¶Zu¶Zr¶Zo¶Zl¶Zi¶Zf¶Zc¶Z`¶Z]¶ZZ¶ZW¶ZT¶ZQ¶ZN¶ZK¶ZH¶ZE¶ZB¶Z?¶Z<¶Z9¶Z6¶Z3¶Z0¶Z-¶Z*¶Z'¶Z$¶Z!¶Z¶Z¶Z¶Z¶Z¶Z¶Z ¶Z ¶Z¶Z¶Z
+¶Y¶Y¶Y¶Xþ¶Xû¶Xø¶Xõ¶Xò¶Xï¶Xì¶Xé¶Xæ¶Xã¶Xà¶XݶXÚ¶X׶XÔ¶XѶXζX˶XȶXŶX¶X¿¶X¼¶X¹¶X¶¶X³¶X°¶X­¶Xª¶X§¶X¤¶X¡¶Xž¶X›¶X˜¶X•¶X’¶X¶XŒ¶X‰¶X†¶Xƒ¶X€¶X}¶Xz¶Xw¶Xt¶Xq¶Xn¶Xk¶Xh¶Xe¶Xb¶X_¶X\¶XY¶XV¶XS¶XP¶XM¶XJ¶XG¶XD¶XA¶X>¶X;¶X8¶X5¶X2¶X/¶X,¶X)¶X&¶X#¶X ¶X¶X¶X¶X¶X¶X¶X ¶X¶X¶X¶Wÿ¶Wü¶Wù¶Wö¶Wó¶Wð¶Wí¶Wê¶Wç¶Wä¶Wá¶WÞ¶WÛ¶WضWÕ¶WÒ¶W϶W̶WɶWƶWöWÀ¶W½¶Wº¶W·¶W´¶W±¶W®¶W«¶W¨¶W¥¶W¢¶WŸ¶Wœ¶W™¶W–¶W“¶W¶W¶WŠ¶W‡¶W„¶W¶W~¶W{¶Wx¶Wu¶Wr¶Wo¶Wl¶Wi¶Wf¶Wc¶W`¶W]¶WZ¶WW¶WT¶WQ¶WN¶WK¶WH¶WE¶WB¶W?¶W<¶W9¶W6¶W3¶W0¶W-¶W*¶W'¶W$¶W!¶W¶W¶W¶W¶W¶W¶W ¶W ¶W¶W¶W
+¶V¶V¶V¶Uþ¶Uû¶Uø¶Uõ¶Uò¶Uï¶Uì¶Ué¶Uæ¶Uã¶Uà¶UݶUÚ¶U׶UÔ¶UѶUζU˶UȶUŶU¶U¿¶U¼¶U¹¶U¶¶U³¶U°¶U­¶Uª¶U§¶U¤¶U¡¶Už¶U›¶U˜¶U•¶U’¶U¶UŒ¶U‰¶U†¶Uƒ¶U€¶U}¶Uz¶Uw¶Ut¶Uq¶Un¶Uk¶Uh¶Ue¶Ub¶U_¶U\¶UY¶UV¶US¶UP¶UM¶UJ¶UG¶UD¶UA¶U>¶U;¶U8¶U5¶U2¶U/¶U,¶U)¶U&¶U#¶U ¶U¶U¶U¶U¶U¶U¶U ¶U¶U¶U¶Tÿ¶Tü¶Tù¶Tö¶Tó¶Tð¶Tí¶Tê¶Tç¶Tä¶Tá¶TÞ¶TÛ¶TضTÕ¶TÒ¶T϶T̶TɶTƶTöTÀ¶T½¶Tº¶T·¶T´¶T±¶T®¶T«¶T¨¶T¥¶T¢¶TŸ¶Tœ¶T™¶T–¶T“¶T¶T¶TŠ¶T‡¶T„¶T¶T~¶T{¶Tx¶Tu¶Tr¶To¶Tl¶Ti¶Tf¶Tc¶T`¶T]¶TZ¶TW¶TT¶TQ¶TN¶TK¶TH¶TE¶TB¶T?¶T<¶T9¶T6¶T3¶T0¶T-¶T*¶T'¶T$¶T!¶T¶T¶T¶T¶T¶T¶T ¶T ¶T¶T¶T
+¶S¶S¶S¶Rþ¶Rû¶Rø¶Rõ¶Rò¶Rï¶Rì¶Ré¶Ræ¶Rã¶Rà¶RݶRÚ¶R׶RÔ¶RѶRζR˶RȶRŶR¶R¿¶R¼¶R¹¶R¶¶R³¶R°¶R­¶Rª¶R§¶R¤¶R¡¶Rž¶R›¶R˜¶R•¶R’¶R¶RŒ¶R‰¶R†¶Rƒ¶R€¶R}¶Rz¶Rw¶Rt¶Rq¶Rn¶Rk¶Rh¶Re¶Rb¶R_¶R\¶RY¶RV¶RS¶RP¶RM¶RJ¶RG¶RD¶RA¶R>¶R;¶R8¶R5¶R2¶R/¶R,¶R)¶R&¶R#¶R ¶R¶R¶R¶R¶R¶R¶R ¶R¶R¶R¶Qÿ¶Qü¶Qù¶Qö¶Qó¶Qð¶Qí¶Qê¶Qç¶Qä¶Qá¶QÞ¶QÛ¶QضQÕ¶QÒ¶Q϶Q̶QɶQƶQöQÀ¶Q½¶Qº¶Q·¶Q´¶Q±¶Q®¶Q«¶Q¨¶Q¥¶Q¢¶QŸ¶Qœ¶Q™¶Q–¶Q“¶Q¶Q¶QŠ¶Q‡¶Q„¶Q¶Q~¶Q{¶Qx¶Qu¶Qr¶Qo¶Ql¶Qi¶Qf¶Qc¶Q`¶Q]¶QZ¶QW¶QT¶QQ¶QN¶QK¶QH¶QE¶QB¶Q?¶Q<¶Q9¶Q6¶Q3¶Q0¶Q-¶Q*¶Q'¶Q$¶Q!¶Q¶Q¶Q¶Q¶Q¶Q¶Q ¶Q ¶Q¶Q¶Q
+¶P¶P¶P¶Oþ¶Oû¶Oø¶Oõ¶Oò¶Oï¶Oì¶Oé¶Oæ¶Oã¶Oà¶OݶOÚ¶O׶OÔ¶OѶOζO˶OȶOŶO¶O¿¶O¼¶O¹¶O¶¶O³¶O°¶O­¶Oª¶O§¶O¤¶O¡¶Ož¶O›¶O˜¶O•¶O’¶O¶OŒ¶O‰¶O†¶Oƒ¶O€¶O}¶Oz¶Ow¶Ot¶Oq¶On¶Ok¶Oh¶Oe¶Ob¶O_¶O\¶OY¶OV¶OS¶OP¶OM¶OJ¶OG¶OD¶OA¶O>¶O;¶O8¶O5¶O2¶O/¶O,¶O)¶O&¶O#¶O ¶O¶O¶O¶O¶O¶O¶O ¶O¶O¶O¶Nÿ¶Nü¶Nù¶Nö¶Nó¶Nð¶Ní¶Nê¶Nç¶Nä¶Ná¶NÞ¶NÛ¶NضNÕ¶NÒ¶N϶N̶NɶNƶNöNÀ¶N½¶Nº¶N·¶N´¶N±¶N®¶N«¶N¨¶N¥¶N¢¶NŸ¶Nœ¶N™¶N–¶N“¶N¶N¶NŠ¶N‡¶N„¶N¶N~¶N{¶Nx¶Nu¶Nr¶No¶Nl¶Ni¶Nf¶Nc¶N`¶N]¶NZ¶NW¶NT¶NQ¶NN¶NK¶NH¶NE¶NB¶N?¶N<¶N9¶N6¶N3¶N0¶N-¶N*¶N'¶N$¶N!¶N¶N¶N¶N¶N¶N¶N ¶N ¶N¶N¶N
+¶M¶M¶M¶Lþ¶Lû¶Lø¶Lõ¶Lò¶Lï¶Lì¶Lé¶Læ¶Lã¶Là¶LݶLÚ¶L׶LÔ¶LѶLζL˶LȶLŶL¶L¿¶L¼¶L¹¶L¶¶L³¶L°¶L­¶Lª¶L§¶L¤¶L¡¶Lž¶L›¶L˜¶L•¶L’¶L¶LŒ¶L‰¶L†¶Lƒ¶L€¶L}¶Lz¶Lw¶Lt¶Lq¶Ln¶Lk¶Lh¶Le¶Lb¶L_¶L\¶LY¶LV¶LS¶LP¶LM¶LJ¶LG¶LD¶LA¶L>¶L;¶L8¶L5¶L2¶L/¶L,¶L)¶L&¶L#¶L ¶L¶L¶L¶L¶L¶L¶L ¶L¶L¶L¶Kÿ¶Kü¶Kù¶Kö¶Kó¶Kð¶Kí¶Kê¶Kç¶Kä¶Ká¶KÞ¶KÛ¶KضKÕ¶KÒ¶K϶K̶KɶKƶKöKÀ¶K½¶Kº¶K·¶K´¶K±¶K®¶K«¶K¨¶K¥¶K¢¶KŸ¶Kœ¶K™¶K–¶K“¶K¶K¶KŠ¶K‡¶K„¶K¶K~¶K{¶Kx¶Ku¶Kr¶Ko¶Kl¶Ki¶Kf¶Kc¶K`¶K]¶KZ¶KW¶KT¶KQ¶KN¶KK¶KH¶KE¶KB¶K?¶K<¶K9¶K6¶K3¶K0¶K-¶K*¶K'¶K$¶K!¶K¶K¶K¶K¶K¶K¶K ¶K ¶K¶K¶K
+¶J¶J¶J¶Iþ¶Iû¶Iø¶Iõ¶Iò¶Iï¶Iì¶Ié¶Iæ¶Iã¶Ià¶IݶIÚ¶I׶IÔ¶IѶIζI˶IȶIŶI¶I¿¶I¼¶I¹¶I¶¶I³¶I°¶I­¶Iª¶I§¶I¤¶I¡¶Iž¶I›¶I˜¶I•¶I’¶I¶IŒ¶I‰¶I†¶Iƒ¶I€¶I}¶Iz¶Iw¶It¶Iq¶In¶Ik¶Ih¶Ie¶Ib¶I_¶I\¶IY¶IV¶IS¶IP¶IM¶IJ¶IG¶ID¶IA¶I>¶I;¶I8¶I5¶I2¶I/¶I,¶I)¶I&¶I#¶I ¶I¶I¶I¶I¶I¶I¶I ¶I¶I¶I¶Hÿ¶Hü¶Hù¶Hö¶Hó¶Hð¶Hí¶Hê¶Hç¶Hä¶Há¶HÞ¶HÛ¶HضHÕ¶HÒ¶H϶H̶HɶHƶHöHÀ¶H½¶Hº¶H·¶H´¶H±¶H®¶H«¶H¨¶H¥¶H¢¶HŸ¶Hœ¶H™¶H–¶H“¶H¶H¶HŠ¶H‡¶H„¶H¶H~¶H{¶Hx¶Hu¶Hr¶Ho¶Hl¶Hi¶Hf¶Hc¶H`¶H]¶HZ¶HW¶HT¶HQ¶HN¶HK¶HH¶HE¶HB¶H?¶H<¶H9¶H6¶H3¶H0¶H-¶H*¶H'¶H$¶H!¶H¶H¶H¶H¶H¶H¶H ¶H ¶H¶H¶H
+¶G¶G¶G¶Fþ¶Fû¶Fø¶Fõ¶Fò¶Fï¶Fì¶Fé¶Fæ¶Fã¶Fà¶FݶFÚ¶F׶FÔ¶FѶFζF˶FȶFŶF¶F¿¶F¼¶F¹¶F¶¶F³¶F°¶F­¶Fª¶F§¶F¤¶F¡¶Fž¶F›¶F˜¶F•¶F’¶F¶FŒ¶F‰¶F†¶Fƒ¶F€¶F}¶Fz¶Fw¶Ft¶Fq¶Fn¶Fk¶Fh¶Fe¶Fb¶F_¶F\¶FY¶FV¶FS¶FP¶FM¶FJ¶FG¶FD¶FA¶F>¶F;¶F8¶F5¶F2¶F/¶F,¶F)¶F&¶F#¶F ¶F¶F¶F¶F¶F¶F¶F ¶F¶F¶F¶Eÿ¶Eü¶Eù¶Eö¶Eó¶Eð¶Eí¶Eê¶Eç¶Eä¶Eá¶EÞ¶EÛ¶EضEÕ¶EÒ¶E϶E̶EɶEƶEöEÀ¶E½¶Eº¶E·¶E´¶E±¶E®¶E«¶E¨¶E¥¶E¢¶EŸ¶Eœ¶E™¶E–¶E“¶E¶E¶EŠ¶E‡¶E„¶E¶E~¶E{¶Ex¶Eu¶Er¶Eo¶El¶Ei¶Ef¶Ec¶E`¶E]¶EZ¶EW¶ET¶EQ¶EN¶EK¶EH¶EE¶EB¶E?¶E<¶E9¶E6¶E3¶E0¶E-¶E*¶E'¶E$¶E!¶E¶E¶E¶E¶E¶E¶E ¶E ¶E¶E¶E
+¶D¶D¶D¶Cþ¶Cû¶Cø¶Cõ¶Cò¶Cï¶Cì¶Cé¶Cæ¶Cã¶Cà¶CݶCÚ¶C׶CÔ¶CѶCζC˶CȶCŶC¶C¿¶C¼¶C¹¶C¶¶C³¶C°¶C­¶Cª¶C§¶C¤¶C¡¶Cž¶C›¶C˜¶C•¶C’¶C¶CŒ¶C‰¶C†¶Cƒ¶C€¶C}¶Cz¶Cw¶Ct¶Cq¶Cn¶Ck¶Ch¶Ce¶Cb¶C_¶C\¶CY¶CV¶CS¶CP¶CM¶CJ¶CG¶CD¶CA¶C>¶C;¶C8¶C5¶C2¶C/¶C,¶C)¶C&¶C#¶C ¶C¶C¶C¶C¶C¶C¶C ¶C¶C¶C¶Bÿ¶Bü¶Bù¶Bö¶Bó¶Bð¶Bí¶Bê¶Bç¶Bä¶Bá¶BÞ¶BÛ¶BضBÕ¶BÒ¶B϶B̶BɶBƶBöBÀ¶B½¶Bº¶B·¶B´¶B±¶B®¶B«¶B¨¶B¥¶B¢¶BŸ¶Bœ¶B™¶B–¶B“¶B¶B¶BŠ¶B‡¶B„¶B¶B~¶B{¶Bx¶Bu¶Br¶Bo¶Bl¶Bi¶Bf¶Bc¶B`¶B]¶BZ¶BW¶BT¶BQ¶BN¶BK¶BH¶BE¶BB¶B?¶B<¶B9¶B6¶B3¶B0¶B-¶B*¶B'¶B$¶B!¶B¶B¶B¶B¶B¶B¶B ¶B ¶B¶B¶B
+¶A¶A¶A¶@þ¶@û¶@ø¶@õ¶@ò¶@ï¶@ì¶@é¶@æ¶@ã¶@à¶@ݶ@Ú¶@׶@Ô¶@Ѷ@ζ@˶@ȶ@Ŷ@¶@¿¶@¼¶@¹¶@¶¶@³¶@°¶@­¶@ª¶@§¶@¤¶@¡¶@ž¶@›¶@˜¶@•¶@’¶@¶@Œ¶@‰¶@†¶@ƒ¶@€¶þ¶ü¶ú¶ø¶ö¶ô¶ò¶ð¶î¶ì¶ê¶è¶æ¶ä¶â¶à¶Þ¶Ü¶Ú¶Ø¶Ö¶Ô¶Ò¶Ð¶Î¶Ì¶Ê¶È¶Æ¶Ä¶Â¶À¶¾¶¼¶º¶¸¶¶¶´¶²¶°¶®¶¬¶ª¶¨¶¦¶¤¶¢¶ ¶ž¶œ¶š¶˜¶–¶”¶’¶¶Ž¶Œ¶Š¶ˆ¶†¶„¶‚¶€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a8468104c65a6002fd3a9d4ac39f3ee34c21ce4a b/webm_parser/fuzzing/corpus/a8468104c65a6002fd3a9d4ac39f3ee34c21ce4a
new file mode 100644
index 0000000..9b15205
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a8468104c65a6002fd3a9d4ac39f3ee34c21ce4a
@@ -0,0 +1 @@
+S€gTÃgŠss‡cÀ„hÊÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a899424027f1d69a05384355858311a6fa3940a3 b/webm_parser/fuzzing/corpus/a899424027f1d69a05384355858311a6fa3940a3
new file mode 100644
index 0000000..3ef41ca
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a899424027f1d69a05384355858311a6fa3940a3
@@ -0,0 +1 @@
+S€gT®kˆ®†à„T° \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/a8a5de5a86a16952aacdf602120f27807294c3ec b/webm_parser/fuzzing/corpus/a8a5de5a86a16952aacdf602120f27807294c3ec
new file mode 100644
index 0000000..865b44e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/a8a5de5a86a16952aacdf602120f27807294c3ec
@@ -0,0 +1 @@
+S€g‘TÃgŒss‰cÀ†hʃ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/aa65229e62d7cb8048d2f5911226b177e8e53cab b/webm_parser/fuzzing/corpus/aa65229e62d7cb8048d2f5911226b177e8e53cab
new file mode 100644
index 0000000..80476c0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/aa65229e62d7cb8048d2f5911226b177e8e53cab
@@ -0,0 +1 @@
+S€gŠT®k…®ƒ×ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/aaa96713f8ccb0bbd5c8e91715d4b86e7b338676 b/webm_parser/fuzzing/corpus/aaa96713f8ccb0bbd5c8e91715d4b86e7b338676
new file mode 100644
index 0000000..1fa146d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/aaa96713f8ccb0bbd5c8e91715d4b86e7b338676
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/aae6354e5ba12ee3ad89fabfd72f5368a639d30f b/webm_parser/fuzzing/corpus/aae6354e5ba12ee3ad89fabfd72f5368a639d30f
new file mode 100644
index 0000000..fb16013
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/aae6354e5ba12ee3ad89fabfd72f5368a639d30f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/ab6f3dc497f93f251ebacc153409b1eb8e05e2db b/webm_parser/fuzzing/corpus/ab6f3dc497f93f251ebacc153409b1eb8e05e2db
new file mode 100644
index 0000000..182a5d9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ab6f3dc497f93f251ebacc153409b1eb8e05e2db
@@ -0,0 +1 @@
+EߣˆB‚ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/abf5b18d1c1155d3e455c8b781948498f364965e b/webm_parser/fuzzing/corpus/abf5b18d1c1155d3e455c8b781948498f364965e
new file mode 100644
index 0000000..e8c81d0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/abf5b18d1c1155d3e455c8b781948498f364965e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/abfe5dbf594a2f22173fae7ca8de28b4e3a40099 b/webm_parser/fuzzing/corpus/abfe5dbf594a2f22173fae7ca8de28b4e3a40099
new file mode 100644
index 0000000..f0c009c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/abfe5dbf594a2f22173fae7ca8de28b4e3a40099
@@ -0,0 +1 @@
+S€gŒT®k‡®…àƒ°ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ac842f2cc55d7f193273a35f8af3521bfe2317d0 b/webm_parser/fuzzing/corpus/ac842f2cc55d7f193273a35f8af3521bfe2317d0
new file mode 100644
index 0000000..77a070c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ac842f2cc55d7f193273a35f8af3521bfe2317d0
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/ac923c36dd85ff5cb2a0c5b29c701999c5f2eaa0 b/webm_parser/fuzzing/corpus/ac923c36dd85ff5cb2a0c5b29c701999c5f2eaa0
new file mode 100644
index 0000000..6375039
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ac923c36dd85ff5cb2a0c5b29c701999c5f2eaa0
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/acc6e100b519d6408a8c6d8aed19203d874a187a b/webm_parser/fuzzing/corpus/acc6e100b519d6408a8c6d8aed19203d874a187a
new file mode 100644
index 0000000..2a83db5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/acc6e100b519d6408a8c6d8aed19203d874a187a
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/aceb250195867e7d9bbf6eb6e0055251e4bb5d67 b/webm_parser/fuzzing/corpus/aceb250195867e7d9bbf6eb6e0055251e4bb5d67
new file mode 100644
index 0000000..c75ee54
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/aceb250195867e7d9bbf6eb6e0055251e4bb5d67
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/acf1dd17e6a2848dfe17fb0d76cef2ce7d59ebdd b/webm_parser/fuzzing/corpus/acf1dd17e6a2848dfe17fb0d76cef2ce7d59ebdd
new file mode 100644
index 0000000..3f65b28
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/acf1dd17e6a2848dfe17fb0d76cef2ce7d59ebdd
@@ -0,0 +1 @@
+S€gTÃgŠss‡cÀ„hÊ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ad94c2bda2ae4afa0f7264070fd642bf37aae596 b/webm_parser/fuzzing/corpus/ad94c2bda2ae4afa0f7264070fd642bf37aae596
new file mode 100644
index 0000000..038d2d9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ad94c2bda2ae4afa0f7264070fd642bf37aae596
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/adad2ca7ab313add6e955f704719e03d5229e4d0 b/webm_parser/fuzzing/corpus/adad2ca7ab313add6e955f704719e03d5229e4d0
new file mode 100644
index 0000000..2105af1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/adad2ca7ab313add6e955f704719e03d5229e4d0
@@ -0,0 +1 @@
+ã \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/adb1f8cd3b68c076d81311071aee2a1c3785c7cb b/webm_parser/fuzzing/corpus/adb1f8cd3b68c076d81311071aee2a1c3785c7cb
new file mode 100644
index 0000000..4710f7f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/adb1f8cd3b68c076d81311071aee2a1c3785c7cb
@@ -0,0 +1 @@
+S€gŒT®k‡®…œƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ade30c327d9f51daf37cb3e39fdb15125b84a257 b/webm_parser/fuzzing/corpus/ade30c327d9f51daf37cb3e39fdb15125b84a257
new file mode 100644
index 0000000..4bc05f4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ade30c327d9f51daf37cb3e39fdb15125b84a257
@@ -0,0 +1 @@
+S€gT®kˆ®†à„S¸ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ae783661f52d36f9d6b87c8394ee6f2d61dae640 b/webm_parser/fuzzing/corpus/ae783661f52d36f9d6b87c8394ee6f2d61dae640
new file mode 100644
index 0000000..d5fe0e2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ae783661f52d36f9d6b87c8394ee6f2d61dae640
@@ -0,0 +1 @@
+S€g“M›tŽM»‹S«ˆMatroska \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ae8da06c3c69076c06108219e19c8b36dba05a5c b/webm_parser/fuzzing/corpus/ae8da06c3c69076c06108219e19c8b36dba05a5c
new file mode 100644
index 0000000..78a347a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ae8da06c3c69076c06108219e19c8b36dba05a5c
@@ -0,0 +1 @@
+Eߣ8S€g;€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/aec8a29238dba3c37f45f2e2e4e64b0e7fa60a74 b/webm_parser/fuzzing/corpus/aec8a29238dba3c37f45f2e2e4e64b0e7fa60a74
new file mode 100644
index 0000000..359bfe6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/aec8a29238dba3c37f45f2e2e4e64b0e7fa60a74
@@ -0,0 +1 @@
+S€gI©f‹D‰ˆ?ùãw›—ô¨ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/affba8e4061bf260b1bdf8a815675cc3b4ebefa5 b/webm_parser/fuzzing/corpus/affba8e4061bf260b1bdf8a815675cc3b4ebefa5
new file mode 100644
index 0000000..94b2f2d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/affba8e4061bf260b1bdf8a815675cc3b4ebefa5
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b01c4e2657a230e2b600a20da6f107b0dfe2467c b/webm_parser/fuzzing/corpus/b01c4e2657a230e2b600a20da6f107b0dfe2467c
new file mode 100644
index 0000000..de433e0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b01c4e2657a230e2b600a20da6f107b0dfe2467c
@@ -0,0 +1 @@
+S€gTÃgŠss‡gÈ„Dz! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b0798ecae4d9e56eefb0c1d95b657865938e62d6 b/webm_parser/fuzzing/corpus/b0798ecae4d9e56eefb0c1d95b657865938e62d6
new file mode 100644
index 0000000..f323a5e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b0798ecae4d9e56eefb0c1d95b657865938e62d6
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b0ab4f92df810edf4371baad1d4bbd95d360c607 b/webm_parser/fuzzing/corpus/b0ab4f92df810edf4371baad1d4bbd95d360c607
new file mode 100644
index 0000000..02f58e2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b0ab4f92df810edf4371baad1d4bbd95d360c607
@@ -0,0 +1 @@
+S€gŒC¶u‡ …›ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b14e5f5f6d29b6b71e7fec03eaf7d8237c0c6b2e b/webm_parser/fuzzing/corpus/b14e5f5f6d29b6b71e7fec03eaf7d8237c0c6b2e
new file mode 100644
index 0000000..32796f0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b14e5f5f6d29b6b71e7fec03eaf7d8237c0c6b2e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b1708df7f6dee0f2feb11f8b6330be0fcffc1f58 b/webm_parser/fuzzing/corpus/b1708df7f6dee0f2feb11f8b6330be0fcffc1f58
new file mode 100644
index 0000000..4a4eccf
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b1708df7f6dee0f2feb11f8b6330be0fcffc1f58
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b1af4f4e86890fad6fac96a23956405c550778be b/webm_parser/fuzzing/corpus/b1af4f4e86890fad6fac96a23956405c550778be
new file mode 100644
index 0000000..77bb0f0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b1af4f4e86890fad6fac96a23956405c550778be
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b1efd9e687d79e3f5a75eba02bd80bedb72350ba b/webm_parser/fuzzing/corpus/b1efd9e687d79e3f5a75eba02bd80bedb72350ba
new file mode 100644
index 0000000..348fa2c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b1efd9e687d79e3f5a75eba02bd80bedb72350ba
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b25b0fbf46cef1d888fe900445c9ab95330f44cd b/webm_parser/fuzzing/corpus/b25b0fbf46cef1d888fe900445c9ab95330f44cd
new file mode 100644
index 0000000..b59c594
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b25b0fbf46cef1d888fe900445c9ab95330f44cd
@@ -0,0 +1 @@
+™ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b2786e0b9d6e7b39165eb4e87e3110362e9e6660 b/webm_parser/fuzzing/corpus/b2786e0b9d6e7b39165eb4e87e3110362e9e6660
new file mode 100644
index 0000000..6004cde
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b2786e0b9d6e7b39165eb4e87e3110362e9e6660
@@ -0,0 +1 @@
+EߣˆB‚@webmS€g©C¶u¤ç@«@ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b332536a77d6efcd379cfc2f9828291516cc1ff4 b/webm_parser/fuzzing/corpus/b332536a77d6efcd379cfc2f9828291516cc1ff4
new file mode 100644
index 0000000..2082647
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b332536a77d6efcd379cfc2f9828291516cc1ff4
@@ -0,0 +1 @@
+S€g–T®k‘®m€Œb@‰P5†Gçƒ'è€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b33962eca397f59591dbb9668e622cf99f2d7cda b/webm_parser/fuzzing/corpus/b33962eca397f59591dbb9668e622cf99f2d7cda
new file mode 100644
index 0000000..9f6ee1d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b33962eca397f59591dbb9668e622cf99f2d7cda
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b38b6e93da1b43441f88aa53370a9f00b35c3326 b/webm_parser/fuzzing/corpus/b38b6e93da1b43441f88aa53370a9f00b35c3326
new file mode 100644
index 0000000..7700315
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b38b6e93da1b43441f88aa53370a9f00b35c3326
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b3e01674a1e4dd78e748782fcfc3add5523f51d8 b/webm_parser/fuzzing/corpus/b3e01674a1e4dd78e748782fcfc3add5523f51d8
new file mode 100644
index 0000000..3416407
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b3e01674a1e4dd78e748782fcfc3add5523f51d8
@@ -0,0 +1 @@
+× \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b53e7ef9aad70fcd80986696ebc586c03495b8ed b/webm_parser/fuzzing/corpus/b53e7ef9aad70fcd80986696ebc586c03495b8ed
new file mode 100644
index 0000000..ee865d8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b53e7ef9aad70fcd80986696ebc586c03495b8ed
@@ -0,0 +1 @@
+GáEߣˆB‚@webmS€g£*M'›t€I©f€C¶u€TSkk»€®€C§p€TÀ†g€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b540412c01f960f95edd2a1bc03f1b4447f2b4f2 b/webm_parser/fuzzing/corpus/b540412c01f960f95edd2a1bc03f1b4447f2b4f2
new file mode 100644
index 0000000..b77bc28
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b540412c01f960f95edd2a1bc03f1b4447f2b4f2
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b62c2c591db32b26e997aa4ece577742db84428a b/webm_parser/fuzzing/corpus/b62c2c591db32b26e997aa4ece577742db84428a
new file mode 100644
index 0000000..64a70ac
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b62c2c591db32b26e997aa4ece577742db84428a
@@ -0,0 +1 @@
+S€g“C¶uŽ ŒŽ‡èƒÌè€Ì \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b62d99583f30c15c3c2dbed2f69c5e45075d7640 b/webm_parser/fuzzing/corpus/b62d99583f30c15c3c2dbed2f69c5e45075d7640
new file mode 100644
index 0000000..edf7ec3
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b62d99583f30c15c3c2dbed2f69c5e45075d7640
@@ -0,0 +1 @@
+{©€gˆC¶uƒ« \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b62f98976c11d79674b019ea78a7ce4d6d78b479 b/webm_parser/fuzzing/corpus/b62f98976c11d79674b019ea78a7ce4d6d78b479
new file mode 100644
index 0000000..6a4f186
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b62f98976c11d79674b019ea78a7ce4d6d78b479
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b6787dabeb5cd64ac85f1ec5a7cbeecfd48b2c5f b/webm_parser/fuzzing/corpus/b6787dabeb5cd64ac85f1ec5a7cbeecfd48b2c5f
new file mode 100644
index 0000000..d043095
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b6787dabeb5cd64ac85f1ec5a7cbeecfd48b2c5f
@@ -0,0 +1 @@
+S€g¼C§p·E¹´¶‘|°…@hello€€€€€€€€€…@B€@…@C¶@ sÄ€€€€€€„€–€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b82ebcf4d09ba28d835cb9667da603e46e3438eb b/webm_parser/fuzzing/corpus/b82ebcf4d09ba28d835cb9667da603e46e3438eb
new file mode 100644
index 0000000..cea92d6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b82ebcf4d09ba28d835cb9667da603e46e3438eb
@@ -0,0 +1 @@
+S€gC§p‹E¹ˆ¶†€„C~! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/b85c6bc2473aa12e22f91db3546dfa9d85d8b3d1 b/webm_parser/fuzzing/corpus/b85c6bc2473aa12e22f91db3546dfa9d85d8b3d1
new file mode 100644
index 0000000..5f82c67
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b85c6bc2473aa12e22f91db3546dfa9d85d8b3d1
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b86e98660980680890bcbf02cacdf568d530fa64 b/webm_parser/fuzzing/corpus/b86e98660980680890bcbf02cacdf568d530fa64
new file mode 100644
index 0000000..207162b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b86e98660980680890bcbf02cacdf568d530fa64
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/b8d9beea35762009941189674c2cfcd14f81254a b/webm_parser/fuzzing/corpus/b8d9beea35762009941189674c2cfcd14f81254a
new file mode 100644
index 0000000..25ec34a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/b8d9beea35762009941189674c2cfcd14f81254a
@@ -0,0 +1 @@
+S€gŒT®k‡®…ˆƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ba517141fc9a468142a8d03d4ee395b4d4f30edc b/webm_parser/fuzzing/corpus/ba517141fc9a468142a8d03d4ee395b4d4f30edc
new file mode 100644
index 0000000..aaea60d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ba517141fc9a468142a8d03d4ee395b4d4f30edc
@@ -0,0 +1 @@
+S€cŽDg‰ss†cS€g‡SÀ»€gÈk€‚»€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/bb0a596017cfc2185505d28065ab3cd238d0e2ea b/webm_parser/fuzzing/corpus/bb0a596017cfc2185505d28065ab3cd238d0e2ea
new file mode 100644
index 0000000..1379811
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/bb0a596017cfc2185505d28065ab3cd238d0e2ea
@@ -0,0 +1 @@
+S€gˆC§pƒE¹€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/bb6232815b373e441e2acfcca015f75c680eafac b/webm_parser/fuzzing/corpus/bb6232815b373e441e2acfcca015f75c680eafac
new file mode 100644
index 0000000..04a597c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/bb6232815b373e441e2acfcca015f75c680eafac
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/bbdf4fa36ba9d645399f72c74033fd9c2631aebb b/webm_parser/fuzzing/corpus/bbdf4fa36ba9d645399f72c74033fd9c2631aebb
new file mode 100644
index 0000000..d277d55
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/bbdf4fa36ba9d645399f72c74033fd9c2631aebb
@@ -0,0 +1 @@
+C|€C|€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/bc53c9f93974b4f13382c1d49b1e3ec374005ee2 b/webm_parser/fuzzing/corpus/bc53c9f93974b4f13382c1d49b1e3ec374005ee2
new file mode 100644
index 0000000..795792f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/bc53c9f93974b4f13382c1d49b1e3ec374005ee2
@@ -0,0 +1 @@
+S€gŒT®k‡®…ƒƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/bcacc9bcd3b9cc7dbda9c52c6e4a06a756a9de90 b/webm_parser/fuzzing/corpus/bcacc9bcd3b9cc7dbda9c52c6e4a06a756a9de90
new file mode 100644
index 0000000..b31cda9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/bcacc9bcd3b9cc7dbda9c52c6e4a06a756a9de90
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/bcc55f432bcf39c6ffc6d7e950612b35e9ea2ec8 b/webm_parser/fuzzing/corpus/bcc55f432bcf39c6ffc6d7e950612b35e9ea2ec8
new file mode 100644
index 0000000..1fb2833
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/bcc55f432bcf39c6ffc6d7e950612b35e9ea2ec8
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/bdb1cc868d6ced390f5d35c5f43da9f464fac464 b/webm_parser/fuzzing/corpus/bdb1cc868d6ced390f5d35c5f43da9f464fac464
new file mode 100644
index 0000000..44bb8cd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/bdb1cc868d6ced390f5d35c5f43da9f464fac464
@@ -0,0 +1 @@
+S€g»TÃg¶ss³gÈ©È„#‡dgÈ„]‡egÈ„D‡dgÈ„]‡egÈ„D‡gÈ€D‡dgÈ€g \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/bf67bb08e0abde692748031c297bc1c7910542f4 b/webm_parser/fuzzing/corpus/bf67bb08e0abde692748031c297bc1c7910542f4
new file mode 100644
index 0000000..8705e84
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/bf67bb08e0abde692748031c297bc1c7910542f4
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/bfc07c62ef2770d53d9188b260f531a8128a5c5e b/webm_parser/fuzzing/corpus/bfc07c62ef2770d53d9188b260f531a8128a5c5e
new file mode 100644
index 0000000..341f94d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/bfc07c62ef2770d53d9188b260f531a8128a5c5e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/bfed121df31ff73b770ed7f27fc7f4da2fa73e0c b/webm_parser/fuzzing/corpus/bfed121df31ff73b770ed7f27fc7f4da2fa73e0c
new file mode 100644
index 0000000..7b2c561
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/bfed121df31ff73b770ed7f27fc7f4da2fa73e0c
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c01816206d93691165e6e3a1924cf9fbc9cf39bd b/webm_parser/fuzzing/corpus/c01816206d93691165e6e3a1924cf9fbc9cf39bd
new file mode 100644
index 0000000..b99d57a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c01816206d93691165e6e3a1924cf9fbc9cf39bd
@@ -0,0 +1 @@
+S€–gT®k‘®m€Œb@‰P5ÿ†GçƒGè€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c02be525edae59ad9d0d9dcbd790629dc9aaee9b b/webm_parser/fuzzing/corpus/c02be525edae59ad9d0d9dcbd790629dc9aaee9b
new file mode 100644
index 0000000..67629fe
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c02be525edae59ad9d0d9dcbd790629dc9aaee9b
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c094ce0c13ee9a4ca37817d9f7dddc11b2d60177 b/webm_parser/fuzzing/corpus/c094ce0c13ee9a4ca37817d9f7dddc11b2d60177
new file mode 100644
index 0000000..9b44fb0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c094ce0c13ee9a4ca37817d9f7dddc11b2d60177
@@ -0,0 +1 @@
+S€g‘C¶uŒ Š¡‡¦…î:ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c0aab5486ad2e80bcc12fca9fb6653984aa68274 b/webm_parser/fuzzing/corpus/c0aab5486ad2e80bcc12fca9fb6653984aa68274
new file mode 100644
index 0000000..d643dc1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c0aab5486ad2e80bcc12fca9fb6653984aa68274
@@ -0,0 +1 @@
+E᜘ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c13ca850db259c032cf24cdf6f2833c9d74529d0 b/webm_parser/fuzzing/corpus/c13ca850db259c032cf24cdf6f2833c9d74529d0
new file mode 100644
index 0000000..46ddda2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c13ca850db259c032cf24cdf6f2833c9d74529d0
@@ -0,0 +1 @@
+EߣˆB‚@webmS€g©C¶uÿÿÿ€« \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c1a8da6cdc8988e6a69961413803acbd1ee935e0 b/webm_parser/fuzzing/corpus/c1a8da6cdc8988e6a69961413803acbd1ee935e0
new file mode 100644
index 0000000..80a14f7
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c1a8da6cdc8988e6a69961413803acbd1ee935e0
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶³sÄVTA‘@’@€@…@B€@…@C¶@sĶ@sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c25abc82a0470129f2d098ac65fabf34c4e11188 b/webm_parser/fuzzing/corpus/c25abc82a0470129f2d098ac65fabf34c4e11188
new file mode 100644
index 0000000..774e5f1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c25abc82a0470129f2d098ac65fabf34c4e11188
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c2787d2cf1d95cbcd8b9bcd15d50f67f7e92ad9f b/webm_parser/fuzzing/corpus/c2787d2cf1d95cbcd8b9bcd15d50f67f7e92ad9f
new file mode 100644
index 0000000..53359f3
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c2787d2cf1d95cbcd8b9bcd15d50f67f7e92ad9f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c2895ff545ebdc4140951c0ca956524d7a364b77 b/webm_parser/fuzzing/corpus/c2895ff545ebdc4140951c0ca956524d7a364b77
new file mode 100644
index 0000000..61489a2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c2895ff545ebdc4140951c0ca956524d7a364b77
@@ -0,0 +1 @@
+²Eߣ‡B \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c2cc55849ff4858bf80f1a4713187618d14a496d b/webm_parser/fuzzing/corpus/c2cc55849ff4858bf80f1a4713187618d14a496d
new file mode 100644
index 0000000..b511eed
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c2cc55849ff4858bf80f1a4713187618d14a496d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c2d63b1d75cf53ee3b955bb143036ca93ef3a256 b/webm_parser/fuzzing/corpus/c2d63b1d75cf53ee3b955bb143036ca93ef3a256
new file mode 100644
index 0000000..3fbf8e4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c2d63b1d75cf53ee3b955bb143036ca93ef3a256
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c372a27f78a62ebc013958fa4953a8bc792db53c b/webm_parser/fuzzing/corpus/c372a27f78a62ebc013958fa4953a8bc792db53c
new file mode 100644
index 0000000..8faacee
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c372a27f78a62ebc013958fa4953a8bc792db53c
@@ -0,0 +1 @@
+Eߣÿÿÿÿÿÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c392963b395a7f92b3bef63fd34bc31ecd3029f3 b/webm_parser/fuzzing/corpus/c392963b395a7f92b3bef63fd34bc31ecd3029f3
new file mode 100644
index 0000000..91487ca
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c392963b395a7f92b3bef63fd34bc31ecd3029f3
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c3b2749ab6c4d303bfd5da9ea9c8807e9f92d259 b/webm_parser/fuzzing/corpus/c3b2749ab6c4d303bfd5da9ea9c8807e9f92d259
new file mode 100644
index 0000000..e94d737
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c3b2749ab6c4d303bfd5da9ea9c8807e9f92d259
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c4dd3c8cdd8d7c95603dd67f1cd873d5f9148b29 b/webm_parser/fuzzing/corpus/c4dd3c8cdd8d7c95603dd67f1cd873d5f9148b29
new file mode 100644
index 0000000..c5fa784
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c4dd3c8cdd8d7c95603dd67f1cd873d5f9148b29
@@ -0,0 +1 @@
+< \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c52fa8d16e520980e470d76e3fb4f6a612f0f3cf b/webm_parser/fuzzing/corpus/c52fa8d16e520980e470d76e3fb4f6a612f0f3cf
new file mode 100644
index 0000000..d614d45
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c52fa8d16e520980e470d76e3fb4f6a612f0f3cf
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c56fb214efcd707e6fa68b803d9e7686fc2f4336 b/webm_parser/fuzzing/corpus/c56fb214efcd707e6fa68b803d9e7686fc2f4336
new file mode 100644
index 0000000..e03fe14
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c56fb214efcd707e6fa68b803d9e7686fc2f4336
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶žsÄVTA‘@’@€@…@B€@…@C¶@sĶ@sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c5902afe54998ebc5d8d59043227d379a8c2eee0 b/webm_parser/fuzzing/corpus/c5902afe54998ebc5d8d59043227d379a8c2eee0
new file mode 100644
index 0000000..d71d573
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c5902afe54998ebc5d8d59043227d379a8c2eee0
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c6a16abeea323833079e97b1830610aa6c6eba91 b/webm_parser/fuzzing/corpus/c6a16abeea323833079e97b1830610aa6c6eba91
new file mode 100644
index 0000000..b48967d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c6a16abeea323833079e97b1830610aa6c6eba91
@@ -0,0 +1 @@
+€gk‹®TEߣˆB‚@®‰á‡xµ„@ÉÛwebmS€g©C¶uÿÿÿ€« \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c6fcfd2a1f91a7f6a8c124f2637e60645be01006 b/webm_parser/fuzzing/corpus/c6fcfd2a1f91a7f6a8c124f2637e60645be01006
new file mode 100644
index 0000000..5aaebfb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c6fcfd2a1f91a7f6a8c124f2637e60645be01006
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c75b02a90370df1f54de2f63a4da8db22f2cf719 b/webm_parser/fuzzing/corpus/c75b02a90370df1f54de2f63a4da8db22f2cf719
new file mode 100644
index 0000000..0cb101b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c75b02a90370df1f54de2f63a4da8db22f2cf719
@@ -0,0 +1 @@
+ŸEßE£‚B; \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c77cb763d73db279aebfb42d2b5dca3d705d3df7 b/webm_parser/fuzzing/corpus/c77cb763d73db279aebfb42d2b5dca3d705d3df7
new file mode 100644
index 0000000..cae0031
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c77cb763d73db279aebfb42d2b5dca3d705d3df7
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c864ac8bce0353ecc7d5cb0ce5a1e77a5239201e b/webm_parser/fuzzing/corpus/c864ac8bce0353ecc7d5cb0ce5a1e77a5239201e
new file mode 100644
index 0000000..d745027
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c864ac8bce0353ecc7d5cb0ce5a1e77a5239201e
@@ -0,0 +1 @@
+ ÿM \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c877c08a79a7408aed779d4a430c5db1bce26314 b/webm_parser/fuzzing/corpus/c877c08a79a7408aed779d4a430c5db1bce26314
new file mode 100644
index 0000000..de29891
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c877c08a79a7408aed779d4a430c5db1bce26314
@@ -0,0 +1 @@
+S€gŒI©f‡D‰„@ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c8aff6e2e2dfb18be385483b871ac86ff6eac63f b/webm_parser/fuzzing/corpus/c8aff6e2e2dfb18be385483b871ac86ff6eac63f
new file mode 100644
index 0000000..d553636
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c8aff6e2e2dfb18be385483b871ac86ff6eac63f
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/c8c1c1f970bc809a75ad076bdb06275b6f72d078 b/webm_parser/fuzzing/corpus/c8c1c1f970bc809a75ad076bdb06275b6f72d078
new file mode 100644
index 0000000..f2f8214
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c8c1c1f970bc809a75ad076bdb06275b6f72d078
@@ -0,0 +1 @@
+Eߣ„B÷ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c995d52934ac188971f02d5dbdb3cdd70cb03267 b/webm_parser/fuzzing/corpus/c995d52934ac188971f02d5dbdb3cdd70cb03267
new file mode 100644
index 0000000..fe179c9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c995d52934ac188971f02d5dbdb3cdd70cb03267
@@ -0,0 +1 @@
+Tº…éJœœœœœœ¼œ"…¼¿À>sÅœŠ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/c9e7fc3e0e1015a1c15992447f678a495c3ea5dc b/webm_parser/fuzzing/corpus/c9e7fc3e0e1015a1c15992447f678a495c3ea5dc
new file mode 100644
index 0000000..b784d77
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/c9e7fc3e0e1015a1c15992447f678a495c3ea5dc
@@ -0,0 +1 @@
+S€g–C§p‘E¹Ž¶@sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ca5b619ce1bbe23d519f5764d53458d2b85eb9fa b/webm_parser/fuzzing/corpus/ca5b619ce1bbe23d519f5764d53458d2b85eb9fa
new file mode 100644
index 0000000..a7b7c19
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ca5b619ce1bbe23d519f5764d53458d2b85eb9fa
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/cb0556c65c7381192c94324a3b1b8afb7e33fecc b/webm_parser/fuzzing/corpus/cb0556c65c7381192c94324a3b1b8afb7e33fecc
new file mode 100644
index 0000000..2a7063e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cb0556c65c7381192c94324a3b1b8afb7e33fecc
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/cb1a093e6810c7f6c002a2a54ea390cf769c9949 b/webm_parser/fuzzing/corpus/cb1a093e6810c7f6c002a2a54ea390cf769c9949
new file mode 100644
index 0000000..6f2ab37
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cb1a093e6810c7f6c002a2a54ea390cf769c9949
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/cc1487af64aeefd7080e7678a04870dc85a7928a b/webm_parser/fuzzing/corpus/cc1487af64aeefd7080e7678a04870dc85a7928a
new file mode 100644
index 0000000..815b907
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cc1487af64aeefd7080e7678a04870dc85a7928a
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/cc71d2c9f5eae12acee133bd9e50d84881a1bd88 b/webm_parser/fuzzing/corpus/cc71d2c9f5eae12acee133bd9e50d84881a1bd88
new file mode 100644
index 0000000..2f87371
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cc71d2c9f5eae12acee133bd9e50d84881a1bd88
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/ccb8f962426683663972534c15354f70c3b34a10 b/webm_parser/fuzzing/corpus/ccb8f962426683663972534c15354f70c3b34a10
new file mode 100644
index 0000000..3b41d17
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ccb8f962426683663972534c15354f70c3b34a10
@@ -0,0 +1 @@
+S€g”T®k®m€Šb@‡P5„Gáÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/cd0771c4754dfcd9c89b9b8a02df96fed974850d b/webm_parser/fuzzing/corpus/cd0771c4754dfcd9c89b9b8a02df96fed974850d
new file mode 100644
index 0000000..519a312
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cd0771c4754dfcd9c89b9b8a02df96fed974850d
@@ -0,0 +1 @@
+’!EߣˆB‚@webm!S€g*M£'›t€I©f€C¶u€TSkk»€®€C§p€TÃg€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/cd8899c66cbd92ee57f94e000744b32662258ba3 b/webm_parser/fuzzing/corpus/cd8899c66cbd92ee57f94e000744b32662258ba3
new file mode 100644
index 0000000..42db6e8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cd8899c66cbd92ee57f94e000744b32662258ba3
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/cdf83138f69f0f7c66c13b56028610ac39038b3c b/webm_parser/fuzzing/corpus/cdf83138f69f0f7c66c13b56028610ac39038b3c
new file mode 100644
index 0000000..5d6c848
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cdf83138f69f0f7c66c13b56028610ac39038b3c
@@ -0,0 +1 @@
+€€€€€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ce0138cd7718397b365d4c15b0b14f666c307419 b/webm_parser/fuzzing/corpus/ce0138cd7718397b365d4c15b0b14f666c307419
new file mode 100644
index 0000000..56b36df
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ce0138cd7718397b365d4c15b0b14f666c307419
@@ -0,0 +1 @@
+Eߣ‡ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/cec5c7e50ef1d865c879563d1a6d677adb86695c b/webm_parser/fuzzing/corpus/cec5c7e50ef1d865c879563d1a6d677adb86695c
new file mode 100644
index 0000000..dea66a5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cec5c7e50ef1d865c879563d1a6d677adb86695c
@@ -0,0 +1 @@
+S€gT®kˆ®†à„Tº \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ceff1dfaf2de4e33d2e3c20aeb7ba4b98f97784c b/webm_parser/fuzzing/corpus/ceff1dfaf2de4e33d2e3c20aeb7ba4b98f97784c
new file mode 100644
index 0000000..1826f3d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ceff1dfaf2de4e33d2e3c20aeb7ba4b98f97784c
@@ -0,0 +1 @@
+S€gŠC¶u…«ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/cf04b5d28cea1971478806979b256a030a671541 b/webm_parser/fuzzing/corpus/cf04b5d28cea1971478806979b256a030a671541
new file mode 100644
index 0000000..48b0b4c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cf04b5d28cea1971478806979b256a030a671541
@@ -0,0 +1 @@
+S€g–T®k‘®m€Œb@‰P5†Gრ\ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/cf1cf6ad5b3554c3ffc86a859319445158665ea7 b/webm_parser/fuzzing/corpus/cf1cf6ad5b3554c3ffc86a859319445158665ea7
new file mode 100644
index 0000000..14d1fdb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cf1cf6ad5b3554c3ffc86a859319445158665ea7
@@ -0,0 +1 @@
+S®Š€g…k\"µ;®œŸƒ× \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/cf2338960588d4a5f02a47f3ee96a556224aad75 b/webm_parser/fuzzing/corpus/cf2338960588d4a5f02a47f3ee96a556224aad75
new file mode 100644
index 0000000..a0ed68e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cf2338960588d4a5f02a47f3ee96a556224aad75
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/cfb09018afa0eb1a829e556d9f8bceff40cb36bc b/webm_parser/fuzzing/corpus/cfb09018afa0eb1a829e556d9f8bceff40cb36bc
new file mode 100644
index 0000000..31fc352
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cfb09018afa0eb1a829e556d9f8bceff40cb36bc
@@ -0,0 +1 @@
+S€gI©f‹Daˆ4Vxš¼Þð \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/cfbe3d66d7eb3199440e8e911a93044cc3165c6c b/webm_parser/fuzzing/corpus/cfbe3d66d7eb3199440e8e911a93044cc3165c6c
new file mode 100644
index 0000000..ba138c4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/cfbe3d66d7eb3199440e8e911a93044cc3165c6c
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d0a91f7984904976de592cb68e8832853fbec9bc b/webm_parser/fuzzing/corpus/d0a91f7984904976de592cb68e8832853fbec9bc
new file mode 100644
index 0000000..d61a53a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d0a91f7984904976de592cb68e8832853fbec9bc
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d22f52563c16725ce4a924dc05d6ac7d64798c43 b/webm_parser/fuzzing/corpus/d22f52563c16725ce4a924dc05d6ac7d64798c43
new file mode 100644
index 0000000..bf45633
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d22f52563c16725ce4a924dc05d6ac7d64798c43
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d2709a1c9d96e72cb844eedca8bfe8440cdb0ef8 b/webm_parser/fuzzing/corpus/d2709a1c9d96e72cb844eedca8bfe8440cdb0ef8
new file mode 100644
index 0000000..c41d905
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d2709a1c9d96e72cb844eedca8bfe8440cdb0ef8
@@ -0,0 +1 @@
+S€gI©f‹Daˆþܺ˜vT2 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d2e385f61ad4b5d45a10a5a6cb85d72e84dea54b b/webm_parser/fuzzing/corpus/d2e385f61ad4b5d45a10a5a6cb85d72e84dea54b
new file mode 100644
index 0000000..433cbfd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d2e385f61ad4b5d45a10a5a6cb85d72e84dea54b
@@ -0,0 +1 @@
+@p€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d2facb213561b30a5bcd012e4e01d0d5b0b26957 b/webm_parser/fuzzing/corpus/d2facb213561b30a5bcd012e4e01d0d5b0b26957
new file mode 100644
index 0000000..26ca861
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d2facb213561b30a5bcd012e4e01d0d5b0b26957
@@ -0,0 +1 @@
+S€gT®kŠ®ˆà†T²ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d300e5e46a825e5892fae1ba3c466c836f9e1da2 b/webm_parser/fuzzing/corpus/d300e5e46a825e5892fae1ba3c466c836f9e1da2
new file mode 100644
index 0000000..7b6f1d8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d300e5e46a825e5892fae1ba3c466c836f9e1da2
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d302c69c881e230e6433283007d318ba437025c3 b/webm_parser/fuzzing/corpus/d302c69c881e230e6433283007d318ba437025c3
new file mode 100644
index 0000000..055ab79
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d302c69c881e230e6433283007d318ba437025c3
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šm€‡b@„P3ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d30c1b65bc141d1a15d4ed622ba182c96dacdf92 b/webm_parser/fuzzing/corpus/d30c1b65bc141d1a15d4ed622ba182c96dacdf92
new file mode 100644
index 0000000..aeb5e2e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d30c1b65bc141d1a15d4ed622ba182c96dacdf92
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d3adf09fe6fb1534157c1dc68eb61365b46b1c35 b/webm_parser/fuzzing/corpus/d3adf09fe6fb1534157c1dc68eb61365b46b1c35
new file mode 100644
index 0000000..ad8336b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d3adf09fe6fb1534157c1dc68eb61365b46b1c35
@@ -0,0 +1 @@
+Eߣ’’pàÓ’ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d3c74d3a64dba86f98a31bb726587ec97a7e4531 b/webm_parser/fuzzing/corpus/d3c74d3a64dba86f98a31bb726587ec97a7e4531
new file mode 100644
index 0000000..c2ce861
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d3c74d3a64dba86f98a31bb726587ec97a7e4531
@@ -0,0 +1 @@
+€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ²€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ²€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶ÿ¶€¶€¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€D¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€II
diff --git a/webm_parser/fuzzing/corpus/d3c9846ab319f12fc646c23a532c780daa9e993f b/webm_parser/fuzzing/corpus/d3c9846ab319f12fc646c23a532c780daa9e993f
new file mode 100644
index 0000000..6986002
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d3c9846ab319f12fc646c23a532c780daa9e993f
@@ -0,0 +1 @@
+S€g™T®k”®’m€b@ŒP1€P2€P3€P5€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d3f03301b52cf4a830c7dd200ed8ccbc09e6ec94 b/webm_parser/fuzzing/corpus/d3f03301b52cf4a830c7dd200ed8ccbc09e6ec94
new file mode 100644
index 0000000..3910637
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d3f03301b52cf4a830c7dd200ed8ccbc09e6ec94
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d4800745440dace38766db3520ffe7baa0bd78f2 b/webm_parser/fuzzing/corpus/d4800745440dace38766db3520ffe7baa0bd78f2
new file mode 100644
index 0000000..78d6b02
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d4800745440dace38766db3520ffe7baa0bd78f2
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d4d6271bba704ba08c2678eb8b1bc4e457144f50 b/webm_parser/fuzzing/corpus/d4d6271bba704ba08c2678eb8b1bc4e457144f50
new file mode 100644
index 0000000..5715e68
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d4d6271bba704ba08c2678eb8b1bc4e457144f50
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d54020f766061e80f445690c2b5694ed8af21e9d b/webm_parser/fuzzing/corpus/d54020f766061e80f445690c2b5694ed8af21e9d
new file mode 100644
index 0000000..d0e8288
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d54020f766061e80f445690c2b5694ed8af21e9d
@@ -0,0 +1 @@
+G†\ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d588515f125ebe968fe6a81cb6df7cfc41969e68 b/webm_parser/fuzzing/corpus/d588515f125ebe968fe6a81cb6df7cfc41969e68
new file mode 100644
index 0000000..4b30c90
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d588515f125ebe968fe6a81cb6df7cfc41969e68
@@ -0,0 +1 @@
+EߣÿEߣ2 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d5ee5e4dc8fad9f1da43102d8322beb005a047c0 b/webm_parser/fuzzing/corpus/d5ee5e4dc8fad9f1da43102d8322beb005a047c0
new file mode 100644
index 0000000..d51648b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d5ee5e4dc8fad9f1da43102d8322beb005a047c0
@@ -0,0 +1 @@
+Eß¾ˆB‚@webmS€g•C¶uÿÿÿÿ«@ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d62459bb217d3050bcd9e29a6327cf81f5ed68b9 b/webm_parser/fuzzing/corpus/d62459bb217d3050bcd9e29a6327cf81f5ed68b9
new file mode 100644
index 0000000..c4ec3c4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d62459bb217d3050bcd9e29a6327cf81f5ed68b9
@@ -0,0 +1 @@
+€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ‚€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ‚€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI
diff --git a/webm_parser/fuzzing/corpus/d6acbf1cb46845618ed0d5a322c8bd4879d16422 b/webm_parser/fuzzing/corpus/d6acbf1cb46845618ed0d5a322c8bd4879d16422
new file mode 100644
index 0000000..479a877
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d6acbf1cb46845618ed0d5a322c8bd4879d16422
@@ -0,0 +1 @@
+S€g»TÃg¶ss³gÈ©È„#‡dgÈ„]‡egÈ„D*dgÈ„]‡egÈ„D‡gÈ€D‡dgÈg \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d6fc8cdbb0f1517159531098e900e2dcc91edba0 b/webm_parser/fuzzing/corpus/d6fc8cdbb0f1517159531098e900e2dcc91edba0
new file mode 100644
index 0000000..d7384b7
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d6fc8cdbb0f1517159531098e900e2dcc91edba0
@@ -0,0 +1 @@
+S€g‡S»k‚»€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d7a9bbd9875a60edf9c528b298ea72a2fad7d3d7 b/webm_parser/fuzzing/corpus/d7a9bbd9875a60edf9c528b298ea72a2fad7d3d7
new file mode 100644
index 0000000..e2c81b6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d7a9bbd9875a60edf9c528b298ea72a2fad7d3d7
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d82e70046a544e95e81f6271dd2695f2599f0574 b/webm_parser/fuzzing/corpus/d82e70046a544e95e81f6271dd2695f2599f0574
new file mode 100644
index 0000000..a2fe29e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d82e70046a544e95e81f6271dd2695f2599f0574
@@ -0,0 +1 @@
+S€gŽTÃg‰ss†cÀ€gÈ€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d84bc8dca7c8fcd227254c06f5b88eaaf7cd5fde b/webm_parser/fuzzing/corpus/d84bc8dca7c8fcd227254c06f5b88eaaf7cd5fde
new file mode 100644
index 0000000..f5d0f36
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d84bc8dca7c8fcd227254c06f5b88eaaf7cd5fde
@@ -0,0 +1 @@
+è \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d8bcb7dd21205e7126e700323b1d58817e9d9a6d b/webm_parser/fuzzing/corpus/d8bcb7dd21205e7126e700323b1d58817e9d9a6d
new file mode 100644
index 0000000..b32e704
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d8bcb7dd21205e7126e700323b1d58817e9d9a6d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d8e2628b4092b9bbab4f02041647f950503eeb48 b/webm_parser/fuzzing/corpus/d8e2628b4092b9bbab4f02041647f950503eeb48
new file mode 100644
index 0000000..6aa71bd
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d8e2628b4092b9bbab4f02041647f950503eeb48
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d8ee4c2b79863a237c432efe7d43d99c8d0afb9b b/webm_parser/fuzzing/corpus/d8ee4c2b79863a237c432efe7d43d99c8d0afb9b
new file mode 100644
index 0000000..db8723c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d8ee4c2b79863a237c432efe7d43d99c8d0afb9b
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/d8ee9724bf16ff336387723dcf27319c3be72e01 b/webm_parser/fuzzing/corpus/d8ee9724bf16ff336387723dcf27319c3be72e01
new file mode 100644
index 0000000..ce72a85
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d8ee9724bf16ff336387723dcf27319c3be72e01
@@ -0,0 +1 @@
+S€gT®kŠ®ˆà†Tºƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d9379969bc956ad623e4bab8bbe47270a880a62d b/webm_parser/fuzzing/corpus/d9379969bc956ad623e4bab8bbe47270a880a62d
new file mode 100644
index 0000000..40bdf1a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d9379969bc956ad623e4bab8bbe47270a880a62d
@@ -0,0 +1 @@
+S€g‹C¶u† „u¢ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/d9ccb79b0e070dcc2f5ed8e15d99bc5ef86ecff7 b/webm_parser/fuzzing/corpus/d9ccb79b0e070dcc2f5ed8e15d99bc5ef86ecff7
new file mode 100644
index 0000000..4c2c858
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/d9ccb79b0e070dcc2f5ed8e15d99bc5ef86ecff7
@@ -0,0 +1 @@
+EߣˆB‚@webmS€g©C¶u¤ç@«@£…ÿÿÿ @ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/db83586ca6266e03067e9a9772ea728ab770ad9a b/webm_parser/fuzzing/corpus/db83586ca6266e03067e9a9772ea728ab770ad9a
new file mode 100644
index 0000000..0b49ef8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/db83586ca6266e03067e9a9772ea728ab770ad9a
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/dcc63c06ed2790d1380bdb9281fe8677f439c76d b/webm_parser/fuzzing/corpus/dcc63c06ed2790d1380bdb9281fe8677f439c76d
new file mode 100644
index 0000000..c5769be
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/dcc63c06ed2790d1380bdb9281fe8677f439c76d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/dd509e9a4660ec34b8f9dc23441c6df4ff97c34d b/webm_parser/fuzzing/corpus/dd509e9a4660ec34b8f9dc23441c6df4ff97c34d
new file mode 100644
index 0000000..2a77375
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/dd509e9a4660ec34b8f9dc23441c6df4ff97c34d
@@ -0,0 +1 @@
+S€g“C§pŽE¹„E¼E¹„E¼ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/dd72f8ff3a067dc7871438b6023e1ed0a4c5787b b/webm_parser/fuzzing/corpus/dd72f8ff3a067dc7871438b6023e1ed0a4c5787b
new file mode 100644
index 0000000..dec5b01
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/dd72f8ff3a067dc7871438b6023e1ed0a4c5787b
@@ -0,0 +1 @@
+S€gŽ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/dd75880c5ad488885260f4031a763c86a8084406 b/webm_parser/fuzzing/corpus/dd75880c5ad488885260f4031a763c86a8084406
new file mode 100644
index 0000000..ad23554
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/dd75880c5ad488885260f4031a763c86a8084406
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/dd962d74d04aa4aed270fd8e6b0ae9c4ac35fd19 b/webm_parser/fuzzing/corpus/dd962d74d04aa4aed270fd8e6b0ae9c4ac35fd19
new file mode 100644
index 0000000..dcb3af4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/dd962d74d04aa4aed270fd8e6b0ae9c4ac35fd19
@@ -0,0 +1 @@
+€€¶€€€¶€¶€¶ÿ6€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€ÿ¶¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€ÿ¶¶€¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€Óÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿö€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ–€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€;€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶ÿ¶€€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿ¶€¶ÿ¶€¶€¶ÿ¶€€€¶€¶€¶€¶€¶ÿ¶ÿ¶€¶ÿ€€¶€¶€¶ÿ¶€¶€¶ÿJI
diff --git a/webm_parser/fuzzing/corpus/dd9aa9a49dd790b2ce99c5af1933232d46a7f80d b/webm_parser/fuzzing/corpus/dd9aa9a49dd790b2ce99c5af1933232d46a7f80d
new file mode 100644
index 0000000..28fd4f5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/dd9aa9a49dd790b2ce99c5af1933232d46a7f80d
@@ -0,0 +1 @@
+S€gŒI©f‡D‰„@ÉÛ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ddad7630818a1caa8054d2d7280a1d01bdb33ca3 b/webm_parser/fuzzing/corpus/ddad7630818a1caa8054d2d7280a1d01bdb33ca3
new file mode 100644
index 0000000..5fdbe5b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ddad7630818a1caa8054d2d7280a1d01bdb33ca3
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/ddb2eaf33960ce69d579a551a7b05733adcd52aa b/webm_parser/fuzzing/corpus/ddb2eaf33960ce69d579a551a7b05733adcd52aa
new file mode 100644
index 0000000..9f07b96
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ddb2eaf33960ce69d579a551a7b05733adcd52aa
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/de0d98cb997c0a0f7be127a46d8a24d8a003931d b/webm_parser/fuzzing/corpus/de0d98cb997c0a0f7be127a46d8a24d8a003931d
new file mode 100644
index 0000000..5a5f555
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/de0d98cb997c0a0f7be127a46d8a24d8a003931d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/de0ff884898c83fb880498f1b8328f78701e6534 b/webm_parser/fuzzing/corpus/de0ff884898c83fb880498f1b8328f78701e6534
new file mode 100644
index 0000000..b9f4c9d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/de0ff884898c83fb880498f1b8328f78701e6534
@@ -0,0 +1 @@
+¦€Ý€ç¦€¦€Ý€ç¦€ÝuÝu \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/de9e0ed8ae29220e5b65e5b97eb3b254ccbe7e0c b/webm_parser/fuzzing/corpus/de9e0ed8ae29220e5b65e5b97eb3b254ccbe7e0c
new file mode 100644
index 0000000..d48be42
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/de9e0ed8ae29220e5b65e5b97eb3b254ccbe7e0c
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/dec5e8ffb35aa707d127a11a49e47cb59954e969 b/webm_parser/fuzzing/corpus/dec5e8ffb35aa707d127a11a49e47cb59954e969
new file mode 100644
index 0000000..9289f7e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/dec5e8ffb35aa707d127a11a49e47cb59954e969
@@ -0,0 +1 @@
+S€gTÃgŠss‡gÈ„D‡! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/df1b205e339e7199b5094fcf0ec3b8a8ca7a692c b/webm_parser/fuzzing/corpus/df1b205e339e7199b5094fcf0ec3b8a8ca7a692c
new file mode 100644
index 0000000..337ffd0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/df1b205e339e7199b5094fcf0ec3b8a8ca7a692c
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/df254daba2299f3ff2367e284efc53a3296d136b b/webm_parser/fuzzing/corpus/df254daba2299f3ff2367e284efc53a3296d136b
new file mode 100644
index 0000000..f8834a2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/df254daba2299f3ff2367e284efc53a3296d136b
@@ -0,0 +1 @@
+€€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/dfdcde31231b8b3d3fd4e0ef8ea88885f488c4db b/webm_parser/fuzzing/corpus/dfdcde31231b8b3d3fd4e0ef8ea88885f488c4db
new file mode 100644
index 0000000..d7b04ea
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/dfdcde31231b8b3d3fd4e0ef8ea88885f488c4db
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/e01962d7dc1b94e5c4424ec7adae16a7c03b9773 b/webm_parser/fuzzing/corpus/e01962d7dc1b94e5c4424ec7adae16a7c03b9773
new file mode 100644
index 0000000..8d7f32a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e01962d7dc1b94e5c4424ec7adae16a7c03b9773
@@ -0,0 +1 @@
+€€X \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e058723f2964bf1405ae043ddb99efb17d821e15 b/webm_parser/fuzzing/corpus/e058723f2964bf1405ae043ddb99efb17d821e15
new file mode 100644
index 0000000..a294613
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e058723f2964bf1405ae043ddb99efb17d821e15
@@ -0,0 +1 @@
+S€gC§pŠE¹‡¶…‘ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e09e306ef596dcd0e44bd3ef3c5f7019c7343f84 b/webm_parser/fuzzing/corpus/e09e306ef596dcd0e44bd3ef3c5f7019c7343f84
new file mode 100644
index 0000000..1abbf62
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e09e306ef596dcd0e44bd3ef3c5f7019c7343f84
@@ -0,0 +1 @@
+S€gT®k‹®‰á‡xµ„Û \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e154eb76d096c1e545dcad591a58a02c37cd71ff b/webm_parser/fuzzing/corpus/e154eb76d096c1e545dcad591a58a02c37cd71ff
new file mode 100644
index 0000000..f575e8b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e154eb76d096c1e545dcad591a58a02c37cd71ff
@@ -0,0 +1 @@
+!EߣˆB‚@webmS€g£*M'›t€I©f€C¶u€TSkk»€®€C§p€TÃg€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e198d2e4f2f3528c2ff46d769a9281ebb3cfb44a b/webm_parser/fuzzing/corpus/e198d2e4f2f3528c2ff46d769a9281ebb3cfb44a
new file mode 100644
index 0000000..75c0df8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e198d2e4f2f3528c2ff46d769a9281ebb3cfb44a
@@ -0,0 +1 @@
+S€g”T®k®m€Šb@‡P5„Gâ!S€g”T®k®m€Šb@‡P5„Gâ!S€g›T®k–®”m€‘S€g›T®k–®”m€‘ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e2068af1e903f4a81cd6fa5f4022e62070c259ec b/webm_parser/fuzzing/corpus/e2068af1e903f4a81cd6fa5f4022e62070c259ec
new file mode 100644
index 0000000..8ceb559
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e2068af1e903f4a81cd6fa5f4022e62070c259ec
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/e2890330b5655cb277a581b8dd2eeba0d9061ba5 b/webm_parser/fuzzing/corpus/e2890330b5655cb277a581b8dd2eeba0d9061ba5
new file mode 100644
index 0000000..ffdb9c0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e2890330b5655cb277a581b8dd2eeba0d9061ba5
@@ -0,0 +1 @@
+S€g”T®k®m€Šb@‡P5„Gá \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e2e0767d055a7042c24a7acd5d5b6b7c093ad065 b/webm_parser/fuzzing/corpus/e2e0767d055a7042c24a7acd5d5b6b7c093ad065
new file mode 100644
index 0000000..145b54c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e2e0767d055a7042c24a7acd5d5b6b7c093ad065
@@ -0,0 +1 @@
+S€gŽT®k‰®‡à…ºƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e394a8e21e2c43c42135daa034ad5aabb06acffb b/webm_parser/fuzzing/corpus/e394a8e21e2c43c42135daa034ad5aabb06acffb
new file mode 100644
index 0000000..3bfae2e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e394a8e21e2c43c42135daa034ad5aabb06acffb
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/e3b200e97ec226a197e91f12103aaa53d5c37b0e b/webm_parser/fuzzing/corpus/e3b200e97ec226a197e91f12103aaa53d5c37b0e
new file mode 100644
index 0000000..a2f27b7
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e3b200e97ec226a197e91f12103aaa53d5c37b0e
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶³sÄV…@¦¶@sä¶@sĶ@sĶ@sĶ@sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e45b077cda64c380ae1b0910bc81d010c27e1c93 b/webm_parser/fuzzing/corpus/e45b077cda64c380ae1b0910bc81d010c27e1c93
new file mode 100644
index 0000000..e05b783
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e45b077cda64c380ae1b0910bc81d010c27e1c93
@@ -0,0 +1 @@
+Sx€g%T:kŒ®Šm€‡b@„1 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e45e71ca01ebe01b01b0ca99b8f20a756c36b967 b/webm_parser/fuzzing/corpus/e45e71ca01ebe01b01b0ca99b8f20a756c36b967
new file mode 100644
index 0000000..3d1f9b0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e45e71ca01ebe01b01b0ca99b8f20a756c36b967
@@ -0,0 +1 @@
+$ö€TÃg€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e4ff4bd938c3737a02862fc0656a1859d384d066 b/webm_parser/fuzzing/corpus/e4ff4bd938c3737a02862fc0656a1859d384d066
new file mode 100644
index 0000000..635b823
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e4ff4bd938c3737a02862fc0656a1859d384d066
@@ -0,0 +1 @@
+S€gC§pˆE¹…¶ƒ’ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e5a1acb7e6f71bc1a2fedaa6173764dfd04c844c b/webm_parser/fuzzing/corpus/e5a1acb7e6f71bc1a2fedaa6173764dfd04c844c
new file mode 100644
index 0000000..ca4a19c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e5a1acb7e6f71bc1a2fedaa6173764dfd04c844c
@@ -0,0 +1 @@
+S€g–T®k‘®m€Œb@‰P5†GçƒGè€S€gg—T®kÀ®m€b@ŠP5‡Gâ„;G \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e6081eeff4ddb76e88e87ef9e4b5f199f5f11c28 b/webm_parser/fuzzing/corpus/e6081eeff4ddb76e88e87ef9e4b5f199f5f11c28
new file mode 100644
index 0000000..7b80d1b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e6081eeff4ddb76e88e87ef9e4b5f199f5f11c28
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/e73c42dd266c4d9671da0c7af09e98c02fc052fd b/webm_parser/fuzzing/corpus/e73c42dd266c4d9671da0c7af09e98c02fc052fd
new file mode 100644
index 0000000..18e6ae2
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e73c42dd266c4d9671da0c7af09e98c02fc052fd
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/e7b4559a77df21b73285243a8350b844775bb380 b/webm_parser/fuzzing/corpus/e7b4559a77df21b73285243a8350b844775bb380
new file mode 100644
index 0000000..7885c3c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e7b4559a77df21b73285243a8350b844775bb380
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/e7dd34a80646a8c38ae1ec3a27c1358bab13b360 b/webm_parser/fuzzing/corpus/e7dd34a80646a8c38ae1ec3a27c1358bab13b360
new file mode 100644
index 0000000..8a7f10e
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e7dd34a80646a8c38ae1ec3a27c1358bab13b360
@@ -0,0 +1 @@
+S€gŠT®k…®ƒƒÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e80b344f943ff0ad9219277c4578d3b4100b71dc b/webm_parser/fuzzing/corpus/e80b344f943ff0ad9219277c4578d3b4100b71dc
new file mode 100644
index 0000000..0f27b93
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e80b344f943ff0ad9219277c4578d3b4100b71dc
@@ -0,0 +1 @@
+Eߣÿø£b \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e86e26200290d9d428c5e98e191ec874eb918fb6 b/webm_parser/fuzzing/corpus/e86e26200290d9d428c5e98e191ec874eb918fb6
new file mode 100644
index 0000000..dedb463
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e86e26200290d9d428c5e98e191ec874eb918fb6
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šm€‡b@„P2ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e873a3b8f5c3b716e6446df34279b837cf8d2c30 b/webm_parser/fuzzing/corpus/e873a3b8f5c3b716e6446df34279b837cf8d2c30
new file mode 100644
index 0000000..40a9456
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e873a3b8f5c3b716e6446df34279b837cf8d2c30
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/e8fb71319db98d8e8cd131a4eb82879bfbec14d0 b/webm_parser/fuzzing/corpus/e8fb71319db98d8e8cd131a4eb82879bfbec14d0
new file mode 100644
index 0000000..2bf9e0d
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e8fb71319db98d8e8cd131a4eb82879bfbec14d0
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/e93c3f14614595a2675993438b4c1bfaafdc02d0 b/webm_parser/fuzzing/corpus/e93c3f14614595a2675993438b4c1bfaafdc02d0
new file mode 100644
index 0000000..b0c4d91
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e93c3f14614595a2675993438b4c1bfaafdc02d0
@@ -0,0 +1 @@
+S€gT®kŠ®ˆà†T³ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e93faef2d77b7c467ae280ba433928d66d63ea63 b/webm_parser/fuzzing/corpus/e93faef2d77b7c467ae280ba433928d66d63ea63
new file mode 100644
index 0000000..231c621
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e93faef2d77b7c467ae280ba433928d66d63ea63
@@ -0,0 +1 @@
+S€gT®kˆ®†à„Tºÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e95401b11d974ba63270668d3c32c29e95ae85da b/webm_parser/fuzzing/corpus/e95401b11d974ba63270668d3c32c29e95ae85da
new file mode 100644
index 0000000..8f657f6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e95401b11d974ba63270668d3c32c29e95ae85da
@@ -0,0 +1 @@
+S€gC§pŠE¹‡¶…€ƒ… \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e9723cebe688912f684bcd19b48e5bc8efafaf2a b/webm_parser/fuzzing/corpus/e9723cebe688912f684bcd19b48e5bc8efafaf2a
new file mode 100644
index 0000000..861afde
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e9723cebe688912f684bcd19b48e5bc8efafaf2a
@@ -0,0 +1 @@
+ÿÿÿ€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e9a4389895c006d4b912e8ac1169229e6b2a66da b/webm_parser/fuzzing/corpus/e9a4389895c006d4b912e8ac1169229e6b2a66da
new file mode 100644
index 0000000..cab0d08
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e9a4389895c006d4b912e8ac1169229e6b2a66da
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/e9dc3d10b47ea580404c8e80b844a9978fcf4747 b/webm_parser/fuzzing/corpus/e9dc3d10b47ea580404c8e80b844a9978fcf4747
new file mode 100644
index 0000000..565b13f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e9dc3d10b47ea580404c8e80b844a9978fcf4747
@@ -0,0 +1 @@
+S€gŒM›t‡M»„S« \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/e9e2ac24e5674e7ee424e2b270e5abe84f1a15c9 b/webm_parser/fuzzing/corpus/e9e2ac24e5674e7ee424e2b270e5abe84f1a15c9
new file mode 100644
index 0000000..14d396f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/e9e2ac24e5674e7ee424e2b270e5abe84f1a15c9
@@ -0,0 +1 @@
+S€gTÃgŠss‡cÀ„cÅ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ea2b4df24b526aad253ab175334cb934b9d80b83 b/webm_parser/fuzzing/corpus/ea2b4df24b526aad253ab175334cb934b9d80b83
new file mode 100644
index 0000000..936aa37
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ea2b4df24b526aad253ab175334cb934b9d80b83
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/ea681d11486feeab2f080b06ddd2533575b7ace4 b/webm_parser/fuzzing/corpus/ea681d11486feeab2f080b06ddd2533575b7ace4
new file mode 100644
index 0000000..e16eeba
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ea681d11486feeab2f080b06ddd2533575b7ace4
@@ -0,0 +1 @@
+S€gŽC¶u‰ ‡Ž…èƒÌÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ebb747925360528a9f366f9a57730883c636b2c7 b/webm_parser/fuzzing/corpus/ebb747925360528a9f366f9a57730883c636b2c7
new file mode 100644
index 0000000..e6ea8c8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ebb747925360528a9f366f9a57730883c636b2c7
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/ec39abab70a8e1ff072eb082caa6ca77b1ae8087 b/webm_parser/fuzzing/corpus/ec39abab70a8e1ff072eb082caa6ca77b1ae8087
new file mode 100644
index 0000000..fbefd01
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ec39abab70a8e1ff072eb082caa6ca77b1ae8087
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/ecdd96d6cab2dc714a0b0ada1c4fcb18c75298e4 b/webm_parser/fuzzing/corpus/ecdd96d6cab2dc714a0b0ada1c4fcb18c75298e4
new file mode 100644
index 0000000..ed93df6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ecdd96d6cab2dc714a0b0ada1c4fcb18c75298e4
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶³sÄV…@B€@…@C¶@sĶ@sä¶@sĶ@sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ed008faa6c9001951f50588a1597e03931501343 b/webm_parser/fuzzing/corpus/ed008faa6c9001951f50588a1597e03931501343
new file mode 100644
index 0000000..fe1b376
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ed008faa6c9001951f50588a1597e03931501343
@@ -0,0 +1 @@
+S€gC¶uŠ ˆu¡…¦ƒî \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ed12e272a27a2fbdbfe5a6e78e49ed722ebd3c69 b/webm_parser/fuzzing/corpus/ed12e272a27a2fbdbfe5a6e78e49ed722ebd3c69
new file mode 100644
index 0000000..a666d51
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ed12e272a27a2fbdbfe5a6e78e49ed722ebd3c69
@@ -0,0 +1 @@
+S€gŒT®k‡®…àƒš \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ed799ba0608690ac68dd85c588004197b86e02bf b/webm_parser/fuzzing/corpus/ed799ba0608690ac68dd85c588004197b86e02bf
new file mode 100644
index 0000000..082e9a5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ed799ba0608690ac68dd85c588004197b86e02bf
@@ -0,0 +1 @@
+S€g@äC¶u@Þ @Û¡@Øc \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/edd982c5bd3030bde8c044760e9a678c2a9306f4 b/webm_parser/fuzzing/corpus/edd982c5bd3030bde8c044760e9a678c2a9306f4
new file mode 100644
index 0000000..48da75a
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/edd982c5bd3030bde8c044760e9a678c2a9306f4
@@ -0,0 +1 @@
+S€g‘T®kŒ®Šm€‡b@„P2 \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/ee82c97e35ec92ec3b0bbf911904a050b3aca633 b/webm_parser/fuzzing/corpus/ee82c97e35ec92ec3b0bbf911904a050b3aca633
new file mode 100644
index 0000000..696f0eb
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ee82c97e35ec92ec3b0bbf911904a050b3aca633
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/ef11f14feee00e3c198015e6bc76688e970e2d8c b/webm_parser/fuzzing/corpus/ef11f14feee00e3c198015e6bc76688e970e2d8c
new file mode 100644
index 0000000..414c1b3
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ef11f14feee00e3c198015e6bc76688e970e2d8c
@@ -0,0 +1 @@
+S€g¯€ø \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/efa12e91e0d63c2353c120ca1ded7b36afbf57eb b/webm_parser/fuzzing/corpus/efa12e91e0d63c2353c120ca1ded7b36afbf57eb
new file mode 100644
index 0000000..1673fc0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/efa12e91e0d63c2353c120ca1ded7b36afbf57eb
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/efa14cba9bbaf749067b7bb8515a5d8a289fa389 b/webm_parser/fuzzing/corpus/efa14cba9bbaf749067b7bb8515a5d8a289fa389
new file mode 100644
index 0000000..7c34b99
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/efa14cba9bbaf749067b7bb8515a5d8a289fa389
@@ -0,0 +1 @@
+S€g‰I©f„WA! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/eff9bf13cd33ea50a8eacc5f2839cc4b5d67b2de b/webm_parser/fuzzing/corpus/eff9bf13cd33ea50a8eacc5f2839cc4b5d67b2de
new file mode 100644
index 0000000..5f0ecb9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/eff9bf13cd33ea50a8eacc5f2839cc4b5d67b2de
@@ -0,0 +1 @@
+Sg€„IŒë@D‡‰ÉÛ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f0143756917f0b2e374bd03e731cd64168180054 b/webm_parser/fuzzing/corpus/f0143756917f0b2e374bd03e731cd64168180054
new file mode 100644
index 0000000..6773d0f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f0143756917f0b2e374bd03e731cd64168180054
@@ -0,0 +1 @@
+S€g‰C¶u„ ‚¢€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f0b9dcd4e1845f774bb0f42653b11040baee0765 b/webm_parser/fuzzing/corpus/f0b9dcd4e1845f774bb0f42653b11040baee0765
new file mode 100644
index 0000000..71caf14
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f0b9dcd4e1845f774bb0f42653b11040baee0765
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/f1a41ceb420b6b3df50b10f27108c133d4d07508 b/webm_parser/fuzzing/corpus/f1a41ceb420b6b3df50b10f27108c133d4d07508
new file mode 100644
index 0000000..a9c68c1
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f1a41ceb420b6b3df50b10f27108c133d4d07508
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/f39378fd978c6cdb4a8d08cdffc9034e2ca5b687 b/webm_parser/fuzzing/corpus/f39378fd978c6cdb4a8d08cdffc9034e2ca5b687
new file mode 100644
index 0000000..76de32c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f39378fd978c6cdb4a8d08cdffc9034e2ca5b687
@@ -0,0 +1 @@
+S€g™T®k”®’m€b@ŒP5‰á€Gâ€Gç€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f39707a104112d13d9d6bcfbef0efe8dfd98270c b/webm_parser/fuzzing/corpus/f39707a104112d13d9d6bcfbef0efe8dfd98270c
new file mode 100644
index 0000000..95f4c2c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f39707a104112d13d9d6bcfbef0efe8dfd98270c
@@ -0,0 +1 @@
+· \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f4341645a7d466113655d63c5aa00855904166c5 b/webm_parser/fuzzing/corpus/f4341645a7d466113655d63c5aa00855904166c5
new file mode 100644
index 0000000..919b544
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f4341645a7d466113655d63c5aa00855904166c5
@@ -0,0 +1 @@
+çÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f5511a42d83f94619ff8ca6c940cacc32bdc4834 b/webm_parser/fuzzing/corpus/f5511a42d83f94619ff8ca6c940cacc32bdc4834
new file mode 100644
index 0000000..9ba5fc3
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f5511a42d83f94619ff8ca6c940cacc32bdc4834
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/f57a39fa918249e6941b4e770e15a8131ac16ea5 b/webm_parser/fuzzing/corpus/f57a39fa918249e6941b4e770e15a8131ac16ea5
new file mode 100644
index 0000000..7387db3
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f57a39fa918249e6941b4e770e15a8131ac16ea5
@@ -0,0 +1 @@
+S€g½C§p¸E¹µ¶žsÄVTA‘@’@€@…@B€@…@C¶@sĶ@sĶ@sÄ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f5ac272a1dbf362e265210869aaf70ca410f1703 b/webm_parser/fuzzing/corpus/f5ac272a1dbf362e265210869aaf70ca410f1703
new file mode 100644
index 0000000..19acfef
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f5ac272a1dbf362e265210869aaf70ca410f1703
@@ -0,0 +1 @@
+S€g‘TÃgŒss‰cÀ†cŃ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f64c5ca1de57bcf323741f56754f53c642be8ab0 b/webm_parser/fuzzing/corpus/f64c5ca1de57bcf323741f56754f53c642be8ab0
new file mode 100644
index 0000000..a5222e4
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f64c5ca1de57bcf323741f56754f53c642be8ab0
@@ -0,0 +1 @@
+S€gC§p‹E¹ˆ¶†€„C|! \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f64dcdee393b4b0a3343f8e684c9db91a6eaeb6e b/webm_parser/fuzzing/corpus/f64dcdee393b4b0a3343f8e684c9db91a6eaeb6e
new file mode 100644
index 0000000..103c0aa
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f64dcdee393b4b0a3343f8e684c9db91a6eaeb6e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/f69d2954da077043c6ae085e2771a702689314d5 b/webm_parser/fuzzing/corpus/f69d2954da077043c6ae085e2771a702689314d5
new file mode 100644
index 0000000..8474e60
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f69d2954da077043c6ae085e2771a702689314d5
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/f74e2203adb9c94ba80f7cc3214e3b3040e5675d b/webm_parser/fuzzing/corpus/f74e2203adb9c94ba80f7cc3214e3b3040e5675d
new file mode 100644
index 0000000..0caab79
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f74e2203adb9c94ba80f7cc3214e3b3040e5675d
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/f7906e7dc01323b9d3d6e298e8dc2386c8d152f6 b/webm_parser/fuzzing/corpus/f7906e7dc01323b9d3d6e298e8dc2386c8d152f6
new file mode 100644
index 0000000..d70e0ac
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f7906e7dc01323b9d3d6e298e8dc2386c8d152f6
@@ -0,0 +1 @@
+×À€gˆ¶Cƒu« \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f825cac511f3dc38a5ecf3aab3691b4c49ca8f0e b/webm_parser/fuzzing/corpus/f825cac511f3dc38a5ecf3aab3691b4c49ca8f0e
new file mode 100644
index 0000000..8ea43ca
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f825cac511f3dc38a5ecf3aab3691b4c49ca8f0e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/f84c3f9e305172f2ae15dff0b4d955324b3a398e b/webm_parser/fuzzing/corpus/f84c3f9e305172f2ae15dff0b4d955324b3a398e
new file mode 100644
index 0000000..c1987a9
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f84c3f9e305172f2ae15dff0b4d955324b3a398e
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/f87cfad97831c33610fbbe34a04369bd93862464 b/webm_parser/fuzzing/corpus/f87cfad97831c33610fbbe34a04369bd93862464
new file mode 100644
index 0000000..c05f98f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f87cfad97831c33610fbbe34a04369bd93862464
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/f8ebd7703fd3ba1a135b243fd947dbd61907d0f4 b/webm_parser/fuzzing/corpus/f8ebd7703fd3ba1a135b243fd947dbd61907d0f4
new file mode 100644
index 0000000..1d9fee6
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f8ebd7703fd3ba1a135b243fd947dbd61907d0f4
@@ -0,0 +1 @@
+S€g“T®kŽ®Œm€‰b@†P2ƒ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f926f6b2337650f8b518422c2f63a8869f47c742 b/webm_parser/fuzzing/corpus/f926f6b2337650f8b518422c2f63a8869f47c742
new file mode 100644
index 0000000..c30c8d0
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f926f6b2337650f8b518422c2f63a8869f47c742
@@ -0,0 +1 @@
+S€gT®kˆ®†m€ƒb@€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f939897b9fbe865c96020927dc81de9dc255d385 b/webm_parser/fuzzing/corpus/f939897b9fbe865c96020927dc81de9dc255d385
new file mode 100644
index 0000000..12352de
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f939897b9fbe865c96020927dc81de9dc255d385
@@ -0,0 +1 @@
+S€gˆC¶uƒçÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/f985f995ca7d1b691e6ae3b3ef57e8b3c7aa7129 b/webm_parser/fuzzing/corpus/f985f995ca7d1b691e6ae3b3ef57e8b3c7aa7129
new file mode 100644
index 0000000..e35f109
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/f985f995ca7d1b691e6ae3b3ef57e8b3c7aa7129
@@ -0,0 +1 @@
+€ÿm \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/fa79e8ad34cabea4d3c434cc02ea1499069fcafc b/webm_parser/fuzzing/corpus/fa79e8ad34cabea4d3c434cc02ea1499069fcafc
new file mode 100644
index 0000000..ac147e8
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fa79e8ad34cabea4d3c434cc02ea1499069fcafc
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/fa7a8dfdd46845ab0fd9b5b7004e37d0232941bf b/webm_parser/fuzzing/corpus/fa7a8dfdd46845ab0fd9b5b7004e37d0232941bf
new file mode 100644
index 0000000..858ffc5
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fa7a8dfdd46845ab0fd9b5b7004e37d0232941bf
@@ -0,0 +1 @@
+S€gT®kˆ®†à„T³ÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/fcb3054fde86111e2c346aa71af2456a1705902c b/webm_parser/fuzzing/corpus/fcb3054fde86111e2c346aa71af2456a1705902c
new file mode 100644
index 0000000..318ea20
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fcb3054fde86111e2c346aa71af2456a1705902c
@@ -0,0 +1 @@
+S€g™TÃg”ss‡cÀ„hÊss‡cÀ„hÊ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/fce30dcdf2f23b14c580c282a39e065d7aacbfe8 b/webm_parser/fuzzing/corpus/fce30dcdf2f23b14c580c282a39e065d7aacbfe8
new file mode 100644
index 0000000..394d54b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fce30dcdf2f23b14c580c282a39e065d7aacbfe8
@@ -0,0 +1 @@
+S€gÿ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/fd15b8dd6c27bc65f90e1c988e0245b1ad7d51c5 b/webm_parser/fuzzing/corpus/fd15b8dd6c27bc65f90e1c988e0245b1ad7d51c5
new file mode 100644
index 0000000..0e40a40
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fd15b8dd6c27bc65f90e1c988e0245b1ad7d51c5
@@ -0,0 +1 @@
+S€g™T®k”®’m€b@ŒP5‰Gá€Gâ€ç€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/fd608012362d161cc7f3d30e1700c51f4ccef4ac b/webm_parser/fuzzing/corpus/fd608012362d161cc7f3d30e1700c51f4ccef4ac
new file mode 100644
index 0000000..601a585
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fd608012362d161cc7f3d30e1700c51f4ccef4ac
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/fdce5b4f3a038ce7cfeee4deb9a4644edb78189a b/webm_parser/fuzzing/corpus/fdce5b4f3a038ce7cfeee4deb9a4644edb78189a
new file mode 100644
index 0000000..5339393
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fdce5b4f3a038ce7cfeee4deb9a4644edb78189a
@@ -0,0 +1,3 @@
+’’ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/fe203731ada762e02bf843b82e33daee4c2efbf5 b/webm_parser/fuzzing/corpus/fe203731ada762e02bf843b82e33daee4c2efbf5
new file mode 100644
index 0000000..dcbe52b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fe203731ada762e02bf843b82e33daee4c2efbf5
@@ -0,0 +1 @@
+S€g—T®k’®m€b@ŠP5‡Gç„Gè \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/fe46e6ef4cd5788d89a101c77123387e3fc9d206 b/webm_parser/fuzzing/corpus/fe46e6ef4cd5788d89a101c77123387e3fc9d206
new file mode 100644
index 0000000..a53ee8b
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fe46e6ef4cd5788d89a101c77123387e3fc9d206
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/fe7ac2ef276b817af3487bab5fe089186ca0484b b/webm_parser/fuzzing/corpus/fe7ac2ef276b817af3487bab5fe089186ca0484b
new file mode 100644
index 0000000..da2a10f
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fe7ac2ef276b817af3487bab5fe089186ca0484b
@@ -0,0 +1 @@
+S€g‹M›t†M»ƒS¬€ \ No newline at end of file
diff --git a/webm_parser/fuzzing/corpus/fe7ef7c0e835873b7b1cd780f248114f18adf13a b/webm_parser/fuzzing/corpus/fe7ef7c0e835873b7b1cd780f248114f18adf13a
new file mode 100644
index 0000000..04e205c
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/fe7ef7c0e835873b7b1cd780f248114f18adf13a
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/ff5274cad94d590347d6cdba7637078f82e3d44a b/webm_parser/fuzzing/corpus/ff5274cad94d590347d6cdba7637078f82e3d44a
new file mode 100644
index 0000000..853a484
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ff5274cad94d590347d6cdba7637078f82e3d44a
Binary files differ
diff --git a/webm_parser/fuzzing/corpus/ffff6a92363e0e55a9688d9bc025cd8dea3b50d4 b/webm_parser/fuzzing/corpus/ffff6a92363e0e55a9688d9bc025cd8dea3b50d4
new file mode 100644
index 0000000..975d9b3
--- /dev/null
+++ b/webm_parser/fuzzing/corpus/ffff6a92363e0e55a9688d9bc025cd8dea3b50d4
@@ -0,0 +1 @@
+S€gTÃgŠss‡gÈ„D…! \ No newline at end of file
diff --git a/webm_parser/fuzzing/webm.dict b/webm_parser/fuzzing/webm.dict
new file mode 100644
index 0000000..7660ce8
--- /dev/null
+++ b/webm_parser/fuzzing/webm.dict
@@ -0,0 +1,152 @@
+# Element IDs.
+IdEbml = "\x1A\x45\xDF\xA3"
+IdEbmlVersion = "\x42\x86"
+IdEbmlReadVersion = "\x42\xF7"
+IdEbmlMaxIdLength = "\x42\xF2"
+IdEbmlMaxSizeLength = "\x42\xF3"
+IdDocType = "\x42\x82"
+IdDocTypeVersion = "\x42\x87"
+IdDocTypeReadVersion = "\x42\x85"
+IdVoid = "\xEC"
+IdSegment = "\x18\x53\x80\x67"
+IdSeekHead = "\x11\x4D\x9B\x74"
+IdSeek = "\x4D\xBB"
+IdSeekId = "\x53\xAB"
+IdSeekPosition = "\x53\xAC"
+IdInfo = "\x15\x49\xA9\x66"
+IdTimecodeScale = "\x2A\xD7\xB1"
+IdDuration = "\x44\x89"
+IdDateUtc = "\x44\x61"
+IdTitle = "\x7B\xA9"
+IdMuxingApp = "\x4D\x80"
+IdWritingApp = "\x57\x41"
+IdCluster = "\x1F\x43\xB6\x75"
+IdTimecode = "\xE7"
+IdPrevSize = "\xAB"
+IdSimpleBlock = "\xA3"
+IdBlockGroup = "\xA0"
+IdBlock = "\xA1"
+IdBlockVirtual = "\xA2"
+IdBlockAdditions = "\x75\xA1"
+IdBlockMore = "\xA6"
+IdBlockAddId = "\xEE"
+IdBlockAdditional = "\xA5"
+IdBlockDuration = "\x9B"
+IdReferenceBlock = "\xFB"
+IdDiscardPadding = "\x75\xA2"
+IdSlices = "\x8E"
+IdTimeSlice = "\xE8"
+IdLaceNumber = "\xCC"
+IdTracks = "\x16\x54\xAE\x6B"
+IdTrackEntry = "\xAE"
+IdTrackNumber = "\xD7"
+IdTrackUid = "\x73\xC5"
+IdTrackType = "\x83"
+IdFlagEnabled = "\xB9"
+IdFlagDefault = "\x88"
+IdFlagForced = "\x55\xAA"
+IdFlagLacing = "\x9C"
+IdDefaultDuration = "\x23\xE3\x83"
+IdName = "\x53\x6E"
+IdLanguage = "\x22\xB5\x9C"
+IdCodecId = "\x86"
+IdCodecPrivate = "\x63\xA2"
+IdCodecName = "\x25\x86\x88"
+IdCodecDelay = "\x56\xAA"
+IdSeekPreRoll = "\x56\xBB"
+IdVideo = "\xE0"
+IdFlagInterlaced = "\x9A"
+IdStereoMode = "\x53\xB8"
+IdAlphaMode = "\x53\xC0"
+IdPixelWidth = "\xB0"
+IdPixelHeight = "\xBA"
+IdPixelCropBottom = "\x54\xAA"
+IdPixelCropTop = "\x54\xBB"
+IdPixelCropLeft = "\x54\xCC"
+IdPixelCropRight = "\x54\xDD"
+IdDisplayWidth = "\x54\xB0"
+IdDisplayHeight = "\x54\xBA"
+IdDisplayUnit = "\x54\xB2"
+IdAspectRatioType = "\x54\xB3"
+IdFrameRate = "\x23\x83\xE3"
+IdColour = "\x55\xB0"
+IdMatrixCoefficients = "\x55\xB1"
+IdBitsPerChannel = "\x55\xB2"
+IdChromaSubsamplingHorz = "\x55\xB3"
+IdChromaSubsamplingVert = "\x55\xB4"
+IdCbSubsamplingHorz = "\x55\xB5"
+IdCbSubsamplingVert = "\x55\xB6"
+IdChromaSitingHorz = "\x55\xB7"
+IdChromaSitingVert = "\x55\xB8"
+IdRange = "\x55\xB9"
+IdTransferCharacteristics = "\x55\xBA"
+IdPrimaries = "\x55\xBB"
+IdMaxCll = "\x55\xBC"
+IdMaxFall = "\x55\xBD"
+IdMasteringMetadata = "\x55\xD0"
+IdPrimaryRChromaticityX = "\x55\xD1"
+IdPrimaryRChromaticityY = "\x55\xD2"
+IdPrimaryGChromaticityX = "\x55\xD3"
+IdPrimaryGChromaticityY = "\x55\xD4"
+IdPrimaryBChromaticityX = "\x55\xD5"
+IdPrimaryBChromaticityY = "\x55\xD6"
+IdWhitePointChromaticityX = "\x55\xD7"
+IdWhitePointChromaticityY = "\x55\xD8"
+IdLuminanceMax = "\x55\xD9"
+IdLuminanceMin = "\x55\xDA"
+IdProjection = "\x76\x70"
+IdProjectionType = "\x76\x71"
+IdProjectionPrivate = "\x76\x72"
+IdProjectionPoseYaw = "\x76\x73"
+IdProjectionPosePitch = "\x76\x74"
+IdProjectionPoseRoll = "\x76\x75"
+IdAudio = "\xE1"
+IdSamplingFrequency = "\xB5"
+IdOutputSamplingFrequency = "\x78\xB5"
+IdChannels = "\x9F"
+IdBitDepth = "\x62\x64"
+IdContentEncodings = "\x6D\x80"
+IdContentEncoding = "\x62\x40"
+IdContentEncodingOrder = "\x50\x31"
+IdContentEncodingScope = "\x50\x32"
+IdContentEncodingType = "\x50\x33"
+IdContentEncryption = "\x50\x35"
+IdContentEncAlgo = "\x47\xE1"
+IdContentEncKeyId = "\x47\xE2"
+IdContentEncAesSettings = "\x47\xE7"
+IdAesSettingsCipherMode = "\x47\xE8"
+IdCues = "\x1C\x53\xBB\x6B"
+IdCuePoint = "\xBB"
+IdCueTime = "\xB3"
+IdCueTrackPositions = "\xB7"
+IdCueTrack = "\xF7"
+IdCueClusterPosition = "\xF1"
+IdCueRelativePosition = "\xF0"
+IdCueDuration = "\xB2"
+IdCueBlockNumber = "\x53\x78"
+IdChapters = "\x10\x43\xA7\x70"
+IdEditionEntry = "\x45\xB9"
+IdChapterAtom = "\xB6"
+IdChapterUid = "\x73\xC4"
+IdChapterStringUid = "\x56\x54"
+IdChapterTimeStart = "\x91"
+IdChapterTimeEnd = "\x92"
+IdChapterDisplay = "\x80"
+IdChapString = "\x85"
+IdChapLanguage = "\x43\x7C"
+IdChapCountry = "\x43\x7E"
+IdTags = "\x12\x54\xC3\x67"
+IdTag = "\x73\x73"
+IdTargets = "\x63\xC0"
+IdTargetTypeValue = "\x68\xCA"
+IdTargetType = "\x63\xCA"
+IdTagTrackUid = "\x63\xC5"
+IdSimpleTag = "\x67\xC8"
+IdTagName = "\x45\xA3"
+IdTagLanguage = "\x44\x7A"
+IdTagDefault = "\x44\x84"
+IdTagString = "\x44\x87"
+IdTagBinary = "\x44\x85"
+
+# Interesting sizes.
+SizeUnknown = "\xFF"
diff --git a/webm_parser/fuzzing/webm_fuzzer.cc b/webm_parser/fuzzing/webm_fuzzer.cc
new file mode 100644
index 0000000..bfc4f62
--- /dev/null
+++ b/webm_parser/fuzzing/webm_fuzzer.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include <cassert>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+#include <new>
+#include <vector>
+
+#include "webm/buffer_reader.h"
+#include "webm/callback.h"
+#include "webm/file_reader.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+#include "webm/webm_parser.h"
+
+using webm::BufferReader;
+using webm::Callback;
+using webm::FileReader;
+using webm::Reader;
+using webm::Status;
+using webm::WebmParser;
+
+static int Run(Reader* reader) {
+ Callback callback;
+ WebmParser parser;
+
+#if WEBM_FUZZER_SEEK_FIRST
+ parser.DidSeek();
+#endif
+
+ Status status(-1);
+ try {
+ status = parser.Feed(&callback, reader);
+ } catch (std::bad_alloc&) {
+ // Failed allocations are okay. MSan doesn't throw std::bad_alloc, but
+ // someday it might.
+ }
+
+ // BufferReader/FileReader should never return either of the following codes,
+ // which means the parser never should too:
+ assert(status.code != Status::kWouldBlock);
+ assert(status.code != Status::kOkPartial);
+
+ // Only the following ranges have status codes defined:
+ assert((-1031 <= status.code && status.code <= -1025) ||
+ (-3 <= status.code && status.code <= 0));
+
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data,
+ std::size_t size) {
+ BufferReader reader(std::vector<std::uint8_t>(data, data + size));
+ return Run(&reader);
+}
+
+#if __AFL_COMPILER
+int main(int argc, char* argv[]) {
+ FILE* file = (argc == 2) ? std::fopen(argv[1], "rb")
+ : std::freopen(nullptr, "rb", stdin);
+ if (!file) {
+ std::cerr << "File cannot be opened\n";
+ return EXIT_FAILURE;
+ }
+
+ FileReader reader(file);
+ return Run(&reader);
+}
+#endif
diff --git a/webm_parser/include/webm/buffer_reader.h b/webm_parser/include/webm/buffer_reader.h
new file mode 100644
index 0000000..d479b5c
--- /dev/null
+++ b/webm_parser/include/webm/buffer_reader.h
@@ -0,0 +1,138 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef INCLUDE_WEBM_BUFFER_READER_H_
+#define INCLUDE_WEBM_BUFFER_READER_H_
+
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <vector>
+
+#include "./reader.h"
+#include "./status.h"
+
+/**
+ \file
+ A `Reader` implementation that reads from a `std::vector<std::uint8_t>`.
+ */
+
+namespace webm {
+
+/**
+ \addtogroup PUBLIC_API
+ @{
+ */
+
+/**
+ A simple reader that reads data from a buffer of bytes.
+ */
+class BufferReader : public Reader {
+ public:
+ /**
+ Constructs a new, empty reader.
+ */
+ BufferReader() = default;
+
+ /**
+ Constructs a new reader by copying the provided reader into the new reader.
+
+ \param other The source reader to copy.
+ */
+ BufferReader(const BufferReader& other) = default;
+
+ /**
+ Copies the provided reader into this reader.
+
+ \param other The source reader to copy. May be equal to `*this`, in which
+ case this is a no-op.
+ \return `*this`.
+ */
+ BufferReader& operator=(const BufferReader& other) = default;
+
+ /**
+ Constructs a new reader by moving the provided reader into the new reader.
+
+ \param other The source reader to move. After moving, it will be reset to an
+ empty stream.
+ */
+ BufferReader(BufferReader&&);
+
+ /**
+ Moves the provided reader into this reader.
+
+ \param other The source reader to move. After moving, it will be reset to an
+ empty stream. May be equal to `*this`, in which case this is a no-op.
+ \return `*this`.
+ */
+ BufferReader& operator=(BufferReader&&);
+
+ /**
+ Creates a new `BufferReader` populated with the provided bytes.
+
+ \param bytes Bytes that are assigned to the internal buffer and used as the
+ source which is read from.
+ */
+ BufferReader(std::initializer_list<std::uint8_t> bytes);
+
+ /**
+ Creates a new `BufferReader` populated with the provided data.
+
+ \param vector A vector of bytes that is copied to the internal buffer and
+ used as the source which is read from.
+ */
+ explicit BufferReader(const std::vector<std::uint8_t>& vector);
+
+ /**
+ Creates a new `BufferReader` populated with the provided data.
+
+ \param vector A vector of bytes that is moved to the internal buffer and used
+ as the source which is read from.
+ */
+ explicit BufferReader(std::vector<std::uint8_t>&& vector);
+
+ /**
+ Resets the reader to read from the given list of bytes, starting at the
+ beginning.
+
+ This makes `reader = {1, 2, 3};` effectively equivalent to `reader =
+ BufferReader({1, 2, 3});`.
+
+ \param bytes Bytes that are assigned to the internal buffer and used as the
+ source which is read from.
+ \return `*this`.
+ */
+ BufferReader& operator=(std::initializer_list<std::uint8_t> bytes);
+
+ Status Read(std::size_t num_to_read, std::uint8_t* buffer,
+ std::uint64_t* num_actually_read) override;
+
+ Status Skip(std::uint64_t num_to_skip,
+ std::uint64_t* num_actually_skipped) override;
+
+ std::uint64_t Position() const override;
+
+ /**
+ Gets the total size of the buffer.
+ */
+ std::size_t size() const { return data_.size(); }
+
+ private:
+ // Stores the byte buffer from which data is read.
+ std::vector<std::uint8_t> data_;
+
+ // The position of the reader in the byte buffer.
+ std::size_t pos_ = 0;
+};
+
+/**
+ @}
+ */
+
+} // namespace webm
+
+#endif // INCLUDE_WEBM_BUFFER_READER_H_
diff --git a/webm_parser/include/webm/callback.h b/webm_parser/include/webm/callback.h
new file mode 100644
index 0000000..f70f1f8
--- /dev/null
+++ b/webm_parser/include/webm/callback.h
@@ -0,0 +1,363 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef INCLUDE_WEBM_CALLBACK_H_
+#define INCLUDE_WEBM_CALLBACK_H_
+
+#include <cstdint>
+
+#include "./dom_types.h"
+#include "./reader.h"
+#include "./status.h"
+
+/**
+ \file
+ The main callback type that receives parsing events.
+ */
+
+namespace webm {
+
+/**
+ \addtogroup PUBLIC_API
+ @{
+ */
+
+/**
+ The action to be performed when parsing an element.
+ */
+enum class Action {
+ /**
+ Read and parse the element.
+ */
+ kRead,
+
+ /**
+ Skip the element. Skipped elements are not parsed or stored, and the callback
+ is not given any further notifications regarding the element.
+ */
+ kSkip,
+};
+
+/**
+ A callback that receives parsing events.
+
+ Every method that returns a `Status` should return `Status::kOkCompleted` when
+ the method has completed and parsing should continue. Returning any other value
+ will cause parsing to stop. Parsing may be resumed if the returned status was
+ not a parsing error (see `Status::is_parsing_error()`). When parsing is
+ resumed, the same `Callback` method will be called again.
+
+ Methods that take a `Reader` expect the implementation to consume (either via
+ `Reader::Read()` or `Reader::Skip()`) the specified number of bytes before
+ returning `Status::kOkCompleted`. Default implementations will call
+ `Reader::Skip()` to skip the specified number of bytes and the resulting
+ `Status` will be returned (unless it's `Status::kOkPartial`, in which case
+ `Reader::Skip()` will be called again to skip more data).
+
+ Throwing an exception from the member functions is permitted, though if the
+ exception will be caught and parsing resumed, then the reader should not
+ advance its position (for methods that take a `Reader`) before the exception is
+ thrown. When parsing is resumed, the same `Callback` method will be called
+ again.
+
+ Users should derive from this class and override member methods as needed.
+ */
+class Callback {
+ public:
+ virtual ~Callback() = default;
+
+ /**
+ Called when the parser starts a new element. This is called after the
+ elements ID and size has been parsed, but before any of its body has been
+ read (or validated).
+
+ Defaults to `Action::kRead` and returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element that has just been encountered.
+ \param[out] action The action that should be taken when handling this
+ element. Will not be null.
+ */
+ virtual Status OnElementBegin(const ElementMetadata& metadata,
+ Action* action);
+
+ /**
+ Called when the parser encounters an unknown element.
+
+ Defaults to calling (and returning the result of) `Reader::Skip()`.
+
+ \param metadata Metadata about the element.
+ \param reader The reader that should be used to consume data. Will not be
+ null.
+ \param[in,out] bytes_remaining The number of remaining bytes that need to be
+ consumed for the element. Will not be null.
+ \return `Status::kOkCompleted` when the element has been fully consumed and
+ `bytes_remaining` is now zero.
+ */
+ virtual Status OnUnknownElement(const ElementMetadata& metadata,
+ Reader* reader,
+ std::uint64_t* bytes_remaining);
+
+ /**
+ Called when the parser encounters an `Id::kEbml` element and it has been
+ fully parsed.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param ebml The parsed element.
+ */
+ virtual Status OnEbml(const ElementMetadata& metadata, const Ebml& ebml);
+
+ /**
+ Called when the parser encounters an Id::kVoid element.
+
+ Defaults to calling (and returning the result of) Reader::Skip.
+
+ \param metadata Metadata about the element.
+ \param reader The reader that should be used to consume data. Will not be
+ null.
+ \param[in,out] bytes_remaining The number of remaining bytes that need to be
+ consumed for the element. Will not be null.
+ \return `Status::kOkCompleted` when the element has been fully consumed and
+ `bytes_remaining` is now zero.
+ */
+ virtual Status OnVoid(const ElementMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining);
+
+ /**
+ Called when the parser starts an `Id::kSegment` element.
+
+ Defaults to `Action::kRead` and returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element that has just been encountered.
+ \param[out] action The action that should be taken when handling this
+ element. Will not be null.
+ */
+ virtual Status OnSegmentBegin(const ElementMetadata& metadata,
+ Action* action);
+
+ /**
+ Called when the parser encounters an `Id::kSeek` element and it has been
+ fully parsed.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param seek The parsed element.
+ */
+ virtual Status OnSeek(const ElementMetadata& metadata, const Seek& seek);
+
+ /**
+ Called when the parser encounters an `Id::kInfo` element and it has been
+ fully parsed.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param info The parsed element.
+ */
+ virtual Status OnInfo(const ElementMetadata& metadata, const Info& info);
+
+ /**
+ Called when the parser starts an `Id::kCluster` element.
+
+ Because Cluster elements should start with a Timecode (and optionally
+ PrevSize) child, this method is not invoked until a child BlockGroup or
+ SimpleBlock element is encountered (or the Cluster ends if no such child
+ exists).
+
+ Defaults to `Action::kRead` and returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param cluster The element, as it has currently been parsed.
+ \param[out] action The action that should be taken when handling this
+ element. Will not be null.
+ */
+ virtual Status OnClusterBegin(const ElementMetadata& metadata,
+ const Cluster& cluster, Action* action);
+
+ /**
+ Called when the parser starts an `Id::kSimpleBlock` element.
+
+ Defaults to `Action::kRead` and returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param simple_block The parsed SimpleBlock header.
+ \param[out] action The action that should be taken when handling this
+ element. Will not be null.
+ */
+ virtual Status OnSimpleBlockBegin(const ElementMetadata& metadata,
+ const SimpleBlock& simple_block,
+ Action* action);
+
+ /**
+ Called when the parser finishes an `Id::kSimpleBlock` element.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param simple_block The parsed SimpleBlock header.
+ */
+ virtual Status OnSimpleBlockEnd(const ElementMetadata& metadata,
+ const SimpleBlock& simple_block);
+
+ /**
+ Called when the parser starts an `Id::kBlockGroup` element.
+
+ Defaults to `Action::kRead` and returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param[out] action The action that should be taken when handling this
+ element. Will not be null.
+ */
+ virtual Status OnBlockGroupBegin(const ElementMetadata& metadata,
+ Action* action);
+
+ /**
+ Called when the parser starts an `Id::kBlock` element.
+
+ Defaults to `Action::kRead` and returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param block The parsed Block header.
+ \param[out] action The action that should be taken when handling this
+ element. Will not be null.
+ */
+ virtual Status OnBlockBegin(const ElementMetadata& metadata,
+ const Block& block, Action* action);
+
+ /**
+ Called when the parser finishes an `Id::Block` element.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param block The parsed Block header.
+ */
+ virtual Status OnBlockEnd(const ElementMetadata& metadata,
+ const Block& block);
+
+ /**
+ Called when the parser finishes an `Id::kBlockGroup` element.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param block_group The parsed element.
+ */
+ virtual Status OnBlockGroupEnd(const ElementMetadata& metadata,
+ const BlockGroup& block_group);
+
+ /**
+ Called when the parser encounters a frame within a `Id::kBlock` or
+ `Id::kSimpleBlock` element.
+
+ Defaults to calling (and returning the result of) `Reader::Skip`.
+
+ \param metadata Metadata about the frame.
+ \param reader The reader that should be used to consume data. Will not be
+ null.
+ \param[in,out] bytes_remaining The number of remaining bytes that need to be
+ consumed for the frame. Will not be null.
+ \return `Status::kOkCompleted` when the frame has been fully consumed and
+ `bytes_remaining` is now zero.
+ */
+ virtual Status OnFrame(const FrameMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining);
+
+ /**
+ Called when the parser finishes an `Id::kCluster` element.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param cluster The parsed element.
+ */
+ virtual Status OnClusterEnd(const ElementMetadata& metadata,
+ const Cluster& cluster);
+
+ /**
+ Called when the parser starts an `Id::kTrackEntry` element.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param track_entry The parsed element.
+ */
+ virtual Status OnTrackEntry(const ElementMetadata& metadata,
+ const TrackEntry& track_entry);
+
+ /**
+ Called when the parser encounters an `Id::kCuePoint` element and it has been
+ fully parsed.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param cue_point The parsed element.
+ */
+ virtual Status OnCuePoint(const ElementMetadata& metadata,
+ const CuePoint& cue_point);
+
+ /**
+ Called when the parser encounters an `Id::kEditionEntry` element and it has
+ been fully parsed.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param edition_entry The parsed element.
+ */
+ virtual Status OnEditionEntry(const ElementMetadata& metadata,
+ const EditionEntry& edition_entry);
+
+ /**
+ Called when the parser encounters an `Id::kTag` element and it has been fully
+ parsed.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ \param tag The parsed element.
+ */
+ virtual Status OnTag(const ElementMetadata& metadata, const Tag& tag);
+
+ /**
+ Called when the parser finishes an `Id::kSegment` element.
+
+ Defaults to returning `Status::kOkCompleted`.
+
+ \param metadata Metadata about the element.
+ */
+ virtual Status OnSegmentEnd(const ElementMetadata& metadata);
+
+ protected:
+ /**
+ Calls (and returns the result of) `Reader::Skip()`, skipping (up to) the
+ requested number of bytes.
+
+ Unlike `Reader::Skip()`, this method may be called with `*bytes_remaining ==
+ 0`, which will result in `Status::kOkCompleted`. `Reader::Skip()` will be
+ called multiple times if it returns `Status::kOkPartial` until it returns a
+ different status (indicating the requested number of bytes has been fully
+ skipped or some error occurred).
+
+ \param reader The reader that should be used to skip data. Must not be null.
+ \param[in,out] bytes_remaining The number of remaining bytes that need to be
+ skipped. Must not be null. May be zero.
+ \return The result of `Reader::Skip()`.
+ */
+ static Status Skip(Reader* reader, std::uint64_t* bytes_remaining);
+};
+
+/**
+ @}
+ */
+
+} // namespace webm
+
+#endif // INCLUDE_WEBM_CALLBACK_H_
diff --git a/webm_parser/include/webm/dom_types.h b/webm_parser/include/webm/dom_types.h
new file mode 100644
index 0000000..7ae4a0c
--- /dev/null
+++ b/webm_parser/include/webm/dom_types.h
@@ -0,0 +1,1781 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef INCLUDE_WEBM_DOM_TYPES_H_
+#define INCLUDE_WEBM_DOM_TYPES_H_
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "./element.h"
+#include "./id.h"
+
+/**
+ \file
+ Data structures representing parsed DOM objects.
+
+ For more information on each type and member, see the WebM specification for
+ the element that each type/member represents.
+ */
+
+namespace webm {
+
+/**
+ \addtogroup PUBLIC_API
+ @{
+ */
+
+/**
+ Metadata for a single frame.
+ */
+struct FrameMetadata {
+ /**
+ Metadata for the EBML element (\WebMID{Block} or \WebMID{SimpleBlock}) that
+ contains this frame.
+ */
+ ElementMetadata parent_element;
+
+ /**
+ Absolute byte position (from the beginning of the byte stream/file) of the
+ frame start.
+ */
+ std::uint64_t position;
+
+ /**
+ Size (in bytes) of the frame.
+ */
+ std::uint64_t size;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const FrameMetadata& other) const {
+ return parent_element == other.parent_element &&
+ position == other.position && size == other.size;
+ }
+};
+
+/**
+ A parsed \WebMID{BlockMore} element.
+ */
+struct BlockMore {
+ /**
+ A parsed \WebMID{BlockAddID} element.
+ */
+ Element<std::uint64_t> id{1};
+
+ /**
+ A parsed \WebMID{BlockAdditional} element.
+ */
+ Element<std::vector<std::uint8_t>> data;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const BlockMore& other) const {
+ return id == other.id && data == other.data;
+ }
+};
+
+/**
+ A parsed \WebMID{BlockAdditions} element.
+ */
+struct BlockAdditions {
+ /**
+ Parsed \WebMID{BlockMore} elements.
+ */
+ std::vector<Element<BlockMore>> block_mores;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const BlockAdditions& other) const {
+ return block_mores == other.block_mores;
+ }
+};
+
+/**
+ A parsed \WebMID{TimeSlice} element (deprecated).
+ */
+struct TimeSlice {
+ /**
+ A parsed \WebMID{LaceNumber} element (deprecated).
+ */
+ Element<std::uint64_t> lace_number;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const TimeSlice& other) const {
+ return lace_number == other.lace_number;
+ }
+};
+
+/**
+ A parsed \WebMID{Slices} element (deprecated).
+ */
+struct Slices {
+ /**
+ Parsed \WebMID{TimeSlice} elements (deprecated).
+ */
+ std::vector<Element<TimeSlice>> slices;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Slices& other) const { return slices == other.slices; }
+};
+
+/**
+ A parsed \WebMID{BlockVirtual} element.
+ */
+struct VirtualBlock {
+ /**
+ The virtual block's track number.
+ */
+ std::uint64_t track_number;
+
+ /**
+ The timecode of the virtual block.
+ */
+ std::int16_t timecode;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const VirtualBlock& other) const {
+ return track_number == other.track_number && timecode == other.timecode;
+ }
+};
+
+/**
+ The frame lacing used in a block.
+ */
+enum class Lacing : std::uint8_t {
+ /**
+ No lacing is used.
+ */
+ kNone = 0x00,
+
+ /**
+ Xiph-style lacing is used.
+ */
+ kXiph = 0x02,
+
+ /**
+ Fixed-lacing is used, where each frame has the same, fixed size.
+ */
+ kFixed = 0x04,
+
+ /**
+ EBML-style lacing is used.
+ */
+ kEbml = 0x06,
+};
+
+/**
+ A parsed \WebMID{Block} element.
+ */
+struct Block {
+ /**
+ The block's track number.
+ */
+ std::uint64_t track_number;
+
+ /**
+ The number of frames in the block.
+ */
+ int num_frames;
+
+ /**
+ The timecode of the block (relative to the containing \WebMID{Cluster}'s
+ timecode).
+ */
+ std::int16_t timecode;
+
+ /**
+ The lacing used to store frames in the block.
+ */
+ Lacing lacing;
+
+ /**
+ True if the frames are visible, false if they are invisible.
+ */
+ bool is_visible;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Block& other) const {
+ return track_number == other.track_number &&
+ num_frames == other.num_frames && timecode == other.timecode &&
+ lacing == other.lacing && is_visible == other.is_visible;
+ }
+};
+
+/**
+ A parsed \WebMID{SimpleBlock} element.
+ */
+struct SimpleBlock : public Block {
+ /**
+ True if the frames are all key frames.
+ */
+ bool is_key_frame;
+
+ /**
+ True if frames can be discarded during playback if needed.
+ */
+ bool is_discardable;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const SimpleBlock& other) const {
+ return Block::operator==(other) && is_key_frame == other.is_key_frame &&
+ is_discardable == other.is_discardable;
+ }
+};
+
+/**
+ A parsed \WebMID{BlockGroup} element.
+ */
+struct BlockGroup {
+ /**
+ A parsed \WebMID{Block} element.
+ */
+ Element<Block> block;
+
+ /**
+ A parsed \WebMID{BlockVirtual} element.
+ */
+ Element<VirtualBlock> virtual_block;
+
+ /**
+ A parsed \WebMID{BlockAdditions} element.
+ */
+ Element<BlockAdditions> additions;
+
+ /**
+ A parsed \WebMID{BlockDuration} element.
+ */
+ Element<std::uint64_t> duration;
+
+ /**
+ Parsed \WebMID{ReferenceBlock} elements.
+ */
+ std::vector<Element<std::int64_t>> references;
+
+ /**
+ A parsed \WebMID{DiscardPadding} element.
+ */
+ Element<std::int64_t> discard_padding;
+
+ /**
+ A parsed \WebMID{Slices} element (deprecated).
+ */
+ Element<Slices> slices;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const BlockGroup& other) const {
+ return block == other.block && virtual_block == other.virtual_block &&
+ additions == other.additions && duration == other.duration &&
+ references == other.references &&
+ discard_padding == other.discard_padding && slices == other.slices;
+ }
+};
+
+/**
+ A parsed \WebMID{Cluster} element.
+ */
+struct Cluster {
+ /**
+ A parsed \WebMID{Timecode} element.
+ */
+ Element<std::uint64_t> timecode;
+
+ /**
+ A parsed \WebMID{PrevSize} element.
+ */
+ Element<std::uint64_t> previous_size;
+
+ /**
+ Parsed \WebMID{SimpleBlock} elements.
+ */
+ std::vector<Element<SimpleBlock>> simple_blocks;
+
+ /**
+ Parsed \WebMID{BlockGroup} elements.
+ */
+ std::vector<Element<BlockGroup>> block_groups;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Cluster& other) const {
+ return timecode == other.timecode && previous_size == other.previous_size &&
+ simple_blocks == other.simple_blocks &&
+ block_groups == other.block_groups;
+ }
+};
+
+/**
+ A parsed \WebMID{EBML} element.
+ */
+struct Ebml {
+ /**
+ A parsed \WebMID{EBMLVersion} element.
+ */
+ Element<std::uint64_t> ebml_version{1};
+
+ /**
+ A parsed \WebMID{EBMLReadVersion} element.
+ */
+ Element<std::uint64_t> ebml_read_version{1};
+
+ /**
+ A parsed \WebMID{EBMLMaxIDLength} element.
+ */
+ Element<std::uint64_t> ebml_max_id_length{4};
+
+ /**
+ A parsed \WebMID{EBMLMaxSizeLength} element.
+ */
+ Element<std::uint64_t> ebml_max_size_length{8};
+
+ /**
+ A parsed \WebMID{DocType} element.
+ */
+ Element<std::string> doc_type{"matroska"};
+
+ /**
+ A parsed \WebMID{DocTypeVersion} element.
+ */
+ Element<std::uint64_t> doc_type_version{1};
+
+ /**
+ A parsed \WebMID{DocTypeReadVersion} element.
+ */
+ Element<std::uint64_t> doc_type_read_version{1};
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Ebml& other) const {
+ return ebml_version == other.ebml_version &&
+ ebml_read_version == other.ebml_read_version &&
+ ebml_max_id_length == other.ebml_max_id_length &&
+ ebml_max_size_length == other.ebml_max_size_length &&
+ doc_type == other.doc_type &&
+ doc_type_version == other.doc_type_version &&
+ doc_type_read_version == other.doc_type_read_version;
+ }
+};
+
+/**
+ A parsed \WebMID{Info} element.
+ */
+struct Info {
+ /**
+ A parsed \WebMID{TimecodeScale} element.
+ */
+ Element<std::uint64_t> timecode_scale{1000000};
+
+ /**
+ A parsed \WebMID{Duration} element.
+ */
+ Element<double> duration;
+
+ /**
+ A parsed \WebMID{DateUTC} element.
+ */
+ Element<std::int64_t> date_utc;
+
+ /**
+ A parsed \WebMID{Title} element.
+ */
+ Element<std::string> title;
+
+ /**
+ A parsed \WebMID{MuxingApp} element.
+ */
+ Element<std::string> muxing_app;
+
+ /**
+ A parsed \WebMID{WritingApp} element.
+ */
+ Element<std::string> writing_app;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Info& other) const {
+ return timecode_scale == other.timecode_scale &&
+ duration == other.duration && date_utc == other.date_utc &&
+ title == other.title && muxing_app == other.muxing_app &&
+ writing_app == other.writing_app;
+ }
+};
+
+/**
+ A parsed \WebMID{Seek} element.
+ */
+struct Seek {
+ /**
+ A parsed \WebMID{SeekID} element.
+ */
+ Element<Id> id;
+
+ /**
+ A parsed \WebMID{SeekPosition} element.
+ */
+ Element<std::uint64_t> position;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Seek& other) const {
+ return id == other.id && position == other.position;
+ }
+};
+
+/**
+ A parsed \WebMID{Audio} element.
+ */
+struct Audio {
+ /**
+ A parsed \WebMID{SamplingFrequency} element.
+ */
+ Element<double> sampling_frequency{8000};
+
+ /**
+ A parsed \WebMID{OutputSamplingFrequency} element.
+ */
+ Element<double> output_frequency{8000};
+
+ /**
+ A parsed \WebMID{Channels} element.
+ */
+ Element<std::uint64_t> channels{1};
+
+ /**
+ A parsed \WebMID{BitDepth} element.
+ */
+ Element<std::uint64_t> bit_depth;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Audio& other) const {
+ return sampling_frequency == other.sampling_frequency &&
+ output_frequency == other.output_frequency &&
+ channels == other.channels && bit_depth == other.bit_depth;
+ }
+};
+
+/**
+ A parsed \WebMID{MasteringMetadata} element.
+ */
+struct MasteringMetadata {
+ /**
+ A parsed \WebMID{PrimaryRChromaticityX} element.
+ */
+ Element<double> primary_r_chromaticity_x;
+
+ /**
+ A parsed \WebMID{PrimaryRChromaticityY} element.
+ */
+ Element<double> primary_r_chromaticity_y;
+
+ /**
+ A parsed \WebMID{PrimaryGChromaticityX} element.
+ */
+ Element<double> primary_g_chromaticity_x;
+
+ /**
+ A parsed \WebMID{PrimaryGChromaticityY} element.
+ */
+ Element<double> primary_g_chromaticity_y;
+
+ /**
+ A parsed \WebMID{PrimaryBChromaticityX} element.
+ */
+ Element<double> primary_b_chromaticity_x;
+
+ /**
+ A parsed \WebMID{PrimaryBChromaticityY} element.
+ */
+ Element<double> primary_b_chromaticity_y;
+
+ /**
+ A parsed \WebMID{WhitePointChromaticityX} element.
+ */
+ Element<double> white_point_chromaticity_x;
+
+ /**
+ A parsed \WebMID{WhitePointChromaticityY} element.
+ */
+ Element<double> white_point_chromaticity_y;
+
+ /**
+ A parsed \WebMID{LuminanceMax} element.
+ */
+ Element<double> luminance_max;
+
+ /**
+ A parsed \WebMID{LuminanceMin} element.
+ */
+ Element<double> luminance_min;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const MasteringMetadata& other) const {
+ return primary_r_chromaticity_x == other.primary_r_chromaticity_x &&
+ primary_r_chromaticity_y == other.primary_r_chromaticity_y &&
+ primary_g_chromaticity_x == other.primary_g_chromaticity_x &&
+ primary_g_chromaticity_y == other.primary_g_chromaticity_y &&
+ primary_b_chromaticity_x == other.primary_b_chromaticity_x &&
+ primary_b_chromaticity_y == other.primary_b_chromaticity_y &&
+ white_point_chromaticity_x == other.white_point_chromaticity_x &&
+ white_point_chromaticity_y == other.white_point_chromaticity_y &&
+ luminance_max == other.luminance_max &&
+ luminance_min == other.luminance_min;
+ }
+};
+
+/**
+ A parsed \WebMID{MatrixCoefficients} element.
+
+ Matroska/WebM adopted these values from Table 4 of ISO/IEC 23001-8:2013/DCOR1.
+ See that document for further details.
+ */
+enum class MatrixCoefficients : std::uint64_t {
+ /**
+ The identity matrix.
+
+ Typically used for GBR (often referred to as RGB); however, may also be used
+ for YZX (often referred to as XYZ).
+ */
+ kRgb = 0,
+
+ /**
+ Rec. ITU-R BT.709-5.
+ */
+ kBt709 = 1,
+
+ /**
+ Image characteristics are unknown or are determined by the application.
+ */
+ kUnspecified = 2,
+
+ /**
+ United States Federal Communications Commission Title 47 Code of Federal
+ Regulations (2003) 73.682 (a) (20).
+ */
+ kFcc = 4,
+
+ /**
+ Rec. ITU-R BT.470‑6 System B, G (historical).
+ */
+ kBt470Bg = 5,
+
+ /**
+ Society of Motion Picture and Television Engineers 170M (2004).
+ */
+ kSmpte170M = 6,
+
+ /**
+ Society of Motion Picture and Television Engineers 240M (1999).
+ */
+ kSmpte240M = 7,
+
+ /**
+ YCgCo.
+ */
+ kYCgCo = 8,
+
+ /**
+ Rec. ITU-R BT.2020 (non-constant luminance).
+ */
+ kBt2020NonconstantLuminance = 9,
+
+ /**
+ Rec. ITU-R BT.2020 (constant luminance).
+ */
+ kBt2020ConstantLuminance = 10,
+};
+
+/**
+ A parsed \WebMID{Range} element.
+ */
+enum class Range : std::uint64_t {
+ /**
+ Unspecified.
+ */
+ kUnspecified = 0,
+
+ /**
+ Broadcast range.
+ */
+ kBroadcast = 1,
+
+ /**
+ Full range (no clipping).
+ */
+ kFull = 2,
+
+ /**
+ Defined by MatrixCoefficients/TransferCharacteristics.
+ */
+ kDerived = 3,
+};
+
+/**
+ A parsed \WebMID{TransferCharacteristics} element.
+
+ Matroska/WebM adopted these values from Table 3 of ISO/IEC 23001-8:2013/DCOR1.
+ See that document for further details.
+ */
+enum class TransferCharacteristics : std::uint64_t {
+ /**
+ Rec. ITU-R BT.709-6.
+ */
+ kBt709 = 1,
+
+ /**
+ Image characteristics are unknown or are determined by the application.
+ */
+ kUnspecified = 2,
+
+ /**
+ Rec. ITU‑R BT.470‑6 System M (historical) with assumed display gamma 2.2.
+ */
+ kGamma22curve = 4,
+
+ /**
+ Rec. ITU‑R BT.470-6 System B, G (historical) with assumed display gamma 2.8.
+ */
+ kGamma28curve = 5,
+
+ /**
+ Society of Motion Picture and Television Engineers 170M (2004).
+ */
+ kSmpte170M = 6,
+
+ /**
+ Society of Motion Picture and Television Engineers 240M (1999).
+ */
+ kSmpte240M = 7,
+
+ /**
+ Linear transfer characteristics.
+ */
+ kLinear = 8,
+
+ /**
+ Logarithmic transfer characteristic (100:1 range).
+ */
+ kLog = 9,
+
+ /**
+ Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range).
+ */
+ kLogSqrt = 10,
+
+ /**
+ IEC 61966-2-4.
+ */
+ kIec6196624 = 11,
+
+ /**
+ Rec. ITU‑R BT.1361-0 extended colour gamut system (historical).
+ */
+ kBt1361ExtendedColourGamut = 12,
+
+ /**
+ IEC 61966-2-1 sRGB or sYCC.
+ */
+ kIec6196621 = 13,
+
+ /**
+ Rec. ITU-R BT.2020-2 (10-bit system).
+ */
+ k10BitBt2020 = 14,
+
+ /**
+ Rec. ITU-R BT.2020-2 (12-bit system).
+ */
+ k12BitBt2020 = 15,
+
+ /**
+ Society of Motion Picture and Television Engineers ST 2084.
+ */
+ kSmpteSt2084 = 16,
+
+ /**
+ Society of Motion Picture and Television Engineers ST 428-1.
+ */
+ kSmpteSt4281 = 17,
+
+ /**
+ Association of Radio Industries and Businesses (ARIB) STD-B67.
+ */
+ kAribStdB67Hlg = 18,
+};
+
+/**
+ A parsed \WebMID{Primaries} element.
+
+ Matroska/WebM adopted these values from Table 2 of ISO/IEC 23001-8:2013/DCOR1.
+ See that document for further details.
+ */
+enum class Primaries : std::uint64_t {
+ /**
+ Rec. ITU‑R BT.709-6.
+ */
+ kBt709 = 1,
+
+ /**
+ Image characteristics are unknown or are determined by the application.
+ */
+ kUnspecified = 2,
+
+ /**
+ Rec. ITU‑R BT.470‑6 System M (historical).
+ */
+ kBt470M = 4,
+
+ /**
+ Rec. ITU‑R BT.470‑6 System B, G (historical).
+ */
+ kBt470Bg = 5,
+
+ /**
+ Society of Motion Picture and Television Engineers 170M (2004).
+ */
+ kSmpte170M = 6,
+
+ /**
+ Society of Motion Picture and Television Engineers 240M (1999).
+ */
+ kSmpte240M = 7,
+
+ /**
+ Generic film.
+ */
+ kFilm = 8,
+
+ /**
+ Rec. ITU-R BT.2020-2.
+ */
+ kBt2020 = 9,
+
+ /**
+ Society of Motion Picture and Television Engineers ST 428-1.
+ */
+ kSmpteSt4281 = 10,
+
+ /**
+ Society of Motion Picture and Television Engineers RP 431-2 (a.k.a. DCI-P3).
+ */
+ kSmpteRp431 = 11,
+
+ /**
+ Society of Motion Picture and Television Engineers EG 432-1
+ (a.k.a. DCI-P3 D65).
+ */
+ kSmpteEg432 = 12,
+
+ /**
+ JEDEC P22 phosphors/EBU Tech. 3213-E (1975).
+ */
+ kJedecP22Phosphors = 22,
+};
+
+/**
+ A parsed \WebMID{Colour} element.
+ */
+struct Colour {
+ /**
+ A parsed \WebMID{MatrixCoefficients} element.
+ */
+ Element<MatrixCoefficients> matrix_coefficients{
+ MatrixCoefficients::kUnspecified};
+
+ /**
+ A parsed \WebMID{BitsPerChannel} element.
+ */
+ Element<std::uint64_t> bits_per_channel{0};
+
+ /**
+ A parsed \WebMID{ChromaSubsamplingHorz} element.
+ */
+ Element<std::uint64_t> chroma_subsampling_x;
+
+ /**
+ A parsed \WebMID{ChromaSubsamplingVert} element.
+ */
+ Element<std::uint64_t> chroma_subsampling_y;
+
+ /**
+ A parsed \WebMID{CbSubsamplingHorz} element.
+ */
+ Element<std::uint64_t> cb_subsampling_x;
+
+ /**
+ A parsed \WebMID{CbSubsamplingVert} element.
+ */
+ Element<std::uint64_t> cb_subsampling_y;
+
+ /**
+ A parsed \WebMID{ChromaSitingHorz} element.
+ */
+ Element<std::uint64_t> chroma_siting_x{0};
+
+ /**
+ A parsed \WebMID{ChromaSitingVert} element.
+ */
+ Element<std::uint64_t> chroma_siting_y{0};
+
+ /**
+ A parsed \WebMID{Range} element.
+ */
+ Element<Range> range{Range::kUnspecified};
+
+ /**
+ A parsed \WebMID{TransferCharacteristics} element.
+ */
+ Element<TransferCharacteristics> transfer_characteristics{
+ TransferCharacteristics::kUnspecified};
+
+ /**
+ A parsed \WebMID{Primaries} element.
+ */
+ Element<Primaries> primaries{Primaries::kUnspecified};
+
+ /**
+ A parsed \WebMID{MaxCLL} element.
+ */
+ Element<std::uint64_t> max_cll;
+
+ /**
+ A parsed \WebMID{MaxFALL} element.
+ */
+ Element<std::uint64_t> max_fall;
+
+ /**
+ A parsed \WebMID{MasteringMetadata} element.
+ */
+ Element<MasteringMetadata> mastering_metadata;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Colour& other) const {
+ return matrix_coefficients == other.matrix_coefficients &&
+ bits_per_channel == other.bits_per_channel &&
+ chroma_subsampling_x == other.chroma_subsampling_x &&
+ chroma_subsampling_y == other.chroma_subsampling_y &&
+ cb_subsampling_x == other.cb_subsampling_x &&
+ cb_subsampling_y == other.cb_subsampling_y &&
+ chroma_siting_x == other.chroma_siting_x &&
+ chroma_siting_y == other.chroma_siting_y && range == other.range &&
+ transfer_characteristics == other.transfer_characteristics &&
+ primaries == other.primaries && max_cll == other.max_cll &&
+ max_fall == other.max_fall &&
+ mastering_metadata == other.mastering_metadata;
+ }
+};
+
+/**
+ A parsed \WebMID{ProjectionType} element.
+ */
+enum class ProjectionType : std::uint64_t {
+ /**
+ Rectangular.
+ */
+ kRectangular = 0,
+
+ /**
+ Equirectangular.
+ */
+ kEquirectangular = 1,
+
+ /**
+ Cube map.
+ */
+ kCubeMap = 2,
+
+ /**
+ Mesh.
+ */
+ kMesh = 3,
+};
+
+/**
+ A parsed \WebMID{Projection} element.
+ */
+struct Projection {
+ /**
+ A parsed \WebMID{ProjectionType} element.
+ */
+ Element<ProjectionType> type;
+
+ /**
+ A parsed \WebMID{ProjectionPrivate} element.
+ */
+ Element<std::vector<std::uint8_t>> projection_private;
+
+ /**
+ A parsed \WebMID{ProjectionPoseYaw} element.
+ */
+ Element<double> pose_yaw;
+
+ /**
+ A parsed \WebMID{ProjectionPosePitch} element.
+ */
+ Element<double> pose_pitch;
+
+ /**
+ A parsed \WebMID{ProjectionPoseRoll} element.
+ */
+ Element<double> pose_roll;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Projection& other) const {
+ return type == other.type &&
+ projection_private == other.projection_private &&
+ pose_yaw == other.pose_yaw && pose_pitch == other.pose_pitch &&
+ pose_roll == other.pose_roll;
+ }
+};
+
+/**
+ A parsed \WebMID{FlagInterlaced} element.
+ */
+enum class FlagInterlaced : std::uint64_t {
+ /**
+ Unspecified.
+ */
+ kUnspecified = 0,
+
+ /**
+ Interlaced.
+ */
+ kInterlaced = 1,
+
+ /**
+ Progressive.
+ */
+ kProgressive = 2,
+};
+
+/**
+ A parsed \WebMID{StereoMode} element.
+ */
+enum class StereoMode : std::uint64_t {
+ /**
+ Mono.
+ */
+ kMono = 0,
+
+ /**
+ Side-by-side, left eye is first.
+ */
+ kSideBySideLeftFirst = 1,
+
+ /**
+ Top-bottom, right eye is first.
+ */
+ kTopBottomRightFirst = 2,
+
+ /**
+ Top-bottom, left eye is first.
+ */
+ kTopBottomLeftFirst = 3,
+
+ /**
+ Checkboard, right eye is first.
+ */
+ kCheckboardRightFirst = 4,
+
+ /**
+ Checkboard, left eye is first.
+ */
+ kCheckboardLeftFirst = 5,
+
+ /**
+ Row interleaved, right eye is first.
+ */
+ kRowInterleavedRightFirst = 6,
+
+ /**
+ Row interleaved, left eye is first.
+ */
+ kRowInterleavedLeftFirst = 7,
+
+ /**
+ Column interleaved, right eye is first.
+ */
+ kColumnInterleavedRightFirst = 8,
+
+ /**
+ Column interleaved, left eye is first.
+ */
+ kColumnInterleavedLeftFirst = 9,
+
+ /**
+ Anaglyph (cyan/read).
+ */
+ kAnaglyphCyanRed = 10,
+
+ /**
+ Side-by-side, right eye is first.
+ */
+ kSideBySideRightFirst = 11,
+
+ /**
+ Anaglyph (green/magenta).
+ */
+ kAnaglyphGreenMagenta = 12,
+
+ /**
+ Both eyes are laced in one block, left eye is first.
+ */
+ kBlockLacedLeftFirst = 13,
+
+ /**
+ Both eyes are laced in one block, right eye is first.
+ */
+ kBlockLacedRightFirst = 14,
+
+ /**
+ Stereo, but the layout for the left and right eyes is application dependent
+ and must be determined from other data (like the ProjectionPrivate element).
+ */
+ kStereoCustom = 15,
+};
+
+/**
+ A parsed \WebMID{DisplayUnit} element.
+
+ Note that WebM only supports pixel display units. Centimeters, inches, and
+ display aspect ratio aren't supported.
+ */
+enum class DisplayUnit : std::uint64_t {
+ // The only value legal value in WebM is 0 (pixels).
+ /**
+ Pixels.
+
+ This is the only option supported by WebM.
+ */
+ kPixels = 0,
+
+ /**
+ Centimeters.
+ */
+ kCentimeters = 1,
+
+ /**
+ Inches.
+ */
+ kInches = 2,
+
+ /**
+ Display aspect ratio.
+ */
+ kDisplayAspectRatio = 3,
+};
+
+/**
+ A parsed \WebMID{AspectRatioType} element.
+ */
+enum class AspectRatioType : std::uint64_t {
+ /**
+ Free resizing.
+ */
+ kFreeResizing = 0,
+
+ /**
+ Keep aspect ratio.
+ */
+ kKeep = 1,
+
+ /**
+ Fixed aspect ratio.
+ */
+ kFixed = 2,
+};
+
+/**
+ A parsed \WebMID{Video} element.
+ */
+struct Video {
+ /**
+ A parsed \WebMID{FlagInterlaced} element.
+ */
+ Element<FlagInterlaced> interlaced{FlagInterlaced::kUnspecified};
+
+ /**
+ A parsed \WebMID{StereoMode} element.
+ */
+ Element<StereoMode> stereo_mode{StereoMode::kMono};
+
+ /**
+ A parsed \WebMID{AlphaMode} element.
+ */
+ Element<std::uint64_t> alpha_mode{0};
+
+ /**
+ A parsed \WebMID{PixelWidth} element.
+ */
+ Element<std::uint64_t> pixel_width;
+
+ /**
+ A parsed \WebMID{PixelHeight} element.
+ */
+ Element<std::uint64_t> pixel_height;
+
+ /**
+ A parsed \WebMID{PixelCropBottom} element.
+ */
+ Element<std::uint64_t> pixel_crop_bottom{0};
+
+ /**
+ A parsed \WebMID{PixelCropTop} element.
+ */
+ Element<std::uint64_t> pixel_crop_top{0};
+
+ /**
+ A parsed \WebMID{PixelCropLeft} element.
+ */
+ Element<std::uint64_t> pixel_crop_left{0};
+
+ /**
+ A parsed \WebMID{PixelCropRight} element.
+ */
+ Element<std::uint64_t> pixel_crop_right{0};
+
+ /**
+ A parsed \WebMID{DisplayWidth} element.
+ */
+ Element<std::uint64_t> display_width;
+
+ /**
+ A parsed \WebMID{DisplayHeight} element.
+ */
+ Element<std::uint64_t> display_height;
+
+ /**
+ A parsed \WebMID{DisplayUnit} element.
+ */
+ Element<DisplayUnit> display_unit{DisplayUnit::kPixels};
+
+ /**
+ A parsed \WebMID{AspectRatioType} element.
+ */
+ Element<AspectRatioType> aspect_ratio_type{AspectRatioType::kFreeResizing};
+
+ /**
+ A parsed \WebMID{FrameRate} element (deprecated).
+ */
+ Element<double> frame_rate;
+
+ /**
+ A parsed \WebMID{Colour} element.
+ */
+ Element<Colour> colour;
+
+ /**
+ A parsed \WebMID{Projection} element.
+ */
+ Element<Projection> projection;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Video& other) const {
+ return interlaced == other.interlaced && stereo_mode == other.stereo_mode &&
+ alpha_mode == other.alpha_mode && pixel_width == other.pixel_width &&
+ pixel_height == other.pixel_height &&
+ pixel_crop_bottom == other.pixel_crop_bottom &&
+ pixel_crop_top == other.pixel_crop_top &&
+ pixel_crop_left == other.pixel_crop_left &&
+ pixel_crop_right == other.pixel_crop_right &&
+ display_width == other.display_width &&
+ display_height == other.display_height &&
+ display_unit == other.display_unit &&
+ aspect_ratio_type == other.aspect_ratio_type &&
+ frame_rate == other.frame_rate && colour == other.colour &&
+ projection == other.projection;
+ }
+};
+
+/**
+ A parsed \WebMID{AESSettingsCipherMode} element.
+ */
+enum class AesSettingsCipherMode : std::uint64_t {
+ /**
+ Counter (CTR).
+ */
+ kCtr = 1,
+};
+
+/**
+ A parsed \WebMID{ContentEncAESSettings} element.
+ */
+struct ContentEncAesSettings {
+ /**
+ A parsed \WebMID{AESSettingsCipherMode} element.
+ */
+ Element<AesSettingsCipherMode> aes_settings_cipher_mode{
+ AesSettingsCipherMode::kCtr};
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const ContentEncAesSettings& other) const {
+ return aes_settings_cipher_mode == other.aes_settings_cipher_mode;
+ }
+};
+
+/**
+ A parsed \WebMID{ContentEncAlgo} element.
+ */
+enum class ContentEncAlgo : std::uint64_t {
+ /**
+ The contents have been signed but not encrypted.
+ */
+ kOnlySigned = 0,
+
+ /**
+ DES encryption.
+ */
+ kDes = 1,
+
+ /**
+ 3DES encryption.
+ */
+ k3Des = 2,
+
+ /**
+ Twofish encryption.
+ */
+ kTwofish = 3,
+
+ /**
+ Blowfish encryption.
+ */
+ kBlowfish = 4,
+
+ /**
+ AES encryption.
+ */
+ kAes = 5,
+};
+
+/**
+ A parsed \WebMID{ContentEncryption} element.
+ */
+struct ContentEncryption {
+ /**
+ A parsed \WebMID{ContentEncAlgo} element.
+ */
+ Element<ContentEncAlgo> algorithm{ContentEncAlgo::kOnlySigned};
+
+ /**
+ A parsed \WebMID{ContentEncKeyID} element.
+ */
+ Element<std::vector<std::uint8_t>> key_id;
+
+ /**
+ A parsed \WebMID{ContentEncAESSettings} element.
+ */
+ Element<ContentEncAesSettings> aes_settings;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const ContentEncryption& other) const {
+ return algorithm == other.algorithm && key_id == other.key_id &&
+ aes_settings == other.aes_settings;
+ }
+};
+
+/**
+ A parsed \WebMID{ContentEncodingType} element.
+ */
+enum class ContentEncodingType : std::uint64_t {
+ /**
+ Compression.
+ */
+ kCompression = 0,
+
+ /**
+ Encryption.
+ */
+ kEncryption = 1,
+};
+
+/**
+ A parsed \WebMID{ContentEncoding} element.
+ */
+struct ContentEncoding {
+ /**
+ A parsed \WebMID{ContentEncodingOrder} element.
+ */
+ Element<std::uint64_t> order{0};
+
+ /**
+ A parsed \WebMID{ContentEncodingScope} element.
+ */
+ Element<std::uint64_t> scope{1};
+
+ /**
+ A parsed \WebMID{ContentEncodingType} element.
+ */
+ Element<ContentEncodingType> type{ContentEncodingType::kCompression};
+
+ /**
+ A parsed \WebMID{ContentEncryption} element.
+ */
+ Element<ContentEncryption> encryption;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const ContentEncoding& other) const {
+ return order == other.order && scope == other.scope && type == other.type &&
+ encryption == other.encryption;
+ }
+};
+
+/**
+ A parsed \WebMID{ContentEncodings} element.
+ */
+struct ContentEncodings {
+ /**
+ Parsed \WebMID{ContentEncoding} elements.
+ */
+ std::vector<Element<ContentEncoding>> encodings;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const ContentEncodings& other) const {
+ return encodings == other.encodings;
+ }
+};
+
+/**
+ A parsed \WebMID{TrackType} element.
+ */
+enum class TrackType : std::uint64_t {
+ /**
+ Video.
+ */
+ kVideo = 0x01,
+
+ /**
+ Audio.
+ */
+ kAudio = 0x02,
+
+ /**
+ Complex.
+ */
+ kComplex = 0x03,
+
+ /**
+ Logo.
+ */
+ kLogo = 0x10,
+
+ /**
+ Subtitle.
+ */
+ kSubtitle = 0x11,
+
+ /**
+ Buttons.
+ */
+ kButtons = 0x12,
+
+ /**
+ Control.
+ */
+ kControl = 0x20,
+};
+
+/**
+ A parsed \WebMID{TrackEntry} element.
+ */
+struct TrackEntry {
+ /**
+ A parsed \WebMID{TrackNumber} element.
+ */
+ Element<std::uint64_t> track_number;
+
+ /**
+ A parsed \WebMID{TrackUID} element.
+ */
+ Element<std::uint64_t> track_uid;
+
+ /**
+ A parsed \WebMID{TrackType} element.
+ */
+ Element<TrackType> track_type;
+
+ /**
+ A parsed \WebMID{FlagEnabled} element.
+ */
+ Element<bool> is_enabled{true};
+
+ /**
+ A parsed \WebMID{FlagDefault} element.
+ */
+ Element<bool> is_default{true};
+
+ /**
+ A parsed \WebMID{FlagForced} element.
+ */
+ Element<bool> is_forced{false};
+
+ /**
+ A parsed \WebMID{FlagLacing} element.
+ */
+ Element<bool> uses_lacing{true};
+
+ /**
+ A parsed \WebMID{DefaultDuration} element.
+ */
+ Element<std::uint64_t> default_duration;
+
+ /**
+ A parsed \WebMID{Name} element.
+ */
+ Element<std::string> name;
+
+ /**
+ A parsed \WebMID{Language} element.
+ */
+ Element<std::string> language{"eng"};
+
+ /**
+ A parsed \WebMID{CodecID} element.
+ */
+ Element<std::string> codec_id;
+
+ /**
+ A parsed \WebMID{CodecPrivate} element.
+ */
+ Element<std::vector<std::uint8_t>> codec_private;
+
+ /**
+ A parsed \WebMID{CodecName} element.
+ */
+ Element<std::string> codec_name;
+
+ /**
+ A parsed \WebMID{CodecDelay} element.
+ */
+ Element<std::uint64_t> codec_delay{0};
+
+ /**
+ A parsed \WebMID{SeekPreRoll} element.
+ */
+ Element<std::uint64_t> seek_pre_roll{0};
+
+ /**
+ A parsed \WebMID{Video} element.
+ */
+ Element<Video> video;
+
+ /**
+ A parsed \WebMID{Audio} element.
+ */
+ Element<Audio> audio;
+
+ /**
+ A parsed \WebMID{ContentEncodings} element.
+ */
+ Element<ContentEncodings> content_encodings;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const TrackEntry& other) const {
+ return track_number == other.track_number && track_uid == other.track_uid &&
+ track_type == other.track_type && is_enabled == other.is_enabled &&
+ is_default == other.is_default && is_forced == other.is_forced &&
+ uses_lacing == other.uses_lacing &&
+ default_duration == other.default_duration && name == other.name &&
+ language == other.language && codec_id == other.codec_id &&
+ codec_private == other.codec_private &&
+ codec_name == other.codec_name && codec_delay == other.codec_delay &&
+ seek_pre_roll == other.seek_pre_roll && video == other.video &&
+ audio == other.audio && content_encodings == other.content_encodings;
+ }
+};
+
+/**
+ A parsed \WebMID{CueTrackPositions} element.
+ */
+struct CueTrackPositions {
+ /**
+ A parsed \WebMID{CueTrack} element.
+ */
+ Element<std::uint64_t> track;
+
+ /**
+ A parsed \WebMID{CueClusterPosition} element.
+ */
+ Element<std::uint64_t> cluster_position;
+
+ /**
+ A parsed \WebMID{CueRelativePosition} element.
+ */
+ Element<std::uint64_t> relative_position;
+
+ /**
+ A parsed \WebMID{CueDuration} element.
+ */
+ Element<std::uint64_t> duration;
+
+ /**
+ A parsed \WebMID{CueBlockNumber} element.
+ */
+ Element<std::uint64_t> block_number{1};
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const CueTrackPositions& other) const {
+ return track == other.track && cluster_position == other.cluster_position &&
+ relative_position == other.relative_position &&
+ duration == other.duration && block_number == other.block_number;
+ }
+};
+
+/**
+ A parsed \WebMID{CuePoint} element.
+ */
+struct CuePoint {
+ /**
+ A parsed \WebMID{CueTime} element.
+ */
+ Element<std::uint64_t> time;
+
+ /**
+ Parsed \WebMID{CueTrackPositions} elements.
+ */
+ std::vector<Element<CueTrackPositions>> cue_track_positions;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const CuePoint& other) const {
+ return time == other.time &&
+ cue_track_positions == other.cue_track_positions;
+ }
+};
+
+/**
+ A parsed \WebMID{ChapterDisplay} element.
+ */
+struct ChapterDisplay {
+ /**
+ A parsed \WebMID{ChapString} element.
+ */
+ Element<std::string> string;
+
+ /**
+ Parsed \WebMID{ChapLanguage} elements.
+ */
+ std::vector<Element<std::string>> languages{Element<std::string>{"eng"}};
+
+ /**
+ Parsed \WebMID{ChapCountry} elements.
+ */
+ std::vector<Element<std::string>> countries;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const ChapterDisplay& other) const {
+ return string == other.string && languages == other.languages &&
+ countries == other.countries;
+ }
+};
+
+/**
+ A parsed \WebMID{ChapterAtom} element.
+ */
+struct ChapterAtom {
+ /**
+ A parsed \WebMID{ChapterUID} element.
+ */
+ Element<std::uint64_t> uid;
+
+ /**
+ A parsed \WebMID{ChapterStringUID} element.
+ */
+ Element<std::string> string_uid;
+
+ /**
+ A parsed \WebMID{ChapterTimeStart} element.
+ */
+ Element<std::uint64_t> time_start;
+
+ /**
+ A parsed \WebMID{ChapterTimeEnd} element.
+ */
+ Element<std::uint64_t> time_end;
+
+ /**
+ Parsed \WebMID{ChapterDisplay} elements.
+ */
+ std::vector<Element<ChapterDisplay>> displays;
+
+ /**
+ Parsed \WebMID{ChapterAtom} elements.
+ */
+ std::vector<Element<ChapterAtom>> atoms;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const ChapterAtom& other) const {
+ return uid == other.uid && string_uid == other.string_uid &&
+ time_start == other.time_start && time_end == other.time_end &&
+ displays == other.displays && atoms == other.atoms;
+ }
+};
+
+/**
+ A parsed \WebMID{EditionEntry} element.
+ */
+struct EditionEntry {
+ /**
+ Parsed \WebMID{ChapterAtom} elements.
+ */
+ std::vector<Element<ChapterAtom>> atoms;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const EditionEntry& other) const {
+ return atoms == other.atoms;
+ }
+};
+
+/**
+ A parsed \WebMID{SimpleTag} element.
+ */
+struct SimpleTag {
+ /**
+ A parsed \WebMID{TagName} element.
+ */
+ Element<std::string> name;
+
+ /**
+ A parsed \WebMID{TagLanguage} element.
+ */
+ Element<std::string> language{"und"};
+
+ /**
+ A parsed \WebMID{TagDefault} element.
+ */
+ Element<bool> is_default{true};
+
+ /**
+ A parsed \WebMID{TagString} element.
+ */
+ Element<std::string> string;
+
+ /**
+ A parsed \WebMID{TagBinary} element.
+ */
+ Element<std::vector<std::uint8_t>> binary;
+
+ /**
+ Parsed \WebMID{SimpleTag} elements.
+ */
+ std::vector<Element<SimpleTag>> tags;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const SimpleTag& other) const {
+ return name == other.name && language == other.language &&
+ is_default == other.is_default && string == other.string &&
+ binary == other.binary && tags == other.tags;
+ }
+};
+
+/**
+ A parsed \WebMID{Targets} element.
+ */
+struct Targets {
+ /**
+ A parsed \WebMID{TargetTypeValue} element.
+ */
+ Element<std::uint64_t> type_value{50};
+
+ /**
+ A parsed \WebMID{TargetType} element.
+ */
+ Element<std::string> type;
+
+ /**
+ Parsed \WebMID{TagTrackUID} elements.
+ */
+ std::vector<Element<std::uint64_t>> track_uids;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Targets& other) const {
+ return type_value == other.type_value && type == other.type &&
+ track_uids == other.track_uids;
+ }
+};
+
+/**
+ A parsed \WebMID{Tag} element.
+ */
+struct Tag {
+ /**
+ A parsed \WebMID{Targets} element.
+ */
+ Element<Targets> targets;
+
+ /**
+ Parsed \WebMID{SimpleTag} elements.
+ */
+ std::vector<Element<SimpleTag>> tags;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const Tag& other) const {
+ return targets == other.targets && tags == other.tags;
+ }
+};
+
+/**
+ @}
+ */
+
+} // namespace webm
+
+#endif // INCLUDE_WEBM_DOM_TYPES_H_
diff --git a/webm_parser/include/webm/element.h b/webm_parser/include/webm/element.h
new file mode 100644
index 0000000..a7f6235
--- /dev/null
+++ b/webm_parser/include/webm/element.h
@@ -0,0 +1,208 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef INCLUDE_WEBM_ELEMENT_H_
+#define INCLUDE_WEBM_ELEMENT_H_
+
+#include <cstdint>
+#include <limits>
+#include <utility>
+
+#include "./id.h"
+
+/**
+ \file
+ A wrapper around an object that represents a WebM element, including its parsed
+ metadata.
+ */
+
+namespace webm {
+
+/**
+ \addtogroup PUBLIC_API
+ @{
+ */
+
+/**
+ A wrapper around an object that represents a WebM element.
+
+ Since some elements may be absent, this wrapper is used to indicate the
+ presence (or lack thereof) of an element in a WebM document. If the element is
+ encoded in the file and it has been parsed, `is_present()` will return true.
+ Otherwise it will return false since the element was ommitted or skipped when
+ parsing.
+ */
+template <typename T>
+class Element {
+ public:
+ /**
+ Value-initializes the element's value and makes `is_present()` false.
+ */
+ constexpr Element() = default;
+
+ /**
+ Creates an element with the given value and makes `is_present()` false.
+
+ \param value The value of the element.
+ */
+ explicit constexpr Element(const T& value) : value_(value) {}
+
+ /**
+ Creates an element with the given value and makes `is_present()` false.
+
+ \param value The value of the element.
+ */
+ explicit constexpr Element(T&& value) : value_(std::move(value)) {}
+
+ /**
+ Creates an element with the given value and presence state.
+
+ \param value The value of the element.
+ \param is_present True if the element is present, false if it is absent.
+ */
+ constexpr Element(const T& value, bool is_present)
+ : value_(value), is_present_(is_present) {}
+
+ /**
+ Creates an element with the given value and presence state.
+
+ \param value The value of the element.
+ \param is_present True if the element is present, false if it is absent.
+ */
+ constexpr Element(T&& value, bool is_present)
+ : value_(std::move(value)), is_present_(is_present) {}
+
+ constexpr Element(const Element<T>& other) = default;
+ constexpr Element(Element<T>&& other) = default;
+
+ ~Element() = default;
+
+ Element<T>& operator=(const Element<T>& other) = default;
+ Element<T>& operator=(Element<T>&& other) = default;
+
+ /**
+ Sets the element's value and state.
+
+ \param value The new value for the element.
+ \param is_present The new presence state for the element.
+ */
+ void Set(const T& value, bool is_present) {
+ value_ = value;
+ is_present_ = is_present;
+ }
+
+ /**
+ Sets the element's value and state.
+
+ \param value The new value for the element.
+ \param is_present The new presence state for the element.
+ */
+ void Set(T&& value, bool is_present) {
+ value_ = std::move(value);
+ is_present_ = is_present;
+ }
+
+ /**
+ Gets the element's value.
+ */
+ constexpr const T& value() const { return value_; }
+
+ /**
+ Gets a mutuable pointer to the element's value (will never be null).
+ */
+ T* mutable_value() { return &value_; }
+
+ /**
+ Returns true if the element is present, false otherwise.
+ */
+ constexpr bool is_present() const { return is_present_; }
+
+ bool operator==(const Element<T>& other) const {
+ return is_present_ == other.is_present_ && value_ == other.value_;
+ }
+
+ private:
+ T value_{};
+ bool is_present_ = false;
+};
+
+/**
+ Metadata for WebM elements that are encountered when parsing.
+ */
+struct ElementMetadata {
+ /**
+ The EBML ID of the element.
+ */
+ Id id;
+
+ /**
+ The number of bytes that were used to encode the EBML ID and element size.
+
+ If the size of the header is unknown (which is only the case if a seek was
+ performed to the middle of an element, so its header was not parsed), this
+ will be the value `kUnknownHeaderSize`.
+ */
+ std::uint32_t header_size;
+
+ /**
+ The size of the element.
+
+ This is number of bytes in the element's body, which excludes the header
+ bytes.
+
+ If the size of the element's body is unknown, this will be the value
+ `kUnknownElementSize`.
+ */
+ std::uint64_t size;
+
+ /**
+ The absolute byte position of the element, starting at the first byte of the
+ element's header.
+
+ If the position of the element is unknown (which is only the case if a seek
+ was performed to the middle of an element), this will be the value
+ `kUnknownElementPosition`.
+ */
+ std::uint64_t position;
+
+ /**
+ Returns true if every member within the two objects are equal.
+ */
+ bool operator==(const ElementMetadata& other) const {
+ return id == other.id && header_size == other.header_size &&
+ size == other.size && position == other.position;
+ }
+};
+
+/**
+ A special value for `ElementMetadata::header_size` indicating the header size
+ is not known.
+ */
+constexpr std::uint64_t kUnknownHeaderSize =
+ std::numeric_limits<std::uint32_t>::max();
+
+/**
+ A special value for `ElementMetadata::size` indicating the element's size is
+ not known.
+ */
+constexpr std::uint64_t kUnknownElementSize =
+ std::numeric_limits<std::uint64_t>::max();
+
+/**
+ A special value for `ElementMetadata::position` indicating the element's
+ position is not known.
+ */
+constexpr std::uint64_t kUnknownElementPosition =
+ std::numeric_limits<std::uint64_t>::max();
+
+/**
+ @}
+ */
+
+} // namespace webm
+
+#endif // INCLUDE_WEBM_ELEMENT_H_
diff --git a/webm_parser/include/webm/file_reader.h b/webm_parser/include/webm/file_reader.h
new file mode 100644
index 0000000..6ccdc4d
--- /dev/null
+++ b/webm_parser/include/webm/file_reader.h
@@ -0,0 +1,90 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef INCLUDE_WEBM_FILE_READER_H_
+#define INCLUDE_WEBM_FILE_READER_H_
+
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <memory>
+
+#include "./reader.h"
+#include "./status.h"
+
+/**
+ \file
+ A `Reader` implementation that reads from a `FILE*`.
+ */
+
+namespace webm {
+
+/**
+ A `Reader` implementation that can read from `FILE*` resources.
+ */
+class FileReader : public Reader {
+ public:
+ /**
+ Constructs a new, empty reader.
+ */
+ FileReader() = default;
+
+ /**
+ Constructs a new reader, using the provided file as the data source.
+
+ Ownership of the file is taken, and `std::fclose()` will be called when the
+ object is destroyed.
+
+ \param file The file to use as a data source. Must not be null. The file will
+ be closed (via `std::fclose()`) when the `FileReader`'s destructor runs.
+ */
+ explicit FileReader(FILE* file);
+
+ /**
+ Constructs a new reader by moving the provided reader into the new reader.
+
+ \param other The source reader to move. After moving, it will be reset to an
+ empty stream.
+ */
+ FileReader(FileReader&& other);
+
+ /**
+ Moves the provided reader into this reader.
+
+ \param other The source reader to move. After moving, it will be reset to an
+ empty stream. May be equal to `*this`, in which case this is a no-op.
+ \return `*this`.
+ */
+ FileReader& operator=(FileReader&& other);
+
+ Status Read(std::size_t num_to_read, std::uint8_t* buffer,
+ std::uint64_t* num_actually_read) override;
+
+ Status Skip(std::uint64_t num_to_skip,
+ std::uint64_t* num_actually_skipped) override;
+
+ std::uint64_t Position() const override;
+
+ private:
+ struct FileCloseFunctor {
+ void operator()(FILE* file) const {
+ if (file)
+ std::fclose(file);
+ }
+ };
+
+ std::unique_ptr<FILE, FileCloseFunctor> file_;
+
+ // We can't rely on ftell() for the position (since it only returns long, and
+ // doesn't work on things like pipes); we need to manually track the reading
+ // position.
+ std::uint64_t position_ = 0;
+};
+
+} // namespace webm
+
+#endif // INCLUDE_WEBM_FILE_READER_H_
diff --git a/webm_parser/include/webm/id.h b/webm_parser/include/webm/id.h
new file mode 100644
index 0000000..0f761db
--- /dev/null
+++ b/webm_parser/include/webm/id.h
@@ -0,0 +1,1085 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef INCLUDE_WEBM_ID_H_
+#define INCLUDE_WEBM_ID_H_
+
+#include <cstdint>
+
+/**
+ \file
+ A full enumeration of WebM's EBML IDs.
+ */
+
+namespace webm {
+
+/**
+ \addtogroup PUBLIC_API
+ @{
+ */
+
+/**
+ An EBML ID for a WebM element.
+
+ The enum names correspond to the element names from the Matroska and WebM
+ specifications. See those specifications for further information on each
+ element.
+ */
+// For the WebM spec and element info, see:
+// http://www.webmproject.org/docs/container/
+// http://www.webmproject.org/docs/webm-encryption/#42-new-matroskawebm-elements
+// http://matroska.org/technical/specs/index.html
+enum class Id : std::uint32_t {
+ // The MatroskaID alias links to the WebM and Matroska specifications.
+ // The WebMID alias links to the WebM specification.
+ // The WebMTable alias produces a table given the following arguments:
+ // Type, Level, Mandatory, Multiple, Recursive, Value range, Default value
+
+ /**
+ \MatroskaID{EBML} element ID.
+
+ \WebMTable{Master, 0, Yes, Yes, No, , }
+ */
+ kEbml = 0x1A45DFA3,
+
+ /**
+ \MatroskaID{EBMLVersion} element ID.
+
+ \WebMTable{Unsigned integer, 1, Yes, No, No, , 1}
+ */
+ kEbmlVersion = 0x4286,
+
+ /**
+ \MatroskaID{EBMLReadVersion} element ID.
+
+ \WebMTable{Unsigned integer, 1, Yes, No, No, , 1}
+ */
+ kEbmlReadVersion = 0x42F7,
+
+ /**
+ \MatroskaID{EBMLMaxIDLength} element ID.
+
+ \WebMTable{Unsigned integer, 1, Yes, No, No, , 4}
+ */
+ kEbmlMaxIdLength = 0x42F2,
+
+ /**
+ \MatroskaID{EBMLMaxSizeLength} element ID.
+
+ \WebMTable{Unsigned integer, 1, Yes, No, No, , 8}
+ */
+ kEbmlMaxSizeLength = 0x42F3,
+
+ /**
+ \MatroskaID{DocType} element ID.
+
+ \WebMTable{ASCII string, 1, Yes, No, No, , matroska}
+ */
+ kDocType = 0x4282,
+
+ /**
+ \MatroskaID{DocTypeVersion} element ID.
+
+ \WebMTable{Unsigned integer, 1, Yes, No, No, , 1}
+ */
+ kDocTypeVersion = 0x4287,
+
+ /**
+ \MatroskaID{DocTypeReadVersion} element ID.
+
+ \WebMTable{Unsigned integer, 1, Yes, No, No, , 1}
+ */
+ kDocTypeReadVersion = 0x4285,
+
+ /**
+ \MatroskaID{Void} element ID.
+
+ \WebMTable{Binary, g, No, No, No, , }
+ */
+ kVoid = 0xEC,
+
+ /**
+ \MatroskaID{Segment} element ID.
+
+ \WebMTable{Master, 0, Yes, Yes, No, , }
+ */
+ kSegment = 0x18538067,
+
+ /**
+ \MatroskaID{SeekHead} element ID.
+
+ \WebMTable{Master, 1, No, Yes, No, , }
+ */
+ kSeekHead = 0x114D9B74,
+
+ /**
+ \MatroskaID{Seek} element ID.
+
+ \WebMTable{Master, 2, Yes, Yes, No, , }
+ */
+ kSeek = 0x4DBB,
+
+ /**
+ \MatroskaID{SeekID} element ID.
+
+ \WebMTable{Binary, 3, Yes, No, No, , }
+ */
+ kSeekId = 0x53AB,
+
+ /**
+ \MatroskaID{SeekPosition} element ID.
+
+ \WebMTable{Unsigned integer, 3, Yes, No, No, , 0}
+ */
+ kSeekPosition = 0x53AC,
+
+ /**
+ \MatroskaID{Info} element ID.
+
+ \WebMTable{Master, 1, Yes, Yes, No, , }
+ */
+ kInfo = 0x1549A966,
+
+ /**
+ \MatroskaID{TimecodeScale} element ID.
+
+ \WebMTable{Unsigned integer, 2, Yes, No, No, , 1000000}
+ */
+ kTimecodeScale = 0x2AD7B1,
+
+ /**
+ \MatroskaID{Duration} element ID.
+
+ \WebMTable{Float, 2, No, No, No, > 0, 0}
+ */
+ kDuration = 0x4489,
+
+ /**
+ \MatroskaID{DateUTC} element ID.
+
+ \WebMTable{Date, 2, No, No, No, , 0}
+ */
+ kDateUtc = 0x4461,
+
+ /**
+ \MatroskaID{Title} element ID.
+
+ \WebMTable{UTF-8 string, 2, No, No, No, , }
+ */
+ kTitle = 0x7BA9,
+
+ /**
+ \MatroskaID{MuxingApp} element ID.
+
+ \WebMTable{UTF-8 string, 2, Yes, No, No, , }
+ */
+ kMuxingApp = 0x4D80,
+
+ /**
+ \MatroskaID{WritingApp} element ID.
+
+ \WebMTable{UTF-8 string, 2, Yes, No, No, , }
+ */
+ kWritingApp = 0x5741,
+
+ /**
+ \MatroskaID{Cluster} element ID.
+
+ \WebMTable{Master, 1, No, Yes, No, , }
+ */
+ kCluster = 0x1F43B675,
+
+ /**
+ \MatroskaID{Timecode} element ID.
+
+ \WebMTable{Unsigned integer, 2, Yes, No, No, , 0}
+ */
+ kTimecode = 0xE7,
+
+ /**
+ \MatroskaID{PrevSize} element ID.
+
+ \WebMTable{Unsigned integer, 2, No, No, No, , 0}
+ */
+ kPrevSize = 0xAB,
+
+ /**
+ \MatroskaID{SimpleBlock} element ID.
+
+ \WebMTable{Binary, 2, No, Yes, No, , }
+ */
+ kSimpleBlock = 0xA3,
+
+ /**
+ \MatroskaID{BlockGroup} element ID.
+
+ \WebMTable{Master, 2, No, Yes, No, , }
+ */
+ kBlockGroup = 0xA0,
+
+ /**
+ \MatroskaID{Block} element ID.
+
+ \WebMTable{Binary, 3, Yes, No, No, , }
+ */
+ kBlock = 0xA1,
+
+ /**
+ \MatroskaID{BlockVirtual} (deprecated) element ID.
+
+ \WebMTable{Binary, 3, No, No, No, , }
+ */
+ kBlockVirtual = 0xA2,
+
+ /**
+ \MatroskaID{BlockAdditions} element ID.
+
+ \WebMTable{Master, 3, No, No, No, , }
+ */
+ kBlockAdditions = 0x75A1,
+
+ /**
+ \MatroskaID{BlockMore} element ID.
+
+ \WebMTable{Master, 4, Yes, Yes, No, , }
+ */
+ kBlockMore = 0xA6,
+
+ /**
+ \MatroskaID{BlockAddID} element ID.
+
+ \WebMTable{Unsigned integer, 5, Yes, No, No, Not 0, 1}
+ */
+ kBlockAddId = 0xEE,
+
+ /**
+ \MatroskaID{BlockAdditional} element ID.
+
+ \WebMTable{Binary, 5, Yes, No, No, , }
+ */
+ kBlockAdditional = 0xA5,
+
+ /**
+ \MatroskaID{BlockDuration} element ID.
+
+ \WebMTable{Unsigned integer, 3, No, No, No, , DefaultDuration}
+ */
+ kBlockDuration = 0x9B,
+
+ /**
+ \MatroskaID{ReferenceBlock} element ID.
+
+ \WebMTable{Signed integer, 3, No, Yes, No, , 0}
+ */
+ kReferenceBlock = 0xFB,
+
+ /**
+ \MatroskaID{DiscardPadding} element ID.
+
+ \WebMTable{Signed integer, 3, No, No, No, , 0}
+ */
+ kDiscardPadding = 0x75A2,
+
+ /**
+ \MatroskaID{Slices} (deprecated).
+
+ \WebMTable{Master, 3, No, No, No, , }
+ */
+ kSlices = 0x8E,
+
+ /**
+ \MatroskaID{TimeSlice} (deprecated) element ID.
+
+ \WebMTable{Master, 4, No, Yes, No, , }
+ */
+ kTimeSlice = 0xE8,
+
+ /**
+ \MatroskaID{LaceNumber} (deprecated) element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kLaceNumber = 0xCC,
+
+ /**
+ \MatroskaID{Tracks} element ID.
+
+ \WebMTable{Master, 1, No, Yes, No, , }
+ */
+ kTracks = 0x1654AE6B,
+
+ /**
+ \MatroskaID{TrackEntry} element ID.
+
+ \WebMTable{Master, 2, Yes, Yes, No, , }
+ */
+ kTrackEntry = 0xAE,
+
+ /**
+ \MatroskaID{TrackNumber} element ID.
+
+ \WebMTable{Unsigned integer, 3, Yes, No, No, Not 0, 0}
+ */
+ kTrackNumber = 0xD7,
+
+ /**
+ \MatroskaID{TrackUID} element ID.
+
+ \WebMTable{Unsigned integer, 3, Yes, No, No, Not 0, 0}
+ */
+ kTrackUid = 0x73C5,
+
+ /**
+ \MatroskaID{TrackType} element ID.
+
+ \WebMTable{Unsigned integer, 3, Yes, No, No, 1-254, 0}
+ */
+ kTrackType = 0x83,
+
+ /**
+ \MatroskaID{FlagEnabled} element ID.
+
+ \WebMTable{Unsigned integer, 3, Yes, No, No, 0-1, 1}
+ */
+ kFlagEnabled = 0xB9,
+
+ /**
+ \MatroskaID{FlagDefault} element ID.
+
+ \WebMTable{Unsigned integer, 3, Yes, No, No, 0-1, 1}
+ */
+ kFlagDefault = 0x88,
+
+ /**
+ \MatroskaID{FlagForced} element ID.
+
+ \WebMTable{Unsigned integer, 3, Yes, No, No, 0-1, 0}
+ */
+ kFlagForced = 0x55AA,
+
+ /**
+ \MatroskaID{FlagLacing} element ID.
+
+ \WebMTable{Unsigned integer, 3, Yes, No, No, 0-1, 1}
+ */
+ kFlagLacing = 0x9C,
+
+ /**
+ \MatroskaID{DefaultDuration} element ID.
+
+ \WebMTable{Unsigned integer, 3, No, No, No, Not 0, 0}
+ */
+ kDefaultDuration = 0x23E383,
+
+ /**
+ \MatroskaID{Name} element ID.
+
+ \WebMTable{UTF-8 string, 3, No, No, No, , }
+ */
+ kName = 0x536E,
+
+ /**
+ \MatroskaID{Language} element ID.
+
+ \WebMTable{ASCII string, 3, No, No, No, , eng}
+ */
+ kLanguage = 0x22B59C,
+
+ /**
+ \MatroskaID{CodecID} element ID.
+
+ \WebMTable{ASCII string, 3, Yes, No, No, , }
+ */
+ kCodecId = 0x86,
+
+ /**
+ \MatroskaID{CodecPrivate} element ID.
+
+ \WebMTable{Binary, 3, No, No, No, , }
+ */
+ kCodecPrivate = 0x63A2,
+
+ /**
+ \MatroskaID{CodecName} element ID.
+
+ \WebMTable{UTF-8 string, 3, No, No, No, , }
+ */
+ kCodecName = 0x258688,
+
+ /**
+ \MatroskaID{CodecDelay} element ID.
+
+ \WebMTable{Unsigned integer, 3, No, No, No, , 0}
+ */
+ kCodecDelay = 0x56AA,
+
+ /**
+ \MatroskaID{SeekPreRoll} element ID.
+
+ \WebMTable{Unsigned integer, 3, Yes, No, No, , 0}
+ */
+ kSeekPreRoll = 0x56BB,
+
+ /**
+ \MatroskaID{Video} element ID.
+
+ \WebMTable{Master, 3, No, No, No, , }
+ */
+ kVideo = 0xE0,
+
+ /**
+ \MatroskaID{FlagInterlaced} element ID.
+
+ \WebMTable{Unsigned integer, 4, Yes, No, No, 0-1, 0}
+ */
+ kFlagInterlaced = 0x9A,
+
+ /**
+ \MatroskaID{StereoMode} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kStereoMode = 0x53B8,
+
+ /**
+ \MatroskaID{AlphaMode} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kAlphaMode = 0x53C0,
+
+ /**
+ \MatroskaID{PixelWidth} element ID.
+
+ \WebMTable{Unsigned integer, 4, Yes, No, No, Not 0, 0}
+ */
+ kPixelWidth = 0xB0,
+
+ /**
+ \MatroskaID{PixelHeight} element ID.
+
+ \WebMTable{Unsigned integer, 4, Yes, No, No, Not 0, 0}
+ */
+ kPixelHeight = 0xBA,
+
+ /**
+ \MatroskaID{PixelCropBottom} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kPixelCropBottom = 0x54AA,
+
+ /**
+ \MatroskaID{PixelCropTop} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kPixelCropTop = 0x54BB,
+
+ /**
+ \MatroskaID{PixelCropLeft} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kPixelCropLeft = 0x54CC,
+
+ /**
+ \MatroskaID{PixelCropRight} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kPixelCropRight = 0x54DD,
+
+ /**
+ \MatroskaID{DisplayWidth} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, Not 0, PixelWidth}
+ */
+ kDisplayWidth = 0x54B0,
+
+ /**
+ \MatroskaID{DisplayHeight} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, Not 0, PixelHeight}
+ */
+ kDisplayHeight = 0x54BA,
+
+ /**
+ \MatroskaID{DisplayUnit} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kDisplayUnit = 0x54B2,
+
+ /**
+ \MatroskaID{AspectRatioType} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kAspectRatioType = 0x54B3,
+
+ /**
+ \MatroskaID{FrameRate} (deprecated) element ID.
+
+ \WebMTable{Float, 4, No, No, No, > 0, 0}
+ */
+ kFrameRate = 0x2383E3,
+
+ /**
+ \MatroskaID{Colour} element ID.
+
+ \WebMTable{Master, 4, No, No, No, , }
+ */
+ kColour = 0x55B0,
+
+ /**
+ \MatroskaID{MatrixCoefficients} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 2}
+ */
+ kMatrixCoefficients = 0x55B1,
+
+ /**
+ \MatroskaID{BitsPerChannel} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kBitsPerChannel = 0x55B2,
+
+ /**
+ \MatroskaID{ChromaSubsamplingHorz} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kChromaSubsamplingHorz = 0x55B3,
+
+ /**
+ \MatroskaID{ChromaSubsamplingVert} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kChromaSubsamplingVert = 0x55B4,
+
+ /**
+ \MatroskaID{CbSubsamplingHorz} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kCbSubsamplingHorz = 0x55B5,
+
+ /**
+ \MatroskaID{CbSubsamplingVert} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kCbSubsamplingVert = 0x55B6,
+
+ /**
+ \MatroskaID{ChromaSitingHorz} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kChromaSitingHorz = 0x55B7,
+
+ /**
+ \MatroskaID{ChromaSitingVert} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kChromaSitingVert = 0x55B8,
+
+ /**
+ \MatroskaID{Range} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kRange = 0x55B9,
+
+ /**
+ \MatroskaID{TransferCharacteristics} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 2}
+ */
+ kTransferCharacteristics = 0x55BA,
+
+ /**
+ \MatroskaID{Primaries} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 2}
+ */
+ kPrimaries = 0x55BB,
+
+ /**
+ \MatroskaID{MaxCLL} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kMaxCll = 0x55BC,
+
+ /**
+ \MatroskaID{MaxFALL} element ID.
+
+ \WebMTable{Unsigned integer, 5, No, No, No, , 0}
+ */
+ kMaxFall = 0x55BD,
+
+ /**
+ \MatroskaID{MasteringMetadata} element ID.
+
+ \WebMTable{Master, 5, No, No, No, , }
+ */
+ kMasteringMetadata = 0x55D0,
+
+ /**
+ \MatroskaID{PrimaryRChromaticityX} element ID.
+
+ \WebMTable{Float, 6, No, No, No, 0-1, 0}
+ */
+ kPrimaryRChromaticityX = 0x55D1,
+
+ /**
+ \MatroskaID{PrimaryRChromaticityY} element ID.
+
+ \WebMTable{Float, 6, No, No, No, 0-1, 0}
+ */
+ kPrimaryRChromaticityY = 0x55D2,
+
+ /**
+ \MatroskaID{PrimaryGChromaticityX} element ID.
+
+ \WebMTable{Float, 6, No, No, No, 0-1, 0}
+ */
+ kPrimaryGChromaticityX = 0x55D3,
+
+ /**
+ \MatroskaID{PrimaryGChromaticityY} element ID.
+
+ \WebMTable{Float, 6, No, No, No, 0-1, 0}
+ */
+ kPrimaryGChromaticityY = 0x55D4,
+
+ /**
+ \MatroskaID{PrimaryBChromaticityX} element ID.
+
+ \WebMTable{Float, 6, No, No, No, 0-1, 0}
+ */
+ kPrimaryBChromaticityX = 0x55D5,
+
+ /**
+ \MatroskaID{PrimaryBChromaticityY} element ID.
+
+ \WebMTable{Float, 6, No, No, No, 0-1, 0}
+ */
+ kPrimaryBChromaticityY = 0x55D6,
+
+ /**
+ \MatroskaID{WhitePointChromaticityX} element ID.
+
+ \WebMTable{Float, 6, No, No, No, 0-1, 0}
+ */
+ kWhitePointChromaticityX = 0x55D7,
+
+ /**
+ \MatroskaID{WhitePointChromaticityY} element ID.
+
+ \WebMTable{Float, 6, No, No, No, 0-1, 0}
+ */
+ kWhitePointChromaticityY = 0x55D8,
+
+ /**
+ \MatroskaID{LuminanceMax} element ID.
+
+ \WebMTable{Float, 6, No, No, No, 0-9999.99, 0}
+ */
+ kLuminanceMax = 0x55D9,
+
+ /**
+ \MatroskaID{LuminanceMin} element ID.
+
+ \WebMTable{Float, 6, No, No, No, 0-999.9999, 0}
+ */
+ kLuminanceMin = 0x55DA,
+
+ /**
+ \WebMID{Projection} element ID.
+
+ \WebMTable{Master, 5, No, No, No, , }
+ */
+ kProjection = 0x7670,
+
+ /**
+ \WebMID{ProjectionType} element ID.
+
+ \WebMTable{Unsigned integer, 6, Yes, No, No, , 0}
+ */
+ kProjectionType = 0x7671,
+
+ /**
+ \WebMID{ProjectionPrivate} element ID.
+
+ \WebMTable{Binary, 6, No, No, No, , }
+ */
+ kProjectionPrivate = 0x7672,
+
+ /**
+ \WebMID{ProjectionPoseYaw} element ID.
+
+ \WebMTable{Float, 6, Yes, No, No, , 0}
+ */
+ kProjectionPoseYaw = 0x7673,
+
+ /**
+ \WebMID{ProjectionPosePitch} element ID.
+
+ \WebMTable{Float, 6, Yes, No, No, , 0}
+ */
+ kProjectionPosePitch = 0x7674,
+
+ /**
+ \WebMID{ProjectionPoseRoll} element ID.
+
+ \WebMTable{Float, 6, Yes, No, No, , 0}
+ */
+ kProjectionPoseRoll = 0x7675,
+
+ /**
+ \MatroskaID{Audio} element ID.
+
+ \WebMTable{Master, 3, No, No, No, , }
+ */
+ kAudio = 0xE1,
+
+ /**
+ \MatroskaID{SamplingFrequency} element ID.
+
+ \WebMTable{Float, 4, Yes, No, No, > 0, 8000}
+ */
+ kSamplingFrequency = 0xB5,
+
+ /**
+ \MatroskaID{OutputSamplingFrequency} element ID.
+
+ \WebMTable{Float, 4, No, No, No, > 0, SamplingFrequency}
+ */
+ kOutputSamplingFrequency = 0x78B5,
+
+ /**
+ \MatroskaID{Channels} element ID.
+
+ \WebMTable{Unsigned integer, 4, Yes, No, No, Not 0, 1}
+ */
+ kChannels = 0x9F,
+
+ /**
+ \MatroskaID{BitDepth} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, Not 0, 0}
+ */
+ kBitDepth = 0x6264,
+
+ /**
+ \MatroskaID{ContentEncodings} element ID.
+
+ \WebMTable{Master, 3, No, No, No, , }
+ */
+ kContentEncodings = 0x6D80,
+
+ /**
+ \MatroskaID{ContentEncoding} element ID.
+
+ \WebMTable{Master, 4, Yes, Yes, No, , }
+ */
+ kContentEncoding = 0x6240,
+
+ /**
+ \MatroskaID{ContentEncodingOrder} element ID.
+
+ \WebMTable{Unsigned integer, 5, Yes, No, No, , 0}
+ */
+ kContentEncodingOrder = 0x5031,
+
+ /**
+ \MatroskaID{ContentEncodingScope} element ID.
+
+ \WebMTable{Unsigned integer, 5, Yes, No, No, Not 0, 1}
+ */
+ kContentEncodingScope = 0x5032,
+
+ /**
+ \MatroskaID{ContentEncodingType} element ID.
+
+ \WebMTable{Unsigned integer, 5, Yes, No, No, , 0}
+ */
+ kContentEncodingType = 0x5033,
+
+ /**
+ \MatroskaID{ContentEncryption} element ID.
+
+ \WebMTable{Master, 5, No, No, No, , }
+ */
+ kContentEncryption = 0x5035,
+
+ /**
+ \MatroskaID{ContentEncAlgo} element ID.
+
+ \WebMTable{Unsigned integer, 6, No, No, No, , 0}
+ */
+ kContentEncAlgo = 0x47E1,
+
+ /**
+ \MatroskaID{ContentEncKeyID} element ID.
+
+ \WebMTable{Binary, 6, No, No, No, , }
+ */
+ kContentEncKeyId = 0x47E2,
+
+ /**
+ \WebMID{ContentEncAESSettings} element ID.
+
+ \WebMTable{Master, 6, No, No, No, , }
+ */
+ kContentEncAesSettings = 0x47E7,
+
+ /**
+ \WebMID{AESSettingsCipherMode} element ID.
+
+ \WebMTable{Unsigned integer, 7, Yes, No, No, 1, 1}
+ */
+ kAesSettingsCipherMode = 0x47E8,
+
+ /**
+ \MatroskaID{Cues} element ID.
+
+ \WebMTable{Master, 1, No, No, No, , }
+ */
+ kCues = 0x1C53BB6B,
+
+ /**
+ \MatroskaID{CuePoint} element ID.
+
+ \WebMTable{Master, 2, Yes, Yes, No, , }
+ */
+ kCuePoint = 0xBB,
+
+ /**
+ \MatroskaID{CueTime} element ID.
+
+ \WebMTable{Unsigned integer, 3, Yes, No, No, , 0}
+ */
+ kCueTime = 0xB3,
+
+ /**
+ \MatroskaID{CueTrackPositions} element ID.
+
+ \WebMTable{Master, 3, Yes, Yes, No, , }
+ */
+ kCueTrackPositions = 0xB7,
+
+ /**
+ \MatroskaID{CueTrack} element ID.
+
+ \WebMTable{Unsigned integer, 4, Yes, No, No, Not 0, 0}
+ */
+ kCueTrack = 0xF7,
+
+ /**
+ \MatroskaID{CueClusterPosition} element ID.
+
+ \WebMTable{Unsigned integer, 4, Yes, No, No, , 0}
+ */
+ kCueClusterPosition = 0xF1,
+
+ /**
+ \MatroskaID{CueRelativePosition} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kCueRelativePosition = 0xF0,
+
+ /**
+ \MatroskaID{CueDuration} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kCueDuration = 0xB2,
+
+ /**
+ \MatroskaID{CueBlockNumber} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, Not 0, 1}
+ */
+ kCueBlockNumber = 0x5378,
+
+ /**
+ \MatroskaID{Chapters} element ID.
+
+ \WebMTable{Master, 1, No, No, No, , }
+ */
+ kChapters = 0x1043A770,
+
+ /**
+ \MatroskaID{EditionEntry} element ID.
+
+ \WebMTable{Master, 2, Yes, Yes, No, , }
+ */
+ kEditionEntry = 0x45B9,
+
+ /**
+ \MatroskaID{ChapterAtom} element ID.
+
+ \WebMTable{Master, 3, Yes, Yes, Yes, , }
+ */
+ kChapterAtom = 0xB6,
+
+ /**
+ \MatroskaID{ChapterUID} element ID.
+
+ \WebMTable{Unsigned integer, 4, Yes, No, No, Not 0, 0}
+ */
+ kChapterUid = 0x73C4,
+
+ /**
+ \MatroskaID{ChapterStringUID} element ID.
+
+ \WebMTable{UTF-8 string, 4, No, No, No, , }
+ */
+ kChapterStringUid = 0x5654,
+
+ /**
+ \MatroskaID{ChapterTimeStart} element ID.
+
+ \WebMTable{Unsigned integer, 4, Yes, No, No, , 0}
+ */
+ kChapterTimeStart = 0x91,
+
+ /**
+ \MatroskaID{ChapterTimeEnd} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 0}
+ */
+ kChapterTimeEnd = 0x92,
+
+ /**
+ \MatroskaID{ChapterDisplay} element ID.
+
+ \WebMTable{Master, 4, No, Yes, No, , }
+ */
+ kChapterDisplay = 0x80,
+
+ /**
+ \MatroskaID{ChapString} element ID.
+
+ \WebMTable{UTF-8 string, 5, Yes, No, No, , }
+ */
+ kChapString = 0x85,
+
+ /**
+ \MatroskaID{ChapLanguage} element ID.
+
+ \WebMTable{ASCII string, 5, Yes, Yes, No, , eng}
+ */
+ kChapLanguage = 0x437C,
+
+ /**
+ \MatroskaID{ChapCountry} element ID.
+
+ \WebMTable{ASCII string, 5, No, Yes, No, , }
+ */
+ kChapCountry = 0x437E,
+
+ /**
+ \MatroskaID{Tags} element ID.
+
+ \WebMTable{Master, 1, No, Yes, No, , }
+ */
+ kTags = 0x1254C367,
+
+ /**
+ \MatroskaID{Tag} element ID.
+
+ \WebMTable{Master, 2, Yes, Yes, No, , }
+ */
+ kTag = 0x7373,
+
+ /**
+ \MatroskaID{Targets} element ID.
+
+ \WebMTable{Master, 3, Yes, No, No, , }
+ */
+ kTargets = 0x63C0,
+
+ /**
+ \MatroskaID{TargetTypeValue} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, No, No, , 50}
+ */
+ kTargetTypeValue = 0x68CA,
+
+ /**
+ \MatroskaID{TargetType} element ID.
+
+ \WebMTable{ASCII string, 4, No, No, No, , }
+ */
+ kTargetType = 0x63CA,
+
+ /**
+ \MatroskaID{TagTrackUID} element ID.
+
+ \WebMTable{Unsigned integer, 4, No, Yes, No, , 0}
+ */
+ kTagTrackUid = 0x63C5,
+
+ /**
+ \MatroskaID{SimpleTag} element ID.
+
+ \WebMTable{Master, 3, Yes, Yes, Yes, , }
+ */
+ kSimpleTag = 0x67C8,
+
+ /**
+ \MatroskaID{TagName} element ID.
+
+ \WebMTable{UTF-8 string, 4, Yes, No, No, , }
+ */
+ kTagName = 0x45A3,
+
+ /**
+ \MatroskaID{TagLanguage} element ID.
+
+ \WebMTable{ASCII string, 4, Yes, No, No, , und}
+ */
+ kTagLanguage = 0x447A,
+
+ /**
+ \MatroskaID{TagDefault} element ID.
+
+ \WebMTable{Unsigned integer, 4, Yes, No, No, 0-1, 1}
+ */
+ kTagDefault = 0x4484,
+
+ /**
+ \MatroskaID{TagString} element ID.
+
+ \WebMTable{UTF-8 string, 4, No, No, No, , }
+ */
+ kTagString = 0x4487,
+
+ /**
+ \MatroskaID{TagBinary} element ID.
+
+ \WebMTable{Binary, 4, No, No, No, , }
+ */
+ kTagBinary = 0x4485,
+};
+
+/**
+ @}
+ */
+
+} // namespace webm
+
+#endif // INCLUDE_WEBM_ID_H_
diff --git a/webm_parser/include/webm/istream_reader.h b/webm_parser/include/webm/istream_reader.h
new file mode 100644
index 0000000..9de1053
--- /dev/null
+++ b/webm_parser/include/webm/istream_reader.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef INCLUDE_WEBM_ISTREAM_READER_H_
+#define INCLUDE_WEBM_ISTREAM_READER_H_
+
+#include <cstdint>
+#include <cstdlib>
+#include <istream>
+#include <memory>
+#include <type_traits>
+
+#include "./reader.h"
+#include "./status.h"
+
+/**
+ \file
+ A `Reader` implementation that reads from a `std::istream`.
+ */
+
+namespace webm {
+
+/**
+ A `Reader` implementation that can read from `std::istream`-based resources.
+ */
+class IstreamReader : public Reader {
+ public:
+ /**
+ Constructs a new, empty reader.
+ */
+ IstreamReader() = default;
+
+ /**
+ Constructs a new reader, using the provided `std::istream` as the data
+ source.
+
+ Ownership of the stream is taken, and it will be moved into a new internal
+ instance.
+
+ \param istream The stream to use as a data source. Must be an rvalue
+ reference derived from `std::istream`.
+ */
+ template <typename T>
+ explicit IstreamReader(T&& istream) : istream_(new T(std::move(istream))) {}
+
+ /**
+ Constructs a new reader by moving the provided reader into the new reader.
+
+ \param other The source reader to move. After moving, it will be reset to an
+ empty stream.
+ */
+ IstreamReader(IstreamReader&& other);
+
+ /**
+ Moves the provided reader into this reader.
+
+ \param other The source reader to move. After moving, it will be reset to an
+ empty stream. May be equal to `*this`, in which case this is a no-op.
+ \return `*this`.
+ */
+ IstreamReader& operator=(IstreamReader&& other);
+
+ Status Read(std::size_t num_to_read, std::uint8_t* buffer,
+ std::uint64_t* num_actually_read) override;
+
+ Status Skip(std::uint64_t num_to_skip,
+ std::uint64_t* num_actually_skipped) override;
+
+ std::uint64_t Position() const override;
+
+ /**
+ Constructs a new reader and its data source in place.
+
+ `T` must be derived from `std::istream` and constructible from the provided
+ arguments.
+
+ \param args Arguments that will be forwarded to the `T`'s constructor.
+ \return A new `IstreamReader` backed by the underlying data source equivalent
+ to `T(std::forward<Args>(args)...`.
+ */
+ template <typename T, typename... Args>
+ static IstreamReader Emplace(Args&&... args) {
+ IstreamReader reader;
+ reader.istream_.reset(new T(std::forward<Args>(args)...));
+ return reader;
+ }
+
+ private:
+ std::unique_ptr<std::istream> istream_;
+ std::uint64_t position_ = 0;
+};
+
+} // namespace webm
+
+#endif // INCLUDE_WEBM_ISTREAM_READER_H_
diff --git a/webm_parser/include/webm/reader.h b/webm_parser/include/webm/reader.h
new file mode 100644
index 0000000..9275b50
--- /dev/null
+++ b/webm_parser/include/webm/reader.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef INCLUDE_WEBM_READER_H_
+#define INCLUDE_WEBM_READER_H_
+
+#include <cstddef>
+#include <cstdint>
+
+#include "./status.h"
+
+/**
+ \file
+ An interface that acts as a data source for the parser to read from.
+ */
+
+namespace webm {
+
+/**
+ \addtogroup PUBLIC_API
+ @{
+ */
+
+/**
+ A generic interface for reading from a data source.
+
+ Throwing an exception from the member functions is permitted, though if the
+ exception will be caught and parsing resumed, then the reader should not
+ advance its position before throwing the exception.
+ */
+class Reader {
+ public:
+ virtual ~Reader() = default;
+
+ /**
+ Reads data from the source and advances the reader's position by the number
+ of bytes read.
+
+ Short reads are permitted, as is reading no data.
+
+ \param num_to_read The number of bytes that should be read. Must not be 0.
+ \param buffer The buffer to store the read bytes. Must be large enough to
+ store at least `num_to_read` bytes. Must not be null.
+ \param[out] num_actually_read The number of bytes that were actually read is
+ stored in this integer. Must not be null.
+ \return `Status::kOkCompleted` if `num_to_read` bytes were read.
+ `Status::kOkPartial` if the number of bytes read is > 0 and < `num_to_read`.
+ If no bytes are read, then some status other than `Status::kOkCompleted` and
+ `Status::kOkPartial` must be returned and `num_actually_read` must be set to
+ 0.
+ */
+ virtual Status Read(std::size_t num_to_read, std::uint8_t* buffer,
+ std::uint64_t* num_actually_read) = 0;
+
+ /**
+ Skips data from the source and advances the reader's position by the number
+ of bytes skipped.
+
+ Short skips are permitted, as is skipping no data. This is similar to the
+ `Read()` method, but does not store data in an output buffer.
+
+ \param num_to_skip The number of bytes that should be skipped. Must not be 0.
+ \param[out] num_actually_skipped The number of bytes that were actually
+ skipped is stored in this integer. Must not be null.
+ \return `Status::kOkCompleted` if `num_to_skip` bytes were skipped.
+ `Status::kOkPartial` if the number of bytes skipped is > 0 and <
+ `num_to_skip`. If no bytes are skipped, then some status other than
+ `Status::kOkCompleted` and `Status::kOkPartial` must be returned and
+ `num_actually_skipped` must be set to 0.
+ */
+ virtual Status Skip(std::uint64_t num_to_skip,
+ std::uint64_t* num_actually_skipped) = 0;
+
+ /**
+ Gets the Reader's current absolute byte position in the stream.
+
+ Implementations don't necessarily need to start from 0 (which might be the
+ case if parsing is starting in the middle of a data source). The value
+ `kUnknownElementPosition` must not be returned.
+ */
+ virtual std::uint64_t Position() const = 0;
+};
+
+/**
+ @}
+ */
+
+} // namespace webm
+
+#endif // INCLUDE_WEBM_READER_H_
diff --git a/webm_parser/include/webm/status.h b/webm_parser/include/webm/status.h
new file mode 100644
index 0000000..ff3f0c6
--- /dev/null
+++ b/webm_parser/include/webm/status.h
@@ -0,0 +1,161 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef INCLUDE_WEBM_STATUS_H_
+#define INCLUDE_WEBM_STATUS_H_
+
+#include <cstdint>
+
+/**
+ \file
+ Status information that represents success, failure, etc. for operations
+ throughout the API.
+ */
+
+namespace webm {
+
+/**
+ \addtogroup PUBLIC_API
+ @{
+ */
+
+/**
+ An object used to represent the resulting status of various operations,
+ indicating success, failure, or some other result.
+ */
+struct Status {
+ /**
+ A list of generic status codes used by the parser. Users are encouraged to
+ reuse these values as needed in the derivations of `Callback`. These values
+ will always be <= 0.
+ */
+ enum Code : std::int32_t {
+ // General status codes. Range: 0 to -1024.
+ /**
+ The operation successfully completed.
+ */
+ kOkCompleted = 0,
+
+ /**
+ The operation was successful but only partially completed (for example, a
+ read that resulted in fewer bytes than requested).
+ */
+ kOkPartial = -1,
+
+ /**
+ The operation would block, and should be tried again later.
+ */
+ kWouldBlock = -2,
+
+ /**
+ More data was requested but the file has ended.
+ */
+ kEndOfFile = -3,
+
+ // Parsing errors. Range: -1025 to -2048.
+ /**
+ An element's ID is malformed.
+ */
+ kInvalidElementId = -1025,
+
+ /**
+ An element's size is malformed.
+ */
+ kInvalidElementSize = -1026,
+
+ /**
+ An unknown element has unknown size.
+ */
+ kIndefiniteUnknownElement = -1027,
+
+ /**
+ A child element overflowed the parent element's bounds.
+ */
+ kElementOverflow = -1028,
+
+ /**
+ An element's size exceeds the system's memory limits.
+ */
+ kNotEnoughMemory = -1029,
+
+ /**
+ An element's value is illegal/malformed.
+ */
+ kInvalidElementValue = -1030,
+
+ /**
+ A recursive element was so deeply nested that exceeded the parser's limit.
+ */
+ kExceededRecursionDepthLimit = -1031,
+
+ // The following codes are internal-only and should not be used by users.
+ // Additionally, these codes should never be returned to the user; doing so
+ // is considered a bug.
+ /**
+ \internal Parsing should switch from reading to skipping elements.
+ */
+ kSwitchToSkip = INT32_MIN,
+ };
+
+ /**
+ Status codes <= 0 are reserved by the parsing library. User error codes
+ should be positive (> 0). Users are encouraged to use codes from the `Code`
+ enum, but may use a positive error code if some application-specific error is
+ encountered.
+ */
+ std::int32_t code;
+
+ Status() = default;
+ Status(const Status&) = default;
+ Status(Status&&) = default;
+ Status& operator=(const Status&) = default;
+ Status& operator=(Status&&) = default;
+
+ /**
+ Creates a new `Status` object with the given status code.
+
+ \param code The status code which will be used to set the `code` member.
+ */
+ constexpr explicit Status(Code code) : code(code) {}
+
+ /**
+ Creates a new `Status` object with the given status code.
+
+ \param code The status code which will be used to set the `code` member.
+ */
+ constexpr explicit Status(std::int32_t code) : code(code) {}
+
+ /**
+ Returns true if the status code is either `kOkCompleted` or `kOkPartial`.
+ Provided for convenience.
+ */
+ constexpr bool ok() const {
+ return code == kOkCompleted || code == kOkPartial;
+ }
+
+ /**
+ Returns true if the status code is `kOkCompleted`. Provided for convenience.
+ */
+ constexpr bool completed_ok() const { return code == kOkCompleted; }
+
+ /**
+ Returns true if the status is considered a parsing error. Parsing errors
+ represent unrecoverable errors due to malformed data. Only status codes in
+ the range -2048 to -1025 (inclusive) are considered parsing errors.
+ */
+ constexpr bool is_parsing_error() const {
+ return -2048 <= code && code <= -1025;
+ }
+};
+
+/**
+ @}
+ */
+
+} // namespace webm
+
+#endif // INCLUDE_WEBM_STATUS_H_
diff --git a/webm_parser/include/webm/webm_parser.h b/webm_parser/include/webm/webm_parser.h
new file mode 100644
index 0000000..6b794d5
--- /dev/null
+++ b/webm_parser/include/webm/webm_parser.h
@@ -0,0 +1,133 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef INCLUDE_WEBM_WEBM_PARSER_H_
+#define INCLUDE_WEBM_WEBM_PARSER_H_
+
+#include <memory>
+
+#include "./callback.h"
+#include "./reader.h"
+#include "./status.h"
+
+/**
+ \file
+ The main parser class for parsing WebM files.
+ */
+
+namespace webm {
+
+/**
+ \defgroup PUBLIC_API Public API
+ Public types and header files intended for use by users.
+ */
+
+/**
+ \addtogroup PUBLIC_API
+ @{
+ */
+
+/**
+ Incrementally parses a WebM document encoded in a byte stream.
+
+ It is expected that the parsing will begin at the start of the WebM
+ file/document. Otherwise, the `DidSeek()` method should be used to inform the
+ parser that reading may not necessarily begin at the beginning of the document
+ and the parser should be prepared to handle data at an arbitrary point in the
+ document.
+
+ WebM files are mostly a subset of Matroska, with a few small modifications.
+ Matroska is a format built on top of EBML.
+ */
+// Spec references:
+// http://www.webmproject.org/docs/container/
+// https://matroska.org/technical/specs/index.html
+// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown
+class WebmParser {
+ public:
+ /**
+ Constructs the object and prepares it for parsing.
+ */
+ WebmParser();
+
+ /*
+ Cleans up the parser. No further cleanup is required from the user.
+ */
+ ~WebmParser();
+
+ // Non-copyable and non-movable.
+ WebmParser(const WebmParser&) = delete;
+ WebmParser& operator=(const WebmParser&) = delete;
+
+ /**
+ Resets the parser after a seek was performed on the reader, which prepares
+ the parser for starting at an arbitrary point in the stream.
+
+ The seek must be to the start of an element (that is, it can't be any random
+ byte) or parsing will fail.
+ */
+ void DidSeek();
+
+ /**
+ Feeds data into the parser from the provided reader.
+
+ If a parsing error code has been returned (which indicates a malformed
+ document), calling Feed() again will just result in the same error; parsing
+ has already failed at that point and further progress can't be made.
+
+ \param callback The callback which receives parsing events. No internal
+ references are maintained to `callback`, so it may be modified or freed after
+ this method returns.
+ \param reader The reader the parser will use to request data. No internal
+ references are maintained to `reader`, so it may be modified or freed after
+ this method returns.
+ \return `Status::kOkCompleted` when parsing completes successfully.
+ `Status::kOkPartial` or another non-parsing error code (see status.h for
+ error codes classified as parsing errors) will be returned if parsing has
+ only partially completed, and Feed() should be called again to resume
+ parsing.
+ */
+ Status Feed(Callback* callback, Reader* reader);
+
+ /**
+ Swaps this parser and the provided parser.
+
+ \param other The parser to swap values/states with. Must not be null.
+ */
+ void Swap(WebmParser* other);
+
+ private:
+ // This header can only rely on sources in the public API.
+ class DocumentParser;
+
+ // The internal implementation of the parser.
+ std::unique_ptr<DocumentParser> parser_;
+
+ // The status of the parser, used to prevent further progress if an
+ // unrecoverable parsing error has already been encountered.
+ Status parsing_status_ = Status(Status::kOkPartial);
+};
+
+/**
+ Swaps the two parsers.
+
+ This is provided so code can use argument dependent lookup in an idiomatic way
+ when swapping (especially since `std::swap` won't work since the parser is
+ non-movable).
+
+ \param left The first parser to swap.
+ \param right The second parser to swap.
+ */
+void swap(WebmParser& left, WebmParser& right);
+
+/**
+ @}
+ */
+
+} // namespace webm
+
+#endif // INCLUDE_WEBM_WEBM_PARSER_H_
diff --git a/webm_parser/src/ancestory.cc b/webm_parser/src/ancestory.cc
new file mode 100644
index 0000000..9860a28
--- /dev/null
+++ b/webm_parser/src/ancestory.cc
@@ -0,0 +1,334 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/ancestory.h"
+
+#include "webm/id.h"
+
+namespace webm {
+
+bool Ancestory::ById(Id id, Ancestory* ancestory) {
+ // These lists of IDs were generated and must match the switch statement and
+ // have static storage duration. They were generated as follows:
+ //
+ // 1. List all the master elements:
+ // kEbml
+ // kSegment
+ // kSeekHead
+ // kSeek
+ // kInfo
+ // kCluster
+ // kBlockGroup
+ // kBlockAdditions
+ // kBlockMore
+ // kSlices
+ // kTimeSlice
+ // etc.
+ //
+ // 2. Now prefix each entry with its full ancestory:
+ // kEbml
+ // kSegment
+ // kSegment, kSeekHead
+ // kSegment, kSeekHead, kSeek
+ // kSegment, kInfo
+ // kSegment, kCluster
+ // kSegment, kCluster, kBlockGroup
+ // kSegment, kCluster, kBlockGroup, kBlockAdditions
+ // kSegment, kCluster, kBlockGroup, kBlockAdditions, kBlockMore
+ // kSegment, kCluster, kBlockGroup, kSlices
+ // kSegment, kCluster, kBlockGroup, kSlices, kTimeSlice
+ // etc.
+ //
+ // 3. Now remove entries that are just subsets of others:
+ // kEbml
+ // kSegment, kSeekHead, kSeek
+ // kSegment, kInfo
+ // kSegment, kCluster, kBlockGroup, kBlockAdditions, kBlockMore
+ // kSegment, kCluster, kBlockGroup, kSlices, kTimeSlice
+ // etc.
+ static constexpr Id kEbmlAncestory[] = {
+ Id::kEbml,
+ };
+ static constexpr Id kSeekAncestory[] = {
+ Id::kSegment,
+ Id::kSeekHead,
+ Id::kSeek,
+ };
+ static constexpr Id kInfoAncestory[] = {
+ Id::kSegment,
+ Id::kInfo,
+ };
+ static constexpr Id kBlockMoreAncestory[] = {
+ Id::kSegment, Id::kCluster, Id::kBlockGroup,
+ Id::kBlockAdditions, Id::kBlockMore,
+ };
+ static constexpr Id kTimeSliceAncestory[] = {
+ Id::kSegment, Id::kCluster, Id::kBlockGroup, Id::kSlices, Id::kTimeSlice,
+ };
+ static constexpr Id kVideoAncestory[] = {
+ Id::kSegment,
+ Id::kTracks,
+ Id::kTrackEntry,
+ Id::kVideo,
+ };
+ static constexpr Id kAudioAncestory[] = {
+ Id::kSegment,
+ Id::kTracks,
+ Id::kTrackEntry,
+ Id::kAudio,
+ };
+ static constexpr Id kContentEncAesSettingsAncestory[] = {
+ Id::kSegment,
+ Id::kTracks,
+ Id::kTrackEntry,
+ Id::kContentEncodings,
+ Id::kContentEncoding,
+ Id::kContentEncryption,
+ Id::kContentEncAesSettings,
+ };
+ static constexpr Id kCueTrackPositionsAncestory[] = {
+ Id::kSegment,
+ Id::kCues,
+ Id::kCuePoint,
+ Id::kCueTrackPositions,
+ };
+ static constexpr Id kChapterDisplayAncestory[] = {
+ Id::kSegment, Id::kChapters, Id::kEditionEntry,
+ Id::kChapterAtom, Id::kChapterDisplay,
+ };
+ static constexpr Id kTargetsAncestory[] = {
+ Id::kSegment,
+ Id::kTags,
+ Id::kTag,
+ Id::kTargets,
+ };
+ static constexpr Id kSimpleTagAncestory[] = {
+ Id::kSegment,
+ Id::kTags,
+ Id::kTag,
+ Id::kSimpleTag,
+ };
+
+ switch (id) {
+ case Id::kEbmlVersion:
+ case Id::kEbmlReadVersion:
+ case Id::kEbmlMaxIdLength:
+ case Id::kEbmlMaxSizeLength:
+ case Id::kDocType:
+ case Id::kDocTypeVersion:
+ case Id::kDocTypeReadVersion:
+ *ancestory = Ancestory(kEbmlAncestory, 1);
+ return true;
+
+ case Id::kSeekHead:
+ case Id::kInfo:
+ case Id::kCluster:
+ case Id::kTracks:
+ case Id::kCues:
+ case Id::kChapters:
+ case Id::kTags:
+ *ancestory = Ancestory(kSeekAncestory, 1);
+ return true;
+
+ case Id::kSeek:
+ *ancestory = Ancestory(kSeekAncestory, 2);
+ return true;
+
+ case Id::kSeekId:
+ case Id::kSeekPosition:
+ *ancestory = Ancestory(kSeekAncestory, 3);
+ return true;
+
+ case Id::kTimecodeScale:
+ case Id::kDuration:
+ case Id::kDateUtc:
+ case Id::kTitle:
+ case Id::kMuxingApp:
+ case Id::kWritingApp:
+ *ancestory = Ancestory(kInfoAncestory, 2);
+ return true;
+
+ case Id::kTimecode:
+ case Id::kPrevSize:
+ case Id::kSimpleBlock:
+ case Id::kBlockGroup:
+ *ancestory = Ancestory(kBlockMoreAncestory, 2);
+ return true;
+
+ case Id::kBlock:
+ case Id::kBlockVirtual:
+ case Id::kBlockAdditions:
+ case Id::kBlockDuration:
+ case Id::kReferenceBlock:
+ case Id::kDiscardPadding:
+ case Id::kSlices:
+ *ancestory = Ancestory(kBlockMoreAncestory, 3);
+ return true;
+
+ case Id::kBlockMore:
+ *ancestory = Ancestory(kBlockMoreAncestory, 4);
+ return true;
+
+ case Id::kBlockAddId:
+ case Id::kBlockAdditional:
+ *ancestory = Ancestory(kBlockMoreAncestory, 5);
+ return true;
+
+ case Id::kTimeSlice:
+ *ancestory = Ancestory(kTimeSliceAncestory, 4);
+ return true;
+
+ case Id::kLaceNumber:
+ *ancestory = Ancestory(kTimeSliceAncestory, 5);
+ return true;
+
+ case Id::kTrackEntry:
+ *ancestory = Ancestory(kVideoAncestory, 2);
+ return true;
+
+ case Id::kTrackNumber:
+ case Id::kTrackUid:
+ case Id::kTrackType:
+ case Id::kFlagEnabled:
+ case Id::kFlagDefault:
+ case Id::kFlagForced:
+ case Id::kFlagLacing:
+ case Id::kDefaultDuration:
+ case Id::kName:
+ case Id::kLanguage:
+ case Id::kCodecId:
+ case Id::kCodecPrivate:
+ case Id::kCodecName:
+ case Id::kCodecDelay:
+ case Id::kSeekPreRoll:
+ case Id::kVideo:
+ case Id::kAudio:
+ case Id::kContentEncodings:
+ *ancestory = Ancestory(kVideoAncestory, 3);
+ return true;
+
+ case Id::kFlagInterlaced:
+ case Id::kStereoMode:
+ case Id::kAlphaMode:
+ case Id::kPixelWidth:
+ case Id::kPixelHeight:
+ case Id::kPixelCropBottom:
+ case Id::kPixelCropTop:
+ case Id::kPixelCropLeft:
+ case Id::kPixelCropRight:
+ case Id::kDisplayWidth:
+ case Id::kDisplayHeight:
+ case Id::kDisplayUnit:
+ case Id::kAspectRatioType:
+ case Id::kFrameRate:
+ *ancestory = Ancestory(kVideoAncestory, 4);
+ return true;
+
+ case Id::kSamplingFrequency:
+ case Id::kOutputSamplingFrequency:
+ case Id::kChannels:
+ case Id::kBitDepth:
+ *ancestory = Ancestory(kAudioAncestory, 4);
+ return true;
+
+ case Id::kContentEncoding:
+ *ancestory = Ancestory(kContentEncAesSettingsAncestory, 4);
+ return true;
+
+ case Id::kContentEncodingOrder:
+ case Id::kContentEncodingScope:
+ case Id::kContentEncodingType:
+ case Id::kContentEncryption:
+ *ancestory = Ancestory(kContentEncAesSettingsAncestory, 5);
+ return true;
+
+ case Id::kContentEncAlgo:
+ case Id::kContentEncKeyId:
+ case Id::kContentEncAesSettings:
+ *ancestory = Ancestory(kContentEncAesSettingsAncestory, 6);
+ return true;
+
+ case Id::kAesSettingsCipherMode:
+ *ancestory = Ancestory(kContentEncAesSettingsAncestory, 7);
+ return true;
+
+ case Id::kCuePoint:
+ *ancestory = Ancestory(kCueTrackPositionsAncestory, 2);
+ return true;
+
+ case Id::kCueTime:
+ case Id::kCueTrackPositions:
+ *ancestory = Ancestory(kCueTrackPositionsAncestory, 3);
+ return true;
+
+ case Id::kCueTrack:
+ case Id::kCueClusterPosition:
+ case Id::kCueRelativePosition:
+ case Id::kCueDuration:
+ case Id::kCueBlockNumber:
+ *ancestory = Ancestory(kCueTrackPositionsAncestory, 4);
+ return true;
+
+ case Id::kEditionEntry:
+ *ancestory = Ancestory(kChapterDisplayAncestory, 2);
+ return true;
+
+ case Id::kChapterAtom:
+ *ancestory = Ancestory(kChapterDisplayAncestory, 3);
+ return true;
+
+ case Id::kChapterUid:
+ case Id::kChapterStringUid:
+ case Id::kChapterTimeStart:
+ case Id::kChapterTimeEnd:
+ case Id::kChapterDisplay:
+ *ancestory = Ancestory(kChapterDisplayAncestory, 4);
+ return true;
+
+ case Id::kChapString:
+ case Id::kChapLanguage:
+ case Id::kChapCountry:
+ *ancestory = Ancestory(kChapterDisplayAncestory, 5);
+ return true;
+
+ case Id::kTag:
+ *ancestory = Ancestory(kTargetsAncestory, 2);
+ return true;
+
+ case Id::kTargets:
+ case Id::kSimpleTag:
+ *ancestory = Ancestory(kTargetsAncestory, 3);
+ return true;
+
+ case Id::kTargetTypeValue:
+ case Id::kTargetType:
+ case Id::kTagTrackUid:
+ *ancestory = Ancestory(kTargetsAncestory, 4);
+ return true;
+
+ case Id::kTagName:
+ case Id::kTagLanguage:
+ case Id::kTagDefault:
+ case Id::kTagString:
+ case Id::kTagBinary:
+ *ancestory = Ancestory(kSimpleTagAncestory, 4);
+ return true;
+
+ case Id::kEbml:
+ case Id::kSegment:
+ *ancestory = {};
+ return true;
+
+ default:
+ // This is an unknown element or a global element (i.e. Void); its
+ // ancestory cannot be deduced.
+ *ancestory = {};
+ return false;
+ }
+}
+
+} // namespace webm
diff --git a/webm_parser/src/ancestory.h b/webm_parser/src/ancestory.h
new file mode 100644
index 0000000..793b7e3
--- /dev/null
+++ b/webm_parser/src/ancestory.h
@@ -0,0 +1,83 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_ANCESTORY_H_
+#define SRC_ANCESTORY_H_
+
+#include <cassert>
+#include <cstddef>
+#include <iterator>
+
+#include "webm/id.h"
+
+namespace webm {
+
+// Represents an element's ancestory in descending order. For example, the
+// Id::kTrackNumber element has an ancestory of {Id::kSegment, Id::kTracks,
+// Id::kTrackEntry}.
+class Ancestory {
+ public:
+ // Constructs an empty ancestory.
+ Ancestory() = default;
+
+ Ancestory(const Ancestory&) = default;
+ Ancestory(Ancestory&&) = default;
+ Ancestory& operator=(const Ancestory&) = default;
+ Ancestory& operator=(Ancestory&&) = default;
+
+ // Returns the ancestory with the top-level parent removed. For example, if
+ // the current ancestory is {Id::kSegment, Id::kTracks, Id::kTrackEntry}, next
+ // will return {Id::kTracks, Id::kTrackEntry}. This must not be called if the
+ // ancestory is empty.
+ Ancestory next() const {
+ assert(begin_ < end_);
+ Ancestory copy = *this;
+ ++copy.begin_;
+ return copy;
+ }
+
+ // Gets the Id of the top-level parent. For example, if the current ancestory
+ // is {Id::kSegment, Id::kTracks, Id::kTrackEntry}, id will return
+ // Id::kSegment. This must not be called if the ancestory is empty.
+ Id id() const {
+ assert(begin_ < end_);
+ return *begin_;
+ }
+
+ // Returns true if the ancestory is empty.
+ bool empty() const { return begin_ == end_; }
+
+ // Looks up the ancestory of the given id. Returns true and sets ancestory if
+ // the element's ancestory could be deduced. Global elements (i.e. Id::kVoid)
+ // and unknown elements can't have their ancestory deduced.
+ static bool ById(Id id, Ancestory* ancestory);
+
+ private:
+ // Constructs an Ancestory using the first count elements of ancestory.
+ // ancestory must have static storage duration.
+ template <std::size_t N>
+ Ancestory(const Id (&ancestory)[N], std::size_t count)
+ : begin_(ancestory), end_(ancestory + count) {
+ assert(count <= N);
+ }
+
+ // The following invariants apply to begin_ and end_:
+ // begin_ <= end_
+ // (begin_ == end_) || (std::begin(kIds) <= begin_ && end_ <= std::end(kIds))
+
+ // The beginning (inclusive) of the sequence of IDs in kIds that defines the
+ // ancestory.
+ const Id* begin_ = nullptr;
+
+ // The ending (exclusive) of the sequence of IDs in kIds that defines the
+ // ancestory.
+ const Id* end_ = nullptr;
+};
+
+} // namespace webm
+
+#endif // SRC_ANCESTORY_H_
diff --git a/webm_parser/src/audio_parser.h b/webm_parser/src/audio_parser.h
new file mode 100644
index 0000000..eabab1f
--- /dev/null
+++ b/webm_parser/src/audio_parser.h
@@ -0,0 +1,84 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_AUDIO_PARSER_H_
+#define SRC_AUDIO_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/float_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/callback.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Audio
+// http://www.webmproject.org/docs/container/#Audio
+class AudioParser : public MasterValueParser<Audio> {
+ public:
+ AudioParser()
+ : MasterValueParser<Audio>(
+ MakeChild<FloatParser>(Id::kSamplingFrequency,
+ &Audio::sampling_frequency),
+ MakeChild<FloatParser>(Id::kOutputSamplingFrequency,
+ &Audio::output_frequency)
+ .NotifyOnParseComplete(),
+ MakeChild<UnsignedIntParser>(Id::kChannels, &Audio::channels),
+ MakeChild<UnsignedIntParser>(Id::kBitDepth, &Audio::bit_depth)) {}
+
+ Status Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) override {
+ output_frequency_has_value_ = false;
+
+ return MasterValueParser::Init(metadata, max_size);
+ }
+
+ void InitAfterSeek(const Ancestory& child_ancestory,
+ const ElementMetadata& child_metadata) override {
+ output_frequency_has_value_ = false;
+
+ return MasterValueParser::InitAfterSeek(child_ancestory, child_metadata);
+ }
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override {
+ const Status status =
+ MasterValueParser::Feed(callback, reader, num_bytes_read);
+ if (status.completed_ok()) {
+ FixMissingOutputFrequency();
+ }
+ return status;
+ }
+
+ protected:
+ void OnChildParsed(const ElementMetadata& metadata) override {
+ assert(metadata.id == Id::kOutputSamplingFrequency);
+
+ output_frequency_has_value_ = metadata.size > 0;
+ }
+
+ private:
+ bool output_frequency_has_value_;
+
+ void FixMissingOutputFrequency() {
+ if (!output_frequency_has_value_) {
+ *mutable_value()->output_frequency.mutable_value() =
+ value().sampling_frequency.value();
+ }
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_AUDIO_PARSER_H_
diff --git a/webm_parser/src/bit_utils.cc b/webm_parser/src/bit_utils.cc
new file mode 100644
index 0000000..3956be0
--- /dev/null
+++ b/webm_parser/src/bit_utils.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/bit_utils.h"
+
+#include <cstdint>
+
+namespace webm {
+
+std::uint8_t CountLeadingZeros(std::uint8_t value) {
+ // Special case for 0 since we can't shift by sizeof(T) * 8 bytes.
+ if (value == 0)
+ return 8;
+
+ std::uint8_t count = 0;
+ while (!(value & (0x80 >> count))) {
+ ++count;
+ }
+ return count;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/bit_utils.h b/webm_parser/src/bit_utils.h
new file mode 100644
index 0000000..b5e90b6
--- /dev/null
+++ b/webm_parser/src/bit_utils.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_BIT_UTILS_H_
+#define SRC_BIT_UTILS_H_
+
+#include <cstdint>
+
+namespace webm {
+
+// Counts the number of leading zero bits.
+// For example:
+// assert(8 == CountLeadingZeros(0x00));
+// assert(4 == CountLeadingZeros(0x0f));
+// assert(0 == CountLeadingZeros(0xf0));
+std::uint8_t CountLeadingZeros(std::uint8_t value);
+
+} // namespace webm
+
+#endif // SRC_BIT_UTILS_H_
diff --git a/webm_parser/src/block_additions_parser.h b/webm_parser/src/block_additions_parser.h
new file mode 100644
index 0000000..be15e26
--- /dev/null
+++ b/webm_parser/src/block_additions_parser.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_BLOCK_ADDITIONS_PARSER_H_
+#define SRC_BLOCK_ADDITIONS_PARSER_H_
+
+#include "src/block_more_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#BlockAdditions
+// http://www.webmproject.org/docs/container/#BlockAdditions
+class BlockAdditionsParser : public MasterValueParser<BlockAdditions> {
+ public:
+ BlockAdditionsParser()
+ : MasterValueParser<BlockAdditions>(MakeChild<BlockMoreParser>(
+ Id::kBlockMore, &BlockAdditions::block_mores)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_BLOCK_ADDITIONS_PARSER_H_
diff --git a/webm_parser/src/block_group_parser.h b/webm_parser/src/block_group_parser.h
new file mode 100644
index 0000000..feca6a4
--- /dev/null
+++ b/webm_parser/src/block_group_parser.h
@@ -0,0 +1,71 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_BLOCK_GROUP_PARSER_H_
+#define SRC_BLOCK_GROUP_PARSER_H_
+
+#include "src/block_additions_parser.h"
+#include "src/block_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "src/slices_parser.h"
+#include "src/virtual_block_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#BlockGroup
+// http://www.webmproject.org/docs/container/#BlockGroup
+class BlockGroupParser : public MasterValueParser<BlockGroup> {
+ public:
+ BlockGroupParser()
+ : MasterValueParser<BlockGroup>(
+ MakeChild<BlockParser>(Id::kBlock, &BlockGroup::block),
+ MakeChild<VirtualBlockParser>(Id::kBlockVirtual,
+ &BlockGroup::virtual_block),
+ MakeChild<BlockAdditionsParser>(Id::kBlockAdditions,
+ &BlockGroup::additions),
+ MakeChild<UnsignedIntParser>(Id::kBlockDuration,
+ &BlockGroup::duration),
+ MakeChild<SignedIntParser>(Id::kReferenceBlock,
+ &BlockGroup::references),
+ MakeChild<SignedIntParser>(Id::kDiscardPadding,
+ &BlockGroup::discard_padding),
+ MakeChild<SlicesParser>(Id::kSlices, &BlockGroup::slices)) {}
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override {
+ *num_bytes_read = 0;
+
+ if (!parse_started_event_completed()) {
+ Action action = Action::kRead;
+ Status status = OnParseStarted(callback, &action);
+ if (!status.completed_ok()) {
+ return status;
+ }
+
+ set_parse_started_event_completed_with_action(action);
+ }
+
+ return MasterValueParser::Feed(callback, reader, num_bytes_read);
+ }
+
+ protected:
+ Status OnParseStarted(Callback* callback, Action* action) override {
+ return callback->OnBlockGroupBegin(metadata(Id::kBlockGroup), action);
+ }
+
+ Status OnParseCompleted(Callback* callback) override {
+ return callback->OnBlockGroupEnd(metadata(Id::kBlockGroup), value());
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_BLOCK_GROUP_PARSER_H_
diff --git a/webm_parser/src/block_header_parser.cc b/webm_parser/src/block_header_parser.cc
new file mode 100644
index 0000000..59921ae
--- /dev/null
+++ b/webm_parser/src/block_header_parser.cc
@@ -0,0 +1,76 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/block_header_parser.h"
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/parser_utils.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#block_structure
+// http://matroska.org/technical/specs/index.html#simpleblock_structure
+// http://matroska.org/technical/specs/index.html#block_virtual
+Status BlockHeaderParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ *num_bytes_read = 0;
+
+ Status status;
+ std::uint64_t local_num_bytes_read;
+
+ while (true) {
+ switch (state_) {
+ case State::kReadingTrackNumber: {
+ status = uint_parser_.Feed(callback, reader, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ if (!status.completed_ok()) {
+ return status;
+ }
+ value_.track_number = uint_parser_.value();
+ state_ = State::kReadingTimecode;
+ continue;
+ }
+
+ case State::kReadingTimecode: {
+ status =
+ AccumulateIntegerBytes(timecode_bytes_remaining_, reader,
+ &value_.timecode, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ timecode_bytes_remaining_ -= static_cast<int>(local_num_bytes_read);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ state_ = State::kReadingFlags;
+ continue;
+ }
+
+ case State::kReadingFlags: {
+ assert(timecode_bytes_remaining_ == 0);
+ status = ReadByte(reader, &value_.flags);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ ++*num_bytes_read;
+ state_ = State::kDone;
+ continue;
+ }
+
+ case State::kDone: {
+ return Status(Status::kOkCompleted);
+ }
+ }
+ }
+}
+
+} // namespace webm
diff --git a/webm_parser/src/block_header_parser.h b/webm_parser/src/block_header_parser.h
new file mode 100644
index 0000000..9ce35e1
--- /dev/null
+++ b/webm_parser/src/block_header_parser.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_BLOCK_HEADER_PARSER_H_
+#define SRC_BLOCK_HEADER_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/parser.h"
+#include "src/var_int_parser.h"
+#include "webm/callback.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+struct BlockHeader {
+ std::uint64_t track_number;
+ std::int16_t timecode;
+ std::uint8_t flags;
+
+ bool operator==(const BlockHeader& other) const {
+ return track_number == other.track_number && timecode == other.timecode &&
+ flags == other.flags;
+ }
+};
+
+class BlockHeaderParser : public Parser {
+ public:
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ // Gets the parsed block header information. This must not be called until the
+ // parse has been successfully completed.
+ const BlockHeader& value() const {
+ assert(state_ == State::kDone);
+ return value_;
+ }
+
+ private:
+ BlockHeader value_;
+
+ VarIntParser uint_parser_;
+
+ int timecode_bytes_remaining_ = 2;
+
+ enum class State {
+ /* clang-format off */
+ // State Transitions to state When
+ kReadingTrackNumber, // kReadingTimecode track parsed
+ kReadingTimecode, // kReadingFlags timecode parsed
+ kReadingFlags, // kDone flags parsed
+ kDone, // No transitions from here (must call Init)
+ /* clang-format on */
+ } state_ = State::kReadingTrackNumber;
+};
+
+} // namespace webm
+
+#endif // SRC_BLOCK_HEADER_PARSER_H_
diff --git a/webm_parser/src/block_more_parser.h b/webm_parser/src/block_more_parser.h
new file mode 100644
index 0000000..1ab850d
--- /dev/null
+++ b/webm_parser/src/block_more_parser.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_BLOCK_MORE_PARSER_H_
+#define SRC_BLOCK_MORE_PARSER_H_
+
+#include "src/byte_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#BlockMore
+// http://www.webmproject.org/docs/container/#BlockMore
+class BlockMoreParser : public MasterValueParser<BlockMore> {
+ public:
+ BlockMoreParser()
+ : MasterValueParser<BlockMore>(
+ MakeChild<UnsignedIntParser>(Id::kBlockAddId, &BlockMore::id),
+ MakeChild<BinaryParser>(Id::kBlockAdditional, &BlockMore::data)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_BLOCK_MORE_PARSER_H_
diff --git a/webm_parser/src/block_parser.cc b/webm_parser/src/block_parser.cc
new file mode 100644
index 0000000..b851f51
--- /dev/null
+++ b/webm_parser/src/block_parser.cc
@@ -0,0 +1,285 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/block_parser.h"
+
+#include <cassert>
+#include <cstdint>
+#include <numeric>
+#include <type_traits>
+#include <vector>
+
+#include "src/parser_utils.h"
+#include "webm/element.h"
+
+namespace webm {
+
+namespace {
+
+// The ParseBasicBlockFlags functions parse extra flag bits into the block,
+// depending on the type of block that is being parsed.
+void ParseBasicBlockFlags(std::uint8_t /* flags */, Block* /* block */) {
+ // Block has no extra flags that aren't already handled.
+}
+
+void ParseBasicBlockFlags(std::uint8_t flags, SimpleBlock* block) {
+ block->is_key_frame = (0x80 & flags) != 0;
+ block->is_discardable = (0x01 & flags) != 0;
+}
+
+// The BasicBlockBegin functions call the Callback event handler and get the
+// correct action for the parser, depending on the type of block that is being
+// parsed.
+Status BasicBlockBegin(const ElementMetadata& metadata, const Block& block,
+ Callback* callback, Action* action) {
+ return callback->OnBlockBegin(metadata, block, action);
+}
+
+Status BasicBlockBegin(const ElementMetadata& metadata,
+ const SimpleBlock& block, Callback* callback,
+ Action* action) {
+ return callback->OnSimpleBlockBegin(metadata, block, action);
+}
+
+// The BasicBlockEnd functions call the Callback event handler depending on the
+// type of block that is being parsed.
+Status BasicBlockEnd(const ElementMetadata& metadata, const Block& block,
+ Callback* callback) {
+ return callback->OnBlockEnd(metadata, block);
+}
+
+Status BasicBlockEnd(const ElementMetadata& metadata, const SimpleBlock& block,
+ Callback* callback) {
+ return callback->OnSimpleBlockEnd(metadata, block);
+}
+
+} // namespace
+
+template <typename T>
+Status BasicBlockParser<T>::Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ if (metadata.size == kUnknownElementSize || metadata.size < 5) {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ *this = {};
+ frame_metadata_.parent_element = metadata;
+
+ return Status(Status::kOkCompleted);
+}
+
+template <typename T>
+Status BasicBlockParser<T>::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ *num_bytes_read = 0;
+
+ Status status;
+ std::uint64_t local_num_bytes_read;
+
+ while (true) {
+ switch (state_) {
+ case State::kReadingHeader: {
+ status = header_parser_.Feed(callback, reader, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ header_bytes_read_ += local_num_bytes_read;
+ if (!status.completed_ok()) {
+ return status;
+ }
+ value_.track_number = header_parser_.value().track_number;
+ value_.timecode = header_parser_.value().timecode;
+
+ std::uint8_t flags = header_parser_.value().flags;
+ value_.is_visible = (0x08 & flags) == 0;
+ value_.lacing = static_cast<Lacing>(flags & 0x06);
+ ParseBasicBlockFlags(flags, &value_);
+
+ if (value_.lacing == Lacing::kNone) {
+ value_.num_frames = 1;
+ state_ = State::kGettingAction;
+ } else {
+ state_ = State::kReadingLaceCount;
+ }
+ continue;
+ }
+
+ case State::kReadingLaceCount: {
+ assert(lace_sizes_.empty());
+ std::uint8_t lace_count;
+ status = ReadByte(reader, &lace_count);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ ++*num_bytes_read;
+ ++header_bytes_read_;
+ // Lace count is stored as (count - 1).
+ value_.num_frames = lace_count + 1;
+ state_ = State::kGettingAction;
+ continue;
+ }
+
+ case State::kGettingAction: {
+ Action action = Action::kRead;
+ status = BasicBlockBegin(frame_metadata_.parent_element, value_,
+ callback, &action);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ if (action == Action::kSkip) {
+ state_ = State::kSkipping;
+ } else if (value_.lacing == Lacing::kNone || value_.num_frames == 1) {
+ state_ = State::kValidatingSize;
+ } else if (value_.lacing == Lacing::kXiph) {
+ state_ = State::kReadingXiphLaceSizes;
+ } else if (value_.lacing == Lacing::kEbml) {
+ state_ = State::kReadingFirstEbmlLaceSize;
+ } else {
+ state_ = State::kCalculatingFixedLaceSizes;
+ }
+ continue;
+ }
+
+ case State::kReadingXiphLaceSizes:
+ assert(value_.num_frames > 0);
+ while (static_cast<int>(lace_sizes_.size()) < value_.num_frames - 1) {
+ std::uint8_t byte;
+ do {
+ status = ReadByte(reader, &byte);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ ++*num_bytes_read;
+ ++header_bytes_read_;
+ xiph_lace_size_ += byte;
+ } while (byte == 255);
+
+ lace_sizes_.push_back(xiph_lace_size_);
+ xiph_lace_size_ = 0;
+ }
+ state_ = State::kValidatingSize;
+ continue;
+
+ case State::kReadingFirstEbmlLaceSize:
+ assert(value_.num_frames > 0);
+ assert(lace_sizes_.empty());
+ status = uint_parser_.Feed(callback, reader, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ header_bytes_read_ += local_num_bytes_read;
+ if (!status.completed_ok()) {
+ return status;
+ }
+ lace_sizes_.push_back(uint_parser_.value());
+ uint_parser_ = {};
+ state_ = State::kReadingEbmlLaceSizes;
+ continue;
+
+ case State::kReadingEbmlLaceSizes:
+ assert(value_.num_frames > 0);
+ assert(!lace_sizes_.empty());
+ while (static_cast<int>(lace_sizes_.size()) < value_.num_frames - 1) {
+ status = uint_parser_.Feed(callback, reader, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ header_bytes_read_ += local_num_bytes_read;
+ if (!status.completed_ok()) {
+ return status;
+ }
+ constexpr std::uint64_t one = 1; // Prettier than a static_cast.
+ std::uint64_t offset =
+ (one << (uint_parser_.encoded_length() * 7 - 1)) - 1;
+ lace_sizes_.push_back(lace_sizes_.back() + uint_parser_.value() -
+ offset);
+ uint_parser_ = {};
+ }
+ state_ = State::kValidatingSize;
+ continue;
+
+ case State::kCalculatingFixedLaceSizes: {
+ assert(value_.num_frames > 0);
+ assert(lace_sizes_.empty());
+ if (header_bytes_read_ >= frame_metadata_.parent_element.size) {
+ return Status(Status::kInvalidElementValue);
+ }
+ std::uint64_t laced_data_size =
+ frame_metadata_.parent_element.size - header_bytes_read_;
+ std::uint64_t frame_size = laced_data_size / value_.num_frames;
+ if (laced_data_size % value_.num_frames != 0) {
+ return Status(Status::kInvalidElementValue);
+ }
+ lace_sizes_.resize(value_.num_frames, frame_size);
+ frame_metadata_.position =
+ frame_metadata_.parent_element.position + header_bytes_read_;
+ frame_metadata_.size = frame_size;
+ state_ = State::kReadingFrames;
+ continue;
+ }
+
+ case State::kValidatingSize: {
+ std::uint64_t sum = std::accumulate(
+ lace_sizes_.begin(), lace_sizes_.end(), header_bytes_read_);
+ if (sum >= frame_metadata_.parent_element.size) {
+ return Status(Status::kInvalidElementValue);
+ }
+ lace_sizes_.push_back(frame_metadata_.parent_element.size - sum);
+ frame_metadata_.position = frame_metadata_.parent_element.position +
+ frame_metadata_.parent_element.header_size +
+ header_bytes_read_;
+ frame_metadata_.size = lace_sizes_[0];
+ state_ = State::kReadingFrames;
+ continue;
+ }
+
+ case State::kSkipping:
+ do {
+ // Skip the remaining part of the header and all of the frames.
+ status = reader->Skip(
+ frame_metadata_.parent_element.size - header_bytes_read_,
+ &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ header_bytes_read_ += local_num_bytes_read;
+ } while (status.code == Status::kOkPartial);
+ return status;
+
+ case State::kReadingFrames:
+ assert(value_.num_frames > 0);
+ assert(static_cast<int>(lace_sizes_.size()) == value_.num_frames);
+ for (; current_lace_ < lace_sizes_.size(); ++current_lace_) {
+ const std::uint64_t original = lace_sizes_[current_lace_];
+ status = callback->OnFrame(frame_metadata_, reader,
+ &lace_sizes_[current_lace_]);
+ *num_bytes_read += original - lace_sizes_[current_lace_];
+ if (!status.completed_ok()) {
+ return status;
+ }
+ assert(lace_sizes_[current_lace_] == 0);
+ if (current_lace_ + 1 < lace_sizes_.size()) {
+ frame_metadata_.position += frame_metadata_.size;
+ frame_metadata_.size = lace_sizes_[current_lace_ + 1];
+ }
+ }
+ state_ = State::kDone;
+ continue;
+
+ case State::kDone:
+ return BasicBlockEnd(frame_metadata_.parent_element, value_, callback);
+ }
+ }
+}
+
+template <typename T>
+bool BasicBlockParser<T>::WasSkipped() const {
+ return state_ == State::kSkipping;
+}
+
+template class BasicBlockParser<Block>;
+template class BasicBlockParser<SimpleBlock>;
+
+} // namespace webm
diff --git a/webm_parser/src/block_parser.h b/webm_parser/src/block_parser.h
new file mode 100644
index 0000000..a906826
--- /dev/null
+++ b/webm_parser/src/block_parser.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_BLOCK_PARSER_H_
+#define SRC_BLOCK_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+#include <type_traits>
+#include <vector>
+
+#include "src/block_header_parser.h"
+#include "src/element_parser.h"
+#include "src/var_int_parser.h"
+#include "webm/callback.h"
+#include "webm/dom_types.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses Block and SimpleBlock elements. It is recommended to use the
+// BlockParser and SimpleBlockParser aliases.
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Block
+// http://matroska.org/technical/specs/index.html#SimpleBlock
+// http://www.webmproject.org/docs/container/#SimpleBlock
+// http://www.webmproject.org/docs/container/#Block
+// http://matroska.org/technical/specs/index.html#block_structure
+// http://matroska.org/technical/specs/index.html#simpleblock_structure
+template <typename T>
+class BasicBlockParser : public ElementParser {
+ static_assert(std::is_same<T, Block>::value ||
+ std::is_same<T, SimpleBlock>::value,
+ "T must be Block or SimpleBlock");
+
+ public:
+ Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ bool WasSkipped() const override;
+
+ // Gets the parsed block header information. The frames are not included. This
+ // must not be called until the parse has been successfully completed.
+ const T& value() const {
+ assert(state_ == State::kDone);
+ return value_;
+ }
+
+ // Gets the parsed block header information. The frames are not included. This
+ // must not be called until the parse has been successfully completed.
+ T* mutable_value() {
+ assert(state_ == State::kDone);
+ return &value_;
+ }
+
+ private:
+ // The number of header bytes read (header meaning everything before the
+ // frames).
+ std::uint64_t header_bytes_read_ = 0;
+
+ // The parsed header value for the element.
+ T value_{};
+
+ // Metadata for the frame that is currently being read.
+ FrameMetadata frame_metadata_;
+
+ // Parser for parsing header metadata that is common between Block and
+ // SimpleBlock.
+ BlockHeaderParser header_parser_;
+
+ // Parser for parsing unsigned EBML variable-sized integers.
+ VarIntParser uint_parser_;
+
+ // The current lace size when parsing Xiph lace sizes.
+ std::uint64_t xiph_lace_size_ = 0;
+
+ // Lace (frame) sizes, where each entry represents the size of a frame.
+ std::vector<std::uint64_t> lace_sizes_;
+
+ // The current index into lace_sizes_ for the current frame being read.
+ std::size_t current_lace_ = 0;
+
+ // Parsing states for the finite-state machine.
+ enum class State {
+ /* clang-format off */
+ // State Transitions to state When
+ kReadingHeader, // kGettingAction no lacing
+ // kReadingLaceCount yes lacing
+ kReadingLaceCount, // kGettingAction no errors
+ kGettingAction, // kSkipping action == skip
+ // kValidatingSize no lacing
+ // kReadingXiphLaceSizes xiph lacing
+ // kReadingFirstEbmlLaceSize ebml lacing
+ // kCalculatingFixedLaceSizes fixed lacing
+ kReadingXiphLaceSizes, // kValidatingSize all sizes read
+ kReadingFirstEbmlLaceSize, // kReadingEbmlLaceSizes first size read
+ kReadingEbmlLaceSizes, // kValidatingSize all sizes read
+ kCalculatingFixedLaceSizes, // kReadingFrames no errors
+ kValidatingSize, // kReadingFrames no errors
+ kSkipping, // No transitions from here (must call Init)
+ kReadingFrames, // kDone all frames read
+ kDone, // No transitions from here (must call Init)
+ /* clang-format on */
+ };
+
+ // The current state of the parser.
+ State state_ = State::kReadingHeader;
+};
+
+extern template class BasicBlockParser<Block>;
+extern template class BasicBlockParser<SimpleBlock>;
+
+using BlockParser = BasicBlockParser<Block>;
+using SimpleBlockParser = BasicBlockParser<SimpleBlock>;
+
+} // namespace webm
+
+#endif // SRC_BLOCK_PARSER_H_
diff --git a/webm_parser/src/bool_parser.h b/webm_parser/src/bool_parser.h
new file mode 100644
index 0000000..3d7f11e
--- /dev/null
+++ b/webm_parser/src/bool_parser.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_BOOL_PARSER_H_
+#define SRC_BOOL_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/element_parser.h"
+#include "src/parser_utils.h"
+#include "webm/callback.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses a boolean from a byte stream. EBML does not have a boolean type, but
+// the Matroska spec defines some unsigned integer elements that have a range of
+// [0, 1]. The BoolParser parses these unsigned integer elements into
+// true/false, and reports a Status::kInvalidElementValue error if the integer
+// is outside of its permitted range.
+class BoolParser : public ElementParser {
+ public:
+ explicit BoolParser(bool default_value = false)
+ : default_value_(default_value) {}
+
+ Status Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) override {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ // Booleans are really just unsigned integers with a range limit of 0-1.
+ // Unsigned integers can't be encoded with more than 8 bytes.
+ if (metadata.size > 8) {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ size_ = num_bytes_remaining_ = static_cast<int>(metadata.size);
+ value_ = default_value_;
+
+ return Status(Status::kOkCompleted);
+ }
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ std::uint64_t uint_value = 0;
+ const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader,
+ &uint_value, num_bytes_read);
+ num_bytes_remaining_ -= static_cast<int>(*num_bytes_read);
+
+ // Only the last byte should have a value, and it should only be 0 or 1.
+ if ((num_bytes_remaining_ != 0 && uint_value != 0) || uint_value > 1) {
+ return Status(Status::kInvalidElementValue);
+ }
+
+ if (size_ > 0) {
+ value_ = uint_value == 1;
+ }
+
+ return status;
+ }
+
+ // Gets the parsed bool. This must not be called until the parse had been
+ // successfully completed.
+ bool value() const {
+ assert(num_bytes_remaining_ == 0);
+ return value_;
+ }
+
+ // Gets the parsed bool. This must not be called until the parse had been
+ // successfully completed.
+ bool* mutable_value() {
+ assert(num_bytes_remaining_ == 0);
+ return &value_;
+ }
+
+ private:
+ bool value_;
+ bool default_value_;
+ int num_bytes_remaining_ = -1;
+ int size_;
+};
+
+} // namespace webm
+
+#endif // SRC_BOOL_PARSER_H_
diff --git a/webm_parser/src/buffer_reader.cc b/webm_parser/src/buffer_reader.cc
new file mode 100644
index 0000000..51b0b01
--- /dev/null
+++ b/webm_parser/src/buffer_reader.cc
@@ -0,0 +1,110 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "webm/buffer_reader.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <utility>
+#include <vector>
+
+#include "webm/status.h"
+
+namespace webm {
+
+BufferReader::BufferReader(std::initializer_list<std::uint8_t> bytes)
+ : data_(bytes) {}
+
+BufferReader::BufferReader(const std::vector<std::uint8_t>& vector)
+ : data_(vector) {}
+
+BufferReader::BufferReader(std::vector<std::uint8_t>&& vector)
+ : data_(std::move(vector)) {}
+
+BufferReader::BufferReader(BufferReader&& other)
+ : data_(std::move(other.data_)), pos_(other.pos_) {
+ other.pos_ = 0;
+}
+
+BufferReader& BufferReader::operator=(BufferReader&& other) {
+ if (this != &other) {
+ data_ = std::move(other.data_);
+ pos_ = other.pos_;
+ other.pos_ = 0;
+ }
+ return *this;
+}
+
+BufferReader& BufferReader::operator=(
+ std::initializer_list<std::uint8_t> bytes) {
+ data_ = std::vector<std::uint8_t>(bytes);
+ pos_ = 0;
+ return *this;
+}
+
+Status BufferReader::Read(std::size_t num_to_read, std::uint8_t* buffer,
+ std::uint64_t* num_actually_read) {
+ assert(num_to_read > 0);
+ assert(buffer != nullptr);
+ assert(num_actually_read != nullptr);
+
+ *num_actually_read = 0;
+ std::size_t expected = num_to_read;
+
+ std::size_t num_remaining = data_.size() - pos_;
+ if (num_remaining == 0) {
+ return Status(Status::kEndOfFile);
+ }
+
+ if (num_to_read > num_remaining) {
+ num_to_read = static_cast<std::size_t>(num_remaining);
+ }
+
+ std::copy_n(data_.data() + pos_, num_to_read, buffer);
+ *num_actually_read = num_to_read;
+ pos_ += num_to_read;
+
+ if (*num_actually_read != expected) {
+ return Status(Status::kOkPartial);
+ }
+
+ return Status(Status::kOkCompleted);
+}
+
+Status BufferReader::Skip(std::uint64_t num_to_skip,
+ std::uint64_t* num_actually_skipped) {
+ assert(num_to_skip > 0);
+ assert(num_actually_skipped != nullptr);
+
+ *num_actually_skipped = 0;
+ std::uint64_t expected = num_to_skip;
+
+ std::size_t num_remaining = data_.size() - pos_;
+ if (num_remaining == 0) {
+ return Status(Status::kEndOfFile);
+ }
+
+ if (num_to_skip > num_remaining) {
+ num_to_skip = static_cast<std::uint64_t>(num_remaining);
+ }
+
+ *num_actually_skipped = num_to_skip;
+ pos_ += num_to_skip;
+
+ if (*num_actually_skipped != expected) {
+ return Status(Status::kOkPartial);
+ }
+
+ return Status(Status::kOkCompleted);
+}
+
+std::uint64_t BufferReader::Position() const { return pos_; }
+
+} // namespace webm
diff --git a/webm_parser/src/byte_parser.h b/webm_parser/src/byte_parser.h
new file mode 100644
index 0000000..f2ed3cb
--- /dev/null
+++ b/webm_parser/src/byte_parser.h
@@ -0,0 +1,144 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_BYTE_PARSER_H_
+#define SRC_BYTE_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "src/element_parser.h"
+#include "webm/callback.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses an EBML string (UTF-8 and ASCII) or binary element from a byte stream.
+// Spec reference for string/binary elements:
+// http://matroska.org/technical/specs/index.html#EBML_ex
+// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#ebml-element-types
+template <typename T>
+class ByteParser : public ElementParser {
+ public:
+ static_assert(std::is_same<T, std::vector<std::uint8_t>>::value ||
+ std::is_same<T, std::string>::value,
+ "T must be std::vector<std::uint8_t> or std::string");
+
+ // Constructs a new parser which will use the given default_value as the
+ // value for the element if its size is zero. Defaults to the empty string
+ // or empty binary element (as the EBML spec indicates).
+ explicit ByteParser(T default_value = {})
+ : default_value_(std::move(default_value)) {}
+
+ ByteParser(ByteParser&&) = default;
+ ByteParser& operator=(ByteParser&&) = default;
+
+ ByteParser(const ByteParser&) = delete;
+ ByteParser& operator=(const ByteParser&) = delete;
+
+ Status Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) override {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ if (metadata.size == kUnknownElementSize) {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ if (metadata.size > std::numeric_limits<std::size_t>::max() ||
+ metadata.size > value_.max_size()) {
+ return Status(Status::kNotEnoughMemory);
+ }
+
+#if WEBM_FUZZER_BYTE_ELEMENT_SIZE_LIMIT
+ // AFL and ASan just kill the process if too much memory is allocated, so
+ // let's cap the maximum size of the element. It's too easy for the fuzzer
+ // to make an element with a ridiculously huge size, and that just creates
+ // uninteresting false positives.
+ if (metadata.size > WEBM_FUZZER_BYTE_ELEMENT_SIZE_LIMIT) {
+ return Status(Status::kNotEnoughMemory);
+ }
+#endif
+
+ if (metadata.size == 0) {
+ value_ = default_value_;
+ total_read_ = default_value_.size();
+ } else {
+ value_.resize(static_cast<std::size_t>(metadata.size));
+ total_read_ = 0;
+ }
+
+ return Status(Status::kOkCompleted);
+ }
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ *num_bytes_read = 0;
+
+ if (total_read_ == value_.size()) {
+ return Status(Status::kOkCompleted);
+ }
+
+ Status status;
+ do {
+ std::uint64_t local_num_bytes_read = 0;
+ std::uint8_t* buffer =
+ reinterpret_cast<std::uint8_t*>(&value_.front()) + total_read_;
+ std::size_t buffer_size = value_.size() - total_read_;
+ status = reader->Read(buffer_size, buffer, &local_num_bytes_read);
+ assert((status.completed_ok() && local_num_bytes_read == buffer_size) ||
+ (status.ok() && local_num_bytes_read < buffer_size) ||
+ (!status.ok() && local_num_bytes_read == 0));
+ *num_bytes_read += local_num_bytes_read;
+ total_read_ += static_cast<std::size_t>(local_num_bytes_read);
+ } while (status.code == Status::kOkPartial);
+
+ // UTF-8 and ASCII string elements can be padded with NUL characters at the
+ // end, which should be ignored.
+ if (std::is_same<T, std::string>::value && status.completed_ok()) {
+ while (!value_.empty() && value_.back() == '\0') {
+ value_.pop_back();
+ }
+ }
+
+ return status;
+ }
+
+ // Gets the parsed value. This must not be called until the parse has been
+ // successfully completed.
+ const T& value() const {
+ assert(total_read_ >= value_.size());
+ return value_;
+ }
+
+ // Gets the parsed value. This must not be called until the parse has been
+ // successfully completed.
+ T* mutable_value() {
+ assert(total_read_ >= value_.size());
+ return &value_;
+ }
+
+ private:
+ T value_;
+ T default_value_;
+ std::size_t total_read_;
+};
+
+using StringParser = ByteParser<std::string>;
+using BinaryParser = ByteParser<std::vector<std::uint8_t>>;
+
+} // namespace webm
+
+#endif // SRC_BYTE_PARSER_H_
diff --git a/webm_parser/src/callback.cc b/webm_parser/src/callback.cc
new file mode 100644
index 0000000..cda30c8
--- /dev/null
+++ b/webm_parser/src/callback.cc
@@ -0,0 +1,155 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "webm/callback.h"
+
+#include <cassert>
+
+namespace webm {
+
+Status Callback::OnElementBegin(const ElementMetadata& /* metadata */,
+ Action* action) {
+ assert(action != nullptr);
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnUnknownElement(const ElementMetadata& /* metadata */,
+ Reader* reader,
+ std::uint64_t* bytes_remaining) {
+ assert(reader != nullptr);
+ assert(bytes_remaining != nullptr);
+ return Skip(reader, bytes_remaining);
+}
+
+Status Callback::OnEbml(const ElementMetadata& /* metadata */,
+ const Ebml& /* ebml */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnVoid(const ElementMetadata& /* metadata */, Reader* reader,
+ std::uint64_t* bytes_remaining) {
+ assert(reader != nullptr);
+ assert(bytes_remaining != nullptr);
+ return Skip(reader, bytes_remaining);
+}
+
+Status Callback::OnSegmentBegin(const ElementMetadata& /* metadata */,
+ Action* action) {
+ assert(action != nullptr);
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnSeek(const ElementMetadata& /* metadata */,
+ const Seek& /* seek */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnInfo(const ElementMetadata& /* metadata */,
+ const Info& /* info */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnClusterBegin(const ElementMetadata& /* metadata */,
+ const Cluster& /* cluster */, Action* action) {
+ assert(action != nullptr);
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnSimpleBlockBegin(const ElementMetadata& /* metadata */,
+ const SimpleBlock& /* simple_block */,
+ Action* action) {
+ assert(action != nullptr);
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnSimpleBlockEnd(const ElementMetadata& /* metadata */,
+ const SimpleBlock& /* simple_block */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnBlockGroupBegin(const ElementMetadata& /* metadata */,
+ Action* action) {
+ assert(action != nullptr);
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnBlockBegin(const ElementMetadata& /* metadata */,
+ const Block& /* block */, Action* action) {
+ assert(action != nullptr);
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnBlockEnd(const ElementMetadata& /* metadata */,
+ const Block& /* block */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnBlockGroupEnd(const ElementMetadata& /* metadata */,
+ const BlockGroup& /* block_group */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnFrame(const FrameMetadata& /* metadata */, Reader* reader,
+ std::uint64_t* bytes_remaining) {
+ assert(reader != nullptr);
+ assert(bytes_remaining != nullptr);
+ return Skip(reader, bytes_remaining);
+}
+
+Status Callback::OnClusterEnd(const ElementMetadata& /* metadata */,
+ const Cluster& /* cluster */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnTrackEntry(const ElementMetadata& /* metadata */,
+ const TrackEntry& /* track_entry */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnCuePoint(const ElementMetadata& /* metadata */,
+ const CuePoint& /* cue_point */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnEditionEntry(const ElementMetadata& /* metadata */,
+ const EditionEntry& /* edition_entry */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnTag(const ElementMetadata& /* metadata */,
+ const Tag& /* tag */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::OnSegmentEnd(const ElementMetadata& /* metadata */) {
+ return Status(Status::kOkCompleted);
+}
+
+Status Callback::Skip(Reader* reader, std::uint64_t* bytes_remaining) {
+ assert(reader != nullptr);
+ assert(bytes_remaining != nullptr);
+
+ if (*bytes_remaining == 0)
+ return Status(Status::kOkCompleted);
+
+ Status status;
+ do {
+ std::uint64_t num_actually_skipped;
+ status = reader->Skip(*bytes_remaining, &num_actually_skipped);
+ *bytes_remaining -= num_actually_skipped;
+ } while (status.code == Status::kOkPartial);
+
+ return status;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/chapter_atom_parser.h b/webm_parser/src/chapter_atom_parser.h
new file mode 100644
index 0000000..653f59f
--- /dev/null
+++ b/webm_parser/src/chapter_atom_parser.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CHAPTER_ATOM_PARSER_H_
+#define SRC_CHAPTER_ATOM_PARSER_H_
+
+#include "src/byte_parser.h"
+#include "src/chapter_display_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#ChapterAtom
+// http://www.webmproject.org/docs/container/#ChapterAtom
+class ChapterAtomParser : public MasterValueParser<ChapterAtom> {
+ public:
+ explicit ChapterAtomParser(std::size_t max_recursive_depth = 25)
+ : MasterValueParser<ChapterAtom>(
+ MakeChild<UnsignedIntParser>(Id::kChapterUid, &ChapterAtom::uid),
+ MakeChild<StringParser>(Id::kChapterStringUid,
+ &ChapterAtom::string_uid),
+ MakeChild<UnsignedIntParser>(Id::kChapterTimeStart,
+ &ChapterAtom::time_start),
+ MakeChild<UnsignedIntParser>(Id::kChapterTimeEnd,
+ &ChapterAtom::time_end),
+ MakeChild<ChapterDisplayParser>(Id::kChapterDisplay,
+ &ChapterAtom::displays),
+ MakeChild<ChapterAtomParser>(Id::kChapterAtom, &ChapterAtom::atoms,
+ max_recursive_depth)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_CHAPTER_ATOM_PARSER_H_
diff --git a/webm_parser/src/chapter_display_parser.h b/webm_parser/src/chapter_display_parser.h
new file mode 100644
index 0000000..4c4cfd2
--- /dev/null
+++ b/webm_parser/src/chapter_display_parser.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CHAPTER_DISPLAY_PARSER_H_
+#define SRC_CHAPTER_DISPLAY_PARSER_H_
+
+#include "src/byte_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#ChapterDisplay
+// http://www.webmproject.org/docs/container/#ChapterDisplay
+class ChapterDisplayParser : public MasterValueParser<ChapterDisplay> {
+ public:
+ ChapterDisplayParser()
+ : MasterValueParser<ChapterDisplay>(
+ MakeChild<StringParser>(Id::kChapString, &ChapterDisplay::string),
+ MakeChild<StringParser>(Id::kChapLanguage,
+ &ChapterDisplay::languages),
+ MakeChild<StringParser>(Id::kChapCountry,
+ &ChapterDisplay::countries)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_CHAPTER_DISPLAY_PARSER_H_
diff --git a/webm_parser/src/chapters_parser.h b/webm_parser/src/chapters_parser.h
new file mode 100644
index 0000000..2863cf9
--- /dev/null
+++ b/webm_parser/src/chapters_parser.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CHAPTERS_PARSER_H_
+#define SRC_CHAPTERS_PARSER_H_
+
+#include "src/edition_entry_parser.h"
+#include "src/master_parser.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Chapters
+// http://www.webmproject.org/docs/container/#Chapters
+class ChaptersParser : public MasterParser {
+ public:
+ ChaptersParser()
+ : MasterParser(MakeChild<EditionEntryParser>(Id::kEditionEntry)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_CHAPTERS_PARSER_H_
diff --git a/webm_parser/src/cluster_parser.h b/webm_parser/src/cluster_parser.h
new file mode 100644
index 0000000..35cba92
--- /dev/null
+++ b/webm_parser/src/cluster_parser.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CLUSTER_PARSER_H_
+#define SRC_CLUSTER_PARSER_H_
+
+#include "src/block_group_parser.h"
+#include "src/block_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Cluster
+// http://www.webmproject.org/docs/container/#Cluster
+class ClusterParser : public MasterValueParser<Cluster> {
+ public:
+ ClusterParser()
+ : MasterValueParser<Cluster>(
+ MakeChild<UnsignedIntParser>(Id::kTimecode, &Cluster::timecode),
+ MakeChild<UnsignedIntParser>(Id::kPrevSize,
+ &Cluster::previous_size),
+ MakeChild<SimpleBlockParser>(Id::kSimpleBlock,
+ &Cluster::simple_blocks)
+ .UseAsStartEvent(),
+ MakeChild<BlockGroupParser>(Id::kBlockGroup, &Cluster::block_groups)
+ .UseAsStartEvent()) {}
+
+ protected:
+ Status OnParseStarted(Callback* callback, Action* action) override {
+ return callback->OnClusterBegin(metadata(Id::kCluster), value(), action);
+ }
+
+ Status OnParseCompleted(Callback* callback) override {
+ return callback->OnClusterEnd(metadata(Id::kCluster), value());
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_CLUSTER_PARSER_H_
diff --git a/webm_parser/src/colour_parser.h b/webm_parser/src/colour_parser.h
new file mode 100644
index 0000000..e05f14f
--- /dev/null
+++ b/webm_parser/src/colour_parser.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_COLOUR_PARSER_H_
+#define SRC_COLOUR_PARSER_H_
+
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "src/mastering_metadata_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Colour
+// http://www.webmproject.org/docs/container/#Colour
+class ColourParser : public MasterValueParser<Colour> {
+ public:
+ ColourParser()
+ : MasterValueParser<Colour>(
+ MakeChild<IntParser<MatrixCoefficients>>(
+ Id::kMatrixCoefficients, &Colour::matrix_coefficients),
+ MakeChild<UnsignedIntParser>(Id::kBitsPerChannel,
+ &Colour::bits_per_channel),
+ MakeChild<UnsignedIntParser>(Id::kChromaSubsamplingHorz,
+ &Colour::chroma_subsampling_x),
+ MakeChild<UnsignedIntParser>(Id::kChromaSubsamplingVert,
+ &Colour::chroma_subsampling_y),
+ MakeChild<UnsignedIntParser>(Id::kCbSubsamplingHorz,
+ &Colour::cb_subsampling_x),
+ MakeChild<UnsignedIntParser>(Id::kCbSubsamplingVert,
+ &Colour::cb_subsampling_y),
+ MakeChild<UnsignedIntParser>(Id::kChromaSitingHorz,
+ &Colour::chroma_siting_x),
+ MakeChild<UnsignedIntParser>(Id::kChromaSitingVert,
+ &Colour::chroma_siting_y),
+ MakeChild<IntParser<Range>>(Id::kRange, &Colour::range),
+ MakeChild<IntParser<TransferCharacteristics>>(
+ Id::kTransferCharacteristics,
+ &Colour::transfer_characteristics),
+ MakeChild<IntParser<Primaries>>(Id::kPrimaries, &Colour::primaries),
+ MakeChild<UnsignedIntParser>(Id::kMaxCll, &Colour::max_cll),
+ MakeChild<UnsignedIntParser>(Id::kMaxFall, &Colour::max_fall),
+ MakeChild<MasteringMetadataParser>(Id::kMasteringMetadata,
+ &Colour::mastering_metadata)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_COLOUR_PARSER_H_
diff --git a/webm_parser/src/content_enc_aes_settings_parser.h b/webm_parser/src/content_enc_aes_settings_parser.h
new file mode 100644
index 0000000..27f8635
--- /dev/null
+++ b/webm_parser/src/content_enc_aes_settings_parser.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CONTENT_ENC_AES_SETTINGS_PARSER_H_
+#define SRC_CONTENT_ENC_AES_SETTINGS_PARSER_H_
+
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://www.webmproject.org/docs/webm-encryption/#42-new-matroskawebm-elements
+class ContentEncAesSettingsParser
+ : public MasterValueParser<ContentEncAesSettings> {
+ public:
+ ContentEncAesSettingsParser()
+ : MasterValueParser<ContentEncAesSettings>(
+ MakeChild<IntParser<AesSettingsCipherMode>>(
+ Id::kAesSettingsCipherMode,
+ &ContentEncAesSettings::aes_settings_cipher_mode)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_CONTENT_ENC_AES_SETTINGS_PARSER_H_
diff --git a/webm_parser/src/content_encoding_parser.h b/webm_parser/src/content_encoding_parser.h
new file mode 100644
index 0000000..4fccfe7
--- /dev/null
+++ b/webm_parser/src/content_encoding_parser.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CONTENT_ENCODING_PARSER_H_
+#define SRC_CONTENT_ENCODING_PARSER_H_
+
+#include "src/content_encryption_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#ContentEncoding
+// http://www.webmproject.org/docs/container/#ContentEncoding
+class ContentEncodingParser : public MasterValueParser<ContentEncoding> {
+ public:
+ ContentEncodingParser()
+ : MasterValueParser<ContentEncoding>(
+ MakeChild<UnsignedIntParser>(Id::kContentEncodingOrder,
+ &ContentEncoding::order),
+ MakeChild<UnsignedIntParser>(Id::kContentEncodingScope,
+ &ContentEncoding::scope),
+ MakeChild<IntParser<ContentEncodingType>>(Id::kContentEncodingType,
+ &ContentEncoding::type),
+ MakeChild<ContentEncryptionParser>(Id::kContentEncryption,
+ &ContentEncoding::encryption)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_CONTENT_ENCODING_PARSER_H_
diff --git a/webm_parser/src/content_encodings_parser.h b/webm_parser/src/content_encodings_parser.h
new file mode 100644
index 0000000..0933fda
--- /dev/null
+++ b/webm_parser/src/content_encodings_parser.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CONTENT_ENCODINGS_PARSER_H_
+#define SRC_CONTENT_ENCODINGS_PARSER_H_
+
+#include "src/content_encoding_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#ContentEncodings
+// http://www.webmproject.org/docs/container/#ContentEncodings
+class ContentEncodingsParser : public MasterValueParser<ContentEncodings> {
+ public:
+ ContentEncodingsParser()
+ : MasterValueParser<ContentEncodings>(MakeChild<ContentEncodingParser>(
+ Id::kContentEncoding, &ContentEncodings::encodings)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_CONTENT_ENCODINGS_PARSER_H_
diff --git a/webm_parser/src/content_encryption_parser.h b/webm_parser/src/content_encryption_parser.h
new file mode 100644
index 0000000..46ec2e7
--- /dev/null
+++ b/webm_parser/src/content_encryption_parser.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CONTENT_ENCRYPTION_PARSER_H_
+#define SRC_CONTENT_ENCRYPTION_PARSER_H_
+
+#include "src/byte_parser.h"
+#include "src/content_enc_aes_settings_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#ContentEncryption
+// http://www.webmproject.org/docs/container/#ContentEncryption
+class ContentEncryptionParser : public MasterValueParser<ContentEncryption> {
+ public:
+ ContentEncryptionParser()
+ : MasterValueParser<ContentEncryption>(
+ MakeChild<IntParser<ContentEncAlgo>>(Id::kContentEncAlgo,
+ &ContentEncryption::algorithm),
+ MakeChild<BinaryParser>(Id::kContentEncKeyId,
+ &ContentEncryption::key_id),
+ MakeChild<ContentEncAesSettingsParser>(
+ Id::kContentEncAesSettings, &ContentEncryption::aes_settings)) {
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_CONTENT_ENCRYPTION_PARSER_H_
diff --git a/webm_parser/src/cue_point_parser.h b/webm_parser/src/cue_point_parser.h
new file mode 100644
index 0000000..a8ebe4d
--- /dev/null
+++ b/webm_parser/src/cue_point_parser.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CUE_POINT_PARSER_H_
+#define SRC_CUE_POINT_PARSER_H_
+
+#include "src/cue_track_positions_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#CuePoint
+// http://www.webmproject.org/docs/container/#CuePoint
+class CuePointParser : public MasterValueParser<CuePoint> {
+ public:
+ CuePointParser()
+ : MasterValueParser<CuePoint>(
+ MakeChild<UnsignedIntParser>(Id::kCueTime, &CuePoint::time),
+ MakeChild<CueTrackPositionsParser>(
+ Id::kCueTrackPositions, &CuePoint::cue_track_positions)) {}
+
+ protected:
+ Status OnParseCompleted(Callback* callback) override {
+ return callback->OnCuePoint(metadata(Id::kCuePoint), value());
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_CUE_POINT_PARSER_H_
diff --git a/webm_parser/src/cue_track_positions_parser.h b/webm_parser/src/cue_track_positions_parser.h
new file mode 100644
index 0000000..eede78e
--- /dev/null
+++ b/webm_parser/src/cue_track_positions_parser.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CUE_TRACK_POSITIONS_PARSER_H_
+#define SRC_CUE_TRACK_POSITIONS_PARSER_H_
+
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#CueTrackPositions
+// http://www.webmproject.org/docs/container/#CueTrackPositions
+class CueTrackPositionsParser : public MasterValueParser<CueTrackPositions> {
+ public:
+ CueTrackPositionsParser()
+ : MasterValueParser<CueTrackPositions>(
+ MakeChild<UnsignedIntParser>(Id::kCueTrack,
+ &CueTrackPositions::track),
+ MakeChild<UnsignedIntParser>(Id::kCueClusterPosition,
+ &CueTrackPositions::cluster_position),
+ MakeChild<UnsignedIntParser>(Id::kCueRelativePosition,
+ &CueTrackPositions::relative_position),
+ MakeChild<UnsignedIntParser>(Id::kCueDuration,
+ &CueTrackPositions::duration),
+ MakeChild<UnsignedIntParser>(Id::kCueBlockNumber,
+ &CueTrackPositions::block_number)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_CUE_TRACK_POSITIONS_PARSER_H_
diff --git a/webm_parser/src/cues_parser.h b/webm_parser/src/cues_parser.h
new file mode 100644
index 0000000..7316b82
--- /dev/null
+++ b/webm_parser/src/cues_parser.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_CUES_PARSER_H_
+#define SRC_CUES_PARSER_H_
+
+#include "src/cue_point_parser.h"
+#include "src/master_parser.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Cues
+// http://www.webmproject.org/docs/container/#Cues
+class CuesParser : public MasterParser {
+ public:
+ CuesParser() : MasterParser(MakeChild<CuePointParser>(Id::kCuePoint)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_CUES_PARSER_H_
diff --git a/webm_parser/src/date_parser.cc b/webm_parser/src/date_parser.cc
new file mode 100644
index 0000000..1eb8801
--- /dev/null
+++ b/webm_parser/src/date_parser.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/date_parser.h"
+
+#include <cassert>
+#include <cstdint>
+#include <limits>
+
+#include "src/parser_utils.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#EBML_ex
+// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#ebml-element-types
+DateParser::DateParser(std::int64_t default_value)
+ : default_value_(default_value) {}
+
+Status DateParser::Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ if (metadata.size != 0 && metadata.size != 8) {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ num_bytes_remaining_ = static_cast<int>(metadata.size);
+
+ // The meaning of a 0-byte element is still being debated. EBML says the value
+ // is zero; Matroska says it's the default according to whatever the document
+ // spec says. Neither specifies what a 0-byte mandatory element means. I've
+ // asked about this on the Matroska mailing list. I'm going to assume a 0-byte
+ // mandatory element should be treated the same as a 0-byte optional element,
+ // meaning that they both get their default value (which may be some value
+ // other than zero). This applies to all non-master-elements (not just dates).
+ // This parser is an EBML-level parser, and so will default to a value of
+ // zero. The Matroska-level parser can reset this default value to something
+ // else after parsing (as needed).
+ // See:
+ // https://github.com/Matroska-Org/ebml-specification/pull/17
+ // http://lists.matroska.org/pipermail/matroska-devel/2015-October/004866.html
+ if (metadata.size == 0) {
+ value_ = default_value_;
+ } else {
+ value_ = 0;
+ }
+
+ return Status(Status::kOkCompleted);
+}
+
+Status DateParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader,
+ &value_, num_bytes_read);
+ num_bytes_remaining_ -= static_cast<int>(*num_bytes_read);
+
+ return status;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/date_parser.h b/webm_parser/src/date_parser.h
new file mode 100644
index 0000000..8038be3
--- /dev/null
+++ b/webm_parser/src/date_parser.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_DATE_PARSER_H_
+#define SRC_DATE_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/element_parser.h"
+#include "webm/callback.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses an EBML date from a byte stream. EBML dates are signed integer values
+// that represent the offset, in nanoseconds, from 2001-01-01T00:00:00.00 UTC.
+class DateParser : public ElementParser {
+ public:
+ // Constructs a new parser which will use the given default_value as the
+ // value for the element if its size is zero. Defaults to the value zero (as
+ // the EBML spec indicates).
+ explicit DateParser(std::int64_t default_value = 0);
+
+ DateParser(DateParser&&) = default;
+ DateParser& operator=(DateParser&&) = default;
+
+ DateParser(const DateParser&) = delete;
+ DateParser& operator=(const DateParser&) = delete;
+
+ Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ // Gets the parsed date. This must not be called until the parse had been
+ // successfully completed.
+ std::int64_t value() const {
+ assert(num_bytes_remaining_ == 0);
+ return value_;
+ }
+
+ // Gets the parsed date. This must not be called until the parse had been
+ // successfully completed.
+ std::int64_t* mutable_value() {
+ assert(num_bytes_remaining_ == 0);
+ return &value_;
+ }
+
+ private:
+ std::int64_t value_;
+ std::int64_t default_value_;
+ int num_bytes_remaining_ = -1;
+};
+
+} // namespace webm
+
+#endif // SRC_DATE_PARSER_H_
diff --git a/webm_parser/src/ebml_parser.h b/webm_parser/src/ebml_parser.h
new file mode 100644
index 0000000..c5de155
--- /dev/null
+++ b/webm_parser/src/ebml_parser.h
@@ -0,0 +1,48 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_EBML_PARSER_H_
+#define SRC_EBML_PARSER_H_
+
+#include "src/byte_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec references:
+// http://matroska.org/technical/specs/index.html#EBML
+// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#ebml-header-elements
+// http://www.webmproject.org/docs/container/#EBML
+class EbmlParser : public MasterValueParser<Ebml> {
+ public:
+ EbmlParser()
+ : MasterValueParser<Ebml>(
+ MakeChild<UnsignedIntParser>(Id::kEbmlVersion, &Ebml::ebml_version),
+ MakeChild<UnsignedIntParser>(Id::kEbmlReadVersion,
+ &Ebml::ebml_read_version),
+ MakeChild<UnsignedIntParser>(Id::kEbmlMaxIdLength,
+ &Ebml::ebml_max_id_length),
+ MakeChild<UnsignedIntParser>(Id::kEbmlMaxSizeLength,
+ &Ebml::ebml_max_size_length),
+ MakeChild<StringParser>(Id::kDocType, &Ebml::doc_type),
+ MakeChild<UnsignedIntParser>(Id::kDocTypeVersion,
+ &Ebml::doc_type_version),
+ MakeChild<UnsignedIntParser>(Id::kDocTypeReadVersion,
+ &Ebml::doc_type_read_version)) {}
+
+ protected:
+ Status OnParseCompleted(Callback* callback) override {
+ return callback->OnEbml(metadata(Id::kEbml), value());
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_EBML_PARSER_H_
diff --git a/webm_parser/src/edition_entry_parser.h b/webm_parser/src/edition_entry_parser.h
new file mode 100644
index 0000000..1a82228
--- /dev/null
+++ b/webm_parser/src/edition_entry_parser.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_EDITION_ENTRY_PARSER_H_
+#define SRC_EDITION_ENTRY_PARSER_H_
+
+#include "src/chapter_atom_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#EditionEntry
+// http://www.webmproject.org/docs/container/#EditionEntry
+class EditionEntryParser : public MasterValueParser<EditionEntry> {
+ public:
+ EditionEntryParser()
+ : MasterValueParser<EditionEntry>(MakeChild<ChapterAtomParser>(
+ Id::kChapterAtom, &EditionEntry::atoms)) {}
+
+ protected:
+ Status OnParseCompleted(Callback* callback) override {
+ return callback->OnEditionEntry(metadata(Id::kEditionEntry), value());
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_EDITION_ENTRY_PARSER_H_
diff --git a/webm_parser/src/element_parser.h b/webm_parser/src/element_parser.h
new file mode 100644
index 0000000..5e202e8
--- /dev/null
+++ b/webm_parser/src/element_parser.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_ELEMENT_PARSER_H_
+#define SRC_ELEMENT_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/ancestory.h"
+#include "src/parser.h"
+#include "webm/callback.h"
+#include "webm/element.h"
+
+namespace webm {
+
+// Parses an element from a WebM byte stream. Objects that implement this
+// interface are expected to be used as follows in order to parse the specific
+// WebM element that they are designed to handle.
+//
+// Reader* reader = ...; // Create some Reader.
+// Callback* callback = ...; // Create some Callback.
+//
+// ElementMetadata metadata = {
+// id, // Element parsed from the reader.
+// header_size, // The number of bytes used to encode the id and size.
+// size_in_bytes, // The number of bytes in the element body.
+// position, // The position of the element (starting at the ID).
+// };
+//
+// std::uint64_t max_size = ...; // Some upper bound on this element's size.
+// ElementParser* parser = ...; // Create some parser capable of handling
+// // elements that match id.
+//
+// Status status = parser->Init(metadata, max_size);
+// if (!status.completed_ok()) {
+// // An error occurred. See status.code for the reason.
+// } else {
+// do {
+// std::uint64_t num_bytes_read = 0;
+// status = parser->Feed(callback, reader, &num_bytes_read);
+// } while (status.code == Status::kOkPartial);
+//
+// if (status.completed_ok()) {
+// // Parsing successfully completed.
+// } else {
+// // An error occurred. If status.code is a parsing error (see status.h for
+// // errors that are considered parsing errors), do not call Feed again;
+// // parsing has already failed and further progress can't be made. If
+// // status.code is not a parsing error (i.e. Status::kWouldBlock), then
+// // Feed may be called again to attempt resuming parsing.
+// }
+// }
+class ElementParser : public Parser {
+ public:
+ // Initializes the parser and prepares it for parsing its element. Returns
+ // Status::kOkCompleted if successful. Must not return Status::kOkPartial (it
+ // is not resumable). metadata is the metadata associated with this element.
+ // max_size must be <= metadata.size (unless metadata.size is
+ // kUnknownElementSize).
+ virtual Status Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) = 0;
+
+ // Initializes the parser after a seek was done and prepares it for parsing.
+ // The reader is now at the position of the child element indicated by
+ // child_metadata, whose ancestory is child_ancestory. The child element for
+ // this parser is the first element in child_ancestory, or if that is empty,
+ // then child_metadata itself. If the child is not a valid child of this
+ // parser, then a debug assertion is made (because that indicates a bug).
+ virtual void InitAfterSeek(const Ancestory& /* child_ancestory */,
+ const ElementMetadata& /* child_metadata */) {
+ assert(false);
+ }
+
+ // Returns true and sets metadata if this parser read too far and read the
+ // element metadata for an element that is not its child. This may happen, for
+ // example, when an element with unknown size is being read (because its end
+ // is considered the first element that is not a valid child, so it must read
+ // further to detect this). If this did not happen and false is returned, then
+ // metadata will not be modified. metadata must not be null.
+ virtual bool GetCachedMetadata(ElementMetadata* metadata) {
+ assert(metadata != nullptr);
+
+ return false;
+ }
+
+ // Returns true if this parser skipped the element instead of fully parsing
+ // it. This will be true if the user requested a kSkip action from the
+ // Callback in Feed(). This method should only be called after Feed() has
+ // returned kOkCompleted. If the element was skipped, do not try to access its
+ // value; it has no meaningful value and doing so will likely result in an
+ // assertion failing.
+ virtual bool WasSkipped() const { return false; }
+};
+
+} // namespace webm
+
+#endif // SRC_ELEMENT_PARSER_H_
diff --git a/webm_parser/src/file_reader.cc b/webm_parser/src/file_reader.cc
new file mode 100644
index 0000000..0921abe
--- /dev/null
+++ b/webm_parser/src/file_reader.cc
@@ -0,0 +1,122 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "webm/file_reader.h"
+
+#include <cassert>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <limits>
+#include <memory>
+
+#include "webm/status.h"
+
+namespace webm {
+
+FileReader::FileReader(FILE* file) : file_(file) { assert(file); }
+
+FileReader::FileReader(FileReader&& other)
+ : file_(std::move(other.file_)), position_(other.position_) {
+ other.position_ = 0;
+}
+
+FileReader& FileReader::operator=(FileReader&& other) {
+ if (this != &other) {
+ file_ = std::move(other.file_);
+ position_ = other.position_;
+ other.position_ = 0;
+ }
+ return *this;
+}
+
+Status FileReader::Read(std::size_t num_to_read, std::uint8_t* buffer,
+ std::uint64_t* num_actually_read) {
+ assert(num_to_read > 0);
+ assert(buffer != nullptr);
+ assert(num_actually_read != nullptr);
+
+ if (file_ == nullptr) {
+ *num_actually_read = 0;
+ return Status(Status::kEndOfFile);
+ }
+
+ std::size_t actual =
+ std::fread(static_cast<void*>(buffer), 1, num_to_read, file_.get());
+ *num_actually_read = static_cast<std::uint64_t>(actual);
+ position_ += *num_actually_read;
+
+ if (actual == 0) {
+ return Status(Status::kEndOfFile);
+ }
+
+ if (actual == num_to_read) {
+ return Status(Status::kOkCompleted);
+ } else {
+ return Status(Status::kOkPartial);
+ }
+}
+
+Status FileReader::Skip(std::uint64_t num_to_skip,
+ std::uint64_t* num_actually_skipped) {
+ assert(num_to_skip > 0);
+ assert(num_actually_skipped != nullptr);
+
+ *num_actually_skipped = 0;
+
+ if (file_ == nullptr) {
+ return Status(Status::kEndOfFile);
+ }
+
+ // Try seeking forward first.
+ long seek_offset = std::numeric_limits<long>::max(); // NOLINT
+ if (num_to_skip < static_cast<unsigned long>(seek_offset)) { // NOLINT
+ seek_offset = static_cast<long>(num_to_skip); // NOLINT
+ }
+ // TODO(mjbshaw): Use fseeko64/_fseeki64 if available.
+ if (!std::fseek(file_.get(), seek_offset, SEEK_CUR)) {
+ *num_actually_skipped = static_cast<std::uint64_t>(seek_offset);
+ position_ += static_cast<std::uint64_t>(seek_offset);
+ if (static_cast<unsigned long>(seek_offset) == num_to_skip) { // NOLINT
+ return Status(Status::kOkCompleted);
+ } else {
+ return Status(Status::kOkPartial);
+ }
+ }
+ std::clearerr(file_.get());
+
+ // Seeking doesn't work on things like pipes, so if seeking failed then fall
+ // back to reading the data into a junk buffer.
+ std::size_t actual = 0;
+ do {
+ std::uint8_t junk[1024];
+ std::size_t num_to_read = sizeof(junk);
+ if (num_to_skip < num_to_read) {
+ num_to_read = static_cast<std::size_t>(num_to_skip);
+ }
+
+ std::size_t actual =
+ std::fread(static_cast<void*>(junk), 1, num_to_read, file_.get());
+ *num_actually_skipped += static_cast<std::uint64_t>(actual);
+ position_ += static_cast<std::uint64_t>(actual);
+ num_to_skip -= static_cast<std::uint64_t>(actual);
+ } while (actual > 0 && num_to_skip > 0);
+
+ if (*num_actually_skipped == 0) {
+ return Status(Status::kEndOfFile);
+ }
+
+ if (num_to_skip == 0) {
+ return Status(Status::kOkCompleted);
+ } else {
+ return Status(Status::kOkPartial);
+ }
+}
+
+std::uint64_t FileReader::Position() const { return position_; }
+
+} // namespace webm
diff --git a/webm_parser/src/float_parser.cc b/webm_parser/src/float_parser.cc
new file mode 100644
index 0000000..fc46860
--- /dev/null
+++ b/webm_parser/src/float_parser.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/float_parser.h"
+
+#include <cassert>
+#include <cstdint>
+#include <cstring>
+#include <limits>
+
+#include "src/parser_utils.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+FloatParser::FloatParser(double default_value)
+ : default_value_(default_value) {}
+
+Status FloatParser::Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ if (metadata.size == 0) {
+ value_ = default_value_;
+ } else if (metadata.size == 4 || metadata.size == 8) {
+ uint64_value_ = 0;
+ } else {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ num_bytes_remaining_ = static_cast<int>(metadata.size);
+ use_32_bits_ = metadata.size == 4;
+
+ return Status(Status::kOkCompleted);
+}
+
+Status FloatParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ if (num_bytes_remaining_ == 0) {
+ return Status(Status::kOkCompleted);
+ }
+
+ const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader,
+ &uint64_value_, num_bytes_read);
+ num_bytes_remaining_ -= static_cast<int>(*num_bytes_read);
+
+ if (num_bytes_remaining_ == 0) {
+ if (use_32_bits_) {
+ static_assert(std::numeric_limits<float>::is_iec559,
+ "Your compiler does not support 32-bit IEC 559/IEEE 754 "
+ "floating point types");
+ std::uint32_t uint32_value = static_cast<std::uint32_t>(uint64_value_);
+ float float32_value;
+ std::memcpy(&float32_value, &uint32_value, 4);
+ value_ = float32_value;
+ } else {
+ static_assert(std::numeric_limits<double>::is_iec559,
+ "Your compiler does not support 64-bit IEC 559/IEEE 754 "
+ "floating point types");
+ std::memcpy(&value_, &uint64_value_, 8);
+ }
+ }
+
+ return status;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/float_parser.h b/webm_parser/src/float_parser.h
new file mode 100644
index 0000000..1412b54
--- /dev/null
+++ b/webm_parser/src/float_parser.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_FLOAT_PARSER_H_
+#define SRC_FLOAT_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/element_parser.h"
+#include "webm/callback.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses an EBML float from a byte stream.
+class FloatParser : public ElementParser {
+ public:
+ // Constructs a new parser which will use the given default_value as the
+ // value for the element if its size is zero. Defaults to the value zero (as
+ // the EBML spec indicates).
+ explicit FloatParser(double default_value = 0.0);
+
+ FloatParser(FloatParser&&) = default;
+ FloatParser& operator=(FloatParser&&) = default;
+
+ FloatParser(const FloatParser&) = delete;
+ FloatParser& operator=(const FloatParser&) = delete;
+
+ Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ // Gets the parsed float. This must not be called until the parse had been
+ // successfully completed.
+ double value() const {
+ assert(num_bytes_remaining_ == 0);
+ return value_;
+ }
+
+ // Gets the parsed float. This must not be called until the parse had been
+ // successfully completed.
+ double* mutable_value() {
+ assert(num_bytes_remaining_ == 0);
+ return &value_;
+ }
+
+ private:
+ double value_;
+ double default_value_;
+ std::uint64_t uint64_value_;
+ int num_bytes_remaining_ = -1;
+ bool use_32_bits_;
+};
+
+} // namespace webm
+
+#endif // SRC_FLOAT_PARSER_H_
diff --git a/webm_parser/src/id_element_parser.cc b/webm_parser/src/id_element_parser.cc
new file mode 100644
index 0000000..849ad47
--- /dev/null
+++ b/webm_parser/src/id_element_parser.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/id_element_parser.h"
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/element_parser.h"
+#include "src/parser_utils.h"
+#include "webm/callback.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+Status IdElementParser::Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ if (metadata.size == 0 || metadata.size > 4) {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ num_bytes_remaining_ = static_cast<int>(metadata.size);
+ value_ = static_cast<Id>(0);
+
+ return Status(Status::kOkCompleted);
+}
+
+Status IdElementParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader,
+ &value_, num_bytes_read);
+ num_bytes_remaining_ -= static_cast<int>(*num_bytes_read);
+
+ return status;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/id_element_parser.h b/webm_parser/src/id_element_parser.h
new file mode 100644
index 0000000..9b64089
--- /dev/null
+++ b/webm_parser/src/id_element_parser.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_ID_ELEMENT_PARSER_H_
+#define SRC_ID_ELEMENT_PARSER_H_
+
+#include <cstdint>
+
+#include "src/element_parser.h"
+#include "webm/callback.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+class IdElementParser : public ElementParser {
+ public:
+ IdElementParser() = default;
+
+ IdElementParser(IdElementParser&&) = default;
+ IdElementParser& operator=(IdElementParser&&) = default;
+
+ IdElementParser(const IdElementParser&) = delete;
+ IdElementParser& operator=(const IdElementParser&) = delete;
+
+ Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ // Gets the parsed Id. This must not be called until the parse had been
+ // successfully completed.
+ Id value() const {
+ assert(num_bytes_remaining_ == 0);
+ return value_;
+ }
+
+ // Gets the parsed Id. This must not be called until the parse had been
+ // successfully completed.
+ Id* mutable_value() {
+ assert(num_bytes_remaining_ == 0);
+ return &value_;
+ }
+
+ private:
+ Id value_;
+ int num_bytes_remaining_ = -1;
+};
+
+} // namespace webm
+
+#endif // SRC_ID_ELEMENT_PARSER_H_
diff --git a/webm_parser/src/id_parser.cc b/webm_parser/src/id_parser.cc
new file mode 100644
index 0000000..292a13c
--- /dev/null
+++ b/webm_parser/src/id_parser.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/id_parser.h"
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/bit_utils.h"
+#include "src/parser_utils.h"
+#include "webm/id.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+Status IdParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+ assert(num_bytes_remaining_ != 0);
+
+ *num_bytes_read = 0;
+
+ // Spec references:
+ // http://matroska.org/technical/specs/index.html#EBML_ex
+ // https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#variable-size-integer
+ // https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#element-id
+
+ // IDs are encoded like so (big-endian):
+ // 0b1xxx xxxx
+ // 0b01xx xxxx xxxx xxxx
+ // 0b001x xxxx xxxx xxxx xxxx xxxx
+ // 0b0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx
+
+ if (num_bytes_remaining_ == -1) {
+ std::uint8_t first_byte;
+ const Status status = ReadByte(reader, &first_byte);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ ++*num_bytes_read;
+
+ // The marker bit is the first 1-bit. It indicates the length of the ID.
+ // If there is no marker bit in the first half-octet, this isn't a valid
+ // ID, since IDs can't be more than 4 octets in MKV/WebM.
+ if (!(first_byte & 0xf0)) {
+ return Status(Status::kInvalidElementId);
+ }
+
+ num_bytes_remaining_ = CountLeadingZeros(first_byte);
+
+ id_ = static_cast<Id>(first_byte);
+ }
+
+ std::uint64_t local_num_bytes_read;
+ const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader,
+ &id_, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ num_bytes_remaining_ -= static_cast<int>(local_num_bytes_read);
+
+ return status;
+}
+
+Id IdParser::id() const {
+ assert(num_bytes_remaining_ == 0);
+ return id_;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/id_parser.h b/webm_parser/src/id_parser.h
new file mode 100644
index 0000000..7594094
--- /dev/null
+++ b/webm_parser/src/id_parser.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_ID_PARSER_H_
+#define SRC_ID_PARSER_H_
+
+#include <cstdint>
+
+#include "src/parser.h"
+#include "webm/callback.h"
+#include "webm/id.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses an EBML ID from a byte stream.
+class IdParser : public Parser {
+ public:
+ IdParser() = default;
+ IdParser(IdParser&&) = default;
+ IdParser& operator=(IdParser&&) = default;
+
+ IdParser(const IdParser&) = delete;
+ IdParser& operator=(const IdParser&) = delete;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ // Gets the parsed ID. This must not be called until the parse had been
+ // successfully completed.
+ Id id() const;
+
+ private:
+ int num_bytes_remaining_ = -1;
+ Id id_;
+};
+
+} // namespace webm
+
+#endif // SRC_ID_PARSER_H_
diff --git a/webm_parser/src/info_parser.h b/webm_parser/src/info_parser.h
new file mode 100644
index 0000000..085c8ad
--- /dev/null
+++ b/webm_parser/src/info_parser.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_INFO_PARSER_H_
+#define SRC_INFO_PARSER_H_
+
+#include "src/byte_parser.h"
+#include "src/date_parser.h"
+#include "src/float_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Info
+// http://www.webmproject.org/docs/container/#Info
+class InfoParser : public MasterValueParser<Info> {
+ public:
+ InfoParser()
+ : MasterValueParser<Info>(
+ MakeChild<UnsignedIntParser>(Id::kTimecodeScale,
+ &Info::timecode_scale),
+ MakeChild<FloatParser>(Id::kDuration, &Info::duration),
+ MakeChild<DateParser>(Id::kDateUtc, &Info::date_utc),
+ MakeChild<StringParser>(Id::kTitle, &Info::title),
+ MakeChild<StringParser>(Id::kMuxingApp, &Info::muxing_app),
+ MakeChild<StringParser>(Id::kWritingApp, &Info::writing_app)) {}
+
+ protected:
+ Status OnParseCompleted(Callback* callback) override {
+ return callback->OnInfo(metadata(Id::kInfo), value());
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_INFO_PARSER_H_
diff --git a/webm_parser/src/int_parser.h b/webm_parser/src/int_parser.h
new file mode 100644
index 0000000..f0ed392
--- /dev/null
+++ b/webm_parser/src/int_parser.h
@@ -0,0 +1,121 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_INT_PARSER_H_
+#define SRC_INT_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+
+#include "src/element_parser.h"
+#include "src/parser_utils.h"
+#include "webm/callback.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses an EBML signed/unsigned int from a byte stream.
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#EBML_ex
+// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#element-data-size
+// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#ebml-element-types
+template <typename T>
+class IntParser : public ElementParser {
+ public:
+ static_assert(
+ std::is_same<T, std::int64_t>::value ||
+ std::is_same<T, std::uint64_t>::value ||
+ (std::is_enum<T>::value && sizeof(T) == 8),
+ "T must be either std::int64_t, std::uint64_t, or a 64-bit enum");
+
+ // Constructs a new parser which will use the given default_value as the
+ // value for the element if its size is zero. Defaults to the value zero (as
+ // the EBML spec indicates).
+ explicit IntParser(T default_value = {}) : default_value_(default_value) {}
+
+ IntParser(IntParser&&) = default;
+ IntParser& operator=(IntParser&&) = default;
+
+ IntParser(const IntParser&) = delete;
+ IntParser& operator=(const IntParser&) = delete;
+
+ Status Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) override {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ // Matroska requires integers to be 0-8 bytes in size.
+ if (metadata.size > 8) {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ size_ = num_bytes_remaining_ = static_cast<int>(metadata.size);
+
+ if (metadata.size == 0) {
+ value_ = default_value_;
+ } else {
+ value_ = {};
+ }
+
+ return Status(Status::kOkCompleted);
+ }
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader,
+ &value_, num_bytes_read);
+ num_bytes_remaining_ -= static_cast<int>(*num_bytes_read);
+
+ // Sign extend the integer if it's a negative value. EBML allows for
+ // negative integers to drop superfluous sign bytes (i.e. -1 can be encoded
+ // as 0xFF instead of 0xFFFFFFFFFFFFFFFF).
+ if (std::is_signed<T>::value && num_bytes_remaining_ == 0 && size_ > 0) {
+ std::uint64_t sign_bits = std::numeric_limits<std::uint64_t>::max()
+ << (8 * size_ - 1);
+ std::uint64_t unsigned_value = static_cast<std::uint64_t>(value_);
+ if (unsigned_value & sign_bits) {
+ value_ = static_cast<T>(unsigned_value | sign_bits);
+ }
+ }
+
+ return status;
+ }
+
+ // Gets the parsed int. This must not be called until the parse had been
+ // successfully completed.
+ T value() const {
+ assert(num_bytes_remaining_ == 0);
+ return value_;
+ }
+
+ // Gets the parsed int. This must not be called until the parse had been
+ // successfully completed.
+ T* mutable_value() {
+ assert(num_bytes_remaining_ == 0);
+ return &value_;
+ }
+
+ private:
+ T value_;
+ T default_value_;
+ int num_bytes_remaining_ = -1;
+ int size_;
+};
+
+using SignedIntParser = IntParser<std::int64_t>;
+using UnsignedIntParser = IntParser<std::uint64_t>;
+
+} // namespace webm
+
+#endif // SRC_INT_PARSER_H_
diff --git a/webm_parser/src/istream_reader.cc b/webm_parser/src/istream_reader.cc
new file mode 100644
index 0000000..6993fc0
--- /dev/null
+++ b/webm_parser/src/istream_reader.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "webm/istream_reader.h"
+
+#include <cassert>
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <limits>
+#include <memory>
+
+#include "webm/status.h"
+
+namespace webm {
+
+IstreamReader::IstreamReader(IstreamReader&& other)
+ : istream_(std::move(other.istream_)), position_(other.position_) {
+ other.position_ = 0;
+}
+
+IstreamReader& IstreamReader::operator=(IstreamReader&& other) {
+ if (this != &other) {
+ istream_ = std::move(other.istream_);
+ position_ = other.position_;
+ other.position_ = 0;
+ }
+ return *this;
+}
+
+Status IstreamReader::Read(std::size_t num_to_read, std::uint8_t* buffer,
+ std::uint64_t* num_actually_read) {
+ assert(num_to_read > 0);
+ assert(buffer != nullptr);
+ assert(num_actually_read != nullptr);
+
+ if (istream_ == nullptr) {
+ *num_actually_read = 0;
+ return Status(Status::kEndOfFile);
+ }
+
+ using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
+ constexpr std::streamsize streamsize_max =
+ std::numeric_limits<std::streamsize>::max();
+ std::streamsize limited_num_to_read;
+ if (num_to_read > static_cast<unsigned_streamsize>(streamsize_max)) {
+ limited_num_to_read = streamsize_max;
+ } else {
+ limited_num_to_read = static_cast<std::streamsize>(num_to_read);
+ }
+
+ istream_->read(reinterpret_cast<char*>(buffer), limited_num_to_read);
+ std::streamsize actual = istream_->gcount();
+ *num_actually_read = static_cast<std::uint64_t>(actual);
+ position_ += *num_actually_read;
+
+ if (actual == 0) {
+ return Status(Status::kEndOfFile);
+ }
+
+ if (static_cast<std::size_t>(actual) == num_to_read) {
+ return Status(Status::kOkCompleted);
+ } else {
+ return Status(Status::kOkPartial);
+ }
+}
+
+Status IstreamReader::Skip(std::uint64_t num_to_skip,
+ std::uint64_t* num_actually_skipped) {
+ assert(num_to_skip > 0);
+ assert(num_actually_skipped != nullptr);
+
+ *num_actually_skipped = 0;
+ if (istream_ == nullptr || !istream_->good()) {
+ return Status(Status::kEndOfFile);
+ }
+
+ // Try seeking forward first.
+ using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
+ constexpr std::streamsize streamsize_max =
+ std::numeric_limits<std::streamsize>::max();
+ std::streamsize seek_offset;
+ if (num_to_skip > static_cast<unsigned_streamsize>(streamsize_max)) {
+ seek_offset = streamsize_max;
+ } else {
+ seek_offset = static_cast<std::streamsize>(num_to_skip);
+ }
+ if (istream_->seekg(seek_offset, std::ios_base::cur)) {
+ *num_actually_skipped = static_cast<std::uint64_t>(seek_offset);
+ position_ += static_cast<std::uint64_t>(seek_offset);
+ if (static_cast<std::uint64_t>(seek_offset) == num_to_skip) {
+ return Status(Status::kOkCompleted);
+ } else {
+ return Status(Status::kOkPartial);
+ }
+ }
+ istream_->clear();
+
+ // Seeking doesn't work on things like pipes, so if seeking failed then fall
+ // back to reading the data into a junk buffer.
+ std::size_t actual = 0;
+ do {
+ char junk[1024];
+ std::streamsize num_to_read = static_cast<std::streamsize>(sizeof(junk));
+ if (num_to_skip < static_cast<std::uint64_t>(num_to_read)) {
+ num_to_read = static_cast<std::streamsize>(num_to_skip);
+ }
+
+ istream_->read(junk, num_to_read);
+ std::streamsize actual = istream_->gcount();
+ *num_actually_skipped += static_cast<std::uint64_t>(actual);
+ position_ += static_cast<std::uint64_t>(actual);
+ num_to_skip -= static_cast<std::uint64_t>(actual);
+ } while (actual > 0 && num_to_skip > 0);
+
+ if (*num_actually_skipped == 0) {
+ return Status(Status::kEndOfFile);
+ }
+
+ if (num_to_skip == 0) {
+ return Status(Status::kOkCompleted);
+ } else {
+ return Status(Status::kOkPartial);
+ }
+}
+
+std::uint64_t IstreamReader::Position() const { return position_; }
+
+} // namespace webm
diff --git a/webm_parser/src/master_parser.cc b/webm_parser/src/master_parser.cc
new file mode 100644
index 0000000..d8c3dba
--- /dev/null
+++ b/webm_parser/src/master_parser.cc
@@ -0,0 +1,298 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/master_parser.h"
+
+#include <cassert>
+#include <cstdint>
+#include <limits>
+
+#include "src/element_parser.h"
+#include "src/skip_callback.h"
+#include "webm/element.h"
+#include "webm/id.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#EBML_ex
+// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown
+Status MasterParser::Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ InitSetup(metadata.header_size, metadata.size, metadata.position);
+
+ if (metadata.size != kUnknownElementSize) {
+ max_size_ = metadata.size;
+ } else {
+ max_size_ = max_size;
+ }
+
+ if (metadata.size == 0) {
+ state_ = State::kEndReached;
+ } else {
+ state_ = State::kFirstReadOfChildId;
+ }
+
+ return Status(Status::kOkCompleted);
+}
+
+void MasterParser::InitAfterSeek(const Ancestory& child_ancestory,
+ const ElementMetadata& child_metadata) {
+ InitSetup(kUnknownHeaderSize, kUnknownElementSize, kUnknownElementPosition);
+ max_size_ = std::numeric_limits<std::uint64_t>::max();
+
+ if (child_ancestory.empty()) {
+ child_metadata_ = child_metadata;
+ auto iter = parsers_.find(child_metadata_.id);
+ assert(iter != parsers_.end());
+ child_parser_ = iter->second.get();
+ state_ = State::kGettingAction;
+ } else {
+ child_metadata_.id = child_ancestory.id();
+ child_metadata_.header_size = kUnknownHeaderSize;
+ child_metadata_.size = kUnknownElementSize;
+ child_metadata_.position = kUnknownElementPosition;
+
+ auto iter = parsers_.find(child_metadata_.id);
+ assert(iter != parsers_.end());
+ child_parser_ = iter->second.get();
+ child_parser_->InitAfterSeek(child_ancestory.next(), child_metadata);
+ state_ = State::kReadingChildBody;
+ }
+}
+
+Status MasterParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ *num_bytes_read = 0;
+
+ Callback* const original_callback = callback;
+
+ SkipCallback skip_callback;
+ if (action_ == Action::kSkip) {
+ callback = &skip_callback;
+ }
+
+ Status status;
+ std::uint64_t local_num_bytes_read;
+ while (true) {
+ switch (state_) {
+ case State::kFirstReadOfChildId: {
+ // This separate case for the first read of the child ID is needed to
+ // avoid potential bugs where calling Feed() twice in a row on an
+ // unsized element at the end of the stream would return
+ // Status::kOkCompleted instead of Status::kEndOfFile (since we convert
+ // Status::kEndOfFile to Status::kOkCompleted when EOF is hit for an
+ // unsized element after its children have been fully parsed). Once
+ // the ID parser consumes > 0 bytes, this state must be exited.
+ assert(child_parser_ == nullptr);
+ assert(my_size_ == kUnknownElementSize || total_bytes_read_ < my_size_);
+ child_metadata_.position = reader->Position();
+ child_metadata_.header_size = 0;
+ status = id_parser_.Feed(callback, reader, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ total_bytes_read_ += local_num_bytes_read;
+ child_metadata_.header_size +=
+ static_cast<std::uint32_t>(local_num_bytes_read);
+ if (status.code == Status::kEndOfFile &&
+ my_size_ == kUnknownElementSize && local_num_bytes_read == 0) {
+ state_ = State::kEndReached;
+ } else if (!status.ok()) {
+ if (local_num_bytes_read > 0) {
+ state_ = State::kFinishingReadingChildId;
+ }
+ return status;
+ } else if (status.completed_ok()) {
+ state_ = State::kReadingChildSize;
+ } else {
+ state_ = State::kFinishingReadingChildId;
+ }
+ continue;
+ }
+
+ case State::kFinishingReadingChildId: {
+ assert(child_parser_ == nullptr);
+ assert(my_size_ == kUnknownElementSize || total_bytes_read_ < my_size_);
+ status = id_parser_.Feed(callback, reader, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ total_bytes_read_ += local_num_bytes_read;
+ child_metadata_.header_size +=
+ static_cast<std::uint32_t>(local_num_bytes_read);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ state_ = State::kReadingChildSize;
+ continue;
+ }
+
+ case State::kReadingChildSize: {
+ assert(child_parser_ == nullptr);
+ assert(total_bytes_read_ > 0);
+ status = size_parser_.Feed(callback, reader, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ total_bytes_read_ += local_num_bytes_read;
+ child_metadata_.header_size +=
+ static_cast<std::uint32_t>(local_num_bytes_read);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ child_metadata_.id = id_parser_.id();
+ child_metadata_.size = size_parser_.size();
+ state_ = State::kValidatingChildSize;
+ continue;
+ }
+
+ case State::kValidatingChildSize: {
+ assert(child_parser_ == nullptr);
+
+ std::uint64_t byte_count = total_bytes_read_;
+ if (child_metadata_.size != kUnknownElementSize) {
+ byte_count += child_metadata_.size;
+ }
+
+ std::uint64_t byte_cap = max_size_;
+ // my_size_ is <= max_size_ if it's known, so pick the smaller value.
+ if (my_size_ != kUnknownElementSize) {
+ byte_cap = my_size_;
+ }
+
+ if (byte_count > byte_cap) {
+ return Status(Status::kElementOverflow);
+ }
+
+ auto iter = parsers_.find(child_metadata_.id);
+ bool unknown_child = iter == parsers_.end();
+
+ if (my_size_ == kUnknownElementSize && unknown_child) {
+ // The end of an unsized master element is considered to be the first
+ // instance of an element that isn't a known/valid child element.
+ has_cached_metadata_ = true;
+ state_ = State::kEndReached;
+ continue;
+ } else if (unknown_child &&
+ child_metadata_.size == kUnknownElementSize) {
+ // We can't skip or otherwise handle unknown elements with an unknown
+ // size.
+ return Status(Status::kIndefiniteUnknownElement);
+ }
+ if (unknown_child) {
+ child_parser_ = &unknown_parser_;
+ } else {
+ child_parser_ = iter->second.get();
+ }
+ state_ = State::kGettingAction;
+ continue;
+ }
+
+ case State::kGettingAction: {
+ assert(child_parser_ != nullptr);
+ status = callback->OnElementBegin(child_metadata_, &action_);
+ if (!status.completed_ok()) {
+ return status;
+ }
+
+ if (action_ == Action::kSkip) {
+ callback = &skip_callback;
+ if (child_metadata_.size != kUnknownElementSize) {
+ child_parser_ = &skip_parser_;
+ }
+ }
+ state_ = State::kInitializingChildParser;
+ continue;
+ }
+
+ case State::kInitializingChildParser: {
+ assert(child_parser_ != nullptr);
+ status =
+ child_parser_->Init(child_metadata_, max_size_ - total_bytes_read_);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ state_ = State::kReadingChildBody;
+ continue;
+ }
+
+ case State::kReadingChildBody: {
+ assert(child_parser_ != nullptr);
+ status = child_parser_->Feed(callback, reader, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ total_bytes_read_ += local_num_bytes_read;
+ if (!status.completed_ok()) {
+ return status;
+ }
+ state_ = State::kChildFullyParsed;
+ continue;
+ }
+
+ case State::kChildFullyParsed: {
+ assert(child_parser_ != nullptr);
+ std::uint64_t byte_cap = max_size_;
+ // my_size_ is <= max_size_ if it's known, so pick the smaller value.
+ if (my_size_ != kUnknownElementSize) {
+ byte_cap = my_size_;
+ }
+
+ if (total_bytes_read_ > byte_cap) {
+ return Status(Status::kElementOverflow);
+ } else if (total_bytes_read_ == byte_cap) {
+ state_ = State::kEndReached;
+ continue;
+ }
+
+ if (child_parser_->GetCachedMetadata(&child_metadata_)) {
+ state_ = State::kValidatingChildSize;
+ } else {
+ state_ = State::kFirstReadOfChildId;
+ }
+ PrepareForNextChild();
+ callback = original_callback;
+ continue;
+ }
+
+ case State::kEndReached: {
+ return Status(Status::kOkCompleted);
+ }
+ }
+ }
+}
+
+bool MasterParser::GetCachedMetadata(ElementMetadata* metadata) {
+ assert(metadata != nullptr);
+
+ if (has_cached_metadata_) {
+ *metadata = child_metadata_;
+ }
+ return has_cached_metadata_;
+}
+
+void MasterParser::InitSetup(std::uint32_t header_size,
+ std::uint64_t size_in_bytes,
+ std::uint64_t position) {
+ PrepareForNextChild();
+ header_size_ = header_size;
+ my_size_ = size_in_bytes;
+ my_position_ = position;
+ total_bytes_read_ = 0;
+ has_cached_metadata_ = false;
+}
+
+void MasterParser::PrepareForNextChild() {
+ // Do not reset child_metadata_ here.
+ id_parser_ = {};
+ size_parser_ = {};
+ child_parser_ = nullptr;
+ action_ = Action::kRead;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/master_parser.h b/webm_parser/src/master_parser.h
new file mode 100644
index 0000000..2000167
--- /dev/null
+++ b/webm_parser/src/master_parser.h
@@ -0,0 +1,227 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_MASTER_PARSER_H_
+#define SRC_MASTER_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <type_traits>
+#include <unordered_map>
+#include <utility>
+
+#include "src/element_parser.h"
+#include "src/id_parser.h"
+#include "src/size_parser.h"
+#include "src/skip_parser.h"
+#include "src/unknown_parser.h"
+#include "src/void_parser.h"
+#include "webm/callback.h"
+#include "webm/element.h"
+#include "webm/id.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// A general purpose parser for EBML master elements.
+//
+// For example, if a document specification defines a Foo master element that
+// has two boolean children (Bar and Baz), then a FooParser capable of parsing
+// the Foo master element could be defined as follows:
+//
+// struct FooParser : public MasterParser {
+// FooParser()
+// : MasterParser(MakeChild<BoolParser>(Id::kBar),
+// MakeChild<BoolParser>(Id::kBaz)) {}
+// };
+//
+// See the MasterValueParser for an alternative class for parsing master
+// elements into a data structure.
+class MasterParser : public ElementParser {
+ public:
+ // Constructs a new MasterParser that uses the given
+ // {Id, std::unique_ptr<ElementParser>} pairs to map child IDs to the
+ // appropriate parser/handler. Each argument must be of type
+ // std::pair<Id, std::unique_ptr<ElementParser>>. If a parser is not
+ // explicitly provided for Id::kVoid, a VoidParser will automatically be used
+ // for it.
+ //
+ // Initializer lists don't support move-only types (i.e. std::unique_ptr), so
+ // instead a variadic template is used.
+ template <typename... T>
+ explicit MasterParser(T&&... parser_pairs) {
+ // Prefer an odd reserve size. This makes libc++ use a prime number for the
+ // bucket count. Otherwise, if it happens to be a power of 2, then libc++
+ // will use a power-of-2 bucket count (and since Matroska EBML IDs have low
+ // entropy in the low bits, there will be a lot of collisions). libstdc++
+ // always prefers a prime bucket count. I'm not sure how MSVC or others are
+ // implemented, but this shouldn't adversely affect them even if they are
+ // implemented differently. Add one to the count because we'll likely need
+ // to insert a parser for Id::kVoid.
+ parsers_.reserve((sizeof...(T) + 1) | 1);
+
+ // This dummy initializer list is just used to force the parameter pack to
+ // be expanded, which turns the expression into a for-each "loop" that
+ // inserts each argument into the map.
+ auto dummy = {0, (InsertParser(std::forward<T>(parser_pairs)), 0)...};
+ (void)dummy; // Silence unused variable warning.
+
+ if (parsers_.find(Id::kVoid) == parsers_.end()) {
+ InsertParser(MakeChild<VoidParser>(Id::kVoid));
+ }
+ }
+
+ MasterParser(const MasterParser&) = delete;
+ MasterParser& operator=(const MasterParser&) = delete;
+
+ Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override;
+
+ void InitAfterSeek(const Ancestory& child_ancestory,
+ const ElementMetadata& child_metadata) override;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ bool GetCachedMetadata(ElementMetadata* metadata) override;
+
+ std::uint32_t header_size() const { return header_size_; }
+
+ // Gets the size of this element. May be called before the parse is fully
+ // complete (but only after Init() has already been called and successfully
+ // returned).
+ std::uint64_t size() const { return my_size_; }
+
+ // Gets absolute byte position of the start of the element in the byte stream.
+ // May be called before the parse is fully complete (but only after Init() has
+ // already been called and successfully returned).
+ std::uint64_t position() const { return my_position_; }
+
+ // Gets the metadata for the child that is currently being parsed. This may
+ // only be called while the child's body (not its header information like ID
+ // and size) is being parsed.
+ const ElementMetadata& child_metadata() const {
+ assert(state_ == State::kValidatingChildSize ||
+ state_ == State::kGettingAction ||
+ state_ == State::kInitializingChildParser ||
+ state_ == State::kReadingChildBody);
+ return child_metadata_;
+ }
+
+ protected:
+ // Allocates a new parser of type T, forwarding args to the constructor, and
+ // creates a std::pair<Id, std::unique_ptr<ElementParser>> using the given id
+ // and the allocated parser.
+ template <typename T, typename... Args>
+ static std::pair<Id, std::unique_ptr<ElementParser>> MakeChild(
+ Id id, Args&&... args) {
+ std::unique_ptr<ElementParser> ptr(new T(std::forward<Args>(args)...));
+ return std::pair<Id, std::unique_ptr<ElementParser>>(id, std::move(ptr));
+ }
+
+ private:
+ // Parsing states for the finite-state machine.
+ enum class State {
+ /* clang-format off */
+ // State Transitions to state When
+ kFirstReadOfChildId, // kFinishingReadingChildId size(id) > 1
+ // kReadingChildSize size(id) == 1
+ // kEndReached EOF
+ kFinishingReadingChildId, // kReadingChildSize done
+ kReadingChildSize, // kValidatingChildSize done
+ kValidatingChildSize, // kGettingAction done
+ // kEndReached unknown id & unsized
+ kGettingAction, // kInitializingChildParser done
+ kInitializingChildParser, // kReadingChildBody done
+ kReadingChildBody, // kChildFullyParsed child parse done
+ kChildFullyParsed, // kValidatingChildSize cached metadata
+ // kFirstReadOfChildId read < my_size_
+ // kEndReached read == my_size_
+ kEndReached, // No transitions from here (must call Init)
+ /* clang-format on */
+ };
+
+ using StdHashId = std::hash<std::underlying_type<Id>::type>;
+
+ // Hash functor for hashing Id enums for storage in std::unordered_map.
+ struct IdHash : StdHashId {
+ // Type aliases for conforming to the std::hash interface.
+ using argument_type = Id;
+ using result_type = StdHashId::result_type;
+
+ // Returns the hash of the given id.
+ result_type operator()(argument_type id) const {
+ return StdHashId::operator()(static_cast<StdHashId::argument_type>(id));
+ }
+ };
+
+ // The parser for parsing element Ids.
+ IdParser id_parser_;
+
+ // The parser for parsing element sizes.
+ SizeParser size_parser_;
+
+ // Metadata for the child element that is currently being parsed.
+ ElementMetadata child_metadata_;
+
+ // Maps child IDs to the appropriate parser that can handle that child.
+ std::unordered_map<Id, std::unique_ptr<ElementParser>, IdHash> parsers_;
+
+ // The parser that is used to parse unknown children.
+ UnknownParser unknown_parser_;
+
+ // The parser that is used to skip over children.
+ SkipParser skip_parser_;
+
+ // The parser that is being used to parse the current child. This must be null
+ // or a pointer in parsers_.
+ ElementParser* child_parser_;
+
+ // The current parsing action for the child that is currently being parsed.
+ Action action_ = Action::kRead;
+
+ // The current state of the parser.
+ State state_;
+
+ std::uint32_t header_size_;
+
+ // The size of this element.
+ std::uint64_t my_size_;
+
+ std::uint64_t my_position_;
+
+ std::uint64_t max_size_;
+
+ // The total number of bytes read by this parser.
+ std::uint64_t total_bytes_read_;
+
+ // Set to true if parsing has completed and this parser consumed an extra
+ // element header (ID and size) that wasn't from a child.
+ bool has_cached_metadata_ = false;
+
+ // Inserts the parser into the parsers_ map and asserts it is the only parser
+ // registers to parse the corresponding Id.
+ template <typename T>
+ void InsertParser(T&& parser) {
+ bool inserted = parsers_.insert(std::forward<T>(parser)).second;
+ (void)inserted; // Silence unused variable warning.
+ assert(inserted); // Make sure there aren't duplicates.
+ }
+
+ // Common initialization logic for Init/InitAfterseek.
+ void InitSetup(std::uint32_t header_size, std::uint64_t size_in_bytes,
+ std::uint64_t position);
+
+ // Resets the internal parsers in preparation for parsing the next child.
+ void PrepareForNextChild();
+};
+
+} // namespace webm
+
+#endif // SRC_MASTER_PARSER_H_
diff --git a/webm_parser/src/master_value_parser.h b/webm_parser/src/master_value_parser.h
new file mode 100644
index 0000000..2a02a3f
--- /dev/null
+++ b/webm_parser/src/master_value_parser.h
@@ -0,0 +1,533 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_MASTER_VALUE_PARSER_H_
+#define SRC_MASTER_VALUE_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "src/master_parser.h"
+#include "src/recursive_parser.h"
+#include "src/skip_callback.h"
+#include "webm/callback.h"
+#include "webm/element.h"
+#include "webm/id.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses Master elements from an EBML stream, storing child values in a
+// structure of type T. This class differs from MasterParser in that
+// MasterParser does not collect the parsed data into an object that can then be
+// retrieved.
+//
+// For example, consider the following Foo object, which represents a master
+// element that contains two booleans:
+//
+// struct Foo {
+// Element<bool> bar;
+// Element<bool> baz;
+// };
+//
+// A FooParser implemented via MasterParser, like below, could be used to parse
+// the master element and the boolean children, but the boolean values could not
+// be retrieved from the parser.
+//
+// struct FooParser : public MasterParser {
+// FooParser()
+// : MasterParser({Id::kBar, new BoolParser}, // std::pair<*> types
+// {Id::kBaz, new BoolParser}) {} // omitted for brevity.
+// };
+//
+// However, if FooParser is implemented via MasterValueParser<Foo>, then the
+// boolean values will be parsed into a Foo object that can be retrieved from
+// FooParser via its value() and mutable_value() methods.
+//
+// struct FooParser : public MasterValueParser<Foo> {
+// FooParser()
+// : MasterValueParser(MakeChild<BoolParser>(Id::kBar, &Foo::bar),
+// MakeChild<BoolParser>(Id::kBaz, &Foo::baz)) {}
+// };
+template <typename T>
+class MasterValueParser : public ElementParser {
+ public:
+ Status Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) override {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ PreInit();
+
+ const Status status = master_parser_.Init(metadata, max_size);
+ if (!status.completed_ok()) {
+ return status;
+ }
+
+ return status;
+ }
+
+ void InitAfterSeek(const Ancestory& child_ancestory,
+ const ElementMetadata& child_metadata) override {
+ PreInit();
+ started_done_ = true;
+ master_parser_.InitAfterSeek(child_ancestory, child_metadata);
+ }
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ *num_bytes_read = 0;
+
+ if (!parse_complete_) {
+ // TODO(mjbshaw): just call Reader::Skip if element's size is known and
+ // action is skip.
+ SkipCallback skip_callback;
+ if (action_ == Action::kSkip) {
+ callback = &skip_callback;
+ }
+
+ Status status = master_parser_.Feed(callback, reader, num_bytes_read);
+ // Check if we've artificially injected an error code, and if so, switch
+ // into skipping mode.
+ if (status.code == Status::kSwitchToSkip) {
+ assert(started_done_);
+ assert(action_ == Action::kSkip);
+ callback = &skip_callback;
+ std::uint64_t local_num_bytes_read;
+ status = master_parser_.Feed(callback, reader, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ }
+ if (!status.completed_ok()) {
+ return status;
+ }
+ parse_complete_ = true;
+ }
+
+ if (!started_done_) {
+ Status status = OnParseStarted(callback, &action_);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ started_done_ = true;
+ }
+
+ if (action_ != Action::kSkip) {
+ return OnParseCompleted(callback);
+ }
+
+ return Status(Status::kOkCompleted);
+ }
+
+ bool GetCachedMetadata(ElementMetadata* metadata) override {
+ return master_parser_.GetCachedMetadata(metadata);
+ }
+
+ bool WasSkipped() const override { return action_ == Action::kSkip; }
+
+ const T& value() const { return value_; }
+
+ T* mutable_value() { return &value_; }
+
+ protected:
+ // Users and subclasses are not meant to use the Tag* classes; they're an
+ // internal implementation detail.
+ // A tag that will cause the internal ChildParser to use the parsed child as a
+ // start event.
+ struct TagUseAsStart {};
+ // A tag that will cause the internal ChildParser to call OnChildParsed once
+ // it has been fully parsed.
+ struct TagNotifyOnParseComplete {};
+
+ // A factory that will create a std::pair<Id, std::unique_ptr<ElementParser>>.
+ // Users and subclasses are not meant to use this class directly, as it is an
+ // internal implementation detail of this class. Subclasses should use
+ // MakeChild instead of using this class directly.
+ template <typename Parser, typename Value, typename... Tags>
+ class SingleChildFactory {
+ public:
+ constexpr SingleChildFactory(Id id, Element<Value> T::*member)
+ : id_(id), member_(member) {}
+
+ // Builds a std::pair<Id, std::unique_ptr<ElementParser>>. The parent
+ // pointer must be a pointer to the MasterValueParser that is being
+ // constructed. The given value pointer must be the pointer to the fully
+ // constructed MasterValueParser::value_ object.
+ std::pair<Id, std::unique_ptr<ElementParser>> BuildParser(
+ MasterValueParser* parent, T* value) {
+ assert(parent != nullptr);
+ assert(value != nullptr);
+
+ Element<Value>* child_member = &(value->*member_);
+ auto lambda = [child_member](Parser* parser) {
+ child_member->Set(std::move(*parser->mutable_value()), true);
+ };
+ return {id_, MakeChildParser<Parser, Value, Tags...>(
+ parent, std::move(lambda), child_member)};
+ }
+
+ // If called, OnParseStarted will be called on the parent element when this
+ // particular element is encountered.
+ constexpr SingleChildFactory<Parser, Value, TagUseAsStart, Tags...>
+ UseAsStartEvent() const {
+ return {id_, member_};
+ }
+
+ // If called, OnChildParsed will be called on the parent element when this
+ // particular element is fully parsed.
+ constexpr SingleChildFactory<Parser, Value, TagNotifyOnParseComplete,
+ Tags...>
+ NotifyOnParseComplete() const {
+ return {id_, member_};
+ }
+
+ private:
+ Id id_;
+ Element<Value> T::*member_;
+ };
+
+ template <typename Parser, typename Value, typename... Tags>
+ class RepeatedChildFactory {
+ public:
+ constexpr RepeatedChildFactory(Id id,
+ std::vector<Element<Value>> T::*member)
+ : id_(id), member_(member) {}
+
+ // Builds a std::pair<Id, std::unique_ptr<ElementParser>>. The parent
+ // pointer must be a pointer to the MasterValueParser that is being
+ // constructed. The given value pointer must be the pointer to the fully
+ // constructed MasterValueParser::value_ object.
+ std::pair<Id, std::unique_ptr<ElementParser>> BuildParser(
+ MasterValueParser* parent, T* value) {
+ assert(parent != nullptr);
+ assert(value != nullptr);
+
+ std::vector<Element<Value>>* child_member = &(value->*member_);
+ auto lambda = [child_member](Parser* parser) {
+ if (child_member->size() == 1 && !child_member->front().is_present()) {
+ child_member->clear();
+ }
+ child_member->emplace_back(std::move(*parser->mutable_value()), true);
+ };
+ return {id_, MakeChildParser<Parser, Value, Tags...>(
+ parent, std::move(lambda), child_member)};
+ }
+
+ // If called, OnParseStarted will be called on the parent element when this
+ // particular element is encountered.
+ constexpr RepeatedChildFactory<Parser, Value, TagUseAsStart, Tags...>
+ UseAsStartEvent() const {
+ return {id_, member_};
+ }
+
+ // If called, OnChildParsed will be called on the parent element when this
+ // particular element is fully parsed.
+ constexpr RepeatedChildFactory<Parser, Value, TagNotifyOnParseComplete,
+ Tags...>
+ NotifyOnParseComplete() const {
+ return {id_, member_};
+ }
+
+ private:
+ Id id_;
+ std::vector<Element<Value>> T::*member_;
+ };
+
+ template <typename Parser, typename... Tags>
+ class RecursiveChildFactory {
+ public:
+ constexpr RecursiveChildFactory(Id id, std::vector<Element<T>> T::*member,
+ std::size_t max_recursion_depth)
+ : id_(id), member_(member), max_recursion_depth_(max_recursion_depth) {}
+
+ // Builds a std::pair<Id, std::unique_ptr<ElementParser>>. The parent
+ // pointer must be a pointer to the MasterValueParser that is being
+ // constructed. The given value pointer must be the pointer to the fully
+ // constructed MasterValueParser::value_ object.
+ std::pair<Id, std::unique_ptr<ElementParser>> BuildParser(
+ MasterValueParser* parent, T* value) {
+ assert(parent != nullptr);
+ assert(value != nullptr);
+
+ std::vector<Element<T>>* child_member = &(value->*member_);
+ auto lambda = [child_member](RecursiveParser<Parser>* parser) {
+ if (child_member->size() == 1 && !child_member->front().is_present()) {
+ child_member->clear();
+ }
+ child_member->emplace_back(std::move(*parser->mutable_value()), true);
+ };
+
+ return {id_, std::unique_ptr<ElementParser>(
+ new ChildParser<RecursiveParser<Parser>,
+ decltype(lambda), Tags...>(
+ parent, std::move(lambda), max_recursion_depth_))};
+ }
+
+ // If called, OnParseStarted will be called on the parent element when this
+ // particular element is encountered.
+ constexpr RecursiveChildFactory<Parser, TagUseAsStart, Tags...>
+ UseAsStartEvent() const {
+ return {id_, member_, max_recursion_depth_};
+ }
+
+ // If called, OnChildParsed will be called on the parent element when this
+ // particular element is fully parsed.
+ constexpr RecursiveChildFactory<Parser, TagNotifyOnParseComplete, Tags...>
+ NotifyOnParseComplete() const {
+ return {id_, member_, max_recursion_depth_};
+ }
+
+ private:
+ Id id_;
+ std::vector<Element<T>> T::*member_;
+ std::size_t max_recursion_depth_;
+ };
+
+ // Constructs a new parser. Each argument must be a *ChildFactory, constructed
+ // from the MakeChild method.
+ template <typename... Args>
+ explicit MasterValueParser(Args&&... args)
+ : master_parser_(args.BuildParser(this, &value_)...) {}
+
+ // Returns a factory that will produce a
+ // std::pair<Id, std::unique_ptr<ElementParser>>. When a child element of the
+ // given ID is encountered, a parser of type Parser will be used to parse it,
+ // and store its value in the member pointer when the parse is complete. The
+ // given default value will be used in the event that the child element has a
+ // zero size. This method is only meant to be used by subclasses to provide
+ // the necessary factories to the constructor.
+ template <typename Parser, typename Value>
+ static SingleChildFactory<Parser, Value> MakeChild(
+ Id id, Element<Value> T::*member) {
+ static_assert(std::is_base_of<ElementParser, Parser>::value,
+ "Parser must derive from ElementParser");
+ static_assert(!std::is_base_of<MasterValueParser<T>, Parser>::value,
+ "Recursive elements should be contained in a std::vector");
+ return SingleChildFactory<Parser, Value>(id, member);
+ }
+
+ template <typename Parser, typename Value>
+ static RepeatedChildFactory<Parser, Value> MakeChild(
+ Id id, std::vector<Element<Value>> T::*member) {
+ static_assert(std::is_base_of<ElementParser, Parser>::value,
+ "Parser must derive from ElementParser");
+ static_assert(!std::is_base_of<MasterValueParser<T>, Parser>::value,
+ "Recursive elements require a maximum recursion depth");
+ return RepeatedChildFactory<Parser, Value>(id, member);
+ }
+
+ template <typename Parser>
+ static RecursiveChildFactory<Parser> MakeChild(
+ Id id, std::vector<Element<T>> T::*member,
+ std::size_t max_recursion_depth) {
+ static_assert(std::is_base_of<MasterValueParser<T>, Parser>::value,
+ "Child must be recusrive to use maximum recursion depth");
+ return RecursiveChildFactory<Parser>(id, member, max_recursion_depth);
+ }
+
+ // Gets the metadata for this element, setting the EBML element ID to id. Only
+ // call after Init() has been called.
+ ElementMetadata metadata(Id id) const {
+ return {id, master_parser_.header_size(), master_parser_.size(),
+ master_parser_.position()};
+ }
+
+ // This method will be called once the element has been fully parsed, or a
+ // particular child element of interest (see UseAsStartEvent()) is
+ // encountered. By default it just sets *action to Action::kRead and returns
+ // Status::kOkCompleted. May be overridden (i.e. in order to call a Callback
+ // method). Returning anything other than Status::kOkCompleted will stop
+ // parsing and the status to be returned by Init or Feed (whichever originated
+ // the call to this method). In this case, resuming parsing will result in
+ // this method being called again.
+ virtual Status OnParseStarted(Callback* callback, Action* action) {
+ assert(callback != nullptr);
+ assert(action != nullptr);
+ *action = Action::kRead;
+ return Status(Status::kOkCompleted);
+ }
+
+ // This method is the companion to OnParseStarted, and will only be called
+ // after OnParseStarted has already been called and parsing has completed.
+ // This will not be called if OnParseStarted set the action to Action::kSkip.
+ // By default it just returns Status::kOkCompleted. Returning anything other
+ // than Status::kOkCompleted will stop parsing and the status to be returned
+ // by Init or Feed (whichever originated the call to this method). In this
+ // case, resuming parsing will result in this method being called again.
+ virtual Status OnParseCompleted(Callback* callback) {
+ assert(callback != nullptr);
+ return Status(Status::kOkCompleted);
+ }
+
+ // Returns true if the OnParseStarted method has already been called and has
+ // completed.
+ bool parse_started_event_completed() const { return started_done_; }
+
+ // Derived classes may manually call OnParseStarted before calling Feed, in
+ // which case this method should be called to inform this class that
+ // OnParseStarted has already been called and it should not be called again.
+ void set_parse_started_event_completed_with_action(Action action) {
+ assert(!started_done_);
+
+ action_ = action;
+ started_done_ = true;
+ }
+
+ // This method will be called for each child element that has been fully
+ // parsed for which NotifyOnParseComplete() was requested. The provided
+ // metadata is for the child element that has just completed parsing. By
+ // default this method does nothing.
+ virtual void OnChildParsed(const ElementMetadata& /* metadata */) {}
+
+ private:
+ T value_;
+ Action action_ = Action::kRead;
+ bool parse_complete_;
+ bool started_done_;
+ // master_parser_ must be after value_ to ensure correct initialization order.
+ MasterParser master_parser_;
+
+ const ElementMetadata& child_metadata() const {
+ return master_parser_.child_metadata();
+ }
+
+ // Helper struct that will be std::true_type if Tag is in Tags, or
+ // std::false_type otherwise.
+ template <typename Tag, typename... Tags>
+ struct HasTag;
+
+ // Base condition: Tags is empty, so it trivially does not contain Tag.
+ template <typename Tag>
+ struct HasTag<Tag> : std::false_type {};
+
+ // If the head of the Tags list is a different tag, skip it and check the
+ // remaining tags.
+ template <typename Tag, typename DifferentTag, typename... Tags>
+ struct HasTag<Tag, DifferentTag, Tags...> : HasTag<Tag, Tags...> {};
+
+ // If the head of the Tags list is the same as Tag, then we're done.
+ template <typename Tag, typename... Tags>
+ struct HasTag<Tag, Tag, Tags...> : std::true_type {};
+
+ template <typename Base, typename F, typename... Tags>
+ class ChildParser : public Base {
+ public:
+ using Base::WasSkipped;
+
+ template <typename... Args>
+ explicit ChildParser(MasterValueParser* parent, F consume_element_value,
+ Args&&... base_args)
+ : Base(std::forward<Args>(base_args)...),
+ parent_(parent),
+ consume_element_value_(std::move(consume_element_value)) {}
+
+ ChildParser() = delete;
+ ChildParser(const ChildParser&) = delete;
+ ChildParser& operator=(const ChildParser&) = delete;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override {
+ *num_bytes_read = 0;
+
+ Status status = Prepare(callback);
+ if (!status.completed_ok()) {
+ return status;
+ }
+
+ status = Base::Feed(callback, reader, num_bytes_read);
+ if (status.completed_ok() && parent_->action_ != Action::kSkip &&
+ !WasSkipped()) {
+ consume_element_value_(this);
+ if (has_tag<TagNotifyOnParseComplete>()) {
+ parent_->OnChildParsed(parent_->child_metadata());
+ }
+ }
+ return status;
+ }
+
+ private:
+ MasterValueParser* parent_;
+ F consume_element_value_;
+
+ Status Prepare(Callback* callback) {
+ if (has_tag<TagUseAsStart>() && !parent_->started_done_) {
+ const Status status =
+ parent_->OnParseStarted(callback, &parent_->action_);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ parent_->started_done_ = true;
+ if (parent_->action_ == Action::kSkip) {
+ return Status(Status::kSwitchToSkip);
+ }
+ }
+ return Status(Status::kOkCompleted);
+ }
+
+ template <typename Tag>
+ constexpr static bool has_tag() {
+ return MasterValueParser::HasTag<Tag, Tags...>::value;
+ }
+ };
+
+ // Returns a std::unique_ptr<ElementParser> that points to a ChildParser
+ // when the Parser's constructor does not take a Value parameter.
+ template <typename Parser, typename Value, typename... Tags, typename F>
+ static typename std::enable_if<!std::is_constructible<Parser, Value>::value,
+ std::unique_ptr<ElementParser>>::type
+ MakeChildParser(MasterValueParser* parent, F consume_element_value, ...) {
+ return std::unique_ptr<ElementParser>(new ChildParser<Parser, F, Tags...>(
+ parent, std::move(consume_element_value)));
+ }
+
+ // Returns a std::unique_ptr<ElementParser> that points to a ChildParser
+ // when the Parser's constructor does take a Value parameter.
+ template <typename Parser, typename Value, typename... Tags, typename F>
+ static typename std::enable_if<std::is_constructible<Parser, Value>::value,
+ std::unique_ptr<ElementParser>>::type
+ MakeChildParser(MasterValueParser* parent, F consume_element_value,
+ const Element<Value>* default_value) {
+ return std::unique_ptr<ElementParser>(new ChildParser<Parser, F, Tags...>(
+ parent, std::move(consume_element_value), default_value->value()));
+ }
+
+ // Returns a std::unique_ptr<ElementParser> that points to a ChildParser
+ // when the Parser's constructor does take a Value parameter.
+ template <typename Parser, typename Value, typename... Tags, typename F>
+ static typename std::enable_if<std::is_constructible<Parser, Value>::value,
+ std::unique_ptr<ElementParser>>::type
+ MakeChildParser(MasterValueParser* parent, F consume_element_value,
+ const std::vector<Element<Value>>* member) {
+ Value default_value{};
+ if (!member->empty()) {
+ default_value = member->front().value();
+ }
+ return std::unique_ptr<ElementParser>(new ChildParser<Parser, F, Tags...>(
+ parent, std::move(consume_element_value), std::move(default_value)));
+ }
+
+ // Initializes object state. Call immediately before initializing
+ // master_parser_.
+ void PreInit() {
+ value_ = {};
+ action_ = Action::kRead;
+ parse_complete_ = false;
+ started_done_ = false;
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_MASTER_VALUE_PARSER_H_
diff --git a/webm_parser/src/mastering_metadata_parser.h b/webm_parser/src/mastering_metadata_parser.h
new file mode 100644
index 0000000..db6179c
--- /dev/null
+++ b/webm_parser/src/mastering_metadata_parser.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_MASTERING_METADATA_PARSER_H_
+#define SRC_MASTERING_METADATA_PARSER_H_
+
+#include "src/float_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#MasteringMetadata
+// http://www.webmproject.org/docs/container/#MasteringMetadata
+class MasteringMetadataParser : public MasterValueParser<MasteringMetadata> {
+ public:
+ MasteringMetadataParser()
+ : MasterValueParser<MasteringMetadata>(
+ MakeChild<FloatParser>(
+ Id::kPrimaryRChromaticityX,
+ &MasteringMetadata::primary_r_chromaticity_x),
+ MakeChild<FloatParser>(
+ Id::kPrimaryRChromaticityY,
+ &MasteringMetadata::primary_r_chromaticity_y),
+ MakeChild<FloatParser>(
+ Id::kPrimaryGChromaticityX,
+ &MasteringMetadata::primary_g_chromaticity_x),
+ MakeChild<FloatParser>(
+ Id::kPrimaryGChromaticityY,
+ &MasteringMetadata::primary_g_chromaticity_y),
+ MakeChild<FloatParser>(
+ Id::kPrimaryBChromaticityX,
+ &MasteringMetadata::primary_b_chromaticity_x),
+ MakeChild<FloatParser>(
+ Id::kPrimaryBChromaticityY,
+ &MasteringMetadata::primary_b_chromaticity_y),
+ MakeChild<FloatParser>(
+ Id::kWhitePointChromaticityX,
+ &MasteringMetadata::white_point_chromaticity_x),
+ MakeChild<FloatParser>(
+ Id::kWhitePointChromaticityY,
+ &MasteringMetadata::white_point_chromaticity_y),
+ MakeChild<FloatParser>(Id::kLuminanceMax,
+ &MasteringMetadata::luminance_max),
+ MakeChild<FloatParser>(Id::kLuminanceMin,
+ &MasteringMetadata::luminance_min)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_MASTERING_METADATA_PARSER_H_
diff --git a/webm_parser/src/parser.h b/webm_parser/src/parser.h
new file mode 100644
index 0000000..4c449ed
--- /dev/null
+++ b/webm_parser/src/parser.h
@@ -0,0 +1,34 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_PARSER_H_
+#define SRC_PARSER_H_
+
+#include "webm/callback.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+class Parser {
+ public:
+ virtual ~Parser() = default;
+
+ // Feeds data into the parser, with the number of bytes read from the reader
+ // returned in num_bytes_read. Returns Status::kOkCompleted when parsing is
+ // complete, or an appropriate error code if the data is malformed and cannot
+ // be parsed. Otherwise, the status of Reader::Read is returned if only a
+ // partial parse could be done because the reader couldn't immediately provide
+ // all the needed data. reader and num_bytes_read must not be null. Do not
+ // call again once the parse is complete.
+ virtual Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) = 0;
+};
+
+} // namespace webm
+
+#endif // SRC_PARSER_H_
diff --git a/webm_parser/src/parser_utils.cc b/webm_parser/src/parser_utils.cc
new file mode 100644
index 0000000..8563872
--- /dev/null
+++ b/webm_parser/src/parser_utils.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/parser_utils.h"
+
+#include <cassert>
+#include <cstdint>
+
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+Status ReadByte(Reader* reader, std::uint8_t* byte) {
+ assert(reader != nullptr);
+ assert(byte != nullptr);
+
+ std::uint64_t num_bytes_read;
+ const Status status = reader->Read(1, byte, &num_bytes_read);
+
+ if (!status.completed_ok()) {
+ assert(num_bytes_read == 0);
+ } else {
+ assert(num_bytes_read == 1);
+ }
+
+ return status;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/parser_utils.h b/webm_parser/src/parser_utils.h
new file mode 100644
index 0000000..becb1c7
--- /dev/null
+++ b/webm_parser/src/parser_utils.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_PARSER_UTILS_H_
+#define SRC_PARSER_UTILS_H_
+
+#include <cassert>
+#include <cstdint>
+#include <type_traits>
+
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Reads a single byte from the reader, and returns the status of the read. If
+// the status is not Status::kOkCompleted, then no data was read.
+Status ReadByte(Reader* reader, std::uint8_t* byte);
+
+// Accumulates bytes from the reader into the integer. The integer will be
+// extracted as a big-endian integer and stored with the native
+// host-endianness. num_bytes_remaining is the number of bytes to
+template <typename T>
+Status AccumulateIntegerBytes(int num_to_read, Reader* reader, T* integer,
+ std::uint64_t* num_actually_read) {
+ static_assert(std::is_integral<T>::value || std::is_enum<T>::value,
+ "T must be an integer or enum type");
+ // Use unsigned integers for bitwise arithmetic because it's well-defined (as
+ // opposed to signed integers, where left shifting a negative integer is
+ // undefined, for example).
+ using UnsignedT = typename std::make_unsigned<T>::type;
+
+ assert(reader != nullptr);
+ assert(integer != nullptr);
+ assert(num_actually_read != nullptr);
+ assert(num_to_read >= 0);
+ assert(static_cast<std::size_t>(num_to_read) <= sizeof(T));
+
+ *num_actually_read = 0;
+
+ if (num_to_read < 0 || static_cast<std::size_t>(num_to_read) > sizeof(T)) {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ for (; num_to_read > 0; --num_to_read) {
+ std::uint8_t byte;
+ const Status status = ReadByte(reader, &byte);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ ++*num_actually_read;
+ *integer = static_cast<T>((static_cast<UnsignedT>(*integer) << 8) | byte);
+ }
+
+ return Status(Status::kOkCompleted);
+}
+
+} // namespace webm
+
+#endif // SRC_PARSER_UTILS_H_
diff --git a/webm_parser/src/projection_parser.h b/webm_parser/src/projection_parser.h
new file mode 100644
index 0000000..a851eb6
--- /dev/null
+++ b/webm_parser/src/projection_parser.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_PROJECTION_PARSER_H_
+#define SRC_PROJECTION_PARSER_H_
+
+#include "src/byte_parser.h"
+#include "src/float_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// https://github.com/google/spatial-media/blob/master/docs/spherical-video-v2-rfc.md#projection-master-element
+class ProjectionParser : public MasterValueParser<Projection> {
+ public:
+ ProjectionParser()
+ : MasterValueParser(
+ MakeChild<IntParser<ProjectionType>>(Id::kProjectionType,
+ &Projection::type),
+ MakeChild<BinaryParser>(Id::kProjectionPrivate,
+ &Projection::projection_private),
+ MakeChild<FloatParser>(Id::kProjectionPoseYaw,
+ &Projection::pose_yaw),
+ MakeChild<FloatParser>(Id::kProjectionPosePitch,
+ &Projection::pose_pitch),
+ MakeChild<FloatParser>(Id::kProjectionPoseRoll,
+ &Projection::pose_roll)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_PROJECTION_PARSER_H_
diff --git a/webm_parser/src/recursive_parser.h b/webm_parser/src/recursive_parser.h
new file mode 100644
index 0000000..1646f2f
--- /dev/null
+++ b/webm_parser/src/recursive_parser.h
@@ -0,0 +1,93 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_RECURSIVE_PARSER_H_
+#define SRC_RECURSIVE_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+#include <memory>
+#include <utility>
+
+#include "src/element_parser.h"
+#include "webm/callback.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Lazily instantiates a parser of type T, and uses that parser to handle all
+// parsing operations. The parser is allocated when Init is called. This class
+// is intended to be used with recursive elements, where a parser needs to
+// recursively instantiate parsers of the same type.
+template <typename T>
+class RecursiveParser : public ElementParser {
+ public:
+ explicit RecursiveParser(std::size_t max_recursion_depth = 25)
+ : max_recursion_depth_(max_recursion_depth){};
+
+ RecursiveParser(RecursiveParser&&) = default;
+ RecursiveParser& operator=(RecursiveParser&&) = default;
+
+ RecursiveParser(const RecursiveParser&) = delete;
+ RecursiveParser& operator=(const RecursiveParser&) = delete;
+
+ Status Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) override {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ if (max_recursion_depth_ == 0) {
+ return Status(Status::kExceededRecursionDepthLimit);
+ }
+
+ if (!impl_) {
+ impl_.reset(new T(max_recursion_depth_ - 1));
+ }
+
+ return impl_->Init(metadata, max_size);
+ }
+
+ void InitAfterSeek(const Ancestory& child_ancestory,
+ const ElementMetadata& child_metadata) override {
+ assert(max_recursion_depth_ > 0);
+ if (!impl_) {
+ impl_.reset(new T(max_recursion_depth_ - 1));
+ }
+
+ impl_->InitAfterSeek(child_ancestory, child_metadata);
+ }
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+ assert(impl_ != nullptr);
+
+ return impl_->Feed(callback, reader, num_bytes_read);
+ }
+
+ decltype(std::declval<T>().value()) value() const {
+ assert(impl_ != nullptr);
+
+ return impl_->value();
+ }
+
+ decltype(std::declval<T>().mutable_value()) mutable_value() {
+ assert(impl_ != nullptr);
+
+ return impl_->mutable_value();
+ }
+
+ private:
+ std::unique_ptr<T> impl_;
+ std::size_t max_recursion_depth_;
+};
+
+} // namespace webm
+
+#endif // SRC_RECURSIVE_PARSER_H_
diff --git a/webm_parser/src/seek_head_parser.h b/webm_parser/src/seek_head_parser.h
new file mode 100644
index 0000000..09e3727
--- /dev/null
+++ b/webm_parser/src/seek_head_parser.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_SEEK_HEAD_PARSER_H_
+#define SRC_SEEK_HEAD_PARSER_H_
+
+#include "src/master_parser.h"
+#include "src/seek_parser.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#SeekHead
+// http://www.webmproject.org/docs/container/#SeekHead
+class SeekHeadParser : public MasterParser {
+ public:
+ SeekHeadParser() : MasterParser(MakeChild<SeekParser>(Id::kSeek)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_SEEK_HEAD_PARSER_H_
diff --git a/webm_parser/src/seek_parser.h b/webm_parser/src/seek_parser.h
new file mode 100644
index 0000000..d2cc415
--- /dev/null
+++ b/webm_parser/src/seek_parser.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_SEEK_PARSER_H_
+#define SRC_SEEK_PARSER_H_
+
+#include "src/id_element_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Seek
+// http://www.webmproject.org/docs/container/#Seek
+class SeekParser : public MasterValueParser<Seek> {
+ public:
+ SeekParser()
+ : MasterValueParser<Seek>(
+ MakeChild<IdElementParser>(Id::kSeekId, &Seek::id),
+ MakeChild<UnsignedIntParser>(Id::kSeekPosition, &Seek::position)) {}
+
+ protected:
+ Status OnParseCompleted(Callback* callback) override {
+ return callback->OnSeek(metadata(Id::kSeek), value());
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_SEEK_PARSER_H_
diff --git a/webm_parser/src/segment_parser.cc b/webm_parser/src/segment_parser.cc
new file mode 100644
index 0000000..9b2e7d5
--- /dev/null
+++ b/webm_parser/src/segment_parser.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/segment_parser.h"
+
+#include "src/chapters_parser.h"
+#include "src/cluster_parser.h"
+#include "src/cues_parser.h"
+#include "src/info_parser.h"
+#include "src/seek_head_parser.h"
+#include "src/skip_callback.h"
+#include "src/tags_parser.h"
+#include "src/tracks_parser.h"
+#include "webm/id.h"
+
+namespace webm {
+
+SegmentParser::SegmentParser()
+ : MasterParser(MakeChild<ChaptersParser>(Id::kChapters),
+ MakeChild<ClusterParser>(Id::kCluster),
+ MakeChild<CuesParser>(Id::kCues),
+ MakeChild<InfoParser>(Id::kInfo),
+ MakeChild<SeekHeadParser>(Id::kSeekHead),
+ MakeChild<TagsParser>(Id::kTags),
+ MakeChild<TracksParser>(Id::kTracks)) {}
+
+Status SegmentParser::Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ begin_done_ = false;
+ parse_completed_ = false;
+ return MasterParser::Init(metadata, max_size);
+}
+
+void SegmentParser::InitAfterSeek(const Ancestory& child_ancestory,
+ const ElementMetadata& child_metadata) {
+ MasterParser::InitAfterSeek(child_ancestory, child_metadata);
+
+ begin_done_ = true;
+ parse_completed_ = false;
+ action_ = Action::kRead;
+}
+
+Status SegmentParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ *num_bytes_read = 0;
+
+ if (!begin_done_) {
+ const ElementMetadata metadata{Id::kSegment, header_size(), size(),
+ position()};
+ const Status status = callback->OnSegmentBegin(metadata, &action_);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ begin_done_ = true;
+ }
+
+ SkipCallback skip_callback;
+ if (action_ == Action::kSkip) {
+ callback = &skip_callback;
+ }
+
+ if (!parse_completed_) {
+ const Status status = MasterParser::Feed(callback, reader, num_bytes_read);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ parse_completed_ = true;
+ }
+
+ return callback->OnSegmentEnd(
+ {Id::kSegment, header_size(), size(), position()});
+}
+
+bool SegmentParser::WasSkipped() const { return action_ == Action::kSkip; }
+
+} // namespace webm
diff --git a/webm_parser/src/segment_parser.h b/webm_parser/src/segment_parser.h
new file mode 100644
index 0000000..b5bb702
--- /dev/null
+++ b/webm_parser/src/segment_parser.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_SEGMENT_PARSER_H_
+#define SRC_SEGMENT_PARSER_H_
+
+#include <cstdint>
+
+#include "src/master_parser.h"
+#include "webm/callback.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses Segment elements from a WebM byte stream. This class adheres to the
+// ElementParser interface; see element_parser.h for further documentation on
+// how it should be used.
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Segment
+// http://www.webmproject.org/docs/container/#Segment
+class SegmentParser : public MasterParser {
+ public:
+ SegmentParser();
+
+ Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override;
+
+ void InitAfterSeek(const Ancestory& child_ancestory,
+ const ElementMetadata& child_metadata) override;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ bool WasSkipped() const override;
+
+ private:
+ // Set to true iff Callback::OnSegmentBegin has completed.
+ bool begin_done_;
+
+ // Set to true iff the base class has completed parsing.
+ bool parse_completed_;
+
+ // The action requested by Callback::OnSegmentBegin.
+ Action action_ = Action::kRead;
+};
+
+} // namespace webm
+
+#endif // SRC_SEGMENT_PARSER_H_
diff --git a/webm_parser/src/simple_tag_parser.h b/webm_parser/src/simple_tag_parser.h
new file mode 100644
index 0000000..d8b9da6
--- /dev/null
+++ b/webm_parser/src/simple_tag_parser.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_SIMPLE_TAG_PARSER_H_
+#define SRC_SIMPLE_TAG_PARSER_H_
+
+#include "src/bool_parser.h"
+#include "src/byte_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#SimpleTag
+// http://www.webmproject.org/docs/container/#SimpleTag
+class SimpleTagParser : public MasterValueParser<SimpleTag> {
+ public:
+ SimpleTagParser(std::size_t max_recursive_depth = 25)
+ : MasterValueParser<SimpleTag>(
+ MakeChild<StringParser>(Id::kTagName, &SimpleTag::name),
+ MakeChild<StringParser>(Id::kTagLanguage, &SimpleTag::language),
+ MakeChild<BoolParser>(Id::kTagDefault, &SimpleTag::is_default),
+ MakeChild<StringParser>(Id::kTagString, &SimpleTag::string),
+ MakeChild<BinaryParser>(Id::kTagBinary, &SimpleTag::binary),
+ MakeChild<SimpleTagParser>(Id::kSimpleTag, &SimpleTag::tags,
+ max_recursive_depth)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_SIMPLE_TAG_PARSER_H_
diff --git a/webm_parser/src/size_parser.cc b/webm_parser/src/size_parser.cc
new file mode 100644
index 0000000..3294382
--- /dev/null
+++ b/webm_parser/src/size_parser.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/size_parser.h"
+
+#include <cassert>
+#include <cstdint>
+#include <limits>
+
+#include "src/bit_utils.h"
+#include "src/parser_utils.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Spec references:
+// http://matroska.org/technical/specs/index.html#EBML_ex
+// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#element-data-size
+Status SizeParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ // Within the EBML header, the size can be encoded with 1-4 octets. After
+ // the EBML header, the size can be encoded with 1-8 octets (though not more
+ // than EBMLMaxSizeLength).
+
+ Status status = uint_parser_.Feed(callback, reader, num_bytes_read);
+
+ if (status.code == Status::kInvalidElementValue) {
+ status.code = Status::kInvalidElementSize;
+ }
+
+ return status;
+}
+
+std::uint64_t SizeParser::size() const {
+ // If all data bits are set, then it represents an unknown element size.
+ const std::uint64_t data_bits =
+ std::numeric_limits<std::uint64_t>::max() >>
+ (57 - 7 * (uint_parser_.encoded_length() - 1));
+ if (uint_parser_.value() == data_bits) {
+ return kUnknownElementSize;
+ }
+
+ return uint_parser_.value();
+}
+
+} // namespace webm
diff --git a/webm_parser/src/size_parser.h b/webm_parser/src/size_parser.h
new file mode 100644
index 0000000..d277f33
--- /dev/null
+++ b/webm_parser/src/size_parser.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_SIZE_PARSER_H_
+#define SRC_SIZE_PARSER_H_
+
+#include <cstdint>
+
+#include "src/parser.h"
+#include "src/var_int_parser.h"
+#include "webm/callback.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+class SizeParser : public Parser {
+ public:
+ SizeParser() = default;
+ SizeParser(SizeParser&&) = default;
+ SizeParser& operator=(SizeParser&&) = default;
+
+ SizeParser(const SizeParser&) = delete;
+ SizeParser& operator=(const SizeParser&) = delete;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ // Gets the parsed size. This must not be called until the parse had been
+ // successfully completed.
+ std::uint64_t size() const;
+
+ private:
+ VarIntParser uint_parser_;
+};
+
+} // namespace webm
+
+#endif // SRC_SIZE_PARSER_H_
diff --git a/webm_parser/src/skip_callback.h b/webm_parser/src/skip_callback.h
new file mode 100644
index 0000000..e1c28e5
--- /dev/null
+++ b/webm_parser/src/skip_callback.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_SKIP_CALLBACK_H_
+#define SRC_SKIP_CALLBACK_H_
+
+#include "webm/callback.h"
+#include "webm/dom_types.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// An implementation of Callback that skips all elements. Every method that
+// yields an action will yield Action::kSkip, and Reader::Skip will be called
+// if the callback ever needs to process data from the byte stream.
+class SkipCallback : public Callback {
+ public:
+ Status OnElementBegin(const ElementMetadata& /* metadata */,
+ Action* action) override {
+ *action = Action::kSkip;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnSegmentBegin(const ElementMetadata& /* metadata */,
+ Action* action) override {
+ *action = Action::kSkip;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnClusterBegin(const ElementMetadata& /* metadata */,
+ const Cluster& /* cluster */, Action* action) override {
+ *action = Action::kSkip;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnSimpleBlockBegin(const ElementMetadata& /* metadata */,
+ const SimpleBlock& /* simple_block */,
+ Action* action) override {
+ *action = Action::kSkip;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnBlockGroupBegin(const ElementMetadata& /* metadata */,
+ Action* action) override {
+ *action = Action::kSkip;
+ return Status(Status::kOkCompleted);
+ }
+
+ Status OnBlockBegin(const ElementMetadata& /* metadata */,
+ const Block& /* block */, Action* action) override {
+ *action = Action::kSkip;
+ return Status(Status::kOkCompleted);
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_SKIP_CALLBACK_H_
diff --git a/webm_parser/src/skip_parser.cc b/webm_parser/src/skip_parser.cc
new file mode 100644
index 0000000..8f63f2b
--- /dev/null
+++ b/webm_parser/src/skip_parser.cc
@@ -0,0 +1,59 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/skip_parser.h"
+
+#include <cassert>
+#include <cstdint>
+
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+Status SkipParser::Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ if (metadata.size == kUnknownElementSize) {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ num_bytes_remaining_ = metadata.size;
+
+ return Status(Status::kOkCompleted);
+}
+
+Status SkipParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ *num_bytes_read = 0;
+
+ if (num_bytes_remaining_ == 0) {
+ return Status(Status::kOkCompleted);
+ }
+
+ Status status;
+ do {
+ std::uint64_t local_num_bytes_read = 0;
+ status = reader->Skip(num_bytes_remaining_, &local_num_bytes_read);
+ assert((status.completed_ok() &&
+ local_num_bytes_read == num_bytes_remaining_) ||
+ (status.ok() && local_num_bytes_read < num_bytes_remaining_) ||
+ (!status.ok() && local_num_bytes_read == 0));
+ *num_bytes_read += local_num_bytes_read;
+ num_bytes_remaining_ -= local_num_bytes_read;
+ } while (status.code == Status::kOkPartial);
+
+ return status;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/skip_parser.h b/webm_parser/src/skip_parser.h
new file mode 100644
index 0000000..6251bae
--- /dev/null
+++ b/webm_parser/src/skip_parser.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_SKIP_PARSER_H_
+#define SRC_SKIP_PARSER_H_
+
+#include <cstdint>
+
+#include "src/element_parser.h"
+#include "webm/callback.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// A simple parser that merely skips (via Reader::Skip) ahead in the stream
+// until the element has been fully skipped.
+class SkipParser : public ElementParser {
+ public:
+ Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ private:
+ std::uint64_t num_bytes_remaining_;
+};
+
+} // namespace webm
+
+#endif // SRC_SKIP_PARSER_H_
diff --git a/webm_parser/src/slices_parser.h b/webm_parser/src/slices_parser.h
new file mode 100644
index 0000000..ae19c86
--- /dev/null
+++ b/webm_parser/src/slices_parser.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_SLICES_PARSER_H_
+#define SRC_SLICES_PARSER_H_
+
+#include "src/master_value_parser.h"
+#include "src/time_slice_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Slices
+// http://www.webmproject.org/docs/container/#Slices
+class SlicesParser : public MasterValueParser<Slices> {
+ public:
+ SlicesParser()
+ : MasterValueParser<Slices>(
+ MakeChild<TimeSliceParser>(Id::kTimeSlice, &Slices::slices)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_SLICES_PARSER_H_
diff --git a/webm_parser/src/tag_parser.h b/webm_parser/src/tag_parser.h
new file mode 100644
index 0000000..9cf8cf9
--- /dev/null
+++ b/webm_parser/src/tag_parser.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_TAG_PARSER_H_
+#define SRC_TAG_PARSER_H_
+
+#include "src/master_value_parser.h"
+#include "src/simple_tag_parser.h"
+#include "src/targets_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Tag
+// http://www.webmproject.org/docs/container/#Tag
+class TagParser : public MasterValueParser<Tag> {
+ public:
+ TagParser()
+ : MasterValueParser<Tag>(
+ MakeChild<TargetsParser>(Id::kTargets, &Tag::targets),
+ MakeChild<SimpleTagParser>(Id::kSimpleTag, &Tag::tags)) {}
+
+ protected:
+ Status OnParseCompleted(Callback* callback) override {
+ return callback->OnTag(metadata(Id::kTag), value());
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_TAG_PARSER_H_
diff --git a/webm_parser/src/tags_parser.h b/webm_parser/src/tags_parser.h
new file mode 100644
index 0000000..4784235
--- /dev/null
+++ b/webm_parser/src/tags_parser.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_TAGS_PARSER_H_
+#define SRC_TAGS_PARSER_H_
+
+#include "src/master_parser.h"
+#include "src/tag_parser.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Tags
+// http://www.webmproject.org/docs/container/#Tags
+class TagsParser : public MasterParser {
+ public:
+ TagsParser() : MasterParser(MakeChild<TagParser>(Id::kTag)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_TAGS_PARSER_H_
diff --git a/webm_parser/src/targets_parser.h b/webm_parser/src/targets_parser.h
new file mode 100644
index 0000000..043c110
--- /dev/null
+++ b/webm_parser/src/targets_parser.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_TARGETS_PARSER_H_
+#define SRC_TARGETS_PARSER_H_
+
+#include "src/byte_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Targets
+// http://www.webmproject.org/docs/container/#Targets
+class TargetsParser : public MasterValueParser<Targets> {
+ public:
+ TargetsParser()
+ : MasterValueParser<Targets>(
+ MakeChild<UnsignedIntParser>(Id::kTargetTypeValue,
+ &Targets::type_value),
+ MakeChild<StringParser>(Id::kTargetType, &Targets::type),
+ MakeChild<UnsignedIntParser>(Id::kTagTrackUid,
+ &Targets::track_uids)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_TARGETS_PARSER_H_
diff --git a/webm_parser/src/time_slice_parser.h b/webm_parser/src/time_slice_parser.h
new file mode 100644
index 0000000..a8fa41c
--- /dev/null
+++ b/webm_parser/src/time_slice_parser.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_TIME_SLICE_PARSER_H_
+#define SRC_TIME_SLICE_PARSER_H_
+
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#TimeSlice
+// http://www.webmproject.org/docs/container/#TimeSlice
+class TimeSliceParser : public MasterValueParser<TimeSlice> {
+ public:
+ TimeSliceParser()
+ : MasterValueParser<TimeSlice>(MakeChild<UnsignedIntParser>(
+ Id::kLaceNumber, &TimeSlice::lace_number)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_TIME_SLICE_PARSER_H_
diff --git a/webm_parser/src/track_entry_parser.h b/webm_parser/src/track_entry_parser.h
new file mode 100644
index 0000000..d103864
--- /dev/null
+++ b/webm_parser/src/track_entry_parser.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef SRC_TRACK_ENTRY_PARSER_H_
+#define SRC_TRACK_ENTRY_PARSER_H_
+
+#include "src/audio_parser.h"
+#include "src/bool_parser.h"
+#include "src/byte_parser.h"
+#include "src/content_encodings_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "src/video_parser.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#TrackEntry
+// http://www.webmproject.org/docs/container/#TrackEntry
+class TrackEntryParser : public MasterValueParser<TrackEntry> {
+ public:
+ TrackEntryParser()
+ : MasterValueParser<TrackEntry>(
+ MakeChild<UnsignedIntParser>(Id::kTrackNumber,
+ &TrackEntry::track_number),
+ MakeChild<UnsignedIntParser>(Id::kTrackUid, &TrackEntry::track_uid),
+ MakeChild<IntParser<TrackType>>(Id::kTrackType,
+ &TrackEntry::track_type),
+ MakeChild<BoolParser>(Id::kFlagEnabled, &TrackEntry::is_enabled),
+ MakeChild<BoolParser>(Id::kFlagDefault, &TrackEntry::is_default),
+ MakeChild<BoolParser>(Id::kFlagForced, &TrackEntry::is_forced),
+ MakeChild<BoolParser>(Id::kFlagLacing, &TrackEntry::uses_lacing),
+ MakeChild<UnsignedIntParser>(Id::kDefaultDuration,
+ &TrackEntry::default_duration),
+ MakeChild<StringParser>(Id::kName, &TrackEntry::name),
+ MakeChild<StringParser>(Id::kLanguage, &TrackEntry::language),
+ MakeChild<StringParser>(Id::kCodecId, &TrackEntry::codec_id),
+ MakeChild<BinaryParser>(Id::kCodecPrivate,
+ &TrackEntry::codec_private),
+ MakeChild<StringParser>(Id::kCodecName, &TrackEntry::codec_name),
+ MakeChild<UnsignedIntParser>(Id::kCodecDelay,
+ &TrackEntry::codec_delay),
+ MakeChild<UnsignedIntParser>(Id::kSeekPreRoll,
+ &TrackEntry::seek_pre_roll),
+ MakeChild<VideoParser>(Id::kVideo, &TrackEntry::video),
+ MakeChild<AudioParser>(Id::kAudio, &TrackEntry::audio),
+ MakeChild<ContentEncodingsParser>(
+ Id::kContentEncodings, &TrackEntry::content_encodings)) {}
+
+ protected:
+ Status OnParseCompleted(Callback* callback) override {
+ return callback->OnTrackEntry(metadata(Id::kTrackEntry), value());
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_TRACK_ENTRY_PARSER_H_
diff --git a/webm_parser/src/tracks_parser.h b/webm_parser/src/tracks_parser.h
new file mode 100644
index 0000000..57d25c4
--- /dev/null
+++ b/webm_parser/src/tracks_parser.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_TRACKS_PARSER_H_
+#define SRC_TRACKS_PARSER_H_
+
+#include "src/master_parser.h"
+#include "src/track_entry_parser.h"
+#include "webm/id.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Tracks
+// http://www.webmproject.org/docs/container/#Tracks
+class TracksParser : public MasterParser {
+ public:
+ TracksParser() : MasterParser(MakeChild<TrackEntryParser>(Id::kTrackEntry)) {}
+};
+
+} // namespace webm
+
+#endif // SRC_TRACKS_PARSER_H_
diff --git a/webm_parser/src/unknown_parser.cc b/webm_parser/src/unknown_parser.cc
new file mode 100644
index 0000000..042cf71
--- /dev/null
+++ b/webm_parser/src/unknown_parser.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/unknown_parser.h"
+
+#include <cassert>
+#include <cstdint>
+
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+Status UnknownParser::Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ if (metadata.size == kUnknownElementSize) {
+ return Status(Status::kIndefiniteUnknownElement);
+ }
+
+ metadata_ = metadata;
+ bytes_remaining_ = metadata.size;
+
+ return Status(Status::kOkCompleted);
+}
+
+Status UnknownParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ const std::uint64_t original_bytes_remaining = bytes_remaining_;
+ const Status status =
+ callback->OnUnknownElement(metadata_, reader, &bytes_remaining_);
+ assert(bytes_remaining_ <= original_bytes_remaining);
+
+ *num_bytes_read = original_bytes_remaining - bytes_remaining_;
+
+ return status;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/unknown_parser.h b/webm_parser/src/unknown_parser.h
new file mode 100644
index 0000000..d625bab
--- /dev/null
+++ b/webm_parser/src/unknown_parser.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_UNKNOWN_PARSER_H_
+#define SRC_UNKNOWN_PARSER_H_
+
+#include <cstdint>
+
+#include "src/element_parser.h"
+#include "webm/callback.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses unknown elements by delegating to Callback::OnUnknownElement.
+class UnknownParser : public ElementParser {
+ public:
+ Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ private:
+ // The metadata for this element.
+ ElementMetadata metadata_;
+
+ // The number of bytes remaining that have not been read in the element.
+ std::uint64_t bytes_remaining_;
+};
+
+} // namespace webm
+
+#endif // SRC_UNKNOWN_PARSER_H_
diff --git a/webm_parser/src/var_int_parser.cc b/webm_parser/src/var_int_parser.cc
new file mode 100644
index 0000000..5db9c44
--- /dev/null
+++ b/webm_parser/src/var_int_parser.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/var_int_parser.h"
+
+#include <cassert>
+#include <cstdint>
+#include <limits>
+
+#include "src/bit_utils.h"
+#include "src/parser_utils.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Spec references:
+// http://matroska.org/technical/specs/index.html#EBML_ex
+// https://github.com/Matroska-Org/ebml-specification/blob/master/specification.markdown#variable-size-integer
+Status VarIntParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+ assert(num_bytes_remaining_ != 0);
+
+ *num_bytes_read = 0;
+
+ if (num_bytes_remaining_ == -1) {
+ std::uint8_t first_byte;
+ const Status status = ReadByte(reader, &first_byte);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ ++*num_bytes_read;
+
+ // The first byte must have a marker bit set to indicate how many octets are
+ // used.
+ if (first_byte == 0) {
+ return Status(Status::kInvalidElementValue);
+ }
+
+ total_data_bytes_ = CountLeadingZeros(first_byte);
+ num_bytes_remaining_ = total_data_bytes_;
+
+ value_ = first_byte;
+ }
+
+ std::uint64_t local_num_bytes_read;
+ const Status status = AccumulateIntegerBytes(num_bytes_remaining_, reader,
+ &value_, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ num_bytes_remaining_ -= static_cast<int>(local_num_bytes_read);
+
+ if (!status.completed_ok()) {
+ return status;
+ }
+
+ // Clear the marker bit.
+ constexpr std::uint64_t all_bits = std::numeric_limits<std::uint64_t>::max();
+ const std::uint64_t data_bits = all_bits >> (57 - 7 * total_data_bytes_);
+ value_ &= data_bits;
+
+ return Status(Status::kOkCompleted);
+}
+
+} // namespace webm
diff --git a/webm_parser/src/var_int_parser.h b/webm_parser/src/var_int_parser.h
new file mode 100644
index 0000000..2ec51e3
--- /dev/null
+++ b/webm_parser/src/var_int_parser.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_VAR_INT_PARSER_H_
+#define SRC_VAR_INT_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/parser.h"
+#include "webm/callback.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+class VarIntParser : public Parser {
+ public:
+ VarIntParser() = default;
+ VarIntParser(VarIntParser&&) = default;
+ VarIntParser& operator=(VarIntParser&&) = default;
+
+ VarIntParser(const VarIntParser&) = delete;
+ VarIntParser& operator=(const VarIntParser&) = delete;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ // Gets the parsed value. This must not be called until the parse had been
+ // successfully completed.
+ std::uint64_t value() const {
+ assert(num_bytes_remaining_ == 0);
+ return value_;
+ }
+
+ // Gets the number of bytes which were used to encode the integer value in the
+ // byte stream. This must not be called until the parse had been successfully
+ // completed.
+ int encoded_length() const {
+ assert(num_bytes_remaining_ == 0);
+ return total_data_bytes_ + 1;
+ }
+
+ private:
+ int num_bytes_remaining_ = -1;
+ int total_data_bytes_;
+ std::uint64_t value_;
+};
+
+} // namespace webm
+
+#endif // SRC_VAR_INT_PARSER_H_
diff --git a/webm_parser/src/video_parser.h b/webm_parser/src/video_parser.h
new file mode 100644
index 0000000..20c12e2
--- /dev/null
+++ b/webm_parser/src/video_parser.h
@@ -0,0 +1,122 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_VIDEO_PARSER_H_
+#define SRC_VIDEO_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/bool_parser.h"
+#include "src/colour_parser.h"
+#include "src/float_parser.h"
+#include "src/int_parser.h"
+#include "src/master_value_parser.h"
+#include "src/projection_parser.h"
+#include "webm/callback.h"
+#include "webm/dom_types.h"
+#include "webm/id.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Video
+// http://www.webmproject.org/docs/container/#Video
+class VideoParser : public MasterValueParser<Video> {
+ public:
+ VideoParser()
+ : MasterValueParser<Video>(
+ MakeChild<IntParser<FlagInterlaced>>(Id::kFlagInterlaced,
+ &Video::interlaced),
+ MakeChild<IntParser<StereoMode>>(Id::kStereoMode,
+ &Video::stereo_mode),
+ MakeChild<UnsignedIntParser>(Id::kAlphaMode, &Video::alpha_mode),
+ MakeChild<UnsignedIntParser>(Id::kPixelWidth, &Video::pixel_width),
+ MakeChild<UnsignedIntParser>(Id::kPixelHeight,
+ &Video::pixel_height),
+ MakeChild<UnsignedIntParser>(Id::kPixelCropBottom,
+ &Video::pixel_crop_bottom),
+ MakeChild<UnsignedIntParser>(Id::kPixelCropTop,
+ &Video::pixel_crop_top),
+ MakeChild<UnsignedIntParser>(Id::kPixelCropLeft,
+ &Video::pixel_crop_left),
+ MakeChild<UnsignedIntParser>(Id::kPixelCropRight,
+ &Video::pixel_crop_right),
+ MakeChild<UnsignedIntParser>(Id::kDisplayWidth,
+ &Video::display_width)
+ .NotifyOnParseComplete(),
+ MakeChild<UnsignedIntParser>(Id::kDisplayHeight,
+ &Video::display_height)
+ .NotifyOnParseComplete(),
+ MakeChild<IntParser<DisplayUnit>>(Id::kDisplayUnit,
+ &Video::display_unit),
+ MakeChild<IntParser<AspectRatioType>>(Id::kAspectRatioType,
+ &Video::aspect_ratio_type),
+ MakeChild<FloatParser>(Id::kFrameRate, &Video::frame_rate),
+ MakeChild<ColourParser>(Id::kColour, &Video::colour),
+ MakeChild<ProjectionParser>(Id::kProjection, &Video::projection)) {}
+
+ Status Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) override {
+ display_width_has_value_ = false;
+ display_height_has_value_ = false;
+
+ return MasterValueParser::Init(metadata, max_size);
+ }
+
+ void InitAfterSeek(const Ancestory& child_ancestory,
+ const ElementMetadata& child_metadata) override {
+ display_width_has_value_ = false;
+ display_height_has_value_ = false;
+
+ return MasterValueParser::InitAfterSeek(child_ancestory, child_metadata);
+ }
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override {
+ const Status status =
+ MasterValueParser::Feed(callback, reader, num_bytes_read);
+ if (status.completed_ok()) {
+ FixMissingDisplaySize();
+ }
+ return status;
+ }
+
+ protected:
+ void OnChildParsed(const ElementMetadata& metadata) override {
+ assert(metadata.id == Id::kDisplayWidth ||
+ metadata.id == Id::kDisplayHeight);
+
+ if (metadata.id == Id::kDisplayWidth) {
+ display_width_has_value_ = metadata.size > 0;
+ } else {
+ display_height_has_value_ = metadata.size > 0;
+ }
+ }
+
+ private:
+ bool display_width_has_value_;
+ bool display_height_has_value_;
+
+ void FixMissingDisplaySize() {
+ if (!display_width_has_value_) {
+ *mutable_value()->display_width.mutable_value() =
+ value().pixel_width.value();
+ }
+
+ if (!display_height_has_value_) {
+ *mutable_value()->display_height.mutable_value() =
+ value().pixel_height.value();
+ }
+ }
+};
+
+} // namespace webm
+
+#endif // SRC_VIDEO_PARSER_H_
diff --git a/webm_parser/src/virtual_block_parser.cc b/webm_parser/src/virtual_block_parser.cc
new file mode 100644
index 0000000..712c77f
--- /dev/null
+++ b/webm_parser/src/virtual_block_parser.cc
@@ -0,0 +1,72 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/virtual_block_parser.h"
+
+#include <cassert>
+#include <cstdint>
+
+#include "webm/element.h"
+
+namespace webm {
+
+Status VirtualBlockParser::Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ if (metadata.size == kUnknownElementSize || metadata.size < 4) {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ *this = {};
+ my_size_ = metadata.size;
+
+ return Status(Status::kOkCompleted);
+}
+
+Status VirtualBlockParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ *num_bytes_read = 0;
+
+ Status status;
+ std::uint64_t local_num_bytes_read;
+
+ while (true) {
+ switch (state_) {
+ case State::kReadingHeader: {
+ status = parser_.Feed(callback, reader, &local_num_bytes_read);
+ *num_bytes_read += local_num_bytes_read;
+ total_bytes_read_ += local_num_bytes_read;
+ if (!status.completed_ok()) {
+ return status;
+ }
+ value_.track_number = parser_.value().track_number;
+ value_.timecode = parser_.value().timecode;
+ state_ = State::kValidatingSize;
+ continue;
+ }
+
+ case State::kValidatingSize: {
+ if (my_size_ < total_bytes_read_) {
+ return Status(Status::kInvalidElementValue);
+ }
+ state_ = State::kDone;
+ continue;
+ }
+
+ case State::kDone: {
+ return Status(Status::kOkCompleted);
+ }
+ }
+ }
+}
+
+} // namespace webm
diff --git a/webm_parser/src/virtual_block_parser.h b/webm_parser/src/virtual_block_parser.h
new file mode 100644
index 0000000..d7b8cd7
--- /dev/null
+++ b/webm_parser/src/virtual_block_parser.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_VIRTUAL_BLOCK_PARSER_H_
+#define SRC_VIRTUAL_BLOCK_PARSER_H_
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/block_header_parser.h"
+#include "src/element_parser.h"
+#include "webm/callback.h"
+#include "webm/dom_types.h"
+#include "webm/element.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#BlockVirtual
+// http://www.webmproject.org/docs/container/#BlockVirtual
+// http://matroska.org/technical/specs/index.html#block_virtual
+class VirtualBlockParser : public ElementParser {
+ public:
+ Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ // Gets the parsed block header information. This must not be called until the
+ // parse has been successfully completed.
+ const VirtualBlock& value() const {
+ assert(state_ == State::kDone);
+ return value_;
+ }
+
+ // Gets the parsed block header information. This must not be called until the
+ // parse has been successfully completed.
+ VirtualBlock* mutable_value() {
+ assert(state_ == State::kDone);
+ return &value_;
+ }
+
+ private:
+ std::uint64_t my_size_;
+ std::uint64_t total_bytes_read_ = 0;
+
+ VirtualBlock value_{};
+
+ BlockHeaderParser parser_;
+
+ enum class State {
+ /* clang-format off */
+ // State Transitions to state When
+ kReadingHeader, // kValidatingSize header parsed
+ kValidatingSize, // kDone no errors
+ kDone, // No transitions from here (must call Init)
+ /* clang-format on */
+ } state_ = State::kReadingHeader;
+};
+
+} // namespace webm
+
+#endif // SRC_VIRTUAL_BLOCK_PARSER_H_
diff --git a/webm_parser/src/void_parser.cc b/webm_parser/src/void_parser.cc
new file mode 100644
index 0000000..043bced
--- /dev/null
+++ b/webm_parser/src/void_parser.cc
@@ -0,0 +1,49 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/void_parser.h"
+
+#include <cassert>
+#include <cstdint>
+
+#include "webm/callback.h"
+#include "webm/element.h"
+#include "webm/id.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+Status VoidParser::Init(const ElementMetadata& metadata,
+ std::uint64_t max_size) {
+ assert(metadata.size == kUnknownElementSize || metadata.size <= max_size);
+
+ if (metadata.size == kUnknownElementSize) {
+ return Status(Status::kInvalidElementSize);
+ }
+
+ metadata_ = metadata;
+ bytes_remaining_ = metadata.size;
+
+ return Status(Status::kOkCompleted);
+}
+
+Status VoidParser::Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+ assert(num_bytes_read != nullptr);
+
+ const std::uint64_t original_bytes_remaining = bytes_remaining_;
+ const Status status = callback->OnVoid(metadata_, reader, &bytes_remaining_);
+ assert(bytes_remaining_ <= original_bytes_remaining);
+
+ *num_bytes_read = original_bytes_remaining - bytes_remaining_;
+ return status;
+}
+
+} // namespace webm
diff --git a/webm_parser/src/void_parser.h b/webm_parser/src/void_parser.h
new file mode 100644
index 0000000..e793faf
--- /dev/null
+++ b/webm_parser/src/void_parser.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef SRC_VOID_PARSER_H_
+#define SRC_VOID_PARSER_H_
+
+#include <cstdint>
+
+#include "src/element_parser.h"
+#include "webm/callback.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Parses a Void element by delegating to Callback::OnVoid.
+// Spec reference:
+// http://matroska.org/technical/specs/index.html#Void
+// http://www.webmproject.org/docs/container/#Void
+class VoidParser : public ElementParser {
+ public:
+ Status Init(const ElementMetadata& metadata, std::uint64_t max_size) override;
+
+ Status Feed(Callback* callback, Reader* reader,
+ std::uint64_t* num_bytes_read) override;
+
+ private:
+ // The metadata for this element.
+ ElementMetadata metadata_;
+
+ // The number of bytes remaining that have not been read in the element.
+ std::uint64_t bytes_remaining_ = 0;
+};
+
+} // namespace webm
+
+#endif // SRC_VOID_PARSER_H_
diff --git a/webm_parser/src/webm_parser.cc b/webm_parser/src/webm_parser.cc
new file mode 100644
index 0000000..cf8bc94
--- /dev/null
+++ b/webm_parser/src/webm_parser.cc
@@ -0,0 +1,282 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "webm/webm_parser.h"
+
+#include <cassert>
+#include <cstdint>
+
+#include "src/ebml_parser.h"
+#include "src/master_parser.h"
+#include "src/segment_parser.h"
+#include "src/unknown_parser.h"
+#include "webm/element.h"
+
+namespace webm {
+
+// Parses WebM EBML documents (i.e. level-0 WebM elements).
+class WebmParser::DocumentParser {
+ public:
+ // Resets the parser after a seek to a new position in the reader.
+ void DidSeek() {
+ PrepareForNextChild();
+ did_seek_ = true;
+ state_ = State::kBegin;
+ }
+
+ // Feeds the parser; will return Status::kOkCompleted when the reader returns
+ // Status::kEndOfFile, but only if the parser has already completed parsing
+ // its child elements.
+ Status Feed(Callback* callback, Reader* reader) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+
+ Callback* const original_callback = callback;
+ if (action_ == Action::kSkip) {
+ callback = &skip_callback_;
+ }
+
+ Status status;
+ std::uint64_t num_bytes_read;
+ while (true) {
+ switch (state_) {
+ case State::kBegin: {
+ child_metadata_.header_size = 0;
+ child_metadata_.position = reader->Position();
+ state_ = State::kReadingChildId;
+ continue;
+ }
+
+ case State::kReadingChildId: {
+ assert(child_parser_ == nullptr);
+ status = id_parser_.Feed(callback, reader, &num_bytes_read);
+ child_metadata_.header_size += num_bytes_read;
+ if (!status.completed_ok()) {
+ if (status.code == Status::kEndOfFile &&
+ reader->Position() == child_metadata_.position) {
+ state_ = State::kEndReached;
+ continue;
+ }
+ return status;
+ }
+ state_ = State::kReadingChildSize;
+ continue;
+ }
+
+ case State::kReadingChildSize: {
+ assert(child_parser_ == nullptr);
+ status = size_parser_.Feed(callback, reader, &num_bytes_read);
+ child_metadata_.header_size += num_bytes_read;
+ if (!status.completed_ok()) {
+ return status;
+ }
+ child_metadata_.id = id_parser_.id();
+ child_metadata_.size = size_parser_.size();
+ state_ = State::kValidatingChildSize;
+ continue;
+ }
+
+ case State::kValidatingChildSize: {
+ assert(child_parser_ == nullptr);
+
+ if (child_metadata_.id == Id::kSegment) {
+ child_parser_ = &segment_parser_;
+ did_seek_ = false;
+ state_ = State::kGettingAction;
+ continue;
+ } else if (child_metadata_.id == Id::kEbml) {
+ child_parser_ = &ebml_parser_;
+ did_seek_ = false;
+ state_ = State::kGettingAction;
+ continue;
+ }
+
+ Ancestory ancestory;
+ if (did_seek_ && Ancestory::ById(child_metadata_.id, &ancestory)) {
+ assert(!ancestory.empty());
+ assert(ancestory.id() == Id::kSegment ||
+ ancestory.id() == Id::kEbml);
+
+ if (ancestory.id() == Id::kSegment) {
+ child_parser_ = &segment_parser_;
+ } else {
+ child_parser_ = &ebml_parser_;
+ }
+
+ child_parser_->InitAfterSeek(ancestory.next(), child_metadata_);
+ child_metadata_.id = ancestory.id();
+ child_metadata_.header_size = kUnknownHeaderSize;
+ child_metadata_.size = kUnknownElementSize;
+ child_metadata_.position = kUnknownElementPosition;
+ did_seek_ = false;
+ action_ = Action::kRead;
+ state_ = State::kReadingChildBody;
+ continue;
+ }
+
+ if (child_metadata_.id == Id::kVoid) {
+ child_parser_ = &void_parser_;
+ } else {
+ if (child_metadata_.size == kUnknownElementSize) {
+ return Status(Status::kIndefiniteUnknownElement);
+ }
+ child_parser_ = &unknown_parser_;
+ }
+ state_ = State::kGettingAction;
+ continue;
+ }
+
+ case State::kGettingAction: {
+ assert(child_parser_ != nullptr);
+ status = callback->OnElementBegin(child_metadata_, &action_);
+ if (!status.completed_ok()) {
+ return status;
+ }
+
+ if (action_ == Action::kSkip) {
+ callback = &skip_callback_;
+ if (child_metadata_.size != kUnknownElementSize) {
+ child_parser_ = &skip_parser_;
+ }
+ }
+ state_ = State::kInitializingChildParser;
+ continue;
+ }
+
+ case State::kInitializingChildParser: {
+ assert(child_parser_ != nullptr);
+ status = child_parser_->Init(child_metadata_, child_metadata_.size);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ state_ = State::kReadingChildBody;
+ continue;
+ }
+
+ case State::kReadingChildBody: {
+ assert(child_parser_ != nullptr);
+ status = child_parser_->Feed(callback, reader, &num_bytes_read);
+ if (!status.completed_ok()) {
+ return status;
+ }
+ if (child_parser_->GetCachedMetadata(&child_metadata_)) {
+ state_ = State::kValidatingChildSize;
+ } else {
+ child_metadata_.header_size = 0;
+ state_ = State::kReadingChildId;
+ }
+ PrepareForNextChild();
+ callback = original_callback;
+ child_metadata_.position = reader->Position();
+ continue;
+ }
+
+ case State::kEndReached: {
+ return Status(Status::kOkCompleted);
+ }
+ }
+ }
+ }
+
+ private:
+ // Parsing states for the finite-state machine.
+ enum class State {
+ /* clang-format off */
+ // State Transitions to state When
+ kBegin, // kReadingChildId done
+ kReadingChildId, // kReadingChildSize done
+ // kEndReached EOF
+ kReadingChildSize, // kValidatingChildSize done
+ kValidatingChildSize, // kGettingAction done
+ kGettingAction, // kInitializingChildParser done
+ kInitializingChildParser, // kReadingChildBody done
+ kReadingChildBody, // kValidatingChildSize cached metadata
+ // kReadingChildId otherwise
+ kEndReached, // No transitions from here
+ /* clang-format on */
+ };
+
+ // The parser for parsing child element Ids.
+ IdParser id_parser_;
+
+ // The parser for parsing child element sizes.
+ SizeParser size_parser_;
+
+ // The parser for Id::kEbml elements.
+ EbmlParser ebml_parser_;
+
+ // The parser for Id::kSegment child elements.
+ SegmentParser segment_parser_;
+
+ // The parser for Id::kVoid child elements.
+ VoidParser void_parser_;
+
+ // The parser used when skipping elements (if the element's size is known).
+ SkipParser skip_parser_;
+
+ // The parser used for unknown children.
+ UnknownParser unknown_parser_;
+
+ // The callback used when skipping elements.
+ SkipCallback skip_callback_;
+
+ // The parser that is parsing the current child element.
+ ElementParser* child_parser_ = nullptr;
+
+ // Metadata for the current child being parsed.
+ ElementMetadata child_metadata_ = {};
+
+ // Action for the current child being parsed.
+ Action action_ = Action::kRead;
+
+ // True if a seek was performed and the parser needs to handle it.
+ bool did_seek_ = false;
+
+ // The current state of the finite state machine.
+ State state_ = State::kBegin;
+
+ // Resets state in preparation for parsing a child element.
+ void PrepareForNextChild() {
+ id_parser_ = {};
+ size_parser_ = {};
+ child_parser_ = nullptr;
+ action_ = Action::kRead;
+ }
+};
+
+// We have to explicitly declare a destructor (even if it's just defaulted)
+// because using the pimpl idiom with std::unique_ptr requires it. See Herb
+// Sutter's GotW #100 for further explanation.
+WebmParser::~WebmParser() = default;
+
+WebmParser::WebmParser() : parser_(new DocumentParser) {}
+
+void WebmParser::DidSeek() {
+ parser_->DidSeek();
+ parsing_status_ = Status(Status::kOkPartial);
+}
+
+Status WebmParser::Feed(Callback* callback, Reader* reader) {
+ assert(callback != nullptr);
+ assert(reader != nullptr);
+
+ if (parsing_status_.is_parsing_error()) {
+ return parsing_status_;
+ }
+ parsing_status_ = parser_->Feed(callback, reader);
+ return parsing_status_;
+}
+
+void WebmParser::Swap(WebmParser* other) {
+ assert(other != nullptr);
+ parser_.swap(other->parser_);
+ std::swap(parsing_status_, other->parsing_status_);
+}
+
+void swap(WebmParser& left, WebmParser& right) { left.Swap(&right); }
+
+} // namespace webm
diff --git a/webm_parser/test_utils/element_parser_test.h b/webm_parser/test_utils/element_parser_test.h
new file mode 100644
index 0000000..d0a6530
--- /dev/null
+++ b/webm_parser/test_utils/element_parser_test.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef TEST_UTILS_ELEMENT_PARSER_TEST_H_
+#define TEST_UTILS_ELEMENT_PARSER_TEST_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "test_utils/limited_reader.h"
+#include "test_utils/parser_test.h"
+#include "webm/buffer_reader.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Base class for unit tests that test an instance of the ElementParser
+// inteface. The template parameter T is the parser class being tested, and the
+// optional id is the element ID associated with elements from the parser.
+template <typename T, Id id = static_cast<Id>(0)>
+class ElementParserTest : public ParserTest<T> {
+ public:
+ // Sets the reader's internal buffer to the given buffer and metadata_ to
+ // data.size().
+ void SetReaderData(std::vector<std::uint8_t> data) override {
+ metadata_.size = data.size();
+ ParserTest<T>::SetReaderData(std::move(data));
+ }
+
+ // Sets metadata_.size to size and then calls Init() on the parser, ensuring
+ // that it returns the expected status code.
+ void TestInit(std::uint64_t size, Status::Code expected) {
+ metadata_.size = size;
+ const Status status = parser_.Init(metadata_, metadata_.size);
+ ASSERT_EQ(expected, status.code);
+ }
+
+ // Similar to the base class implementation, but with the difference that
+ // Init() is also called (after setting metadata_.size to size).
+ void ParseAndVerify(std::uint64_t size) override {
+ TestInit(size, Status::kOkCompleted);
+
+ std::uint64_t num_bytes_read = 0;
+ const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read);
+ ASSERT_EQ(Status::kOkCompleted, status.code);
+
+ if (size != kUnknownElementSize) {
+ ASSERT_EQ(size, num_bytes_read);
+ }
+ }
+
+ void ParseAndVerify() override { ParseAndVerify(metadata_.size); }
+
+ void IncrementalParseAndVerify() override {
+ TestInit(metadata_.size, Status::kOkCompleted);
+
+ webm::LimitedReader limited_reader(
+ std::unique_ptr<webm::Reader>(new BufferReader(std::move(reader_))));
+
+ Status status;
+ std::uint64_t num_bytes_read = 0;
+ do {
+ limited_reader.set_total_read_skip_limit(1);
+ std::uint64_t local_num_bytes_read = 0;
+ status = parser_.Feed(&callback_, &limited_reader, &local_num_bytes_read);
+ num_bytes_read += local_num_bytes_read;
+ ASSERT_GE(static_cast<std::uint64_t>(1), local_num_bytes_read);
+ } while (status.code == Status::kWouldBlock ||
+ status.code == Status::kOkPartial);
+
+ ASSERT_EQ(Status::kOkCompleted, status.code);
+
+ if (metadata_.size != kUnknownElementSize) {
+ ASSERT_EQ(metadata_.size, num_bytes_read);
+ }
+ }
+
+ // Initializes the parser (after setting metadata_.size to size), ensures it
+ // succeeds, and then calls Feed() on the parser, making sure it returns the
+ // expected status code.
+ void ParseAndExpectResult(Status::Code expected, std::uint64_t size) {
+ TestInit(size, Status::kOkCompleted);
+
+ std::uint64_t num_bytes_read = 0;
+ const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read);
+ ASSERT_EQ(expected, status.code);
+ }
+
+ // Initializes the parser, ensures it succeeds, and then calls Feed() on the
+ // parser, making sure it returns the expected status code.
+ void ParseAndExpectResult(Status::Code expected) override {
+ ParseAndExpectResult(expected, metadata_.size);
+ }
+
+ protected:
+ using ParserTest<T>::callback_;
+ using ParserTest<T>::parser_;
+ using ParserTest<T>::reader_;
+
+ // Element metadata associated with the element parsed by parser_. This is
+ // passed to Init() when initializing the parser.
+ ElementMetadata metadata_ = {id, 0, 0, 0};
+};
+
+} // namespace webm
+
+#endif // TEST_UTILS_ELEMENT_PARSER_TEST_H_
diff --git a/webm_parser/test_utils/limited_reader.cc b/webm_parser/test_utils/limited_reader.cc
new file mode 100644
index 0000000..f1228a1
--- /dev/null
+++ b/webm_parser/test_utils/limited_reader.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "test_utils/limited_reader.h"
+
+namespace webm {
+
+LimitedReader::LimitedReader(std::unique_ptr<Reader> impl)
+ : impl_(std::move(impl)) {}
+
+Status LimitedReader::Read(std::size_t num_to_read, std::uint8_t* buffer,
+ std::uint64_t* num_actually_read) {
+ assert(num_to_read > 0);
+ assert(buffer != nullptr);
+ assert(num_actually_read != nullptr);
+
+ *num_actually_read = 0;
+ std::size_t expected = num_to_read;
+
+ num_to_read = std::min({num_to_read, single_read_limit_, total_read_limit_});
+
+ // Handle total_read_skip_limit_ separately since std::size_t can be
+ // smaller than std::uint64_t.
+ if (num_to_read > total_read_skip_limit_) {
+ num_to_read = static_cast<std::size_t>(total_read_skip_limit_);
+ }
+
+ if (num_to_read == 0) {
+ return return_status_when_blocked_;
+ }
+
+ Status status = impl_->Read(num_to_read, buffer, num_actually_read);
+ assert(*num_actually_read <= num_to_read);
+
+ if (status.code == Status::kOkCompleted && *num_actually_read < expected) {
+ status.code = Status::kOkPartial;
+ }
+
+ if (total_read_limit_ != std::numeric_limits<std::size_t>::max()) {
+ total_read_limit_ -= static_cast<std::size_t>(*num_actually_read);
+ }
+
+ if (total_read_skip_limit_ != std::numeric_limits<std::uint64_t>::max()) {
+ total_read_skip_limit_ -= *num_actually_read;
+ }
+
+ return status;
+}
+
+Status LimitedReader::Skip(std::uint64_t num_to_skip,
+ std::uint64_t* num_actually_skipped) {
+ assert(num_to_skip > 0);
+ assert(num_actually_skipped != nullptr);
+
+ *num_actually_skipped = 0;
+ std::uint64_t expected = num_to_skip;
+
+ num_to_skip = std::min({num_to_skip, single_skip_limit_, total_skip_limit_,
+ total_read_skip_limit_});
+
+ if (num_to_skip == 0) {
+ return return_status_when_blocked_;
+ }
+
+ Status status = impl_->Skip(num_to_skip, num_actually_skipped);
+ assert(*num_actually_skipped <= num_to_skip);
+
+ if (status.code == Status::kOkCompleted && *num_actually_skipped < expected) {
+ status.code = Status::kOkPartial;
+ }
+
+ if (total_skip_limit_ != std::numeric_limits<std::uint64_t>::max()) {
+ total_skip_limit_ -= *num_actually_skipped;
+ }
+
+ if (total_read_skip_limit_ != std::numeric_limits<std::uint64_t>::max()) {
+ total_read_skip_limit_ -= *num_actually_skipped;
+ }
+
+ return status;
+}
+
+std::uint64_t LimitedReader::Position() const { return impl_->Position(); }
+
+void LimitedReader::set_return_status_when_blocked(Status status) {
+ return_status_when_blocked_ = status;
+}
+
+void LimitedReader::set_single_read_limit(std::size_t max_num_bytes) {
+ single_read_limit_ = max_num_bytes;
+}
+
+void LimitedReader::set_single_skip_limit(std::uint64_t max_num_bytes) {
+ single_skip_limit_ = max_num_bytes;
+}
+
+void LimitedReader::set_total_read_limit(std::size_t max_num_bytes) {
+ total_read_limit_ = max_num_bytes;
+}
+
+void LimitedReader::set_total_skip_limit(std::uint64_t max_num_bytes) {
+ total_skip_limit_ = max_num_bytes;
+}
+
+void LimitedReader::set_total_read_skip_limit(std::uint64_t max_num_bytes) {
+ total_read_skip_limit_ = max_num_bytes;
+}
+
+} // namespace webm
diff --git a/webm_parser/test_utils/limited_reader.h b/webm_parser/test_utils/limited_reader.h
new file mode 100644
index 0000000..d7f37ef
--- /dev/null
+++ b/webm_parser/test_utils/limited_reader.h
@@ -0,0 +1,115 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef TEST_UTILS_LIMITED_READER_H_
+#define TEST_UTILS_LIMITED_READER_H_
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <memory>
+
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// An adapter that uses an underlying reader to read data, but with added
+// limitations on how much data can be read/skipped. Its primary use is for
+// testing APIs that consume a reader to make sure they gracefully handle
+// arbitrary reading failures.
+class LimitedReader : public Reader {
+ public:
+ LimitedReader() = delete;
+ LimitedReader(const LimitedReader&) = delete;
+ LimitedReader& operator=(const LimitedReader&) = delete;
+
+ LimitedReader(LimitedReader&&) = default;
+ LimitedReader& operator=(LimitedReader&&) = default;
+
+ explicit LimitedReader(std::unique_ptr<Reader> impl);
+
+ // Reads data using the internal reader, but limits the number of bytes that
+ // can be read based on the settings of this LimitedReader. If this reader has
+ // reached its cap of maximum number of bytes allowed to be read, the chosen
+ // status will be returned.
+ Status Read(std::size_t num_to_read, std::uint8_t* buffer,
+ std::uint64_t* num_actually_read) override;
+
+ // Skips data using the internal reader, but limits the number of bytes that
+ // can be skipped based on the settings of this LimitedReader. If this reader
+ // has reached its cap of maximum number of bytes allowed to be skipped, the
+ // chosen status will be returned.
+ Status Skip(std::uint64_t num_to_skip,
+ std::uint64_t* num_actually_skipped) override;
+
+ std::uint64_t Position() const override;
+
+ // Sets the status that should be returned when the reader reaches its cap of
+ // maximum number of bytes that can be read/skipped and cannot read/skip any
+ // more bytes. By default, this reader will return Status::kWouldBlock when
+ // this maximum limit is hit.
+ void set_return_status_when_blocked(Status status);
+
+ // Sets the total number of bytes that can be read in a single call to Read.
+ void set_single_read_limit(std::size_t max_num_bytes);
+
+ // Sets the total number of bytes that can be skipped in a single call to
+ // Skip.
+ void set_single_skip_limit(std::uint64_t max_num_bytes);
+
+ // Sets the total number of bytes that can be read by the reader with Read.
+ // This total is considered to be cumulative for reads, but not skips.
+ // Setting this to std::numeric_limits<std::size_t>::max() will result in no
+ // extra limitation being imposed on reads.
+ void set_total_read_limit(std::size_t max_num_bytes);
+
+ // Sets the total number of bytes that can be skipped by the reader with Skip.
+ // This total is considered to be cumulative for skips, but not reads.
+ // Setting this to std::numeric_limits<std::uint64_t>::max() will result in no
+ // extra limitation being imposed on skips.
+ void set_total_skip_limit(std::uint64_t max_num_bytes);
+
+ // Sets the total number of bytes that can be read/skipped by the reader.
+ // This total is considered to be cumulative between reads and skips.
+ // Setting this to std::numeric_limits<std::uint64_t>::max() will result in no
+ // extra limitation being imposed on reads/skips.
+ void set_total_read_skip_limit(std::uint64_t max_num_bytes);
+
+ private:
+ // The maximum number of bytes to let a single call to Read return.
+ std::size_t single_read_limit_ = std::numeric_limits<std::size_t>::max();
+
+ // The maximum number of bytes to let a single call to Skip return.
+ std::uint64_t single_skip_limit_ = std::numeric_limits<std::uint64_t>::max();
+
+ // The total maximum number of bytes that can be read with multiple calls to
+ // Read.
+ std::size_t total_read_limit_ = std::numeric_limits<std::size_t>::max();
+
+ // The total maximum number of bytes that can be skipped with multiple calls
+ // to Skip.
+ std::uint64_t total_skip_limit_ = std::numeric_limits<std::uint64_t>::max();
+
+ // The total maximum number of bytes that can be read or skipped with multiple
+ // calls to Read and/or Skip.
+ std::uint64_t total_read_skip_limit_ =
+ std::numeric_limits<std::uint64_t>::max();
+
+ // The status to return when the reader has reached is maximum limit for
+ // Read/Skip and cannot read or skip any data.
+ Status return_status_when_blocked_ = Status(Status::kWouldBlock);
+
+ // The actual reader that does the real reading/skipping.
+ std::unique_ptr<Reader> impl_;
+};
+
+} // namespace webm
+
+#endif // TEST_UTILS_LIMITED_READER_H_
diff --git a/webm_parser/test_utils/mock_callback.h b/webm_parser/test_utils/mock_callback.h
new file mode 100644
index 0000000..e4e9912
--- /dev/null
+++ b/webm_parser/test_utils/mock_callback.h
@@ -0,0 +1,247 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef TEST_UTILS_MOCK_CALLBACK_H_
+#define TEST_UTILS_MOCK_CALLBACK_H_
+
+#include <cstdint>
+
+#include "gmock/gmock.h"
+
+#include "webm/callback.h"
+#include "webm/dom_types.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// A simple version of Callback that can be used with Google Mock. By default,
+// the mocked methods will call through to the corresponding Callback methods.
+class MockCallback : public Callback {
+ public:
+ MockCallback() {
+ using testing::_;
+ using testing::Invoke;
+
+ ON_CALL(*this, OnElementBegin(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnElementBeginConcrete));
+ ON_CALL(*this, OnUnknownElement(_, _, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnUnknownElementConcrete));
+ ON_CALL(*this, OnEbml(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnEbmlConcrete));
+ ON_CALL(*this, OnVoid(_, _, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnVoidConcrete));
+ ON_CALL(*this, OnSegmentBegin(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnSegmentBeginConcrete));
+ ON_CALL(*this, OnSeek(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnSeekConcrete));
+ ON_CALL(*this, OnInfo(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnInfoConcrete));
+ ON_CALL(*this, OnClusterBegin(_, _, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnClusterBeginConcrete));
+ ON_CALL(*this, OnSimpleBlockBegin(_, _, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnSimpleBlockBeginConcrete));
+ ON_CALL(*this, OnSimpleBlockEnd(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnSimpleBlockEndConcrete));
+ ON_CALL(*this, OnBlockGroupBegin(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnBlockGroupBeginConcrete));
+ ON_CALL(*this, OnBlockBegin(_, _, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnBlockBeginConcrete));
+ ON_CALL(*this, OnBlockEnd(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnBlockEndConcrete));
+ ON_CALL(*this, OnBlockGroupEnd(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnBlockGroupEndConcrete));
+ ON_CALL(*this, OnFrame(_, _, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnFrameConcrete));
+ ON_CALL(*this, OnClusterEnd(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnClusterEndConcrete));
+ ON_CALL(*this, OnTrackEntry(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnTrackEntryConcrete));
+ ON_CALL(*this, OnCuePoint(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnCuePointConcrete));
+ ON_CALL(*this, OnEditionEntry(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnEditionEntryConcrete));
+ ON_CALL(*this, OnTag(_, _))
+ .WillByDefault(Invoke(this, &MockCallback::OnTagConcrete));
+ ON_CALL(*this, OnSegmentEnd(_))
+ .WillByDefault(Invoke(this, &MockCallback::OnSegmentEndConcrete));
+ }
+
+ // Mocks for methods from Callback.
+ MOCK_METHOD2(OnElementBegin,
+ Status(const ElementMetadata& metadata, Action* action));
+
+ MOCK_METHOD3(OnUnknownElement,
+ Status(const ElementMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining));
+
+ MOCK_METHOD2(OnEbml,
+ Status(const ElementMetadata& metadata, const Ebml& ebml));
+
+ MOCK_METHOD3(OnVoid, Status(const ElementMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining));
+
+ MOCK_METHOD2(OnSegmentBegin,
+ Status(const ElementMetadata& metadata, Action* action));
+
+ MOCK_METHOD2(OnSeek,
+ Status(const ElementMetadata& metadata, const Seek& seek));
+
+ MOCK_METHOD2(OnInfo,
+ Status(const ElementMetadata& metadata, const Info& info));
+
+ MOCK_METHOD3(OnClusterBegin, Status(const ElementMetadata& metadata,
+ const Cluster& cluster, Action* action));
+
+ MOCK_METHOD3(OnSimpleBlockBegin,
+ Status(const ElementMetadata& metadata,
+ const SimpleBlock& simple_block, Action* action));
+
+ MOCK_METHOD2(OnSimpleBlockEnd, Status(const ElementMetadata& metadata,
+ const SimpleBlock& simple_block));
+
+ MOCK_METHOD2(OnBlockGroupBegin,
+ Status(const ElementMetadata& metadata, Action* action));
+
+ MOCK_METHOD3(OnBlockBegin, Status(const ElementMetadata& metadata,
+ const Block& block, Action* action));
+
+ MOCK_METHOD2(OnBlockEnd,
+ Status(const ElementMetadata& metadata, const Block& block));
+
+ MOCK_METHOD2(OnBlockGroupEnd, Status(const ElementMetadata& metadata,
+ const BlockGroup& block_group));
+
+ MOCK_METHOD3(OnFrame, Status(const FrameMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining));
+
+ MOCK_METHOD2(OnClusterEnd,
+ Status(const ElementMetadata& metadata, const Cluster& cluster));
+
+ MOCK_METHOD2(OnTrackEntry, Status(const ElementMetadata& metadata,
+ const TrackEntry& track_entry));
+
+ MOCK_METHOD2(OnCuePoint, Status(const ElementMetadata& metadata,
+ const CuePoint& cue_point));
+
+ MOCK_METHOD2(OnEditionEntry, Status(const ElementMetadata& metadata,
+ const EditionEntry& edition_entry));
+
+ MOCK_METHOD2(OnTag, Status(const ElementMetadata& metadata, const Tag& tag));
+
+ MOCK_METHOD1(OnSegmentEnd, Status(const ElementMetadata& metadata));
+
+ // Concrete implementations that the corresponding mocked method may call,
+ // provided for convenience. These methods just call through to the
+ // corrensponding methods in Callback, and provide an convenient way for the
+ // MockCallback to exhibit the same behavior as Callback.
+ Status OnElementBeginConcrete(const ElementMetadata& metadata,
+ Action* action) {
+ return Callback::OnElementBegin(metadata, action);
+ }
+
+ Status OnUnknownElementConcrete(const ElementMetadata& metadata,
+ Reader* reader,
+ std::uint64_t* bytes_remaining) {
+ return Callback::OnUnknownElement(metadata, reader, bytes_remaining);
+ }
+
+ Status OnEbmlConcrete(const ElementMetadata& metadata, const Ebml& ebml) {
+ return Callback::OnEbml(metadata, ebml);
+ }
+
+ Status OnVoidConcrete(const ElementMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining) {
+ return Callback::OnVoid(metadata, reader, bytes_remaining);
+ }
+
+ Status OnSegmentBeginConcrete(const ElementMetadata& metadata,
+ Action* action) {
+ return Callback::OnSegmentBegin(metadata, action);
+ }
+
+ Status OnSeekConcrete(const ElementMetadata& metadata, const Seek& seek) {
+ return Callback::OnSeek(metadata, seek);
+ }
+
+ Status OnInfoConcrete(const ElementMetadata& metadata, const Info& info) {
+ return Callback::OnInfo(metadata, info);
+ }
+
+ Status OnClusterBeginConcrete(const ElementMetadata& metadata,
+ const Cluster& cluster, Action* action) {
+ return Callback::OnClusterBegin(metadata, cluster, action);
+ }
+
+ Status OnSimpleBlockBeginConcrete(const ElementMetadata& metadata,
+ const SimpleBlock& simple_block,
+ Action* action) {
+ return Callback::OnSimpleBlockBegin(metadata, simple_block, action);
+ }
+
+ Status OnSimpleBlockEndConcrete(const ElementMetadata& metadata,
+ const SimpleBlock& simple_block) {
+ return Callback::OnSimpleBlockEnd(metadata, simple_block);
+ }
+
+ Status OnBlockGroupBeginConcrete(const ElementMetadata& metadata,
+ Action* action) {
+ return Callback::OnBlockGroupBegin(metadata, action);
+ }
+
+ Status OnBlockBeginConcrete(const ElementMetadata& metadata,
+ const Block& block, Action* action) {
+ return Callback::OnBlockBegin(metadata, block, action);
+ }
+
+ Status OnBlockEndConcrete(const ElementMetadata& metadata,
+ const Block& block) {
+ return Callback::OnBlockEnd(metadata, block);
+ }
+
+ Status OnBlockGroupEndConcrete(const ElementMetadata& metadata,
+ const BlockGroup& block_group) {
+ return Callback::OnBlockGroupEnd(metadata, block_group);
+ }
+
+ Status OnFrameConcrete(const FrameMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining) {
+ return Callback::OnFrame(metadata, reader, bytes_remaining);
+ }
+
+ Status OnClusterEndConcrete(const ElementMetadata& metadata,
+ const Cluster& cluster) {
+ return Callback::OnClusterEnd(metadata, cluster);
+ }
+
+ Status OnTrackEntryConcrete(const ElementMetadata& metadata,
+ const TrackEntry& track_entry) {
+ return Callback::OnTrackEntry(metadata, track_entry);
+ }
+
+ Status OnCuePointConcrete(const ElementMetadata& metadata,
+ const CuePoint& cue_point) {
+ return Callback::OnCuePoint(metadata, cue_point);
+ }
+
+ Status OnEditionEntryConcrete(const ElementMetadata& metadata,
+ const EditionEntry& edition_entry) {
+ return Callback::OnEditionEntry(metadata, edition_entry);
+ }
+
+ Status OnTagConcrete(const ElementMetadata& metadata, const Tag& tag) {
+ return Callback::OnTag(metadata, tag);
+ }
+
+ Status OnSegmentEndConcrete(const ElementMetadata& metadata) {
+ return Callback::OnSegmentEnd(metadata);
+ }
+};
+
+} // namespace webm
+
+#endif // TEST_UTILS_MOCK_CALLBACK_H_
diff --git a/webm_parser/test_utils/parser_test.h b/webm_parser/test_utils/parser_test.h
new file mode 100644
index 0000000..61d35b2
--- /dev/null
+++ b/webm_parser/test_utils/parser_test.h
@@ -0,0 +1,108 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef TEST_UTILS_PARSER_TEST_H_
+#define TEST_UTILS_PARSER_TEST_H_
+
+#include <cstdint>
+#include <new>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/limited_reader.h"
+#include "test_utils/mock_callback.h"
+#include "webm/buffer_reader.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+namespace webm {
+
+// Base class for unit tests that test an instance of the Parser inteface. The
+// template parameter T is the parser class being tested.
+template <typename T>
+class ParserTest : public testing::Test {
+ public:
+ // Sets the reader's internal buffer to the given buffer.
+ virtual void SetReaderData(std::vector<std::uint8_t> data) {
+ reader_ = BufferReader(std::move(data));
+ }
+
+ // Destroys and recreates the parser, forwarding the arguments to the
+ // constructor. This is primarily useful for tests that require the parser to
+ // have different constructor parameters.
+ template <typename... Args>
+ void ResetParser(Args&&... args) {
+ parser_.~T();
+ new (&parser_) T(std::forward<Args>(args)...);
+ }
+
+ // Calls Feed() on the parser, making sure it completes successfully and reads
+ // size number of bytes.
+ virtual void ParseAndVerify(std::uint64_t size) {
+ std::uint64_t num_bytes_read = 0;
+ const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read);
+ ASSERT_EQ(Status::kOkCompleted, status.code);
+ ASSERT_EQ(size, num_bytes_read);
+ }
+
+ // Calls Feed() on the parser, making sure it completes successfully and reads
+ // all the data available in the reader.
+ virtual void ParseAndVerify() { ParseAndVerify(reader_.size()); }
+
+ // Similar to ParseAndVerify(), but instead artificially limits the reader to
+ // providing one byte per call to Feed(). If Feed() returns
+ // Status::kWouldBlock or Status::kOkPartial, Feed() will be called again
+ // (feeding it another byte).
+ virtual void IncrementalParseAndVerify() {
+ const std::uint64_t expected_num_bytes_read = reader_.size();
+
+ webm::LimitedReader limited_reader(
+ std::unique_ptr<webm::Reader>(new BufferReader(std::move(reader_))));
+
+ Status status;
+ std::uint64_t num_bytes_read = 0;
+ do {
+ limited_reader.set_total_read_skip_limit(1);
+ std::uint64_t local_num_bytes_read = 0;
+ status = parser_.Feed(&callback_, &limited_reader, &local_num_bytes_read);
+ num_bytes_read += local_num_bytes_read;
+ const std::uint64_t kMinBytesRead = 1;
+ ASSERT_GE(kMinBytesRead, local_num_bytes_read);
+ } while (status.code == Status::kWouldBlock ||
+ status.code == Status::kOkPartial);
+
+ ASSERT_EQ(Status::kOkCompleted, status.code);
+ ASSERT_EQ(expected_num_bytes_read, num_bytes_read);
+ }
+
+ // Calls Feed() on the parser, making sure it returns the expected status
+ // code.
+ virtual void ParseAndExpectResult(Status::Code expected) {
+ std::uint64_t num_bytes_read = 0;
+ const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read);
+ ASSERT_EQ(expected, status.code);
+ }
+
+ protected:
+ // These members are protected (not private) so unit tests have access to
+ // them. This is intentional.
+
+ // The parser that the unit tests will be testing.
+ T parser_;
+
+ // The callback that is used during parsing.
+ testing::NiceMock<MockCallback> callback_;
+
+ // The reader used for feeding data into the parser.
+ BufferReader reader_;
+};
+
+} // namespace webm
+
+#endif // TEST_UTILS_PARSER_TEST_H_
diff --git a/webm_parser/tests/audio_parser_test.cc b/webm_parser/tests/audio_parser_test.cc
new file mode 100644
index 0000000..3a609b0
--- /dev/null
+++ b/webm_parser/tests/audio_parser_test.cc
@@ -0,0 +1,161 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/audio_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::Audio;
+using webm::AudioParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class AudioParserTest : public ElementParserTest<AudioParser, Id::kAudio> {};
+
+TEST_F(AudioParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const Audio audio = parser_.value();
+
+ EXPECT_FALSE(audio.sampling_frequency.is_present());
+ EXPECT_EQ(8000, audio.sampling_frequency.value());
+
+ EXPECT_FALSE(audio.output_frequency.is_present());
+ EXPECT_EQ(8000, audio.output_frequency.value());
+
+ EXPECT_FALSE(audio.channels.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), audio.channels.value());
+
+ EXPECT_FALSE(audio.bit_depth.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), audio.bit_depth.value());
+}
+
+TEST_F(AudioParserTest, DefaultValues) {
+ SetReaderData({
+ 0xB5, // ID = 0x85 (SamplingFrequency).
+ 0x40, 0x00, // Size = 0.
+
+ 0x78, 0xB5, // ID = 0x78B5 (OutputSamplingFrequency).
+ 0x80, // Size = 0.
+
+ 0x9F, // ID = 0x9F (Channels).
+ 0x40, 0x00, // Size = 0.
+
+ 0x62, 0x64, // ID = 0x6264 (BitDepth).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const Audio audio = parser_.value();
+
+ EXPECT_TRUE(audio.sampling_frequency.is_present());
+ EXPECT_EQ(8000, audio.sampling_frequency.value());
+
+ EXPECT_TRUE(audio.output_frequency.is_present());
+ EXPECT_EQ(8000, audio.output_frequency.value());
+
+ EXPECT_TRUE(audio.channels.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), audio.channels.value());
+
+ EXPECT_TRUE(audio.bit_depth.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), audio.bit_depth.value());
+}
+
+TEST_F(AudioParserTest, CustomValues) {
+ SetReaderData({
+ 0xB5, // ID = 0x85 (SamplingFrequency).
+ 0x84, // Size = 4.
+ 0x3F, 0x80, 0x00, 0x00, // Body (value = 1.0f).
+
+ 0x78, 0xB5, // ID = 0x78B5 (OutputSamplingFrequency).
+ 0x84, // Size = 4.
+ 0x3F, 0xDD, 0xB3, 0xD7, // Body (value = 1.73205077648162841796875f).
+
+ 0x9F, // ID = 0x9F (Channels).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0x62, 0x64, // ID = 0x6264 (BitDepth).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+ });
+
+ ParseAndVerify();
+
+ const Audio audio = parser_.value();
+
+ EXPECT_TRUE(audio.sampling_frequency.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), audio.sampling_frequency.value());
+
+ EXPECT_TRUE(audio.output_frequency.is_present());
+ EXPECT_EQ(1.73205077648162841796875, audio.output_frequency.value());
+
+ EXPECT_TRUE(audio.channels.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), audio.channels.value());
+
+ EXPECT_TRUE(audio.bit_depth.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), audio.bit_depth.value());
+}
+
+TEST_F(AudioParserTest, AbsentOutputSamplingFrequency) {
+ SetReaderData({
+ 0xB5, // ID = 0x85 (SamplingFrequency).
+ 0x84, // Size = 4.
+ 0x3F, 0x80, 0x00, 0x00, // Body (value = 1.0f).
+ });
+
+ ParseAndVerify();
+
+ const Audio audio = parser_.value();
+
+ EXPECT_TRUE(audio.sampling_frequency.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), audio.sampling_frequency.value());
+
+ EXPECT_FALSE(audio.output_frequency.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), audio.output_frequency.value());
+
+ EXPECT_FALSE(audio.channels.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), audio.channels.value());
+
+ EXPECT_FALSE(audio.bit_depth.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), audio.bit_depth.value());
+}
+
+TEST_F(AudioParserTest, DefaultOutputSamplingFrequency) {
+ SetReaderData({
+ 0xB5, // ID = 0x85 (SamplingFrequency).
+ 0x84, // Size = 4.
+ 0x3F, 0x80, 0x00, 0x00, // Body (value = 1.0f).
+
+ 0x78, 0xB5, // ID = 0x78B5 (OutputSamplingFrequency).
+ 0x10, 0x00, 0x00, 0x00, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const Audio audio = parser_.value();
+
+ EXPECT_TRUE(audio.sampling_frequency.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), audio.sampling_frequency.value());
+
+ EXPECT_TRUE(audio.output_frequency.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), audio.output_frequency.value());
+
+ EXPECT_FALSE(audio.channels.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), audio.channels.value());
+
+ EXPECT_FALSE(audio.bit_depth.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), audio.bit_depth.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/bit_utils_test.cc b/webm_parser/tests/bit_utils_test.cc
new file mode 100644
index 0000000..b3e3132
--- /dev/null
+++ b/webm_parser/tests/bit_utils_test.cc
@@ -0,0 +1,24 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/bit_utils.h"
+
+#include "gtest/gtest.h"
+
+using webm::CountLeadingZeros;
+
+namespace {
+
+class BitUtilsTest : public testing::Test {};
+
+TEST_F(BitUtilsTest, CountLeadingZeros) {
+ EXPECT_EQ(8, CountLeadingZeros(0x00));
+ EXPECT_EQ(4, CountLeadingZeros(0x0f));
+ EXPECT_EQ(0, CountLeadingZeros(0xf0));
+}
+
+} // namespace
diff --git a/webm_parser/tests/block_additions_parser_test.cc b/webm_parser/tests/block_additions_parser_test.cc
new file mode 100644
index 0000000..290b3f5
--- /dev/null
+++ b/webm_parser/tests/block_additions_parser_test.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/block_additions_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::BlockAdditions;
+using webm::BlockAdditionsParser;
+using webm::BlockMore;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class BlockAdditionsParserTest
+ : public ElementParserTest<BlockAdditionsParser, Id::kBlockAdditions> {};
+
+TEST_F(BlockAdditionsParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const BlockAdditions block_additions = parser_.value();
+
+ const std::size_t kExpectedBlockMoresSize = 0;
+ EXPECT_EQ(kExpectedBlockMoresSize, block_additions.block_mores.size());
+}
+
+TEST_F(BlockAdditionsParserTest, DefaultValues) {
+ SetReaderData({
+ 0xA6, // ID = 0xA6 (BlockMore).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const BlockAdditions block_additions = parser_.value();
+
+ ASSERT_EQ(static_cast<std::size_t>(1), block_additions.block_mores.size());
+ EXPECT_TRUE(block_additions.block_mores[0].is_present());
+ EXPECT_EQ(BlockMore{}, block_additions.block_mores[0].value());
+}
+
+TEST_F(BlockAdditionsParserTest, CustomValues) {
+ SetReaderData({
+ 0xA6, // ID = 0xA6 (BlockMore).
+ 0x83, // Size = 3.
+
+ 0xEE, // ID = 0xEE (BlockAddID).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0xA6, // ID = 0xA6 (BlockMore).
+ 0x83, // Size = 3.
+
+ 0xEE, // ID = 0xEE (BlockAddID).
+ 0x81, // Size = 1.
+ 0x03, // Body (value = 3).
+ });
+
+ ParseAndVerify();
+
+ const BlockAdditions block_additions = parser_.value();
+
+ BlockMore expected;
+
+ ASSERT_EQ(static_cast<std::size_t>(2), block_additions.block_mores.size());
+ expected.id.Set(2, true);
+ EXPECT_TRUE(block_additions.block_mores[0].is_present());
+ EXPECT_EQ(expected, block_additions.block_mores[0].value());
+ expected.id.Set(3, true);
+ EXPECT_TRUE(block_additions.block_mores[1].is_present());
+ EXPECT_EQ(expected, block_additions.block_mores[1].value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/block_group_parser_test.cc b/webm_parser/tests/block_group_parser_test.cc
new file mode 100644
index 0000000..d5b0088
--- /dev/null
+++ b/webm_parser/tests/block_group_parser_test.cc
@@ -0,0 +1,234 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/block_group_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using testing::_;
+using testing::InSequence;
+using testing::NotNull;
+using testing::Return;
+
+using webm::Block;
+using webm::BlockAdditions;
+using webm::BlockGroup;
+using webm::BlockGroupParser;
+using webm::BlockMore;
+using webm::ElementParserTest;
+using webm::Id;
+using webm::Slices;
+using webm::Status;
+using webm::TimeSlice;
+using webm::VirtualBlock;
+
+namespace {
+
+class BlockGroupParserTest
+ : public ElementParserTest<BlockGroupParser, Id::kBlockGroup> {};
+
+TEST_F(BlockGroupParserTest, InvalidBlock) {
+ SetReaderData({
+ 0xA1, // ID = 0xA1 (Block).
+ 0x80, // Size = 0.
+ });
+
+ EXPECT_CALL(callback_, OnBlockGroupBegin(_, _)).Times(1);
+ EXPECT_CALL(callback_, OnBlockGroupEnd(_, _)).Times(0);
+
+ ParseAndExpectResult(Status::kInvalidElementSize);
+}
+
+TEST_F(BlockGroupParserTest, InvalidVirtualBlock) {
+ SetReaderData({
+ 0xA2, // ID = 0xA2 (BlockVirtual).
+ 0x80, // Size = 0.
+ });
+
+ EXPECT_CALL(callback_, OnBlockGroupBegin(_, _)).Times(1);
+ EXPECT_CALL(callback_, OnBlockGroupEnd(_, _)).Times(0);
+
+ ParseAndExpectResult(Status::kInvalidElementSize);
+}
+
+TEST_F(BlockGroupParserTest, DefaultParse) {
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnBlockGroupBegin(metadata_, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnBlockGroupEnd(metadata_, BlockGroup{})).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(BlockGroupParserTest, DefaultActionIsRead) {
+ {
+ InSequence dummy;
+
+ // This intentionally does not set the action and relies on the parser using
+ // a default action value of kRead.
+ EXPECT_CALL(callback_, OnBlockGroupBegin(metadata_, NotNull()))
+ .WillOnce(Return(Status(Status::kOkCompleted)));
+ EXPECT_CALL(callback_, OnBlockGroupEnd(metadata_, BlockGroup{})).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(BlockGroupParserTest, DefaultValues) {
+ SetReaderData({
+ 0xA1, // ID = 0xA1 (Block).
+ 0x85, // Size = 5.
+ 0x81, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x00, // Flags = 0.
+ 0x00, // Frame 0.
+
+ 0xA2, // ID = 0xA2 (BlockVirtual).
+ 0x84, // Size = 4.
+ 0x81, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x00, // Flags = 0.
+
+ 0x75, 0xA1, // ID = 0x75A1 (BlockAdditions).
+ 0x80, // Size = 0.
+
+ 0x9B, // ID = 0x9B (BlockDuration).
+ 0x40, 0x00, // Size = 0.
+
+ 0xFB, // ID = 0xFB (ReferenceBlock).
+ 0x40, 0x00, // Size = 0.
+
+ 0x75, 0xA2, // ID = 0x75A2 (DiscardPadding).
+ 0x80, // Size = 0.
+
+ 0x8E, // ID = 0x8E (Slices).
+ 0x40, 0x00, // Size = 0.
+ });
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnBlockGroupBegin(metadata_, NotNull())).Times(1);
+
+ BlockGroup block_group;
+ // Blocks and VirtualBlocks don't have any kind of default defined, so they
+ // can't have an element state of kPresentAsDefault.
+ Block block{};
+ block.track_number = 1;
+ block.num_frames = 1;
+ block.is_visible = true;
+ block_group.block.Set(block, true);
+
+ EXPECT_CALL(callback_, OnBlockBegin(_, block, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnFrame(_, NotNull(), NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnBlockEnd(_, block_group.block.value())).Times(1);
+
+ VirtualBlock virtual_block{};
+ virtual_block.track_number = 1;
+ block_group.virtual_block.Set(virtual_block, true);
+
+ block_group.additions.Set({}, true);
+ block_group.duration.Set(0, true);
+ block_group.references.emplace_back(0, true);
+ block_group.discard_padding.Set(0, true);
+ block_group.slices.Set({}, true);
+
+ EXPECT_CALL(callback_, OnBlockGroupEnd(metadata_, block_group)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(BlockGroupParserTest, CustomValues) {
+ SetReaderData({
+ 0xA1, // ID = 0xA1 (Block).
+ 0x85, // Size = 5.
+ 0x82, // Track number = 2.
+ 0x00, 0x00, // Timecode = 0.
+ 0x00, // Flags = 0.
+ 0x00, // Frame 0.
+
+ 0xA2, // ID = 0xA2 (BlockVirtual).
+ 0x84, // Size = 4.
+ 0x83, // Track number = 3.
+ 0x00, 0x00, // Timecode = 0.
+ 0x00, // Flags = 0.
+
+ 0x75, 0xA1, // ID = 0x75A1 (BlockAdditions).
+ 0x83, // Size = 3.
+
+ 0xA6, // ID = 0xA6 (BlockMore).
+ 0x40, 0x00, // Size = 0.
+
+ 0x9B, // ID = 0x9B (BlockDuration).
+ 0x40, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xFB, // ID = 0xFB (ReferenceBlock).
+ 0x40, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xFB, // ID = 0xFB (ReferenceBlock).
+ 0x40, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0x75, 0xA2, // ID = 0x75A2 (DiscardPadding).
+ 0x81, // Size = 1.
+ 0xFF, // Body (value = -1).
+
+ 0x8E, // ID = 0x8E (Slices).
+ 0x40, 0x03, // Size = 3.
+
+ 0xE8, // ID = 0xE8 (TimeSlice).
+ 0x40, 0x00, // Size = 0.
+ });
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnBlockGroupBegin(metadata_, NotNull())).Times(1);
+
+ BlockGroup block_group;
+ // Blocks and VirtualBlocks don't have any kind of default defined, so they
+ // can't have an element state of kPresentAsDefault.
+ Block block{};
+ block.track_number = 2;
+ block.num_frames = 1;
+ block.is_visible = true;
+ block_group.block.Set(block, true);
+
+ VirtualBlock virtual_block{};
+ virtual_block.track_number = 3;
+ block_group.virtual_block.Set(virtual_block, true);
+
+ BlockAdditions block_additions;
+ block_additions.block_mores.emplace_back(BlockMore{}, true);
+ block_group.additions.Set(block_additions, true);
+
+ block_group.duration.Set(1, true);
+ block_group.references.emplace_back(1, true);
+ block_group.references.emplace_back(2, true);
+ block_group.discard_padding.Set(-1, true);
+
+ Slices slices;
+ slices.slices.emplace_back(TimeSlice{}, true);
+ block_group.slices.Set(slices, true);
+
+ EXPECT_CALL(callback_, OnBlockGroupEnd(metadata_, block_group)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/block_header_parser_test.cc b/webm_parser/tests/block_header_parser_test.cc
new file mode 100644
index 0000000..3f06dc3
--- /dev/null
+++ b/webm_parser/tests/block_header_parser_test.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/block_header_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/parser_test.h"
+#include "webm/status.h"
+
+using webm::BlockHeader;
+using webm::BlockHeaderParser;
+using webm::ParserTest;
+
+namespace {
+
+class BlockHeaderParserTest : public ParserTest<BlockHeaderParser> {};
+
+TEST_F(BlockHeaderParserTest, ValidBlock) {
+ SetReaderData({
+ 0x81, // Track number = 1.
+ 0x12, 0x34, // Timecode = 4660.
+ 0x00, // Flags.
+ });
+
+ ParseAndVerify();
+
+ const BlockHeader& block_header = parser_.value();
+
+ EXPECT_EQ(static_cast<std::uint64_t>(1), block_header.track_number);
+ EXPECT_EQ(0x1234, block_header.timecode);
+ EXPECT_EQ(0x00, block_header.flags);
+}
+
+TEST_F(BlockHeaderParserTest, IncrementalParse) {
+ SetReaderData({
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // Track number = 2.
+ 0xFF, 0xFE, // Timecode = -2.
+ 0xFF, // Flags.
+ });
+
+ IncrementalParseAndVerify();
+
+ const BlockHeader& block_header = parser_.value();
+
+ EXPECT_EQ(static_cast<std::uint64_t>(2), block_header.track_number);
+ EXPECT_EQ(-2, block_header.timecode);
+ EXPECT_EQ(0xFF, block_header.flags);
+}
+
+} // namespace
diff --git a/webm_parser/tests/block_more_parser_test.cc b/webm_parser/tests/block_more_parser_test.cc
new file mode 100644
index 0000000..41c892c
--- /dev/null
+++ b/webm_parser/tests/block_more_parser_test.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/block_more_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::BlockMore;
+using webm::BlockMoreParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class BlockMoreParserTest
+ : public ElementParserTest<BlockMoreParser, Id::kBlockMore> {};
+
+TEST_F(BlockMoreParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const BlockMore block_more = parser_.value();
+
+ EXPECT_FALSE(block_more.id.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), block_more.id.value());
+
+ EXPECT_FALSE(block_more.data.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{}, block_more.data.value());
+}
+
+TEST_F(BlockMoreParserTest, DefaultValues) {
+ SetReaderData({
+ 0xEE, // ID = 0xEE (BlockAddID).
+ 0x80, // Size = 0.
+
+ 0xA5, // ID = 0xA5 (BlockAdditional).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const BlockMore block_more = parser_.value();
+
+ EXPECT_TRUE(block_more.id.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), block_more.id.value());
+
+ EXPECT_TRUE(block_more.data.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{}, block_more.data.value());
+}
+
+TEST_F(BlockMoreParserTest, CustomValues) {
+ SetReaderData({
+ 0xEE, // ID = 0xEE (BlockAddID).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0xA5, // ID = 0xA5 (BlockAdditional).
+ 0x81, // Size = 1.
+ 0x00, // Body.
+ });
+
+ ParseAndVerify();
+
+ const BlockMore block_more = parser_.value();
+
+ EXPECT_TRUE(block_more.id.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), block_more.id.value());
+
+ EXPECT_TRUE(block_more.data.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{0x00}, block_more.data.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/block_parser_test.cc b/webm_parser/tests/block_parser_test.cc
new file mode 100644
index 0000000..5e414e4
--- /dev/null
+++ b/webm_parser/tests/block_parser_test.cc
@@ -0,0 +1,865 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/block_parser.h"
+
+#include <cstdint>
+#include <memory>
+#include <type_traits>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "src/parser_utils.h"
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using testing::_;
+using testing::Between;
+using testing::DoAll;
+using testing::Exactly;
+using testing::InSequence;
+using testing::Invoke;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgPointee;
+
+using webm::Action;
+using webm::Block;
+using webm::BlockParser;
+using webm::ElementParserTest;
+using webm::FrameMetadata;
+using webm::Id;
+using webm::kUnknownElementSize;
+using webm::Lacing;
+using webm::ReadByte;
+using webm::Reader;
+using webm::SimpleBlock;
+using webm::SimpleBlockParser;
+using webm::Status;
+
+namespace {
+
+// Represents a single block and its expected parsing results for use in tests.
+struct TestData {
+ // Block data.
+ std::vector<std::uint8_t> data;
+
+ // Expected results.
+ std::uint64_t expected_track_number;
+ std::int16_t expected_timecode;
+ Lacing expected_lacing;
+ bool expected_is_visible;
+ bool expected_is_key_frame;
+ bool expected_is_discardable;
+ int expected_num_frames;
+
+ std::uint64_t expected_frame_start_position;
+ std::vector<std::uint64_t> expected_frame_sizes;
+};
+
+// Test data for an EBML-laced block containing only one frame.
+const TestData ebml_lacing_one_frame = {
+ // Data.
+ {
+ 0x81, // Track number = 1.
+ 0x00,
+ 0x00, // Timecode = 0.
+ 0x86, // Flags = key_frame | ebml_lacing.
+ 0x00, // Lace count - 1 = 0 (1 frame).
+
+ // Lace data (1 frame).
+ // Frame 0.
+ 0x00,
+ },
+
+ // Expected results.
+ 1, // expected_track_number
+ 0, // expected_timecode
+ Lacing::kEbml, // expected_lacing
+ true, // expected_is_visible
+ true, // expected_is_key_frame
+ false, // expected_is_discardable
+ 1, // expected_num_frames
+
+ 5, // expected_frame_start_position
+ // expected_frame_sizes
+ {1},
+};
+
+// Test data for a Xiph-laced block containing only one frame.
+const TestData xiph_lacing_one_frame = {
+ // Data.
+ {
+ 0x81, // Track number = 1.
+ 0x00,
+ 0x00, // Timecode = 0.
+ 0x82, // Flags = key_frame | xiph_lacing.
+ 0x00, // Lace count - 1 = 0 (1 frame).
+
+ // Lace data (1 frame).
+ // Frame 0.
+ 0x00,
+ },
+
+ // Expected results.
+ 1, // expected_track_number
+ 0, // expected_timecode
+ Lacing::kXiph, // expected_lacing
+ true, // expected_is_visible
+ true, // expected_is_key_frame
+ false, // expected_is_discardable
+ 1, // expected_num_frames
+
+ 5, // expected_frame_start_position
+ // expected_frame_sizes
+ {1},
+};
+
+// Test data for a fixed-laced block containing only one frame.
+const TestData fixed_lacing_one_frame = {
+ // Data.
+ {
+ 0x81, // Track number = 1.
+ 0x00,
+ 0x00, // Timecode = 0.
+ 0x84, // Flags = key_frame | fixed_lacing.
+ 0x00, // Lace count - 1 = 0 (1 frame).
+
+ // Lace data (1 frame).
+ 0x00,
+ },
+
+ // Expected results.
+ 1, // expected_track_number
+ 0, // expected_timecode
+ Lacing::kFixed, // expected_lacing
+ true, // expected_is_visible
+ true, // expected_is_key_frame
+ false, // expected_is_discardable
+ 1, // expected_num_frames
+
+ 5, // expected_frame_start_position
+ // expected_frame_sizes
+ {1},
+};
+
+// Test data for an EBML-laced block.
+// clang-format off
+const TestData ebml_lacing = {
+ // Data.
+ {
+ 0x81, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x86, // Flags = key_frame | ebml_lacing.
+ 0x05, // Lace count - 1 = 5 (6 frames).
+
+ // Lace data (6 frames).
+ 0xFF, // Lace 0 size = 127.
+ 0x5F, 0x81, // Lace 1 size = 1.
+ 0xC0, // Lace 2 size = 2.
+ 0xFF, // Lace 3 size = 66.
+ 0x81, // Lace 4 size = 4.
+ // Lace 5 size inferred to be 5.
+
+ // Lace data (6 frames).
+ // Frame 0.
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+ // Frame 1.
+ 0x01,
+
+ // Frame 2.
+ 0x02, 0x02,
+
+ // Frame 3.
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+ 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
+
+ // Frame 4.
+ 0x04, 0x04, 0x04, 0x04,
+
+ // Frame 5.
+ 0x05, 0x05, 0x05, 0x05, 0x05,
+ },
+
+ // Expected results.
+ 1, // expected_track_number
+ 0, // expected_timecode
+ Lacing::kEbml, // expected_lacing
+ true, // expected_is_visible
+ true, // expected_is_key_frame
+ false, // expected_is_discardable
+ 6, // expected_num_frames
+
+ 11, // expected_frame_start_position
+ // expected_frame_sizes
+ {127, 1, 2, 66, 4, 5},
+};
+
+// Test data for a Xiph-laced block.
+const TestData xiph_lacing = {
+ // Data.
+ {
+ 0x81, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x82, // Flags = key_frame | xiph_lacing.
+ 0x03, // Lace count - 1 = 3 (4 frames).
+
+ // Lace sizes.
+ 0xFF, 0xFF, 0x00, // Lace 0 size = 510.
+ 0xFF, 0x01, // Lace 1 size = 256.
+ 0x02, // Lace 2 size = 2.
+ // Lace 3 size inferred to be 3.
+
+ // Lace data (4 frames).
+ // Frame 0.
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+ // Frame 1.
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+ 0x01, 0x01, 0x01, 0x01,
+
+ // Frame 2.
+ 0x02, 0x02,
+
+ // Frame 3.
+ 0x03, 0x03, 0x03,
+ },
+
+ // Expected results.
+ 1, // expected_track_number
+ 0, // expected_timecode
+ Lacing::kXiph, // expected_lacing
+ true, // expected_is_visible
+ true, // expected_is_key_frame
+ false, // expected_is_discardable
+ 4, // expected_num_frames
+
+ 11, // expected_frame_start_position
+ // expected_frame_sizes
+ {510, 256, 2, 3},
+};
+// clang-format on
+
+// Test data for a fixed-laced block.
+const TestData fixed_lacing = {
+ // Data.
+ {
+ 0x81, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x84, // Flags = key_frame | fixed_lacing.
+ 0x03, // Lace count - 1 = 3 (4 frames).
+
+ // Lace data (4 frames).
+ 0x00, 0x00, // Frame 0.
+ 0x01, 0x01, // Frame 1.
+ 0x02, 0x02, // Frame 2.
+ 0x03, 0x03, // Frame 3.
+ },
+
+ // Expected results.
+ 1, // expected_track_number
+ 0, // expected_timecode
+ Lacing::kFixed, // expected_lacing
+ true, // expected_is_visible
+ true, // expected_is_key_frame
+ false, // expected_is_discardable
+ 4, // expected_num_frames
+
+ 5, // expected_frame_start_position
+ // expected_frame_sizes
+ {2, 2, 2, 2},
+};
+
+// Test data for a block that has no lacing.
+const TestData no_lacing = {
+ // Data.
+ {
+ 0x40, 0x01, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x80, // Flags = key_frame.
+
+ // Lace data (1 frame).
+ 0x00, 0x00, 0x00, // Frame 0.
+ },
+
+ // Expected results.
+ 1, // expected_track_number
+ 0, // expected_timecode
+ Lacing::kNone, // expected_lacing
+ true, // expected_is_visible
+ true, // expected_is_key_frame
+ false, // expected_is_discardable
+ 1, // expected_num_frames
+
+ 5, // expected_frame_start_position
+ // expected_frame_sizes
+ {3},
+};
+
+// Test data for a block that has no flags set in the header.
+const TestData no_flags = {
+ // Data.
+ {
+ 0x81, // Track number = 1.
+ 0x00,
+ 0x00, // Timecode = 0.
+ 0x00, // Flags = 0.
+
+ // Lace data (1 frame).
+ 0x00,
+ },
+
+ // Expected results.
+ 1, // expected_track_number
+ 0, // expected_timecode
+ Lacing::kNone, // expected_lacing
+ true, // expected_is_visible
+ false, // expected_is_key_frame
+ false, // expected_is_discardable
+ 1, // expected_num_frames
+
+ 4, // expected_frame_start_position
+ // expected_frame_sizes
+ {1},
+};
+
+// Test data for a block that has all Block (ID = Id::kBlock (0xA1)) flags set
+// in the header (and no other flags).
+const TestData block_flags = {
+ // Data.
+ {
+ 0x82, // Track number = 2.
+ 0xFE,
+ 0xDC, // Timecode = -292.
+ 0x08, // Flags = invisible.
+
+ // Lace data (1 frame).
+ 0x00,
+ },
+
+ // Expected results.
+ 2, // expected_track_number
+ -292, // expected_timecode
+ Lacing::kNone, // expected_lacing
+ false, // expected_is_visible
+ false, // expected_is_key_frame
+ false, // expected_is_discardable
+ 1, // expected_num_frames
+
+ 4, // expected_frame_start_position
+ // expected_frame_sizes
+ {1},
+};
+
+// Test data for a block that has all SimpleBlock (ID = Id::kSimpleBlock (0xA3))
+// flags set in the header.
+const TestData simple_block_flags = {
+ // Data.
+ {
+ 0x41,
+ 0x23, // Track number = 291.
+ 0x12,
+ 0x34, // Timecode = 4660.
+ 0x89, // Flags = key_frame | invisible | discardable.
+
+ // Lace data (1 frame).
+ 0x00,
+ },
+
+ // Expected results.
+ 291, // expected_track_number
+ 4660, // expected_timecode
+ Lacing::kNone, // expected_lacing
+ false, // expected_is_visible
+ true, // expected_is_key_frame
+ true, // expected_is_discardable
+ 1, // expected_num_frames
+
+ 5, // expected_frame_start_position
+ // expected_frame_sizes
+ {1},
+};
+
+// Checks that the Block matches the expected results in the TestData.
+void ValidateBlock(const TestData& test_data, const Block& actual) {
+ EXPECT_EQ(test_data.expected_track_number, actual.track_number);
+ EXPECT_EQ(test_data.expected_timecode, actual.timecode);
+ EXPECT_EQ(test_data.expected_is_visible, actual.is_visible);
+ EXPECT_EQ(test_data.expected_lacing, actual.lacing);
+ EXPECT_EQ(test_data.expected_num_frames, actual.num_frames);
+}
+
+// Checks that the SimpleBlock matches the expected results in the TestData.
+void ValidateBlock(const TestData& test_data, const SimpleBlock& actual) {
+ ValidateBlock(test_data, static_cast<const Block&>(actual));
+ EXPECT_EQ(test_data.expected_is_key_frame, actual.is_key_frame);
+ EXPECT_EQ(test_data.expected_is_discardable, actual.is_discardable);
+}
+
+// Constructs a SimpleBlock populated with the expected results for this test.
+SimpleBlock ExpectedSimpleBlock(const TestData& test_data) {
+ SimpleBlock expected;
+ expected.track_number = test_data.expected_track_number;
+ expected.timecode = test_data.expected_timecode;
+ expected.lacing = test_data.expected_lacing;
+ expected.is_visible = test_data.expected_is_visible;
+ expected.is_key_frame = test_data.expected_is_key_frame;
+ expected.is_discardable = test_data.expected_is_discardable;
+ expected.num_frames = test_data.expected_num_frames;
+ return expected;
+}
+
+// Simple functor that can be used for Callback::OnFrame() that will validate
+// the frame metadata and frame byte values.
+struct FrameHandler {
+ // The expected value for the frame metadata.
+ FrameMetadata expected_metadata;
+
+ // The expected value for each byte in the frame.
+ std::uint8_t expected_frame_byte_value;
+
+ // Can be used for Callback::OnFrame() to consume data from the reader and
+ // validate the results.
+ Status operator()(const FrameMetadata& metadata, Reader* reader,
+ std::uint64_t* bytes_remaining) const {
+ EXPECT_EQ(expected_metadata, metadata);
+
+ std::uint8_t frame_byte_value;
+ Status status;
+ do {
+ status = ReadByte(reader, &frame_byte_value);
+ if (!status.completed_ok()) {
+ break;
+ }
+
+ EXPECT_EQ(expected_frame_byte_value, frame_byte_value);
+
+ --*bytes_remaining;
+ } while (*bytes_remaining > 0);
+
+ return status;
+ }
+};
+
+template <typename T, Id id>
+class BasicBlockParserTest : public ElementParserTest<T, id> {
+ public:
+ // Sets expectations for a normal (i.e. successful parse) test.
+ void SetExpectations(const TestData& test_data, bool incremental,
+ bool set_action) {
+ InSequence dummy;
+
+ const SimpleBlock expected_simple_block = ExpectedSimpleBlock(test_data);
+ const Block expected_block = ExpectedSimpleBlock(test_data);
+
+ FrameMetadata metadata = FirstFrameMetadata(test_data);
+ if (std::is_same<T, SimpleBlockParser>::value) {
+ auto& expectation = EXPECT_CALL(
+ callback_, OnSimpleBlockBegin(metadata.parent_element,
+ expected_simple_block, NotNull()));
+ if (set_action) {
+ expectation.Times(1);
+ } else {
+ expectation.WillOnce(Return(Status(Status::kOkCompleted)));
+ }
+ EXPECT_CALL(callback_, OnBlockBegin(_, _, _)).Times(0);
+ } else {
+ auto& expectation = EXPECT_CALL(
+ callback_,
+ OnBlockBegin(metadata.parent_element, expected_block, NotNull()));
+ if (set_action) {
+ expectation.Times(1);
+ } else {
+ expectation.WillOnce(Return(Status(Status::kOkCompleted)));
+ }
+ EXPECT_CALL(callback_, OnSimpleBlockBegin(_, _, _)).Times(0);
+ }
+
+ std::uint8_t expected_frame_byte_value = 0;
+ for (const std::uint64_t frame_size : test_data.expected_frame_sizes) {
+ metadata.size = frame_size;
+ const FrameHandler frame_handler = {metadata, expected_frame_byte_value};
+
+ // Incremental parsing will call OnFrame once for every byte, plus
+ // maybe one more time if the first call reads zero bytes (if the reader
+ // is blocked).
+ const int this_frame_size = static_cast<int>(frame_size);
+ EXPECT_CALL(callback_, OnFrame(metadata, NotNull(), NotNull()))
+ .Times(incremental ? Between(this_frame_size, this_frame_size + 1)
+ : Exactly(1))
+ .WillRepeatedly(Invoke(frame_handler));
+
+ metadata.position += metadata.size;
+ ++expected_frame_byte_value;
+ }
+
+ if (std::is_same<T, SimpleBlockParser>::value) {
+ EXPECT_CALL(callback_, OnSimpleBlockEnd(metadata.parent_element,
+ expected_simple_block))
+ .Times(1);
+ EXPECT_CALL(callback_, OnBlockEnd(_, _)).Times(0);
+ } else {
+ EXPECT_CALL(callback_,
+ OnBlockEnd(metadata.parent_element, expected_block))
+ .Times(1);
+ EXPECT_CALL(callback_, OnSimpleBlockEnd(_, _)).Times(0);
+ }
+ }
+
+ // Runs a single test using the provided test data.
+ void RunTest(const TestData& test_data) {
+ SetReaderData(test_data.data);
+ SetExpectations(test_data, false, true);
+
+ ParseAndVerify();
+
+ ValidateBlock(test_data, parser_.value());
+ }
+
+ // Same as RunTest(), except it forces parsers to parse one byte at a time.
+ void RunIncrementalTest(const TestData& test_data) {
+ SetReaderData(test_data.data);
+ SetExpectations(test_data, true, true);
+
+ IncrementalParseAndVerify();
+
+ ValidateBlock(test_data, parser_.value());
+ }
+
+ // Tests invalid element sizes.
+ void TestInvalidElementSize() {
+ TestInit(0, Status::kInvalidElementSize);
+ TestInit(4, Status::kInvalidElementSize);
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+ }
+
+ // Tests invalid blocks by feeding only the header of the block into the
+ // parser.
+ void TestInvalidHeaderOnly(const TestData& test_data) {
+ EXPECT_CALL(callback_, OnFrame(_, _, _)).Times(0);
+ EXPECT_CALL(callback_, OnBlockEnd(_, _)).Times(0);
+ EXPECT_CALL(callback_, OnSimpleBlockEnd(_, _)).Times(0);
+
+ SetReaderData(test_data.data);
+
+ ParseAndExpectResult(Status::kInvalidElementValue,
+ test_data.expected_frame_start_position);
+ }
+
+ // Tests an invalid fixed-lace block that has inconsistent frame sizes.
+ void TestInvalidFixedLaceSizes() {
+ SetReaderData({
+ 0x81, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x84, // Flags = key_frame | fixed_lacing.
+ 0x01, // Lace count - 1 = 1 (2 frames).
+
+ // Lace data (2 frames).
+ 0x00, 0x00, // Frame 0.
+ 0x01, // Frame 1 (invalid: inconsistent frame size).
+ });
+
+ EXPECT_CALL(callback_, OnFrame(_, _, _)).Times(0);
+ EXPECT_CALL(callback_, OnBlockEnd(_, _)).Times(0);
+ EXPECT_CALL(callback_, OnSimpleBlockEnd(_, _)).Times(0);
+
+ ParseAndExpectResult(Status::kInvalidElementValue);
+ }
+
+ // Tests setting the action to Action::kSkip in Callback::OnSimpleBlockBegin
+ // for the SimpleBlockParser.
+ void TestSimpleBlockSkip(const TestData& test_data) {
+ SetReaderData(test_data.data);
+
+ const SimpleBlock expected_simple_block = ExpectedSimpleBlock(test_data);
+ const FrameMetadata metadata = FirstFrameMetadata(test_data);
+
+ EXPECT_CALL(callback_, OnSimpleBlockBegin(metadata.parent_element,
+ expected_simple_block, NotNull()))
+ .WillOnce(Return(Status(Status::kOkPartial)))
+ .WillOnce(DoAll(SetArgPointee<2>(Action::kSkip),
+ Return(Status(Status::kOkCompleted))));
+
+ EXPECT_CALL(callback_, OnFrame(_, _, _)).Times(0);
+ EXPECT_CALL(callback_, OnBlockEnd(_, _)).Times(0);
+ EXPECT_CALL(callback_, OnSimpleBlockEnd(_, _)).Times(0);
+
+ IncrementalParseAndVerify();
+ }
+
+ protected:
+ using ElementParserTest<T, id>::callback_;
+ using ElementParserTest<T, id>::IncrementalParseAndVerify;
+ using ElementParserTest<T, id>::metadata_;
+ using ElementParserTest<T, id>::ParseAndExpectResult;
+ using ElementParserTest<T, id>::ParseAndVerify;
+ using ElementParserTest<T, id>::parser_;
+ using ElementParserTest<T, id>::SetReaderData;
+ using ElementParserTest<T, id>::TestInit;
+
+ private:
+ // Gets the FrameMetadata for the very first frame in the test data.
+ FrameMetadata FirstFrameMetadata(const TestData& test_data) {
+ FrameMetadata metadata;
+ metadata.parent_element = metadata_;
+ metadata.parent_element.size = test_data.data.size();
+ metadata.position = test_data.expected_frame_start_position +
+ metadata.parent_element.position +
+ metadata.parent_element.header_size;
+ metadata.size = test_data.expected_frame_sizes[0];
+ return metadata;
+ }
+};
+
+class BlockParserTest : public BasicBlockParserTest<BlockParser, Id::kBlock> {};
+
+TEST_F(BlockParserTest, InvalidElementSize) { TestInvalidElementSize(); }
+
+TEST_F(BlockParserTest, InvalidHeaderOnlyNoLacing) {
+ TestInvalidHeaderOnly(no_lacing);
+}
+
+TEST_F(BlockParserTest, InvalidHeaderOnlyFixedLacing) {
+ TestInvalidHeaderOnly(fixed_lacing);
+}
+
+TEST_F(BlockParserTest, InvalidFixedLaceSizes) { TestInvalidFixedLaceSizes(); }
+
+TEST_F(BlockParserTest, BlockNoFlags) { RunTest(no_flags); }
+
+TEST_F(BlockParserTest, BlockFlags) { RunTest(block_flags); }
+
+TEST_F(BlockParserTest, EbmlLacingOneFrame) { RunTest(ebml_lacing_one_frame); }
+
+TEST_F(BlockParserTest, EbmlLacing) { RunTest(ebml_lacing); }
+
+TEST_F(BlockParserTest, XiphLacingOneFrame) { RunTest(xiph_lacing_one_frame); }
+
+TEST_F(BlockParserTest, XiphLacing) { RunTest(xiph_lacing); }
+
+TEST_F(BlockParserTest, FixedLacingOneFrame) {
+ RunTest(fixed_lacing_one_frame);
+}
+
+TEST_F(BlockParserTest, FixedLacing) { RunTest(fixed_lacing); }
+
+TEST_F(BlockParserTest, NoLacing) { RunTest(no_lacing); }
+
+TEST_F(BlockParserTest, BlockWithPositionAndHeaderSize) {
+ metadata_.position = 15;
+ metadata_.header_size = 3;
+ RunTest(no_lacing);
+}
+
+TEST_F(BlockParserTest, IncrementalBlockFlags) {
+ RunIncrementalTest(block_flags);
+}
+
+TEST_F(BlockParserTest, IncrementalEbmlLacingOneFrame) {
+ RunIncrementalTest(ebml_lacing_one_frame);
+}
+
+TEST_F(BlockParserTest, IncrementalEbmlLacing) {
+ RunIncrementalTest(ebml_lacing);
+}
+
+TEST_F(BlockParserTest, IncrementalXiphLacingOneFrame) {
+ RunIncrementalTest(xiph_lacing_one_frame);
+}
+
+TEST_F(BlockParserTest, IncrementalXiphLacing) {
+ RunIncrementalTest(xiph_lacing);
+}
+
+TEST_F(BlockParserTest, IncrementalFixedLacingOneFrame) {
+ RunIncrementalTest(fixed_lacing_one_frame);
+}
+
+TEST_F(BlockParserTest, IncrementalFixedLacing) {
+ RunIncrementalTest(fixed_lacing);
+}
+
+TEST_F(BlockParserTest, DefaultActionIsRead) {
+ SetReaderData(fixed_lacing_one_frame.data);
+ SetExpectations(fixed_lacing_one_frame, false, false);
+ ParseAndVerify();
+ ValidateBlock(fixed_lacing_one_frame, parser_.value());
+}
+
+TEST_F(BlockParserTest, IncrementalNoLacing) { RunIncrementalTest(no_lacing); }
+
+class SimpleBlockParserTest
+ : public BasicBlockParserTest<SimpleBlockParser, Id::kSimpleBlock> {};
+
+TEST_F(SimpleBlockParserTest, InvalidElementSize) { TestInvalidElementSize(); }
+
+TEST_F(SimpleBlockParserTest, InvalidHeaderOnlyNoLacing) {
+ TestInvalidHeaderOnly(no_lacing);
+}
+
+TEST_F(SimpleBlockParserTest, InvalidHeaderOnlyFixedLacing) {
+ TestInvalidHeaderOnly(fixed_lacing);
+}
+
+TEST_F(SimpleBlockParserTest, InvalidFixedLaceSizes) {
+ TestInvalidFixedLaceSizes();
+}
+
+TEST_F(SimpleBlockParserTest, SimpleBlockSkip) {
+ TestSimpleBlockSkip(no_flags);
+}
+
+TEST_F(SimpleBlockParserTest, SimpleBlockNoFlags) { RunTest(no_flags); }
+
+TEST_F(SimpleBlockParserTest, SimpleBlockFlags) { RunTest(simple_block_flags); }
+
+TEST_F(SimpleBlockParserTest, EbmlLacingOneFrame) {
+ RunTest(ebml_lacing_one_frame);
+}
+
+TEST_F(SimpleBlockParserTest, EbmlLacing) { RunTest(ebml_lacing); }
+
+TEST_F(SimpleBlockParserTest, XiphLacingOneFrame) {
+ RunTest(xiph_lacing_one_frame);
+}
+
+TEST_F(SimpleBlockParserTest, XiphLacing) { RunTest(xiph_lacing); }
+
+TEST_F(SimpleBlockParserTest, FixedLacingOneFrame) {
+ RunTest(fixed_lacing_one_frame);
+}
+
+TEST_F(SimpleBlockParserTest, FixedLacing) { RunTest(fixed_lacing); }
+
+TEST_F(SimpleBlockParserTest, NoLacing) { RunTest(no_lacing); }
+
+TEST_F(BlockParserTest, SimpleBlockWithPositionAndHeaderSize) {
+ metadata_.position = 16;
+ metadata_.header_size = 4;
+ RunTest(no_lacing);
+}
+
+TEST_F(SimpleBlockParserTest, IncrementalSimpleBlockFlags) {
+ RunIncrementalTest(simple_block_flags);
+}
+
+TEST_F(SimpleBlockParserTest, IncrementalEbmlLacingOneFrame) {
+ RunIncrementalTest(ebml_lacing_one_frame);
+}
+
+TEST_F(SimpleBlockParserTest, IncrementalEbmlLacing) {
+ RunIncrementalTest(ebml_lacing);
+}
+
+TEST_F(SimpleBlockParserTest, IncrementalXiphLacingOneFrame) {
+ RunIncrementalTest(xiph_lacing_one_frame);
+}
+
+TEST_F(SimpleBlockParserTest, IncrementalXiphLacing) {
+ RunIncrementalTest(xiph_lacing);
+}
+
+TEST_F(SimpleBlockParserTest, IncrementalFixedLacingOneFrame) {
+ RunIncrementalTest(fixed_lacing_one_frame);
+}
+
+TEST_F(SimpleBlockParserTest, IncrementalFixedLacing) {
+ RunIncrementalTest(fixed_lacing);
+}
+
+TEST_F(SimpleBlockParserTest, IncrementalNoLacing) {
+ RunIncrementalTest(no_lacing);
+}
+
+TEST_F(SimpleBlockParserTest, DefaultActionIsRead) {
+ SetReaderData(fixed_lacing_one_frame.data);
+ SetExpectations(fixed_lacing_one_frame, false, false);
+ ParseAndVerify();
+ ValidateBlock(fixed_lacing_one_frame, parser_.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/bool_parser_test.cc b/webm_parser/tests/bool_parser_test.cc
new file mode 100644
index 0000000..d7f85d7
--- /dev/null
+++ b/webm_parser/tests/bool_parser_test.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/bool_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/status.h"
+
+using webm::BoolParser;
+using webm::ElementParserTest;
+using webm::kUnknownElementSize;
+using webm::Status;
+
+namespace {
+
+class BoolParserTest : public ElementParserTest<BoolParser> {};
+
+TEST_F(BoolParserTest, InvalidSize) {
+ TestInit(9, Status::kInvalidElementSize);
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(BoolParserTest, InvalidValue) {
+ SetReaderData({0x02});
+ ParseAndExpectResult(Status::kInvalidElementValue);
+
+ SetReaderData({0xFF, 0xFF});
+ ParseAndExpectResult(Status::kInvalidElementValue);
+}
+
+TEST_F(BoolParserTest, CustomDefault) {
+ ResetParser(true);
+
+ ParseAndVerify();
+
+ EXPECT_EQ(true, parser_.value());
+}
+
+TEST_F(BoolParserTest, ValidBool) {
+ ParseAndVerify();
+ EXPECT_EQ(false, parser_.value());
+
+ SetReaderData({0x00, 0x00, 0x01});
+ ParseAndVerify();
+ EXPECT_EQ(true, parser_.value());
+
+ SetReaderData({0x00, 0x00, 0x00, 0x00, 0x00});
+ ParseAndVerify();
+ EXPECT_EQ(false, parser_.value());
+
+ SetReaderData({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01});
+ ParseAndVerify();
+ EXPECT_EQ(true, parser_.value());
+}
+
+TEST_F(BoolParserTest, IncrementalParse) {
+ SetReaderData({0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
+
+ IncrementalParseAndVerify();
+
+ EXPECT_EQ(false, parser_.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/buffer_reader_test.cc b/webm_parser/tests/buffer_reader_test.cc
new file mode 100644
index 0000000..57f9d3d
--- /dev/null
+++ b/webm_parser/tests/buffer_reader_test.cc
@@ -0,0 +1,165 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "webm/buffer_reader.h"
+
+#include <array>
+#include <cstdint>
+
+#include "gtest/gtest.h"
+
+using webm::BufferReader;
+using webm::Status;
+
+namespace {
+
+class BufferReaderTest : public testing::Test {};
+
+TEST_F(BufferReaderTest, Assignment) {
+ // Test the reader to make sure it resets correctly when assigned.
+ std::array<std::uint8_t, 4> buffer;
+ std::uint64_t count;
+ Status status;
+
+ BufferReader reader({});
+ const std::size_t kExpectedSize = 0;
+ EXPECT_EQ(kExpectedSize, reader.size());
+
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(Status::kEndOfFile, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), count);
+
+ reader = {1, 2, 3, 4};
+ EXPECT_EQ(static_cast<std::size_t>(4), reader.size());
+
+ status = reader.Read(2, buffer.data(), &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+
+ reader = {5, 6, 7, 8};
+ status = reader.Read(2, buffer.data() + 2, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+
+ std::array<std::uint8_t, 4> expected = {{1, 2, 5, 6}};
+ EXPECT_EQ(expected, buffer);
+}
+
+TEST_F(BufferReaderTest, Empty) {
+ // Test the reader to make sure it reports EOF on empty inputs.
+ std::array<std::uint8_t, 1> buffer;
+ std::uint64_t count;
+ Status status;
+
+ BufferReader reader({});
+
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(Status::kEndOfFile, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), count);
+
+ status = reader.Skip(1, &count);
+ EXPECT_EQ(Status::kEndOfFile, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), count);
+}
+
+TEST_F(BufferReaderTest, Read) {
+ // Test the Read method to make sure it reads data correctly.
+ std::array<std::uint8_t, 15> buffer{};
+ std::uint64_t count;
+ Status status;
+
+ BufferReader reader({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
+
+ status = reader.Read(5, buffer.data(), &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(5), count);
+
+ status = reader.Read(10, buffer.data() + 5, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(5), count);
+
+ std::array<std::uint8_t, 15> expected = {{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}};
+ EXPECT_EQ(expected, buffer);
+
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(Status::kEndOfFile, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), count);
+}
+
+TEST_F(BufferReaderTest, Skip) {
+ // Test the Skip method to make sure it skips data correctly.
+ std::uint64_t count;
+ Status status;
+
+ BufferReader reader({0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
+
+ status = reader.Skip(3, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(3), count);
+
+ status = reader.Skip(10, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(7), count);
+
+ status = reader.Skip(1, &count);
+ EXPECT_EQ(Status::kEndOfFile, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), count);
+}
+
+TEST_F(BufferReaderTest, ReadAndSkip) {
+ // Test the Read and Skip methods together to make sure they interact
+ // correclty.
+ std::array<std::uint8_t, 10> buffer = {};
+ std::uint64_t count;
+ Status status;
+
+ BufferReader reader({9, 8, 7, 6, 5, 4, 3, 2, 1, 0});
+
+ status = reader.Read(5, buffer.data(), &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(5), count);
+
+ status = reader.Skip(3, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(3), count);
+
+ status = reader.Read(5, buffer.data() + 5, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+
+ std::array<std::uint8_t, 10> expected = {{9, 8, 7, 6, 5, 1, 0, 0, 0, 0}};
+ EXPECT_EQ(expected, buffer);
+}
+
+TEST_F(BufferReaderTest, Position) {
+ std::array<std::uint8_t, 10> buffer = {};
+ std::uint64_t count;
+ Status status;
+
+ BufferReader reader({9, 8, 7, 6, 5, 4, 3, 2, 1, 0});
+ EXPECT_EQ(static_cast<std::uint64_t>(0), reader.Position());
+
+ status = reader.Read(5, buffer.data(), &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(5), count);
+ EXPECT_EQ(static_cast<std::uint64_t>(5), reader.Position());
+
+ status = reader.Skip(3, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(3), count);
+ EXPECT_EQ(static_cast<std::uint64_t>(8), reader.Position());
+
+ status = reader.Read(5, buffer.data() + 5, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+ EXPECT_EQ(static_cast<std::uint64_t>(10), reader.Position());
+
+ std::array<std::uint8_t, 10> expected = {{9, 8, 7, 6, 5, 1, 0, 0, 0, 0}};
+ EXPECT_EQ(expected, buffer);
+}
+
+} // namespace
diff --git a/webm_parser/tests/byte_parser_test.cc b/webm_parser/tests/byte_parser_test.cc
new file mode 100644
index 0000000..216d624
--- /dev/null
+++ b/webm_parser/tests/byte_parser_test.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/byte_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/status.h"
+
+using webm::BinaryParser;
+using webm::ElementParserTest;
+using webm::kUnknownElementSize;
+using webm::Status;
+using webm::StringParser;
+
+namespace {
+
+class StringParserTest : public ElementParserTest<StringParser> {};
+
+TEST_F(StringParserTest, StringInvalidSize) {
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(StringParserTest, StringCustomDefault) {
+ ResetParser("foobar");
+
+ ParseAndVerify();
+
+ EXPECT_EQ("foobar", parser_.value());
+}
+
+TEST_F(StringParserTest, StringValidValue) {
+ ParseAndVerify();
+ EXPECT_EQ("", parser_.value());
+
+ SetReaderData({'!'});
+ ParseAndVerify();
+ EXPECT_EQ("!", parser_.value());
+
+ SetReaderData({'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd'});
+ ParseAndVerify();
+ EXPECT_EQ("Hello, world", parser_.value());
+}
+
+TEST_F(StringParserTest, StringTrailingNulCharacters) {
+ // The trailing NUL characters should be trimmed.
+ SetReaderData({'H', 'i', '\0', '\0'});
+ ParseAndVerify();
+ EXPECT_EQ("Hi", parser_.value());
+
+ SetReaderData({'\0'});
+ ParseAndVerify();
+ EXPECT_EQ("", parser_.value());
+}
+
+TEST_F(StringParserTest, StringIncrementalParse) {
+ SetReaderData({'M', 'a', 't', 'r', 'o', 's', 'k', 'a'});
+
+ IncrementalParseAndVerify();
+
+ EXPECT_EQ("Matroska", parser_.value());
+}
+
+class BinaryParserTest : public ElementParserTest<BinaryParser> {};
+
+TEST_F(BinaryParserTest, BinaryInvalidSize) {
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(BinaryParserTest, BinaryCustomDefault) {
+ std::vector<std::uint8_t> expected = {0x00, 0x02, 0x04, 0x06, 0x08};
+ ResetParser(expected);
+
+ ParseAndVerify();
+
+ EXPECT_EQ(expected, parser_.value());
+}
+
+TEST_F(BinaryParserTest, BinaryValidValue) {
+ std::vector<std::uint8_t> expected;
+
+ ParseAndVerify();
+ EXPECT_EQ(expected, parser_.value());
+
+ expected = {0x00};
+ SetReaderData(expected);
+ ParseAndVerify();
+ EXPECT_EQ(expected, parser_.value());
+
+ expected = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
+ SetReaderData(expected);
+ ParseAndVerify();
+ EXPECT_EQ(expected, parser_.value());
+
+ // Unlike StringParser, the BinaryParser should not trim trailing 0-bytes.
+ expected = {'H', 'i', '\0', '\0'};
+ SetReaderData(expected);
+ ParseAndVerify();
+ EXPECT_EQ(expected, parser_.value());
+}
+
+TEST_F(BinaryParserTest, BinaryIncrementalParse) {
+ const std::vector<std::uint8_t> expected = {
+ 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F};
+ SetReaderData(expected);
+
+ IncrementalParseAndVerify();
+
+ EXPECT_EQ(expected, parser_.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/callback_test.cc b/webm_parser/tests/callback_test.cc
new file mode 100644
index 0000000..e8aa974
--- /dev/null
+++ b/webm_parser/tests/callback_test.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "webm/callback.h"
+
+#include <cstdint>
+
+#include "gtest/gtest.h"
+
+#include "webm/buffer_reader.h"
+#include "webm/element.h"
+#include "webm/status.h"
+
+using webm::Action;
+using webm::BufferReader;
+using webm::Callback;
+using webm::ElementMetadata;
+using webm::Reader;
+using webm::Status;
+
+namespace {
+
+void TestCompletedOk(Status (Callback::*function)(const ElementMetadata&)) {
+ Callback callback;
+ ElementMetadata metadata{};
+
+ Status status = (callback.*function)(metadata);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+}
+
+template <typename T>
+void TestCompletedOk(Status (Callback::*function)(const ElementMetadata&,
+ const T&)) {
+ Callback callback;
+ ElementMetadata metadata{};
+ T object{};
+
+ Status status = (callback.*function)(metadata, object);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+}
+
+void TestAction(Status (Callback::*function)(const ElementMetadata&, Action*),
+ Action expected) {
+ Callback callback;
+ ElementMetadata metadata{};
+ Action action;
+
+ Status status = (callback.*function)(metadata, &action);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(expected, action);
+}
+
+template <typename T>
+void TestAction(Status (Callback::*function)(const ElementMetadata&, const T&,
+ Action*),
+ Action expected) {
+ Callback callback;
+ ElementMetadata metadata{};
+ T t{};
+ Action action;
+
+ Status status = (callback.*function)(metadata, t, &action);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(expected, action);
+}
+
+template <typename T>
+void TestRead(Status (Callback::*function)(const T&, Reader*, std::uint64_t*)) {
+ Callback callback;
+ Status status;
+ T metadata{};
+ BufferReader reader = {0x00, 0x01, 0x02, 0x03};
+ std::uint64_t bytes_remaining = 4;
+
+ status = (callback.*function)(metadata, &reader, &bytes_remaining);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), bytes_remaining);
+}
+
+class CallbackTest : public testing::Test {};
+
+TEST_F(CallbackTest, OnElementBegin) {
+ TestAction(&Callback::OnElementBegin, Action::kRead);
+}
+
+TEST_F(CallbackTest, OnUnknownElement) {
+ TestRead(&Callback::OnUnknownElement);
+}
+
+TEST_F(CallbackTest, OnEbml) { TestCompletedOk(&Callback::OnEbml); }
+
+TEST_F(CallbackTest, OnVoid) { TestRead(&Callback::OnVoid); }
+
+TEST_F(CallbackTest, OnSegmentBegin) {
+ TestAction(&Callback::OnSegmentBegin, Action::kRead);
+}
+
+TEST_F(CallbackTest, OnSeek) { TestCompletedOk(&Callback::OnSeek); }
+
+TEST_F(CallbackTest, OnInfo) { TestCompletedOk(&Callback::OnInfo); }
+
+TEST_F(CallbackTest, OnClusterBegin) {
+ TestAction(&Callback::OnClusterBegin, Action::kRead);
+}
+
+TEST_F(CallbackTest, OnSimpleBlockBegin) {
+ TestAction(&Callback::OnSimpleBlockBegin, Action::kRead);
+}
+
+TEST_F(CallbackTest, OnSimpleBlockEnd) {
+ TestCompletedOk(&Callback::OnSimpleBlockEnd);
+}
+
+TEST_F(CallbackTest, OnBlockGroupBegin) {
+ TestAction(&Callback::OnBlockGroupBegin, Action::kRead);
+}
+
+TEST_F(CallbackTest, OnBlockBegin) {
+ TestAction(&Callback::OnBlockBegin, Action::kRead);
+}
+
+TEST_F(CallbackTest, OnBlockEnd) { TestCompletedOk(&Callback::OnBlockEnd); }
+
+TEST_F(CallbackTest, OnBlockGroupEnd) {
+ TestCompletedOk(&Callback::OnBlockGroupEnd);
+}
+
+TEST_F(CallbackTest, OnFrame) { TestRead(&Callback::OnFrame); }
+
+TEST_F(CallbackTest, OnClusterEnd) { TestCompletedOk(&Callback::OnClusterEnd); }
+
+TEST_F(CallbackTest, OnTrackEntry) { TestCompletedOk(&Callback::OnTrackEntry); }
+
+TEST_F(CallbackTest, OnCuePoint) { TestCompletedOk(&Callback::OnCuePoint); }
+
+TEST_F(CallbackTest, OnEditionEntry) {
+ TestCompletedOk(&Callback::OnEditionEntry);
+}
+
+TEST_F(CallbackTest, OnTag) { TestCompletedOk(&Callback::OnTag); }
+
+TEST_F(CallbackTest, OnSegmentEnd) { TestCompletedOk(&Callback::OnSegmentEnd); }
+
+} // namespace
diff --git a/webm_parser/tests/chapter_atom_parser_test.cc b/webm_parser/tests/chapter_atom_parser_test.cc
new file mode 100644
index 0000000..5c82061
--- /dev/null
+++ b/webm_parser/tests/chapter_atom_parser_test.cc
@@ -0,0 +1,209 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/chapter_atom_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using webm::ChapterAtom;
+using webm::ChapterAtomParser;
+using webm::ChapterDisplay;
+using webm::ElementParserTest;
+using webm::Id;
+using webm::Status;
+
+namespace {
+
+class ChapterAtomParserTest
+ : public ElementParserTest<ChapterAtomParser, Id::kChapterAtom> {};
+
+TEST_F(ChapterAtomParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const ChapterAtom chapter_atom = parser_.value();
+
+ EXPECT_FALSE(chapter_atom.uid.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.uid.value());
+
+ EXPECT_FALSE(chapter_atom.string_uid.is_present());
+ EXPECT_EQ("", chapter_atom.string_uid.value());
+
+ EXPECT_FALSE(chapter_atom.time_start.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.time_start.value());
+
+ EXPECT_FALSE(chapter_atom.time_end.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.time_end.value());
+
+ EXPECT_EQ(static_cast<std::size_t>(0), chapter_atom.displays.size());
+
+ EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.atoms.size());
+}
+
+TEST_F(ChapterAtomParserTest, DefaultValues) {
+ SetReaderData({
+ 0x73, 0xC4, // ID = 0x73C4 (ChapterUID).
+ 0x80, // Size = 0.
+
+ 0x56, 0x54, // ID = 0x73C4 (ChapterStringUID).
+ 0x80, // Size = 0.
+
+ 0x91, // ID = 0x91 (ChapterTimeStart).
+ 0x40, 0x00, // Size = 0.
+
+ 0x92, // ID = 0x91 (ChapterTimeEnd).
+ 0x40, 0x00, // Size = 0.
+
+ 0x80, // ID = 0x80 (ChapterDisplay).
+ 0x40, 0x00, // Size = 0.
+
+ 0xB6, // ID = 0xB6 (ChapterAtom).
+ 0x40, 0x00, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const ChapterAtom chapter_atom = parser_.value();
+
+ EXPECT_TRUE(chapter_atom.uid.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.uid.value());
+
+ EXPECT_TRUE(chapter_atom.string_uid.is_present());
+ EXPECT_EQ("", chapter_atom.string_uid.value());
+
+ EXPECT_TRUE(chapter_atom.time_start.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.time_start.value());
+
+ EXPECT_TRUE(chapter_atom.time_end.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), chapter_atom.time_end.value());
+
+ ASSERT_EQ(static_cast<std::uint64_t>(1), chapter_atom.displays.size());
+ EXPECT_TRUE(chapter_atom.displays[0].is_present());
+ EXPECT_EQ(ChapterDisplay{}, chapter_atom.displays[0].value());
+
+ ASSERT_EQ(static_cast<std::uint64_t>(1), chapter_atom.atoms.size());
+ EXPECT_TRUE(chapter_atom.atoms[0].is_present());
+ EXPECT_EQ(ChapterAtom{}, chapter_atom.atoms[0].value());
+}
+
+TEST_F(ChapterAtomParserTest, CustomValues) {
+ SetReaderData({
+ 0x73, 0xC4, // ID = 0x73C4 (ChapterUID).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x56, 0x54, // ID = 0x73C4 (ChapterStringUID).
+ 0x81, // Size = 1.
+ 0x41, // Body (value = "A").
+
+ 0x91, // ID = 0x91 (ChapterTimeStart).
+ 0x40, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0x92, // ID = 0x91 (ChapterTimeEnd).
+ 0x40, 0x01, // Size = 1.
+ 0x03, // Body (value = 3).
+
+ 0x80, // ID = 0x80 (ChapterDisplay).
+ 0x40, 0x04, // Size = 4.
+
+ 0x85, // ID = 0x85 (ChapString).
+ 0x40, 0x01, // Size = 1.
+ 0x42, // Body (value = "B").
+
+ 0x80, // ID = 0x80 (ChapterDisplay).
+ 0x40, 0x04, // Size = 4.
+
+ 0x85, // ID = 0x85 (ChapString).
+ 0x40, 0x01, // Size = 1.
+ 0x43, // Body (value = "C").
+
+ 0xB6, // ID = 0xB6 (ChapterAtom).
+ 0x40, 0x12, // Size = 18.
+
+ 0x73, 0xC4, // ID = 0x73C4 (ChapterUID).
+ 0x81, // Size = 1.
+ 0x04, // Body (value = 4).
+
+ 0xB6, // ID = 0xB6 (ChapterAtom).
+ 0x40, 0x04, // Size = 4.
+
+ 0x73, 0xC4, // ID = 0x73C4 (ChapterUID).
+ 0x81, // Size = 1.
+ 0x05, // Body (value = 5).
+
+ 0xB6, // ID = 0xB6 (ChapterAtom).
+ 0x40, 0x04, // Size = 4.
+
+ 0x73, 0xC4, // ID = 0x73C4 (ChapterUID).
+ 0x81, // Size = 1.
+ 0x06, // Body (value = 6).
+ });
+
+ ParseAndVerify();
+
+ const ChapterAtom chapter_atom = parser_.value();
+
+ EXPECT_TRUE(chapter_atom.uid.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), chapter_atom.uid.value());
+
+ EXPECT_TRUE(chapter_atom.string_uid.is_present());
+ EXPECT_EQ("A", chapter_atom.string_uid.value());
+
+ EXPECT_TRUE(chapter_atom.time_start.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), chapter_atom.time_start.value());
+
+ EXPECT_TRUE(chapter_atom.time_end.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(3), chapter_atom.time_end.value());
+
+ ChapterDisplay expected_chapter_display;
+
+ ASSERT_EQ(static_cast<std::size_t>(2), chapter_atom.displays.size());
+ expected_chapter_display.string.Set("B", true);
+ EXPECT_TRUE(chapter_atom.displays[0].is_present());
+ EXPECT_EQ(expected_chapter_display, chapter_atom.displays[0].value());
+ expected_chapter_display.string.Set("C", true);
+ EXPECT_TRUE(chapter_atom.displays[1].is_present());
+ EXPECT_EQ(expected_chapter_display, chapter_atom.displays[1].value());
+
+ ChapterAtom expected_chapter_atom;
+ expected_chapter_atom.uid.Set(4, true);
+
+ ChapterAtom tmp_atom{};
+ tmp_atom.uid.Set(5, true);
+ expected_chapter_atom.atoms.emplace_back(tmp_atom, true);
+ tmp_atom.uid.Set(6, true);
+ expected_chapter_atom.atoms.emplace_back(tmp_atom, true);
+
+ ASSERT_EQ(static_cast<std::size_t>(1), chapter_atom.atoms.size());
+ EXPECT_TRUE(chapter_atom.atoms[0].is_present());
+ EXPECT_EQ(expected_chapter_atom, chapter_atom.atoms[0].value());
+}
+
+TEST_F(ChapterAtomParserTest, ExceedMaxRecursionDepth) {
+ ResetParser(1);
+
+ SetReaderData({
+ 0xB6, // ID = 0xB6 (ChapterAtom).
+ 0x80, // Size = 0.
+ });
+ ParseAndVerify();
+
+ SetReaderData({
+ 0xB6, // ID = 0xB6 (ChapterAtom).
+ 0x82, // Size = 2.
+
+ 0xB6, // ID = 0xB6 (ChapterAtom).
+ 0x80, // Size = 0.
+ });
+ ParseAndExpectResult(Status::kExceededRecursionDepthLimit);
+}
+
+} // namespace
diff --git a/webm_parser/tests/chapter_display_parser_test.cc b/webm_parser/tests/chapter_display_parser_test.cc
new file mode 100644
index 0000000..224c1da
--- /dev/null
+++ b/webm_parser/tests/chapter_display_parser_test.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/chapter_display_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ChapterDisplay;
+using webm::ChapterDisplayParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class ChapterDisplayParserTest
+ : public ElementParserTest<ChapterDisplayParser, Id::kChapterDisplay> {};
+
+TEST_F(ChapterDisplayParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const ChapterDisplay chapter_display = parser_.value();
+
+ EXPECT_FALSE(chapter_display.string.is_present());
+ EXPECT_EQ("", chapter_display.string.value());
+
+ ASSERT_EQ(static_cast<std::uint64_t>(1), chapter_display.languages.size());
+ EXPECT_FALSE(chapter_display.languages[0].is_present());
+ EXPECT_EQ("eng", chapter_display.languages[0].value());
+
+ EXPECT_EQ(static_cast<std::size_t>(0), chapter_display.countries.size());
+}
+
+TEST_F(ChapterDisplayParserTest, DefaultValues) {
+ SetReaderData({
+ 0x85, // ID = 0x85 (ChapString).
+ 0x40, 0x00, // Size = 0.
+
+ 0x43, 0x7C, // ID = 0x437C (ChapLanguage).
+ 0x80, // Size = 0.
+
+ 0x43, 0x7E, // ID = 0x437E (ChapCountry).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const ChapterDisplay chapter_display = parser_.value();
+
+ EXPECT_TRUE(chapter_display.string.is_present());
+ EXPECT_EQ("", chapter_display.string.value());
+
+ ASSERT_EQ(static_cast<std::size_t>(1), chapter_display.languages.size());
+ EXPECT_TRUE(chapter_display.languages[0].is_present());
+ EXPECT_EQ("eng", chapter_display.languages[0].value());
+
+ ASSERT_EQ(static_cast<std::size_t>(1), chapter_display.countries.size());
+ EXPECT_TRUE(chapter_display.countries[0].is_present());
+ EXPECT_EQ("", chapter_display.countries[0].value());
+}
+
+TEST_F(ChapterDisplayParserTest, CustomValues) {
+ SetReaderData({
+ 0x85, // ID = 0x85 (ChapString).
+ 0x40, 0x05, // Size = 5.
+ 0x68, 0x65, 0x6C, 0x6C, 0x6F, // Body (value = "hello").
+
+ 0x43, 0x7C, // ID = 0x437C (ChapLanguage).
+ 0x85, // Size = 5.
+ 0x6C, 0x61, 0x6E, 0x67, 0x30, // body (value = "lang0").
+
+ 0x43, 0x7E, // ID = 0x437E (ChapCountry).
+ 0x85, // Size = 5.
+ 0x61, 0x72, 0x65, 0x61, 0x30, // Body (value = "area0").
+
+ 0x43, 0x7C, // ID = 0x437C (ChapLanguage).
+ 0x85, // Size = 5.
+ 0x6C, 0x61, 0x6E, 0x67, 0x31, // body (value = "lang1").
+
+ 0x43, 0x7C, // ID = 0x437C (ChapLanguage).
+ 0x85, // Size = 5.
+ 0x6C, 0x61, 0x6E, 0x67, 0x32, // body (value = "lang2").
+
+ 0x43, 0x7E, // ID = 0x437E (ChapCountry).
+ 0x85, // Size = 5.
+ 0x61, 0x72, 0x65, 0x61, 0x31, // Body (value = "area1").
+ });
+
+ ParseAndVerify();
+
+ const ChapterDisplay chapter_display = parser_.value();
+
+ EXPECT_TRUE(chapter_display.string.is_present());
+ EXPECT_EQ("hello", chapter_display.string.value());
+
+ ASSERT_EQ(static_cast<std::size_t>(3), chapter_display.languages.size());
+ EXPECT_TRUE(chapter_display.languages[0].is_present());
+ EXPECT_EQ("lang0", chapter_display.languages[0].value());
+ EXPECT_TRUE(chapter_display.languages[1].is_present());
+ EXPECT_EQ("lang1", chapter_display.languages[1].value());
+ EXPECT_TRUE(chapter_display.languages[2].is_present());
+ EXPECT_EQ("lang2", chapter_display.languages[2].value());
+
+ ASSERT_EQ(static_cast<std::size_t>(2), chapter_display.countries.size());
+ EXPECT_TRUE(chapter_display.countries[0].is_present());
+ EXPECT_EQ("area0", chapter_display.countries[0].value());
+ EXPECT_TRUE(chapter_display.countries[1].is_present());
+ EXPECT_EQ("area1", chapter_display.countries[1].value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/chapters_parser_test.cc b/webm_parser/tests/chapters_parser_test.cc
new file mode 100644
index 0000000..b84deff
--- /dev/null
+++ b/webm_parser/tests/chapters_parser_test.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/chapters_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ChaptersParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class ChaptersParserTest
+ : public ElementParserTest<ChaptersParser, Id::kChapters> {};
+
+TEST_F(ChaptersParserTest, DefaultValues) {
+ ParseAndVerify();
+
+ SetReaderData({
+ 0x45, 0xB9, // ID = 0x45B9 (EditionEntry).
+ 0x80, // Size = 0.
+ });
+ ParseAndVerify();
+}
+
+TEST_F(ChaptersParserTest, RepeatedValues) {
+ SetReaderData({
+ 0x45, 0xB9, // ID = 0x45B9 (EditionEntry).
+ 0x84, // Size = 4.
+
+ 0x45, 0xBC, // ID = 0x45BC (EditionUID).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x45, 0xB9, // ID = 0x45B9 (EditionEntry).
+ 0x84, // Size = 4.
+
+ 0x45, 0xBC, // ID = 0x45BC (EditionUID).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/cluster_parser_test.cc b/webm_parser/tests/cluster_parser_test.cc
new file mode 100644
index 0000000..fb879b6
--- /dev/null
+++ b/webm_parser/tests/cluster_parser_test.cc
@@ -0,0 +1,266 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/cluster_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/element.h"
+#include "webm/id.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::InSequence;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgPointee;
+
+using webm::Action;
+using webm::Ancestory;
+using webm::BlockGroup;
+using webm::Cluster;
+using webm::ClusterParser;
+using webm::ElementMetadata;
+using webm::ElementParserTest;
+using webm::Id;
+using webm::SimpleBlock;
+using webm::Status;
+
+namespace {
+
+class ClusterParserTest
+ : public ElementParserTest<ClusterParser, Id::kCluster> {};
+
+TEST_F(ClusterParserTest, DefaultParse) {
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, Cluster{}, NotNull()))
+ .Times(1);
+
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, Cluster{})).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(ClusterParserTest, DefaultActionIsRead) {
+ {
+ InSequence dummy;
+
+ // This intentionally does not set the action and relies on the parser using
+ // a default action value of kRead.
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, Cluster{}, NotNull()))
+ .WillOnce(Return(Status(Status::kOkCompleted)));
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, Cluster{})).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(ClusterParserTest, DefaultValues) {
+ SetReaderData({
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x40, 0x00, // Size = 0.
+
+ 0xAB, // ID = 0xAB (PrevSize).
+ 0x40, 0x00, // Size = 0.
+
+ 0xA3, // ID = 0xA3 (SimpleBlock).
+ 0x85, // Size = 5.
+ 0x81, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x00, // Flags = 0.
+ 0x00, // Frame 0.
+
+ 0xA0, // ID = 0xA0 (BlockGroup).
+ 0x40, 0x00, // Size = 0.
+ });
+
+ {
+ InSequence dummy;
+
+ Cluster cluster{};
+ cluster.timecode.Set(0, true);
+ cluster.previous_size.Set(0, true);
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .Times(1);
+
+ SimpleBlock simple_block{};
+ simple_block.track_number = 1;
+ simple_block.num_frames = 1;
+ simple_block.is_visible = true;
+ cluster.simple_blocks.emplace_back(simple_block, true);
+
+ BlockGroup block_group{};
+ cluster.block_groups.emplace_back(block_group, true);
+
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(ClusterParserTest, CustomValues) {
+ SetReaderData({
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x40, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xAB, // ID = 0xAB (PrevSize).
+ 0x40, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0xA3, // ID = 0xA3 (SimpleBlock).
+ 0x85, // Size = 5.
+ 0x81, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x00, // Flags = 0.
+ 0x00, // Frame 0.
+
+ 0xA3, // ID = 0xA3 (SimpleBlock).
+ 0x85, // Size = 5.
+ 0x82, // Track number = 2.
+ 0x00, 0x00, // Timecode = 0.
+ 0x00, // Flags = 0.
+ 0x00, // Frame 0.
+
+ 0xA0, // ID = 0xA0 (BlockGroup).
+ 0x40, 0x04, // Size = 4.
+
+ 0x9B, // ID = 0x9B (BlockDuration).
+ 0x40, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xA0, // ID = 0xA0 (BlockGroup).
+ 0x40, 0x04, // Size = 4.
+
+ 0x9B, // ID = 0x9B (BlockDuration).
+ 0x40, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ {
+ InSequence dummy;
+
+ Cluster cluster{};
+ cluster.timecode.Set(1, true);
+ cluster.previous_size.Set(2, true);
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .Times(1);
+
+ SimpleBlock simple_block{};
+ simple_block.num_frames = 1;
+ simple_block.is_visible = true;
+
+ simple_block.track_number = 1;
+ cluster.simple_blocks.emplace_back(simple_block, true);
+ simple_block.track_number = 2;
+ cluster.simple_blocks.emplace_back(simple_block, true);
+
+ BlockGroup block_group{};
+ block_group.duration.Set(1, true);
+ cluster.block_groups.emplace_back(block_group, true);
+ block_group.duration.Set(2, true);
+ cluster.block_groups.emplace_back(block_group, true);
+
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(ClusterParserTest, SkipOnClusterBegin) {
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnClusterBegin(_, _, _)).Times(0);
+
+ EXPECT_CALL(callback_, OnBlockGroupBegin(_, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(1);
+ }
+
+ ElementMetadata child_metadata = {Id::kBlockGroup, 0, 0, 0};
+
+ Ancestory ancestory;
+ ASSERT_TRUE(Ancestory::ById(child_metadata.id, &ancestory));
+ // Skip the Segment and Cluster ancestors.
+ ancestory = ancestory.next().next();
+
+ parser_.InitAfterSeek(ancestory, child_metadata);
+
+ std::uint64_t num_bytes_read = 0;
+ const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(reader_.size(), num_bytes_read);
+}
+
+TEST_F(ClusterParserTest, SkipSimpleBlock) {
+ SetReaderData({
+ 0xA3, // ID = 0xA3 (SimpleBlock).
+ 0x85, // Size = 5.
+ 0x81, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x00, // Flags = 0.
+ 0x00, // Frame 0.
+ });
+
+ {
+ InSequence dummy;
+
+ Cluster cluster{};
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .Times(1);
+
+ SimpleBlock simple_block{};
+ simple_block.num_frames = 1;
+ simple_block.is_visible = true;
+ simple_block.track_number = 1;
+
+ EXPECT_CALL(callback_, OnSimpleBlockBegin(_, simple_block, NotNull()))
+ .WillOnce(DoAll(SetArgPointee<2>(Action::kSkip),
+ Return(Status(Status::kOkCompleted))));
+
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(ClusterParserTest, SkipBlockGroup) {
+ SetReaderData({
+ 0xA0, // ID = 0xA0 (BlockGroup).
+ 0x40, 0x04, // Size = 4.
+
+ 0x9B, // ID = 0x9B (BlockDuration).
+ 0x40, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+ });
+
+ {
+ InSequence dummy;
+
+ Cluster cluster{};
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .Times(1);
+
+ EXPECT_CALL(callback_, OnBlockGroupBegin(_, NotNull()))
+ .WillOnce(DoAll(SetArgPointee<1>(Action::kSkip),
+ Return(Status(Status::kOkCompleted))));
+
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/colour_parser_test.cc b/webm_parser/tests/colour_parser_test.cc
new file mode 100644
index 0000000..0fe31f0
--- /dev/null
+++ b/webm_parser/tests/colour_parser_test.cc
@@ -0,0 +1,285 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/colour_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::Colour;
+using webm::ColourParser;
+using webm::ElementParserTest;
+using webm::Id;
+using webm::MasteringMetadata;
+using webm::MatrixCoefficients;
+using webm::Primaries;
+using webm::Range;
+using webm::TransferCharacteristics;
+
+namespace {
+
+class ColourParserTest : public ElementParserTest<ColourParser, Id::kColour> {};
+
+TEST_F(ColourParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const Colour colour = parser_.value();
+
+ EXPECT_FALSE(colour.matrix_coefficients.is_present());
+ EXPECT_EQ(MatrixCoefficients::kUnspecified,
+ colour.matrix_coefficients.value());
+
+ EXPECT_FALSE(colour.bits_per_channel.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.bits_per_channel.value());
+
+ EXPECT_FALSE(colour.chroma_subsampling_x.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_subsampling_x.value());
+
+ EXPECT_FALSE(colour.chroma_subsampling_y.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_subsampling_y.value());
+
+ EXPECT_FALSE(colour.cb_subsampling_x.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.cb_subsampling_x.value());
+
+ EXPECT_FALSE(colour.cb_subsampling_y.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.cb_subsampling_y.value());
+
+ EXPECT_FALSE(colour.chroma_siting_x.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_siting_x.value());
+
+ EXPECT_FALSE(colour.chroma_siting_y.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_siting_y.value());
+
+ EXPECT_FALSE(colour.range.is_present());
+ EXPECT_EQ(Range::kUnspecified, colour.range.value());
+
+ EXPECT_FALSE(colour.transfer_characteristics.is_present());
+ EXPECT_EQ(TransferCharacteristics::kUnspecified,
+ colour.transfer_characteristics.value());
+
+ EXPECT_FALSE(colour.primaries.is_present());
+ EXPECT_EQ(Primaries::kUnspecified, colour.primaries.value());
+
+ EXPECT_FALSE(colour.max_cll.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.max_cll.value());
+
+ EXPECT_FALSE(colour.max_fall.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.max_fall.value());
+
+ EXPECT_FALSE(colour.mastering_metadata.is_present());
+ EXPECT_EQ(MasteringMetadata{}, colour.mastering_metadata.value());
+}
+
+TEST_F(ColourParserTest, DefaultValues) {
+ SetReaderData({
+ 0x55, 0xB1, // ID = 0x55B1 (MatrixCoefficients).
+ 0x80, // Size = 0.
+
+ 0x55, 0xB2, // ID = 0x55B2 (BitsPerChannel).
+ 0x80, // Size = 0.
+
+ 0x55, 0xB3, // ID = 0x55B3 (ChromaSubsamplingHorz).
+ 0x80, // Size = 0.
+
+ 0x55, 0xB4, // ID = 0x55B4 (ChromaSubsamplingVert).
+ 0x80, // Size = 0.
+
+ 0x55, 0xB5, // ID = 0x55B5 (CbSubsamplingHorz).
+ 0x80, // Size = 0.
+
+ 0x55, 0xB6, // ID = 0x55B6 (CbSubsamplingVert).
+ 0x80, // Size = 0.
+
+ 0x55, 0xB7, // ID = 0x55B7 (ChromaSitingHorz).
+ 0x80, // Size = 0.
+
+ 0x55, 0xB8, // ID = 0x55B8 (ChromaSitingVert).
+ 0x80, // Size = 0.
+
+ 0x55, 0xB9, // ID = 0x55B9 (Range).
+ 0x80, // Size = 0.
+
+ 0x55, 0xBA, // ID = 0x55BA (TransferCharacteristics).
+ 0x80, // Size = 0.
+
+ 0x55, 0xBB, // ID = 0x55BB (Primaries).
+ 0x80, // Size = 0.
+
+ 0x55, 0xBC, // ID = 0x55BC (MaxCLL).
+ 0x80, // Size = 0.
+
+ 0x55, 0xBD, // ID = 0x55BD (MaxFALL).
+ 0x80, // Size = 0.
+
+ 0x55, 0xD0, // ID = 0x55D0 (MasteringMetadata).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const Colour colour = parser_.value();
+
+ EXPECT_TRUE(colour.matrix_coefficients.is_present());
+ EXPECT_EQ(MatrixCoefficients::kUnspecified,
+ colour.matrix_coefficients.value());
+
+ EXPECT_TRUE(colour.bits_per_channel.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.bits_per_channel.value());
+
+ EXPECT_TRUE(colour.chroma_subsampling_x.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_subsampling_x.value());
+
+ EXPECT_TRUE(colour.chroma_subsampling_y.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_subsampling_y.value());
+
+ EXPECT_TRUE(colour.cb_subsampling_x.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.cb_subsampling_x.value());
+
+ EXPECT_TRUE(colour.cb_subsampling_y.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.cb_subsampling_y.value());
+
+ EXPECT_TRUE(colour.chroma_siting_x.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_siting_x.value());
+
+ EXPECT_TRUE(colour.chroma_siting_y.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.chroma_siting_y.value());
+
+ EXPECT_TRUE(colour.range.is_present());
+ EXPECT_EQ(Range::kUnspecified, colour.range.value());
+
+ EXPECT_TRUE(colour.transfer_characteristics.is_present());
+ EXPECT_EQ(TransferCharacteristics::kUnspecified,
+ colour.transfer_characteristics.value());
+
+ EXPECT_TRUE(colour.primaries.is_present());
+ EXPECT_EQ(Primaries::kUnspecified, colour.primaries.value());
+
+ EXPECT_TRUE(colour.max_cll.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.max_cll.value());
+
+ EXPECT_TRUE(colour.max_fall.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), colour.max_fall.value());
+
+ EXPECT_TRUE(colour.mastering_metadata.is_present());
+ EXPECT_EQ(MasteringMetadata{}, colour.mastering_metadata.value());
+}
+
+TEST_F(ColourParserTest, CustomValues) {
+ SetReaderData({
+ 0x55, 0xB1, // ID = 0x55B1 (MatrixCoefficients).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = BT.709).
+
+ 0x55, 0xB2, // ID = 0x55B2 (BitsPerChannel).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0x55, 0xB3, // ID = 0x55B3 (ChromaSubsamplingHorz).
+ 0x81, // Size = 1.
+ 0x03, // Body (value = 3).
+
+ 0x55, 0xB4, // ID = 0x55B4 (ChromaSubsamplingVert).
+ 0x81, // Size = 1.
+ 0x04, // Body (value = 4).
+
+ 0x55, 0xB5, // ID = 0x55B5 (CbSubsamplingHorz).
+ 0x81, // Size = 1.
+ 0x05, // Body (value = 5).
+
+ 0x55, 0xB6, // ID = 0x55B6 (CbSubsamplingVert).
+ 0x81, // Size = 1.
+ 0x06, // Body (value = 6).
+
+ 0x55, 0xB7, // ID = 0x55B7 (ChromaSitingHorz).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x55, 0xB8, // ID = 0x55B8 (ChromaSitingVert).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0x55, 0xB9, // ID = 0x55B9 (Range).
+ 0x81, // Size = 1.
+ 0x03, // Body (value = 3 (derived)).
+
+ 0x55, 0xBA, // ID = 0x55BA (TransferCharacteristics).
+ 0x81, // Size = 1.
+ 0x04, // Body (value = BT.470‑6 System M with display gamma 2.2).
+
+ 0x55, 0xBB, // ID = 0x55BB (Primaries).
+ 0x81, // Size = 1.
+ 0x05, // Body (value = BT.470‑6 System B, G).
+
+ 0x55, 0xBC, // ID = 0x55BC (MaxCLL).
+ 0x81, // Size = 1.
+ 0x06, // Body (value = 6).
+
+ 0x55, 0xBD, // ID = 0x55BD (MaxFALL).
+ 0x81, // Size = 1.
+ 0x07, // Body (value = 7).
+
+ 0x55, 0xD0, // ID = 0x55D0 (MasteringMetadata).
+ 0x87, // Size = 7.
+
+ 0x55, 0xD1, // ID = 0x55D1 (PrimaryRChromaticityX).
+ 0x84, // Size = 4.
+ 0x3F, 0x80, 0x00, 0x00, // Body (value = 1).
+ });
+
+ ParseAndVerify();
+
+ const Colour colour = parser_.value();
+
+ EXPECT_TRUE(colour.matrix_coefficients.is_present());
+ EXPECT_EQ(MatrixCoefficients::kBt709, colour.matrix_coefficients.value());
+
+ EXPECT_TRUE(colour.bits_per_channel.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), colour.bits_per_channel.value());
+
+ EXPECT_TRUE(colour.chroma_subsampling_x.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(3), colour.chroma_subsampling_x.value());
+
+ EXPECT_TRUE(colour.chroma_subsampling_y.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(4), colour.chroma_subsampling_y.value());
+
+ EXPECT_TRUE(colour.cb_subsampling_x.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(5), colour.cb_subsampling_x.value());
+
+ EXPECT_TRUE(colour.cb_subsampling_y.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(6), colour.cb_subsampling_y.value());
+
+ EXPECT_TRUE(colour.chroma_siting_x.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), colour.chroma_siting_x.value());
+
+ EXPECT_TRUE(colour.chroma_siting_y.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), colour.chroma_siting_y.value());
+
+ EXPECT_TRUE(colour.range.is_present());
+ EXPECT_EQ(Range::kDerived, colour.range.value());
+
+ EXPECT_TRUE(colour.transfer_characteristics.is_present());
+ EXPECT_EQ(TransferCharacteristics::kGamma22curve,
+ colour.transfer_characteristics.value());
+
+ EXPECT_TRUE(colour.primaries.is_present());
+ EXPECT_EQ(Primaries::kBt470Bg, colour.primaries.value());
+
+ EXPECT_TRUE(colour.max_cll.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(6), colour.max_cll.value());
+
+ EXPECT_TRUE(colour.max_fall.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(7), colour.max_fall.value());
+
+ MasteringMetadata mastering_metadata{};
+ mastering_metadata.primary_r_chromaticity_x.Set(1.0, true);
+ EXPECT_TRUE(colour.mastering_metadata.is_present());
+ EXPECT_EQ(mastering_metadata, colour.mastering_metadata.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/content_enc_aes_settings_parser_test.cc b/webm_parser/tests/content_enc_aes_settings_parser_test.cc
new file mode 100644
index 0000000..4c021ef
--- /dev/null
+++ b/webm_parser/tests/content_enc_aes_settings_parser_test.cc
@@ -0,0 +1,68 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/content_enc_aes_settings_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::AesSettingsCipherMode;
+using webm::ContentEncAesSettings;
+using webm::ContentEncAesSettingsParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class ContentEncAesSettingsParserTest
+ : public ElementParserTest<ContentEncAesSettingsParser,
+ Id::kContentEncAesSettings> {};
+
+TEST_F(ContentEncAesSettingsParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const ContentEncAesSettings content_enc_aes_settings = parser_.value();
+
+ EXPECT_FALSE(content_enc_aes_settings.aes_settings_cipher_mode.is_present());
+ EXPECT_EQ(AesSettingsCipherMode::kCtr,
+ content_enc_aes_settings.aes_settings_cipher_mode.value());
+}
+
+TEST_F(ContentEncAesSettingsParserTest, DefaultValues) {
+ SetReaderData({
+ 0x47, 0xE8, // ID = 0x47E8 (AESSettingsCipherMode).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const ContentEncAesSettings content_enc_aes_settings = parser_.value();
+
+ EXPECT_TRUE(content_enc_aes_settings.aes_settings_cipher_mode.is_present());
+ EXPECT_EQ(AesSettingsCipherMode::kCtr,
+ content_enc_aes_settings.aes_settings_cipher_mode.value());
+}
+
+TEST_F(ContentEncAesSettingsParserTest, CustomValues) {
+ SetReaderData({
+ 0x47, 0xE8, // ID = 0x47E8 (AESSettingsCipherMode).
+ 0x81, // Size = 1.
+ 0x00, // Body (value = 0).
+ });
+
+ ParseAndVerify();
+
+ const ContentEncAesSettings content_enc_aes_settings = parser_.value();
+
+ EXPECT_TRUE(content_enc_aes_settings.aes_settings_cipher_mode.is_present());
+ EXPECT_EQ(static_cast<AesSettingsCipherMode>(0),
+ content_enc_aes_settings.aes_settings_cipher_mode.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/content_encoding_parser_test.cc b/webm_parser/tests/content_encoding_parser_test.cc
new file mode 100644
index 0000000..c739f50
--- /dev/null
+++ b/webm_parser/tests/content_encoding_parser_test.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/content_encoding_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ContentEncAlgo;
+using webm::ContentEncoding;
+using webm::ContentEncodingParser;
+using webm::ContentEncodingType;
+using webm::ContentEncryption;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class ContentEncodingParserTest
+ : public ElementParserTest<ContentEncodingParser, Id::kContentEncoding> {};
+
+TEST_F(ContentEncodingParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const ContentEncoding content_encoding = parser_.value();
+
+ EXPECT_FALSE(content_encoding.order.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), content_encoding.order.value());
+
+ EXPECT_FALSE(content_encoding.scope.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), content_encoding.scope.value());
+
+ EXPECT_FALSE(content_encoding.type.is_present());
+ EXPECT_EQ(ContentEncodingType::kCompression, content_encoding.type.value());
+
+ EXPECT_FALSE(content_encoding.encryption.is_present());
+ EXPECT_EQ(ContentEncryption{}, content_encoding.encryption.value());
+}
+
+TEST_F(ContentEncodingParserTest, DefaultValues) {
+ SetReaderData({
+ 0x50, 0x31, // ID = 0x5031 (ContentEncodingOrder).
+ 0x80, // Size = 0.
+
+ 0x50, 0x32, // ID = 0x5032 (ContentEncodingScope).
+ 0x80, // Size = 0.
+
+ 0x50, 0x33, // ID = 0x5033 (ContentEncodingType).
+ 0x80, // Size = 0.
+
+ 0x50, 0x35, // ID = 0x5035 (ContentEncryption).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const ContentEncoding content_encoding = parser_.value();
+
+ EXPECT_TRUE(content_encoding.order.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), content_encoding.order.value());
+
+ EXPECT_TRUE(content_encoding.scope.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), content_encoding.scope.value());
+
+ EXPECT_TRUE(content_encoding.type.is_present());
+ EXPECT_EQ(ContentEncodingType::kCompression, content_encoding.type.value());
+
+ EXPECT_TRUE(content_encoding.encryption.is_present());
+ EXPECT_EQ(ContentEncryption{}, content_encoding.encryption.value());
+}
+
+TEST_F(ContentEncodingParserTest, CustomValues) {
+ SetReaderData({
+ 0x50, 0x31, // ID = 0x5031 (ContentEncodingOrder).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x50, 0x32, // ID = 0x5032 (ContentEncodingScope).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0x50, 0x33, // ID = 0x5033 (ContentEncodingType).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = encryption).
+
+ 0x50, 0x35, // ID = 0x5035 (ContentEncryption).
+ 0x84, // Size = 4.
+
+ 0x47, 0xE1, // ID = 0x47E1 (ContentEncAlgo).
+ 0x81, // Size = 1.
+ 0x05, // Body (value = AES).
+ });
+
+ ParseAndVerify();
+
+ const ContentEncoding content_encoding = parser_.value();
+
+ EXPECT_TRUE(content_encoding.order.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), content_encoding.order.value());
+
+ EXPECT_TRUE(content_encoding.scope.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), content_encoding.scope.value());
+
+ EXPECT_TRUE(content_encoding.type.is_present());
+ EXPECT_EQ(ContentEncodingType::kEncryption, content_encoding.type.value());
+
+ ContentEncryption expected;
+ expected.algorithm.Set(ContentEncAlgo::kAes, true);
+
+ EXPECT_TRUE(content_encoding.encryption.is_present());
+ EXPECT_EQ(expected, content_encoding.encryption.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/content_encodings_parser_test.cc b/webm_parser/tests/content_encodings_parser_test.cc
new file mode 100644
index 0000000..fedb6aa
--- /dev/null
+++ b/webm_parser/tests/content_encodings_parser_test.cc
@@ -0,0 +1,82 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/content_encodings_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ContentEncoding;
+using webm::ContentEncodings;
+using webm::ContentEncodingsParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class ContentEncodingsParserTest
+ : public ElementParserTest<ContentEncodingsParser, Id::kContentEncodings> {
+};
+
+TEST_F(ContentEncodingsParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const ContentEncodings content_encodings = parser_.value();
+
+ EXPECT_EQ(static_cast<std::size_t>(0), content_encodings.encodings.size());
+}
+
+TEST_F(ContentEncodingsParserTest, DefaultValues) {
+ SetReaderData({
+ 0x62, 0x40, // ID = 0x6240 (ContentEncoding).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const ContentEncodings content_encodings = parser_.value();
+
+ ASSERT_EQ(static_cast<std::size_t>(1), content_encodings.encodings.size());
+ EXPECT_TRUE(content_encodings.encodings[0].is_present());
+ EXPECT_EQ(ContentEncoding{}, content_encodings.encodings[0].value());
+}
+
+TEST_F(ContentEncodingsParserTest, CustomValues) {
+ SetReaderData({
+ 0x62, 0x40, // ID = 0x6240 (ContentEncoding).
+ 0x84, // Size = 4.
+
+ 0x50, 0x31, // ID = 0x5031 (ContentEncodingOrder).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x62, 0x40, // ID = 0x6240 (ContentEncoding).
+ 0x84, // Size = 4.
+
+ 0x50, 0x31, // ID = 0x5031 (ContentEncodingOrder).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ ParseAndVerify();
+
+ const ContentEncodings content_encodings = parser_.value();
+
+ ContentEncoding expected;
+
+ ASSERT_EQ(static_cast<std::size_t>(2), content_encodings.encodings.size());
+ expected.order.Set(1, true);
+ EXPECT_TRUE(content_encodings.encodings[0].is_present());
+ EXPECT_EQ(expected, content_encodings.encodings[0].value());
+ expected.order.Set(2, true);
+ EXPECT_TRUE(content_encodings.encodings[1].is_present());
+ EXPECT_EQ(expected, content_encodings.encodings[1].value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/content_encryption_parser_test.cc b/webm_parser/tests/content_encryption_parser_test.cc
new file mode 100644
index 0000000..a731390
--- /dev/null
+++ b/webm_parser/tests/content_encryption_parser_test.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/content_encryption_parser.h"
+
+#include <cstdint>
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::AesSettingsCipherMode;
+using webm::ContentEncAesSettings;
+using webm::ContentEncAlgo;
+using webm::ContentEncryption;
+using webm::ContentEncryptionParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class ContentEncryptionParserTest
+ : public ElementParserTest<ContentEncryptionParser,
+ Id::kContentEncryption> {};
+
+TEST_F(ContentEncryptionParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const ContentEncryption content_encryption = parser_.value();
+
+ EXPECT_FALSE(content_encryption.algorithm.is_present());
+ EXPECT_EQ(ContentEncAlgo::kOnlySigned, content_encryption.algorithm.value());
+
+ EXPECT_FALSE(content_encryption.key_id.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{}, content_encryption.key_id.value());
+
+ EXPECT_FALSE(content_encryption.aes_settings.is_present());
+ EXPECT_EQ(ContentEncAesSettings{}, content_encryption.aes_settings.value());
+}
+
+TEST_F(ContentEncryptionParserTest, DefaultValues) {
+ SetReaderData({
+ 0x47, 0xE1, // ID = 0x47E1 (ContentEncAlgo).
+ 0x80, // Size = 0.
+
+ 0x47, 0xE2, // ID = 0x47E2 (ContentEncKeyID).
+ 0x80, // Size = 0.
+
+ 0x47, 0xE7, // ID = 0x47E7 (ContentEncAESSettings).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const ContentEncryption content_encryption = parser_.value();
+
+ EXPECT_TRUE(content_encryption.algorithm.is_present());
+ EXPECT_EQ(ContentEncAlgo::kOnlySigned, content_encryption.algorithm.value());
+
+ EXPECT_TRUE(content_encryption.key_id.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{}, content_encryption.key_id.value());
+
+ EXPECT_TRUE(content_encryption.aes_settings.is_present());
+ EXPECT_EQ(ContentEncAesSettings{}, content_encryption.aes_settings.value());
+}
+
+TEST_F(ContentEncryptionParserTest, CustomValues) {
+ SetReaderData({
+ 0x47, 0xE1, // ID = 0x47E1 (ContentEncAlgo).
+ 0x81, // Size = 1.
+ 0x05, // Body (value = AES).
+
+ 0x47, 0xE2, // ID = 0x47E2 (ContentEncKeyID).
+ 0x81, // Size = 1.
+ 0x00, // Body.
+
+ 0x47, 0xE7, // ID = 0x47E7 (ContentEncAESSettings).
+ 0x84, // Size = 4.
+
+ 0x47, 0xE8, // ID = 0x47E8 (AESSettingsCipherMode).
+ 0x81, // Size = 1.
+ 0x00, // Body (value = 0).
+ });
+
+ ParseAndVerify();
+
+ const ContentEncryption content_encryption = parser_.value();
+
+ EXPECT_TRUE(content_encryption.algorithm.is_present());
+ EXPECT_EQ(ContentEncAlgo::kAes, content_encryption.algorithm.value());
+
+ EXPECT_TRUE(content_encryption.key_id.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{0x00}, content_encryption.key_id.value());
+
+ ContentEncAesSettings expected;
+ expected.aes_settings_cipher_mode.Set(static_cast<AesSettingsCipherMode>(0),
+ true);
+ EXPECT_TRUE(content_encryption.aes_settings.is_present());
+ EXPECT_EQ(expected, content_encryption.aes_settings.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/cue_point_parser_test.cc b/webm_parser/tests/cue_point_parser_test.cc
new file mode 100644
index 0000000..9f3d48a
--- /dev/null
+++ b/webm_parser/tests/cue_point_parser_test.cc
@@ -0,0 +1,87 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/cue_point_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::CuePoint;
+using webm::CuePointParser;
+using webm::CueTrackPositions;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class CuePointParserTest
+ : public ElementParserTest<CuePointParser, Id::kCuePoint> {};
+
+TEST_F(CuePointParserTest, DefaultParse) {
+ EXPECT_CALL(callback_, OnCuePoint(metadata_, CuePoint{})).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(CuePointParserTest, DefaultValues) {
+ SetReaderData({
+ 0xB3, // ID = 0xB3 (CueTime).
+ 0x80, // Size = 0.
+
+ 0xB7, // ID = 0xB7 (CueTrackPositions).
+ 0x80, // Size = 0.
+ });
+
+ CuePoint cue_point;
+ cue_point.time.Set(0, true);
+ cue_point.cue_track_positions.emplace_back();
+ cue_point.cue_track_positions[0].Set({}, true);
+
+ EXPECT_CALL(callback_, OnCuePoint(metadata_, cue_point)).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(CuePointParserTest, CustomValues) {
+ SetReaderData({
+ 0xB3, // ID = 0xB3 (CueTime).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xB7, // ID = 0xB7 (CueTrackPositions).
+ 0x83, // Size = 3.
+
+ 0xF1, // ID = 0xF1 (CueClusterPosition).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0xB7, // ID = 0xB7 (CueTrackPositions).
+ 0x83, // Size = 3.
+
+ 0xF7, // ID = 0xF7 (CueTrack).
+ 0x81, // Size = 1.
+ 0x03, // Body (value = 3).
+ });
+
+ CuePoint cue_point;
+ cue_point.time.Set(1, true);
+ CueTrackPositions cue_track_positions;
+ cue_track_positions.cluster_position.Set(2, true);
+ cue_point.cue_track_positions.emplace_back(cue_track_positions, true);
+ cue_track_positions = {};
+ cue_track_positions.track.Set(3, true);
+ cue_point.cue_track_positions.emplace_back(cue_track_positions, true);
+
+ EXPECT_CALL(callback_, OnCuePoint(metadata_, cue_point)).Times(1);
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/cue_track_positions_parser_test.cc b/webm_parser/tests/cue_track_positions_parser_test.cc
new file mode 100644
index 0000000..d7b6844
--- /dev/null
+++ b/webm_parser/tests/cue_track_positions_parser_test.cc
@@ -0,0 +1,140 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/cue_track_positions_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::CueTrackPositions;
+using webm::CueTrackPositionsParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class CueTrackPositionsParserTest
+ : public ElementParserTest<CueTrackPositionsParser,
+ Id::kCueTrackPositions> {};
+
+TEST_F(CueTrackPositionsParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const CueTrackPositions cue_track_positions = parser_.value();
+
+ EXPECT_FALSE(cue_track_positions.track.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), cue_track_positions.track.value());
+
+ EXPECT_FALSE(cue_track_positions.cluster_position.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0),
+ cue_track_positions.cluster_position.value());
+
+ EXPECT_FALSE(cue_track_positions.relative_position.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0),
+ cue_track_positions.relative_position.value());
+
+ EXPECT_FALSE(cue_track_positions.duration.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0),
+ cue_track_positions.duration.value());
+
+ EXPECT_FALSE(cue_track_positions.block_number.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1),
+ cue_track_positions.block_number.value());
+}
+
+TEST_F(CueTrackPositionsParserTest, DefaultValues) {
+ SetReaderData({
+ 0xF7, // ID = 0xF7 (CueTrack).
+ 0x40, 0x00, // Size = 0.
+
+ 0xF1, // ID = 0xF1 (CueClusterPosition).
+ 0x40, 0x00, // Size = 0.
+
+ 0xF0, // ID = 0xF0 (CueRelativePosition).
+ 0x40, 0x00, // Size = 0.
+
+ 0xB2, // ID = 0xB2 (CueDuration).
+ 0x40, 0x00, // Size = 0.
+
+ 0x53, 0x78, // ID = 0x5378 (CueBlockNumber).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const CueTrackPositions cue_track_positions = parser_.value();
+
+ EXPECT_TRUE(cue_track_positions.track.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), cue_track_positions.track.value());
+
+ EXPECT_TRUE(cue_track_positions.cluster_position.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0),
+ cue_track_positions.cluster_position.value());
+
+ EXPECT_TRUE(cue_track_positions.relative_position.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0),
+ cue_track_positions.relative_position.value());
+
+ EXPECT_TRUE(cue_track_positions.duration.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0),
+ cue_track_positions.duration.value());
+
+ EXPECT_TRUE(cue_track_positions.block_number.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1),
+ cue_track_positions.block_number.value());
+}
+
+TEST_F(CueTrackPositionsParserTest, CustomValues) {
+ SetReaderData({
+ 0xF7, // ID = 0xF7 (CueTrack).
+ 0x40, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xF1, // ID = 0xF1 (CueClusterPosition).
+ 0x40, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0xF0, // ID = 0xF0 (CueRelativePosition).
+ 0x40, 0x01, // Size = 1.
+ 0x03, // Body (value = 3).
+
+ 0xB2, // ID = 0xB2 (CueDuration).
+ 0x40, 0x01, // Size = 1.
+ 0x04, // Body (value = 4).
+
+ 0x53, 0x78, // ID = 0x5378 (CueBlockNumber).
+ 0x81, // Size = 1.
+ 0x05, // Body (value = 5).
+ });
+
+ ParseAndVerify();
+
+ const CueTrackPositions cue_track_positions = parser_.value();
+
+ EXPECT_TRUE(cue_track_positions.track.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), cue_track_positions.track.value());
+
+ EXPECT_TRUE(cue_track_positions.cluster_position.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2),
+ cue_track_positions.cluster_position.value());
+
+ EXPECT_TRUE(cue_track_positions.relative_position.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(3),
+ cue_track_positions.relative_position.value());
+
+ EXPECT_TRUE(cue_track_positions.duration.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(4),
+ cue_track_positions.duration.value());
+
+ EXPECT_TRUE(cue_track_positions.block_number.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(5),
+ cue_track_positions.block_number.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/cues_parser_test.cc b/webm_parser/tests/cues_parser_test.cc
new file mode 100644
index 0000000..e9957b3
--- /dev/null
+++ b/webm_parser/tests/cues_parser_test.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/cues_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::CuesParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class CuesParserTest : public ElementParserTest<CuesParser, Id::kCues> {};
+
+TEST_F(CuesParserTest, DefaultValues) {
+ ParseAndVerify();
+
+ SetReaderData({
+ 0xBB, // ID = 0xBB (CuePoint).
+ 0x80, // Size = 0.
+ });
+ ParseAndVerify();
+}
+
+TEST_F(CuesParserTest, RepeatedValues) {
+ SetReaderData({
+ 0xBB, // ID = 0xBB (CuePoint).
+ 0x83, // Size = 3.
+
+ 0xB3, // ID = 0xB3 (CueTime).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xBB, // ID = 0xBB (CuePoint).
+ 0x83, // Size = 3.
+
+ 0xB3, // ID = 0xB3 (CueTime).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/date_parser_test.cc b/webm_parser/tests/date_parser_test.cc
new file mode 100644
index 0000000..e81f452
--- /dev/null
+++ b/webm_parser/tests/date_parser_test.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/date_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/status.h"
+
+using webm::DateParser;
+using webm::ElementParserTest;
+using webm::kUnknownElementSize;
+using webm::Status;
+
+namespace {
+
+class DateParserTest : public ElementParserTest<DateParser> {};
+
+TEST_F(DateParserTest, InvalidSize) {
+ TestInit(4, Status::kInvalidElementSize);
+ TestInit(9, Status::kInvalidElementSize);
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(DateParserTest, CustomDefault) {
+ ResetParser(-1);
+
+ ParseAndVerify();
+
+ EXPECT_EQ(-1, parser_.value());
+}
+
+TEST_F(DateParserTest, ValidDate) {
+ ParseAndVerify();
+ EXPECT_EQ(0, parser_.value());
+
+ SetReaderData({0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0});
+ ParseAndVerify();
+ EXPECT_EQ(0x123456789ABCDEF0, parser_.value());
+}
+
+TEST_F(DateParserTest, IncrementalParse) {
+ SetReaderData({0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10});
+
+ IncrementalParseAndVerify();
+
+ EXPECT_EQ(static_cast<std::int64_t>(0xFEDCBA9876543210), parser_.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/ebml_parser_test.cc b/webm_parser/tests/ebml_parser_test.cc
new file mode 100644
index 0000000..741f424
--- /dev/null
+++ b/webm_parser/tests/ebml_parser_test.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/ebml_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::Ebml;
+using webm::EbmlParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class EbmlParserTest : public ElementParserTest<EbmlParser, Id::kEbml> {};
+
+TEST_F(EbmlParserTest, DefaultParse) {
+ EXPECT_CALL(callback_, OnEbml(metadata_, Ebml{})).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(EbmlParserTest, DefaultValues) {
+ SetReaderData({
+ 0x42, 0x86, // ID = 0x4286 (EBMLVersion).
+ 0x80, // Size = 0.
+
+ 0x42, 0xF7, // ID = 0x42F7 (EBMLReadVersion).
+ 0x80, // Size = 0.
+
+ 0x42, 0xF2, // ID = 0x42F2 (EBMLMaxIDLength).
+ 0x40, 0x00, // Size = 0.
+
+ 0x42, 0xF3, // ID = 0x42F3 (EBMLMaxSizeLength).
+ 0x80, // Size = 0.
+
+ 0xEC, // ID = 0xEC (Void).
+ 0x40, 0x00, // Size = 0.
+
+ 0x42, 0x82, // ID = 0x4282 (DocType).
+ 0x40, 0x00, // Size = 0.
+
+ 0x42, 0x87, // ID = 0x4287 (DocTypeVersion).
+ 0x80, // Size = 0.
+
+ 0x42, 0x85, // ID = 0x4285 (DocTypeReadVersion).
+ 0x80, // Size = 0.
+
+ 0xEC, // ID = 0xEC (Void).
+ 0x82, // Size = 2.
+ 0x01, 0x02, // Body.
+ });
+
+ Ebml ebml;
+ ebml.ebml_version.Set(1, true);
+ ebml.ebml_read_version.Set(1, true);
+ ebml.ebml_max_id_length.Set(4, true);
+ ebml.ebml_max_size_length.Set(8, true);
+ ebml.doc_type.Set("matroska", true);
+ ebml.doc_type_version.Set(1, true);
+ ebml.doc_type_read_version.Set(1, true);
+
+ EXPECT_CALL(callback_, OnEbml(metadata_, ebml)).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(EbmlParserTest, CustomValues) {
+ SetReaderData({
+ 0x42, 0x86, // ID = 0x4286 (EBMLVersion).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0x42, 0xF7, // ID = 0x42F7 (EBMLReadVersion).
+ 0x81, // Size = 1.
+ 0x04, // Body (value = 4).
+
+ 0x42, 0xF2, // ID = 0x42F2 (EBMLMaxIDLength).
+ 0x40, 0x02, // Size = 2.
+ 0x00, 0x02, // Body (value = 2).
+
+ 0x42, 0xF3, // ID = 0x42F3 (EBMLMaxSizeLength).
+ 0x81, // Size = 1.
+ 0x04, // Body (value = 4).
+
+ 0xEC, // ID = 0xEC (Void).
+ 0x40, 0x00, // Size = 0.
+
+ 0x42, 0x82, // ID = 0x4282 (DocType).
+ 0x40, 0x02, // Size = 2.
+ 0x48, 0x69, // Body (value = "Hi").
+
+ 0x42, 0x87, // ID = 0x4287 (DocTypeVersion).
+ 0x81, // Size = 1.
+ 0xFF, // Body (value = 255).
+
+ 0x42, 0x85, // ID = 0x4285 (DocTypeReadVersion).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0xEC, // ID = 0xEC (Void).
+ 0x82, // Size = 2.
+ 0x01, 0x02, // Body.
+ });
+
+ Ebml ebml;
+ ebml.ebml_version.Set(2, true);
+ ebml.ebml_read_version.Set(4, true);
+ ebml.ebml_max_id_length.Set(2, true);
+ ebml.ebml_max_size_length.Set(4, true);
+ ebml.doc_type.Set("Hi", true);
+ ebml.doc_type_version.Set(255, true);
+ ebml.doc_type_read_version.Set(2, true);
+
+ EXPECT_CALL(callback_, OnEbml(metadata_, ebml)).Times(1);
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/edition_entry_parser_test.cc b/webm_parser/tests/edition_entry_parser_test.cc
new file mode 100644
index 0000000..b2d137c
--- /dev/null
+++ b/webm_parser/tests/edition_entry_parser_test.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/edition_entry_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ChapterAtom;
+using webm::EditionEntry;
+using webm::EditionEntryParser;
+using webm::ElementParserTest;
+using webm::Id;
+
+namespace {
+
+class EditionEntryParserTest
+ : public ElementParserTest<EditionEntryParser, Id::kEditionEntry> {};
+
+TEST_F(EditionEntryParserTest, DefaultParse) {
+ EXPECT_CALL(callback_, OnEditionEntry(metadata_, EditionEntry{})).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(EditionEntryParserTest, DefaultValues) {
+ SetReaderData({
+ 0xB6, // ID = 0xB6 (ChapterAtom).
+ 0x80, // Size = 0.
+ });
+
+ EditionEntry edition_entry;
+ edition_entry.atoms.emplace_back();
+ edition_entry.atoms[0].Set({}, true);
+
+ EXPECT_CALL(callback_, OnEditionEntry(metadata_, edition_entry)).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(EditionEntryParserTest, CustomValues) {
+ SetReaderData({
+ 0xB6, // ID = 0xB6 (ChapterAtom).
+ 0x40, 0x04, // Size = 4.
+
+ 0x73, 0xC4, // ID = 0x73C4 (ChapterUID).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xB6, // ID = 0xB6 (ChapterAtom).
+ 0x40, 0x04, // Size = 4.
+
+ 0x73, 0xC4, // ID = 0x73C4 (ChapterUID).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ EditionEntry edition_entry;
+ ChapterAtom chapter_atom;
+ chapter_atom.uid.Set(1, true);
+ edition_entry.atoms.emplace_back(chapter_atom, true);
+ chapter_atom.uid.Set(2, true);
+ edition_entry.atoms.emplace_back(chapter_atom, true);
+
+ EXPECT_CALL(callback_, OnEditionEntry(metadata_, edition_entry)).Times(1);
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/element_test.cc b/webm_parser/tests/element_test.cc
new file mode 100644
index 0000000..d88ac07
--- /dev/null
+++ b/webm_parser/tests/element_test.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "webm/element.h"
+
+#include <string>
+#include <utility>
+
+#include "gtest/gtest.h"
+
+using webm::Element;
+
+namespace {
+
+class ElementTest : public testing::Test {};
+
+TEST_F(ElementTest, Construction) {
+ Element<int> value_initialized;
+ EXPECT_EQ(false, value_initialized.is_present());
+ EXPECT_EQ(0, value_initialized.value());
+
+ Element<int> absent_custom_default(1);
+ EXPECT_EQ(false, absent_custom_default.is_present());
+ EXPECT_EQ(1, absent_custom_default.value());
+
+ Element<int> present(2, true);
+ EXPECT_EQ(true, present.is_present());
+ EXPECT_EQ(2, present.value());
+}
+
+TEST_F(ElementTest, Assignment) {
+ Element<int> e;
+
+ e.Set(42, true);
+ EXPECT_EQ(true, e.is_present());
+ EXPECT_EQ(42, e.value());
+
+ *e.mutable_value() = 0;
+ EXPECT_EQ(true, e.is_present());
+ EXPECT_EQ(0, e.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/float_parser_test.cc b/webm_parser/tests/float_parser_test.cc
new file mode 100644
index 0000000..ffa392d
--- /dev/null
+++ b/webm_parser/tests/float_parser_test.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/float_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/status.h"
+
+using webm::ElementParserTest;
+using webm::FloatParser;
+using webm::kUnknownElementSize;
+using webm::Status;
+
+namespace {
+
+class FloatParserTest : public ElementParserTest<FloatParser> {};
+
+TEST_F(FloatParserTest, InvalidSize) {
+ TestInit(1, Status::kInvalidElementSize);
+ TestInit(5, Status::kInvalidElementSize);
+ TestInit(9, Status::kInvalidElementSize);
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(FloatParserTest, CustomDefault) {
+ const double sqrt2 = 1.4142135623730951454746218587388284504413604736328125;
+ ResetParser(sqrt2);
+
+ ParseAndVerify();
+
+ EXPECT_EQ(sqrt2, parser_.value());
+}
+
+TEST_F(FloatParserTest, ValidFloat) {
+ ParseAndVerify();
+ EXPECT_EQ(0, parser_.value());
+
+ SetReaderData({0x40, 0xC9, 0x0F, 0xDB});
+ ParseAndVerify();
+ EXPECT_EQ(6.283185482025146484375, parser_.value());
+
+ SetReaderData({0x40, 0x05, 0xBF, 0x0A, 0x8B, 0x14, 0x57, 0x69});
+ ParseAndVerify();
+ EXPECT_EQ(2.718281828459045090795598298427648842334747314453125,
+ parser_.value());
+}
+
+TEST_F(FloatParserTest, IncrementalParse) {
+ SetReaderData({0x3F, 0xF9, 0xE3, 0x77, 0x9B, 0x97, 0xF4, 0xA8});
+
+ IncrementalParseAndVerify();
+
+ EXPECT_EQ(1.6180339887498949025257388711906969547271728515625,
+ parser_.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/id_element_parser_test.cc b/webm_parser/tests/id_element_parser_test.cc
new file mode 100644
index 0000000..8cfbb45
--- /dev/null
+++ b/webm_parser/tests/id_element_parser_test.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/id_element_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/element.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::IdElementParser;
+using webm::kUnknownElementSize;
+using webm::Status;
+
+namespace {
+
+class IdElementParserTest : public ElementParserTest<IdElementParser> {};
+
+TEST_F(IdElementParserTest, InvalidSize) {
+ TestInit(0, Status::kInvalidElementSize);
+ TestInit(5, Status::kInvalidElementSize);
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(IdElementParserTest, ValidId) {
+ SetReaderData({0xEC});
+ ParseAndVerify();
+ EXPECT_EQ(Id::kVoid, parser_.value());
+
+ SetReaderData({0x1F, 0x43, 0xB6, 0x75});
+ ParseAndVerify();
+ EXPECT_EQ(Id::kCluster, parser_.value());
+}
+
+TEST_F(IdElementParserTest, IncrementalParse) {
+ SetReaderData({0x2A, 0xD7, 0xB1});
+
+ IncrementalParseAndVerify();
+
+ EXPECT_EQ(Id::kTimecodeScale, parser_.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/id_parser_test.cc b/webm_parser/tests/id_parser_test.cc
new file mode 100644
index 0000000..73d13d8
--- /dev/null
+++ b/webm_parser/tests/id_parser_test.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/id_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/parser_test.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using webm::Id;
+using webm::IdParser;
+using webm::ParserTest;
+using webm::Status;
+
+namespace {
+
+class IdParserTest : public ParserTest<IdParser> {};
+
+TEST_F(IdParserTest, InvalidId) {
+ SetReaderData({0x00});
+ ParseAndExpectResult(Status::kInvalidElementId);
+
+ ResetParser();
+ SetReaderData({0x0F});
+ ParseAndExpectResult(Status::kInvalidElementId);
+}
+
+TEST_F(IdParserTest, EarlyEndOfFile) {
+ SetReaderData({0x40});
+ ParseAndExpectResult(Status::kEndOfFile);
+}
+
+TEST_F(IdParserTest, ValidId) {
+ SetReaderData({0x80});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<Id>(0x80), parser_.id());
+
+ ResetParser();
+ SetReaderData({0x1F, 0xFF, 0xFF, 0xFF});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<Id>(0x1FFFFFFF), parser_.id());
+}
+
+TEST_F(IdParserTest, IncrementalParse) {
+ SetReaderData({0x1A, 0x45, 0xDF, 0xA3});
+ IncrementalParseAndVerify();
+ EXPECT_EQ(Id::kEbml, parser_.id());
+}
+
+} // namespace
diff --git a/webm_parser/tests/info_parser_test.cc b/webm_parser/tests/info_parser_test.cc
new file mode 100644
index 0000000..fecc33a
--- /dev/null
+++ b/webm_parser/tests/info_parser_test.cc
@@ -0,0 +1,105 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/info_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::Info;
+using webm::InfoParser;
+
+namespace {
+
+class InfoParserTest : public ElementParserTest<InfoParser, Id::kInfo> {};
+
+TEST_F(InfoParserTest, DefaultParse) {
+ EXPECT_CALL(callback_, OnInfo(metadata_, Info{})).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(InfoParserTest, DefaultValues) {
+ SetReaderData({
+ 0x2A, 0xD7, 0xB1, // ID = 0x2AD7B1 (TimecodeScale).
+ 0x80, // Size = 0.
+
+ 0x44, 0x89, // ID = 0x4489 (Duration).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x44, 0x61, // ID = 0x4461 (DateUTC).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x7B, 0xA9, // ID = 0x7BA9 (Title).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x4D, 0x80, // ID = 0x4D80 (MuxingApp).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x57, 0x41, // ID = 0x5741 (WritingApp).
+ 0x20, 0x00, 0x00, // Size = 0.
+ });
+
+ Info info;
+ info.timecode_scale.Set(1000000, true);
+ info.duration.Set(0, true);
+ info.date_utc.Set(0, true);
+ info.title.Set("", true);
+ info.muxing_app.Set("", true);
+ info.writing_app.Set("", true);
+
+ EXPECT_CALL(callback_, OnInfo(metadata_, info)).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(InfoParserTest, CustomValues) {
+ SetReaderData({
+ 0x2A, 0xD7, 0xB1, // ID = 0x2AD7B1 (TimecodeScale).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x44, 0x89, // ID = 0x4489 (Duration).
+ 0x84, // Size = 4.
+ 0x4D, 0x8E, 0xF3, 0xC2, // Body (value = 299792448.0f).
+
+ 0x44, 0x61, // ID = 0x4461 (DateUTC).
+ 0x88, // Size = 8.
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Body (value = -1).
+
+ 0x7B, 0xA9, // ID = 0x7BA9 (Title).
+ 0x10, 0x00, 0x00, 0x03, // Size = 3.
+ 0x66, 0x6F, 0x6F, // Body (value = "foo").
+
+ 0x4D, 0x80, // ID = 0x4D80 (MuxingApp).
+ 0x10, 0x00, 0x00, 0x03, // Size = 3.
+ 0x62, 0x61, 0x72, // Body (value = "bar").
+
+ 0x57, 0x41, // ID = 0x5741 (WritingApp).
+ 0x10, 0x00, 0x00, 0x03, // Size = 3.
+ 0x62, 0x61, 0x7A, // Body (value = "baz").
+ });
+
+ Info info;
+ info.timecode_scale.Set(1, true);
+ info.duration.Set(299792448.0f, true);
+ info.date_utc.Set(-1, true);
+ info.title.Set("foo", true);
+ info.muxing_app.Set("bar", true);
+ info.writing_app.Set("baz", true);
+
+ EXPECT_CALL(callback_, OnInfo(metadata_, info)).Times(1);
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/int_parser_test.cc b/webm_parser/tests/int_parser_test.cc
new file mode 100644
index 0000000..c2e74e2
--- /dev/null
+++ b/webm_parser/tests/int_parser_test.cc
@@ -0,0 +1,113 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/int_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/status.h"
+
+using webm::ElementParserTest;
+using webm::kUnknownElementSize;
+using webm::SignedIntParser;
+using webm::Status;
+using webm::UnsignedIntParser;
+
+namespace {
+
+class UnsignedIntParserTest : public ElementParserTest<UnsignedIntParser> {};
+
+TEST_F(UnsignedIntParserTest, UnsignedInvalidSize) {
+ TestInit(9, Status::kInvalidElementSize);
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(UnsignedIntParserTest, UnsignedCustomDefault) {
+ ResetParser(1);
+
+ ParseAndVerify();
+
+ EXPECT_EQ(static_cast<std::uint64_t>(1), parser_.value());
+}
+
+TEST_F(UnsignedIntParserTest, UnsignedValidInt) {
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0), parser_.value());
+
+ SetReaderData({0x01, 0x02, 0x03});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0x010203), parser_.value());
+
+ SetReaderData({0xFF, 0xFF, 0xFF, 0xFF, 0xFF});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0xFFFFFFFFFF), parser_.value());
+
+ SetReaderData({0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0x123456789ABCDEF0), parser_.value());
+}
+
+TEST_F(UnsignedIntParserTest, UnsignedIncrementalParse) {
+ SetReaderData({0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10});
+
+ IncrementalParseAndVerify();
+
+ EXPECT_EQ(static_cast<std::uint64_t>(0xFEDCBA9876543210), parser_.value());
+}
+
+class SignedIntParserTest : public ElementParserTest<SignedIntParser> {};
+
+TEST_F(SignedIntParserTest, SignedInvalidSize) {
+ TestInit(9, Status::kInvalidElementSize);
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(SignedIntParserTest, SignedCustomDefault) {
+ ResetParser(-1);
+
+ ParseAndVerify();
+
+ EXPECT_EQ(-1, parser_.value());
+}
+
+TEST_F(SignedIntParserTest, SignedValidPositiveInt) {
+ ParseAndVerify();
+ EXPECT_EQ(0, parser_.value());
+
+ SetReaderData({0x7f});
+ ParseAndVerify();
+ EXPECT_EQ(0x7f, parser_.value());
+
+ SetReaderData({0x12, 0xD6, 0x87});
+ ParseAndVerify();
+ EXPECT_EQ(1234567, parser_.value());
+
+ SetReaderData({0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0});
+ ParseAndVerify();
+ EXPECT_EQ(0x123456789ABCDEF0, parser_.value());
+}
+
+TEST_F(SignedIntParserTest, SignedValidNegativeInt) {
+ SetReaderData({0xFF});
+ ParseAndVerify();
+ EXPECT_EQ(-1, parser_.value());
+
+ SetReaderData({0xF8, 0xA4, 0x32, 0xEB});
+ ParseAndVerify();
+ EXPECT_EQ(-123456789, parser_.value());
+}
+
+TEST_F(SignedIntParserTest, SignedIncrementalParse) {
+ SetReaderData({0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x11});
+
+ IncrementalParseAndVerify();
+
+ EXPECT_EQ(-81985529216486895, parser_.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/istream_reader_test.cc b/webm_parser/tests/istream_reader_test.cc
new file mode 100644
index 0000000..d9c7293
--- /dev/null
+++ b/webm_parser/tests/istream_reader_test.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "webm/istream_reader.h"
+
+#include <algorithm>
+#include <array>
+#include <cstdint>
+#include <sstream>
+#include <type_traits>
+
+#include "gtest/gtest.h"
+
+using webm::IstreamReader;
+using webm::Status;
+
+namespace {
+
+// Creates a std::array from a string literal (and omits the trailing
+// NUL-character).
+template <std::size_t N>
+std::array<std::uint8_t, N - 1> ArrayFromString(const char (&string)[N]) {
+ std::array<std::uint8_t, N - 1> array;
+ std::copy_n(string, N - 1, array.begin());
+ return array;
+}
+
+class IstreamReaderTest : public testing::Test {};
+
+TEST_F(IstreamReaderTest, Read) {
+ std::array<std::uint8_t, 15> buffer{};
+ std::uint64_t count;
+ Status status;
+
+ IstreamReader reader =
+ IstreamReader::Emplace<std::istringstream>("abcdefghij");
+
+ status = reader.Read(5, buffer.data(), &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(5), count);
+
+ status = reader.Read(10, buffer.data() + 5, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(5), count);
+
+ std::array<std::uint8_t, 15> expected =
+ ArrayFromString("abcdefghij\0\0\0\0\0");
+ EXPECT_EQ(expected, buffer);
+
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(Status::kEndOfFile, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), count);
+}
+
+TEST_F(IstreamReaderTest, Skip) {
+ std::uint64_t count;
+ Status status;
+
+ IstreamReader reader =
+ IstreamReader::Emplace<std::istringstream>("abcdefghij");
+
+ status = reader.Skip(3, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(3), count);
+
+ status = reader.Skip(10, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(7), count);
+
+ status = reader.Skip(1, &count);
+ EXPECT_EQ(Status::kEndOfFile, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), count);
+}
+
+TEST_F(IstreamReaderTest, ReadAndSkip) {
+ std::array<std::uint8_t, 10> buffer = {};
+ std::uint64_t count;
+ Status status;
+
+ IstreamReader reader =
+ IstreamReader::Emplace<std::istringstream>("AaBbCcDdEe");
+
+ status = reader.Read(5, buffer.data(), &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(5), count);
+
+ status = reader.Skip(3, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(3), count);
+
+ status = reader.Read(5, buffer.data() + 5, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+
+ std::array<std::uint8_t, 10> expected = ArrayFromString("AaBbCEe\0\0\0");
+ EXPECT_EQ(expected, buffer);
+}
+
+TEST_F(IstreamReaderTest, Position) {
+ std::array<std::uint8_t, 10> buffer = {};
+ std::uint64_t count;
+ Status status;
+
+ IstreamReader reader =
+ IstreamReader::Emplace<std::istringstream>("AaBbCcDdEe");
+ EXPECT_EQ(static_cast<std::uint64_t>(0), reader.Position());
+
+ status = reader.Read(5, buffer.data(), &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(5), count);
+ EXPECT_EQ(static_cast<std::uint64_t>(5), reader.Position());
+
+ status = reader.Skip(3, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(3), count);
+ EXPECT_EQ(static_cast<std::uint64_t>(8), reader.Position());
+
+ status = reader.Read(5, buffer.data() + 5, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+ EXPECT_EQ(static_cast<std::uint64_t>(10), reader.Position());
+
+ std::array<std::uint8_t, 10> expected = ArrayFromString("AaBbCEe\0\0\0");
+ EXPECT_EQ(expected, buffer);
+}
+
+} // namespace
diff --git a/webm_parser/tests/limited_reader_test.cc b/webm_parser/tests/limited_reader_test.cc
new file mode 100644
index 0000000..9064a45
--- /dev/null
+++ b/webm_parser/tests/limited_reader_test.cc
@@ -0,0 +1,250 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "test_utils/limited_reader.h"
+
+#include <array>
+#include <cstdint>
+#include <memory>
+
+#include "gtest/gtest.h"
+
+#include "webm/buffer_reader.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+using webm::BufferReader;
+using webm::LimitedReader;
+using webm::Reader;
+using webm::Status;
+
+namespace {
+
+class LimitedReaderTest : public testing::Test {};
+
+TEST_F(LimitedReaderTest, UnlimitedRead) {
+ std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}};
+ LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4})));
+ std::uint64_t count;
+ Status status;
+
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(4), count);
+
+ std::array<std::uint8_t, 4> expected_buffer = {{1, 2, 3, 4}};
+ EXPECT_EQ(expected_buffer, buffer);
+}
+
+TEST_F(LimitedReaderTest, UnlimitedSkip) {
+ LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4})));
+ std::uint64_t count;
+ Status status;
+
+ status = reader.Skip(4, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(4), count);
+}
+
+TEST_F(LimitedReaderTest, Position) {
+ std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}};
+ LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4})));
+ std::uint64_t count;
+ Status status;
+
+ EXPECT_EQ(static_cast<std::uint64_t>(0), reader.Position());
+
+ status = reader.Read(2, buffer.data(), &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+
+ status = reader.Skip(2, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+ EXPECT_EQ(static_cast<std::uint64_t>(4), reader.Position());
+
+ std::array<std::uint8_t, 4> expected_buffer = {{1, 2, 0, 0}};
+ EXPECT_EQ(expected_buffer, buffer);
+}
+
+TEST_F(LimitedReaderTest, LimitIndividualRead) {
+ std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}};
+ LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4})));
+ std::uint64_t count;
+ Status status;
+
+ reader.set_single_read_limit(1);
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), count);
+
+ status = reader.Read(buffer.size() + 1, buffer.data() + 1, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), count);
+
+ reader.set_single_read_limit(2);
+ status = reader.Read(buffer.size() - 2, buffer.data() + 2, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+
+ std::array<std::uint8_t, 4> expected_buffer = {{1, 2, 3, 4}};
+ EXPECT_EQ(expected_buffer, buffer);
+}
+
+TEST_F(LimitedReaderTest, LimitIndividualSkip) {
+ LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4})));
+ std::uint64_t count;
+ Status status;
+
+ reader.set_single_skip_limit(1);
+ status = reader.Skip(4, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), count);
+
+ status = reader.Skip(3, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), count);
+
+ reader.set_single_skip_limit(2);
+ status = reader.Skip(2, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+}
+
+TEST_F(LimitedReaderTest, LimitRepeatedRead) {
+ std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}};
+ LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4})));
+ std::uint64_t count;
+ Status status;
+
+ reader.set_total_read_limit(1);
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), count);
+
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(Status::kWouldBlock, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), count);
+
+ reader.set_total_read_limit(2);
+ status = reader.Read(buffer.size() - 1, buffer.data() + 1, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+
+ reader.set_total_read_limit(1);
+ status = reader.Read(buffer.size() - 3, buffer.data() + 3, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), count);
+
+ std::array<std::uint8_t, 4> expected_buffer = {{1, 2, 3, 4}};
+ EXPECT_EQ(expected_buffer, buffer);
+}
+
+TEST_F(LimitedReaderTest, LimitRepeatedSkip) {
+ LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4})));
+ std::uint64_t count;
+ Status status;
+
+ reader.set_total_skip_limit(1);
+ status = reader.Skip(4, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), count);
+
+ status = reader.Skip(4, &count);
+ EXPECT_EQ(Status::kWouldBlock, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), count);
+
+ reader.set_total_skip_limit(2);
+ status = reader.Skip(3, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+
+ reader.set_total_skip_limit(1);
+ status = reader.Skip(1, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), count);
+}
+
+TEST_F(LimitedReaderTest, LimitReadsAndSkips) {
+ std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}};
+ LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4})));
+ std::uint64_t count;
+ Status status;
+
+ reader.set_total_read_skip_limit(1);
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), count);
+
+ status = reader.Skip(buffer.size(), &count);
+ EXPECT_EQ(Status::kWouldBlock, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), count);
+
+ reader.set_total_read_skip_limit(2);
+ status = reader.Skip(buffer.size() - 1, &count);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(2), count);
+
+ reader.set_total_read_skip_limit(1);
+ status = reader.Read(buffer.size() - 3, buffer.data() + 3, &count);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), count);
+
+ std::array<std::uint8_t, 4> expected = {{1, 0, 0, 4}};
+ EXPECT_EQ(expected, buffer);
+}
+
+TEST_F(LimitedReaderTest, CustomStatusWhenBlocked) {
+ std::array<std::uint8_t, 4> buffer = {{0, 0, 0, 0}};
+ LimitedReader reader(std::unique_ptr<Reader>(new BufferReader({1, 2, 3, 4})));
+ std::uint64_t count;
+ Status status;
+ Status expected_status = Status(-123);
+ const std::uint64_t kZeroCount = 0;
+
+ reader.set_single_read_limit(0);
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(Status::kWouldBlock, status.code);
+ EXPECT_EQ(kZeroCount, count);
+
+ reader.set_return_status_when_blocked(expected_status);
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(expected_status.code, status.code);
+ EXPECT_EQ(kZeroCount, count);
+
+ reader.set_single_read_limit(std::numeric_limits<std::size_t>::max());
+ reader.set_total_read_limit(0);
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(expected_status.code, status.code);
+ EXPECT_EQ(kZeroCount, count);
+
+ reader.set_total_read_limit(std::numeric_limits<std::size_t>::max());
+ reader.set_single_skip_limit(0);
+ status = reader.Skip(buffer.size(), &count);
+ EXPECT_EQ(expected_status.code, status.code);
+ EXPECT_EQ(kZeroCount, count);
+
+ reader.set_single_skip_limit(std::numeric_limits<std::uint64_t>::max());
+ reader.set_total_skip_limit(0);
+ status = reader.Skip(buffer.size(), &count);
+ EXPECT_EQ(expected_status.code, status.code);
+ EXPECT_EQ(kZeroCount, count);
+
+ reader.set_total_skip_limit(std::numeric_limits<std::uint64_t>::max());
+ reader.set_total_read_skip_limit(0);
+ status = reader.Read(buffer.size(), buffer.data(), &count);
+ EXPECT_EQ(expected_status.code, status.code);
+ EXPECT_EQ(kZeroCount, count);
+ status = reader.Skip(buffer.size(), &count);
+ EXPECT_EQ(expected_status.code, status.code);
+ EXPECT_EQ(kZeroCount, count);
+
+ std::array<std::uint8_t, 4> expected_buffer = {{0, 0, 0, 0}};
+ EXPECT_EQ(expected_buffer, buffer);
+}
+
+} // namespace
diff --git a/webm_parser/tests/master_parser_test.cc b/webm_parser/tests/master_parser_test.cc
new file mode 100644
index 0000000..5f36fd2
--- /dev/null
+++ b/webm_parser/tests/master_parser_test.cc
@@ -0,0 +1,382 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/master_parser.h"
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "src/byte_parser.h"
+#include "test_utils/element_parser_test.h"
+#include "webm/element.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::InSequence;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgPointee;
+
+using webm::Action;
+using webm::BinaryParser;
+using webm::ElementMetadata;
+using webm::ElementParser;
+using webm::ElementParserTest;
+using webm::Id;
+using webm::kUnknownElementSize;
+using webm::LimitedReader;
+using webm::MasterParser;
+using webm::Status;
+
+namespace {
+
+// Simple helper method that just takes an Id and ElementParser* and returns
+// them in a std::pair<Id, std::unique_ptr<ElementParser>>. Provided just for
+// simplifying some statements.
+std::pair<Id, std::unique_ptr<ElementParser>> ParserForId(
+ Id id, ElementParser* parser) {
+ return {id, std::unique_ptr<ElementParser>(parser)};
+}
+
+class MasterParserTest : public ElementParserTest<MasterParser> {};
+
+// Errors parsing an ID should be returned to the caller.
+TEST_F(MasterParserTest, BadId) {
+ SetReaderData({
+ 0x00, // Invalid ID.
+ 0x80, // ID = 0x80 (unknown).
+ 0x80, // Size = 0.
+ });
+
+ EXPECT_CALL(callback_, OnElementBegin(_, _)).Times(0);
+
+ ParseAndExpectResult(Status::kInvalidElementId);
+}
+
+// Errors from a child parser's Init should be returned to the caller.
+TEST_F(MasterParserTest, ChildInitFails) {
+ SetReaderData({
+ 0xEC, // ID = 0xEC (Void).
+ 0xFF, // Size = unknown.
+ });
+
+ const ElementMetadata metadata = {Id::kVoid, 2, kUnknownElementSize, 0};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ ParseAndExpectResult(Status::kInvalidElementSize);
+}
+
+// Indefinite unknown children should result in an error.
+TEST_F(MasterParserTest, IndefiniteUnknownChild) {
+ SetReaderData({
+ 0x80, // ID = 0x80 (unknown).
+ 0xFF, // Size = unknown.
+ 0x00, 0x00, // Body.
+ });
+
+ EXPECT_CALL(callback_, OnElementBegin(_, _)).Times(0);
+
+ ParseAndExpectResult(Status::kIndefiniteUnknownElement);
+}
+
+// Child elements that overflow the master element's size should be detected.
+TEST_F(MasterParserTest, ChildOverflow) {
+ SetReaderData({
+ 0xEC, // ID = 0xEC (Void).
+ 0x82, // Size = 2.
+ 0x00, 0x00, // Body.
+ });
+
+ EXPECT_CALL(callback_, OnElementBegin(_, _)).Times(0);
+ EXPECT_CALL(callback_, OnVoid(_, _, _)).Times(0);
+
+ ParseAndExpectResult(Status::kElementOverflow, reader_.size() - 1);
+}
+
+// Child elements with an unknown size can't be naively checked to see if they
+// overflow the master element's size. Make sure the overflow is still detected.
+TEST_F(MasterParserTest, ChildOverflowWithUnknownSize) {
+ SetReaderData({
+ 0xA1, // ID = 0xA1 (Block) (master).
+ 0xFF, // Size = unknown.
+
+ 0xEC, // ID = 0xEC (Void) (child).
+ 0x81, // Size = 1.
+ 0x12, // Body.
+ });
+
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {Id::kBlock, 2, kUnknownElementSize, 0};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+ }
+
+ ResetParser(ParserForId(Id::kBlock, new MasterParser));
+
+ ParseAndExpectResult(Status::kElementOverflow, 4);
+}
+
+// An element with an unknown size should be terminated by its parents bounds.
+TEST_F(MasterParserTest, ChildWithUnknownSizeBoundedByParentSize) {
+ SetReaderData({
+ 0xA1, // ID = 0xA1 (Block) (master).
+ 0xFF, // Size = unknown.
+
+ 0xEC, // ID = 0xEC (Void) (child).
+ 0x81, // Size = 1.
+ 0x12, // Body.
+
+ 0x00, // Invalid ID. This should not be read.
+ });
+
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {Id::kBlock, 2, kUnknownElementSize, 0};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ metadata = {Id::kVoid, 2, 1, 2};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1);
+ }
+
+ ResetParser(ParserForId(Id::kBlock, new MasterParser));
+
+ ParseAndVerify(reader_.size() - 1);
+}
+
+// An empty master element is okay.
+TEST_F(MasterParserTest, Empty) {
+ EXPECT_CALL(callback_, OnElementBegin(_, _)).Times(0);
+
+ ParseAndVerify();
+}
+
+TEST_F(MasterParserTest, DefaultActionIsRead) {
+ SetReaderData({
+ 0xEC, // ID = 0xEC (Void).
+ 0x80, // Size = 0.
+ });
+
+ {
+ InSequence dummy;
+
+ const ElementMetadata metadata = {Id::kVoid, 2, 0, 0};
+
+ // This intentionally does not set the action and relies on the parser using
+ // a default action value of kRead.
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull()))
+ .WillOnce(Return(Status(Status::kOkCompleted)));
+ EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+// Unrecognized children should be skipped over.
+TEST_F(MasterParserTest, UnknownChildren) {
+ SetReaderData({
+ 0x40, 0x00, // ID = 0x4000 (unknown).
+ 0x80, // Size = 0.
+
+ 0x80, // ID = 0x80 (unknown).
+ 0x40, 0x00, // Size = 0.
+ });
+
+ EXPECT_CALL(callback_, OnVoid(_, _, _)).Times(0);
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {static_cast<Id>(0x4000), 3, 0, 0};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ metadata = {static_cast<Id>(0x80), 3, 0, 3};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+// A master element with unknown size is terminated by the first element that is
+// not a valid child.
+TEST_F(MasterParserTest, UnknownSize) {
+ SetReaderData({
+ // Void elements may appear anywhere in a master element and should not
+ // terminate the parse for a master element with an unknown size. In other
+ // words, they're always valid children.
+ 0xEC, // ID = 0xEC (Void).
+ 0x81, // Size = 1.
+ 0x00, // Body.
+
+ // This element marks the end for the parser since this is the first
+ // unrecognized element. The ID and size should be read (which the parser
+ // uses to determine the end has been reached), but nothing more.
+ 0x80, // ID = 0x80 (unknown).
+ 0x81, // Size = 1.
+ 0x12, // Body.
+ });
+
+ {
+ InSequence dummy;
+
+ const ElementMetadata metadata = {Id::kVoid, 2, 1, 0};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1);
+ }
+
+ ParseAndVerify(kUnknownElementSize);
+
+ EXPECT_EQ(static_cast<std::uint64_t>(5), reader_.Position());
+}
+
+// Consecutive elements with unknown size should parse without issues, despite
+// the internal parsers having to read ahead into the next (non-child) element.
+TEST_F(MasterParserTest, MultipleUnknownChildSize) {
+ SetReaderData({
+ 0xA1, // ID = 0xA1 (Block) (master).
+ 0xFF, // Size = unknown.
+
+ 0xEC, // ID = 0xEC (Void) (child).
+ 0x81, // Size = 1.
+ 0x12, // Body.
+
+ 0xA1, // ID = 0xA1 (Block) (master).
+ 0xFF, // Size = unknown.
+
+ 0xEC, // ID = 0xEC (Void) (child).
+ 0x81, // Size = 1.
+ 0x13, // Body.
+ });
+
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {Id::kBlock, 2, kUnknownElementSize, 0};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ metadata = {Id::kVoid, 2, 1, 2};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1);
+
+ metadata = {Id::kBlock, 2, kUnknownElementSize, 5};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ metadata = {Id::kVoid, 2, 1, 7};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1);
+ }
+
+ ResetParser(ParserForId(Id::kBlock, new MasterParser));
+
+ ParseAndVerify();
+}
+
+// Reaching the end of the file while reading an element with unknown size
+// should return Status::kOkCompleted instead of Status::kEndOfFile.
+TEST_F(MasterParserTest, UnknownSizeToFileEnd) {
+ SetReaderData({
+ 0xEC, // ID = 0xEC (Void).
+ 0x81, // Size = 1.
+ 0x00, // Body.
+ });
+
+ {
+ InSequence dummy;
+
+ const ElementMetadata metadata = {Id::kVoid, 2, 1, 0};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+// Parsing one byte at a time is okay.
+TEST_F(MasterParserTest, IncrementalParse) {
+ SetReaderData({
+ 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML).
+ 0x08, 0x00, 0x00, 0x00, 0x06, // Size = 6.
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // Body.
+ });
+
+ const ElementMetadata metadata = {Id::kEbml, 9, 6, 0};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ BinaryParser* binary_parser = new BinaryParser;
+ ResetParser(ParserForId(Id::kEbml, binary_parser));
+
+ IncrementalParseAndVerify();
+
+ std::vector<std::uint8_t> expected = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
+ EXPECT_EQ(expected, binary_parser->value());
+}
+
+// Alternating actions between skip and read is okay. The parser should remember
+// the requested action between repeated calls to Feed.
+TEST_F(MasterParserTest, IncrementalSkipThenReadThenSkip) {
+ SetReaderData({
+ 0xA1, // ID = 0xA1 (Block) (master).
+ 0x83, // Size = 3.
+
+ 0xEC, // ID = 0xEC (Void) (child).
+ 0x81, // Size = 1.
+ 0x12, // Body.
+
+ 0xA1, // ID = 0xA1 (Block) (master).
+ 0x83, // Size = 3.
+
+ 0xEC, // ID = 0xEC (Void) (child).
+ 0x81, // Size = 1.
+ 0x13, // Body.
+
+ 0xA1, // ID = 0xA1 (Block) (master).
+ 0xFF, // Size = unknown.
+
+ 0xEC, // ID = 0xEC (Void) (child).
+ 0x81, // Size = 1.
+ 0x14, // Body.
+ });
+
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {Id::kBlock, 2, 3, 0};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull()))
+ .WillOnce(Return(Status(Status::kOkPartial)))
+ .WillOnce(DoAll(SetArgPointee<1>(Action::kSkip),
+ Return(Status(Status::kOkCompleted))));
+
+ metadata = {Id::kBlock, 2, 3, 5};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ metadata = {Id::kVoid, 2, 1, 7};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ // Expect to get called twice because we'll cap the LimitedReader to 1-byte
+ // reads. The first attempt to read will fail because we'll have already
+ // reached the 1-byte max.
+ EXPECT_CALL(callback_, OnVoid(metadata, NotNull(), NotNull())).Times(2);
+
+ metadata = {Id::kBlock, 2, kUnknownElementSize, 10};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull()))
+ .WillOnce(DoAll(SetArgPointee<1>(Action::kSkip),
+ Return(Status(Status::kOkCompleted))));
+ }
+
+ ResetParser(ParserForId(Id::kBlock, new MasterParser));
+
+ IncrementalParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/master_value_parser_test.cc b/webm_parser/tests/master_value_parser_test.cc
new file mode 100644
index 0000000..c9d5779
--- /dev/null
+++ b/webm_parser/tests/master_value_parser_test.cc
@@ -0,0 +1,366 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/master_value_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "src/int_parser.h"
+#include "test_utils/element_parser_test.h"
+#include "webm/element.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::InSequence;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgPointee;
+
+using webm::Action;
+using webm::Callback;
+using webm::Cluster;
+using webm::ElementMetadata;
+using webm::ElementParserTest;
+using webm::Id;
+using webm::MasterValueParser;
+using webm::Status;
+using webm::UnsignedIntParser;
+
+namespace {
+
+// An instance of MasterValueParser that can actually be used in the tests to
+// parse Cluster structures.
+class FakeClusterParser : public MasterValueParser<Cluster> {
+ public:
+ FakeClusterParser()
+ : MasterValueParser(
+ MakeChild<UnsignedIntParser>(Id::kTimecode, &Cluster::timecode),
+ MakeChild<UnsignedIntParser>(Id::kPrevSize, &Cluster::previous_size)
+ .UseAsStartEvent()) {}
+
+ protected:
+ Status OnParseStarted(Callback* callback, Action* action) override {
+ return callback->OnClusterBegin(metadata(Id::kCluster), value(), action);
+ }
+
+ Status OnParseCompleted(Callback* callback) override {
+ return callback->OnClusterEnd(metadata(Id::kCluster), value());
+ }
+};
+
+class MasterValueParserTest
+ : public ElementParserTest<FakeClusterParser, Id::kCluster> {};
+
+TEST_F(MasterValueParserTest, InvalidId) {
+ SetReaderData({
+ 0x00, // Invalid ID.
+ });
+
+ ParseAndExpectResult(Status::kInvalidElementId);
+}
+
+TEST_F(MasterValueParserTest, InvalidSize) {
+ SetReaderData({
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0xFF, // Size = unknown.
+ });
+
+ ParseAndExpectResult(Status::kInvalidElementSize);
+}
+
+TEST_F(MasterValueParserTest, DefaultParse) {
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, Cluster{}, NotNull()))
+ .Times(1);
+
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, Cluster{})).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(MasterValueParserTest, DefaultValues) {
+ SetReaderData({
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x80, // Size = 0.
+
+ 0xAB, // ID = 0xAB (PrevSize).
+ 0x80, // Size = 0.
+ });
+
+ {
+ InSequence dummy;
+
+ Cluster cluster{};
+ cluster.timecode.Set(0, true);
+
+ ElementMetadata child_metadata = {Id::kTimecode, 2, 0, 0};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ child_metadata = {Id::kPrevSize, 2, 0, 2};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .Times(1);
+
+ cluster.previous_size.Set(0, true);
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(MasterValueParserTest, CustomValues) {
+ SetReaderData({
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x82, // Size = 2.
+ 0x04, 0x00, // Body (value = 1024).
+
+ 0xAB, // ID = 0xAB (PrevSize).
+ 0x40, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+ });
+
+ {
+ InSequence dummy;
+
+ Cluster cluster{};
+ cluster.timecode.Set(1024, true);
+
+ ElementMetadata child_metadata = {Id::kTimecode, 2, 2, 0};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ child_metadata = {Id::kPrevSize, 3, 1, 4};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .Times(1);
+
+ cluster.previous_size.Set(1, true);
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(MasterValueParserTest, IncrementalParse) {
+ SetReaderData({
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x40, 0x02, // Size = 2.
+ 0x04, 0x00, // Body (value = 1024).
+
+ 0x40, 0x00, // ID = 0x4000 (Unknown).
+ 0x40, 0x02, // Size = 2.
+ 0x00, 0x00, // Body.
+
+ 0xAB, // ID = 0xAB (PrevSize).
+ 0x40, 0x02, // Size = 2.
+ 0x00, 0x01, // Body (value = 1).
+ });
+
+ {
+ InSequence dummy;
+
+ Cluster cluster{};
+ cluster.timecode.Set(1024, true);
+
+ ElementMetadata child_metadata = {Id::kTimecode, 3, 2, 0};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ child_metadata = {static_cast<Id>(0x4000), 4, 2, 5};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback_,
+ OnUnknownElement(child_metadata, NotNull(), NotNull()))
+ .Times(3);
+
+ child_metadata = {Id::kPrevSize, 3, 2, 11};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .Times(1);
+
+ cluster.previous_size.Set(1, true);
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1);
+ }
+
+ IncrementalParseAndVerify();
+}
+
+TEST_F(MasterValueParserTest, StartEventWithoutPreviousSize) {
+ SetReaderData({
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x80, // Size = 0.
+ });
+
+ {
+ InSequence dummy;
+
+ Cluster cluster{};
+
+ const ElementMetadata child_metadata = {Id::kTimecode, 2, 0, 0};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ cluster.timecode.Set(0, true);
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .Times(1);
+
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(MasterValueParserTest, StartEventWithPreviousSize) {
+ SetReaderData({
+ 0xAB, // ID = 0xAB (PrevSize).
+ 0x80, // Size = 0.
+
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x80, // Size = 0.
+ });
+
+ {
+ InSequence dummy;
+
+ Cluster cluster{};
+
+ ElementMetadata child_metadata = {Id::kPrevSize, 2, 0, 0};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .Times(1);
+
+ child_metadata = {Id::kTimecode, 2, 0, 2};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ cluster.previous_size.Set(0, true);
+ cluster.timecode.Set(0, true);
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(MasterValueParserTest, PartialStartThenRead) {
+ SetReaderData({
+ 0xAB, // ID = 0xAB (PrevSize).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+ });
+
+ {
+ InSequence dummy;
+
+ Cluster cluster{};
+
+ ElementMetadata child_metadata = {Id::kPrevSize, 2, 1, 0};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .WillOnce(Return(Status(Status::kOkPartial)))
+ .WillOnce(DoAll(SetArgPointee<2>(Action::kRead),
+ Return(Status(Status::kOkCompleted))));
+
+ child_metadata = {Id::kTimecode, 2, 1, 3};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ cluster.previous_size.Set(1, true);
+ cluster.timecode.Set(1, true);
+ EXPECT_CALL(callback_, OnClusterEnd(metadata_, cluster)).Times(1);
+ }
+
+ IncrementalParseAndVerify();
+}
+
+TEST_F(MasterValueParserTest, EmptySkip) {
+ const Cluster cluster{};
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .WillOnce(DoAll(SetArgPointee<2>(Action::kSkip),
+ Return(Status(Status::kOkCompleted))));
+
+ EXPECT_CALL(callback_, OnElementBegin(_, _)).Times(0);
+ EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(0);
+
+ ParseAndVerify();
+
+ EXPECT_EQ(cluster, parser_.value());
+}
+
+TEST_F(MasterValueParserTest, Skip) {
+ SetReaderData({
+ 0xAB, // ID = 0xAB (PrevSize).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+ });
+
+ const Cluster cluster{};
+
+ {
+ InSequence dummy;
+
+ const ElementMetadata child_metadata = {Id::kPrevSize, 2, 1, 0};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .WillOnce(DoAll(SetArgPointee<2>(Action::kSkip),
+ Return(Status(Status::kOkCompleted))));
+
+ EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(0);
+ }
+
+ ParseAndVerify();
+
+ EXPECT_EQ(cluster, parser_.value());
+}
+
+TEST_F(MasterValueParserTest, PartialStartThenSkip) {
+ SetReaderData({
+ 0xAB, // ID = 0xAB (PrevSize).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+ });
+
+ const Cluster cluster{};
+
+ {
+ InSequence dummy;
+
+ const ElementMetadata child_metadata = {Id::kPrevSize, 2, 1, 0};
+ EXPECT_CALL(callback_, OnElementBegin(child_metadata, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnClusterBegin(metadata_, cluster, NotNull()))
+ .WillOnce(Return(Status(Status::kOkPartial)))
+ .WillOnce(DoAll(SetArgPointee<2>(Action::kSkip),
+ Return(Status(Status::kOkCompleted))));
+
+ EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(0);
+ }
+
+ IncrementalParseAndVerify();
+
+ EXPECT_EQ(cluster, parser_.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/mastering_metadata_parser_test.cc b/webm_parser/tests/mastering_metadata_parser_test.cc
new file mode 100644
index 0000000..cfbb264
--- /dev/null
+++ b/webm_parser/tests/mastering_metadata_parser_test.cc
@@ -0,0 +1,208 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/mastering_metadata_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::MasteringMetadata;
+using webm::MasteringMetadataParser;
+
+namespace {
+
+class MasteringMetadataParserTest
+ : public ElementParserTest<MasteringMetadataParser,
+ Id::kMasteringMetadata> {};
+
+TEST_F(MasteringMetadataParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const MasteringMetadata mastering_metadata = parser_.value();
+
+ EXPECT_FALSE(mastering_metadata.primary_r_chromaticity_x.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_r_chromaticity_x.value());
+
+ EXPECT_FALSE(mastering_metadata.primary_r_chromaticity_y.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_r_chromaticity_y.value());
+
+ EXPECT_FALSE(mastering_metadata.primary_g_chromaticity_x.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_g_chromaticity_x.value());
+
+ EXPECT_FALSE(mastering_metadata.primary_g_chromaticity_y.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_g_chromaticity_y.value());
+
+ EXPECT_FALSE(mastering_metadata.primary_b_chromaticity_x.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_b_chromaticity_x.value());
+
+ EXPECT_FALSE(mastering_metadata.primary_b_chromaticity_y.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_b_chromaticity_y.value());
+
+ EXPECT_FALSE(mastering_metadata.white_point_chromaticity_x.is_present());
+ EXPECT_EQ(0, mastering_metadata.white_point_chromaticity_x.value());
+
+ EXPECT_FALSE(mastering_metadata.white_point_chromaticity_y.is_present());
+ EXPECT_EQ(0, mastering_metadata.white_point_chromaticity_y.value());
+
+ EXPECT_FALSE(mastering_metadata.luminance_max.is_present());
+ EXPECT_EQ(0, mastering_metadata.luminance_max.value());
+
+ EXPECT_FALSE(mastering_metadata.luminance_min.is_present());
+ EXPECT_EQ(0, mastering_metadata.luminance_min.value());
+}
+
+TEST_F(MasteringMetadataParserTest, DefaultValues) {
+ SetReaderData({
+ 0x55, 0xD1, // ID = 0x55D1 (PrimaryRChromaticityX).
+ 0x80, // Size = 0.
+
+ 0x55, 0xD2, // ID = 0x55D2 (PrimaryRChromaticityY).
+ 0x80, // Size = 0.
+
+ 0x55, 0xD3, // ID = 0x55D3 (PrimaryGChromaticityX).
+ 0x80, // Size = 0.
+
+ 0x55, 0xD4, // ID = 0x55D4 (PrimaryGChromaticityY).
+ 0x80, // Size = 0.
+
+ 0x55, 0xD5, // ID = 0x55D5 (PrimaryBChromaticityX).
+ 0x80, // Size = 0.
+
+ 0x55, 0xD6, // ID = 0x55D6 (PrimaryBChromaticityY).
+ 0x80, // Size = 0.
+
+ 0x55, 0xD7, // ID = 0x55D7 (WhitePointChromaticityX).
+ 0x80, // Size = 0.
+
+ 0x55, 0xD8, // ID = 0x55D8 (WhitePointChromaticityY).
+ 0x80, // Size = 0.
+
+ 0x55, 0xD9, // ID = 0x55D9 (LuminanceMax).
+ 0x80, // Size = 0.
+
+ 0x55, 0xDA, // ID = 0x55DA (LuminanceMin).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const MasteringMetadata mastering_metadata = parser_.value();
+
+ EXPECT_TRUE(mastering_metadata.primary_r_chromaticity_x.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_r_chromaticity_x.value());
+
+ EXPECT_TRUE(mastering_metadata.primary_r_chromaticity_y.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_r_chromaticity_y.value());
+
+ EXPECT_TRUE(mastering_metadata.primary_g_chromaticity_x.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_g_chromaticity_x.value());
+
+ EXPECT_TRUE(mastering_metadata.primary_g_chromaticity_y.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_g_chromaticity_y.value());
+
+ EXPECT_TRUE(mastering_metadata.primary_b_chromaticity_x.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_b_chromaticity_x.value());
+
+ EXPECT_TRUE(mastering_metadata.primary_b_chromaticity_y.is_present());
+ EXPECT_EQ(0, mastering_metadata.primary_b_chromaticity_y.value());
+
+ EXPECT_TRUE(mastering_metadata.white_point_chromaticity_x.is_present());
+ EXPECT_EQ(0, mastering_metadata.white_point_chromaticity_x.value());
+
+ EXPECT_TRUE(mastering_metadata.white_point_chromaticity_y.is_present());
+ EXPECT_EQ(0, mastering_metadata.white_point_chromaticity_y.value());
+
+ EXPECT_TRUE(mastering_metadata.luminance_max.is_present());
+ EXPECT_EQ(0, mastering_metadata.luminance_max.value());
+
+ EXPECT_TRUE(mastering_metadata.luminance_min.is_present());
+ EXPECT_EQ(0, mastering_metadata.luminance_min.value());
+}
+
+TEST_F(MasteringMetadataParserTest, CustomValues) {
+ SetReaderData({
+ 0x55, 0xD1, // ID = 0x55D1 (PrimaryRChromaticityX).
+ 0x84, // Size = 4.
+ 0x3E, 0x00, 0x00, 0x00, // Body (value = 0.125).
+
+ 0x55, 0xD2, // ID = 0x55D2 (PrimaryRChromaticityY).
+ 0x84, // Size = 4.
+ 0x3E, 0x80, 0x00, 0x00, // Body (value = 0.25).
+
+ 0x55, 0xD3, // ID = 0x55D3 (PrimaryGChromaticityX).
+ 0x84, // Size = 4.
+ 0x3E, 0xC0, 0x00, 0x00, // Body (value = 0.375).
+
+ 0x55, 0xD4, // ID = 0x55D4 (PrimaryGChromaticityY).
+ 0x84, // Size = 4.
+ 0x3F, 0x00, 0x00, 0x00, // Body (value = 0.5).
+
+ 0x55, 0xD5, // ID = 0x55D5 (PrimaryBChromaticityX).
+ 0x84, // Size = 4.
+ 0x3F, 0x20, 0x00, 0x00, // Body (value = 0.625).
+
+ 0x55, 0xD6, // ID = 0x55D6 (PrimaryBChromaticityY).
+ 0x84, // Size = 4.
+ 0x3F, 0x40, 0x00, 0x00, // Body (value = 0.75).
+
+ 0x55, 0xD7, // ID = 0x55D7 (WhitePointChromaticityX).
+ 0x84, // Size = 4.
+ 0x3F, 0x60, 0x00, 0x00, // Body (value = 0.875).
+
+ 0x55, 0xD8, // ID = 0x55D8 (WhitePointChromaticityY).
+ 0x84, // Size = 4.
+ 0x3F, 0x80, 0x00, 0x00, // Body (value = 1).
+
+ 0x55, 0xD9, // ID = 0x55D9 (LuminanceMax).
+ 0x84, // Size = 4.
+ 0x40, 0x00, 0x00, 0x00, // Body (value = 2).
+
+ 0x55, 0xDA, // ID = 0x55DA (LuminanceMin).
+ 0x84, // Size = 4.
+ 0x40, 0x40, 0x00, 0x00, // Body (value = 3).
+ });
+
+ ParseAndVerify();
+
+ const MasteringMetadata mastering_metadata = parser_.value();
+
+ EXPECT_TRUE(mastering_metadata.primary_r_chromaticity_x.is_present());
+ EXPECT_EQ(0.125, mastering_metadata.primary_r_chromaticity_x.value());
+
+ EXPECT_TRUE(mastering_metadata.primary_r_chromaticity_y.is_present());
+ EXPECT_EQ(0.25, mastering_metadata.primary_r_chromaticity_y.value());
+
+ EXPECT_TRUE(mastering_metadata.primary_g_chromaticity_x.is_present());
+ EXPECT_EQ(0.375, mastering_metadata.primary_g_chromaticity_x.value());
+
+ EXPECT_TRUE(mastering_metadata.primary_g_chromaticity_y.is_present());
+ EXPECT_EQ(0.5, mastering_metadata.primary_g_chromaticity_y.value());
+
+ EXPECT_TRUE(mastering_metadata.primary_b_chromaticity_x.is_present());
+ EXPECT_EQ(0.625, mastering_metadata.primary_b_chromaticity_x.value());
+
+ EXPECT_TRUE(mastering_metadata.primary_b_chromaticity_y.is_present());
+ EXPECT_EQ(0.75, mastering_metadata.primary_b_chromaticity_y.value());
+
+ EXPECT_TRUE(mastering_metadata.white_point_chromaticity_x.is_present());
+ EXPECT_EQ(0.875, mastering_metadata.white_point_chromaticity_x.value());
+
+ EXPECT_TRUE(mastering_metadata.white_point_chromaticity_y.is_present());
+ EXPECT_EQ(1.0, mastering_metadata.white_point_chromaticity_y.value());
+
+ EXPECT_TRUE(mastering_metadata.luminance_max.is_present());
+ EXPECT_EQ(2.0, mastering_metadata.luminance_max.value());
+
+ EXPECT_TRUE(mastering_metadata.luminance_min.is_present());
+ EXPECT_EQ(3.0, mastering_metadata.luminance_min.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/parser_utils_test.cc b/webm_parser/tests/parser_utils_test.cc
new file mode 100644
index 0000000..69d3679
--- /dev/null
+++ b/webm_parser/tests/parser_utils_test.cc
@@ -0,0 +1,100 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/parser_utils.h"
+
+#include <cstdint>
+#include <limits>
+#include <memory>
+
+#include "gtest/gtest.h"
+
+#include "test_utils/limited_reader.h"
+#include "webm/buffer_reader.h"
+#include "webm/reader.h"
+#include "webm/status.h"
+
+using webm::BufferReader;
+using webm::LimitedReader;
+using webm::Reader;
+using webm::Status;
+
+namespace {
+
+class ParserUtilsTest : public testing::Test {};
+
+TEST_F(ParserUtilsTest, ReadByte) {
+ LimitedReader reader(
+ std::unique_ptr<Reader>(new BufferReader({0x12, 0x34, 0x56, 0x78})));
+
+ Status status;
+ std::uint8_t byte;
+
+ status = ReadByte(&reader, &byte);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(0x12, byte);
+
+ status = ReadByte(&reader, &byte);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(0x34, byte);
+
+ status = ReadByte(&reader, &byte);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(0x56, byte);
+
+ reader.set_total_read_limit(0);
+ status = ReadByte(&reader, &byte);
+ EXPECT_EQ(Status::kWouldBlock, status.code);
+ EXPECT_EQ(0x56, byte);
+
+ reader.set_total_read_limit(std::numeric_limits<std::size_t>::max());
+ status = ReadByte(&reader, &byte);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(0x78, byte);
+
+ status = ReadByte(&reader, &byte);
+ EXPECT_EQ(Status::kEndOfFile, status.code);
+ EXPECT_EQ(0x78, byte);
+}
+
+TEST_F(ParserUtilsTest, AccumulateIntegerBytes) {
+ LimitedReader reader(
+ std::unique_ptr<Reader>(new BufferReader({0x12, 0x34, 0x56, 0x78})));
+
+ std::uint32_t integer = 0;
+ Status status;
+ std::uint64_t num_bytes_actually_read;
+
+ reader.set_total_read_limit(1);
+ status =
+ AccumulateIntegerBytes(4, &reader, &integer, &num_bytes_actually_read);
+ EXPECT_EQ(Status::kWouldBlock, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), num_bytes_actually_read);
+ EXPECT_EQ(static_cast<std::uint32_t>(0x12), integer);
+
+ reader.set_total_read_limit(std::numeric_limits<std::size_t>::max());
+ status =
+ AccumulateIntegerBytes(3, &reader, &integer, &num_bytes_actually_read);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(3), num_bytes_actually_read);
+ EXPECT_EQ(static_cast<std::uint32_t>(0x12345678), integer);
+
+ // Make sure calling with num_bytes_remaining == 0 is a no-op.
+ status =
+ AccumulateIntegerBytes(0, &reader, &integer, &num_bytes_actually_read);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), num_bytes_actually_read);
+ EXPECT_EQ(static_cast<std::uint32_t>(0x12345678), integer);
+
+ status =
+ AccumulateIntegerBytes(4, &reader, &integer, &num_bytes_actually_read);
+ EXPECT_EQ(Status::kEndOfFile, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(0), num_bytes_actually_read);
+ EXPECT_EQ(static_cast<std::uint32_t>(0x12345678), integer);
+}
+
+} // namespace
diff --git a/webm_parser/tests/projection_parser_test.cc b/webm_parser/tests/projection_parser_test.cc
new file mode 100644
index 0000000..747c4f5
--- /dev/null
+++ b/webm_parser/tests/projection_parser_test.cc
@@ -0,0 +1,144 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/projection_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::Projection;
+using webm::ProjectionParser;
+using webm::ProjectionType;
+
+namespace {
+
+class ProjectionParserTest
+ : public ElementParserTest<ProjectionParser, Id::kProjection> {};
+
+TEST_F(ProjectionParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const Projection projection = parser_.value();
+
+ EXPECT_FALSE(projection.type.is_present());
+ EXPECT_EQ(ProjectionType::kRectangular, projection.type.value());
+
+ EXPECT_FALSE(projection.projection_private.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{}, projection.projection_private.value());
+
+ EXPECT_FALSE(projection.pose_yaw.is_present());
+ EXPECT_EQ(0.0, projection.pose_yaw.value());
+
+ EXPECT_FALSE(projection.pose_pitch.is_present());
+ EXPECT_EQ(0.0, projection.pose_pitch.value());
+
+ EXPECT_FALSE(projection.pose_roll.is_present());
+ EXPECT_EQ(0.0, projection.pose_roll.value());
+}
+
+TEST_F(ProjectionParserTest, DefaultValues) {
+ SetReaderData({
+ 0x76, 0x71, // ID = 0x7671 (ProjectionType).
+ 0x80, // Size = 0.
+
+ 0x76, 0x72, // ID = 0x7672 (ProjectionPrivate).
+ 0x80, // Size = 0.
+
+ 0x76, 0x73, // ID = 0x7673 (ProjectionPoseYaw).
+ 0x80, // Size = 0.
+
+ 0x76, 0x74, // ID = 0x7674 (ProjectionPosePitch).
+ 0x80, // Size = 0.
+
+ 0x76, 0x75, // ID = 0x7675 (ProjectionPoseRoll).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const Projection projection = parser_.value();
+
+ EXPECT_TRUE(projection.type.is_present());
+ EXPECT_EQ(ProjectionType::kRectangular, projection.type.value());
+
+ EXPECT_TRUE(projection.projection_private.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{}, projection.projection_private.value());
+
+ EXPECT_TRUE(projection.pose_yaw.is_present());
+ EXPECT_EQ(0.0, projection.pose_yaw.value());
+
+ EXPECT_TRUE(projection.pose_pitch.is_present());
+ EXPECT_EQ(0.0, projection.pose_pitch.value());
+
+ EXPECT_TRUE(projection.pose_roll.is_present());
+ EXPECT_EQ(0.0, projection.pose_roll.value());
+}
+
+TEST_F(ProjectionParserTest, CustomValues) {
+ SetReaderData({
+ 0x76, 0x71, // ID = 0x7671 (ProjectionType).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = equirectangular).
+
+ 0x76, 0x72, // ID = 0x7672 (ProjectionPrivate).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x00, // Body.
+
+ 0x76, 0x73, // ID = 0x7673 (ProjectionPoseYaw).
+ 0x10, 0x00, 0x00, 0x04, // Size = 4.
+ 0x3f, 0x80, 0x00, 0x00, // Body (value = 1.0).
+
+ 0x76, 0x74, // ID = 0x7674 (ProjectionPosePitch).
+ 0x10, 0x00, 0x00, 0x04, // Size = 4.
+ 0x40, 0x00, 0x00, 0x00, // Body (value = 2.0).
+
+ 0x76, 0x75, // ID = 0x7675 (ProjectionPoseRoll).
+ 0x10, 0x00, 0x00, 0x04, // Size = 4.
+ 0x40, 0x80, 0x00, 0x00, // Body (value = 4.0).
+ });
+
+ ParseAndVerify();
+
+ const Projection projection = parser_.value();
+
+ EXPECT_TRUE(projection.type.is_present());
+ EXPECT_EQ(ProjectionType::kEquirectangular, projection.type.value());
+
+ EXPECT_TRUE(projection.projection_private.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{0x00},
+ projection.projection_private.value());
+
+ EXPECT_TRUE(projection.pose_yaw.is_present());
+ EXPECT_EQ(1.0, projection.pose_yaw.value());
+
+ EXPECT_TRUE(projection.pose_pitch.is_present());
+ EXPECT_EQ(2.0, projection.pose_pitch.value());
+
+ EXPECT_TRUE(projection.pose_roll.is_present());
+ EXPECT_EQ(4.0, projection.pose_roll.value());
+}
+
+TEST_F(ProjectionParserTest, MeshProjection) {
+ SetReaderData({
+ 0x76, 0x71, // ID = 0x7671 (ProjectionType).
+ 0x81, // Size = 1.
+ 0x03, // Body (value = mesh).
+ });
+
+ ParseAndVerify();
+
+ const Projection projection = parser_.value();
+
+ EXPECT_TRUE(projection.type.is_present());
+ EXPECT_EQ(ProjectionType::kMesh, projection.type.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/recursive_parser_test.cc b/webm_parser/tests/recursive_parser_test.cc
new file mode 100644
index 0000000..8965a3e
--- /dev/null
+++ b/webm_parser/tests/recursive_parser_test.cc
@@ -0,0 +1,90 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/recursive_parser.h"
+
+#include <cstdint>
+
+#include "gtest/gtest.h"
+
+#include "src/byte_parser.h"
+#include "src/element_parser.h"
+#include "test_utils/element_parser_test.h"
+#include "webm/element.h"
+#include "webm/status.h"
+
+using webm::Callback;
+using webm::ElementMetadata;
+using webm::ElementParser;
+using webm::ElementParserTest;
+using webm::Reader;
+using webm::RecursiveParser;
+using webm::Status;
+using webm::StringParser;
+
+namespace {
+
+class FailParser : public ElementParser {
+ public:
+ explicit FailParser(std::size_t /* max_recursion_depth */) { ADD_FAILURE(); }
+
+ Status Init(const ElementMetadata& /* metadata */,
+ std::uint64_t /* max_size */) override {
+ ADD_FAILURE();
+ return Status(Status::kInvalidElementSize);
+ }
+
+ Status Feed(Callback* /* callback */, Reader* /* reader */,
+ std::uint64_t* num_bytes_read) override {
+ ADD_FAILURE();
+ *num_bytes_read = 0;
+ return Status(Status::kInvalidElementSize);
+ }
+
+ int value() const {
+ ADD_FAILURE();
+ return 0;
+ }
+
+ int* mutable_value() {
+ ADD_FAILURE();
+ return nullptr;
+ }
+};
+
+class StringParserWrapper : public StringParser {
+ public:
+ explicit StringParserWrapper(std::size_t max_recursion_depth) {
+ EXPECT_EQ(max_recursion_depth, 24);
+ }
+};
+
+class RecursiveFailParserTest
+ : public ElementParserTest<RecursiveParser<FailParser>> {};
+
+TEST_F(RecursiveFailParserTest, NoConstruction) {
+ RecursiveParser<FailParser> parser;
+}
+
+class RecursiveStringParserTest
+ : public ElementParserTest<RecursiveParser<StringParserWrapper>> {};
+
+TEST_F(RecursiveStringParserTest, ParsesOkay) {
+ ParseAndVerify();
+ EXPECT_EQ("", parser_.value());
+
+ SetReaderData({0x48, 0x69}); // "Hi".
+ ParseAndVerify();
+ EXPECT_EQ("Hi", parser_.value());
+}
+
+TEST_F(RecursiveStringParserTest, ExceedMaxRecursionDepth) {
+ ResetParser(0);
+ TestInit(0, Status::kExceededRecursionDepthLimit);
+}
+
+} // namespace
diff --git a/webm_parser/tests/seek_head_parser_test.cc b/webm_parser/tests/seek_head_parser_test.cc
new file mode 100644
index 0000000..4fb6ce5
--- /dev/null
+++ b/webm_parser/tests/seek_head_parser_test.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/seek_head_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/buffer_reader.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::SeekHeadParser;
+
+namespace {
+
+class SeekHeadParserTest
+ : public ElementParserTest<SeekHeadParser, Id::kSeekHead> {};
+
+TEST_F(SeekHeadParserTest, DefaultValues) {
+ ParseAndVerify();
+
+ SetReaderData({
+ 0x4D, 0xBB, // ID = 0x4DBB (Seek).
+ 0x80, // Size = 0.
+ });
+ ParseAndVerify();
+}
+
+TEST_F(SeekHeadParserTest, RepeatedValues) {
+ SetReaderData({
+ 0x4D, 0xBB, // ID = 0x4DBB (Seek).
+ 0x84, // Size = 4.
+
+ 0x53, 0xAC, // ID = 0x53AC (SeekPosition).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x4D, 0xBB, // ID = 0x4DBB (Seek).
+ 0x84, // Size = 4.
+
+ 0x53, 0xAC, // ID = 0x53AC (SeekPosition).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/seek_parser_test.cc b/webm_parser/tests/seek_parser_test.cc
new file mode 100644
index 0000000..e829140
--- /dev/null
+++ b/webm_parser/tests/seek_parser_test.cc
@@ -0,0 +1,67 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/seek_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::Seek;
+using webm::SeekParser;
+
+namespace {
+
+class SeekParserTest : public ElementParserTest<SeekParser, Id::kSeek> {};
+
+TEST_F(SeekParserTest, DefaultParse) {
+ EXPECT_CALL(callback_, OnSeek(metadata_, Seek{})).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(SeekParserTest, DefaultValues) {
+ SetReaderData({
+ // A SeekID element with a length of 0 isn't valid, so we don't test it.
+
+ 0x53, 0xAC, // ID = 0x53AC (SeekPosition).
+ 0x80, // Size = 0.
+ });
+
+ Seek seek;
+ seek.position.Set(0, true);
+
+ EXPECT_CALL(callback_, OnSeek(metadata_, seek)).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(SeekParserTest, CustomValues) {
+ SetReaderData({
+ 0x53, 0xAB, // ID = 0x53AB (SeekID).
+ 0x81, // Size = 1.
+ 0x01, // Body.
+
+ 0x53, 0xAC, // ID = 0x53AC (SeekPosition).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ Seek seek;
+ seek.id.Set(static_cast<Id>(0x01), true);
+ seek.position.Set(2, true);
+
+ EXPECT_CALL(callback_, OnSeek(metadata_, seek)).Times(1);
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/segment_parser_test.cc b/webm_parser/tests/segment_parser_test.cc
new file mode 100644
index 0000000..d1a444b
--- /dev/null
+++ b/webm_parser/tests/segment_parser_test.cc
@@ -0,0 +1,369 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/segment_parser.h"
+
+#include <cstdint>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::InSequence;
+using testing::NotNull;
+using testing::Return;
+using testing::SetArgPointee;
+
+using webm::Action;
+using webm::Ancestory;
+using webm::ElementMetadata;
+using webm::ElementParserTest;
+using webm::Id;
+using webm::SegmentParser;
+using webm::Status;
+
+namespace {
+
+class SegmentParserTest
+ : public ElementParserTest<SegmentParser, Id::kSegment> {};
+
+TEST_F(SegmentParserTest, EmptyParse) {
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnSegmentEnd(metadata_)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(SegmentParserTest, DefaultActionIsRead) {
+ {
+ InSequence dummy;
+
+ // This intentionally does not set the action and relies on the parser using
+ // a default action value of kRead.
+ EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull()))
+ .WillOnce(Return(Status(Status::kOkCompleted)));
+ EXPECT_CALL(callback_, OnSegmentEnd(metadata_)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(SegmentParserTest, EmptyParseWithDelayedStart) {
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull()))
+ .WillOnce(Return(Status(Status::kOkPartial)))
+ .WillOnce(DoAll(SetArgPointee<1>(Action::kRead),
+ Return(Status(Status::kOkCompleted))));
+
+ EXPECT_CALL(callback_, OnSegmentEnd(metadata_)).Times(1);
+ }
+
+ IncrementalParseAndVerify();
+}
+
+TEST_F(SegmentParserTest, DefaultValues) {
+ SetReaderData({
+ 0x11, 0x4D, 0x9B, 0x74, // ID = 0x114D9B74 (SeekHead).
+ 0x80, // Size = 0.
+
+ 0x15, 0x49, 0xA9, 0x66, // ID = 0x1549A966 (Info).
+ 0x80, // Size = 0.
+
+ 0x1F, 0x43, 0xB6, 0x75, // ID = 0x1F43B675 (Cluster).
+ 0x80, // Size = 0.
+
+ 0x16, 0x54, 0xAE, 0x6B, // ID = 0x1654AE6B (Tracks).
+ 0x80, // Size = 0.
+
+ 0x1C, 0x53, 0xBB, 0x6B, // ID = 0x1C53BB6B (Cues).
+ 0x80, // Size = 0.
+
+ 0x10, 0x43, 0xA7, 0x70, // ID = 0x1043A770 (Chapters).
+ 0x80, // Size = 0.
+
+ 0x12, 0x54, 0xC3, 0x67, // ID = 0x1254C367 (Tags).
+ 0x80, // Size = 0.
+ });
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull())).Times(1);
+
+ ElementMetadata metadata = {Id::kInfo, 5, 0, 5};
+ EXPECT_CALL(callback_, OnInfo(metadata, _)).Times(1);
+
+ metadata = {Id::kCluster, 5, 0, 10};
+ EXPECT_CALL(callback_, OnClusterBegin(metadata, _, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnClusterEnd(metadata, _)).Times(1);
+
+ EXPECT_CALL(callback_, OnSegmentEnd(metadata_)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(SegmentParserTest, RepeatedValues) {
+ SetReaderData({
+ // Mutliple SeekHead elements.
+ 0x11, 0x4D, 0x9B, 0x74, // ID = 0x114D9B74 (SeekHead).
+ 0x8D, // Size = 13.
+
+ 0x4D, 0xBB, // ID = 0x4DBB (Seek).
+ 0x10, 0x00, 0x00, 0x07, // Size = 7.
+
+ 0x53, 0xAC, // ID = 0x53AC (SeekPosition).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x11, 0x4D, 0x9B, 0x74, // ID = 0x114D9B74 (SeekHead).
+ 0x8D, // Size = 13.
+
+ 0x4D, 0xBB, // ID = 0x4DBB (Seek).
+ 0x10, 0x00, 0x00, 0x07, // Size = 7.
+
+ 0x53, 0xAC, // ID = 0x53AC (SeekPosition).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ // Multiple Info elements.
+ 0x15, 0x49, 0xA9, 0x66, // ID = 0x1549A966 (Info).
+ 0x88, // Size = 8.
+
+ 0x2A, 0xD7, 0xB1, // ID = 0x2AD7B1 (TimecodeScale).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x15, 0x49, 0xA9, 0x66, // ID = 0x1549A966 (Info).
+ 0x88, // Size = 8.
+
+ 0x2A, 0xD7, 0xB1, // ID = 0x2AD7B1 (TimecodeScale).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ // Multiple Cluster elements.
+ 0x1F, 0x43, 0xB6, 0x75, // ID = 0x1F43B675 (Cluster).
+ 0x86, // Size = 6.
+
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x1F, 0x43, 0xB6, 0x75, // ID = 0x1F43B675 (Cluster).
+ 0x86, // Size = 6.
+
+ 0xE7, // ID = 0xE7 (Timecode).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ // Multiple Tracks elements.
+ 0x16, 0x54, 0xAE, 0x6B, // ID = 0x1654AE6B (Tracks).
+ 0x8B, // Size = 11.
+
+ 0xAE, // ID = 0xAE (TrackEntry).
+ 0x10, 0x00, 0x00, 0x06, // Size = 6.
+
+ 0xD7, // ID = 0xD7 (TrackNumber).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x16, 0x54, 0xAE, 0x6B, // ID = 0x1654AE6B (Tracks).
+ 0x8B, // Size = 11.
+
+ 0xAE, // ID = 0xAE (TrackEntry).
+ 0x10, 0x00, 0x00, 0x06, // Size = 6.
+
+ 0xD7, // ID = 0xD7 (TrackNumber).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ // Single Cues element.
+ 0x1C, 0x53, 0xBB, 0x6B, // ID = 0x1C53BB6B (Cues).
+ 0x8B, // Size = 11.
+
+ 0xBB, // ID = 0xBB (CuePoint).
+ 0x10, 0x00, 0x00, 0x06, // Size = 6.
+
+ 0xB3, // ID = 0xB3 (CueTime).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ // Single Chapters element.
+ 0x10, 0x43, 0xA7, 0x70, // ID = 0x1043A770 (Chapters).
+ 0x8D, // Size = 13.
+
+ 0x45, 0xB9, // ID = 0x45B9 (EditionEntry).
+ 0x10, 0x00, 0x00, 0x07, // Size = 7.
+
+ 0x45, 0xBC, // ID = 0x45BC (EditionUID).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ // Multiple Tags elements.
+ 0x12, 0x54, 0xC3, 0x67, // ID = 0x1254C367 (Tags).
+ 0x93, // Size = 19.
+
+ 0x73, 0x73, // ID = 0x7373 (Tag).
+ 0x10, 0x00, 0x00, 0x0D, // Size = 13.
+
+ 0x63, 0xC0, // ID = 0x63C0 (Targets).
+ 0x10, 0x00, 0x00, 0x07, // Size = 7.
+
+ 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x12, 0x54, 0xC3, 0x67, // ID = 0x1254C367 (Tags).
+ 0x93, // Size = 19.
+
+ 0x73, 0x73, // ID = 0x7373 (Tag).
+ 0x10, 0x00, 0x00, 0x0D, // Size = 13.
+
+ 0x63, 0xC0, // ID = 0x63C0 (Targets).
+ 0x10, 0x00, 0x00, 0x07, // Size = 7.
+
+ 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnSeek(_, _)).Times(2);
+
+ EXPECT_CALL(callback_, OnInfo(_, _)).Times(2);
+
+ EXPECT_CALL(callback_, OnClusterBegin(_, _, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(1);
+
+ EXPECT_CALL(callback_, OnClusterBegin(_, _, NotNull())).Times(1);
+ EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(1);
+
+ EXPECT_CALL(callback_, OnTrackEntry(_, _)).Times(2);
+
+ EXPECT_CALL(callback_, OnCuePoint(_, _)).Times(1);
+
+ EXPECT_CALL(callback_, OnEditionEntry(_, _)).Times(1);
+
+ EXPECT_CALL(callback_, OnTag(_, _)).Times(2);
+
+ EXPECT_CALL(callback_, OnSegmentEnd(metadata_)).Times(1);
+ }
+
+ ParseAndVerify();
+}
+
+TEST_F(SegmentParserTest, Skip) {
+ SetReaderData({
+ 0x1F, 0x43, 0xB6, 0x75, // ID = 0x1F43B675 (Cluster).
+ 0x80, // Size = 0.
+ });
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnSegmentBegin(metadata_, NotNull()))
+ .WillOnce(Return(Status(Status::kOkPartial)))
+ .WillOnce(DoAll(SetArgPointee<1>(Action::kSkip),
+ Return(Status(Status::kOkCompleted))));
+
+ EXPECT_CALL(callback_, OnClusterBegin(_, _, _)).Times(0);
+ EXPECT_CALL(callback_, OnClusterEnd(_, _)).Times(0);
+
+ EXPECT_CALL(callback_, OnSegmentEnd(_)).Times(0);
+ }
+
+ IncrementalParseAndVerify();
+}
+
+TEST_F(SegmentParserTest, Seek) {
+ SetReaderData({
+ // We start the reader at the beginning of a FlagInterlaced element
+ // (skipping its ID and size, as they're passed into InitAfterSeek).
+ // FlagInterlaced, StereoMode, and AlphaMode are all part of the Video
+ // element, with the ancestory: Segment -> Tracks -> TrackEntry -> Video.
+ 0x01, // Body (value = 1).
+
+ 0x53, 0xB8, // ID = 0x53B8 (StereoMode).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0x53, 0xC0, // ID = 0x53C0 (AlphaMode).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x03, // Body (value = 3).
+
+ // Single Cues element.
+ 0x1C, 0x53, 0xBB, 0x6B, // ID = 0x1C53BB6B (Cues).
+ 0x8B, // Size = 11.
+
+ 0xBB, // ID = 0xBB (CuePoint).
+ 0x10, 0x00, 0x00, 0x06, // Size = 6.
+
+ 0xB3, // ID = 0xB3 (CueTime).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+ });
+
+ const ElementMetadata flag_interlaced_metadata = {Id::kFlagInterlaced, 0, 1,
+ 0};
+
+ EXPECT_CALL(callback_, OnSegmentBegin(_, _)).Times(0);
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback_, OnElementBegin(flag_interlaced_metadata, NotNull()))
+ .Times(1);
+
+ ElementMetadata metadata = {Id::kStereoMode, 6, 1, 1};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ metadata = {Id::kAlphaMode, 6, 1, 8};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnTrackEntry(_, _)).Times(1);
+
+ metadata = {Id::kCues, 5, 11, 15};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ metadata = {Id::kCuePoint, 5, 6, 20};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ metadata = {Id::kCueTime, 5, 1, 25};
+ EXPECT_CALL(callback_, OnElementBegin(metadata, NotNull())).Times(1);
+
+ EXPECT_CALL(callback_, OnCuePoint(_, _)).Times(1);
+
+ EXPECT_CALL(callback_, OnSegmentEnd(_)).Times(1);
+ }
+
+ Ancestory ancestory;
+ ASSERT_TRUE(Ancestory::ById(flag_interlaced_metadata.id, &ancestory));
+ ancestory = ancestory.next(); // Skip the Segment ancestor.
+
+ parser_.InitAfterSeek(ancestory, flag_interlaced_metadata);
+
+ std::uint64_t num_bytes_read = 0;
+ const Status status = parser_.Feed(&callback_, &reader_, &num_bytes_read);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(reader_.size(), num_bytes_read);
+}
+
+} // namespace
diff --git a/webm_parser/tests/simple_tag_parser_test.cc b/webm_parser/tests/simple_tag_parser_test.cc
new file mode 100644
index 0000000..f67191c
--- /dev/null
+++ b/webm_parser/tests/simple_tag_parser_test.cc
@@ -0,0 +1,205 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/simple_tag_parser.h"
+
+#include <cstdint>
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::SimpleTag;
+using webm::SimpleTagParser;
+using webm::Status;
+
+namespace {
+
+class SimpleTagParserTest
+ : public ElementParserTest<SimpleTagParser, Id::kSimpleTag> {};
+
+TEST_F(SimpleTagParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const SimpleTag simple_tag = parser_.value();
+
+ EXPECT_FALSE(simple_tag.name.is_present());
+ EXPECT_EQ("", simple_tag.name.value());
+
+ EXPECT_FALSE(simple_tag.language.is_present());
+ EXPECT_EQ("und", simple_tag.language.value());
+
+ EXPECT_FALSE(simple_tag.is_default.is_present());
+ EXPECT_EQ(true, simple_tag.is_default.value());
+
+ EXPECT_FALSE(simple_tag.string.is_present());
+ EXPECT_EQ("", simple_tag.string.value());
+
+ EXPECT_FALSE(simple_tag.binary.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{}, simple_tag.binary.value());
+
+ EXPECT_EQ(static_cast<std::size_t>(0), simple_tag.tags.size());
+}
+
+TEST_F(SimpleTagParserTest, DefaultValues) {
+ SetReaderData({
+ 0x45, 0xA3, // ID = 0x45A3 (TagName).
+ 0x80, // Size = 0.
+
+ 0x44, 0x7A, // ID = 0x447A (TagLanguage).
+ 0x80, // Size = 0.
+
+ 0x44, 0x84, // ID = 0x4484 (TagDefault).
+ 0x80, // Size = 0.
+
+ 0x44, 0x87, // ID = 0x4487 (TagString).
+ 0x80, // Size = 0.
+
+ 0x44, 0x85, // ID = 0x4485 (TagBinary).
+ 0x80, // Size = 0.
+
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const SimpleTag simple_tag = parser_.value();
+
+ EXPECT_TRUE(simple_tag.name.is_present());
+ EXPECT_EQ("", simple_tag.name.value());
+
+ EXPECT_TRUE(simple_tag.language.is_present());
+ EXPECT_EQ("und", simple_tag.language.value());
+
+ EXPECT_TRUE(simple_tag.is_default.is_present());
+ EXPECT_EQ(true, simple_tag.is_default.value());
+
+ EXPECT_TRUE(simple_tag.string.is_present());
+ EXPECT_EQ("", simple_tag.string.value());
+
+ EXPECT_TRUE(simple_tag.binary.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{}, simple_tag.binary.value());
+
+ ASSERT_EQ(static_cast<std::size_t>(1), simple_tag.tags.size());
+ EXPECT_TRUE(simple_tag.tags[0].is_present());
+ EXPECT_EQ(SimpleTag{}, simple_tag.tags[0].value());
+}
+
+TEST_F(SimpleTagParserTest, CustomValues) {
+ SetReaderData({
+ 0x45, 0xA3, // ID = 0x45A3 (TagName).
+ 0x81, // Size = 1.
+ 0x61, // Body (value = "a").
+
+ 0x44, 0x7A, // ID = 0x447A (TagLanguage).
+ 0x81, // Size = 1.
+ 0x62, // Body (value = "b").
+
+ 0x44, 0x84, // ID = 0x4484 (TagDefault).
+ 0x81, // Size = 1.
+ 0x00, // Body (value = 0).
+
+ 0x44, 0x87, // ID = 0x4487 (TagString).
+ 0x81, // Size = 1.
+ 0x63, // Body (value = "c").
+
+ 0x44, 0x85, // ID = 0x4485 (TagBinary).
+ 0x81, // Size = 1.
+ 0x01, // Body.
+
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x99, // Size = 25.
+
+ 0x44, 0x87, // ID = 0x4487 (TagString).
+ 0x81, // Size = 1.
+ 0x64, // Body (value = "d").
+
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x8B, // Size = 11.
+
+ 0x44, 0x87, // ID = 0x4487 (TagString).
+ 0x81, // Size = 1.
+ 0x65, // Body (value = "e").
+
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x84, // Size = 4.
+
+ 0x44, 0x87, // ID = 0x4487 (TagString).
+ 0x81, // Size = 1.
+ 0x66, // Body (value = "f").
+
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x84, // Size = 4.
+
+ 0x44, 0x87, // ID = 0x4487 (TagString).
+ 0x81, // Size = 1.
+ 0x67, // Body (value = "g").
+ });
+
+ ParseAndVerify();
+
+ const SimpleTag simple_tag = parser_.value();
+
+ EXPECT_TRUE(simple_tag.name.is_present());
+ EXPECT_EQ("a", simple_tag.name.value());
+
+ EXPECT_TRUE(simple_tag.language.is_present());
+ EXPECT_EQ("b", simple_tag.language.value());
+
+ EXPECT_TRUE(simple_tag.is_default.is_present());
+ EXPECT_EQ(false, simple_tag.is_default.value());
+
+ EXPECT_TRUE(simple_tag.string.is_present());
+ EXPECT_EQ("c", simple_tag.string.value());
+
+ EXPECT_TRUE(simple_tag.binary.is_present());
+ EXPECT_EQ(std::vector<std::uint8_t>{0x01}, simple_tag.binary.value());
+
+ SimpleTag expected;
+ expected.string.Set("d", true);
+
+ SimpleTag temp{};
+
+ temp.string.Set("e", true);
+ expected.tags.emplace_back(temp, true);
+
+ temp.string.Set("f", true);
+ expected.tags[0].mutable_value()->tags.emplace_back(temp, true);
+
+ temp.string.Set("g", true);
+ expected.tags.emplace_back(temp, true);
+
+ ASSERT_EQ(static_cast<std::size_t>(1), simple_tag.tags.size());
+ EXPECT_TRUE(simple_tag.tags[0].is_present());
+ EXPECT_EQ(expected, simple_tag.tags[0].value());
+}
+
+TEST_F(SimpleTagParserTest, ExceedMaxRecursionDepth) {
+ ResetParser(1);
+
+ SetReaderData({
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x80, // Size = 0.
+ });
+ ParseAndVerify();
+
+ SetReaderData({
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x83, // Size = 3.
+
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x80, // Size = 0.
+ });
+ ParseAndExpectResult(Status::kExceededRecursionDepthLimit);
+}
+
+} // namespace
diff --git a/webm_parser/tests/size_parser_test.cc b/webm_parser/tests/size_parser_test.cc
new file mode 100644
index 0000000..2cb7f53
--- /dev/null
+++ b/webm_parser/tests/size_parser_test.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/size_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/parser_test.h"
+#include "webm/status.h"
+
+using webm::ParserTest;
+using webm::SizeParser;
+using webm::Status;
+
+namespace {
+
+class SizeParserTest : public ParserTest<SizeParser> {};
+
+TEST_F(SizeParserTest, InvalidSize) {
+ SetReaderData({0x00});
+ ParseAndExpectResult(Status::kInvalidElementSize);
+}
+
+TEST_F(SizeParserTest, EarlyEndOfFile) {
+ SetReaderData({0x01});
+ ParseAndExpectResult(Status::kEndOfFile);
+}
+
+TEST_F(SizeParserTest, ValidSize) {
+ SetReaderData({0x80});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0), parser_.size());
+
+ ResetParser();
+ SetReaderData({0x01, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0x123456789ABCDE), parser_.size());
+}
+
+TEST_F(SizeParserTest, UnknownSize) {
+ SetReaderData({0xFF});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0xFFFFFFFFFFFFFFFF), parser_.size());
+
+ ResetParser();
+ SetReaderData({0x1F, 0xFF, 0xFF, 0xFF});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0xFFFFFFFFFFFFFFFF), parser_.size());
+}
+
+TEST_F(SizeParserTest, IncrementalParse) {
+ SetReaderData({0x11, 0x23, 0x45, 0x67});
+ IncrementalParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0x01234567), parser_.size());
+}
+
+} // namespace
diff --git a/webm_parser/tests/skip_parser_test.cc b/webm_parser/tests/skip_parser_test.cc
new file mode 100644
index 0000000..8c728be
--- /dev/null
+++ b/webm_parser/tests/skip_parser_test.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/skip_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/element.h"
+#include "webm/status.h"
+
+using webm::ElementParserTest;
+using webm::kUnknownElementSize;
+using webm::SkipParser;
+using webm::Status;
+
+namespace {
+
+class SkipParserTest : public ElementParserTest<SkipParser> {};
+
+TEST_F(SkipParserTest, InvalidSize) {
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(SkipParserTest, Skip) {
+ ParseAndVerify();
+
+ SetReaderData({0x00, 0x01, 0x02, 0x04});
+ ParseAndVerify();
+}
+
+TEST_F(SkipParserTest, IncrementalSkip) {
+ SetReaderData({0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10});
+
+ IncrementalParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/slices_parser_test.cc b/webm_parser/tests/slices_parser_test.cc
new file mode 100644
index 0000000..44abba6
--- /dev/null
+++ b/webm_parser/tests/slices_parser_test.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/slices_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::Slices;
+using webm::SlicesParser;
+using webm::TimeSlice;
+
+namespace {
+
+class SlicesParserTest : public ElementParserTest<SlicesParser, Id::kSlices> {};
+
+TEST_F(SlicesParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const Slices slices = parser_.value();
+
+ EXPECT_EQ(static_cast<std::size_t>(0), slices.slices.size());
+}
+
+TEST_F(SlicesParserTest, DefaultValues) {
+ SetReaderData({
+ 0xE8, // ID = 0xE8 (TimeSlice).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const Slices slices = parser_.value();
+
+ ASSERT_EQ(static_cast<std::size_t>(1), slices.slices.size());
+ EXPECT_TRUE(slices.slices[0].is_present());
+ EXPECT_EQ(TimeSlice{}, slices.slices[0].value());
+}
+
+TEST_F(SlicesParserTest, CustomValues) {
+ SetReaderData({
+ 0xE8, // ID = 0xE8 (TimeSlice).
+ 0x83, // Size = 3.
+
+ 0xCC, // ID = 0xCC (LaceNumber).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xE8, // ID = 0xE8 (TimeSlice).
+ 0x83, // Size = 3.
+
+ 0xCC, // ID = 0xCC (LaceNumber).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ ParseAndVerify();
+
+ const Slices slices = parser_.value();
+
+ TimeSlice expected;
+
+ ASSERT_EQ(static_cast<std::size_t>(2), slices.slices.size());
+ expected.lace_number.Set(1, true);
+ EXPECT_TRUE(slices.slices[0].is_present());
+ EXPECT_EQ(expected, slices.slices[0].value());
+ expected.lace_number.Set(2, true);
+ EXPECT_TRUE(slices.slices[1].is_present());
+ EXPECT_EQ(expected, slices.slices[1].value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/tag_parser_test.cc b/webm_parser/tests/tag_parser_test.cc
new file mode 100644
index 0000000..579c4b8
--- /dev/null
+++ b/webm_parser/tests/tag_parser_test.cc
@@ -0,0 +1,92 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/tag_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::SimpleTag;
+using webm::Tag;
+using webm::TagParser;
+using webm::Targets;
+
+namespace {
+
+class TagParserTest : public ElementParserTest<TagParser, Id::kTag> {};
+
+TEST_F(TagParserTest, DefaultParse) {
+ EXPECT_CALL(callback_, OnTag(metadata_, Tag{})).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(TagParserTest, DefaultValues) {
+ SetReaderData({
+ 0x63, 0xC0, // ID = 0x63C0 (Targets).
+ 0x80, // Size = 0.
+
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x80, // Size = 0.
+ });
+
+ Tag tag;
+ tag.targets.Set({}, true);
+ tag.tags.emplace_back();
+ tag.tags[0].Set({}, true);
+
+ EXPECT_CALL(callback_, OnTag(metadata_, tag)).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(TagParserTest, CustomValues) {
+ SetReaderData({
+ 0x63, 0xC0, // ID = 0x63C0 (Targets).
+ 0x84, // Size = 4.
+
+ 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue).
+ 0x81, // Size = 1.
+ 0x00, // Body (value = 0).
+
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x84, // Size = 4.
+
+ 0x45, 0xA3, // ID = 0x45A3 (TagName).
+ 0x81, // Size = 1.
+ 0x61, // Body (value = "a").
+
+ 0x67, 0xC8, // ID = 0x67C8 (SimpleTag).
+ 0x84, // Size = 4.
+
+ 0x44, 0x7A, // ID = 0x447A (TagLanguage).
+ 0x81, // Size = 1.
+ 0x62, // Body (value = "b").
+ });
+
+ Tag tag;
+ Targets targets;
+ targets.type_value.Set(0, true);
+ tag.targets.Set(targets, true);
+ SimpleTag simple_tag;
+ simple_tag.name.Set("a", true);
+ tag.tags.emplace_back(simple_tag, true);
+ simple_tag = {};
+ simple_tag.language.Set("b", true);
+ tag.tags.emplace_back(simple_tag, true);
+
+ EXPECT_CALL(callback_, OnTag(metadata_, tag)).Times(1);
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/tags_parser_test.cc b/webm_parser/tests/tags_parser_test.cc
new file mode 100644
index 0000000..8b45f77
--- /dev/null
+++ b/webm_parser/tests/tags_parser_test.cc
@@ -0,0 +1,61 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/tags_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/buffer_reader.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::TagsParser;
+
+namespace {
+
+class TagsParserTest : public ElementParserTest<TagsParser, Id::kTags> {};
+
+// TODO(mjbshaw): validate results via Callback.
+
+TEST_F(TagsParserTest, DefaultValues) {
+ ParseAndVerify();
+
+ SetReaderData({
+ 0x73, 0x73, // ID = 0x7373 (Tag).
+ 0x80, // Size = 0.
+ });
+ ParseAndVerify();
+}
+
+TEST_F(TagsParserTest, RepeatedValues) {
+ SetReaderData({
+ 0x73, 0x73, // ID = 0x7373 (Tag).
+ 0x87, // Size = 7.
+
+ 0x63, 0xC0, // ID = 0x63C0 (Targets).
+ 0x84, // Size = 4.
+
+ 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x73, 0x73, // ID = 0x7373 (Tag).
+ 0x87, // Size = 7.
+
+ 0x63, 0xC0, // ID = 0x63C0 (Targets).
+ 0x84, // Size = 4.
+
+ 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/targets_parser_test.cc b/webm_parser/tests/targets_parser_test.cc
new file mode 100644
index 0000000..d424ecf
--- /dev/null
+++ b/webm_parser/tests/targets_parser_test.cc
@@ -0,0 +1,102 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/targets_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::Targets;
+using webm::TargetsParser;
+
+namespace {
+
+class TargetsParserTest
+ : public ElementParserTest<TargetsParser, Id::kTargets> {};
+
+TEST_F(TargetsParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const Targets targets = parser_.value();
+
+ EXPECT_FALSE(targets.type_value.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(50), targets.type_value.value());
+
+ EXPECT_FALSE(targets.type.is_present());
+ EXPECT_EQ("", targets.type.value());
+
+ EXPECT_EQ(static_cast<std::size_t>(0), targets.track_uids.size());
+}
+
+TEST_F(TargetsParserTest, DefaultValues) {
+ SetReaderData({
+ 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue).
+ 0x80, // Size = 0.
+
+ 0x63, 0xCA, // ID = 0x63CA (TargetType).
+ 0x80, // Size = 0.
+
+ 0x63, 0xC5, // ID = 0x63C5 (TagTrackUID).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const Targets targets = parser_.value();
+
+ EXPECT_TRUE(targets.type_value.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(50), targets.type_value.value());
+
+ EXPECT_TRUE(targets.type.is_present());
+ EXPECT_EQ("", targets.type.value());
+
+ ASSERT_EQ(static_cast<std::size_t>(1), targets.track_uids.size());
+ EXPECT_TRUE(targets.track_uids[0].is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), targets.track_uids[0].value());
+}
+
+TEST_F(TargetsParserTest, CustomValues) {
+ SetReaderData({
+ 0x68, 0xCA, // ID = 0x68CA (TargetTypeValue).
+ 0x81, // Size = 1.
+ 0x00, // Body (value = 0).
+
+ 0x63, 0xCA, // ID = 0x63CA (TargetType).
+ 0x82, // Size = 2.
+ 0x48, 0x69, // Body (value = "Hi").
+
+ 0x63, 0xC5, // ID = 0x63C5 (TagTrackUID).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x63, 0xC5, // ID = 0x63C5 (TagTrackUID).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ ParseAndVerify();
+
+ const Targets targets = parser_.value();
+
+ EXPECT_TRUE(targets.type_value.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), targets.type_value.value());
+
+ EXPECT_TRUE(targets.type.is_present());
+ EXPECT_EQ("Hi", targets.type.value());
+
+ ASSERT_EQ(static_cast<std::size_t>(2), targets.track_uids.size());
+ EXPECT_TRUE(targets.track_uids[0].is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), targets.track_uids[0].value());
+ EXPECT_TRUE(targets.track_uids[1].is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), targets.track_uids[1].value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/time_slice_parser_test.cc b/webm_parser/tests/time_slice_parser_test.cc
new file mode 100644
index 0000000..e9bd145
--- /dev/null
+++ b/webm_parser/tests/time_slice_parser_test.cc
@@ -0,0 +1,63 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/time_slice_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::TimeSlice;
+using webm::TimeSliceParser;
+
+namespace {
+
+class TimeSliceParserTest
+ : public ElementParserTest<TimeSliceParser, Id::kTimeSlice> {};
+
+TEST_F(TimeSliceParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const TimeSlice time_slice = parser_.value();
+
+ EXPECT_FALSE(time_slice.lace_number.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), time_slice.lace_number.value());
+}
+
+TEST_F(TimeSliceParserTest, DefaultValues) {
+ SetReaderData({
+ 0xCC, // ID = 0xCC (LaceNumber).
+ 0x80, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const TimeSlice time_slice = parser_.value();
+
+ EXPECT_TRUE(time_slice.lace_number.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), time_slice.lace_number.value());
+}
+
+TEST_F(TimeSliceParserTest, CustomValues) {
+ SetReaderData({
+ 0xCC, // ID = 0xCC (LaceNumber).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+ });
+
+ ParseAndVerify();
+
+ const TimeSlice time_slice = parser_.value();
+
+ EXPECT_TRUE(time_slice.lace_number.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), time_slice.lace_number.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/track_entry_parser_test.cc b/webm_parser/tests/track_entry_parser_test.cc
new file mode 100644
index 0000000..70d06aa
--- /dev/null
+++ b/webm_parser/tests/track_entry_parser_test.cc
@@ -0,0 +1,245 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/track_entry_parser.h"
+
+#include <cstdint>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::Audio;
+using webm::ContentEncoding;
+using webm::ContentEncodings;
+using webm::DisplayUnit;
+using webm::ElementParserTest;
+using webm::Id;
+using webm::TrackEntry;
+using webm::TrackEntryParser;
+using webm::TrackType;
+using webm::Video;
+
+namespace {
+
+class TrackEntryParserTest
+ : public ElementParserTest<TrackEntryParser, Id::kTrackEntry> {};
+
+TEST_F(TrackEntryParserTest, DefaultParse) {
+ EXPECT_CALL(callback_, OnTrackEntry(metadata_, TrackEntry{})).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(TrackEntryParserTest, DefaultValues) {
+ SetReaderData({
+ 0xD7, // ID = 0xD7 (TrackNumber).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x73, 0xC5, // ID = 0x73C5 (TrackUID).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x83, // ID = 0x83 (TrackType).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0xB9, // ID = 0xB9 (FlagEnabled).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x88, // ID = 0x88 (FlagDefault).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x55, 0xAA, // ID = 0x55AA (FlagForced).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x9C, // ID = 0x9C (FlagLacing).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x23, 0xE3, 0x83, // ID = 0x23E383 (DefaultDuration).
+ 0x80, // Size = 0.
+
+ 0x53, 0x6E, // ID = 0x536E (Name).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x22, 0xB5, 0x9C, // ID = 0x22B59C (Language).
+ 0x80, // Size = 0.
+
+ 0x86, // ID = 0x86 (CodecID).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x63, 0xA2, // ID = 0x63A2 (CodecPrivate).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x25, 0x86, 0x88, // ID = 0x258688 (CodecName).
+ 0x80, // Size = 0.
+
+ 0x56, 0xAA, // ID = 0x56AA (CodecDelay).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x56, 0xBB, // ID = 0x56BB (SeekPreRoll).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0xE0, // ID = 0xE0 (Video).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0xE1, // ID = 0xE1 (Audio).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x6D, 0x80, // ID = 0x6D80 (ContentEncodings).
+ 0x20, 0x00, 0x00, // Size = 0.
+ });
+
+ TrackEntry track_entry;
+ track_entry.track_number.Set(0, true);
+ track_entry.track_uid.Set(0, true);
+ track_entry.track_type.Set(TrackType{}, true);
+ track_entry.is_enabled.Set(true, true);
+ track_entry.is_default.Set(true, true);
+ track_entry.is_forced.Set(false, true);
+ track_entry.uses_lacing.Set(true, true);
+ track_entry.default_duration.Set(0, true);
+ track_entry.name.Set("", true);
+ track_entry.language.Set("eng", true);
+ track_entry.codec_id.Set("", true);
+ track_entry.codec_private.Set(std::vector<std::uint8_t>{}, true);
+ track_entry.codec_name.Set("", true);
+ track_entry.codec_delay.Set(0, true);
+ track_entry.seek_pre_roll.Set(0, true);
+ track_entry.video.Set(Video{}, true);
+ track_entry.audio.Set(Audio{}, true);
+ track_entry.content_encodings.Set(ContentEncodings{}, true);
+
+ EXPECT_CALL(callback_, OnTrackEntry(metadata_, track_entry)).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(TrackEntryParserTest, CustomValues) {
+ SetReaderData({
+ 0xD7, // ID = 0xD7 (TrackNumber).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x73, 0xC5, // ID = 0x73C5 (TrackUID).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0x83, // ID = 0x83 (TrackType).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x03, // Body (value = complex).
+
+ 0xB9, // ID = 0xB9 (FlagEnabled).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x00, // Body (value = 0).
+
+ 0x88, // ID = 0x88 (FlagDefault).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x00, // Body (value = 0).
+
+ 0x55, 0xAA, // ID = 0x55AA (FlagForced).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x9C, // ID = 0x9C (FlagLacing).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x00, // Body (value = 0).
+
+ 0x23, 0xE3, 0x83, // ID = 0x23E383 (DefaultDuration).
+ 0x81, // Size = 1.
+ 0x04, // Body (value = 4).
+
+ 0x53, 0x6E, // ID = 0x536E (Name).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x41, // Body (value = "A").
+
+ 0x22, 0xB5, 0x9C, // ID = 0x22B59C (Language).
+ 0x81, // Size = 1.
+ 0x42, // Body (value = "B").
+
+ 0x86, // ID = 0x86 (CodecID).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x43, // Body (value = "C").
+
+ 0x63, 0xA2, // ID = 0x63A2 (CodecPrivate).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x00, // Body.
+
+ 0x25, 0x86, 0x88, // ID = 0x258688 (CodecName).
+ 0x81, // Size = 1.
+ 0x44, // Body (value = "D").
+
+ 0x56, 0xAA, // ID = 0x56AA (CodecDelay).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x05, // Body (value = 5).
+
+ 0x56, 0xBB, // ID = 0x56BB (SeekPreRoll).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x06, // Body (value = 6).
+
+ 0xE0, // ID = 0xE0 (Video).
+ 0x20, 0x00, 0x06, // Size = 6.
+
+ 0x54, 0xB2, // ID = 0x54B2 (DisplayUnit).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x03, // Body (value = display aspect ratio).
+
+ 0xE1, // ID = 0xE1 (Audio).
+ 0x20, 0x00, 0x05, // Size = 5.
+
+ 0x9F, // ID = 0x9F (Channels).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x08, // Body (value = 8).
+
+ 0x6D, 0x80, // ID = 0x6D80 (ContentEncodings).
+ 0x20, 0x00, 0x0B, // Size = 11.
+
+ 0x62, 0x40, // ID = 0x6240 (ContentEncoding).
+ 0x20, 0x00, 0x06, // Size = 6.
+
+ 0x50, 0x31, // ID = 0x5031 (ContentEncodingOrder).
+ 0x20, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+ });
+
+ TrackEntry track_entry;
+ track_entry.track_number.Set(1, true);
+ track_entry.track_uid.Set(2, true);
+ track_entry.track_type.Set(TrackType::kComplex, true);
+ track_entry.is_enabled.Set(false, true);
+ track_entry.is_default.Set(false, true);
+ track_entry.is_forced.Set(true, true);
+ track_entry.uses_lacing.Set(false, true);
+ track_entry.default_duration.Set(4, true);
+ track_entry.name.Set("A", true);
+ track_entry.language.Set("B", true);
+ track_entry.codec_id.Set("C", true);
+ track_entry.codec_private.Set(std::vector<std::uint8_t>{0x00}, true);
+ track_entry.codec_name.Set("D", true);
+ track_entry.codec_delay.Set(5, true);
+ track_entry.seek_pre_roll.Set(6, true);
+
+ Video expected_video;
+ expected_video.display_unit.Set(DisplayUnit::kDisplayAspectRatio, true);
+ track_entry.video.Set(expected_video, true);
+
+ Audio expected_audio;
+ expected_audio.channels.Set(8, true);
+ track_entry.audio.Set(expected_audio, true);
+
+ ContentEncoding expected_encoding;
+ expected_encoding.order.Set(1, true);
+ ContentEncodings expected_encodings;
+ expected_encodings.encodings.emplace_back(expected_encoding, true);
+ track_entry.content_encodings.Set(expected_encodings, true);
+
+ EXPECT_CALL(callback_, OnTrackEntry(metadata_, track_entry)).Times(1);
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/tracks_parser_test.cc b/webm_parser/tests/tracks_parser_test.cc
new file mode 100644
index 0000000..1d83af0
--- /dev/null
+++ b/webm_parser/tests/tracks_parser_test.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/tracks_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/buffer_reader.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::TracksParser;
+
+namespace {
+
+class TracksParserTest : public ElementParserTest<TracksParser, Id::kTracks> {};
+
+// TODO(mjbshaw): validate results via Callback.
+
+TEST_F(TracksParserTest, DefaultValues) {
+ ParseAndVerify();
+
+ SetReaderData({
+ 0xAE, // ID = 0xAE (TrackEntry).
+ 0x80, // Size = 0.
+ });
+ ParseAndVerify();
+}
+
+TEST_F(TracksParserTest, RepeatedValues) {
+ SetReaderData({
+ 0xAE, // ID = 0xAE (TrackEntry).
+ 0x83, // Size = 3.
+
+ 0xD7, // ID = 0xD7 (TrackNumber).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xAE, // ID = 0xAE (TrackEntry).
+ 0x83, // Size = 3.
+
+ 0xD7, // ID = 0xD7 (TrackNumber).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ ParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/unknown_parser_test.cc b/webm_parser/tests/unknown_parser_test.cc
new file mode 100644
index 0000000..c10b64d
--- /dev/null
+++ b/webm_parser/tests/unknown_parser_test.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/unknown_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/element.h"
+#include "webm/status.h"
+
+using testing::NotNull;
+
+using webm::ElementParserTest;
+using webm::kUnknownElementSize;
+using webm::Status;
+using webm::UnknownParser;
+
+namespace {
+
+class UnknownParserTest : public ElementParserTest<UnknownParser> {};
+
+TEST_F(UnknownParserTest, InvalidSize) {
+ TestInit(kUnknownElementSize, Status::kIndefiniteUnknownElement);
+}
+
+TEST_F(UnknownParserTest, Empty) {
+ EXPECT_CALL(callback_, OnUnknownElement(metadata_, NotNull(), NotNull()))
+ .Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(UnknownParserTest, Valid) {
+ SetReaderData({0x00, 0x01, 0x02, 0x04});
+
+ EXPECT_CALL(callback_, OnUnknownElement(metadata_, NotNull(), NotNull()))
+ .Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(UnknownParserTest, IncrementalSkip) {
+ SetReaderData({0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10});
+
+ EXPECT_CALL(callback_, OnUnknownElement(metadata_, NotNull(), NotNull()))
+ .Times(8);
+
+ IncrementalParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/var_int_parser_test.cc b/webm_parser/tests/var_int_parser_test.cc
new file mode 100644
index 0000000..50e5915
--- /dev/null
+++ b/webm_parser/tests/var_int_parser_test.cc
@@ -0,0 +1,53 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/var_int_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/parser_test.h"
+#include "webm/status.h"
+
+using webm::ParserTest;
+using webm::Status;
+using webm::VarIntParser;
+
+namespace {
+
+class VarIntParserTest : public ParserTest<VarIntParser> {};
+
+TEST_F(VarIntParserTest, NoMarkerBit) {
+ SetReaderData({0x00});
+ ParseAndExpectResult(Status::kInvalidElementValue);
+}
+
+TEST_F(VarIntParserTest, EarlyEndOfFile) {
+ SetReaderData({0x01});
+ ParseAndExpectResult(Status::kEndOfFile);
+}
+
+TEST_F(VarIntParserTest, ValidValue) {
+ SetReaderData({0x80});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0), parser_.value());
+ EXPECT_EQ(1, parser_.encoded_length());
+
+ ResetParser();
+ SetReaderData({0x01, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE});
+ ParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0x123456789ABCDE), parser_.value());
+ EXPECT_EQ(8, parser_.encoded_length());
+}
+
+TEST_F(VarIntParserTest, IncrementalParse) {
+ SetReaderData({0x11, 0x23, 0x45, 0x67});
+ IncrementalParseAndVerify();
+ EXPECT_EQ(static_cast<std::uint64_t>(0x01234567), parser_.value());
+ EXPECT_EQ(4, parser_.encoded_length());
+}
+
+} // namespace
diff --git a/webm_parser/tests/video_parser_test.cc b/webm_parser/tests/video_parser_test.cc
new file mode 100644
index 0000000..96c7e49
--- /dev/null
+++ b/webm_parser/tests/video_parser_test.cc
@@ -0,0 +1,446 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/video_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+
+using webm::AspectRatioType;
+using webm::Colour;
+using webm::DisplayUnit;
+using webm::ElementParserTest;
+using webm::FlagInterlaced;
+using webm::Id;
+using webm::Projection;
+using webm::ProjectionType;
+using webm::StereoMode;
+using webm::Video;
+using webm::VideoParser;
+
+namespace {
+
+class VideoParserTest : public ElementParserTest<VideoParser, Id::kVideo> {};
+
+TEST_F(VideoParserTest, DefaultParse) {
+ ParseAndVerify();
+
+ const Video video = parser_.value();
+
+ EXPECT_FALSE(video.interlaced.is_present());
+ EXPECT_EQ(FlagInterlaced::kUnspecified, video.interlaced.value());
+
+ EXPECT_FALSE(video.stereo_mode.is_present());
+ EXPECT_EQ(StereoMode::kMono, video.stereo_mode.value());
+
+ EXPECT_FALSE(video.alpha_mode.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.alpha_mode.value());
+
+ EXPECT_FALSE(video.pixel_width.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_width.value());
+
+ EXPECT_FALSE(video.pixel_height.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_height.value());
+
+ EXPECT_FALSE(video.pixel_crop_bottom.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_bottom.value());
+
+ EXPECT_FALSE(video.pixel_crop_top.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_top.value());
+
+ EXPECT_FALSE(video.pixel_crop_left.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_left.value());
+
+ EXPECT_FALSE(video.pixel_crop_right.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_right.value());
+
+ EXPECT_FALSE(video.display_width.is_present());
+ EXPECT_EQ(video.pixel_width.value(), video.display_width.value());
+
+ EXPECT_FALSE(video.display_height.is_present());
+ EXPECT_EQ(video.pixel_height.value(), video.display_height.value());
+
+ EXPECT_FALSE(video.display_unit.is_present());
+ EXPECT_EQ(DisplayUnit::kPixels, video.display_unit.value());
+
+ EXPECT_FALSE(video.aspect_ratio_type.is_present());
+ EXPECT_EQ(AspectRatioType::kFreeResizing, video.aspect_ratio_type.value());
+
+ EXPECT_FALSE(video.frame_rate.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.frame_rate.value());
+
+ EXPECT_FALSE(video.colour.is_present());
+ EXPECT_EQ(Colour{}, video.colour.value());
+
+ EXPECT_FALSE(video.projection.is_present());
+ EXPECT_EQ(Projection{}, video.projection.value());
+}
+
+TEST_F(VideoParserTest, DefaultValues) {
+ SetReaderData({
+ 0x9A, // ID = 0x9A (FlagInterlaced).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x53, 0xB8, // ID = 0x53B8 (StereoMode).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x53, 0xC0, // ID = 0x53C0 (AlphaMode).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0xB0, // ID = 0xB0 (PixelWidth).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0xBA, // ID = 0xBA (PixelHeight).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x54, 0xAA, // ID = 0x54AA (PixelCropBottom).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x54, 0xBB, // ID = 0x54BB (PixelCropTop).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x54, 0xCC, // ID = 0x54CC (PixelCropLeft).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x54, 0xDD, // ID = 0x54DD (PixelCropRight).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x54, 0xB0, // ID = 0x54B0 (DisplayWidth).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x54, 0xBA, // ID = 0x54BA (DisplayHeight).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x54, 0xB2, // ID = 0x54B2 (DisplayUnit).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x54, 0xB3, // ID = 0x54B3 (AspectRatioType).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x23, 0x83, 0xE3, // ID = 0x2383E3 (FrameRate).
+ 0x80, // Size = 0.
+
+ 0x55, 0xB0, // ID = 0x55B0 (Colour).
+ 0x20, 0x00, 0x00, // Size = 0.
+
+ 0x76, 0x70, // ID = 0x7670 (Projection).
+ 0x20, 0x00, 0x00, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const Video video = parser_.value();
+
+ EXPECT_TRUE(video.interlaced.is_present());
+ EXPECT_EQ(FlagInterlaced::kUnspecified, video.interlaced.value());
+
+ EXPECT_TRUE(video.stereo_mode.is_present());
+ EXPECT_EQ(StereoMode::kMono, video.stereo_mode.value());
+
+ EXPECT_TRUE(video.alpha_mode.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.alpha_mode.value());
+
+ EXPECT_TRUE(video.pixel_width.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_width.value());
+
+ EXPECT_TRUE(video.pixel_height.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_height.value());
+
+ EXPECT_TRUE(video.pixel_crop_bottom.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_bottom.value());
+
+ EXPECT_TRUE(video.pixel_crop_top.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_top.value());
+
+ EXPECT_TRUE(video.pixel_crop_left.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_left.value());
+
+ EXPECT_TRUE(video.pixel_crop_right.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_right.value());
+
+ EXPECT_TRUE(video.display_width.is_present());
+ EXPECT_EQ(video.pixel_width.value(), video.display_width.value());
+
+ EXPECT_TRUE(video.display_height.is_present());
+ EXPECT_EQ(video.pixel_height.value(), video.display_height.value());
+
+ EXPECT_TRUE(video.display_unit.is_present());
+ EXPECT_EQ(DisplayUnit::kPixels, video.display_unit.value());
+
+ EXPECT_TRUE(video.aspect_ratio_type.is_present());
+ EXPECT_EQ(AspectRatioType::kFreeResizing, video.aspect_ratio_type.value());
+
+ EXPECT_TRUE(video.frame_rate.is_present());
+ EXPECT_EQ(0, video.frame_rate.value());
+
+ EXPECT_TRUE(video.colour.is_present());
+ EXPECT_EQ(Colour{}, video.colour.value());
+
+ EXPECT_TRUE(video.projection.is_present());
+ EXPECT_EQ(Projection{}, video.projection.value());
+}
+
+TEST_F(VideoParserTest, CustomValues) {
+ SetReaderData({
+ 0x9A, // ID = 0x9A (FlagInterlaced).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = progressive).
+
+ 0x53, 0xB8, // ID = 0x53B8 (StereoMode).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = top-bottom (right eye first)).
+
+ 0x53, 0xC0, // ID = 0x53C0 (AlphaMode).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x03, // Body (value = 3).
+
+ 0xB0, // ID = 0xB0 (PixelWidth).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x04, // Body (value = 4).
+
+ 0xBA, // ID = 0xBA (PixelHeight).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x05, // Body (value = 5).
+
+ 0x54, 0xAA, // ID = 0x54AA (PixelCropBottom).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x06, // Body (value = 6).
+
+ 0x54, 0xBB, // ID = 0x54BB (PixelCropTop).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x07, // Body (value = 7).
+
+ 0x54, 0xCC, // ID = 0x54CC (PixelCropLeft).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x08, // Body (value = 8).
+
+ 0x54, 0xDD, // ID = 0x54DD (PixelCropRight).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x09, // Body (value = 9).
+
+ 0x54, 0xB0, // ID = 0x54B0 (DisplayWidth).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x0A, // Body (value = 10).
+
+ 0x54, 0xBA, // ID = 0x54BA (DisplayHeight).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x0B, // Body (value = 11).
+
+ 0x54, 0xB2, // ID = 0x54B2 (DisplayUnit).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = inches).
+
+ 0x54, 0xB3, // ID = 0x54B3 (AspectRatioType).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = keep aspect ratio).
+
+ 0x23, 0x83, 0xE3, // ID = 0x2383E3 (FrameRate).
+ 0x84, // Size = 4.
+ 0x40, 0x0F, 0x1B, 0xBD, // Body (value = 2.2360680103302001953125f).
+
+ 0x55, 0xB0, // ID = 0x55B0 (Colour).
+ 0x10, 0x00, 0x00, 0x07, // Size = 7.
+
+ 0x55, 0xB2, // ID = 0x55B2 (BitsPerChannel).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0x76, 0x70, // ID = 0x7670 (Projection).
+ 0x10, 0x00, 0x00, 0x07, // Size = 7.
+
+ 0x76, 0x71, // ID = 0x7671 (ProjectionType).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = cube map).
+ });
+
+ ParseAndVerify();
+
+ const Video video = parser_.value();
+
+ EXPECT_TRUE(video.interlaced.is_present());
+ EXPECT_EQ(FlagInterlaced::kProgressive, video.interlaced.value());
+
+ EXPECT_TRUE(video.stereo_mode.is_present());
+ EXPECT_EQ(StereoMode::kTopBottomRightFirst, video.stereo_mode.value());
+
+ EXPECT_TRUE(video.alpha_mode.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(3), video.alpha_mode.value());
+
+ EXPECT_TRUE(video.pixel_width.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(4), video.pixel_width.value());
+
+ EXPECT_TRUE(video.pixel_height.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(5), video.pixel_height.value());
+
+ EXPECT_TRUE(video.pixel_crop_bottom.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(6), video.pixel_crop_bottom.value());
+
+ EXPECT_TRUE(video.pixel_crop_top.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(7), video.pixel_crop_top.value());
+
+ EXPECT_TRUE(video.pixel_crop_left.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(8), video.pixel_crop_left.value());
+
+ EXPECT_TRUE(video.pixel_crop_right.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(9), video.pixel_crop_right.value());
+
+ EXPECT_TRUE(video.display_width.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(10), video.display_width.value());
+
+ EXPECT_TRUE(video.display_height.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(11), video.display_height.value());
+
+ EXPECT_TRUE(video.display_unit.is_present());
+ EXPECT_EQ(DisplayUnit::kInches, video.display_unit.value());
+
+ EXPECT_TRUE(video.aspect_ratio_type.is_present());
+ EXPECT_EQ(AspectRatioType::kKeep, video.aspect_ratio_type.value());
+
+ EXPECT_TRUE(video.frame_rate.is_present());
+ EXPECT_EQ(2.2360680103302001953125, video.frame_rate.value());
+
+ EXPECT_TRUE(video.colour.is_present());
+ EXPECT_TRUE(video.colour.value().bits_per_channel.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1),
+ video.colour.value().bits_per_channel.value());
+
+ EXPECT_TRUE(video.projection.is_present());
+ EXPECT_TRUE(video.projection.value().type.is_present());
+ EXPECT_EQ(ProjectionType::kCubeMap, video.projection.value().type.value());
+}
+
+TEST_F(VideoParserTest, AbsentDisplaySize) {
+ SetReaderData({
+ 0xB0, // ID = 0xB0 (PixelWidth).
+ 0x81, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xBA, // ID = 0xBA (PixelHeight).
+ 0x81, // Size = 1.
+ 0x02, // Body (value = 2).
+ });
+
+ ParseAndVerify();
+
+ const Video video = parser_.value();
+
+ EXPECT_FALSE(video.interlaced.is_present());
+ EXPECT_EQ(FlagInterlaced::kUnspecified, video.interlaced.value());
+
+ EXPECT_FALSE(video.stereo_mode.is_present());
+ EXPECT_EQ(StereoMode::kMono, video.stereo_mode.value());
+
+ EXPECT_FALSE(video.alpha_mode.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.alpha_mode.value());
+
+ EXPECT_TRUE(video.pixel_width.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), video.pixel_width.value());
+
+ EXPECT_TRUE(video.pixel_height.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), video.pixel_height.value());
+
+ EXPECT_FALSE(video.pixel_crop_bottom.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_bottom.value());
+
+ EXPECT_FALSE(video.pixel_crop_top.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_top.value());
+
+ EXPECT_FALSE(video.pixel_crop_left.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_left.value());
+
+ EXPECT_FALSE(video.pixel_crop_right.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_right.value());
+
+ EXPECT_FALSE(video.display_width.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), video.display_width.value());
+
+ EXPECT_FALSE(video.display_height.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), video.display_height.value());
+
+ EXPECT_FALSE(video.display_unit.is_present());
+ EXPECT_EQ(DisplayUnit::kPixels, video.display_unit.value());
+
+ EXPECT_FALSE(video.aspect_ratio_type.is_present());
+ EXPECT_EQ(AspectRatioType::kFreeResizing, video.aspect_ratio_type.value());
+
+ EXPECT_FALSE(video.frame_rate.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.frame_rate.value());
+
+ EXPECT_FALSE(video.colour.is_present());
+ EXPECT_EQ(Colour{}, video.colour.value());
+}
+
+TEST_F(VideoParserTest, DefaultDisplaySize) {
+ SetReaderData({
+ 0xB0, // ID = 0xB0 (PixelWidth).
+ 0x40, 0x01, // Size = 1.
+ 0x01, // Body (value = 1).
+
+ 0xBA, // ID = 0xBA (PixelHeight).
+ 0x40, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+
+ 0x54, 0xB0, // ID = 0x54B0 (DisplayWidth).
+ 0x40, 0x00, // Size = 0.
+
+ 0x54, 0xBA, // ID = 0x54BA (DisplayHeight).
+ 0x40, 0x00, // Size = 0.
+ });
+
+ ParseAndVerify();
+
+ const Video video = parser_.value();
+
+ EXPECT_FALSE(video.interlaced.is_present());
+ EXPECT_EQ(FlagInterlaced::kUnspecified, video.interlaced.value());
+
+ EXPECT_FALSE(video.stereo_mode.is_present());
+ EXPECT_EQ(StereoMode::kMono, video.stereo_mode.value());
+
+ EXPECT_FALSE(video.alpha_mode.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.alpha_mode.value());
+
+ EXPECT_TRUE(video.pixel_width.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), video.pixel_width.value());
+
+ EXPECT_TRUE(video.pixel_height.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), video.pixel_height.value());
+
+ EXPECT_FALSE(video.pixel_crop_bottom.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_bottom.value());
+
+ EXPECT_FALSE(video.pixel_crop_top.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_top.value());
+
+ EXPECT_FALSE(video.pixel_crop_left.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_left.value());
+
+ EXPECT_FALSE(video.pixel_crop_right.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.pixel_crop_right.value());
+
+ EXPECT_TRUE(video.display_width.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(1), video.display_width.value());
+
+ EXPECT_TRUE(video.display_height.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(2), video.display_height.value());
+
+ EXPECT_FALSE(video.display_unit.is_present());
+ EXPECT_EQ(DisplayUnit::kPixels, video.display_unit.value());
+
+ EXPECT_FALSE(video.aspect_ratio_type.is_present());
+ EXPECT_EQ(AspectRatioType::kFreeResizing, video.aspect_ratio_type.value());
+
+ EXPECT_FALSE(video.frame_rate.is_present());
+ EXPECT_EQ(static_cast<std::uint64_t>(0), video.frame_rate.value());
+
+ EXPECT_FALSE(video.colour.is_present());
+ EXPECT_EQ(Colour{}, video.colour.value());
+}
+
+} // namespace
diff --git a/webm_parser/tests/virtual_block_parser_test.cc b/webm_parser/tests/virtual_block_parser_test.cc
new file mode 100644
index 0000000..97ef741
--- /dev/null
+++ b/webm_parser/tests/virtual_block_parser_test.cc
@@ -0,0 +1,74 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/virtual_block_parser.h"
+
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::kUnknownElementSize;
+using webm::Status;
+using webm::VirtualBlock;
+using webm::VirtualBlockParser;
+
+namespace {
+
+class VirtualBlockParserTest
+ : public ElementParserTest<VirtualBlockParser, Id::kBlockVirtual> {};
+
+TEST_F(VirtualBlockParserTest, InvalidSize) {
+ TestInit(3, Status::kInvalidElementSize);
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(VirtualBlockParserTest, InvalidBlock) {
+ SetReaderData({
+ 0x40, 0x01, // Track number = 1.
+ 0x00, 0x00, // Timecode = 0.
+ 0x00, // Flags.
+ });
+
+ // Initialize with 1 byte short.
+ ParseAndExpectResult(Status::kInvalidElementValue, reader_.size() - 1);
+}
+
+TEST_F(VirtualBlockParserTest, ValidBlock) {
+ SetReaderData({
+ 0x81, // Track number = 1.
+ 0x12, 0x34, // Timecode = 4660.
+ 0x00, // Flags.
+ });
+
+ ParseAndVerify();
+
+ const VirtualBlock virtual_block = parser_.value();
+
+ EXPECT_EQ(static_cast<std::uint64_t>(1), virtual_block.track_number);
+ EXPECT_EQ(0x1234, virtual_block.timecode);
+}
+
+TEST_F(VirtualBlockParserTest, IncrementalParse) {
+ SetReaderData({
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // Track number = 2.
+ 0xFF, 0xFE, // Timecode = -2.
+ 0x00, // Flags.
+ });
+
+ IncrementalParseAndVerify();
+
+ const VirtualBlock virtual_block = parser_.value();
+
+ EXPECT_EQ(static_cast<std::uint64_t>(2), virtual_block.track_number);
+ EXPECT_EQ(-2, virtual_block.timecode);
+}
+
+} // namespace
diff --git a/webm_parser/tests/void_parser_test.cc b/webm_parser/tests/void_parser_test.cc
new file mode 100644
index 0000000..4223a39
--- /dev/null
+++ b/webm_parser/tests/void_parser_test.cc
@@ -0,0 +1,56 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "src/void_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/element_parser_test.h"
+#include "webm/element.h"
+#include "webm/id.h"
+#include "webm/status.h"
+
+using testing::NotNull;
+
+using webm::ElementParserTest;
+using webm::Id;
+using webm::kUnknownElementSize;
+using webm::Status;
+using webm::VoidParser;
+
+namespace {
+
+class VoidParserTest : public ElementParserTest<VoidParser, Id::kVoid> {};
+
+TEST_F(VoidParserTest, InvalidSize) {
+ TestInit(kUnknownElementSize, Status::kInvalidElementSize);
+}
+
+TEST_F(VoidParserTest, Empty) {
+ EXPECT_CALL(callback_, OnVoid(metadata_, NotNull(), NotNull())).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(VoidParserTest, Valid) {
+ SetReaderData({0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07});
+
+ EXPECT_CALL(callback_, OnVoid(metadata_, NotNull(), NotNull())).Times(1);
+
+ ParseAndVerify();
+}
+
+TEST_F(VoidParserTest, IncrementalParse) {
+ SetReaderData({0x00, 0x01, 0x02, 0x03});
+
+ EXPECT_CALL(callback_, OnVoid(metadata_, NotNull(), NotNull())).Times(4);
+
+ IncrementalParseAndVerify();
+}
+
+} // namespace
diff --git a/webm_parser/tests/webm_parser_test.cc b/webm_parser/tests/webm_parser_test.cc
new file mode 100644
index 0000000..26abbaf
--- /dev/null
+++ b/webm_parser/tests/webm_parser_test.cc
@@ -0,0 +1,339 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "webm/webm_parser.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include "test_utils/mock_callback.h"
+#include "webm/buffer_reader.h"
+#include "webm/status.h"
+
+using testing::_;
+using testing::DoDefault;
+using testing::InSequence;
+using testing::NotNull;
+using testing::Return;
+
+using webm::BufferReader;
+using webm::Ebml;
+using webm::ElementMetadata;
+using webm::Id;
+using webm::Info;
+using webm::kUnknownElementPosition;
+using webm::kUnknownElementSize;
+using webm::kUnknownHeaderSize;
+using webm::MockCallback;
+using webm::Status;
+using webm::WebmParser;
+
+namespace {
+
+class WebmParserTest : public testing::Test {};
+
+TEST_F(WebmParserTest, InvalidId) {
+ BufferReader reader = {
+ 0x00, // IDs cannot start with 0x00.
+ };
+
+ MockCallback callback;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback, OnEbml(_, _)).Times(0);
+
+ EXPECT_CALL(callback, OnSegmentBegin(_, NotNull())).Times(0);
+ EXPECT_CALL(callback, OnSegmentEnd(_)).Times(0);
+ }
+
+ WebmParser parser;
+ Status status = parser.Feed(&callback, &reader);
+ EXPECT_EQ(Status::kInvalidElementId, status.code);
+}
+
+TEST_F(WebmParserTest, InvalidSize) {
+ BufferReader reader = {
+ 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML).
+ 0x00, // Size must have 1+ bits set in the first byte.
+ };
+
+ MockCallback callback;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(callback, OnEbml(_, _)).Times(0);
+
+ EXPECT_CALL(callback, OnSegmentBegin(_, NotNull())).Times(0);
+ EXPECT_CALL(callback, OnSegmentEnd(_)).Times(0);
+ }
+
+ WebmParser parser;
+ Status status = parser.Feed(&callback, &reader);
+ EXPECT_EQ(Status::kInvalidElementSize, status.code);
+}
+
+TEST_F(WebmParserTest, DefaultParse) {
+ BufferReader reader = {
+ 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML).
+ 0x80, // Size = 0.
+
+ 0x18, 0x53, 0x80, 0x67, // ID = 0x18538067 (Segment).
+ 0x80, // Size = 0.
+ };
+
+ MockCallback callback;
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {Id::kEbml, 5, 0, 0};
+ const Ebml ebml{};
+ EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1);
+
+ metadata = {Id::kSegment, 5, 0, 5};
+ EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback, OnSegmentBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback, OnSegmentEnd(metadata)).Times(1);
+ }
+
+ WebmParser parser;
+ Status status = parser.Feed(&callback, &reader);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+}
+
+TEST_F(WebmParserTest, DefaultActionIsRead) {
+ BufferReader reader = {
+ 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML).
+ 0x80, // Size = 0.
+ };
+
+ MockCallback callback;
+ {
+ InSequence dummy;
+
+ const ElementMetadata metadata = {Id::kEbml, 5, 0, 0};
+ const Ebml ebml{};
+
+ // This intentionally does not set the action and relies on the parser using
+ // a default action value of kRead.
+ EXPECT_CALL(callback, OnElementBegin(metadata, NotNull()))
+ .WillOnce(Return(Status(Status::kOkCompleted)));
+ EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1);
+ }
+
+ WebmParser parser;
+ Status status = parser.Feed(&callback, &reader);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+}
+
+TEST_F(WebmParserTest, UnknownElement) {
+ BufferReader reader = {
+ 0x80, // ID = 0x80.
+ 0x80, // Size = 0.
+ };
+
+ MockCallback callback;
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {static_cast<Id>(0x80), 2, 0, 0};
+ EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback, OnUnknownElement(metadata, NotNull(), NotNull()))
+ .Times(1);
+ }
+
+ WebmParser parser;
+ Status status = parser.Feed(&callback, &reader);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+}
+
+TEST_F(WebmParserTest, SeekEbml) {
+ BufferReader reader = {
+ 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML).
+ 0x87, // Size = 7.
+
+ 0x42, 0x86, // ID = 0x4286 (EBMLVersion).
+ 0x10, 0x00, 0x00, 0x01, // Size = 1.
+ 0x02, // Body (value = 2).
+ };
+ std::uint64_t num_to_skip = 5; // Skip the starting EBML element metadata.
+ std::uint64_t num_actually_skipped = 0;
+ reader.Skip(num_to_skip, &num_actually_skipped);
+ EXPECT_EQ(num_to_skip, num_actually_skipped);
+
+ MockCallback callback;
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {Id::kEbmlVersion, 6, 1, num_to_skip};
+ EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1);
+
+ metadata = {Id::kEbml, kUnknownHeaderSize, kUnknownElementSize,
+ kUnknownElementPosition};
+ Ebml ebml{};
+ ebml.ebml_version.Set(2, true);
+ EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1);
+ }
+
+ WebmParser parser;
+ parser.DidSeek();
+ Status status = parser.Feed(&callback, &reader);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(reader.size(), reader.Position());
+}
+
+TEST_F(WebmParserTest, SeekSegment) {
+ BufferReader reader = {
+ 0x18, 0x53, 0x80, 0x67, // ID = 0x18538067 (Segment).
+ 0x85, // Size = 5.
+
+ 0x15, 0x49, 0xA9, 0x66, // ID = 0x1549A966 (Info).
+ 0x80, // Size = 0.
+ };
+ std::uint64_t num_to_skip = 5; // Skip the starting Segment element metadata.
+ std::uint64_t num_actually_skipped = 0;
+ reader.Skip(num_to_skip, &num_actually_skipped);
+ EXPECT_EQ(num_to_skip, num_actually_skipped);
+
+ MockCallback callback;
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {Id::kInfo, 5, 0, num_to_skip};
+ const Info info{};
+ EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback, OnInfo(metadata, info)).Times(1);
+
+ metadata = {Id::kSegment, kUnknownHeaderSize, kUnknownElementSize,
+ kUnknownElementPosition};
+ EXPECT_CALL(callback, OnSegmentEnd(metadata)).Times(1);
+ }
+
+ WebmParser parser;
+ parser.DidSeek();
+ Status status = parser.Feed(&callback, &reader);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(reader.size(), reader.Position());
+}
+
+TEST_F(WebmParserTest, SeekVoid) {
+ BufferReader reader = {
+ 0xEC, // ID = 0xEC (Void).
+ 0x81, // Size = 0.
+
+ 0xEC, // ID = 0xEC (Void).
+ 0x81, // Size = 1.
+ 0x00, // Body.
+
+ 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML).
+ 0x80, // Size = 0.
+ };
+ std::uint64_t num_to_skip = 2; // Skip the first Void element.
+ std::uint64_t num_actually_skipped = 0;
+ reader.Skip(num_to_skip, &num_actually_skipped);
+ EXPECT_EQ(num_to_skip, num_actually_skipped);
+
+ MockCallback callback;
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {Id::kVoid, 2, 1, num_to_skip};
+ EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback, OnVoid(metadata, &reader, NotNull())).Times(1);
+
+ metadata = {Id::kEbml, 5, 0, 5};
+ const Ebml ebml{};
+ EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback, OnEbml(metadata, ebml)).Times(1);
+ }
+
+ WebmParser parser;
+ parser.DidSeek();
+ Status status = parser.Feed(&callback, &reader);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(reader.size(), reader.Position());
+}
+
+TEST_F(WebmParserTest, SwapAfterFailedParse) {
+ BufferReader reader = {
+ 0x00, // Invalid ID.
+
+ 0xEC, // ID = 0xEC (Void).
+ 0x81, // Size = 1.
+ 0x00, // Body.
+ };
+
+ MockCallback expect_nothing;
+ EXPECT_CALL(expect_nothing, OnElementBegin(_, _)).Times(0);
+
+ MockCallback expect_void;
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {Id::kVoid, 2, 1, 1};
+ EXPECT_CALL(expect_void, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(expect_void, OnVoid(metadata, &reader, NotNull())).Times(1);
+ }
+
+ WebmParser parser1;
+ Status status = parser1.Feed(&expect_nothing, &reader);
+ EXPECT_EQ(Status::kInvalidElementId, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), reader.Position());
+
+ // After swapping, the parser should retain its failed state and not consume
+ // more data.
+ WebmParser parser2;
+ parser2.Swap(&parser1);
+ status = parser2.Feed(&expect_nothing, &reader);
+ EXPECT_EQ(Status::kInvalidElementId, status.code);
+ EXPECT_EQ(static_cast<std::uint64_t>(1), reader.Position());
+
+ // parser1 should be a fresh/new parser after the swap, so parsing should
+ // succeed.
+ status = parser1.Feed(&expect_void, &reader);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(reader.size(), reader.Position());
+}
+
+TEST_F(WebmParserTest, Swap) {
+ BufferReader reader = {
+ 0xEC, // ID = 0xEC (Void).
+ 0x81, // Size = 1.
+ 0x00, // Body.
+
+ 0x1A, 0x45, 0xDF, 0xA3, // ID = 0x1A45DFA3 (EBML).
+ 0x80, // Size = 0.
+ };
+
+ MockCallback callback;
+ {
+ InSequence dummy;
+
+ ElementMetadata metadata = {Id::kVoid, 2, 1, 0};
+ EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback, OnVoid(metadata, &reader, NotNull()))
+ .WillOnce(Return(Status(Status::kOkPartial)))
+ .WillOnce(DoDefault());
+
+ metadata = {Id::kEbml, 5, 0, 3};
+ EXPECT_CALL(callback, OnElementBegin(metadata, NotNull())).Times(1);
+ EXPECT_CALL(callback, OnEbml(metadata, Ebml{})).Times(1);
+ }
+
+ WebmParser parser1;
+ Status status = parser1.Feed(&callback, &reader);
+ EXPECT_EQ(Status::kOkPartial, status.code);
+
+ WebmParser parser2;
+ swap(parser1, parser2);
+ status = parser2.Feed(&callback, &reader);
+ EXPECT_EQ(Status::kOkCompleted, status.code);
+ EXPECT_EQ(reader.size(), reader.Position());
+}
+
+} // namespace
diff --git a/webm_parser/tests/webm_parser_tests.cc b/webm_parser/tests/webm_parser_tests.cc
new file mode 100644
index 0000000..4011572
--- /dev/null
+++ b/webm_parser/tests/webm_parser_tests.cc
@@ -0,0 +1,13 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#include "gtest/gtest.h"
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+} \ No newline at end of file
diff --git a/webmids.hpp b/webmids.hpp
new file mode 100644
index 0000000..e0eca45
--- /dev/null
+++ b/webmids.hpp
@@ -0,0 +1,23 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_WEBMIDS_HPP_
+#define LIBWEBM_WEBMIDS_HPP_
+
+// This file is a wrapper for the file included immediately after this comment.
+// New projects should not include this file: include the file included below.
+#include "common/webmids.h"
+
+namespace mkvmuxer {
+// MkvId moved from the mkvmuxer namespace to the libwebm namespace. Pull all
+// of libwebm into mkvmuxer to ease transition to the new namespace. New
+// projects should use libwebm::MkvId and should not expect to find MkvId in
+// mkvmuxer.
+using namespace libwebm;
+} // namespace mkvmuxer
+
+#endif // LIBWEBM_WEBMIDS_HPP_
diff --git a/webvtt/vttreader.cc b/webvtt/vttreader.cc
new file mode 100644
index 0000000..0dba9de
--- /dev/null
+++ b/webvtt/vttreader.cc
@@ -0,0 +1,54 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#include "webvtt/vttreader.h"
+
+namespace libwebvtt {
+
+VttReader::VttReader() : file_(NULL) {}
+
+VttReader::~VttReader() { Close(); }
+
+int VttReader::Open(const char* filename) {
+ if (filename == NULL || file_ != NULL)
+ return -1;
+
+ file_ = fopen(filename, "rb");
+ if (file_ == NULL)
+ return -1;
+
+ return 0; // success
+}
+
+void VttReader::Close() {
+ if (file_) {
+ fclose(file_);
+ file_ = NULL;
+ }
+}
+
+int VttReader::GetChar(char* c) {
+ if (c == NULL || file_ == NULL)
+ return -1;
+
+ const int result = fgetc(file_);
+ if (result != EOF) {
+ *c = static_cast<char>(result);
+ return 0; // success
+ }
+
+ if (ferror(file_))
+ return -1; // error
+
+ if (feof(file_))
+ return 1; // EOF
+
+ return -1; // weird
+}
+
+} // namespace libwebvtt
diff --git a/webvtt/vttreader.h b/webvtt/vttreader.h
new file mode 100644
index 0000000..8c3853f
--- /dev/null
+++ b/webvtt/vttreader.h
@@ -0,0 +1,44 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef WEBVTT_VTTREADER_H_
+#define WEBVTT_VTTREADER_H_
+
+#include <cstdio>
+#include "./webvttparser.h"
+
+namespace libwebvtt {
+
+class VttReader : public libwebvtt::Reader {
+ public:
+ VttReader();
+ virtual ~VttReader();
+
+ // Open the file identified by |filename| in read-only mode, as a
+ // binary stream of bytes. Returns 0 on success, negative if error.
+ int Open(const char* filename);
+
+ // Closes the file stream. Note that the stream is automatically
+ // closed when the VttReader object is destroyed.
+ void Close();
+
+ // Reads the next character in the file stream, as per the semantics
+ // of Reader::GetChar. Returns negative if error, 0 on success, and
+ // positive if end-of-stream has been reached.
+ virtual int GetChar(char* c);
+
+ private:
+ FILE* file_;
+
+ VttReader(const VttReader&);
+ VttReader& operator=(const VttReader&);
+};
+
+} // namespace libwebvtt
+
+#endif // WEBVTT_VTTREADER_H_
diff --git a/webvtt/webvttparser.cc b/webvtt/webvttparser.cc
new file mode 100644
index 0000000..c79d642
--- /dev/null
+++ b/webvtt/webvttparser.cc
@@ -0,0 +1,706 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#include "webvttparser.h"
+
+#include <ctype.h>
+
+#include <climits>
+#include <cstddef>
+
+namespace libwebvtt {
+
+// NOLINT'ing this enum because clang-format puts it in a single line which
+// makes it look really unreadable.
+enum {
+ kNUL = '\x00',
+ kSPACE = ' ',
+ kTAB = '\x09',
+ kLF = '\x0A',
+ kCR = '\x0D'
+}; // NOLINT
+
+Reader::~Reader() {}
+
+LineReader::~LineReader() {}
+
+int LineReader::GetLine(std::string* line_ptr) {
+ if (line_ptr == NULL)
+ return -1;
+
+ std::string& ln = *line_ptr;
+ ln.clear();
+
+ // Consume characters from the stream, until we
+ // reach end-of-line (or end-of-stream).
+
+ // The WebVTT spec states that lines may be
+ // terminated in any of these three ways:
+ // LF
+ // CR
+ // CR LF
+
+ // We interrogate each character as we read it from the stream.
+ // If we detect an end-of-line character, we consume the full
+ // end-of-line indication, and we're done; otherwise, accumulate
+ // the character and repeat.
+
+ for (;;) {
+ char c;
+ const int e = GetChar(&c);
+
+ if (e < 0) // error
+ return e;
+
+ if (e > 0) // EOF
+ return (ln.empty()) ? 1 : 0;
+
+ // We have a character, so we must first determine
+ // whether we have reached end-of-line.
+
+ if (c == kLF)
+ return 0; // handle the easy end-of-line case immediately
+
+ if (c == kCR)
+ break; // handle the hard end-of-line case outside of loop
+
+ if (c == '\xFE' || c == '\xFF') // not UTF-8
+ return -1;
+
+ // To defend against pathological or malicious streams, we
+ // cap the line length at some arbitrarily-large value:
+ enum { kMaxLineLength = 10000 }; // arbitrary
+
+ if (ln.length() >= kMaxLineLength)
+ return -1;
+
+ // We don't have an end-of-line character, so accumulate
+ // the character in our line buffer.
+ ln.push_back(c);
+ }
+
+ // We detected a CR. We must interrogate the next character
+ // in the stream, to determine whether we have a LF (which
+ // would make it part of this same line).
+
+ char c;
+ const int e = GetChar(&c);
+
+ if (e < 0) // error
+ return e;
+
+ if (e > 0) // EOF
+ return 0;
+
+ // If next character in the stream is not a LF, return it
+ // to the stream (because it's part of the next line).
+ if (c != kLF)
+ UngetChar(c);
+
+ return 0;
+}
+
+Parser::Parser(Reader* r) : reader_(r), unget_(-1) {}
+
+Parser::~Parser() {}
+
+int Parser::Init() {
+ int e = ParseBOM();
+
+ if (e < 0) // error
+ return e;
+
+ if (e > 0) // EOF
+ return -1;
+
+ // Parse "WEBVTT". We read from the stream one character at-a-time, in
+ // order to defend against non-WebVTT streams (e.g. binary files) that don't
+ // happen to comprise lines of text demarcated with line terminators.
+
+ const char kId[] = "WEBVTT";
+
+ for (const char* p = kId; *p; ++p) {
+ char c;
+ e = GetChar(&c);
+
+ if (e < 0) // error
+ return e;
+
+ if (e > 0) // EOF
+ return -1;
+
+ if (c != *p)
+ return -1;
+ }
+
+ std::string line;
+
+ e = GetLine(&line);
+
+ if (e < 0) // error
+ return e;
+
+ if (e > 0) // EOF
+ return 0; // weird but valid
+
+ if (!line.empty()) {
+ // Parse optional characters that follow "WEBVTT"
+
+ const char c = line[0];
+
+ if (c != kSPACE && c != kTAB)
+ return -1;
+ }
+
+ // The WebVTT spec requires that the "WEBVTT" line
+ // be followed by an empty line (to separate it from
+ // first cue).
+
+ e = GetLine(&line);
+
+ if (e < 0) // error
+ return e;
+
+ if (e > 0) // EOF
+ return 0; // weird but we allow it
+
+ if (!line.empty())
+ return -1;
+
+ return 0; // success
+}
+
+int Parser::Parse(Cue* cue) {
+ if (cue == NULL)
+ return -1;
+
+ // Parse first non-blank line
+
+ std::string line;
+ int e;
+
+ for (;;) {
+ e = GetLine(&line);
+
+ if (e) // EOF is OK here
+ return e;
+
+ if (!line.empty())
+ break;
+ }
+
+ // A WebVTT cue comprises an optional cue identifier line followed
+ // by a (non-optional) timings line. You determine whether you have
+ // a timings line by scanning for the arrow token, the lexeme of which
+ // may not appear in the cue identifier line.
+
+ const char kArrow[] = "-->";
+ std::string::size_type arrow_pos = line.find(kArrow);
+
+ if (arrow_pos != std::string::npos) {
+ // We found a timings line, which implies that we don't have a cue
+ // identifier.
+
+ cue->identifier.clear();
+ } else {
+ // We did not find a timings line, so we assume that we have a cue
+ // identifier line, and then try again to find the cue timings on
+ // the next line.
+
+ cue->identifier.swap(line);
+
+ e = GetLine(&line);
+
+ if (e < 0) // error
+ return e;
+
+ if (e > 0) // EOF
+ return -1;
+
+ arrow_pos = line.find(kArrow);
+
+ if (arrow_pos == std::string::npos) // not a timings line
+ return -1;
+ }
+
+ e = ParseTimingsLine(&line, arrow_pos, &cue->start_time, &cue->stop_time,
+ &cue->settings);
+
+ if (e) // error
+ return e;
+
+ // The cue payload comprises all the non-empty
+ // lines that follow the timings line.
+
+ Cue::payload_t& p = cue->payload;
+ p.clear();
+
+ for (;;) {
+ e = GetLine(&line);
+
+ if (e < 0) // error
+ return e;
+
+ if (line.empty())
+ break;
+
+ p.push_back(line);
+ }
+
+ if (p.empty())
+ return -1;
+
+ return 0; // success
+}
+
+int Parser::GetChar(char* c) {
+ if (unget_ >= 0) {
+ *c = static_cast<char>(unget_);
+ unget_ = -1;
+ return 0;
+ }
+
+ return reader_->GetChar(c);
+}
+
+void Parser::UngetChar(char c) { unget_ = static_cast<unsigned char>(c); }
+
+int Parser::ParseBOM() {
+ // Explanation of UTF-8 BOM:
+ // http://en.wikipedia.org/wiki/Byte_order_mark
+
+ static const char BOM[] = "\xEF\xBB\xBF"; // UTF-8 BOM
+
+ for (int i = 0; i < 3; ++i) {
+ char c;
+ int e = GetChar(&c);
+
+ if (e < 0) // error
+ return e;
+
+ if (e > 0) // EOF
+ return 1;
+
+ if (c != BOM[i]) {
+ if (i == 0) { // we don't have a BOM
+ UngetChar(c);
+ return 0; // success
+ }
+
+ // We started a BOM, so we must finish the BOM.
+ return -1; // error
+ }
+ }
+
+ return 0; // success
+}
+
+int Parser::ParseTimingsLine(std::string* line_ptr,
+ std::string::size_type arrow_pos, Time* start_time,
+ Time* stop_time, Cue::settings_t* settings) {
+ if (line_ptr == NULL)
+ return -1;
+
+ std::string& line = *line_ptr;
+
+ if (arrow_pos == std::string::npos || arrow_pos >= line.length())
+ return -1;
+
+ // Place a NUL character at the start of the arrow token, in
+ // order to demarcate the start time from remainder of line.
+ line[arrow_pos] = kNUL;
+ std::string::size_type idx = 0;
+
+ int e = ParseTime(line, &idx, start_time);
+ if (e) // error
+ return e;
+
+ // Detect any junk that follows the start time,
+ // but precedes the arrow symbol.
+
+ while (char c = line[idx]) {
+ if (c != kSPACE && c != kTAB)
+ return -1;
+ ++idx;
+ }
+
+ // Place a NUL character at the end of the line,
+ // so the scanner has a place to stop, and begin
+ // the scan just beyond the arrow token.
+
+ line.push_back(kNUL);
+ idx = arrow_pos + 3;
+
+ e = ParseTime(line, &idx, stop_time);
+ if (e) // error
+ return e;
+
+ e = ParseSettings(line, idx, settings);
+ if (e) // error
+ return e;
+
+ return 0; // success
+}
+
+int Parser::ParseTime(const std::string& line, std::string::size_type* idx_ptr,
+ Time* time) {
+ if (idx_ptr == NULL)
+ return -1;
+
+ std::string::size_type& idx = *idx_ptr;
+
+ if (idx == std::string::npos || idx >= line.length())
+ return -1;
+
+ if (time == NULL)
+ return -1;
+
+ // Consume any whitespace that precedes the timestamp.
+
+ while (char c = line[idx]) {
+ if (c != kSPACE && c != kTAB)
+ break;
+ ++idx;
+ }
+
+ // WebVTT timestamp syntax comes in three flavors:
+ // SS[.sss]
+ // MM:SS[.sss]
+ // HH:MM:SS[.sss]
+
+ // Parse a generic number value. We don't know which component
+ // of the time we have yet, until we do more parsing.
+
+ int val = ParseNumber(line, &idx);
+
+ if (val < 0) // error
+ return val;
+
+ Time& t = *time;
+
+ // The presence of a colon character indicates that we have
+ // an [HH:]MM:SS style syntax.
+
+ if (line[idx] == ':') {
+ // We have either HH:MM:SS or MM:SS
+
+ // The value we just parsed is either the hours or minutes.
+ // It must be followed by another number value (that is
+ // either minutes or seconds).
+
+ const int first_val = val;
+
+ ++idx; // consume colon
+
+ // Parse second value
+
+ val = ParseNumber(line, &idx);
+
+ if (val < 0)
+ return val;
+
+ if (val >= 60) // either MM or SS
+ return -1;
+
+ if (line[idx] == ':') {
+ // We have HH:MM:SS
+
+ t.hours = first_val;
+ t.minutes = val; // vetted above
+
+ ++idx; // consume MM:SS colon
+
+ // We have parsed the hours and minutes.
+ // We must now parse the seconds.
+
+ val = ParseNumber(line, &idx);
+
+ if (val < 0)
+ return val;
+
+ if (val >= 60) // SS part of HH:MM:SS
+ return -1;
+
+ t.seconds = val;
+ } else {
+ // We have MM:SS
+
+ // The implication here is that the hour value was omitted
+ // from the timestamp (because it was 0).
+
+ if (first_val >= 60) // minutes
+ return -1;
+
+ t.hours = 0;
+ t.minutes = first_val;
+ t.seconds = val; // vetted above
+ }
+ } else {
+ // We have SS (only)
+
+ // The time is expressed as total number of seconds,
+ // so the seconds value has no upper bound.
+
+ t.seconds = val;
+
+ // Convert SS to HH:MM:SS
+
+ t.minutes = t.seconds / 60;
+ t.seconds -= t.minutes * 60;
+
+ t.hours = t.minutes / 60;
+ t.minutes -= t.hours * 60;
+ }
+
+ // We have parsed the hours, minutes, and seconds.
+ // We must now parse the milliseconds.
+
+ char c = line[idx];
+
+ // TODO(matthewjheaney): one option here is to slightly relax the
+ // syntax rules for WebVTT timestamps, to permit the comma character
+ // to also be used as the seconds/milliseconds separator. This
+ // would handle streams that use localization conventions for
+ // countries in Western Europe. For now we obey the rules specified
+ // in the WebVTT spec (allow "full stop" only).
+
+ const bool have_milliseconds = (c == '.');
+
+ if (!have_milliseconds) {
+ t.milliseconds = 0;
+ } else {
+ ++idx; // consume FULL STOP
+
+ val = ParseNumber(line, &idx);
+
+ if (val < 0)
+ return val;
+
+ if (val >= 1000)
+ return -1;
+
+ if (val < 10)
+ t.milliseconds = val * 100;
+ else if (val < 100)
+ t.milliseconds = val * 10;
+ else
+ t.milliseconds = val;
+ }
+
+ // We have parsed the time proper. We must check for any
+ // junk that immediately follows the time specifier.
+
+ c = line[idx];
+
+ if (c != kNUL && c != kSPACE && c != kTAB)
+ return -1;
+
+ return 0; // success
+}
+
+int Parser::ParseSettings(const std::string& line, std::string::size_type idx,
+ Cue::settings_t* settings) {
+ settings->clear();
+
+ if (idx == std::string::npos || idx >= line.length())
+ return -1;
+
+ for (;;) {
+ // We must parse a line comprising a sequence of 0 or more
+ // NAME:VALUE pairs, separated by whitespace. The line iself is
+ // terminated with a NUL char (indicating end-of-line).
+
+ for (;;) {
+ const char c = line[idx];
+
+ if (c == kNUL) // end-of-line
+ return 0; // success
+
+ if (c != kSPACE && c != kTAB)
+ break;
+
+ ++idx; // consume whitespace
+ }
+
+ // We have consumed the whitespace, and have not yet reached
+ // end-of-line, so there is something on the line for us to parse.
+
+ settings->push_back(Setting());
+ Setting& s = settings->back();
+
+ // Parse the NAME part of the settings pair.
+
+ for (;;) {
+ const char c = line[idx];
+
+ if (c == ':') // we have reached end of NAME part
+ break;
+
+ if (c == kNUL || c == kSPACE || c == kTAB)
+ return -1;
+
+ s.name.push_back(c);
+
+ ++idx;
+ }
+
+ if (s.name.empty())
+ return -1;
+
+ ++idx; // consume colon
+
+ // Parse the VALUE part of the settings pair.
+
+ for (;;) {
+ const char c = line[idx];
+
+ if (c == kNUL || c == kSPACE || c == kTAB)
+ break;
+
+ if (c == ':') // suspicious when part of VALUE
+ return -1; // TODO(matthewjheaney): verify this behavior
+
+ s.value.push_back(c);
+
+ ++idx;
+ }
+
+ if (s.value.empty())
+ return -1;
+ }
+}
+
+int Parser::ParseNumber(const std::string& line,
+ std::string::size_type* idx_ptr) {
+ if (idx_ptr == NULL)
+ return -1;
+
+ std::string::size_type& idx = *idx_ptr;
+
+ if (idx == std::string::npos || idx >= line.length())
+ return -1;
+
+ if (!isdigit(line[idx]))
+ return -1;
+
+ int result = 0;
+
+ while (isdigit(line[idx])) {
+ const char c = line[idx];
+ const int i = c - '0';
+
+ if (result > INT_MAX / 10)
+ return -1;
+
+ result *= 10;
+
+ if (result > INT_MAX - i)
+ return -1;
+
+ result += i;
+
+ ++idx;
+ }
+
+ return result;
+}
+
+bool Time::operator==(const Time& rhs) const {
+ if (hours != rhs.hours)
+ return false;
+
+ if (minutes != rhs.minutes)
+ return false;
+
+ if (seconds != rhs.seconds)
+ return false;
+
+ return (milliseconds == rhs.milliseconds);
+}
+
+bool Time::operator<(const Time& rhs) const {
+ if (hours < rhs.hours)
+ return true;
+
+ if (hours > rhs.hours)
+ return false;
+
+ if (minutes < rhs.minutes)
+ return true;
+
+ if (minutes > rhs.minutes)
+ return false;
+
+ if (seconds < rhs.seconds)
+ return true;
+
+ if (seconds > rhs.seconds)
+ return false;
+
+ return (milliseconds < rhs.milliseconds);
+}
+
+bool Time::operator>(const Time& rhs) const { return rhs.operator<(*this); }
+
+bool Time::operator<=(const Time& rhs) const { return !this->operator>(rhs); }
+
+bool Time::operator>=(const Time& rhs) const { return !this->operator<(rhs); }
+
+presentation_t Time::presentation() const {
+ const presentation_t h = 1000LL * 3600LL * presentation_t(hours);
+ const presentation_t m = 1000LL * 60LL * presentation_t(minutes);
+ const presentation_t s = 1000LL * presentation_t(seconds);
+ const presentation_t result = h + m + s + milliseconds;
+ return result;
+}
+
+Time& Time::presentation(presentation_t d) {
+ if (d < 0) { // error
+ hours = 0;
+ minutes = 0;
+ seconds = 0;
+ milliseconds = 0;
+
+ return *this;
+ }
+
+ seconds = static_cast<int>(d / 1000);
+ milliseconds = static_cast<int>(d - 1000 * seconds);
+
+ minutes = seconds / 60;
+ seconds -= 60 * minutes;
+
+ hours = minutes / 60;
+ minutes -= 60 * hours;
+
+ return *this;
+}
+
+Time& Time::operator+=(presentation_t rhs) {
+ const presentation_t d = this->presentation();
+ const presentation_t dd = d + rhs;
+ this->presentation(dd);
+ return *this;
+}
+
+Time Time::operator+(presentation_t d) const {
+ Time t(*this);
+ t += d;
+ return t;
+}
+
+Time& Time::operator-=(presentation_t d) { return this->operator+=(-d); }
+
+presentation_t Time::operator-(const Time& t) const {
+ const presentation_t rhs = t.presentation();
+ const presentation_t lhs = this->presentation();
+ const presentation_t result = lhs - rhs;
+ return result;
+}
+
+} // namespace libwebvtt
diff --git a/webvtt/webvttparser.h b/webvtt/webvttparser.h
new file mode 100644
index 0000000..c1f4c6b
--- /dev/null
+++ b/webvtt/webvttparser.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2012 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+#ifndef WEBVTT_WEBVTTPARSER_H_
+#define WEBVTT_WEBVTTPARSER_H_
+
+#include <list>
+#include <string>
+
+namespace libwebvtt {
+
+class Reader {
+ public:
+ // Fetch a character from the stream. Return
+ // negative if error, positive if end-of-stream,
+ // and 0 if a character is available.
+ virtual int GetChar(char* c) = 0;
+
+ protected:
+ virtual ~Reader();
+};
+
+class LineReader : protected Reader {
+ public:
+ // Consume a line of text from the stream, stripping off
+ // the line terminator characters. Returns negative if error,
+ // 0 on success, and positive at end-of-stream.
+ int GetLine(std::string* line);
+
+ protected:
+ virtual ~LineReader();
+
+ // Puts a character back into the stream.
+ virtual void UngetChar(char c) = 0;
+};
+
+// As measured in thousandths of a second,
+// e.g. a duration of 1 equals 0.001 seconds,
+// and a duration of 1000 equals 1 second.
+typedef long long presentation_t; // NOLINT
+
+struct Time {
+ int hours;
+ int minutes;
+ int seconds;
+ int milliseconds;
+
+ bool operator==(const Time& rhs) const;
+ bool operator<(const Time& rhs) const;
+ bool operator>(const Time& rhs) const;
+ bool operator<=(const Time& rhs) const;
+ bool operator>=(const Time& rhs) const;
+
+ presentation_t presentation() const;
+ Time& presentation(presentation_t);
+
+ Time& operator+=(presentation_t);
+ Time operator+(presentation_t) const;
+
+ Time& operator-=(presentation_t);
+ presentation_t operator-(const Time&) const;
+};
+
+struct Setting {
+ std::string name;
+ std::string value;
+};
+
+struct Cue {
+ std::string identifier;
+
+ Time start_time;
+ Time stop_time;
+
+ typedef std::list<Setting> settings_t;
+ settings_t settings;
+
+ typedef std::list<std::string> payload_t;
+ payload_t payload;
+};
+
+class Parser : private LineReader {
+ public:
+ explicit Parser(Reader* r);
+ virtual ~Parser();
+
+ // Pre-parse enough of the stream to determine whether
+ // this is really a WEBVTT file. Returns 0 on success,
+ // negative if error.
+ int Init();
+
+ // Parse the next WebVTT cue from the stream. Returns 0 if
+ // an entire cue was parsed, negative if error, and positive
+ // at end-of-stream.
+ int Parse(Cue* cue);
+
+ private:
+ // Returns the next character in the stream, using the look-back character
+ // if present (as per Reader::GetChar).
+ virtual int GetChar(char* c);
+
+ // Puts a character back into the stream (as per LineReader::UngetChar).
+ virtual void UngetChar(char c);
+
+ // Check for presence of a UTF-8 BOM in the stream. Returns
+ // negative if error, 0 on success, and positive at end-of-stream.
+ int ParseBOM();
+
+ // Parse the distinguished "cue timings" line, which includes the start
+ // and stop times and settings. Argument |line| contains the complete
+ // line of text (as returned by ParseLine()), which the function is free
+ // to modify as it sees fit, to facilitate scanning. Argument |arrow_pos|
+ // is the offset of the arrow token ("-->"), which indicates that this is
+ // the timings line. Returns negative if error, 0 on success.
+ //
+ static int ParseTimingsLine(std::string* line,
+ std::string::size_type arrow_pos,
+ Time* start_time, Time* stop_time,
+ Cue::settings_t* settings);
+
+ // Parse a single time specifier (from the timings line), starting
+ // at the given offset; lexical scanning stops when a NUL character
+ // is detected. The function modifies offset |off| by the number of
+ // characters consumed. Returns negative if error, 0 on success.
+ //
+ static int ParseTime(const std::string& line, std::string::size_type* off,
+ Time* time);
+
+ // Parse the cue settings from the timings line, starting at the
+ // given offset. Returns negative if error, 0 on success.
+ //
+ static int ParseSettings(const std::string& line, std::string::size_type off,
+ Cue::settings_t* settings);
+
+ // Parse a non-negative integer from the characters in |line| beginning
+ // at offset |off|. The function increments |off| by the number
+ // of characters consumed. Returns the value, or negative if error.
+ //
+ static int ParseNumber(const std::string& line, std::string::size_type* off);
+
+ Reader* const reader_;
+
+ // Provides one character's worth of look-back, to facilitate scanning.
+ int unget_;
+
+ // Disable copy ctor and copy assign for Parser.
+ Parser(const Parser&);
+ Parser& operator=(const Parser&);
+};
+
+} // namespace libwebvtt
+
+#endif // WEBVTT_WEBVTTPARSER_H_
diff --git a/webvttparser.h b/webvttparser.h
new file mode 100644
index 0000000..d83e841
--- /dev/null
+++ b/webvttparser.h
@@ -0,0 +1,15 @@
+// Copyright (c) 2016 The WebM project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+#ifndef LIBWEBM_WEBVTTPARSER_H_
+#define LIBWEBM_WEBVTTPARSER_H_
+
+// This file is a wrapper for the file included immediately after this comment.
+// New projects should not include this file: include the file included below.
+#include "webvtt/webvttparser.h"
+
+#endif // LIBWEBM_WEBVTTPARSER_H_