isobmff (empty) → 0.13.0.0
raw patch · 99 files changed
+12053/−0 lines, 99 filesdep +QuickCheckdep +basedep +binarysetup-changed
Dependencies added: QuickCheck, base, binary, bytestring, criterion, data-default, function-builder, hspec, isobmff, mtl, pretty-types, singletons, tagged, template-haskell, text, time, type-spec, vector
Files
- .gitignore +20/−0
- .travis.yml +3/−0
- CHANGELOG.md +9/−0
- LICENSE +30/−0
- README.md +10/−0
- Setup.hs +2/−0
- benchmarks/bit-records/Main.hs +193/−0
- doc/ES_Descriptor.markDown +36/−0
- doc/basic-structure.svg +2259/−0
- doc/data_reference_box.svg +2427/−0
- doc/trun.markDown +43/−0
- isobmff.cabal +281/−0
- spec/BitRecordsSpec.hs +471/−0
- spec/BoxFieldsSpec.hs +96/−0
- spec/BoxSpec.hs +92/−0
- spec/DataReferenceSpec.hs +20/−0
- spec/ElementaryStreamDescriptorSpec.hs +40/−0
- spec/EnumSpec.hs +29/−0
- spec/ExpandableSpec.hs +68/−0
- spec/MediaFileSpec.hs +71/−0
- spec/Mp4AudioFileSpec.hs +193/−0
- spec/Mp4AudioSampleEntrySpec.hs +24/−0
- spec/Mp4AudioSegmentSpec.hs +75/−0
- spec/Spec.hs +1/−0
- spec/TypeLayoutSpec.hs +158/−0
- src/Data/ByteString/IsoBaseFileFormat/Box.hs +379/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes.hs +48/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/AudioSampleEntry.hs +30/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/ChunkOffset.hs +52/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/DataEntryUrl.hs +33/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/DataEntryUrn.hs +24/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/DataInformation.hs +18/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/DataReference.hs +38/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/FileType.hs +25/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/Handler.hs +113/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/HintMediaHeader.hs +29/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/HintSampleEntry.hs +15/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/Language.hs +39/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/Media.hs +16/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/MediaData.hs +19/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/MediaHeader.hs +29/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/MediaInformation.hs +18/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/MetaDataSampleEntry.hs +102/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/Movie.hs +18/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieExtends.hs +22/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieExtendsHeader.hs +27/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieFragment.hs +20/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieFragmentHeader.hs +28/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieHeader.hs +66/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/NullMediaHeader.hs +22/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/ProgressiveDownloadInformation.hs +24/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/SampleDescription.hs +28/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/SampleEntry.hs +52/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/SampleSize.hs +42/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/SampleTable.hs +19/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/SampleToChunk.hs +32/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/SegmentType.hs +25/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/Skip.hs +22/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/SoundMediaHeader.hs +26/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/SpecificMediaHeader.hs +11/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/TimeToSample.hs +28/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/Track.hs +18/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackExtends.hs +62/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackFragBaseMediaDecodeTime.hs +32/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackFragment.hs +23/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackFragmentHeader.hs +33/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackHeader.hs +71/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackRun.hs +80/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/VideoMediaHeader.hs +26/−0
- src/Data/ByteString/IsoBaseFileFormat/Boxes/VisualSampleEntry.hs +170/−0
- src/Data/ByteString/IsoBaseFileFormat/Brands/Dash.hs +87/−0
- src/Data/ByteString/IsoBaseFileFormat/MediaFile.hs +18/−0
- src/Data/ByteString/IsoBaseFileFormat/ReExports.hs +41/−0
- src/Data/ByteString/IsoBaseFileFormat/Util/BoxFields.hs +305/−0
- src/Data/ByteString/IsoBaseFileFormat/Util/FullBox.hs +94/−0
- src/Data/ByteString/IsoBaseFileFormat/Util/Time.hs +140/−0
- src/Data/ByteString/IsoBaseFileFormat/Util/TypeLayout.hs +53/−0
- src/Data/ByteString/IsoBaseFileFormat/Util/Versioned.hs +25/−0
- src/Data/ByteString/Mp4/AacInitSegment.hs +102/−0
- src/Data/ByteString/Mp4/AudioStreaming.hs +216/−0
- src/Data/ByteString/Mp4/Boxes/AudioSpecificConfig.hs +301/−0
- src/Data/ByteString/Mp4/Boxes/BaseDescriptor.hs +71/−0
- src/Data/ByteString/Mp4/Boxes/DecoderConfigDescriptor.hs +53/−0
- src/Data/ByteString/Mp4/Boxes/DecoderSpecificInfo.hs +111/−0
- src/Data/ByteString/Mp4/Boxes/ElementaryStreamDescriptor.hs +79/−0
- src/Data/ByteString/Mp4/Boxes/Expandable.hs +72/−0
- src/Data/ByteString/Mp4/Boxes/Mp4AudioSampleEntry.hs +90/−0
- src/Data/ByteString/Mp4/Boxes/SyncLayerConfigDescriptor.hs +16/−0
- src/Data/Kind/Extra.hs +154/−0
- src/Data/Type/BitRecords.hs +22/−0
- src/Data/Type/BitRecords/Arithmetic.hs +78/−0
- src/Data/Type/BitRecords/Assert.hs +91/−0
- src/Data/Type/BitRecords/Builder/BitBuffer.hs +161/−0
- src/Data/Type/BitRecords/Builder/LazyByteStringBuilder.hs +315/−0
- src/Data/Type/BitRecords/Core.hs +345/−0
- src/Data/Type/BitRecords/Enum.hs +148/−0
- src/Data/Type/BitRecords/Sized.hs +73/−0
- src/Data/Type/BitRecords/SizedString.hs +74/−0
- stack.yaml +13/−0
+ .gitignore view
@@ -0,0 +1,20 @@+*.hi+*.o+*.swp+*.tag+*~+*_flymake.hs+.hsenv+.stack-work/+/*.html+TAGS+/.idea/+/.dir-locals.el+*.refactored.hs+/*.html+*-build-timed.log+cabal.project.local+.ghc.environment.*+dist/+dist-newstyle/+result
+ .travis.yml view
@@ -0,0 +1,3 @@+language: haskell+ghc:+ - 8.6.3
+ CHANGELOG.md view
@@ -0,0 +1,9 @@+# 0.11.4.0++* Rename `AacMp4StreamConfig` to `AacInitSegment`+* Rename `InitSegment` to `BinaryAacInitSegment`+* Extract `AacInitSegment` and `BinaryAacInitSegment` from module `AudioStreaming` into a new+ module: `Data.ByteString.Mp4.AacInitSegment`+* Fix unit test `Mp4AudioSampleEntrySpec`+* Add CHANGELOG.md+* Switch to stackage lts-8.6
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Sven Heyll and Lindenbaum GmbH (c) 2016++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 Author name here nor the names of other+ 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+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, addParameter, 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.
+ README.md view
@@ -0,0 +1,10 @@+[](https://travis-ci.org/sheyll/isobmff)+[](http://hackage.haskell.org/package/isobmff)++# ISO-14496-12 Base Media File Format Library++This project contains a parser and builder for the media data container file+format defined as part of the MPEG-4 (ISO-14496) family of standards, called the+Iso Base Media File Format, or ISOBMFF.++It is a container format for both stored and transfered media data.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ benchmarks/bit-records/Main.hs view
@@ -0,0 +1,193 @@+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# LANGUAGE CPP #-}+module Main where++import Prelude hiding ( (.), id )+import Control.Category+import Criterion.Main+import qualified Data.ByteString.Builder as L+import qualified Data.ByteString.Lazy as L+import Data.Proxy+import Data.Type.BitRecords+import Data.Word+import Data.Tagged+import GHC.TypeLits ()+#ifdef FULLBENCHMARKS+import Data.Type.Equality+#endif+import Test.TypeSpecCrazy++#ifdef FULLBENCHMARKS++type Static64 =+ Field 3 := 2+ .+. Field 5 := 4+ .+. Field 9 := 333+ .+. Field 7 := 35+ .+. Field 30 := 458329+ .+. Field 2 := 1+ .+. Field 2 := 0+ .+. Field 2 := 1+ .+. Field 4 := 9+++type Static64WithParams =+ Field 3 := 0+ .+. Field 5 := 0+ .+. Field 9 := 0+ .+. "x" @: Field 7+ .+. Field 30 := 0+ .+. "y" @: Field 2+ .+. Field 2 := 0+ .+. Field 2 := 0+ .+. Field 4 := 0++type Static128 = Field 64 := 3735928559 .+. Field 64 := 3405688830++type Static256 = Static64 :+: Static128 :+: Static64++type Static517 = Static256 :+: Static256 :+. Field 5 := 0++#else++type Static64 = 'BitRecordMember (Field 64 := 0)+++type Static64WithParams =+ "x" @: Field 32+ .+. "y" @: Field 32++#endif++aboutStatic64 ::++ "Test Types Sizes"+ ########################++ It's "64 bit long: Static64" (ShouldBe 64 (BitRecordSize Static64))+ -* It's "64 bit long: Static64WithParams" (ShouldBe 64 (BitRecordSize Static64WithParams))+#ifdef FULLBENCHMARKS+ -* It's "128 bit long" (ShouldBeTrue ((BitRecordSize Static128) == 128))+ -* It's "256 bit long" (ShouldBeTrue ((BitRecordSize Static256) == 256))+ -* It's "517 bit long" (ShouldBeTrue ((BitRecordSize Static517) == 517))+#endif++aboutStatic64 =+ Valid++lumpUp :: Word64 -> L.Builder -> [Word8]+lumpUp m = L.unpack . L.toLazyByteString . mconcat . replicate (fromIntegral m)++static64 m = lumpUp m $+ runBitStringBuilderHoley $ bitStringBuilderHoley (Proxy :: Proxy Static64)++static64WithParam m = lumpUp m $+ runBitStringBuilderHoley (bitStringBuilderHoley (Proxy :: Proxy Static64WithParams))+ (B m)+ (B m)++#ifdef FULLBENCHMARKS++static128 m =+ lumpUp m $ runBitStringBuilderHoley $ bitStringBuilderHoley (Proxy :: Proxy Static128)++static256 m =+ lumpUp m $ runBitStringBuilderHoley $ bitStringBuilderHoley (Proxy :: Proxy Static256)++static517 m =+ lumpUp m $ runBitStringBuilderHoley $ bitStringBuilderHoley (Proxy :: Proxy Static517)++staticPlain512bitBaseline m =+ lumpUp m $ runBitStringBuilderHoley $ bitStringBuilderHoley+ (Proxy :: Proxy (+ Field 64 .+. Field 64 .+. Field 64 .+. Field 64 .+.+ Field 64 .+. Field 64 .+. Field 64 .+. Field 64+ ))+++#endif++main = do+ print aboutStatic64+ defaultMain [ bgroup "ByteStringBuilder"+ [ bgroup "64-bit"+ [ bench "1" $ nf static64 1+ , bench "100" $ nf static64 5+ , bench "1000" $ nf static64 1000+ ]+ , bgroup "64-bit parameterized"+ [ bench "1" $+ nf static64WithParam 1+ , bench "100" $+ nf static64WithParam 5+ , bench "1000" $+ nf static64WithParam 1000+ ]+#ifdef FULLBENCHMARKS+ , bgroup "128-bit"+ [ bench "1" $+ nf static128 1+ , bench "100" $+ nf static128 5+ , bench "1000" $+ nf static128 1000+ ]+ , bgroup "256-bit"+ [ bench "1" $+ nf static256 1+ , bench "100" $+ nf static256 5+ , bench "1000" $+ nf static256 1000+ ]+ , bgroup "517-bit"+ [ bench "1" $+ nf static517 1+ , bench "100" $+ nf static517 5+ , bench "1000" $+ nf static517 1000+ ]+ , bgroup "512-bit baseline"+ [ bench "1" $+ nf staticPlain512bitBaseline 1+ , bench "100" $+ nf staticPlain512bitBaseline 100+ , bench "1000" $+ nf staticPlain512bitBaseline 1000+ ]+#endif+ , bgroup "BitString Word64 direct"+ [ bench "1" $+ nf bitStringWord64Direct 1+ , bench "100" $+ nf bitStringWord64Direct 5+ , bench "1000" $+ nf bitStringWord64Direct 1000+ ]+ , bgroup "BitString Word64 holey"+ [ bench "1" $+ nf bitStringWord64Holey 1+ , bench "100" $+ nf bitStringWord64Holey 5+ , bench "1000" $+ nf bitStringWord64Holey 1000+ ]+ ]+ ]++bitStringWord64Direct m =+ lumpUp 1+ $ runBitStringBuilder+ $ mconcat+ $ replicate m+ $ appendBitString+ $ bitString 64 0x01020304050607++bitStringWord64Holey m =+ lumpUp 1+ $ runBitStringBuilderHoley+ $ mconcat+ $ replicate m+ $ bitStringBuilderHoley+ $ bitString 64 0x01020304050607
+ doc/ES_Descriptor.markDown view
@@ -0,0 +1,36 @@+# `esds` Box Contents++ 00000027 65736473 00000000++ ES_Descriptor+ | length+ x 03 | ES_ID+ x 19 |sdf+ x 0001|url+ b 0|ocr+ b 0| stream priority+ b 0 | DecoderConfigDescriptor+ b 00000 | length+ x 04 | objectype indication: Audio ISO/IEC 14496-3+ x 11 | stream-type: 0x5 AudioStream+ x 40 |Upstream+ b 000101|reserved+ b 0| BufferSize DB+ b 1 | maxBitrate+ x 000000 | avgBitrate+ x 00000000 | DecoderSpecificInfo+ x 00000000 | length+ x 05 |+ x 02+ x 1 1 9 0+ =b 00010 - audioObjectType: 2 AAC LC+ =b 0011 - (=3) samplingFrequencyIndex: 48k+ =b 0010 - channelConfiguration: channel pair+ =b 0 - frameLenflag \+ =b 0 - dependsOnCoreCoder | GASpecificconfig+ =b 0 - coreCoderDelay /+ SLConfigDescrTag+ | length: 1+ x 06 | Predefined according to iso 14496-14 section 3.1.2+ x 01 |+ x 02
+ doc/basic-structure.svg view
@@ -0,0 +1,2259 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!-- Created with Inkscape (http://www.inkscape.org/) -->++<svg+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"+ xmlns:dc="http://purl.org/dc/elements/1.1/"+ xmlns:cc="http://creativecommons.org/ns#"+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+ xmlns:svg="http://www.w3.org/2000/svg"+ xmlns="http://www.w3.org/2000/svg"+ xmlns:xlink="http://www.w3.org/1999/xlink"+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+ width="297mm"+ height="210mm"+ viewBox="0 0 1052.3622 744.09448"+ id="svg2"+ version="1.1"+ inkscape:version="0.91 r13725"+ enable-background="new"+ sodipodi:docname="basic-structure.svg">+ <defs+ id="defs4">+ <marker+ inkscape:isstock="true"+ style="overflow:visible;"+ id="marker15594"+ refX="0.0"+ refY="0.0"+ orient="auto"+ inkscape:stockid="Arrow2Mend">+ <path+ transform="scale(0.6) rotate(180) translate(0,0)"+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#ff00ff;stroke-opacity:0.6;fill:#ff00ff;fill-opacity:0.6"+ id="path15596" />+ </marker>+ <marker+ inkscape:isstock="true"+ style="overflow:visible;"+ id="marker15518"+ refX="0.0"+ refY="0.0"+ orient="auto"+ inkscape:stockid="Arrow2Mend">+ <path+ transform="scale(0.6) rotate(180) translate(0,0)"+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#ff00ff;stroke-opacity:0.6;fill:#ff00ff;fill-opacity:0.6"+ id="path15520" />+ </marker>+ <marker+ inkscape:isstock="true"+ style="overflow:visible;"+ id="marker15508"+ refX="0.0"+ refY="0.0"+ orient="auto"+ inkscape:stockid="Arrow2Mend">+ <path+ transform="scale(0.6) rotate(180) translate(0,0)"+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#ff00ff;stroke-opacity:0.6;fill:#ff00ff;fill-opacity:0.6"+ id="path15510" />+ </marker>+ <linearGradient+ id="linearGradient15474"+ osb:paint="solid">+ <stop+ style="stop-color:#000000;stop-opacity:1;"+ offset="0"+ id="stop15476" />+ </linearGradient>+ <marker+ inkscape:stockid="Arrow2Lend"+ orient="auto"+ refY="0.0"+ refX="0.0"+ id="marker14724"+ style="overflow:visible;"+ inkscape:isstock="true">+ <path+ id="path14726"+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#000000;stroke-opacity:1;fill:#000000;fill-opacity:1"+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "+ transform="scale(1.1) rotate(180) translate(1,0)" />+ </marker>+ <marker+ inkscape:isstock="true"+ style="overflow:visible;"+ id="marker14160"+ refX="0.0"+ refY="0.0"+ orient="auto"+ inkscape:stockid="Arrow2Mend">+ <path+ transform="scale(0.6) rotate(180) translate(0,0)"+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#3c3004;stroke-opacity:1;fill:#3c3004;fill-opacity:1"+ id="path14162" />+ </marker>+ <marker+ inkscape:stockid="Arrow2Mend"+ orient="auto"+ refY="0.0"+ refX="0.0"+ id="Arrow2Mend"+ style="overflow:visible;"+ inkscape:isstock="true"+ inkscape:collect="always">+ <path+ id="path5655"+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round;stroke:#3c3004;stroke-opacity:1;fill:#3c3004;fill-opacity:1"+ d="M 8.7185878,4.0337352 L -2.2072895,0.016013256 L 8.7185884,-4.0017078 C 6.9730900,-1.6296469 6.9831476,1.6157441 8.7185878,4.0337352 z "+ transform="scale(0.6) rotate(180) translate(0,0)" />+ </marker>+ <marker+ inkscape:isstock="true"+ style="overflow:visible;"+ id="marker12997"+ refX="0.0"+ refY="0.0"+ orient="auto"+ inkscape:stockid="Arrow1Lend">+ <path+ transform="scale(0.8) rotate(180) translate(12.5,0)"+ style="fill-rule:evenodd;stroke:#0a0407;stroke-width:1pt;stroke-opacity:1;fill:#0a0407;fill-opacity:1"+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "+ id="path12999" />+ </marker>+ <linearGradient+ id="linearGradient12749"+ osb:paint="solid">+ <stop+ style="stop-color:#3c3004;stop-opacity:0.98039216;"+ offset="0"+ id="stop12751" />+ </linearGradient>+ <linearGradient+ id="linearGradient12515"+ osb:paint="solid">+ <stop+ style="stop-color:#000000;stop-opacity:1;"+ offset="0"+ id="stop12517" />+ </linearGradient>+ <linearGradient+ id="linearGradient11377"+ osb:paint="solid">+ <stop+ style="stop-color:#000000;stop-opacity:1;"+ offset="0"+ id="stop11379" />+ </linearGradient>+ <linearGradient+ id="linearGradient11061"+ osb:paint="solid">+ <stop+ style="stop-color:#000000;stop-opacity:1;"+ offset="0"+ id="stop11063" />+ </linearGradient>+ <linearGradient+ id="linearGradient10993"+ osb:paint="solid">+ <stop+ style="stop-color:#000000;stop-opacity:1;"+ offset="0"+ id="stop10995" />+ </linearGradient>+ <linearGradient+ id="linearGradient10679"+ osb:paint="solid">+ <stop+ style="stop-color:#000000;stop-opacity:1;"+ offset="0"+ id="stop10681" />+ </linearGradient>+ <linearGradient+ id="linearGradient10671"+ osb:paint="solid">+ <stop+ style="stop-color:#000000;stop-opacity:1;"+ offset="0"+ id="stop10673" />+ </linearGradient>+ <linearGradient+ id="linearGradient10665"+ osb:paint="solid">+ <stop+ style="stop-color:#000000;stop-opacity:1;"+ offset="0"+ id="stop10667" />+ </linearGradient>+ <linearGradient+ inkscape:collect="always"+ id="sample-to-media-data-5">+ <stop+ style="stop-color:#ff00ff;stop-opacity:1"+ offset="0"+ id="stop10623" />+ <stop+ style="stop-color:#881800;stop-opacity:1"+ offset="1"+ id="stop10625" />+ </linearGradient>+ <marker+ inkscape:stockid="Arrow1Lstart"+ orient="auto"+ refY="0.0"+ refX="0.0"+ id="marker8320"+ style="overflow:visible"+ inkscape:isstock="true">+ <path+ id="path8322"+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"+ transform="scale(0.8) translate(12.5,0)" />+ </marker>+ <marker+ inkscape:stockid="Arrow1Lstart"+ orient="auto"+ refY="0.0"+ refX="0.0"+ id="Arrow1Lstart"+ style="overflow:visible"+ inkscape:isstock="true">+ <path+ id="path5628"+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt"+ transform="scale(0.8) translate(12.5,0)" />+ </marker>+ <marker+ inkscape:stockid="Arrow1Lend"+ orient="auto"+ refY="0.0"+ refX="0.0"+ id="marker5904"+ style="overflow:visible;"+ inkscape:isstock="true">+ <path+ id="path5906"+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"+ transform="scale(0.8) rotate(180) translate(12.5,0)" />+ </marker>+ <marker+ inkscape:stockid="Arrow1Lend"+ orient="auto"+ refY="0.0"+ refX="0.0"+ id="Arrow1Lend"+ style="overflow:visible;"+ inkscape:isstock="true"+ inkscape:collect="always">+ <path+ id="path5631"+ d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "+ style="fill-rule:evenodd;stroke:#ff00ff;stroke-width:1pt;stroke-opacity:1;fill:#ff00ff;fill-opacity:1"+ transform="scale(0.8) rotate(180) translate(12.5,0)" />+ </marker>+ <linearGradient+ id="linearGradient5596"+ osb:paint="solid">+ <stop+ style="stop-color:#1486c3;stop-opacity:1;"+ offset="0"+ id="stop5598" />+ </linearGradient>+ <linearGradient+ id="linearGradient4154"+ osb:paint="solid">+ <stop+ style="stop-color:#ff5f8d;stop-opacity:1;"+ offset="0"+ id="stop4156" />+ </linearGradient>+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient5600"+ x1="123.13862"+ y1="808.91543"+ x2="649.62811"+ y2="808.91543"+ gradientUnits="userSpaceOnUse"+ gradientTransform="matrix(1.5049175,0,0,0.58385007,-110.74398,-36.11621)" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient5604"+ gradientUnits="userSpaceOnUse"+ gradientTransform="matrix(1.501081,0,0,0.58385991,-110.27177,53.779441)"+ x1="123.13862"+ y1="808.91541"+ x2="649.62811"+ y2="808.91541" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#sample-to-media-data-5"+ id="linearGradient10599"+ x1="222.7077"+ y1="632.0694"+ x2="43.06395"+ y2="949.92651"+ gradientUnits="userSpaceOnUse" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#sample-to-media-data-5"+ id="linearGradient10601"+ x1="248.17426"+ y1="628.49792"+ x2="69.717415"+ y2="957.78363"+ gradientUnits="userSpaceOnUse" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#sample-to-media-data-5"+ id="linearGradient10637"+ x1="-694.37683"+ y1="522.23401"+ x2="-800.94135"+ y2="515.94836"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(106.28654,-0.60360971)" />+ <filter+ inkscape:collect="always"+ style="color-interpolation-filters:sRGB"+ id="filter10659">+ <feBlend+ inkscape:collect="always"+ mode="lighten"+ in2="BackgroundImage"+ id="feBlend10661" />+ </filter>+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient10675"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient10683"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient10701"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient10703"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient10717"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient10719"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient10721"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient10723"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient10997"+ x1="43.296745"+ y1="715.47632"+ x2="230.45461"+ y2="715.47632"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-1.1307678,7.9153744)" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient10999"+ x1="187.71971"+ y1="714.46619"+ x2="352.71191"+ y2="714.46619"+ gradientUnits="userSpaceOnUse" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient11065"+ x1="277.3237"+ y1="760.4281"+ x2="350.99118"+ y2="760.4281"+ gradientUnits="userSpaceOnUse" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient11067"+ x1="188.41422"+ y1="760.4281"+ x2="226.75848"+ y2="760.4281"+ gradientUnits="userSpaceOnUse" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient11239"+ gradientUnits="userSpaceOnUse"+ x1="188.41422"+ y1="760.4281"+ x2="226.75848"+ y2="760.4281"+ gradientTransform="translate(127.27922,3.0304576)" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient11569"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-228,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient11785"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-92,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient11973"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(146,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient12121"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(386,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient12173"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(626,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12249"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(12,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12297"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(252,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12337"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(492,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12389"+ gradientUnits="userSpaceOnUse"+ x1="187.71971"+ y1="714.46619"+ x2="352.71191"+ y2="714.46619" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient12391"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(127.27922,3.0304576)"+ x1="188.41422"+ y1="760.4281"+ x2="226.75848"+ y2="760.4281" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient12393"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(127.27922,3.0304576)"+ x1="188.41422"+ y1="760.4281"+ x2="226.75848"+ y2="760.4281" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient12395"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(127.27922,3.0304576)"+ x1="188.41422"+ y1="760.4281"+ x2="226.75848"+ y2="760.4281" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient12397"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(127.27922,3.0304576)"+ x1="188.41422"+ y1="760.4281"+ x2="226.75848"+ y2="760.4281" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient12399"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(127.27922,3.0304576)"+ x1="188.41422"+ y1="760.4281"+ x2="226.75848"+ y2="760.4281" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12401"+ gradientUnits="userSpaceOnUse"+ x1="187.71971"+ y1="714.46619"+ x2="352.71191"+ y2="714.46619" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12403"+ gradientUnits="userSpaceOnUse"+ x1="187.71971"+ y1="714.46619"+ x2="352.71191"+ y2="714.46619" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12405"+ gradientUnits="userSpaceOnUse"+ x1="187.71971"+ y1="714.46619"+ x2="352.71191"+ y2="714.46619" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12407"+ gradientUnits="userSpaceOnUse"+ x1="187.71971"+ y1="714.46619"+ x2="352.71191"+ y2="714.46619" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12409"+ gradientUnits="userSpaceOnUse"+ x1="187.71971"+ y1="714.46619"+ x2="352.71191"+ y2="714.46619" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient12749"+ id="linearGradient13103"+ gradientUnits="userSpaceOnUse"+ gradientTransform="matrix(0.9280993,0,0,1.0262209,52.012841,99.94962)"+ x1="196.80742"+ y1="581.64874"+ x2="908.20947"+ y2="581.64874" />+ </defs>+ <sodipodi:namedview+ id="base"+ pagecolor="#ffffff"+ bordercolor="#666666"+ borderopacity="1.0"+ inkscape:pageopacity="0.0"+ inkscape:pageshadow="2"+ inkscape:zoom="1"+ inkscape:cx="518.60504"+ inkscape:cy="348.09569"+ inkscape:document-units="px"+ inkscape:current-layer="layer1"+ showgrid="false"+ borderlayer="true"+ showguides="true"+ inkscape:guide-bbox="true">+ <sodipodi:guide+ position="-282.84272,677.81236"+ orientation="1,0"+ id="guide5606" />+ </sodipodi:namedview>+ <metadata+ id="metadata7">+ <rdf:RDF>+ <cc:Work+ rdf:about="">+ <dc:format>image/svg+xml</dc:format>+ <dc:type+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />+ <dc:title></dc:title>+ <cc:license+ rdf:resource="http://creativecommons.org/licenses/by/3.0/" />+ </cc:Work>+ <cc:License+ rdf:about="http://creativecommons.org/licenses/by/3.0/">+ <cc:permits+ rdf:resource="http://creativecommons.org/ns#Reproduction" />+ <cc:permits+ rdf:resource="http://creativecommons.org/ns#Distribution" />+ <cc:requires+ rdf:resource="http://creativecommons.org/ns#Notice" />+ <cc:requires+ rdf:resource="http://creativecommons.org/ns#Attribution" />+ <cc:permits+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />+ </cc:License>+ </rdf:RDF>+ </metadata>+ <g+ inkscape:label="Layer 1"+ inkscape:groupmode="layer"+ id="layer1"+ transform="translate(0,-308.26772)"+ style="filter:url(#filter10659)">+ <rect+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.41121864px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ id="rect3336"+ width="853.16766"+ height="253.81555"+ x="50.713242"+ y="316.1817" />+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="81.822357"+ y="386.67169"+ id="text3338"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan3340"+ x="81.822357"+ y="386.67169">movie</tspan></text>+ <rect+ style="fill:none"+ id="rect3342"+ width="388.90872"+ height="60.609154"+ x="67.680222"+ y="405.86459" />+ <rect+ style="fill:none;stroke:url(#linearGradient5600);stroke-width:4.18100023;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"+ id="rect4152"+ width="792.02228"+ height="70.773323"+ x="74.719971"+ y="400.7825" />+ <rect+ y="490.68549"+ x="74.719383"+ height="70.774506"+ width="790.00317"+ id="rect5602"+ style="fill:none;stroke:url(#linearGradient5604);stroke-width:4.18100023;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />+ <text+ sodipodi:linespacing="125%"+ id="text5612"+ y="451.16916"+ x="84.576263"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ xml:space="preserve"><tspan+ y="451.16916"+ x="84.576263"+ id="tspan5614"+ sodipodi:role="line">track 1</tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="84.576263"+ y="541.07275"+ id="text5616"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan5618"+ x="84.576263"+ y="541.07275">track 2</tspan></text>+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:1.49778581;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker-end:url(#Arrow1Lend)"+ d="m 234.25549,623.48964 661.03572,-1.08562"+ id="path5622"+ inkscape:connector-curvature="0" />+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;opacity:0.93999999;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="841.23474"+ y="687.60681"+ id="text5938"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan5940"+ x="841.23474"+ y="687.60681">seconds </tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#ff00ff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:0.94;"+ x="841.23474"+ y="616.05341"+ id="text8224"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan8226"+ x="841.23474"+ y="616.05341">Samples</tspan></text>+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 236.0051,404.32129 0,219.2031"+ id="path7614"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7616"+ d="m 247.99459,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 259.98408,404.32129 0,219.2031"+ id="path7618"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7620"+ d="m 271.97357,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 283.96308,404.32129 0,219.2031"+ id="path7622"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 295.95257,404.32129 0,219.2031"+ id="path7624"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7626"+ d="m 307.94206,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 319.93157,404.32129 0,219.2031"+ id="path7628"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7630"+ d="m 331.92106,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 343.91056,404.32129 0,219.2031"+ id="path7632"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7634"+ d="m 355.90005,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 367.88952,404.32129 0,219.2031"+ id="path7636"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7638"+ d="m 379.87903,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path7640"+ d="m 391.86852,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 403.85804,404.32129 0,219.2031"+ id="path7642"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7644"+ d="m 415.84754,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 427.83702,404.32129 0,219.2031"+ id="path7646"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7648"+ d="m 439.82653,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 451.81602,404.32129 0,219.2031"+ id="path7650"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7652"+ d="m 463.80551,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 475.79498,404.32129 0,219.2031"+ id="path7654"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 487.78449,404.32129 0,219.2031"+ id="path7656"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7658"+ d="m 499.77398,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 511.76348,404.32129 0,219.2031"+ id="path7660"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7662"+ d="m 523.75297,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path7664"+ d="m 535.7425,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 547.73192,404.32129 0,219.2031"+ id="path7666"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path7668"+ d="m 559.72146,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 571.71095,404.32129 0,219.2031"+ id="path7670"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8228"+ d="m 583.70046,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 595.68994,404.32129 0,219.2031"+ id="path8230"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8232"+ d="m 607.67941,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 619.66896,404.32129 0,219.2031"+ id="path8234"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8236"+ d="m 631.65844,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path8238"+ d="m 643.64793,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 655.63745,404.32129 0,219.2031"+ id="path8240"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8242"+ d="m 667.62693,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 679.61648,404.32129 0,219.2031"+ id="path8244"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8246"+ d="m 691.60596,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 703.59538,404.32129 0,219.2031"+ id="path8248"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8250"+ d="m 715.58491,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 727.5744,404.32129 0,219.2031"+ id="path8252"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 739.56388,404.32129 0,219.2031"+ id="path8254"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8256"+ d="m 751.55334,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 763.54287,404.32129 0,219.2031"+ id="path8258"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8260"+ d="m 775.53235,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 787.52183,404.32129 0,219.2031"+ id="path8262"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8264"+ d="m 799.51135,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 811.50088,404.32129 0,219.2031"+ id="path8266"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8268"+ d="m 823.49034,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path8270"+ d="m 835.47988,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1"+ d="m 847.46937,404.32129 0,219.2031"+ id="path8272"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path8274"+ d="m 859.45887,404.32129 0,219.2031"+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:0.52431506;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1.04863016, 0.52431507;stroke-dashoffset:0;stroke-opacity:1" />+ <g+ id="g8378"+ transform="translate(0,3.0517578e-5)">+ <rect+ y="406.32239"+ x="227.23705"+ height="59.693489"+ width="126.36358"+ id="rect8298"+ style="opacity:1;fill:none;fill-opacity:1;stroke:#00ff17;stroke-width:3.98548293;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:#00ff17;stroke-width:3.98548293;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ id="rect8348"+ width="126.36358"+ height="59.693489"+ x="354.01117"+ y="406.32239" />+ <rect+ y="406.32239"+ x="480.78534"+ height="59.693489"+ width="126.36358"+ id="rect8350"+ style="opacity:1;fill:none;fill-opacity:1;stroke:#00ff17;stroke-width:3.98548293;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:#00ff17;stroke-width:3.98548293;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ id="rect8352"+ width="126.36358"+ height="59.693489"+ x="607.55945"+ y="406.32239" />+ <rect+ y="406.32239"+ x="734.33362"+ height="59.693489"+ width="126.36358"+ id="rect8354"+ style="opacity:1;fill:none;fill-opacity:1;stroke:#00ff17;stroke-width:3.98548293;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />+ <text+ sodipodi:linespacing="125%"+ id="text8358"+ y="450.25116"+ x="227.28432"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ xml:space="preserve"><tspan+ y="450.25116"+ x="227.28432"+ id="tspan8360"+ sodipodi:role="line">Frame</tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="355.32117"+ y="450.25116"+ id="text8362"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan8364"+ x="355.32117"+ y="450.25116">Frame</tspan></text>+ <text+ sodipodi:linespacing="125%"+ id="text8366"+ y="450.25116"+ x="483.35803"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ xml:space="preserve"><tspan+ y="450.25116"+ x="483.35803"+ id="tspan8368"+ sodipodi:role="line">Frame</tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="611.39484"+ y="450.25116"+ id="text8370"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan8372"+ x="611.39484"+ y="450.25116">Frame</tspan></text>+ <text+ sodipodi:linespacing="125%"+ id="text8374"+ y="450.25116"+ x="739.4317"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ xml:space="preserve"><tspan+ y="450.25116"+ x="739.4317"+ id="tspan8376"+ sodipodi:role="line">Frame</tspan></text>+ </g>+ <g+ id="g8395"+ transform="translate(-1.0101525,89.903606)">+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:#00ff17;stroke-width:3.98548293;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ id="rect8397"+ width="126.36358"+ height="59.693489"+ x="227.23705"+ y="406.32239" />+ <rect+ y="406.32239"+ x="354.01117"+ height="59.693489"+ width="126.36358"+ id="rect8399"+ style="opacity:1;fill:none;fill-opacity:1;stroke:#00ff17;stroke-width:3.98548293;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:#00ff17;stroke-width:3.98548293;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ id="rect8401"+ width="126.36358"+ height="59.693489"+ x="480.78534"+ y="406.32239" />+ <rect+ y="406.32239"+ x="607.55945"+ height="59.693489"+ width="126.36358"+ id="rect8403"+ style="opacity:1;fill:none;fill-opacity:1;stroke:#00ff17;stroke-width:3.98548293;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:#00ff17;stroke-width:3.98548293;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ id="rect8405"+ width="126.36358"+ height="59.693489"+ x="734.33362"+ y="406.32239" />+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="227.28432"+ y="450.25116"+ id="text8407"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan8409"+ x="227.28432"+ y="450.25116">Frame</tspan></text>+ <text+ sodipodi:linespacing="125%"+ id="text8411"+ y="450.25116"+ x="355.32117"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ xml:space="preserve"><tspan+ y="450.25116"+ x="355.32117"+ id="tspan8413"+ sodipodi:role="line">Frame</tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="483.35803"+ y="450.25116"+ id="text8415"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan8417"+ x="483.35803"+ y="450.25116">Frame</tspan></text>+ <text+ sodipodi:linespacing="125%"+ id="text8419"+ y="450.25116"+ x="611.39484"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ xml:space="preserve"><tspan+ y="450.25116"+ x="611.39484"+ id="tspan8421"+ sodipodi:role="line">Frame</tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:40px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#00ff00;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="739.4317"+ y="450.25116"+ id="text8423"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan8425"+ x="739.4317"+ y="450.25116">Frame</tspan></text>+ </g>+ <path+ style="fill:none;fill-rule:evenodd;stroke:url(#linearGradient10599);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ d="M 236.55953,624.64926 42.78355,960.20373"+ id="path10587"+ inkscape:connector-curvature="0"+ sodipodi:nodetypes="cc" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:url(#linearGradient10601);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ d="M 248.20166,625.36354 75.404298,960.20373"+ id="path10591"+ inkscape:connector-curvature="0"+ sodipodi:nodetypes="cc" />+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:15.94160843px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:url(#linearGradient10637);stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="-695.56689"+ y="521.3186"+ id="text10627"+ sodipodi:linespacing="125%"+ transform="matrix(0.48547195,-0.87425225,0.87425225,0.48547195,0,0)"><tspan+ sodipodi:role="line"+ id="tspan10629"+ x="-695.56689"+ y="521.3186"+ style="stroke:url(#linearGradient10637)">Sample Config</tspan></text>+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:#881800;stroke-width:5;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ id="rect10639"+ width="1023.2421"+ height="340.10092"+ x="14.32107"+ y="578.96661" />+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:17.5px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="74.751289"+ y="755.72766"+ id="text10643"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan10645"+ x="74.751289"+ y="755.72766">sample size box</tspan></text>+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:#881800;stroke-width:0.98892051;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ id="rect9434"+ width="950.12048"+ height="63.206535"+ x="42.137867"+ y="960.14978" />+ <g+ id="g9684"+ style="stroke:#881800;stroke-opacity:1"+ transform="matrix(1.0006077,0,0,0.39854318,-0.31432415,615.94439)">+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 49.175146,867.1945 0,151.5229"+ id="path9436"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9438"+ d="m 58.057688,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 66.940233,867.1945 0,151.5229"+ id="path9440"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9442"+ d="m 75.822772,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 84.70532,867.1945 0,151.5229"+ id="path9444"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 93.587864,867.1945 0,151.5229"+ id="path9446"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9448"+ d="m 102.4704,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 111.35295,867.1945 0,151.5229"+ id="path9450"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9452"+ d="m 120.2355,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 129.11803,867.1945 0,151.5229"+ id="path9454"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9456"+ d="m 138.00058,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 146.88313,867.1945 0,151.5229"+ id="path9458"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9460"+ d="m 155.76567,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9462"+ d="m 164.64819,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 173.53075,867.1945 0,151.5229"+ id="path9464"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9466"+ d="m 182.41329,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 191.29582,867.1945 0,151.5229"+ id="path9468"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9470"+ d="m 200.17837,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 209.06092,867.1945 0,151.5229"+ id="path9472"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9474"+ d="m 217.94346,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 226.826,867.1945 0,151.5229"+ id="path9476"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 235.70855,867.1945 0,151.5229"+ id="path9478"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9480"+ d="m 244.59109,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 253.47362,867.1945 0,151.5229"+ id="path9482"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9484"+ d="m 262.35618,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9486"+ d="m 271.23871,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 280.12125,867.1945 0,151.5229"+ id="path9488"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9490"+ d="m 289.00379,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 297.88636,867.1945 0,151.5229"+ id="path9492"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9498"+ d="m 306.7689,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 315.65143,867.1945 0,151.5229"+ id="path9500"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9502"+ d="m 324.53398,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 333.4165,867.1945 0,151.5229"+ id="path9504"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9506"+ d="m 342.29905,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9508"+ d="m 351.18159,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 360.06412,867.1945 0,151.5229"+ id="path9510"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9512"+ d="m 368.9467,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 377.82922,867.1945 0,151.5229"+ id="path9514"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9516"+ d="m 386.71177,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 395.59429,867.1945 0,151.5229"+ id="path9518"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9520"+ d="m 404.47685,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 413.35939,867.1945 0,151.5229"+ id="path9522"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 422.24194,867.1945 0,151.5229"+ id="path9524"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9526"+ d="m 431.12448,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 440.00702,867.1945 0,151.5229"+ id="path9528"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9530"+ d="m 448.88955,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 457.7721,867.1945 0,151.5229"+ id="path9532"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9534"+ d="m 466.65463,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 475.53719,867.1945 0,151.5229"+ id="path9536"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9538"+ d="m 484.41974,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9540"+ d="m 493.30227,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 502.18481,867.1945 0,151.5229"+ id="path9542"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9544"+ d="m 511.06735,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9578"+ d="m 519.94988,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 528.83248,867.1945 0,151.5229"+ id="path9580"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9582"+ d="m 537.71502,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 546.59755,867.1945 0,151.5229"+ id="path9584"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9586"+ d="m 555.48009,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9588"+ d="m 564.36269,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 573.24517,867.1945 0,151.5229"+ id="path9590"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9592"+ d="m 582.12778,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 591.01025,867.1945 0,151.5229"+ id="path9594"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9596"+ d="m 599.8928,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 608.77533,867.1945 0,151.5229"+ id="path9598"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9600"+ d="m 617.65791,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 626.54045,867.1945 0,151.5229"+ id="path9602"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 635.42298,867.1945 0,151.5229"+ id="path9604"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9606"+ d="m 644.30555,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 653.18803,867.1945 0,151.5229"+ id="path9608"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9610"+ d="m 662.07061,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 670.95311,867.1945 0,151.5229"+ id="path9612"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9614"+ d="m 679.8357,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 688.71829,867.1945 0,151.5229"+ id="path9616"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9618"+ d="m 697.60075,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9620"+ d="m 706.48336,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 715.36584,867.1945 0,151.5229"+ id="path9622"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9624"+ d="m 724.24843,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 733.13097,867.1945 0,151.5229"+ id="path9626"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 742.01348,867.1945 0,151.5229"+ id="path9628"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9630"+ d="m 750.89608,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 759.77859,867.1945 0,151.5229"+ id="path9632"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9634"+ d="m 768.66112,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 777.54367,867.1945 0,151.5229"+ id="path9636"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9638"+ d="m 786.4262,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 795.30878,867.1945 0,151.5229"+ id="path9640"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9642"+ d="m 804.1913,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 813.07388,867.1945 0,151.5229"+ id="path9644"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 821.95636,867.1945 0,151.5229"+ id="path9646"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9648"+ d="m 830.83891,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 839.72143,867.1945 0,151.5229"+ id="path9650"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9652"+ d="m 848.60402,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 857.4866,867.1945 0,151.5229"+ id="path9654"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9656"+ d="m 866.36909,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 875.25164,867.1945 0,151.5229"+ id="path9658"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9660"+ d="m 884.13413,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9662"+ d="m 893.01671,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 901.89928,867.1945 0,151.5229"+ id="path9664"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9666"+ d="m 910.78178,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 919.66438,867.1945 0,151.5229"+ id="path9668"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9670"+ d="m 928.5469,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 937.42946,867.1945 0,151.5229"+ id="path9672"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9674"+ d="m 946.31196,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 955.19454,867.1945 0,151.5229"+ id="path9676"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 964.0771,867.1945 0,151.5229"+ id="path9678"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9680"+ d="m 972.95957,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 981.84218,867.1945 0,151.5229"+ id="path9682"+ inkscape:connector-curvature="0" />+ </g>+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="48.066017"+ y="1046.7532"+ id="text10249"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan10251"+ x="48.066017"+ y="1046.7532">Media-Data</tspan></text>+ <g+ id="g10973"+ transform="matrix(0.68954077,0,0,1.0074925,14.217977,-7.4348225)">+ <g+ id="g10691">+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10675);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10663"+ width="208.09143"+ height="54.548237"+ x="46.467014"+ y="965.0246" />+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10683);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10677"+ width="129.29953"+ height="55.558392"+ x="256.63965"+ y="964.51953" />+ </g>+ <g+ transform="translate(343.01015,0)"+ id="g10695">+ <rect+ y="965.0246"+ x="46.467014"+ height="54.548237"+ width="208.09143"+ id="rect10697"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10701);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ <rect+ y="964.51953"+ x="256.63965"+ height="55.558392"+ width="129.29953"+ id="rect10699"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10703);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ </g>+ <g+ transform="translate(686,0)"+ id="g10705">+ <rect+ y="965.0246"+ x="46.467014"+ height="54.548237"+ width="208.09143"+ id="rect10707"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10717);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ <rect+ y="964.51953"+ x="256.63965"+ height="55.558392"+ width="129.29953"+ id="rect10709"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10719);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ </g>+ <g+ id="g10711"+ transform="translate(1029.0101,0)">+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10721);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10713"+ width="208.09143"+ height="54.548237"+ x="46.467014"+ y="965.0246" />+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10723);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10715"+ width="129.29953"+ height="55.558392"+ x="256.63965"+ y="964.51953" />+ </g>+ </g>+ <path+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient10997);stroke-width:0.80000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ d="M 227.16371,473.37894 44.326097,973.40445"+ id="path10989"+ inkscape:connector-curvature="0" />+ <path+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12389);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ d="M 350.52293,465.46357 C 222.86936,571.83233 215.18758,787.75295 189.90868,963.46878"+ id="path10991"+ inkscape:connector-curvature="0"+ sodipodi:nodetypes="cc" />+ <path+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient11067);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ d="m 225.26402,555.36715 c 20.15818,137.60669 25.58762,288.18693 -35.35534,410.12193"+ id="path11057"+ inkscape:connector-curvature="0"+ sodipodi:nodetypes="cc" />+ <path+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient11065);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ d="m 349.51278,554.357 c -89.66529,95.12579 -69.8214,258.09851 -70.71068,412.14224"+ id="path11059"+ inkscape:connector-curvature="0"+ sodipodi:nodetypes="cc" />+ <path+ inkscape:connector-curvature="0"+ id="path11237"+ d="m 352.54324,558.39761 c 70.84307,126.40419 56.41235,269.05101 76.77159,405.07117"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12391);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ sodipodi:nodetypes="cc" />+ <path+ sodipodi:nodetypes="cc"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12393);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ d="m 478.81231,558.39761 c -20.40661,132.496 21.89426,269.17249 37.37564,404.06102"+ id="path11241"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path11243"+ d="m 480.83262,560.41792 c 100.09217,116.89753 90.48266,252.74241 178.79699,402.04071"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12395);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ sodipodi:nodetypes="cc" />+ <path+ sodipodi:nodetypes="cc"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12397);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ d="m 604.07123,560.41792 c 69.7221,149.2514 45.35422,277.70823 150.51272,402.04071"+ id="path11245"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path11247"+ d="M 604.07123,560.41792 C 773.88312,685.27297 824.5402,825.80628 897.01546,963.46878"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12399);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ sodipodi:nodetypes="cc" />+ <path+ sodipodi:nodetypes="cc"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient11239);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ d="m 733.37076,559.40777 c 139.50731,143.03779 154.80905,287.61171 254.55843,400.0204"+ id="path11249"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path11251"+ d="m 350.52293,465.46357 c 60.20791,169.02184 -12.38604,332.14138 -68.69037,495.9849"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12401);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ sodipodi:nodetypes="cc" />+ <path+ sodipodi:nodetypes="cc"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12403);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ d="M 479.82246,470.51433 C 402.3894,619.54024 415.34447,790.47843 424.26407,960.43832"+ id="path11253"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path11255"+ d="m 479.82246,470.51433 c 87.39169,173.71093 58.84007,333.28243 39.39595,493.9646"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12405);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ sodipodi:nodetypes="cc" />+ <path+ sodipodi:nodetypes="cc"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12407);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ d="m 604.07122,464.45341 c -28.20629,167.73232 12.56584,332.90988 55.55839,498.00521"+ id="path11257"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path11259"+ d="M 604.07122,464.45341 C 710.73025,632.74802 739.3706,796.45329 755.5941,959.42816"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient12409);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ sodipodi:nodetypes="cc" />+ <path+ sodipodi:nodetypes="cc"+ style="opacity:0.6;fill:none;fill-rule:evenodd;stroke:url(#linearGradient10999);stroke-width:0.8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+ d="M 731.35044,463.44326 893.985,959.42816"+ id="path11261"+ inkscape:connector-curvature="0" />+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:35px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient11569);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="57.873169"+ y="1004.9053"+ id="text11263"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan11265"+ x="57.873169"+ y="1004.9053"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:35px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient11569);fill-opacity:1">Chunk</tspan></text>+ <text+ sodipodi:linespacing="125%"+ id="text11571"+ y="1004.9053"+ x="193.87317"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr;text-anchor:start;fill:url(#linearGradient11785);fill-opacity:1.0;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ xml:space="preserve"><tspan+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr;text-anchor:start;fill:url(#linearGradient11785);fill-opacity:1.0"+ y="1004.9053"+ x="193.87317"+ id="tspan11573"+ sodipodi:role="line">Chunk</tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient11973);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="431.87317"+ y="1004.9053"+ id="text11787"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan11789"+ x="431.87317"+ y="1004.9053"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient11973);fill-opacity:1">Chunk</tspan></text>+ <text+ sodipodi:linespacing="125%"+ id="text11975"+ y="1004.9053"+ x="671.87317"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient12121);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ xml:space="preserve"><tspan+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient12121);fill-opacity:1"+ y="1004.9053"+ x="671.87317"+ id="tspan11977"+ sodipodi:role="line">Chunk</tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient12173);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="911.87317"+ y="1004.9053"+ id="text12123"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan12125"+ x="911.87317"+ y="1004.9053"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient12173);fill-opacity:1">Chunk</tspan></text>+ <text+ sodipodi:linespacing="125%"+ id="text12175"+ y="1004.9053"+ x="297.87317"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:35px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient12249);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ xml:space="preserve"><tspan+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:35px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient12249);fill-opacity:1"+ y="1004.9053"+ x="297.87317"+ id="tspan12177"+ sodipodi:role="line">Chunk</tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:35px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient12297);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="537.87317"+ y="1004.9053"+ id="text12251"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan12253"+ x="537.87317"+ y="1004.9053"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:35px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient12297);fill-opacity:1">Chunk</tspan></text>+ <text+ sodipodi:linespacing="125%"+ id="text12299"+ y="1004.9053"+ x="777.87317"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:35px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient12337);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ xml:space="preserve"><tspan+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:35px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient12337);fill-opacity:1"+ y="1004.9053"+ x="777.87317"+ id="tspan12301"+ sodipodi:role="line">Chunk</tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-weight:normal;font-size:71.43800354px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:0.38805971;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"+ x="16.897184"+ y="1974.3336"+ id="text12491"+ sodipodi:linespacing="125%"+ transform="scale(2.1986526,0.45482402)"><tspan+ sodipodi:role="line"+ id="tspan12493"+ x="16.897184"+ y="1974.3336"><tspan+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:49.1136322px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:0.38805971;"+ id="tspan12495">data reference box</tspan></tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:27.5px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="310.96112"+ y="758.36255"+ id="text14460"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan14462"+ x="310.96112"+ y="758.36255">sample-to-chunk</tspan></text>+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:27.5px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="306.43805"+ y="796.02411"+ id="text14464"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan14466"+ x="306.43805"+ y="796.02411">chunk offset box</tspan></text>+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.60000002;marker-end:url(#marker15518);stroke-miterlimit:4;stroke-dasharray:24, 2;stroke-dashoffset:0"+ d="m 261.20736,623.80122 47.49225,109.68446"+ id="path14468"+ inkscape:connector-curvature="0"+ sodipodi:nodetypes="cc" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:24, 2;stroke-dashoffset:0;stroke-opacity:0.6;marker-end:url(#marker15594)"+ d="m 332.09691,803.37923 20.69764,161.91252"+ id="path14472"+ inkscape:connector-curvature="0"+ sodipodi:nodetypes="cc" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#ff00ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:24, 2;stroke-dashoffset:0;stroke-opacity:0.6;marker-end:url(#marker15508)"+ d="m 259.78423,626.15485 c 16.03073,65.46936 -144.96219,240.48875 69.3923,208.83074"+ id="path14716"+ inkscape:connector-curvature="0"+ sodipodi:nodetypes="cc" />+ <rect+ style="opacity:0.6;fill:#000000;fill-opacity:0;stroke:#ff00ff;stroke-width:1.9069823;stroke-miterlimit:3.5;stroke-dasharray:22.88378754, 1.9069823;stroke-dashoffset:1.8443985;stroke-opacity:1"+ id="rect15328"+ width="243.07732"+ height="28.200623"+ x="301.3685"+ y="736.33148" />+ <rect+ y="774.33148"+ x="301.3685"+ height="28.200623"+ width="243.07732"+ id="rect15632"+ style="opacity:0.6;fill:#000000;fill-opacity:0;stroke:#ff00ff;stroke-width:1.9069823;stroke-miterlimit:3.5;stroke-dasharray:22.88378754, 1.9069823;stroke-dashoffset:1.8443985;stroke-opacity:1" />+ <rect+ y="736.96698"+ x="68.003967"+ height="127.9297"+ width="148.80638"+ id="rect16102"+ style="opacity:0.6;fill:#000000;fill-opacity:0;stroke:#ff00ff;stroke-width:3.17790985;stroke-miterlimit:3.5;stroke-dasharray:38.13491771, 3.17790982;stroke-dashoffset:1.8443985;stroke-opacity:1" />+ <rect+ style="opacity:0.6;fill:#000000;fill-opacity:0;stroke:#ff00ff;stroke-width:3.87848878;stroke-miterlimit:3.5;stroke-dasharray:46.54186579, 3.87848883;stroke-dashoffset:1.8443985;stroke-opacity:1"+ id="rect16104"+ width="970.10577"+ height="29.229124"+ x="32.354256"+ y="875.31726" />+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:27.5px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="688.91504"+ y="648.37811"+ id="text14456"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan14458"+ x="688.91504"+ y="648.37811">time scale = per</tspan></text>+ <g+ id="g16182">+ <path+ style="fill:none;fill-rule:evenodd;stroke:url(#linearGradient13103);stroke-width:3.02537584;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3.5;stroke-dasharray:72.60902197, 3.02537592;stroke-dashoffset:1.8599999;stroke-opacity:1;marker-end:url(#Arrow2Mend)"+ d="m 234.67074,697.90698 657.65431,-1.09455"+ id="path12995"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path16110"+ d="m 306,624.3622 0,74"+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ d="m 380,624.3622 0,74"+ id="path16112"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path16114"+ d="m 456,624.3622 0,74"+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ d="m 532,624.3622 0,74"+ id="path16116"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path16118"+ d="m 608,624.3622 0,74"+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ d="m 684,624.3622 0,74"+ id="path16120"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path16122"+ d="m 758,624.3622 0,74"+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ d="m 834,624.3622 0,74"+ id="path16124"+ inkscape:connector-curvature="0" />+ </g>+ </g>+</svg>
+ doc/data_reference_box.svg view
@@ -0,0 +1,2427 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!-- Created with Inkscape (http://www.inkscape.org/) -->++<svg+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"+ xmlns:dc="http://purl.org/dc/elements/1.1/"+ xmlns:cc="http://creativecommons.org/ns#"+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+ xmlns:svg="http://www.w3.org/2000/svg"+ xmlns="http://www.w3.org/2000/svg"+ xmlns:xlink="http://www.w3.org/1999/xlink"+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+ width="297mm"+ height="210mm"+ viewBox="0 0 1052.3622 744.09448"+ id="svg3709"+ version="1.1"+ inkscape:version="0.91 r13725"+ sodipodi:docname="data_reference_box.svg">+ <defs+ id="defs3711">+ <linearGradient+ id="linearGradient9945"+ osb:paint="solid">+ <stop+ style="stop-color:#000000;stop-opacity:1;"+ offset="0"+ id="stop9947" />+ </linearGradient>+ <linearGradient+ id="linearGradient4154"+ osb:paint="solid">+ <stop+ style="stop-color:#ff5f8d;stop-opacity:1;"+ offset="0"+ id="stop4156" />+ </linearGradient>+ <linearGradient+ id="linearGradient5596"+ osb:paint="solid">+ <stop+ style="stop-color:#1486c3;stop-opacity:1;"+ offset="0"+ id="stop5598" />+ </linearGradient>+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12337"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(492,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient3884"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(492,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12297"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(252,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient3895"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(252,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient12249"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(12,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient3906"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(12,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient12173"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(626,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient3915"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(626,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient12121"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(386,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient3926"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(386,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient11973"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(146,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient3937"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(146,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient11785"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-92,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient3948"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-92,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient11569"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-228,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient3959"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-228,-2)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(-228,-2)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient4144"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(-92,-2)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient4148"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(146,-2)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient4152"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(386,-2)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient4157"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(626,-2)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient4161"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(12,-2)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient4165"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(252,-2)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient4169"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(492,-2)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient4173"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient5549"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(488.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient5865"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(488.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient5545"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(248.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient5876"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(248.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient5541"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(8.9695366,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient5887"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(8.9695366,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient5537"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(622.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient5896"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(622.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient5533"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(382.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient5907"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(382.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient5529"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(142.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient5918"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(142.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient5525"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-95.030466,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient5929"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-95.030466,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient5521"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-231.03047,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient5940"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-231.03047,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient10675"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient10683"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient10701"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient10703"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient10717"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient10719"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient10721"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient10723"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(-231.03047,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient6125"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(-95.030466,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient6129"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(142.96953,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient6133"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(382.96953,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient6137"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(622.96953,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient6141"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(8.9695366,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient6145"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(248.96953,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient6149"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(488.96953,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient6153"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7762"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(488.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7764"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(248.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7766"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(248.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7768"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(8.9695366,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7770"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(8.9695366,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7772"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(622.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7774"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(382.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7776"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(382.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7778"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(142.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7780"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(142.96953,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7782"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-95.030466,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7784"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-95.030466,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7786"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-231.03047,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7788"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-231.03047,-28.725546)"+ x1="288.31458"+ y1="991.90527"+ x2="424.25208"+ y2="991.90527" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7790"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7792"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7794"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7796"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7798"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7800"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient7802"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient7804"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(-231.03047,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient7806"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(-95.030466,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient7809"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(142.96953,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient7812"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(382.96953,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient7815"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(622.96953,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient7818"+ xlink:href="#linearGradient4154"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(8.9695366,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient7821"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(248.96953,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient7824"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ y2="991.90527"+ x2="424.25208"+ y1="991.90527"+ x1="288.31458"+ gradientTransform="translate(488.96953,-28.725546)"+ gradientUnits="userSpaceOnUse"+ id="linearGradient7827"+ xlink:href="#linearGradient5596"+ inkscape:collect="always" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient8866"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient8868"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient8870"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient8872"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient8874"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient8876"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient9002"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient9004"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient9006"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient9008"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient9010"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient9012"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient9014"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(1.0101525,-1.4745979)"+ x1="44.206863"+ y1="993.77332"+ x2="254.79829"+ y2="993.77332" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient4154"+ id="linearGradient9016"+ gradientUnits="userSpaceOnUse"+ gradientTransform="translate(-6,-6.0203247)"+ x1="261.38965"+ y1="998.31903"+ x2="393.18918"+ y2="998.31903" />+ <linearGradient+ inkscape:collect="always"+ xlink:href="#linearGradient5596"+ id="linearGradient9949"+ x1="105.05586"+ y1="612.4408"+ x2="316.17776"+ y2="612.4408"+ gradientUnits="userSpaceOnUse"+ gradientTransform="matrix(0.97435303,0,0,1.9091142,129.10476,-565.86994)" />+ </defs>+ <sodipodi:namedview+ id="base"+ pagecolor="#ffffff"+ bordercolor="#666666"+ borderopacity="1.0"+ inkscape:pageopacity="0.0"+ inkscape:pageshadow="2"+ inkscape:zoom="0.98994949"+ inkscape:cx="633.34229"+ inkscape:cy="401.96717"+ inkscape:document-units="px"+ inkscape:current-layer="layer1"+ showgrid="false"+ inkscape:snap-text-baseline="true"+ inkscape:window-width="1680"+ inkscape:window-height="1034"+ inkscape:window-x="1050"+ inkscape:window-y="331"+ inkscape:window-maximized="0" />+ <metadata+ id="metadata3714">+ <rdf:RDF>+ <cc:Work+ rdf:about="">+ <dc:format>image/svg+xml</dc:format>+ <dc:type+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />+ <dc:title></dc:title>+ </cc:Work>+ </rdf:RDF>+ </metadata>+ <g+ inkscape:label="Layer 1"+ inkscape:groupmode="layer"+ id="layer1"+ transform="translate(0,-308.26772)">+ <g+ id="g8303"+ transform="translate(8,94)">+ <rect+ y="883.58496"+ x="468.16666"+ height="63.433216"+ width="562.44861"+ id="rect9434"+ style="opacity:1;fill:none;fill-opacity:1;stroke:#881800;stroke-width:0.76223844;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter10659)" />+ <g+ transform="matrix(0.59233581,0,0,0.3999725,443.03598,538.14512)"+ style="stroke:#881800;stroke-opacity:1;filter:url(#filter10659)"+ id="g9684">+ <path+ inkscape:connector-curvature="0"+ id="path9436"+ d="m 49.175146,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 58.057688,867.1945 0,151.5229"+ id="path9438"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9440"+ d="m 66.940233,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 75.822772,867.1945 0,151.5229"+ id="path9442"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9444"+ d="m 84.70532,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9446"+ d="m 93.587864,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 102.4704,867.1945 0,151.5229"+ id="path9448"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9450"+ d="m 111.35295,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 120.2355,867.1945 0,151.5229"+ id="path9452"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9454"+ d="m 129.11803,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 138.00058,867.1945 0,151.5229"+ id="path9456"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9458"+ d="m 146.88313,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 155.76567,867.1945 0,151.5229"+ id="path9460"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 164.64819,867.1945 0,151.5229"+ id="path9462"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9464"+ d="m 173.53075,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 182.41329,867.1945 0,151.5229"+ id="path9466"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9468"+ d="m 191.29582,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 200.17837,867.1945 0,151.5229"+ id="path9470"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9472"+ d="m 209.06092,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 217.94346,867.1945 0,151.5229"+ id="path9474"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9476"+ d="m 226.826,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9478"+ d="m 235.70855,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 244.59109,867.1945 0,151.5229"+ id="path9480"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9482"+ d="m 253.47362,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 262.35618,867.1945 0,151.5229"+ id="path9484"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 271.23871,867.1945 0,151.5229"+ id="path9486"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9488"+ d="m 280.12125,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 289.00379,867.1945 0,151.5229"+ id="path9490"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9492"+ d="m 297.88636,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 306.7689,867.1945 0,151.5229"+ id="path9498"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9500"+ d="m 315.65143,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 324.53398,867.1945 0,151.5229"+ id="path9502"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9504"+ d="m 333.4165,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 342.29905,867.1945 0,151.5229"+ id="path9506"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 351.18159,867.1945 0,151.5229"+ id="path9508"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9510"+ d="m 360.06412,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 368.9467,867.1945 0,151.5229"+ id="path9512"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9514"+ d="m 377.82922,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 386.71177,867.1945 0,151.5229"+ id="path9516"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9518"+ d="m 395.59429,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 404.47685,867.1945 0,151.5229"+ id="path9520"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9522"+ d="m 413.35939,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9524"+ d="m 422.24194,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 431.12448,867.1945 0,151.5229"+ id="path9526"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9528"+ d="m 440.00702,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 448.88955,867.1945 0,151.5229"+ id="path9530"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9532"+ d="m 457.7721,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 466.65463,867.1945 0,151.5229"+ id="path9534"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9536"+ d="m 475.53719,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 484.41974,867.1945 0,151.5229"+ id="path9538"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 493.30227,867.1945 0,151.5229"+ id="path9540"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9542"+ d="m 502.18481,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 511.06735,867.1945 0,151.5229"+ id="path9544"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 519.94988,867.1945 0,151.5229"+ id="path9578"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9580"+ d="m 528.83248,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 537.71502,867.1945 0,151.5229"+ id="path9582"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9584"+ d="m 546.59755,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 555.48009,867.1945 0,151.5229"+ id="path9586"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 564.36269,867.1945 0,151.5229"+ id="path9588"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9590"+ d="m 573.24517,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 582.12778,867.1945 0,151.5229"+ id="path9592"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9594"+ d="m 591.01025,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 599.8928,867.1945 0,151.5229"+ id="path9596"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9598"+ d="m 608.77533,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 617.65791,867.1945 0,151.5229"+ id="path9600"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9602"+ d="m 626.54045,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9604"+ d="m 635.42298,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 644.30555,867.1945 0,151.5229"+ id="path9606"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9608"+ d="m 653.18803,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 662.07061,867.1945 0,151.5229"+ id="path9610"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9612"+ d="m 670.95311,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 679.8357,867.1945 0,151.5229"+ id="path9614"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9616"+ d="m 688.71829,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 697.60075,867.1945 0,151.5229"+ id="path9618"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 706.48336,867.1945 0,151.5229"+ id="path9620"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9622"+ d="m 715.36584,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 724.24843,867.1945 0,151.5229"+ id="path9624"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9626"+ d="m 733.13097,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9628"+ d="m 742.01348,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 750.89608,867.1945 0,151.5229"+ id="path9630"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9632"+ d="m 759.77859,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 768.66112,867.1945 0,151.5229"+ id="path9634"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9636"+ d="m 777.54367,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 786.4262,867.1945 0,151.5229"+ id="path9638"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9640"+ d="m 795.30878,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 804.1913,867.1945 0,151.5229"+ id="path9642"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9644"+ d="m 813.07388,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9646"+ d="m 821.95636,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 830.83891,867.1945 0,151.5229"+ id="path9648"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9650"+ d="m 839.72143,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 848.60402,867.1945 0,151.5229"+ id="path9652"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9654"+ d="m 857.4866,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 866.36909,867.1945 0,151.5229"+ id="path9656"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9658"+ d="m 875.25164,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 884.13413,867.1945 0,151.5229"+ id="path9660"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 893.01671,867.1945 0,151.5229"+ id="path9662"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9664"+ d="m 901.89928,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 910.78178,867.1945 0,151.5229"+ id="path9666"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9668"+ d="m 919.66438,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 928.5469,867.1945 0,151.5229"+ id="path9670"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9672"+ d="m 937.42946,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 946.31196,867.1945 0,151.5229"+ id="path9674"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9676"+ d="m 955.19454,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9678"+ d="m 964.0771,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 972.95957,867.1945 0,151.5229"+ id="path9680"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9682"+ d="m 981.84218,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ </g>+ <text+ sodipodi:linespacing="125%"+ id="text10249"+ y="928.30164"+ x="482.20816"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter10659)"+ xml:space="preserve"><tspan+ y="928.30164"+ x="482.20816"+ id="tspan10251"+ sodipodi:role="line">Media-Data http://foo.bar/stream_1.mp4</tspan></text>+ <g+ transform="matrix(0.40819163,0,0,1.0111057,451.63875,-87.46976)"+ id="g10973"+ style="filter:url(#filter10659)">+ <g+ id="g10691">+ <rect+ y="965.0246"+ x="46.467014"+ height="54.548237"+ width="208.09143"+ id="rect10663"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10675);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ <rect+ y="964.51953"+ x="256.63965"+ height="55.558392"+ width="129.29953"+ id="rect10677"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10683);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ </g>+ <g+ id="g10695"+ transform="translate(343.01015,0)">+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10701);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10697"+ width="208.09143"+ height="54.548237"+ x="46.467014"+ y="965.0246" />+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10703);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10699"+ width="129.29953"+ height="55.558392"+ x="256.63965"+ y="964.51953" />+ </g>+ <g+ id="g10705"+ transform="translate(686,0)">+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10717);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10707"+ width="208.09143"+ height="54.548237"+ x="46.467014"+ y="965.0246" />+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10719);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10709"+ width="129.29953"+ height="55.558392"+ x="256.63965"+ y="964.51953" />+ </g>+ <g+ transform="translate(1029.0101,0)"+ id="g10711">+ <rect+ y="965.0246"+ x="46.467014"+ height="54.548237"+ width="208.09143"+ id="rect10713"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10721);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ <rect+ y="964.51953"+ x="256.63965"+ height="55.558392"+ width="129.29953"+ id="rect10715"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient10723);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ </g>+ </g>+ </g>+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:27.5px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+ x="32.934036"+ y="417.79865"+ id="text8472"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan8474"+ x="32.934036"+ y="417.79865">Movie > Track > Media > MediaInfo</tspan><tspan+ sodipodi:role="line"+ x="32.934036"+ y="452.17365"+ id="tspan8476" /><tspan+ sodipodi:role="line"+ x="32.934036"+ y="486.54865"+ id="tspan9969"> > Data Info > Data Reference </tspan><tspan+ sodipodi:role="line"+ x="32.934036"+ y="520.92365"+ id="tspan9951"> [flags </tspan><tspan+ sodipodi:role="line"+ x="32.934036"+ y="555.29865"+ id="tspan9953"> > DataEntry #0 > DataEntryUrl/Urn (External file)</tspan><tspan+ sodipodi:role="line"+ x="32.934036"+ y="589.67365"+ id="tspan9955"> > DataEntry #1 [flag == 0]: </tspan><tspan+ sodipodi:role="line"+ x="32.934036"+ y="624.04865"+ id="tspan9977"> Location = http://foo.bar/stream_0.mp4</tspan><tspan+ sodipodi:role="line"+ x="32.934036"+ y="658.42365"+ id="tspan9959"> > ...</tspan><tspan+ sodipodi:role="line"+ x="32.934036"+ y="692.79865"+ id="tspan9961"> > DataEntry #N > DataEntryUrl (Media embeded in file) </tspan><tspan+ sodipodi:role="line"+ x="32.934036"+ y="727.17365"+ id="tspan9973"> [flag == 1]: The box must be completely </tspan><tspan+ sodipodi:role="line"+ x="32.934036"+ y="761.54865"+ id="tspan9979"> empty, i.e 0 bytes follow the header</tspan><tspan+ sodipodi:role="line"+ x="32.934036"+ y="795.92365"+ id="tspan9963" /><tspan+ sodipodi:role="line"+ x="32.934036"+ y="830.29865"+ id="tspan9965" /><tspan+ sodipodi:role="line"+ x="32.934036"+ y="864.67365"+ id="tspan9967"> > SampleTable</tspan></text>+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:#881800;stroke-width:0.76223844;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter10659)"+ id="rect9434-7"+ width="562.44861"+ height="63.433216"+ x="482.65918"+ y="904.23938" />+ <g+ id="g9684-6"+ style="stroke:#881800;stroke-opacity:1;filter:url(#filter10659)"+ transform="matrix(0.59233581,0,0,0.3999725,457.5285,558.79955)">+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 49.175146,867.1945 0,151.5229"+ id="path9436-8"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9438-9"+ d="m 58.057688,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 66.940233,867.1945 0,151.5229"+ id="path9440-0"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9442-6"+ d="m 75.822772,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 84.70532,867.1945 0,151.5229"+ id="path9444-8"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 93.587864,867.1945 0,151.5229"+ id="path9446-7"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9448-9"+ d="m 102.4704,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 111.35295,867.1945 0,151.5229"+ id="path9450-0"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9452-3"+ d="m 120.2355,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 129.11803,867.1945 0,151.5229"+ id="path9454-3"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9456-3"+ d="m 138.00058,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 146.88313,867.1945 0,151.5229"+ id="path9458-7"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9460-3"+ d="m 155.76567,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9462-2"+ d="m 164.64819,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 173.53075,867.1945 0,151.5229"+ id="path9464-6"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9466-5"+ d="m 182.41329,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 191.29582,867.1945 0,151.5229"+ id="path9468-2"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9470-6"+ d="m 200.17837,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 209.06092,867.1945 0,151.5229"+ id="path9472-5"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9474-8"+ d="m 217.94346,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 226.826,867.1945 0,151.5229"+ id="path9476-7"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 235.70855,867.1945 0,151.5229"+ id="path9478-9"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9480-6"+ d="m 244.59109,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 253.47362,867.1945 0,151.5229"+ id="path9482-0"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9484-4"+ d="m 262.35618,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9486-1"+ d="m 271.23871,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 280.12125,867.1945 0,151.5229"+ id="path9488-0"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9490-4"+ d="m 289.00379,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 297.88636,867.1945 0,151.5229"+ id="path9492-8"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9498-7"+ d="m 306.7689,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 315.65143,867.1945 0,151.5229"+ id="path9500-0"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9502-8"+ d="m 324.53398,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 333.4165,867.1945 0,151.5229"+ id="path9504-6"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9506-2"+ d="m 342.29905,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9508-4"+ d="m 351.18159,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 360.06412,867.1945 0,151.5229"+ id="path9510-7"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9512-9"+ d="m 368.9467,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 377.82922,867.1945 0,151.5229"+ id="path9514-3"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9516-9"+ d="m 386.71177,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 395.59429,867.1945 0,151.5229"+ id="path9518-2"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9520-8"+ d="m 404.47685,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 413.35939,867.1945 0,151.5229"+ id="path9522-3"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 422.24194,867.1945 0,151.5229"+ id="path9524-0"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9526-1"+ d="m 431.12448,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 440.00702,867.1945 0,151.5229"+ id="path9528-7"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9530-8"+ d="m 448.88955,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 457.7721,867.1945 0,151.5229"+ id="path9532-9"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9534-1"+ d="m 466.65463,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 475.53719,867.1945 0,151.5229"+ id="path9536-5"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9538-4"+ d="m 484.41974,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9540-9"+ d="m 493.30227,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 502.18481,867.1945 0,151.5229"+ id="path9542-2"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9544-5"+ d="m 511.06735,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9578-7"+ d="m 519.94988,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 528.83248,867.1945 0,151.5229"+ id="path9580-4"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9582-9"+ d="m 537.71502,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 546.59755,867.1945 0,151.5229"+ id="path9584-9"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9586-4"+ d="m 555.48009,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9588-5"+ d="m 564.36269,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 573.24517,867.1945 0,151.5229"+ id="path9590-9"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9592-3"+ d="m 582.12778,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 591.01025,867.1945 0,151.5229"+ id="path9594-5"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9596-7"+ d="m 599.8928,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 608.77533,867.1945 0,151.5229"+ id="path9598-0"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9600-8"+ d="m 617.65791,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 626.54045,867.1945 0,151.5229"+ id="path9602-1"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 635.42298,867.1945 0,151.5229"+ id="path9604-9"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9606-9"+ d="m 644.30555,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 653.18803,867.1945 0,151.5229"+ id="path9608-7"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9610-8"+ d="m 662.07061,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 670.95311,867.1945 0,151.5229"+ id="path9612-2"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9614-5"+ d="m 679.8357,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 688.71829,867.1945 0,151.5229"+ id="path9616-3"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9618-4"+ d="m 697.60075,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9620-9"+ d="m 706.48336,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 715.36584,867.1945 0,151.5229"+ id="path9622-0"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9624-2"+ d="m 724.24843,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 733.13097,867.1945 0,151.5229"+ id="path9626-0"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 742.01348,867.1945 0,151.5229"+ id="path9628-1"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9630-9"+ d="m 750.89608,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 759.77859,867.1945 0,151.5229"+ id="path9632-6"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9634-2"+ d="m 768.66112,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 777.54367,867.1945 0,151.5229"+ id="path9636-1"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9638-2"+ d="m 786.4262,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 795.30878,867.1945 0,151.5229"+ id="path9640-0"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9642-7"+ d="m 804.1913,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 813.07388,867.1945 0,151.5229"+ id="path9644-3"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 821.95636,867.1945 0,151.5229"+ id="path9646-1"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9648-1"+ d="m 830.83891,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 839.72143,867.1945 0,151.5229"+ id="path9650-9"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9652-0"+ d="m 848.60402,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 857.4866,867.1945 0,151.5229"+ id="path9654-5"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9656-6"+ d="m 866.36909,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 875.25164,867.1945 0,151.5229"+ id="path9658-7"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9660-7"+ d="m 884.13413,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ inkscape:connector-curvature="0"+ id="path9662-4"+ d="m 893.01671,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 901.89928,867.1945 0,151.5229"+ id="path9664-0"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9666-6"+ d="m 910.78178,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 919.66438,867.1945 0,151.5229"+ id="path9668-4"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9670-7"+ d="m 928.5469,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 937.42946,867.1945 0,151.5229"+ id="path9672-4"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9674-8"+ d="m 946.31196,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 955.19454,867.1945 0,151.5229"+ id="path9676-5"+ inkscape:connector-curvature="0" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 964.0771,867.1945 0,151.5229"+ id="path9678-8"+ inkscape:connector-curvature="0" />+ <path+ inkscape:connector-curvature="0"+ id="path9680-2"+ d="m 972.95957,867.1945 0,151.5229"+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1" />+ <path+ style="fill:none;fill-rule:evenodd;stroke:#881800;stroke-width:0.37528634;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:0.75057268, 0.37528634;stroke-dashoffset:0;stroke-opacity:1"+ d="m 981.84218,867.1945 0,151.5229"+ id="path9682-6"+ inkscape:connector-curvature="0" />+ </g>+ <text+ xml:space="preserve"+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter10659)"+ x="496.70068"+ y="948.95605"+ id="text10249-0"+ sodipodi:linespacing="125%"><tspan+ sodipodi:role="line"+ id="tspan10251-6"+ x="496.70068"+ y="948.95605">Media-Data http://foo.bar/stream_0.mp4</tspan></text>+ <g+ style="filter:url(#filter10659)"+ id="g10973-6"+ transform="matrix(0.40819163,0,0,1.0111057,466.13127,-66.81533)">+ <g+ id="g10691-4">+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient9002);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10663-6"+ width="208.09143"+ height="54.548237"+ x="46.467014"+ y="965.0246" />+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient9004);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10677-2"+ width="129.29953"+ height="55.558392"+ x="256.63965"+ y="964.51953" />+ </g>+ <g+ transform="translate(343.01015,0)"+ id="g10695-8">+ <rect+ y="965.0246"+ x="46.467014"+ height="54.548237"+ width="208.09143"+ id="rect10697-9"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient9006);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ <rect+ y="964.51953"+ x="256.63965"+ height="55.558392"+ width="129.29953"+ id="rect10699-6"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient9008);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ </g>+ <g+ transform="translate(686,0)"+ id="g10705-0">+ <rect+ y="965.0246"+ x="46.467014"+ height="54.548237"+ width="208.09143"+ id="rect10707-7"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient9010);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ <rect+ y="964.51953"+ x="256.63965"+ height="55.558392"+ width="129.29953"+ id="rect10709-0"+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient9012);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496" />+ </g>+ <g+ id="g10711-1"+ transform="translate(1029.0101,0)">+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient9014);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10713-0"+ width="208.09143"+ height="54.548237"+ x="46.467014"+ y="965.0246" />+ <rect+ style="opacity:1;fill:none;fill-opacity:1;stroke:url(#linearGradient9016);stroke-width:2.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.99701496"+ id="rect10715-1"+ width="129.29953"+ height="55.558392"+ x="256.63965"+ y="964.51953" />+ </g>+ </g>+ <rect+ style="fill:none;stroke:url(#linearGradient9949);stroke-width:1.3638736"+ id="rect9143"+ width="205.70724"+ height="283.48898"+ x="231.46625"+ y="461.60492" />+ </g>+</svg>
+ doc/trun.markDown view
@@ -0,0 +1,43 @@++# Contents of a `trun` box+++ 0000 047c+ 00000060: 7472 756e 0000 0701 0000 005e 0000 04c8+ 00000070: 0000 0400 0000 0095 0200 0000 0000 0400+ 00000080: 0000 0076 0200 0000 0000 0400 0000 008c+ 00000090: 0200 0000 0000 0400 0000 008b 0200 0000+ 000000a0: 0000 0400 0000 0094 0200 0000 0000 0400+ 000000b0: 0000 007a 0200 0000 0000 0400 0000 0087+ 000000c0: 0200 0000 0000 0400 0000 0099 0200 0000+ 000000d0: 0000 0400 0000 0095 0200 0000 0000 0400++## Box header++ 0x0000047c size+ 0x7472 756e 'trun'+ 0x00 version+ 0x000701 flags+ 0x0000005e sample_count = 94+ 0x000004c8 data_offset++## A closer look at a single entry++ 0x00000400 sample duration+ 0x00000095 sample size+ 0x02000000 sample flags+++### Sample flags++ 0 2 0 0 0 0 0 0 (hex)+ 0000 0010 0000 0000 0000 0000 0000 0000 (binary)++ 0000 -> 4 bits reserved = 0+ 00 -> is_leading+ 10 -> sample_depends+ 00 -> sample_is_depended_on+ 00 -> sample has redundancy+ 000 -> padding+ 0 -> non sync+ 0000 0000 0000 0000 degradation priority
+ isobmff.cabal view
@@ -0,0 +1,281 @@+name: isobmff+version: 0.13.0.0+synopsis: A parser and generator for the ISO-14496-12/14 base media file format+description: Please see README.md+homepage: https://github.com/sheyll/isobmff#readme+license: BSD3+license-file: LICENSE+author: Sven Heyll+maintainer: sven.heyll@lindenbaum.eu+copyright: 2016-2019 Sven Heyll, Lindenbaum GmbH+category: Codec+build-type: Simple+extra-source-files: README.md+ , CHANGELOG.md+ , stack.yaml+ , .travis.yml+ , doc/basic-structure.svg+ , doc/data_reference_box.svg+ , doc/ES_Descriptor.markDown+ , doc/trun.markDown+ , .gitignore+cabal-version: >=1.10++flag tracing+ description: Build with trace output enabled+ default: True++flag fullbenchmarks+ description: Enable the compilation of all benchmarks, takes a long time to compile+ default: True++flag complextests+ description: Enable the compilation of all unit tests, even those, that take a long time to compile+ default: True++library+ hs-source-dirs: src+ exposed-modules: Data.ByteString.IsoBaseFileFormat.Box+ , Data.ByteString.IsoBaseFileFormat.Boxes+ , Data.ByteString.IsoBaseFileFormat.Boxes.AudioSampleEntry+ , Data.ByteString.IsoBaseFileFormat.Boxes.ChunkOffset+ , Data.ByteString.IsoBaseFileFormat.Boxes.DataEntryUrl+ , Data.ByteString.IsoBaseFileFormat.Boxes.DataEntryUrn+ , Data.ByteString.IsoBaseFileFormat.Boxes.DataInformation+ , Data.ByteString.IsoBaseFileFormat.Boxes.DataReference+ , Data.ByteString.IsoBaseFileFormat.Boxes.FileType+ , Data.ByteString.IsoBaseFileFormat.Boxes.Handler+ , Data.ByteString.IsoBaseFileFormat.Boxes.HintMediaHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.HintSampleEntry+ , Data.ByteString.IsoBaseFileFormat.Boxes.Media+ , Data.ByteString.IsoBaseFileFormat.Boxes.MediaData+ , Data.ByteString.IsoBaseFileFormat.Boxes.MediaHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.MediaInformation+ , Data.ByteString.IsoBaseFileFormat.Boxes.MetaDataSampleEntry+ , Data.ByteString.IsoBaseFileFormat.Boxes.Movie+ , Data.ByteString.IsoBaseFileFormat.Boxes.MovieExtends+ , Data.ByteString.IsoBaseFileFormat.Boxes.MovieExtendsHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.MovieFragment+ , Data.ByteString.IsoBaseFileFormat.Boxes.MovieFragmentHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.MovieHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.NullMediaHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.ProgressiveDownloadInformation+ , Data.ByteString.IsoBaseFileFormat.Boxes.Language+ , Data.ByteString.IsoBaseFileFormat.Boxes.SampleDescription+ , Data.ByteString.IsoBaseFileFormat.Boxes.SampleEntry+ , Data.ByteString.IsoBaseFileFormat.Boxes.SampleSize+ , Data.ByteString.IsoBaseFileFormat.Boxes.SampleTable+ , Data.ByteString.IsoBaseFileFormat.Boxes.SampleToChunk+ , Data.ByteString.IsoBaseFileFormat.Boxes.SegmentType+ , Data.ByteString.IsoBaseFileFormat.Boxes.Skip+ , Data.ByteString.IsoBaseFileFormat.Boxes.SoundMediaHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.SpecificMediaHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.TimeToSample+ , Data.ByteString.IsoBaseFileFormat.Boxes.Track+ , Data.ByteString.IsoBaseFileFormat.Boxes.TrackExtends+ , Data.ByteString.IsoBaseFileFormat.Boxes.TrackFragBaseMediaDecodeTime+ , Data.ByteString.IsoBaseFileFormat.Boxes.TrackFragment+ , Data.ByteString.IsoBaseFileFormat.Boxes.TrackFragmentHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.TrackHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.TrackRun+ , Data.ByteString.IsoBaseFileFormat.Boxes.VideoMediaHeader+ , Data.ByteString.IsoBaseFileFormat.Boxes.VisualSampleEntry+ , Data.ByteString.IsoBaseFileFormat.Brands.Dash+ , Data.ByteString.IsoBaseFileFormat.MediaFile+ , Data.ByteString.IsoBaseFileFormat.ReExports+ , Data.ByteString.IsoBaseFileFormat.Util.BoxFields+ , Data.ByteString.IsoBaseFileFormat.Util.FullBox+ , Data.ByteString.IsoBaseFileFormat.Util.Time+ , Data.ByteString.IsoBaseFileFormat.Util.TypeLayout+ , Data.ByteString.IsoBaseFileFormat.Util.Versioned+ , Data.ByteString.Mp4.Boxes.AudioSpecificConfig+ , Data.ByteString.Mp4.Boxes.BaseDescriptor+ , Data.ByteString.Mp4.Boxes.DecoderConfigDescriptor+ , Data.ByteString.Mp4.Boxes.DecoderSpecificInfo+ , Data.ByteString.Mp4.Boxes.ElementaryStreamDescriptor+ , Data.ByteString.Mp4.Boxes.Expandable+ , Data.ByteString.Mp4.Boxes.Mp4AudioSampleEntry+ , Data.ByteString.Mp4.Boxes.SyncLayerConfigDescriptor+ , Data.ByteString.Mp4.AudioStreaming+ , Data.ByteString.Mp4.AacInitSegment+ , Data.Kind.Extra+ , Data.Type.BitRecords+ , Data.Type.BitRecords.Arithmetic+ , Data.Type.BitRecords.Assert+ , Data.Type.BitRecords.Builder.BitBuffer+ , Data.Type.BitRecords.Builder.LazyByteStringBuilder+ , Data.Type.BitRecords.Core+ , Data.Type.BitRecords.Enum+ , Data.Type.BitRecords.SizedString+ , Data.Type.BitRecords.Sized+ build-depends: base >= 4.12 && < 5+ , bytestring+ -- , type-list+ , data-default+ , vector+ , singletons >= 2.5 && < 2.6+ , tagged+ , time+ , function-builder >= 0.1 && < 0.2+ , text+ , type-spec >= 0.3+ , mtl+ , pretty-types >= 0.2.3 && < 0.4+ , template-haskell+ default-language: Haskell2010+ ghc-options: -Wall -funbox-strict-fields -fno-warn-unused-do-bind -fprint-explicit-kinds+ if flag(tracing)+ cpp-options: -DTRACING+ else+ cpp-options: -DNTRACING++ default-extensions: BangPatterns+ , ConstraintKinds+ , CPP+ , DataKinds+ , DefaultSignatures+ , DeriveDataTypeable+ , DeriveFunctor+ , DeriveGeneric+ , DuplicateRecordFields+ , FlexibleInstances+ , FlexibleContexts+ , FunctionalDependencies+ , GADTs+ , GeneralizedNewtypeDeriving+ , KindSignatures+ , MultiParamTypeClasses+ , NoStarIsType+ , OverloadedStrings+ , QuasiQuotes+ , RecordWildCards+ , RankNTypes+ , ScopedTypeVariables+ , StandaloneDeriving+ , TemplateHaskell+ , TupleSections+ , TypeApplications+ , TypeFamilies+ , TypeInType+ , TypeOperators+ , TypeSynonymInstances+ , UnicodeSyntax+test-suite spec+ type: exitcode-stdio-1.0+ ghc-options: -Wall+ hs-source-dirs: spec+ default-language: Haskell2010+ main-is: Spec.hs+ other-modules: BitRecordsSpec+ , BoxFieldsSpec+ , BoxSpec+ , DataReferenceSpec+ , ElementaryStreamDescriptorSpec+ , EnumSpec+ , ExpandableSpec+ , Mp4AudioSampleEntrySpec+ , MediaFileSpec+ , Mp4AudioFileSpec+ , Mp4AudioSegmentSpec+ , TypeLayoutSpec+ build-depends: base >= 4.12 && < 5+ , bytestring+ , hspec+ , isobmff+ , binary+ , text+ , type-spec+ , mtl+ , QuickCheck+ , tagged+ , pretty-types >= 0.2.1 && < 0.4+ default-extensions: BangPatterns+ , ConstraintKinds+ , CPP+ , DataKinds+ , DefaultSignatures+ , DeriveDataTypeable+ , DeriveFunctor+ , DeriveGeneric+ , FlexibleInstances+ , FlexibleContexts+ , FunctionalDependencies+ , GADTs+ , GeneralizedNewtypeDeriving+ , KindSignatures+ , MultiParamTypeClasses+ , OverloadedStrings+ , QuasiQuotes+ , RecordWildCards+ , RankNTypes+ , NoStarIsType+ , ScopedTypeVariables+ , StandaloneDeriving+ , TemplateHaskell+ , TupleSections+ , TypeApplications+ , TypeFamilies+ , TypeInType+ , TypeOperators+ , TypeSynonymInstances+ , UnicodeSyntax+ ghc-options: -Wall +RTS -A256m -n2m -RTS+ -fwarn-unused-binds -fno-warn-unused-do-bind -fno-warn-missing-signatures -fprint-explicit-kinds+ if flag(complextests)+ cpp-options: -DCOMPLEXTESTS+ else+ cpp-options: -DNCOMPLEXTESTS++benchmark bit-records+ type: exitcode-stdio-1.0+ hs-source-dirs: benchmarks/bit-records+ main-is: Main.hs+ build-depends: base >= 4.12 && < 5+ , isobmff+ , binary+ , bytestring+ , criterion+ , tagged+ , type-spec >= 0.3+ default-language: Haskell2010+ default-extensions: BangPatterns+ , ConstraintKinds+ , CPP+ , DataKinds+ , DefaultSignatures+ , DeriveDataTypeable+ , DeriveFunctor+ , DeriveGeneric+ , FlexibleInstances+ , FlexibleContexts+ , FunctionalDependencies+ , GADTs+ , GeneralizedNewtypeDeriving+ , KindSignatures+ , MultiParamTypeClasses+ , NoStarIsType+ , OverloadedStrings+ , QuasiQuotes+ , RecordWildCards+ , RankNTypes+ , ScopedTypeVariables+ , StandaloneDeriving+ , TemplateHaskell+ , TupleSections+ , TypeApplications+ , TypeFamilies+ , TypeInType+ , TypeOperators+ , TypeSynonymInstances+ , UnicodeSyntax+ ghc-options: +RTS -A256m -n2m -RTS+ -Wall -fwarn-unused-binds+ -fno-warn-unused-do-bind -fprint-explicit-kinds+ if flag(fullbenchmarks)+ cpp-options: -DFULLBENCHMARKS+ else+ cpp-options: -DNFULLBENCHMARKS++source-repository head+ type: git+ location: https://github.com/sheyll/isobmff
+ spec/BitRecordsSpec.hs view
@@ -0,0 +1,471 @@+{-# LANGUAGE UndecidableInstances #-}+module BitRecordsSpec (spec) where++import Data.Bits+import Data.ByteString.Builder+import Data.Proxy+import Data.Type.BitRecords+import Data.Type.Equality ()+import Data.Kind.Extra+import GHC.TypeLits+import Prelude hiding ((.), id)+import Test.Hspec+import Test.QuickCheck (property, Arbitrary(..), choose)+import Test.TypeSpecCrazy+import Text.Printf++basicsSpec :: Spec+basicsSpec = do+ describe "Maybe, Lists, Either, Bool, 'True, 'False, FlagJust, FlagNothing" $ do+ let checkFlagJust ::+ "Type Level Bool, 'True, 'False, FlagJust, FlagNothing Accessors"+ ##################################################################++ "The record size works"+ ~~~~~~~~~~~~~~~~~~~~~~~~+ 1 `ShouldBe` BitRecordFieldSize (FlagJust 'Nothing)+ -* 1 `ShouldBe` BitRecordFieldSize (FlagJust ('Just "Blah"))+ -* 1 `ShouldBe` BitRecordFieldSize (FlagNothing ('Just "Blah"))+ -- TODO reenable tests+ -- -* 32 `ShouldBe` BitRecordSize (ToBitRecord ('Just FieldU32))+ -- -* 0 `ShouldBe` BitRecordSize (ToBitRecord '[])+ -- -* 10 `ShouldBe` BitRecordSize (ToBitRecord '[Field 10])+ -- -* 25 `ShouldBe` BitRecordSize (ToBitRecord '[Field 10, Field 15])+ -- -* 1 `ShouldBe` BitRecordSize (ToBitRecord Bool)+ -- -* 1 `ShouldBe` BitRecordSize (ToBitRecord 'True)+ -- -* 1 `ShouldBe` BitRecordSize (ToBitRecord 'False)+ checkFlagJust = Valid+ runIO $ print checkFlagJust+ describe "bitStringBuilder" $ do+ describe "Just x" $ it "writes x" $+ bitStringPrinter (Proxy :: Proxy (OptionalRecord ('Just ('BitRecordMember (Flag := 'True))))) `shouldBe` "<< 80 >>"+ describe "Nothing" $ it "writes nothing" $+ bitStringPrinter (Proxy :: Proxy (OptionalRecord ('Nothing))) `shouldBe` "<< >>"+ -- describe "'[]" $ it "writes nothing" $+ -- bitStringPrinter (Proxy :: Proxy (BitRecordOfList (Fun1 RecordField) ('[]))) `shouldBe` "<< >>"+ -- describe "'[x1, x2]" $ it "writes x1 then x2" $+ -- bitStringPrinter (Proxy :: Proxy (BitRecordOfList (Fun1 RecordField) ('[FieldU8 := 1, FieldU8 := 2]))) `shouldBe` "<< 01 02 >>"+ describe "'True" $ it "writes a single bit with a 1" $+ bitStringPrinter (Proxy :: Proxy (RecordField (Flag := 'True))) `shouldBe` "<< 80 >>"+ describe "'False" $ it "writes a single bit with a 0" $+ bitStringPrinter (Proxy :: Proxy (RecordField (Flag := 'False))) `shouldBe` "<< 00 >>"+ describe "@: labelled fields" $ do+ it "writes them ..." $+ let fld = Proxy @(Eval (RecordField ( "foo" @: FlagJust 'Nothing )))+ in bitStringPrinter fld `shouldBe` "<< 00 >>"+ describe "FlagJust" $ do+ it "writes a single bit '1' for a 'Just ...' parameter" $+ bitStringPrinter (Proxy :: Proxy (RecordField (FlagJust ('Just "test")))) `shouldBe` "<< 80 >>"+ it "writes a single bit '0' for a 'Nothing' parameter" $+ bitStringPrinter (Proxy :: Proxy (RecordField (FlagJust 'Nothing))) `shouldBe` "<< 00 >>"+ describe "FlagNothing" $ do+ it "writes a single bit '0' for a 'Just ...' parameter" $+ bitStringPrinter (Proxy :: Proxy (RecordField (FlagNothing ('Just "test")))) `shouldBe` "<< 00 >>"+ it "writes a single bit '1' for a 'Nothing' parameter" $+ bitStringPrinter (Proxy :: Proxy (RecordField (FlagNothing 'Nothing))) `shouldBe` "<< 80 >>"+ -- TODO reenable showRecord tests+ -- describe "showRecord" $ do+ -- describe "Maybe" $ do+ -- it "prints 'Just x'" $+ -- showRecord (Proxy :: Proxy (ToBitRecord ('Just 'True))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'True"+ -- it "prints nothing for 'Nothing'" $+ -- showRecord (Proxy :: Proxy (ToBitRecord ('Nothing))) `shouldBe` ""+ -- describe "List" $ do+ -- it "prints '[x]'" $+ -- showRecord (Proxy :: Proxy (ToBitRecord ('[ 'True ]))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'True"+ -- it "prints '[x1, x2, x3]'" $+ -- showRecord (Proxy :: Proxy (ToBitRecord ('[ Bool, Bool, Bool ]))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\nField:\n Demote Rep: Bool\n Bits: 1\nField:\n Demote Rep: Bool\n Bits: 1"+ -- it "prints nothing for '[]'" $+ -- showRecord (Proxy :: Proxy (ToBitRecord ('[]))) `shouldBe` ""+ -- describe "Bool" $ do+ -- it "prints 'True" $+ -- showRecord (Proxy :: Proxy (ToBitRecord ('True))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'True"+ -- it "prints 'False" $+ -- showRecord (Proxy :: Proxy (ToBitRecord ('False))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'False"+ -- it "prints a Bool" $+ -- showRecord (Proxy :: Proxy (ToBitRecord Bool)) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1"+ -- describe "FlagJust" $ do+ -- it "prints a 'FlagJust 'Just ..'" $+ -- showRecord (Proxy :: Proxy (ToBitRecord (FlagJust ('Just "123")))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'True"+ -- it "prints a 'FlagJust 'Nothing'" $+ -- showRecord (Proxy :: Proxy (ToBitRecord (FlagJust 'Nothing))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'False"+ -- describe "FlagNothing" $ do+ -- it "prints 'FlagNothing 'Just ..'" $+ -- showRecord (Proxy :: Proxy (ToBitRecord (FlagNothing ('Just "123")))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'False"+ -- it "prints a 'FlagNothing 'Nothing'" $+ -- showRecord (Proxy :: Proxy (ToBitRecord (FlagNothing 'Nothing))) `shouldBe` "Field:\n Demote Rep: Bool\n Bits: 1\n Static Value: 'True"++#ifdef COMPLEXTESTS+arraySpec :: SpecWith ()+arraySpec =+ describe "RecArray" $ do+ describe "level record accessors" $ do+ let checkArrayRec ::+ "BitRecord accessors involving RecArray"+ #######################################++ "The record size works"+ ~~~~~~~~~~~~~~~~~~~~~~~~+ 1 `ShouldBe` BitRecordSize (Eval (RecArray ('BitRecordMember Flag) 1))+ -* 91 `ShouldBe` BitRecordSize (Eval (("foo" @: Flag .+. FieldU8) ^^ 10) :+. Flag)+ -* 91 `ShouldBe` BitRecordSize (Eval (RecArray ("foo" @: Flag .+. FieldU8) 10) :+. Flag)+ checkArrayRec = Valid+ runIO $ print checkArrayRec+ describe "showRecord" $+ it "appends its body n times" $+ let expected = "utf-8(40) := <<hello>> [5 Bytes]\nutf-8(40) := <<hello>> [5 Bytes]\nutf-8(40) := <<hello>> [5 Bytes]\nutf-8(40) := <<hello>> [5 Bytes]\nutf-8(40) := <<hello>> [5 Bytes]"+ actual = showARecord (Proxy @ (('BitRecordMember [utf8|hello|] ^^ 5)))+ in actual `shouldBe` expected+ describe "bitStringBuilder" $+ it "writes its contents n times to the builder" $+ let actual = bitStringPrinter (Proxy :: Proxy (('BitRecordMember (Field 24 := 0x010203) ^^ 4)))+ expected = "<< 01 02 03 01 02 03 01 02 03 01 02 03 >>"+ in actual `shouldBe` expected++sizedSpec =+ describe "Sized" $ do+ describe "TypeChecks" $+ let+ checkSized ::+ "Sized"+ #########++ "SizedString"+ ~~~~~~~~~~~~~~~+ 88 `ShouldBe` BitRecordFieldSize [utf8|Hello World|]+ -* 104 `ShouldBe` BitRecordSize (Eval (RecordField [utf8|Heλλo World|]))++ -/-++ "Sized BitRecord Members"+ ~~~~~~~~~~~~~~~~~~~~~~~~+ 8 `ShouldBe` BitRecordSize (Eval (Sized8 'EmptyBitRecord))+ -* 9 `ShouldBe` BitRecordSize (Eval (Sized8 ('BitRecordMember Flag)))+ -* 0 `ShouldBe` SizeFieldValue 'EmptyBitRecord+ -* 1 `ShouldBe` SizeFieldValue ('BitRecordMember Flag)++ -/-++ "SizedField"+ ~~~~~~~~~~~~+ 9 `ShouldBe` BitRecordSize (Eval (SizedField8 Flag))+ -* 1 `ShouldBe` SizeFieldValue Flag++ -- TODO add more Sized tests, especially for SizedField+ checkSized = Valid+ in runIO $ print checkSized+ describe "showRecord" $ do+ describe "SizedString" $+ it "renders a string containing wide utf-8 characters to a header containing the number of chars and the actual string" $+ showARecord (Proxy :: Proxy (RecordField [utf8|Heλλo World!|])) `shouldBe` "utf-8(112) := <<He\955\955o World!>> [14 Bytes]"+ describe "Sized SizeField16 SizedString" $+ it "renders the number bytes not chars as the size field value" $+ showARecord (Proxy :: Proxy (SizedField16 [utf8|Heλλo World!|])) `shouldBe` "size: U16 := hex: 000e (dec: 14)\nutf-8(112) := <<He\955\955o World!>> [14 Bytes]"+ describe "bitStringBuilder" $ do+ describe "no length prefix" $+ it "renders no size prefix and the string as UTF-8 bytes" $+ bitStringPrinter (Proxy :: Proxy (RecordField [utf8|ABC|]))+ `shouldBe`+ "<< 41 42 43 >>"+ describe "8-bit length prefix" $+ it "renders a single byte size prefix and the string as UTF-8 bytes" $+ bitStringPrinter (Proxy :: Proxy (SizedField8 [utf8|ABC|]))+ `shouldBe`+ "<< 03 41 42 43 >>"+ describe "16-bit length prefix" $+ it "renders a big endian 16 bit size prefix and the string as UTF-8 bytes" $+ bitStringPrinter (Proxy :: Proxy (SizedField16 [utf8|ABC|]))+ `shouldBe`+ "<< 00 03 41 42 43 >>"+ describe "32-bit length prefix" $+ it "renders a big endian 32 bit size prefix and the string as UTF-8 bytes" $+ bitStringPrinter (Proxy :: Proxy (SizedField32 [utf8|ABC|]))+ `shouldBe`+ "<< 00 00 00 03 41 42 43 >>"+ describe "64-bit length prefix" $+ it "renders a big endian 64 bit size prefix and the string as UTF-8 bytes" $+ bitStringPrinter (Proxy :: Proxy (SizedField64 [utf8|ABC|]))+ `shouldBe`+ "<< 00 00 00 00 00 00 00 03 41 42 43 >>"++type TestRecAligned =+ "bar" @: Field 8 .+:+ Field 8 := 0 .+:+ "baz" @: Field 8 .+:+ Field 32 := 0 .+:+ "foo" @: Field 8 .+:+ Field 8 := 0 .+:+ "oof" @: Field 8 .+:+ Field 8 := 0 .+.+ "rab" @: Field 8++checkTestRecAligned+ :: Expect '[ ShouldBe 96 (BitRecordSize TestRecAligned) ]+checkTestRecAligned = Valid++type TestRecUnAligned =+ "bar" @: Field 8 .+:+ Field 8 := 0 .+:+ "baz" @: Field 7 .+:+ Field 32 := 0 .+:+ "foo" @: Field 8 .+.+ Field 8 := 0xfe++checkTestRecUnAligned+ :: Expect '[ ShouldBe 71 (BitRecordSize TestRecUnAligned) ]+checkTestRecUnAligned = Valid++testTakeLastN ::+ "Taking the last n elements of a list" #######################################++ TakeLastN 0 '[1,2,3] `ShouldBe` ('[] :: [Nat])+ -* TakeLastN 1 '[1,2,3] `ShouldBe` '[3]+ -* TakeLastN 2 '[1,2,3] `ShouldBe` '[2,3]+ -* TakeLastN 5 '[1,2,3] `ShouldBe` '[1,2,3]++testTakeLastN = Valid++testRem+ :: Expect '[ Mod 0 3 `ShouldBe` 0+ , Mod 1 3 `ShouldBe` 1+ , Mod 2 3 `ShouldBe` 2+ , Mod 3 3 `ShouldBe` 0+ , Mod 4 3 `ShouldBe` 1+ , Mod 5 3 `ShouldBe` 2+ , Mod 6 3 `ShouldBe` 0+ ]+testRem = Valid++testRemPow2+ ::+ "ModPow2"+ #########++ "Remainder of '1'"+ ~~~~~~~~~~~~~~~~~~++ It "1 `ModPow2` 1 is 1" (Is 1 (ModPow2 1 1))+ -* It "1 `ModPow2` 8 is 1" (Is 1 (ModPow2 1 8))++ -/-++ "Remainder of '3916441'"+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~++ It " `ModPow2` 1 is 1" (Is 1 (ModPow2 3916441 1))+ -* It " `ModPow2` 4 is 9" (Is 9 (ModPow2 3916441 4))+ -* It " `ModPow2` 8 is 153" (Is 153 (ModPow2 3916441 8))++testRemPow2 = Valid++testDiv+ :: Expect '[ Div 0 3 `ShouldBe` 0+ , Div 1 3 `ShouldBe` 0+ , Div 2 3 `ShouldBe` 0+ , Div 3 3 `ShouldBe` 1+ , Div 4 3 `ShouldBe` 1+ , Div 5 3 `ShouldBe` 1+ , Div 6 3 `ShouldBe` 2+ , Div 144 13 `ShouldBe` 11+ -- , Div 512 128 `ShouldBe` 11+ ]+testDiv = Valid++testNatBits+ :: "Type Level bit operations"+ ###########################++ "TestHighBit"+ ~~~~~~~~~~~~++ ShouldBeFalse (TestHighBit 127 8)+ -* ShouldBeFalse (TestHighBit 127 7)+ -* ShouldBeTrue (TestHighBit 127 6)+ -* ShouldBeFalse (TestHighBit 32 6)+ -* ShouldBeTrue (TestHighBit 32 5)+ -* ShouldBeFalse (TestHighBit 16 5)+ -* ShouldBeTrue (TestHighBit 16 4)+ -* ShouldBeFalse (TestHighBit 8 4)+ -* ShouldBeTrue (TestHighBit 8 3)+ -* ShouldBeFalse (TestHighBit 4 3)+ -* ShouldBeTrue (TestHighBit 4 2)+ -* ShouldBeFalse (TestHighBit 2 2)+ -* ShouldBeTrue (TestHighBit 2 1)+ -* ShouldBeFalse (TestHighBit 0 1)+ -* ShouldBeTrue (TestHighBit 1 0)++ -/-++ "ToBits"+ ~~~~~~~~~++ It "returns the empty list for a zero bit length"+ (ToBits 1023 0 `ShouldBe` ('[] :: [Bool]) )++ -* It "returns [] for a single unset bit"+ (ShouldBe ('[] :: [Bool]) (ToBits 0 1))++ -* It "returns [True] for a single set bit"+ (ShouldBe '[ 'True] (ToBits 1 1))++ -* It "returns [True, False] when getting two bits from 0x2"+ (ShouldBe '[ 'True, 'False] (ToBits 0x2 2))++ -* It "returns the list of bits in correct order"+ (ShouldBe '[ 'True, 'True, 'True, 'True+ , 'False, 'False, 'False, 'False]+ (ToBits 0xf0 8))++ -* It "returns no leading 'False (i.e. it omits leading zero bits)"+ (ShouldBe '[ 'True, 'True, 'True, 'True]+ (ToBits 0x0000000f 32))+ -/-++ "FromBits"+ ~~~~~~~~~++ It "returns 0 for '[]"+ (ShouldBe 0 (FromBits ('[] :: [Bool])))++ -* It "returns 0 for [False]"+ (ShouldBe 0 (FromBits '[ 'False]))++ -* It "returns 1 for [True]"+ (ShouldBe 1 (FromBits '[ 'True]))++ -* It "returns 2 for [True, False]"+ (ShouldBe 2 (FromBits '[ 'True, 'False]))++ -* It "returns 4 for [True, False, False]"+ (ShouldBe 4 (FromBits '[ 'True, 'False, 'False]))++ -* It "returns 5 for [True, False, True]"+ (ShouldBe 5 (FromBits '[ 'True, 'False, 'True]))+ -/-++ "ShiftBitsR"+ ~~~~~~~~~~~~++ It "returns the input bits for n == 0"+ (ShouldBe '[ 'True, 'False] (ShiftBitsR ['True, 'False] 0))++ -* It "returns '[] when shifting [True] 1 bits"+ (ShouldBe ('[] :: [Bool]) (ShiftBitsR '[ 'True ] 1))++ -* It "returns '[True] when shifting [True, True] 1 bits"+ (ShouldBe '[ 'True] (ShiftBitsR '[ 'True, 'True ] 1))++ -* It "returns (ToBits 12 8) when shifting (ToBits 97 8) 3 bits to the right"+ (ShouldBe (ToBits 12 8) (ShiftBitsR (ToBits 97 8) 3))+ -/-++ "GetMostSignificantBitIndex"+ ~~~~~~~~~~~~~~~++ It "returns 1 for 0"+ (ShouldBe 1 (GetMostSignificantBitIndex 64 0))++ -* It "returns 1 for 1"+ (ShouldBe 1 (GetMostSignificantBitIndex 64 1))++ -* It "returns 1 for 2"+ (ShouldBe 1 (GetMostSignificantBitIndex 64 2))++ -* It "returns 1 for 3"+ (ShouldBe 1 (GetMostSignificantBitIndex 64 3))++ -* It "returns 2 for 4"+ (ShouldBe 2 (GetMostSignificantBitIndex 64 4))++ -* It "returns 2 for 5"+ (ShouldBe 2 (GetMostSignificantBitIndex 64 4))++ -* It "returns 8 for 511"+ (ShouldBe 8 (GetMostSignificantBitIndex 64 511))++ -* It "returns 63 for (2^64 - 1)"+ (ShouldBe 63 (GetMostSignificantBitIndex 64 (2^64 - 1)))++ -/-+++ "ShiftR"+ ~~~~~~~~~++ It "returns '0' when shifting '42' 6 bits to the right"+ (ShouldBe 0 (ShiftR 64 42 6))+ -* It "returns 2 when shifting 512 8 bits to the right"+ (ShouldBe 2 (ShiftR 64 512 8))++testNatBits = Valid+#endif+++spec :: Spec+spec = do+ basicsSpec+#ifdef COMPLEXTESTS+ sizedSpec+ arraySpec+ describe "The Set of Type Functions" $+ it "is sound" $ do+ print (Valid :: Expect (BitRecordSize (Flag .+. Field 7) `Is` 8))+ print testTakeLastN+ print testRem+ print testRemPow2+ print testDiv+ print testNatBits+ print checkTestRecAligned+ print checkTestRecUnAligned+ describe "showARecord" $ do+ it "prints (Field 4 .+. (Field 4 := 0x96)) to \"<..>0110\"" $+ let actual = showRecord (Proxy :: Proxy (Field 4 .+. Field 4 := 0x96))+ in actual `shouldBe` "bits(4)\nbits(4) := 10010110 (hex: 96 dec: 150)"+ describe "StaticLazybytestringbuilder" $ do+ it "writes (and flushes) bits" $+ let rec = Proxy+ rec :: Proxy TestRecUnAligned+ actualB :: Builder+ actualB =+ runBitStringBuilderHoley+ (bitStringBuilderHoley rec)+ 1+ 3+ 7+ actual = printBuilder actualB+ in actual `shouldBe`+ "<< 01 00 06 00 00 00 00 0f fc >>"+ describe "Formatting sub-byte fields" $ do+ it "only the addressed bits are copied to the output" $+ property $ \value ->+ let rec = Proxy+ rec :: Proxy (Field 4 := 0 .+. "here" @: Field 4)+ actualB :: Builder+ actualB = runBitStringBuilderHoley (bitStringBuilderHoley rec) value+ actual = printBuilder actualB+ expected = printf "<< %.2x >>" (value .&. 0xf)+ in actual `shouldBe` expected+ it "renders (Flag := 0 .+. Field 7 := 130) to << 02 >>" $+ let rec = Proxy+ rec :: Proxy (Flag := 'False .+. Field 7 := 130)+ actual = printBuilder b+ where b = runBitStringBuilderHoley (bitStringBuilderHoley rec)+ in actual `shouldBe` "<< 02 >>"+ describe "ByteStringBuilder" $+ describe "runBitStringBuilderHoley" $+ it "0x01020304050607 to << 00 01 02 03 04 05 06 07 >>" $+ let expected = "<< 00 01 02 03 04 05 06 07 >>"+ actual =+ printBuilder+ (runBitStringBuilderHoley+ (bitStringBuilderHoley+ (bitString 64 0x01020304050607)))+ in actual `shouldBe` expected+#endif++instance (KnownNat n, n <= 64) => Arbitrary (B n) where+ arbitrary = do+ let h = 2^(n'-1) - 1+ n' = fromIntegral (natVal (Proxy @n)) :: Int+ x <- choose (0, h + h + 1)+ return (B x)
+ spec/BoxFieldsSpec.hs view
@@ -0,0 +1,96 @@+module BoxFieldsSpec (spec) where++import Test.Hspec+import Data.ByteString.IsoBaseFileFormat.Boxes+import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import qualified Data.ByteString.Lazy as BL+import Control.Exception (evaluate)++-- import qualified Data.ByteString.Builder as B import qualified Data.ByteString.Lazy as BL import+-- qualified Data.Binary.Get as Binary+spec :: Spec+spec =+ do describe "mkLanguage" $+ do it "throws runtime exceptions when the code is too short or too long" $+ do evaluate (mkLanguage "") `shouldThrow` anyException+ evaluate (mkLanguage "a") `shouldThrow` anyException+ evaluate (mkLanguage "aa") `shouldThrow` anyException+ evaluate (mkLanguage "bbbb") `shouldThrow` anyException+ evaluate (mkLanguage "bbbbb") `shouldThrow` anyException+ it "throws runtime exceptions when the code is not in aaa .. zzz" $+ do evaluate (mkLanguage "! 3") `shouldThrow` anyException+ evaluate (mkLanguage "AAA") `shouldThrow` anyException+ it "renders lower case three-letter strings correctly" $+ let actual =+ BL.unpack (toLazyByteString (boxBuilder (mkLanguage "deu")))+ expected =+ BL.unpack (toLazyByteString (word16BE (4 * 1024 + 5 * 32 + 21)))+ in actual `shouldBe` expected+ describe "Scalar, Constant, Template and ScalarArry composition" $+ describe "IsBoxContent instances" $+ do describe "example1" $+ do it "boxSize reports the correct size" $ boxSize example1 `shouldBe`+ (1 + 1 + (3 * 8) + (7 * 8))+ it "crashes during rendering because of the invalid number of array elements for \"baz\"" $+ evaluate (renderBox example1') `shouldThrow`+ anyException+ describe "example2" $+ do it "boxSize reports the correct size" $ boxSize example2 `shouldBe`+ (2 * 4 + 2 + 2 + 2 + 2 + 9 * 4 + 4 + 4)+ it "it renders the expected content" $ renderBox example2 `shouldBe`+ BL.pack [0 ,0 ,0 ,0+ ,0 ,0 ,0 ,0+ ,0 ,65 ,0 ,66+ ,1 ,0 ,0 ,0+ ,0 ,0 ,0 ,67+ ,0 ,0 ,0 ,68+ ,0 ,0 ,0 ,69+ ,0 ,0 ,0 ,70+ ,0 ,0 ,0 ,71+ ,0 ,0 ,0 ,72+ ,0 ,0 ,0 ,73+ ,0 ,0 ,0 ,74+ ,0 ,0 ,0 ,75+ ,0 ,0 ,0 ,76+ ,0 ,0 ,0 ,77]++renderBox :: IsBoxContent c+ => c -> BL.ByteString+renderBox = toLazyByteString . boxBuilder++type ExampleContent =+ Scalar Word8 "bla"+ :+ Constant (Scalar Word8 "blub") 123+ :+ Template (ScalarArray "foos" 3 Int64) '[1, 2, 3]+ :+ ScalarArray "baz" 7 Word64++type ExampleContentShort =+ U8 "bla"+ :+ Constant (U8 "blub") 123+ :+ Template (I64Arr "foos" 3) '[1, 2, 3]+ :+ U64Arr "baz" 7++example1 :: ExampleContent+example1 = Scalar 100 :+ Constant :+ Template :+ u64Arr [1,2,3,4,5,6,7]++example1' :: ExampleContentShort+example1' = Scalar 100 :+ Constant :+ Template :+ u64Arr [6,6,6]++type Example2 isAudio =+ Constant (I32Arr "reserved" 2) '[0, 0]+ :+ Template (I16 "layer") 0+ :+ Template (I16 "alternate_group") 0+ :+ Template (I16 "volume") (If isAudio 256 0)+ :+ Constant (I16 "reserved") 0+ :+ Template (I32Arr "matrix" 9) '[65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824]+ :+ I32 "width"+ :+ I32 "height"++example2 :: Example2 'True+example2 =+ Constant :+ Custom 65 :+ Custom 66 :+ Template :+ Constant :++ Custom (i32Arr [67 .. 75]) :++ i32 76 :++ i32 77
+ spec/BoxSpec.hs view
@@ -0,0 +1,92 @@+module BoxSpec+ ( spec+ )+where++import Test.Hspec+import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports+import qualified Data.ByteString.Builder as B+import qualified Data.ByteString.Lazy as BL+import qualified Data.Binary.Get as Binary+import qualified Data.Text as T++spec :: Spec+spec =+ describe "IsBoxContent instances" $ do+ describe "IsBoxContent Data.Text.Text" $ do+ describe "boxSize"+ $ it "returns string length + 1 (for the null termination byte)"+ $ do+ boxSize (T.pack "") `shouldBe` 1+ boxSize (T.pack "Hello!") `shouldBe` 7+ describe "boxBuilder of Data.Text.Text" $ do+ it "appends the null termination byte"+ $ let+ expected =+ [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 0]+ actual = BL.unpack $ toLazyByteString $ boxBuilder+ (T.pack "Hello world!")+ in+ actual `shouldBe` expected+ it "appends the null termination byte"+ $ let+ expected =+ [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 0]+ actual = BL.unpack $ toLazyByteString $ boxBuilder+ (T.pack "Hello world!")+ in+ actual `shouldBe` expected+ it "replaces embeded NULL characters with a space (0x20)"+ $ let+ expected =+ [72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 0]+ actual = BL.unpack $ toLazyByteString $ boxBuilder+ (T.pack "Hello\0world!")+ in+ actual `shouldBe` expected+ describe "()" $ do+ describe "boxSize" $ it "returns 0" $ boxSize () `shouldBe` 0+ describe "boxBuilder"+ $ it "emits no data"+ $ BL.length (B.toLazyByteString (boxBuilder ()))+ `shouldBe` 0+ describe "Box" $ do+ describe "boxSize"+ $ it "returns the header size if the content is empty"+ $ boxSize testBox1+ `shouldBe` (4 + 4)+ describe "Boxes" $ do+ describe "boxSize" $ do+ describe "a box with one nested box" $ do+ it "returns the sum of both boxSizes"+ $ boxSize (testParentBox1 $ singletonBox testBox1)+ `shouldBe` (2 * boxSize testBox1)+ it "returns the same value as written by boxBuilder"+ $ let b = testParentBox1 $ singletonBox testBox1+ writtenSize =+ let out = toLazyByteString (boxBuilder b)+ getSize = Binary.runGet Binary.getWord32be+ in BoxSize $ fromIntegral $ getSize out+ reportedSize = boxSize b+ in writtenSize `shouldBe` reportedSize++data TestBox1++instance IsBox TestBox1 where+ type BoxContent TestBox1 = ()++type instance BoxTypeSymbol TestBox1 = "tst1"++testBox1 :: Box TestBox1+testBox1 = Box ()++data TestParentBox1++instance IsBox TestParentBox1 where+ type BoxContent TestParentBox1 = ()++type instance BoxTypeSymbol TestParentBox1 = "par1"++testParentBox1 :: Boxes ts -> Box (ContainerBox TestParentBox1 ts)+testParentBox1 = containerBox ()
+ spec/DataReferenceSpec.hs view
@@ -0,0 +1,20 @@+module DataReferenceSpec (spec) where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Boxes+import Test.Hspec++spec :: Spec+spec =+ describe "IsBoxContent" $+ describe "LocalMediaEntry" $+ describe "boxSize" $+ it "returns 0" $+ let actual = boxSize localMediaDataReference+ expected = drefHeader + entryField + durlHeader+ drefHeader = boxHeader + fullHeader+ entryField = 4+ durlHeader = boxHeader + fullHeader+ boxHeader = 4 + 4+ fullHeader = 4+ in actual `shouldBe` expected
+ spec/ElementaryStreamDescriptorSpec.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE OverloadedStrings #-}+module ElementaryStreamDescriptorSpec (spec) where++import Test.Hspec+import Data.ByteString.Mp4.Boxes.ElementaryStreamDescriptor+import Data.ByteString.Mp4.Boxes.AudioSpecificConfig+import Data.ByteString.Mp4.Boxes.DecoderConfigDescriptor+import Data.ByteString.Mp4.Boxes.DecoderSpecificInfo+import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.ByteString.IsoBaseFileFormat.Box++spec :: Spec+spec = do+#ifndef COMPLEXTESTS+ return ()+#else+ describe "EsdBox" $ do+ let eb = esdBox (Proxy @TestEsDescriptor) False 0 0 0+ it "has the correct size" $+ boxSize eb `shouldBe` 39+ it "generates the expected bits" $+ printBuilder (boxBuilder eb)+ `shouldBe` "<< 00 00 00 27 65 73 64 73 00 00 00 00 03 19 00 01 00 04 11 40 15 00 00 00 00 00 00 00 00 00 00 00 05 02 11 98 06 01 02 >>"++type TestEsDescriptor =+ ESDescriptorMp4File+ (StaticFieldValue "esId" 1)+ TestConfigDescriptor++type TestConfigDescriptor =+ DecoderConfigDescriptor+ 'AudioIso14496_3+ 'AudioStream+ '[AudioConfigAacMinimal+ 'AacLc+ DefaultGASpecificConfig+ (SetEnum "samplingFreq" SamplingFreq 'SF48000)+ (SetEnum "channelConfig" ChannelConfig 'SinglePair)]+ '[]+#endif
+ spec/EnumSpec.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE UndecidableInstances #-}+module EnumSpec (spec) where++import Data.Proxy+import Data.Type.BitRecords+import Data.Type.Equality ()+import Prelude hiding ((.), id)+import Test.Hspec++spec = do+ describe "ToPretty" $ do+ it "renders as a record using showRecord" $+ showRecord (Proxy @(BitRecordOfEnum (EnumParam "test" TestEnumExt)))+ `shouldBe` "test: <<enum>>(2)"+ describe "BitStringBuilder" $ do+ it "produces binary output" $+ bitStringPrinter (Proxy @(BitRecordOfEnum (EnumParam "test" TestEnumExt)))+ (MkEnumValue (Proxy @'A))+ `shouldBe` "<< 40 >>"++type TestEnumExt = ExtEnum TestEnum 2 'Be FieldU16+++data TestEnum =+ A | Be | C++type instance FromEnum TestEnum 'A = 1+type instance FromEnum TestEnum 'Be = 2+type instance FromEnum TestEnum 'C = 4
+ spec/ExpandableSpec.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE UndecidableInstances #-}+module ExpandableSpec (spec) where+++import Data.ByteString.Builder+import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports+import qualified Data.ByteString.Lazy as B+import Data.ByteString.Mp4.Boxes.Expandable+import Test.Hspec+import Test.QuickCheck+++spec :: Spec+spec = do+ describe "StaticExpandable" $ do+ describe "ExpandableSizeLastChunk" $+ it "renders both 2 and 130 as 00000010 " $ do+ let actual130 = bitStringPrinter (Proxy :: Proxy ((ExpandableSizeLastChunk 130)))+ actual2 = bitStringPrinter (Proxy :: Proxy (ExpandableSizeLastChunk 2))+ actual130 `shouldBe` actual2+ actual2 `shouldBe` "<< 02 >>"+ describe "ExpandableSize" $+ it "creates a stdandard conform size representation for the size 130" $+ let actualStr = bitStringPrinter (Proxy :: Proxy (ExpandableSize 130))+ in actualStr `shouldBe` "<< 81 02 >>"++ -- TODO add new test+ -- it "has a boxSize of 3 when using a 16-bit body value" $+ -- boxSize (staticExpandable (Proxy :: Proxy (RecordField (Field 16 := 1234 )))) `shouldBe` BoxSize 3+ -- it "has a boxBuilder that writes the body in big endian byte order for a 32-bit body value" $+ -- B.unpack (toLazyByteString (boxBuilder (staticExpandable (Proxy :: Proxy (RecordField (Field 32 := 0x12345678 ))))))+ -- `shouldBe` [4, 0x12, 0x34, 0x56, 0x78]+ it "writes the size 128 as [ 0b10000001, 0b00000000 ] " $+ let actual = B.unpack $ toLazyByteString (boxBuilder (Expandable (ETC 128)))+ expected = [ 129, 0 ]+ in actual `shouldBe` expected+ it "writes the size (2 ^ 21) as [ 0b10000001, 0b10000000, 0b10000000, 0b00000000 ] " $+ let actual = B.unpack $ toLazyByteString (boxBuilder (Expandable (ETC (2^(21 :: Int)))))+ expected = [ 129, 128, 128, 0 ]+ in actual `shouldBe` expected+ it "writes the size (2 ^ 21 - 1) as [ 0b11111111, 0b11111111, 0b01111111 ] " $+ let actual = B.unpack $ toLazyByteString (boxBuilder (Expandable (ETC (2^(21 :: Int) - 1))))+ expected = [ 255, 255, 127 ]+ in actual `shouldBe` expected+ it "writes size according to the spec" $+ property $ \etc@(ETC s) ->+ let expectedBoxSize =+ BoxSize $+ s ++ if s < 2 ^ ( 7 :: Int) then 1+ else if s < 2 ^ (14 :: Int) then 2+ else if s < 2 ^ (21 :: Int) then 3 else 4+ actualBoxSize = boxSize (Expandable etc)+ in+ actualBoxSize `shouldBe` expectedBoxSize+++instance Arbitrary ExpandableTestContent where+ arbitrary = ETC <$> choose (0, 2^(28::Integer) - 1)++newtype ExpandableTestContent =+ ETC Word64+ deriving (Show, Eq)++instance IsBoxContent ExpandableTestContent where+ boxSize (ETC s) = BoxSize s+ boxBuilder _ = mempty
+ spec/MediaFileSpec.hs view
@@ -0,0 +1,71 @@+module MediaFileSpec+ (spec)+ where++import Test.Hspec+import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.MediaFile+import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.ByteString.Lazy (unpack)++spec :: Spec+spec =+ describe "mediaBuilder" $+ do describe "Empty BoxLayout" $+ it "accepts valid box content types" $+ let test =+ mediaBuilder (Proxy :: Proxy TestBrandEmpty)+ NoBoxes+ in printBuilderStdOut test+ describe "Single Box BoxLayout" $+ it "accepts valid box content types" $+ let test =+ mediaBuilder (Proxy :: Proxy TestBrandSingle)+ (singletonBox testBox1)+ in printBuilderStdOut test+ describe "Multiple nested Boxes BoxLayout" $+ it "accepts valid box content types" $+ let test =+ mediaBuilder (Proxy :: Proxy TestBrandNested)+ (singletonBox (testParentBox1 $: testBox1))+ in printBuilderStdOut test++printBuilderStdOut :: Builder -> IO ()+printBuilderStdOut b =+ putStrLn $ unlines $ (" "++) <$> lines (show (unpack (toLazyByteString b)))++data TestBox1++instance IsBox TestBox1 where+ type BoxContent TestBox1 = ()++type instance BoxTypeSymbol TestBox1 = "tst1"++testBox1 :: Box TestBox1+testBox1 = Box ()++data TestParentBox1++instance IsBox TestParentBox1 where+ type BoxContent TestParentBox1 = ()++type instance BoxTypeSymbol TestParentBox1 = "par1"++testParentBox1+ :: Boxes ts -> Box (ContainerBox TestParentBox1 ts)+testParentBox1 = containerBox ()++data TestBrandEmpty++instance IsMediaFileFormat TestBrandEmpty where+ type BoxLayout TestBrandEmpty = Boxes '[]++data TestBrandSingle++instance IsMediaFileFormat TestBrandSingle where+ type BoxLayout TestBrandSingle = Boxes '[OM_ TestBox1]++data TestBrandNested++instance IsMediaFileFormat TestBrandNested where+ type BoxLayout TestBrandNested = Boxes '[OM TestParentBox1 '[OM_ TestBox1]]
+ spec/Mp4AudioFileSpec.hs view
@@ -0,0 +1,193 @@+{-# LANGUAGE OverloadedStrings #-}+module Mp4AudioFileSpec (spec) where -- TODO rename to mp4 audio init spec++import Test.Hspec+import Data.ByteString.Mp4.Boxes.BaseDescriptor+import Data.ByteString.Mp4.Boxes.AudioSpecificConfig+import Data.ByteString.Mp4.Boxes.Mp4AudioSampleEntry+import Data.ByteString.Mp4.AudioStreaming+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString as BS+import Data.Text ()++spec :: Spec+spec =+ do+ describe "Mp4AacAudioDecoderConfigDescriptor" $ do+ it "can be pretty printed" $+ showRecord (Proxy @(BitRecordOfDescriptor+ $~ (Eval (Mp4AacAudioDecoderConfigDescriptor+ (AudioConfigAacLc+ (EnumParam "samplingFreq" SamplingFreq)+ (EnumParam "channelConfig" ChannelConfig))))))+ `shouldEndWith`+ "has-gas-extension: boolean := no"++#ifdef COMPLEXTESTS+ it "can be transformed to binary output" $+ let actual =+ bitStringPrinter+ (Proxy @(BitRecordOfDescriptor+ $~ Eval (Mp4AacAudioDecoderConfigDescriptor+ (AudioConfigAacLc+ (EnumParam "samplingFreq" SamplingFreq)+ (EnumParam "channelConfig" ChannelConfig)))))+ True+ 1+ 2+ 3+ (MkEnumValue (Proxy @'SF16000))+ (MkEnumValue (Proxy @'SingleChannel))+ expexted = "<< 04 11 40 17 00 00 01 00 00 00 02 00 00 00 03 05 02 14 08 >>"+ in actual `shouldBe` expexted+ describe "Mp4AacLcEsDescriptor" $+ do it "can be transformed to binary output" $+ bitStringPrinter (Proxy @(BitRecordOfDescriptor $~ Eval Mp4AacLcEsDescriptor))+ False 0 0 0+ (MkEnumValue (Proxy @'SF48000))+ (MkEnumValue (Proxy @'SingleChannel))+ `shouldBe`+ "<< 03 19 00 01 00 04 11 40 15 00 00 00 00 00 00 00 00 00 00 00 05 02 11 88 06 01 02 >>"+ it "can be pretty printed" $+ showRecord (Proxy @(BitRecordOfDescriptor $~ Eval Mp4AacLcEsDescriptor))+ `shouldStartWith` "U8 := hex: 03 (dec: 3)\n"+ describe "Mp4HeAacEsDescriptor" $+ do it "can be transformed to binary output" $+ bitStringPrinter (Proxy @(BitRecordOfDescriptor $~ Eval Mp4HeAacEsDescriptor))+ False 0 0 0+ (MkEnumValue (Proxy @'SF48000))+ (MkEnumValue (Proxy @'SingleChannel))+ (MkEnumValue (Proxy @'SF24000))+ `shouldBe`+ "<< 03 1a 00 01 00 04 12 40 15 00 00 00 00 00 00 00 00 00 00 00 05 03 29 8b 08 03 00 81 00 >>"+ describe "SingleAudioTrackInit version 0" $+ do it "renders some output at all" $+ do creationTime <- mp4CurrentTime+ let ct :: Word32+ ct = fromScalar creationTime+ ct3 = (ct `shiftR` 24) .&. 255+ ct2 = (ct `shiftR` 16) .&. 255+ ct1 = (ct `shiftR` 8) .&. 255+ ct0 = (ct `shiftR` 0) .&. 255+ cts :: [Word8]+ cts = fromIntegral <$> [ct3,ct2,ct1,ct0]+ let args = exampleSingleTrackInit creationTime+ doc = buildAacInitSegment args+ rendered = BS.unpack $ fromBinaryAacInitSegment doc+ expected =+ [+ -- ftyp box+ 0,0,0,32+ ,102,116,121,112+ ,105,115,111,53,0,0,0,0+ ,105,115,111,109+ ,105,115,111,53+ ,100,97,115,104+ ,109,112,52,50+ -- skip box+ ,0,0,0,49+ ,115,107,105,112+ ,76,105,110,100,101,110,98,97,117,109,32,71,109,98,72,44,32,105+ ,115,111,98,109,102,102,44,32+ ,83,118,101,110,32,72,101,121,108,108,32,50,48,49,57+ -- moov box+ ,0,0,2,57,109 ,111 ,111 ,118+ -- mvhd box+ ,0 ,0 ,0 ,108 ,109 ,118 ,104 ,100 ,0 ,0 ,0 ,0]+ ++ cts ++ cts +++ [0 ,1 ,95 ,144+ ,0 ,0 ,0 ,0 ,0 ,1 ,0 ,0 ,1 ,0 ,0 ,0+ ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,0 ,0 ,0 ,0 ,0 ,0+ ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0+ ,0 ,0 ,0 ,0 ,0 ,0 ,64 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0+ ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0+ ,0 ,0 ,0 ,2+ -- trak box+ ,0 ,0 ,1,157+ ,116 ,114 ,97 ,107+ -- tkhd box+ ,0 ,0 ,0 ,92+ ,116 ,107 ,104 ,100+ ,0 ,0 ,0 ,7]+ ++ cts ++ cts +++ [0 ,0 ,0 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0+ ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,1 ,0 ,0 ,0+ ,0 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0+ ,0 ,1 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0+ ,0 ,64 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0 ,0+ -- mdia box+ ,0,0,1,57+ ,109,100,105,97+ -- mdhd box+ ,0,0,0,32+ ,109,100,104,100+ ,0,0,0,0]+ ++ cts ++ cts +++ [0,0,187,128+ ,0,0,0,0+ ,85,196+ ,0,0+ -- hdlr box+ ,0,0,0,44+ ,104,100,108,114+ ,0,0,0,0,0,0,0,0,115,111,117,110,0,0,0,0,0,0,0,0,0,0,0,0+ ,72,101,108,108,111,32,119,111,114,108,100,0+ -- minf box+ ,0,0,0,229+ ,109,105,110,102+ -- smhd box+ ,0,0,0,16+ ,115,109,104,100,0,0,0,0,0,0,0,0+ -- dinf boimport Data.ByteString.IsoBaseFileFormat.Boxes+ ,0,0,0,36+ ,100,105,110,102+ -- dref box+ ,0,0,0,28+ ,100,114,101,102+ ,0,0,0,0,0,0,0,1+ -- url box+ ,0,0,0,12+ ,117,114,108,32+ ,0,0,0,1+ -- stbl box+ ,0,0,0,169+ ,115,116,98,108+ -- stsd box+ ,0,0,0,93+ ,115,116,115,100+ ,0,0,0,0,0,0,0,1+ -- mp4a box+ ,0,0,0,77+ ,109,112,52,97+ ,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,16,0,0,0,0,187,128,0,0+ -- esds box+ ,0,0,0,41+ ,101,115,100,115+ ,0,0,0,0,3,26,0,1,0,4,18,64,21+ ,0,0,0,0,0,0,0,0,0,0,0,5,3,41,137,136,3,0,129,0+ -- stts box+ ,0,0,0,16+ ,115,116,116,115,0,0,0,0,0,0,0,0+ -- stsc box+ ,0,0,0,16+ ,115,116,115,99,0,0,0,0,0,0,0,0+ -- stsz box+ ,0,0,0,20+ ,115,116,115,122,0,0,0,0,0,0,0,0,0,0,0,0+ -- stc0 box+ ,0,0,0,16+ ,115,116,99,111,0,0,0,0,0,0,0,0+ -- mvex box+ ,0,0,0,40+ ,109,118,101,120+ -- trex box+ ,0,0,0,32+ ,116,114,101,120,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0]+ BL.writeFile "/tmp/isobmff-test-case-dash-spec.mp4" (BL.pack rendered)+ rendered `shouldBe`+ expected++exampleSingleTrackInit :: U32 "creation_time" -> AacInitSegment+exampleSingleTrackInit creationTime =+ AacInitSegment creationTime "Hello world" True SF48000 SingleChannel+#endif
+ spec/Mp4AudioSampleEntrySpec.hs view
@@ -0,0 +1,24 @@+module Mp4AudioSampleEntrySpec ( spec ) where++import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.ByteString.Mp4.Boxes.AudioSpecificConfig+import Test.Hspec++spec :: Spec+spec = describe "AudioObjectType" $+ it "type-checks" $ do+ print testAudioObjectType++testAudioObjectType = Valid+testAudioObjectType ::++ "Audio Object Types"+ #####################++ "Small Audio Object Types"+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~+ 5 `ShouldBe` BitRecordSize (AudioObjectTypeRec 'AoReserved5) -/-++ "Big Audio Object Types"+ ~~~~~~~~~~~~~~~~~~~~~~~~+ 11 `ShouldBe` BitRecordSize (AudioObjectTypeRec 'AoCustom)
+ spec/Mp4AudioSegmentSpec.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE OverloadedStrings #-}+module Mp4AudioSegmentSpec (spec) where++import qualified Data.ByteString as BS+import Data.ByteString.IsoBaseFileFormat.ReExports+import qualified Data.ByteString.Lazy as BL+import Data.ByteString.Mp4.AudioStreaming+import Data.Text ()+import Test.Hspec++spec :: Spec+spec =+ describe "AacMp4TrackFragment" $+ do let args = moof+ doc = buildAacMp4TrackFragment args+ rendered = BL.unpack $ toLazyByteString $ doc+ dataOffset = 120+ expected =+ [+ -- styp box+ 0,0,0,24+ ,115,116,121,112+ ,109,115,100,104,0,0,0,0+ ,109,115,100,104+ ,100,97,115,104+ -- moov box [offset here: 32]+ ,0,0,0,112+ ,109,111,111,102+ -- mfhd box+ ,0,0,0,16+ ,109,102,104,100+ ,0,0,0,0,0,0,0,13+ -- traf+ ,0,0,0,88+ ,116,114,97,102+ -- tfhd+ ,0,0,0,16+ ,116,102,104,100+ ,0,2,0,0,0,0,0,1+ -- tfdt+ ,0,0,0,20+ ,116,102,100,116+ ,1,0,0,0,0,0,0,0,0,0,0,37+ -- trun+ ,0,0,0,44+ ,116,114,117,110+ ,0,0,7,1+ ,0,0,0,2+ ,0,0,0,dataOffset+ -- sample 1+ ,0,0,0,23 -- duration+ ,0,0,0,192 -- length+ ,2,0,0,0 -- flags+ -- sample 2+ ,0,0,0,23+ ,0,0,0,192+ ,2,0,0,0+ -- mdat+ ,0,0,1,136+ ,109,100,97,116]+ -- sample 1+ ++ [0..191]+ -- sample 2+ ++ [0..191]+ it "renders the exact expectected output" $ do+#ifdef COMPLEXTESTS+ BL.writeFile "/tmp/isobmff-test-case-dash-spec.m4s" (BL.pack rendered)+#endif+ rendered `shouldBe` expected+ it "calculates the data-offset correctly" $+ drop ((fromIntegral dataOffset) + 12 * 2) rendered+ `shouldBe` ([0..191] ++ [0..191])++moof :: AacMp4TrackFragment+moof = AacMp4TrackFragment 13 37 (replicate 2 (23, BS.pack [0..191]))
+ spec/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ spec/TypeLayoutSpec.hs view
@@ -0,0 +1,158 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}+{-# LANGUAGE UndecidableInstances #-}+module TypeLayoutSpec (spec) where++import Test.Hspec+import Data.ByteString.IsoBaseFileFormat.Util.TypeLayout+import Data.ByteString.IsoBaseFileFormat.Box++spec :: Spec+spec =+ describe "IsRuleConform" $ do+ describe "TopLevel" $ do+ it "validates TopLevel Boxes" $ test1 `shouldBe` ()+ describe "ContainerBoxes" $ do+ describe "OnceOptional" $ do+ it "validates empty containters" $ test2a `shouldBe` ()+ it "validates a singleton container" $ test2b `shouldBe` ()+ describe "SomeOptional" $ do+ it "validates empty containters" $ test3a `shouldBe` ()+ it "validates a singleton container" $ test3b `shouldBe` ()+ it "validates a multi-element container" $ test3c `shouldBe` ()+ describe "(implied) mandatory" $ do+ it "validates a singleton container" $ test4 `shouldBe` ()+ describe "SomeMandatory" $ do+ it "validates a singleton container" $ test5a `shouldBe` ()+ it "validates a multi-element container" $ test5b `shouldBe` ()+#ifdef COMPLEXTESTS+ it "validates a Mix of SomeMandatory, OnceOptional, SomeOptional" $ do+ test6a `shouldBe` ()+ test6b `shouldBe` ()+ test6c `shouldBe` ()+ test6d `shouldBe` ()+ it "validates deeply nested container" $ do+ test7a `shouldBe` ()+ test7b `shouldBe` ()+ test7c `shouldBe` ()+#endif++----+data Foo+type instance BoxTypeSymbol Foo = ToSymbol Foo+type instance ToSymbol Foo = "foo "+data Fov+type instance BoxTypeSymbol Fov = ToSymbol Fov+type instance ToSymbol Fov = "fov "+data Bar+type instance BoxTypeSymbol Bar = ToSymbol Bar+type instance ToSymbol Bar = "bar "+data Baz+type instance BoxTypeSymbol Baz = ToSymbol Baz+type instance ToSymbol Baz = "baz "+----+type TestRule1 = TopLevel (MatchSymbol "foo ")+type TestType1 = Foo+test1 :: (IsRuleConform TestType1 TestRule1 ~ 'True) => ()+test1 = ()+----++type TestRule2 = TopLevel (ContainerBox Foo '[OnceOptionalX (MatchSymbol "bar ")])+type TestType2a = Box (ContainerBox Foo '[])+test2a :: (IsRuleConform TestType2a TestRule2 ~ 'True+ ) => ()+test2a = ()+--+type TestType2b = Box (ContainerBox Foo '[Bar])+test2b :: (IsRuleConform TestType2b TestRule2 ~ 'True) => ()+test2b = ()+----+type TestRule3 = TopLevel (ContainerBox Foo '[SomeOptionalX (MatchSymbol "bar ")])+type TestType3a = Box (ContainerBox Foo '[])+test3a :: (IsRuleConform TestType3a TestRule3 ~ 'True) => ()+test3a = ()+--+type TestType3b = Box (ContainerBox Foo '[Bar])+test3b :: (IsRuleConform TestType3b TestRule3 ~ 'True) => ()+test3b = ()+--+type TestType3c = Box (ContainerBox Foo '[Bar,Bar])+test3c :: (IsRuleConform TestType3c TestRule3 ~ 'True) => ()+test3c = ()+----+type TestRule4 = TopLevel (ContainerBox Foo '[MatchSymbol "bar "])+type TestType4 = Box (ContainerBox Foo '[Bar])+test4 :: (IsRuleConform TestType4 TestRule4 ~ 'True) => ()+test4 = ()+----+type TestRule5 = TopLevel (ContainerBox Foo '[SomeMandatoryX (MatchSymbol "bar ")])+type TestType5a = Box (ContainerBox Foo '[Bar])+test5a :: (IsRuleConform TestType5a TestRule5 ~ 'True) => ()+test5a = ()+type TestType5b = Box (ContainerBox Foo '[Bar, Bar])+test5b :: (IsRuleConform TestType5b TestRule5 ~ 'True) => ()+test5b = ()+++----+#ifdef COMPLEXTESTS+type TestRule6 =+ (ContainerBox Foo+ '[ OnceOptionalX (MatchSymbol "baz ")+ , SomeMandatoryX (MatchSymbol "bar ")+ , SomeOptionalX (MatchSymbol "fov ")+ , MatchSymbol "foo "])+type TestType6a =+ Box (ContainerBox Foo '[Baz,Bar,Bar,Bar,Fov,Fov,Foo])+test6a :: (IsRuleConform TestType6a TestRule6 ~ 'True) => ()+test6a = ()+type TestType6b =+ Box (ContainerBox Foo '[Bar,Bar,Bar,Fov,Fov,Foo])+test6b :: (IsRuleConform TestType6b TestRule6 ~ 'True) => ()+test6b = ()+type TestType6c =+ Box (ContainerBox Foo '[Bar,Fov,Fov,Foo])+test6c :: (IsRuleConform TestType6c TestRule6 ~ 'True) => ()+test6c = ()+type TestType6d = Box (ContainerBox Foo '[Bar,Foo])+test6d :: (IsRuleConform TestType6d TestRule6 ~ 'True) => ()+test6d = ()+++----+type TestRule7 =+ TopLevel (ContainerBox Foo+ '[ SomeOptionalX (ContainerBox Fov+ '[ OnceOptionalX (ContainerBox Baz+ '[MatchSymbol "foo "])+ , SomeMandatoryX (MatchSymbol "bar ")+ ])])+type TestType7a = Box (ContainerBox Foo '[ ])+test7a :: (IsRuleConform TestType7a TestRule7 ~ 'True) => ()+test7a = ()+type TestType7b = Box (ContainerBox Foo+ '[ Box (ContainerBox Fov+ '[Box (ContainerBox Baz+ '[Foo])+ , Bar])])+test7b :: (IsRuleConform TestType7b TestRule7 ~ 'True) => ()+test7b = ()+type TestType7c = Box (ContainerBox Foo+ '[ Box (ContainerBox Fov+ '[ Box (ContainerBox Baz+ '[Foo])+ , Bar+ , Bar+ , Bar+ ])+ , Box (ContainerBox Fov+ '[ Box (ContainerBox Baz+ '[Foo])+ , Bar+ , Bar+ , Bar+ ])+ ])+test7c :: (IsRuleConform TestType7c TestRule7 ~ 'True) => ()+test7c = ()+#endif
+ src/Data/ByteString/IsoBaseFileFormat/Box.hs view
@@ -0,0 +1,379 @@+{-# LANGUAGE UndecidableInstances #-}++-- | Definition of the most basic element in an ISOBMFF file: a /box/. See+-- Chapter 4 in the standard document. A box is a container with a type, a+-- size, some data and some nested boxes. The standard defines - among other+-- characteristics - available box types and their semantics, the fields they+-- contain and how they are nested into each other. This library tries to+-- capture some of these characteristics using modern Haskell type system+-- features, in order to provide compile time checks for (partial) standard+-- compliance.+module Data.ByteString.IsoBaseFileFormat.Box where++import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.Singletons.Prelude.List (type (++),+ Length)+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.ByteString as B++-- * Box Type Classes+-- | Base class for all (abstract/phantom/normal-) types that represent boxes+class (KnownSymbol (BoxTypeSymbol t), IsBoxContent (BoxContent t))+ => IsBox (t :: Type) where+ type BoxContent t+ type BoxContent t = t+ toBoxType :: proxy t -> BoxType+ toBoxType _ = parseBoxType (Proxy :: Proxy (BoxTypeSymbol t))++-- | A type family used by the type-level consistency checks. It is required+-- that an instance of this type family exists for every 'IsBox' instance.+-- This family could not be associative since it is used by type families that+-- cannot have type class constraints.+type family BoxTypeSymbol (t :: k) :: Symbol++-- | Types that go into a box. A box content is a piece of data that can be+-- reused in different instances of 'IsBox'. It has no 'BoxType' and hence+-- defines no box.+class IsBoxContent a where+ boxSize :: a -> BoxSize+ boxBuilder :: a -> Builder++-- * Box Contents+-- TODO move all IsBoxContent stuff to BoxContent.hs++-- | A type that wraps the contents of a box and the box type.+data Box b where+ Box ::+ !(BoxContent b) -> Box b++instance IsBox b => IsBox (Box b) where+ type BoxContent (Box b) = BoxContent b+ toBoxType _ = toBoxType (Proxy :: Proxy b)++type instance BoxTypeSymbol (Box b) = BoxTypeSymbol b++instance IsBox cnt => IsBoxContent (Box cnt) where+ boxBuilder b@(Box cnt) = sB <> tB <> sExtB <> tExtB <> cntB+ where s = boxSize b+ t = toBoxType b+ sB = boxBuilder s+ sExtB = boxBuilder (BoxSizeExtension s)+ tB = boxBuilder t+ tExtB = boxBuilder (BoxTypeExtension t)+ cntB = boxBuilder cnt+ boxSize b@(Box cnt) = sPayload + boxSize (BoxSizeExtension sPayload)+ where sPayload =+ boxSize (BoxSize undefined) + boxSize t + boxSize cnt ++ boxSize (BoxTypeExtension t)+ t = toBoxType b++instance (Default (BoxContent b)) => Default (Box b) where+ def = Box def++-- | Compose 'BoxContent' and 'Boxes' under the Constraint that they are+-- composable.+data ContainerBox b (bs :: [Type]) where+ ContainerBox :: IsBox b => !(BoxContent b) -> !(Boxes bs) -> ContainerBox b bs++instance (IsBox b) => IsBox (ContainerBox b bs)++type instance BoxTypeSymbol (ContainerBox b bs) = BoxTypeSymbol b++instance IsBoxContent (ContainerBox b bs) where+ boxSize (ContainerBox c bs) = boxSize (c :+ bs)+ boxBuilder (ContainerBox c bs) = boxBuilder (c :+ bs)++-- | A heterogenous collection of boxes.+data Boxes (boxTypes :: [Type]) where+ NoBoxes :: Boxes '[]+ (:.) :: IsBox l => !(Box l) -> !(Boxes r) -> Boxes (Box l ': r)+ -- | Create a 'Boxes' collection from two 'Box'es+ (:<>) :: !(Boxes l) -> !(Boxes r) -> Boxes (l ++ r)+ (:|) :: (IsBox l, IsBox r) => !(Box l) -> !(Box r) -> Boxes '[Box l, Box r]++infixr 1 :<>+infixr 2 :.+infixr 2 :|+infixr 3 $:++-- | Apply a function to a 'Boxes' collection containing only a single 'Box'.+($:) :: IsBox l => (Boxes '[Box l] -> r) -> Box l -> r+($:) f = f . singletonBox++-- | Create a 'Boxes' collection with a single 'Box'.+singletonBox :: IsBox l => Box l -> Boxes '[Box l]+singletonBox b = b :. NoBoxes++-- | Get the elements in a type level array+typeListLength :: forall a proxy (ts :: [k]) . (KnownNat (Length ts), Num a)+ => proxy ts -> a+typeListLength _ = fromIntegral (natVal (Proxy :: Proxy (Length ts)))++instance IsBoxContent (Boxes bs) where+ boxSize NoBoxes = 0+ boxSize (l :. r) = boxSize l + boxSize r+ boxSize (l :| r) = boxSize l + boxSize r+ boxSize (l :<> r) = boxSize l + boxSize r+ boxBuilder NoBoxes = mempty+ boxBuilder (l :. r) = boxBuilder l <> boxBuilder r+ boxBuilder (l :| r) = boxBuilder l <> boxBuilder r+ boxBuilder (l :<> r) = boxBuilder l <> boxBuilder r++-- | A box that contains no fields, but nested boxes.+containerBox :: (IsBox t)+ => BoxContent t -> Boxes ts -> Box (ContainerBox t ts)+containerBox c bs = Box (ContainerBox c bs)++-- * Box Size and Type+-- | The size of the box. If the size is limited to a (fixed) value, it can be+-- provided as a 'Word64' which will be represented as either a 32bit compact+-- size or as 64 bit /largesize/. If 'UnlimitedSize' is used, the box extends to+-- the end of the file.+data BoxSize+ = UnlimitedSize+ | BoxSize !Word64+ deriving (Show,Eq)++fromBoxSize :: Num a => a -> BoxSize -> a+fromBoxSize !fallback UnlimitedSize = fallback+fromBoxSize _fallback (BoxSize !s) = fromIntegral s++instance IsBoxContent BoxSize where+ boxSize _ = BoxSize 4+ boxBuilder UnlimitedSize = word32BE 0+ boxBuilder (BoxSize n) =+ word32BE $+ if n < (4294967296 :: Word64)+ then fromIntegral n+ else 1++instance Num BoxSize where+ (+) UnlimitedSize _ = UnlimitedSize+ (+) _ UnlimitedSize = UnlimitedSize+ (+) (BoxSize l) (BoxSize r) = BoxSize (l + r)+ (-) UnlimitedSize _ = UnlimitedSize+ (-) _ UnlimitedSize = UnlimitedSize+ (-) (BoxSize l) (BoxSize r) = BoxSize (l - r)+ (*) UnlimitedSize _ = UnlimitedSize+ (*) _ UnlimitedSize = UnlimitedSize+ (*) (BoxSize l) (BoxSize r) = BoxSize (l * r)+ abs UnlimitedSize = UnlimitedSize+ abs (BoxSize n) = BoxSize (abs n)+ signum UnlimitedSize = UnlimitedSize+ signum (BoxSize n) = BoxSize (signum n)+ fromInteger n = BoxSize $ fromInteger n++-- | The 'BoxSize' can be > 2^32 in which case an 'BoxSizeExtension' must be+-- added after the type field.+newtype BoxSizeExtension =+ BoxSizeExtension BoxSize++instance IsBoxContent BoxSizeExtension where+ boxBuilder (BoxSizeExtension UnlimitedSize) = mempty+ boxBuilder (BoxSizeExtension (BoxSize n)) =+ if n < 4294967296+ then mempty+ else word64BE n+ boxSize (BoxSizeExtension UnlimitedSize) = 0+ boxSize (BoxSizeExtension (BoxSize n)) =+ BoxSize $+ if n < 4294967296+ then 0+ else 8++-- | A box has a /type/, this is the value level representation for the box type.+data BoxType+ =+ -- | `FourCc` can be used as @boxType@ in `Box`, standard four letter character+ -- code, e.g. @ftyp@+ StdType !FourCc+ |+ -- | CustomBoxType defines custom @boxType@s in `Box`es.+ CustomBoxType !String+ deriving (Show,Eq)++-- | Create a box type from a 'Symbol'. Parse the symbol value, if it's a four+-- charachter code, then return that as 'StdType' otherwise parse a UUID (TODO)+-- and return a 'CustomBoxType'.+parseBoxType :: KnownSymbol t => proxy t -> BoxType+parseBoxType px = StdType (fromString (symbolVal px))++-- | A type containin a printable four letter character code. TODO replace impl with U32Text+newtype FourCc =+ FourCc (Char,Char,Char,Char)+ deriving (Show,Eq)++instance IsString FourCc where+ fromString !str+ | length str == 4 =+ let [!a,!b,!c,!d] = str+ in FourCc (a,b,c,d)+ | otherwise =+ error ("cannot make a 'FourCc' of a String which isn't exactly 4 bytes long: " +++ show str ++ " has a length of " ++ show (length str))++instance IsBoxContent FourCc where+ boxSize _ = 4+ boxBuilder (FourCc (!a,!b,!c,!d)) = putW a <> putW b <> putW c <> putW d+ where putW = word8 . fromIntegral . fromEnum++instance IsBoxContent BoxType where+ boxSize _ = boxSize (FourCc undefined)+ boxBuilder t =+ case t of+ StdType x -> boxBuilder x+ CustomBoxType _ -> boxBuilder (FourCc ('u','u','i','d'))++-- | When using custom types extra data must be written after the extra size+-- information. Since the box type and the optional custom box type are not+-- guaranteed to be consequtive, this type handles the /second/ part seperately.+newtype BoxTypeExtension =+ BoxTypeExtension BoxType++instance IsBoxContent BoxTypeExtension where+ boxSize (BoxTypeExtension !(StdType _)) = 0+ boxSize (BoxTypeExtension !(CustomBoxType _)) = 16 * 4+ boxBuilder (BoxTypeExtension !(StdType _)) = mempty+ boxBuilder (BoxTypeExtension !(CustomBoxType str)) =+ mconcat (map (word8 . fromIntegral . fromEnum)+ (take (16 * 4) str) +++ repeat (word8 0))++-- * 'IsBoxContent' instances+-- | An empty box content can by represented by @()@ (i.e. /unit/).+instance IsBoxContent () where+ boxSize _ = 0+ boxBuilder _ = mempty++-- | Trivial instance for 'ByteString'+instance IsBoxContent B.ByteString where+ boxSize = fromIntegral . B.length+ boxBuilder = byteString-- -- | A list, a maybe, and every other 'Foldable' of contents is a content.+ -- instance (Foldable f, IsBoxContent t) => IsBoxContent (f t) where+ -- boxSize = foldr' (\e acc -> acc + boxSize e) 0+ -- boxBuilder = foldMap boxBuilder++-- | This 'Text' instance writes a null terminated UTF-8 string.+instance IsBoxContent T.Text where+ boxSize = (1+) . fromIntegral . T.length+ boxBuilder txt = boxBuilder (T.encodeUtf8 txtNoNulls) <> word8 0+ where txtNoNulls = T.map (\c -> if c == '\0' then ' ' else c) txt++-- | This instance writes zero bytes for 'Nothing' and delegates on 'Just'.+instance IsBoxContent a => IsBoxContent (Maybe a) where+ boxSize = maybe 0 boxSize+ boxBuilder = maybe mempty boxBuilder++-- * Box concatenation++-- | Box content composition+data a :+ b = !a :+ !b++infixr 3 :+++instance (IsBoxContent p,IsBoxContent c) => IsBoxContent (p :+ c) where+ boxSize (p :+ c) = boxSize p + boxSize c+ boxBuilder (p :+ c) = boxBuilder p <> boxBuilder c++instance (Default a, Default b) => Default (a :+ b) where+ def = def :+ def++-- * Tagged boxes++instance IsBoxContent c => IsBoxContent (Tagged s c) where+ boxSize = boxSize . untag+ boxBuilder = boxBuilder . untag++-- * List Box Content++-- | A list of things that renders to a size field with the number of elements+-- and the sequence of elements. This type is index with the size field type.+newtype ListContent sizeType contentType =+ ListContent [contentType]++instance (Num sizeType, IsBoxContent sizeType, IsBoxContent contentType)+ => IsBoxContent (ListContent sizeType contentType) where+ boxSize (ListContent es) =+ boxSize (fromIntegral (length es) :: sizeType) + sum (boxSize <$> es)+ boxBuilder (ListContent es) =+ boxBuilder (fromIntegral (length es) :: sizeType)+ <> fold (boxBuilder <$> es)++instance Default (ListContent sizeTupe contentType) where+ def = ListContent []++-- * Words and Integer++instance IsBoxContent Word8 where+ boxSize _ = 1+ boxBuilder = word8++instance IsBoxContent Word16 where+ boxSize _ = 2+ boxBuilder = word16BE++instance IsBoxContent Word32 where+ boxSize _ = 4+ boxBuilder = word32BE++instance IsBoxContent Word64 where+ boxSize _ = 8+ boxBuilder = word64BE++instance IsBoxContent Int8 where+ boxSize _ = 1+ boxBuilder = int8++instance IsBoxContent Int16 where+ boxSize _ = 2+ boxBuilder = int16BE++instance IsBoxContent Int32 where+ boxSize _ = 4+ boxBuilder = int32BE++instance IsBoxContent Int64 where+ boxSize _ = 8+ boxBuilder = int64BE++-- * Boxes of Bits++instance IsBoxContent BuilderBox where+ boxSize (MkBuilderBox !s _) = fromIntegral s+ boxBuilder (MkBuilderBox _ !b) = b++-- * Type Layout Rule Matchers++-- | Mandatory, container box, exactly one+type OM b bs = ContainerBox b bs+-- | Optional, container box, zero or one+type OO b bs = OnceOptionalX (ContainerBox b bs)+-- | Mandatory, container box, one or more+type SM b bs = SomeMandatoryX (ContainerBox b bs)+-- | Optional, container box, zero or more+type SO b bs = SomeOptionalX (ContainerBox b bs)++-- | Mandatory, exactly one, no children+type OM_ b = Box b+-- | Optional, zero or one, no children+type OO_ b = OnceOptionalX (Box b)+-- | Mandatory, one or more, no children+type SM_ b = SomeMandatoryX (Box b)+-- | Optional, zero or more, no children+type SO_ b = SomeOptionalX (Box b)++----+type instance IsRuleConform (Box b) (Box r) = BoxTypeSymbol b == BoxTypeSymbol r+----+type instance IsRuleConform (Boxes bs) (Boxes rs) = IsRuleConform bs rs -- TODO+----+type instance IsRuleConform (Box b) (ContainerBox b' rules)+ = IsContainerBox b+ && IsRuleConform (Box b) (Box b')+ && IsRuleConform (ChildBoxes b) (Boxes rules)+type family IsContainerBox t :: Bool where+ IsContainerBox (ContainerBox a as) = 'True+ IsContainerBox b = 'False+type family ChildBoxes c where+ ChildBoxes (ContainerBox a as) = Boxes as
+ src/Data/ByteString/IsoBaseFileFormat/Boxes.hs view
@@ -0,0 +1,48 @@+-- | This module re-exports all modules needed to build /ISOBMFF/ documents.+module Data.ByteString.IsoBaseFileFormat.Boxes+ ( module X)+ where++import Data.ByteString.IsoBaseFileFormat.Boxes.AudioSampleEntry as X+import Data.ByteString.IsoBaseFileFormat.Boxes.ChunkOffset as X+import Data.ByteString.IsoBaseFileFormat.Boxes.DataEntryUrl as X+import Data.ByteString.IsoBaseFileFormat.Boxes.DataEntryUrn as X+import Data.ByteString.IsoBaseFileFormat.Boxes.DataInformation as X+import Data.ByteString.IsoBaseFileFormat.Boxes.DataReference as X+import Data.ByteString.IsoBaseFileFormat.Boxes.FileType as X+import Data.ByteString.IsoBaseFileFormat.Boxes.Handler as X+import Data.ByteString.IsoBaseFileFormat.Boxes.HintMediaHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.HintSampleEntry as X+import Data.ByteString.IsoBaseFileFormat.Boxes.Language as X+import Data.ByteString.IsoBaseFileFormat.Boxes.Media as X+import Data.ByteString.IsoBaseFileFormat.Boxes.MediaData as X+import Data.ByteString.IsoBaseFileFormat.Boxes.MediaHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.MediaInformation as X+import Data.ByteString.IsoBaseFileFormat.Boxes.MetaDataSampleEntry as X+import Data.ByteString.IsoBaseFileFormat.Boxes.Movie as X+import Data.ByteString.IsoBaseFileFormat.Boxes.MovieExtends as X+import Data.ByteString.IsoBaseFileFormat.Boxes.MovieExtendsHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.MovieFragment as X+import Data.ByteString.IsoBaseFileFormat.Boxes.MovieFragmentHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.MovieHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.NullMediaHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.ProgressiveDownloadInformation as X+import Data.ByteString.IsoBaseFileFormat.Boxes.SampleDescription as X+import Data.ByteString.IsoBaseFileFormat.Boxes.SampleEntry as X+import Data.ByteString.IsoBaseFileFormat.Boxes.SampleSize as X+import Data.ByteString.IsoBaseFileFormat.Boxes.SampleTable as X+import Data.ByteString.IsoBaseFileFormat.Boxes.SampleToChunk as X+import Data.ByteString.IsoBaseFileFormat.Boxes.SegmentType as X+import Data.ByteString.IsoBaseFileFormat.Boxes.Skip as X+import Data.ByteString.IsoBaseFileFormat.Boxes.SoundMediaHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.SpecificMediaHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.TimeToSample as X+import Data.ByteString.IsoBaseFileFormat.Boxes.Track as X+import Data.ByteString.IsoBaseFileFormat.Boxes.TrackExtends as X+import Data.ByteString.IsoBaseFileFormat.Boxes.TrackFragBaseMediaDecodeTime as X+import Data.ByteString.IsoBaseFileFormat.Boxes.TrackFragment as X+import Data.ByteString.IsoBaseFileFormat.Boxes.TrackFragmentHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.TrackHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.TrackRun as X+import Data.ByteString.IsoBaseFileFormat.Boxes.VideoMediaHeader as X+import Data.ByteString.IsoBaseFileFormat.Boxes.VisualSampleEntry as X
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/AudioSampleEntry.hs view
@@ -0,0 +1,30 @@+-- | Detailed audio sample description.+module Data.ByteString.IsoBaseFileFormat.Boxes.AudioSampleEntry where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Boxes.Handler+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | Fields of audio sample entries+newtype AudioSampleEntry b where+ AudioSampleEntry+ :: Constant (U32Arr "reserved" 2) '[0,0]+ :+ Template (U16 "channelcount") 2+ :+ Template (U16 "samplesize") 16+ :+ U16 "pre_defined"+ :+ Constant (U16 "reserved") 0+ :+ Template (U32 "samplerate")+ (DefaultSoundSamplerate * 65536) -- TODO implement fix point integer+ :+ b+ -> AudioSampleEntry b+ deriving (Default, IsBoxContent)++instance Functor AudioSampleEntry where+ fmap fun (AudioSampleEntry (a :+ b :+ c :+ d :+ e :+ f :+ x)) =+ AudioSampleEntry (a :+ b :+ c :+ d :+ e :+ f :+ fun x)++type DefaultSoundSamplerate = 48000++type instance GetHandlerType (AudioSampleEntry b) = 'AudioTrack+type instance BoxTypeSymbol (AudioSampleEntry b) = BoxTypeSymbol b
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/ChunkOffset.hs view
@@ -0,0 +1,52 @@+-- | A table mapping chunks to *absolute* file offsets.+-- Two variants exist: 32 or 64 bits.++module Data.ByteString.IsoBaseFileFormat.Boxes.ChunkOffset where++import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox++-- | Create a hunk offset box for 32 bit entries (@stco@). If possible use+-- this over 'ChunkOffset64'+chunkOffset32 :: [StcoEntry32] -> Box ChunkOffset32+chunkOffset32 = fullBox 0 . ChunkOffsetTable . ListContent++-- | Create a hunk offset box for 64 bit entries (@stco@). If possible use+-- 'ChunkOffset32'+chunkOffset64 :: [StcoEntry64] -> Box ChunkOffset64+chunkOffset64 = fullBox 0 . ChunkOffsetTable . ListContent++-- | Chunk offset box for 32 bit entries (@stco@). If possible use+-- this over 'ChunkOffset64'+type ChunkOffset32 = FullBox ChunkOffsetTable32 0++-- | Chunk offset box for 64 bit entries (@stco@). If possible use+-- 'ChunkOffset32'+type ChunkOffset64 = FullBox ChunkOffsetTable64 0++-- | Alias for 'ChunkOffsetTable' for 32 bit entries (@stco@)+type ChunkOffsetTable32 = ChunkOffsetTable StcoEntry32++-- | Alias for 'ChunkOffsetTable' for 64 bit entries (@co64@)+type ChunkOffsetTable64 = ChunkOffsetTable StcoEntry64++-- | A list of 'StcoEntry32' or 'StcoEntry64' entries.+newtype ChunkOffsetTable stcoEntry =+ ChunkOffsetTable (ListContent (U32 "entry_count") stcoEntry)+ deriving (Default, IsBoxContent)++-- | An entry of the 'ChunkOffsetTable' contains just the absolute file offset+-- to the chunk.+type StcoEntry32 = U32 "chunk_offset"++-- | An entry of the 'ChunkOffsetTable' contains just the absolute file offset+-- to the chunk.+type StcoEntry64 = U64 "chunk_offset"++type instance BoxTypeSymbol ChunkOffsetTable32 = "stco"+type instance BoxTypeSymbol ChunkOffsetTable64 = "co64"++instance IsBox ChunkOffsetTable32+instance IsBox ChunkOffsetTable64
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/DataEntryUrl.hs view
@@ -0,0 +1,33 @@+-- | A data references URL.+module Data.ByteString.IsoBaseFileFormat.Boxes.DataEntryUrl+ (DataEntryUrl()+ ,localMediaDataEntryUrl+ ,dataEntryUrl+ )+ where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import qualified Data.Text as T+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | A container for a URL or an indicator for local only media+newtype DataEntryUrl =+ DataEntryUrl (Maybe T.Text)+ deriving (Default,IsBoxContent)++-- | Create a 'DataEntryUrl' box for local media entry with the flag set and+-- empty content.+localMediaDataEntryUrl+ :: Box (FullBox DataEntryUrl 0)+localMediaDataEntryUrl = dataEntryUrl True Nothing++-- | Create a 'DataEntryUrl' box. The flag determines if the url is local, i.e.+-- the media data is in the same file.+dataEntryUrl+ :: Bool -> Maybe T.Text -> Box (FullBox DataEntryUrl 0)+dataEntryUrl isLocal Nothing = Box (FullBox (fromIntegral $ fromEnum isLocal) $ DataEntryUrl Nothing)+dataEntryUrl isLocal murl = Box (FullBox (fromIntegral $ fromEnum isLocal) $ DataEntryUrl murl)++instance IsBox DataEntryUrl+type instance BoxTypeSymbol DataEntryUrl = "url "
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/DataEntryUrn.hs view
@@ -0,0 +1,24 @@+-- | A table of data references (URN with optional URL.+module Data.ByteString.IsoBaseFileFormat.Boxes.DataEntryUrn+ (DataEntryUrn()+ ,dataEntryUrn+ )+ where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import qualified Data.Text as T++-- | A container for a URN and optionally a URL+newtype DataEntryUrn =+ DataEntryUrn (T.Text :+ T.Text)+ deriving (IsBoxContent)++-- | Create a 'DataEntryUrn' box. The flag determines if the url is local, i.e.+-- the media data is in the same file.+dataEntryUrn+ :: Bool -> T.Text -> T.Text -> Box (FullBox DataEntryUrn 0)+dataEntryUrn isLocal urn url = Box (FullBox (fromIntegral $ fromEnum isLocal) $ DataEntryUrn $ urn :+ url)++instance IsBox DataEntryUrn+type instance BoxTypeSymbol DataEntryUrn = "urn "
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/DataInformation.hs view
@@ -0,0 +1,18 @@+-- | Data information container declare the location of media information of a+-- 'Track' the actual location are stored in 'DataReference's.+module Data.ByteString.IsoBaseFileFormat.Boxes.DataInformation where++import Data.ByteString.IsoBaseFileFormat.Box++-- | Data information box phantom type.+data DataInformation++-- | Compose a 'DataInformation' box.+dataInformation+ :: Boxes ts -> Box (ContainerBox DataInformation ts)+dataInformation = containerBox ()++instance IsBox DataInformation where+ type BoxContent DataInformation = ()++type instance BoxTypeSymbol DataInformation = "dinf"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/DataReference.hs view
@@ -0,0 +1,38 @@+-- | A table of data references (URL/URNs). This tables is referred to by the+-- 'SampleDescription' this supports splitting a file over several files. When a+-- media file is split for transportation, this still counts as being in the+-- same file as the.+module Data.ByteString.IsoBaseFileFormat.Boxes.DataReference+ (DataReference()+ ,dataReference+ ,localMediaDataReference+ )+ where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Boxes.DataEntryUrl+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.Singletons.Prelude.List (Length)+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | A container for 'DataEntryUrl's and 'DataEntryUrn's+newtype DataReference =+ DataReference (U32 "entry_count")+ deriving (Default,IsBoxContent)++-- | Create a 'DataReference' box.+dataReference+ :: KnownNat (Length ts)+ => Boxes ts -> Box (ContainerBox (FullBox DataReference 0) ts)+dataReference bs =+ containerBox (FullBox 0 $ DataReference (typeListLength bs))+ bs++-- | Create a 'DataReference' box with a single local media entry.+localMediaDataReference+ :: Box (ContainerBox (FullBox DataReference 0) '[Box (FullBox DataEntryUrl 0)])+localMediaDataReference = dataReference (singletonBox localMediaDataEntryUrl)++instance IsBox DataReference+type instance BoxTypeSymbol DataReference = "dref"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/FileType.hs view
@@ -0,0 +1,25 @@+module Data.ByteString.IsoBaseFileFormat.Boxes.FileType where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | File Type Box+instance IsBox FileType where+ type BoxContent FileType = FileType+type instance BoxTypeSymbol FileType = "ftyp"++-- | Create a 'FileTypeBox' from a major brand, a minor version and a list of+-- compatible brands+fileTypeBox :: FileType -> Box FileType+fileTypeBox = Box++-- | Contents of a 'ftyp' box are some 'FourCc' /brands/ and a version.+data FileType =+ FileType {majorBrand :: !FourCc -- TODO use U32String+ ,minorVersion :: !Word32 -- TODO use U32String+ ,compatibleBrands :: ![FourCc]} -- TODO use U32String++instance IsBoxContent FileType where+ boxSize (FileType maj _ver comps) = boxSize maj + 4 + sum (boxSize <$> comps)+ boxBuilder (FileType maj ver comps) =+ boxBuilder maj <> word32BE ver <> mconcat (boxBuilder <$> comps)
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/Handler.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE UndecidableInstances #-}+-- | A 'Handler' box describes how a track is presentation and by which sensory+-- process it is perceived, e.g. audio, video, text, see 'HandlerType'+module Data.ByteString.IsoBaseFileFormat.Boxes.Handler+ (Handler()+ ,handler+ ,audioTrackHandler+ ,namedAudioTrackHandler+ ,videoTrackHandler+ ,namedVideoTrackHandler+ ,hintTrackHandler+ ,namedHintTrackHandler+ ,timedMetadataTrackHandler+ ,namedTimedMetadataTrackHandler+ ,auxilliaryVideoTrackHandler+ ,namedAuxilliaryVideoTrackHandler+ ,HandlerType(..)+ ,GetHandlerType+ ,HandlerTypeCode)+ where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import qualified Data.Text as T+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | Handler box fields. A handler box may also contain a null terminated+-- description text in UTF-8. The 'T.Text' parameter is a human readable name of+-- the track type for debugging.+newtype Handler (t :: HandlerType) where+ Handler ::+ Constant (U32 "pre_defined") 0 :++ Constant (U32Text "t") (HandlerTypeCode t) :++ Constant (U32Arr "reserved" 3) '[0, 0, 0] :++ Template T.Text "" -> Handler t++deriving instance (KnownSymbol (HandlerTypeCode t)) => IsBoxContent (Handler t)+deriving instance Default (Handler t)++-- | A 'Handler' can be exactly one of these.+data HandlerType+ = VideoTrack+ | AudioTrack+ | HintTrack+ | TimedMetaDataTrack+ | AuxilliaryVideoTrack++-- | Return 'HandlerType' for 'BoxLayout' checking.+type family GetHandlerType t :: HandlerType++type family HandlerTypeCode (handlertype :: HandlerType) :: Symbol where+ HandlerTypeCode 'VideoTrack = "vide"+ HandlerTypeCode 'AudioTrack = "soun"+ HandlerTypeCode 'HintTrack = "hint"+ HandlerTypeCode 'TimedMetaDataTrack = "meta"+ HandlerTypeCode 'AuxilliaryVideoTrack = "auxv"++-- | Create a 'Handler' box.+handler+ :: (KnownSymbol (HandlerTypeCode t))+ => Handler t -> Box (FullBox (Handler t) 0)+handler = fullBox 0++-- | Create 'Handler' fields for 'AudioTrack's with @"Audio Track"@ as @name@.+audioTrackHandler :: Handler 'AudioTrack+audioTrackHandler = namedAudioTrackHandler "Audio Track"++-- | Create 'Handler' fields for 'AudioTrack's, with the given name.+namedAudioTrackHandler :: T.Text -> Handler 'AudioTrack+namedAudioTrackHandler txt = Handler (def :+ def :+ def :+ Custom txt)++-- | Create 'Handler' fields for 'VideoTrack's with @"Video Track"@ as @name@.+videoTrackHandler :: Handler 'VideoTrack+videoTrackHandler = namedVideoTrackHandler "Video Track"++-- | Create 'Handler' fields for 'VideoTrack's, with the given name.+namedVideoTrackHandler :: T.Text -> Handler 'VideoTrack+namedVideoTrackHandler txt = Handler (def :+ def :+ def :+ Custom txt)++-- | Create 'Handler' fields for 'HintTrack's with @"Hint Track"@ as @name@.+hintTrackHandler :: Handler 'HintTrack+hintTrackHandler = namedHintTrackHandler "Hint Track"++-- | Create 'Handler' fields for 'HintTrack's, with the given name.+namedHintTrackHandler :: T.Text -> Handler 'HintTrack+namedHintTrackHandler txt = Handler (def :+ def :+ def :+ Custom txt)++-- | Create 'Handler' fields for 'TimedMetadataTrack's with @"TimedMetadata+-- Track"@ as @name@.+timedMetadataTrackHandler :: Handler 'TimedMetaDataTrack+timedMetadataTrackHandler =+ namedTimedMetadataTrackHandler "Timed Metadata Track"++-- | Create 'Handler' fields for 'TimedMetadataTrack's, with the given name.+namedTimedMetadataTrackHandler :: T.Text -> Handler 'TimedMetaDataTrack+namedTimedMetadataTrackHandler txt =+ Handler (def :+ def :+ def :+ Custom txt)++-- | Create 'Handler' fields for 'AuxilliaryVideoTrack's with+-- @"AuxilliaryVideoTrack Track"@ as @name@.+auxilliaryVideoTrackHandler :: Handler 'AuxilliaryVideoTrack+auxilliaryVideoTrackHandler =+ namedAuxilliaryVideoTrackHandler "Timed Metadata Track"++-- | Create 'Handler' fields for 'AuxilliaryVideoTrack's, with the given name.+namedAuxilliaryVideoTrackHandler :: T.Text -> Handler 'AuxilliaryVideoTrack+namedAuxilliaryVideoTrackHandler txt =+ Handler (def :+ def :+ def :+ (Custom txt))++instance (KnownSymbol (HandlerTypeCode t)) => IsBox (Handler t)++type instance BoxTypeSymbol (Handler t) = "hdlr"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/HintMediaHeader.hs view
@@ -0,0 +1,29 @@+-- | Media-independent properties of a hint tracks media content.+module Data.ByteString.IsoBaseFileFormat.Boxes.HintMediaHeader where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Boxes.Handler+import Data.ByteString.IsoBaseFileFormat.Boxes.SpecificMediaHeader+import Data.ByteString.IsoBaseFileFormat.ReExports++type instance MediaHeaderFor 'HintTrack = HintMediaHeader++-- | Hint data box.+newtype HintMediaHeader where+ HintMediaHeader+ :: U16 "maxPDUsize"+ :+ U16 "avgPDUsize"+ :+ U16 "maxbitrate"+ :+ U16 "avgbitrate"+ :+ U32 "reserved"+ -> HintMediaHeader+ deriving (Default, IsBoxContent)++-- | Create a hint media header data box.+hintMediaHeader :: HintMediaHeader -> Box (FullBox HintMediaHeader 0)+hintMediaHeader = fullBox 0++instance IsBox HintMediaHeader+type instance BoxTypeSymbol HintMediaHeader = "hmhd"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/HintSampleEntry.hs view
@@ -0,0 +1,15 @@+{-# LANGUAGE UndecidableInstances #-}+-- | Format of the hint track as well as streaming protocol settings.+module Data.ByteString.IsoBaseFileFormat.Boxes.HintSampleEntry where++import Data.ByteString.IsoBaseFileFormat.Boxes.SampleEntry ()+import Data.ByteString.IsoBaseFileFormat.Boxes.Handler+import Data.ByteString.IsoBaseFileFormat.Box++-- | Protocol specific data. To create 'HintSampleEntry's a protocol specific+-- 'HintFields' instance must be provided+newtype HintSampleEntry protocol where+ HintSampleEntry :: protocol -> HintSampleEntry protocol++type instance GetHandlerType (HintSampleEntry protocol) = 'HintTrack+type instance BoxTypeSymbol (HintSampleEntry protocol) = BoxTypeSymbol protocol
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/Language.hs view
@@ -0,0 +1,39 @@+-- | Boxfield indicating an ISO 639-2 alpha-3 language code.+module Data.ByteString.IsoBaseFileFormat.Boxes.Language (Language, mkLanguage) where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | A Boxfield contains an ISO 639-2-T alpha-3 language code, which is encoded+-- as a single bit followed by three 5 bit words, each representing the+-- lower-case character from the language code subtracted by 0x60.+-- If available, the code for /terminoligy/ purposes should be used (T-code).+newtype Language = Language Word16 -- TODO use bitrecords++-- | Create a 'Language', throw a runtime error when bad characters were given.+-- The characters must be in @a - z@ (lower-case!), there must be exactly three+-- characters.+mkLanguage :: String -> Language+mkLanguage [c0,c1,c2] |+ c0 > toEnum 0x60 && c0 < toEnum 0x7b &&+ c1 > toEnum 0x60 && c1 < toEnum 0x7b &&+ c2 > toEnum 0x60 && c2 < toEnum 0x7b =+ let+ s :: Char -> Word16+ s c = fromIntegral (fromEnum c) - 0x60+ in Language (shiftL (s c0) 10 .|. shiftL (s c1) 5 .|. s c2)++mkLanguage other =+ error $+ "Invalid ISO 639-2 language code: " ++ show other +++ ". The code must consist of three lower-case letters between 'a' .. 'z'."++instance IsString Language where+ fromString = mkLanguage++instance Default Language where+ def = mkLanguage "und"++instance IsBoxContent Language where+ boxSize _ = 2+ boxBuilder (Language c) = word16BE c
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/Media.hs view
@@ -0,0 +1,16 @@+-- | Track specific media information container box.+module Data.ByteString.IsoBaseFileFormat.Boxes.Media where++import Data.ByteString.IsoBaseFileFormat.Box++-- | Media data box+data Media++instance IsBox Media where+ type BoxContent Media = ()++type instance BoxTypeSymbol Media = "mdia"++-- | Create a 'MediaDataBox' from a strict 'ByteString'+media :: Boxes ts -> Box (ContainerBox Media ts)+media = containerBox ()
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/MediaData.hs view
@@ -0,0 +1,19 @@+-- | Media data box+module Data.ByteString.IsoBaseFileFormat.Boxes.MediaData where++import Data.ByteString.IsoBaseFileFormat.Box+import qualified Data.ByteString as B++-- | Media data box phantom type+newtype MediaData = MediaData B.ByteString+ deriving (Show, IsBoxContent)++instance IsBox MediaData where+ -- | Contents of a 'mdat' box are just bytes from a strict 'ByteString'+ type BoxContent MediaData = MediaData++type instance BoxTypeSymbol MediaData = "mdat"++-- | Create a 'MediaDataBox' from a strict 'ByteString'+mediaData :: MediaData -> Box MediaData+mediaData = Box
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/MediaHeader.hs view
@@ -0,0 +1,29 @@+-- | Media-independent properties of a tracks media content.+module Data.ByteString.IsoBaseFileFormat.Boxes.MediaHeader where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Boxes.Language+import Data.ByteString.IsoBaseFileFormat.Util.Time+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | Media header data box.+newtype MediaHeader (v :: Nat) where+ MediaHeader+ :: Timing v :+ Language :+ Constant (I16 "pre_defined") 0+ -> MediaHeader v++-- | Create a 'MediaHeader' box.+mediaHeader+ :: (KnownNat v)+ => MediaHeader v -> Box (FullBox (MediaHeader v) v)+mediaHeader = fullBox 0++instance IsBox (MediaHeader v)++type instance BoxTypeSymbol (MediaHeader v) = "mdhd"++instance IsBoxContent (MediaHeader v) where+ boxSize (MediaHeader c) = boxSize c+ boxBuilder (MediaHeader c) = boxBuilder c
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/MediaInformation.hs view
@@ -0,0 +1,18 @@+-- | Media information container for all boxes declaring the specific+-- technicalities of the media data associated with a 'Track'.+module Data.ByteString.IsoBaseFileFormat.Boxes.MediaInformation where++import Data.ByteString.IsoBaseFileFormat.Box++-- | Media information box type.+data MediaInformation++-- | Compose a 'MediaInformation' box.+mediaInformation+ :: Boxes ts -> Box (ContainerBox MediaInformation ts)+mediaInformation = containerBox ()++instance IsBox MediaInformation where+ type BoxContent MediaInformation = ()++type instance BoxTypeSymbol MediaInformation = "minf"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/MetaDataSampleEntry.hs view
@@ -0,0 +1,102 @@+module Data.ByteString.IsoBaseFileFormat.Boxes.MetaDataSampleEntry where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Boxes.Handler+import qualified Data.Text as T+import qualified Data.ByteString as B+import Data.Tagged ()+import Data.Default ()+import Data.ByteString.IsoBaseFileFormat.ReExports++-- * Generat meta data sample entry++type instance GetHandlerType (MetaDataCoding c) = 'TimedMetaDataTrack+type instance BoxTypeSymbol (MetaDataCoding c) = c++-- | A coproduct of meta data codings (XML, Text, ...)+data family MetaDataCoding (c :: Symbol)++-- * XML Meta Data++-- | The xml meta data samples declaration+newtype instance MetaDataCoding "metx" =+ XMLMetaDataSampleEntry+ ( Tagged "content_encoding" T.Text+ :+ Tagged "namespace" T.Text+ :+ Tagged "schema_location" T.Text+ :+ Maybe (Box BitRate))+ deriving (IsBoxContent, Default)++-- * Text Meta Data++-- | The plain text meta data samples declaration+newtype instance MetaDataCoding "mett" =+ TextMetaDataSampleEntry+ ( Tagged "content_encoding" T.Text+ :+ Tagged "mime_format" T.Text+ :+ Maybe (Box BitRate))+ deriving (IsBoxContent, Default)++-- * URI based meta data++-- | The URI meta data samples declaration+newtype instance MetaDataCoding "urim" =+ UriMetaDataSampleEntry+ ( Tagged "the_label" (Box Uri)+ :+ Tagged "init" (Maybe (Box UriInit))+ :+ Maybe (Box BitRate))+ deriving (IsBoxContent, Default)++-- * Uri Box++-- | Contents of a 'Uri' box.+type Uri = FullBox UriField 0++-- | The URI that's inside the box+newtype UriField =+ UriField (Tagged "theURI" T.Text)+ deriving (Default, IsBoxContent)++-- | Make box with a UTF-8 URI.+uriBox :: T.Text -> Box Uri+uriBox t = fullBox 0 (UriField (Tagged t))++type instance BoxTypeSymbol UriField = "uri "++instance IsBox UriField++-- * Uri-Init Box++-- | Opaque data for the applications processing 'Uri' meta data.+newtype UriInitField =+ UriInitField (Tagged "uri_initialization_data" B.ByteString)+ deriving (Default, IsBoxContent)++-- | A 'FullBox' an UriInitField.+type UriInit = FullBox UriInitField 0++-- | Make box an 'UriInitField'+uriInitBox :: B.ByteString -> Box UriInit+uriInitBox = fullBox 0 . UriInitField . Tagged++type instance BoxTypeSymbol UriInitField = "uriI"++instance IsBox UriInitField++-- * Bit Rate Box++-- | Create a box for bit rates. Every 'SampleEntry' instance for meta data+-- may contain this box at the end.+bitRateBox :: BitRate -> Box BitRate+bitRateBox = Box++-- | Bitrate info for meta data samples+newtype BitRate =+ BitRate (U32 "bufferSizeDB" :+ U32 "maxBitrate" :+ U32 "avgBitrate")+ deriving (Default, IsBoxContent)++type instance BoxTypeSymbol BitRate = "btrt"++instance IsBox BitRate
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/Movie.hs view
@@ -0,0 +1,18 @@+-- | Meta data for a presentation of a /movie/.+module Data.ByteString.IsoBaseFileFormat.Boxes.Movie where++import Data.ByteString.IsoBaseFileFormat.Box++-- | Compose a set of boxes into a 'Movie'+movie :: Boxes ts -> Box (ContainerBox Movie ts)+movie = containerBox ()++-- | The metadata for a presentation, a single 'Movie' which occurs only once+-- and top-level. It is pretty empty on it's own, but it contains nested boxes+-- with all the relevant meta data.+data Movie++instance IsBox Movie where+ type BoxContent Movie = ()++type instance BoxTypeSymbol Movie = "moov"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieExtends.hs view
@@ -0,0 +1,22 @@+-- | Indicate the possibility of 'MovieFragment' boxes in the current file; the+-- total samples are comprised of those fragments, so they must be parsed by the+-- player.+module Data.ByteString.IsoBaseFileFormat.Boxes.MovieExtends where++import Data.ByteString.IsoBaseFileFormat.Box++-- * @mvex@ Box++-- | Indicate that the media fragmented; more detailed info is obtained by the+-- extra boxes contained in this box, e.g. the 'MovieExtendsHeader' or the+-- 'TrackExtends' boxes.+movieExtends :: Boxes ts -> Box (ContainerBox MovieExtends ts)+movieExtends = containerBox ()++-- | Phantom type to indicate @mvex@ boxes, see ISO-14496-12 8.8.1+data MovieExtends++instance IsBox MovieExtends where+ type BoxContent MovieExtends = ()++type instance BoxTypeSymbol MovieExtends = "mvex"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieExtendsHeader.hs view
@@ -0,0 +1,27 @@+-- | Overall duration of fragmented media.+module Data.ByteString.IsoBaseFileFormat.Boxes.MovieExtendsHeader where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Util.Versioned+import Data.ByteString.IsoBaseFileFormat.ReExports++-- * @mehd@ Box++-- | Construct a 'MovieHeader' box.+movieExtendsHeader+ :: (KnownNat version)+ => MovieExtendsHeader version -> Box (FullBox (MovieExtendsHeader version) version)+movieExtendsHeader = fullBox 0++-- | Movie length incorporating all fragments.+newtype MovieExtendsHeader (version :: Nat) where+ MovieExtendsHeader ::+ Versioned (U32 "fragment_duration") (U64 "fragment_duration") version+ -> MovieExtendsHeader version+ deriving (IsBoxContent)++instance IsBox (MovieExtendsHeader version)++type instance BoxTypeSymbol (MovieExtendsHeader v) = "mehd"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieFragment.hs view
@@ -0,0 +1,20 @@+-- | TODO: this box is a bit of a hack due to the deadline pressure...+module Data.ByteString.IsoBaseFileFormat.Boxes.MovieFragment where++import Data.ByteString.IsoBaseFileFormat.Box++-- | Compose a set of boxes into a 'MovieFragment'+movieFragment :: Boxes ts -> Box (ContainerBox MovieFragment ts)+movieFragment = containerBox ()++-- | Movie Fragments+data MovieFragment++instance IsBox MovieFragment where+ type BoxContent MovieFragment = ()++type instance BoxTypeSymbol MovieFragment = "moof"++-- | Return the static size of the empty box+movieFragmentStaticSize :: Num a => a+movieFragmentStaticSize = fromBoxSize 0 (boxSize (movieFragment NoBoxes))
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieFragmentHeader.hs view
@@ -0,0 +1,28 @@+-- | Meta data for a presentation of a /movie fragment/.+module Data.ByteString.IsoBaseFileFormat.Boxes.MovieFragmentHeader where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.Default++-- * @mvhd@ Box++-- | Construct a 'MovieFragmentHeader' box.+movieFragmentHeader+ :: MovieFragmentHeader-> Box (FullBox MovieFragmentHeader 0)+movieFragmentHeader = fullBox 0++-- | Movie fragment meta data+-- The sequence number, just an increasing number, of this fragment+newtype MovieFragmentHeader where+ MovieFragmentHeader :: U32 "sequence_number" -> MovieFragmentHeader+ deriving (IsBoxContent, Default)++instance IsBox MovieFragmentHeader++type instance BoxTypeSymbol MovieFragmentHeader = "mfhd"++-- | Return the static size of the empty box+movieFragmentHeaderStaticSize :: Num a => a+movieFragmentHeaderStaticSize = fromBoxSize 0 (boxSize (movieFragmentHeader def))
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/MovieHeader.hs view
@@ -0,0 +1,66 @@+-- | Meta data for a presentation of a /movie/.+module Data.ByteString.IsoBaseFileFormat.Boxes.MovieHeader where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Util.Time+import Data.ByteString.IsoBaseFileFormat.ReExports++-- * @mvhd@ Box++-- | Construct a 'MovieHeader' box.+movieHeader+ :: (KnownNat version)+ => MovieHeader version -> Box (FullBox (MovieHeader version) version)+movieHeader = fullBox 0++-- | Movie meta data, indexed by a version.+--+-- The @rate@ field is a 16.16 fix point number, 1.0 indicates the preferred+-- normal playback rate. @volume@ is also a fix point number, albeit smaller+-- with only 8.8. A volume of 1.0 is the full (loudest) volume. @matrix@ is the+-- video image transformation matrix. The @next_track_ID@ indicates the next+-- available track id, if the track ID is (0xFFFFFFFF) then the system must+-- search through all tracks associated with this presentation and figure it+-- out, 0 is not allowed.+newtype MovieHeader (version :: Nat) where+ MovieHeader ::+ Timing version+ :+ Template (I32 "rate") 0x00010000+ :+ Template (I16 "volume") 0x0100+ :+ Constant (I16 "reserved") 0+ :+ Constant (I32Arr "reserved" 2) '[0,0]+ :+ Template (I32Arr "matrix" 9)+ '[65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824]+ :+ Template (U32Arr "pre_defined" 6) '[0,0,0,0,0,0]+ :+ Template (U32 "next_track_ID") 0xFFFFFFFF+ -> MovieHeader version+ deriving (IsBoxContent)++-- | Time and timing information about a movie (32bit version).+type MovieHeaderTimesV0 = MovieHeaderTimes+ (Scalar Word32)+ (Template (U32 "duration") 0xffffffff)++-- | Time and timing information about a movie (64bit version).+type MovieHeaderTimesV1 = MovieHeaderTimes+ (Scalar Word64)+ (Template (U64 "duration") 0xffffffffffffffff)++-- | Time and timing information about a movie.+--+-- The creation/modification times are in seconds since midnight, Jan. 1, 1904,+-- in UTC time. Time scale declares the time coordinate system, it specifies the+-- number of time units that pass one second. The time coordinate system is used+-- by e.g. the duration field, which by the way contains the duration of the+-- longest track, if known, or simply the equivalent of 1s.+type MovieHeaderTimes uint dur =+ uint "creation_time"+ :+ uint "modification_time"+ :+ TimeScale+ :+ dur++instance IsBox (MovieHeader version)++type instance BoxTypeSymbol (MovieHeader v) = "mvhd"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/NullMediaHeader.hs view
@@ -0,0 +1,22 @@+-- | Indicate that a tracks media content is /null/ or /dummy/ media.+module Data.ByteString.IsoBaseFileFormat.Boxes.NullMediaHeader where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | Null header data box.+data NullMediaHeader = NullMediaHeader++-- | Create a null media header data box.+nullMediaHeader :: Box (FullBox NullMediaHeader 0)+nullMediaHeader = fullBox 0 NullMediaHeader++instance IsBox NullMediaHeader+type instance BoxTypeSymbol NullMediaHeader = "nmhd"++instance Default NullMediaHeader where+ def = NullMediaHeader+instance IsBoxContent NullMediaHeader where+ boxSize _ = 0+ boxBuilder _ = mempty
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/ProgressiveDownloadInformation.hs view
@@ -0,0 +1,24 @@+module Data.ByteString.IsoBaseFileFormat.Boxes.ProgressiveDownloadInformation+ where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox++-- | A Box with progressive download information+data ProgressiveDownload++instance IsBox ProgressiveDownload where+ type BoxContent ProgressiveDownload = ProgressiveDownloadContent++type instance BoxTypeSymbol ProgressiveDownload = "pdin"++-- | Information for progressive media data download/playback encompasses the+-- delay for initial playback and expected download bit rate.+type ProgressiveDownloadContent = U32 "rate" :+ U32 "delay"++-- | Construct a @pdin@ box.+pdinBox+ :: ProgressiveDownloadContent+ -> Box (FullBox ProgressiveDownload 0)+pdinBox = fullBox 0
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/SampleDescription.hs view
@@ -0,0 +1,28 @@+-- | Detailed information about the coced, as well as coding specific+-- initialization. Depending on the 'Trak' media type different kinds of sample+-- entry boxes are entailed.+module Data.ByteString.IsoBaseFileFormat.Boxes.SampleDescription where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.Singletons.Prelude.List (Length)+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | A sample table contains no fieds.+newtype SampleDescription =+ SampleDescription (U32 "entry_count")+ deriving (Default,IsBoxContent)++-- | Create a hint media header data box.+sampleDescription+ :: (KnownNat (Length ts))+ => Boxes ts+ -> Box (ContainerBox (FullBox SampleDescription 0) ts)+sampleDescription bs =+ containerBox (FullBox 0 $ SampleDescription (typeListLength bs))+ bs++instance IsBox SampleDescription++type instance BoxTypeSymbol SampleDescription = "stsd"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/SampleEntry.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE UndecidableInstances #-}+-- | Connect a sample description to a data reference via an index into the data+-- reference entry table.+module Data.ByteString.IsoBaseFileFormat.Boxes.SampleEntry where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Boxes.Handler+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | Create a 'SampleEntry' 'Box' from the data reference index and the+-- 'HandlerType' specific 'SampleEntry' instance.+sampleEntry+ :: U16 "data_reference_index"+ -> handlerSpecific+ -> Box (SampleEntry handlerSpecific)+sampleEntry i se = Box (SampleEntry (Constant :+ i :+ se))++-- | A common header for all specific sample entries, the 'BoxContent' of the+-- abstract 'SampleEntry' is @SampleEntry (SampleEntry h f)@.+newtype SampleEntry handlerSpecific where+ SampleEntry+ :: Constant (U8Arr "reserved" 6) '[0,0,0,0,0,0]+ :+ U16 "data_reference_index"+ :+ handlerSpecific+ -> SampleEntry handlerSpecific++deriving instance IsBoxContent handlerSpecific =>+ IsBoxContent (SampleEntry handlerSpecific)++deriving instance Default handlerSpecific =>+ Default (SampleEntry handlerSpecific)++-- | Use this in 'IsMediaFileFormat's 'BoxLayout' to range over any specific+-- 'SampleEntry', disregarding the second parameter (that indicates low-level+-- format, protocol or codec characteristics). Add a 'GetHandlerType' instance for+-- 'MatchSampleEntry' to match a sample type specific entry.+data MatchSampleEntry :: HandlerType -> Type++type instance+ IsRuleConform (Box (SampleEntry handlerSpecificEntry))+ (MatchSampleEntry handlerType) =+ HandlerTypeCode (GetHandlerType handlerSpecificEntry) == HandlerTypeCode handlerType++-- | The 'BoxTypeSymbol' of sample entry is exactly the @format@ type index of+-- the 'SampleEntry' family.+type instance+ BoxTypeSymbol (SampleEntry handlerSpecific) = BoxTypeSymbol handlerSpecific++instance (IsBoxContent handlertype+ ,KnownSymbol (BoxTypeSymbol handlertype))+ => IsBox (SampleEntry handlertype)
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/SampleSize.hs view
@@ -0,0 +1,42 @@+-- | A box containing the sample count and sample size table.+module Data.ByteString.IsoBaseFileFormat.Boxes.SampleSize where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | Content type of the 'FullBox' containing the 'SampleSize'.+type SampleSize = FullBox SampleSizeTable 0++-- | Create a 'SampleSize' box for the case where all sample sizes are equal, no+-- table with the size of each sample is required.+fixedSampleSize :: Word32 -> Word32 -> Box SampleSize+fixedSampleSize sampleSize sampleCount =+ fullBox 0 $ FixedSampleSize sampleSize sampleCount++-- | Create a 'SampleSize' box for the case where samples have different sizes.+-- The list MUST contain an entry for **every sample** in the media file.+individualSampleSizes :: [Word32] -> Box SampleSize+individualSampleSizes sampleSizes =+ fullBox 0 $+ SampleSizeTable (ListContent sampleSizes)++-- | The 'SampleSize' box content. If @sampleSize == 0@ then each sample has its+-- own size and the @table@ field contains an entry for **every** sample, the+-- number of entries is stored in @sampleCount@. Otherwise the samples are+-- assumed to all have the same size: @sampleSize@.+data SampleSizeTable =+ FixedSampleSize { sampleSize :: !Word32, sampleCount :: !Word32 }+ | SampleSizeTable !(ListContent Word32 Word32)++instance IsBox SampleSizeTable++type instance BoxTypeSymbol SampleSizeTable = "stsz"++instance IsBoxContent SampleSizeTable where+ boxSize (FixedSampleSize _ _) = 8+ boxSize (SampleSizeTable entries) = 4 + boxSize entries+ boxBuilder (FixedSampleSize size count) = word32BE size <> word32BE count+ boxBuilder (SampleSizeTable entries) = word32BE 0 <> boxBuilder entries++-- TODO implement compact sample size box (stz2)
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/SampleTable.hs view
@@ -0,0 +1,19 @@+-- | Time and indexing information for the media samples in a track,a sample+-- table can reference 'DataReference' boxes via a 'SampleTableDescription'.+--+-- **NOTE** A /sample/ referes to a **frame** e.g. a video frame frame or a+-- block of audio samples.+module Data.ByteString.IsoBaseFileFormat.Boxes.SampleTable where++import Data.ByteString.IsoBaseFileFormat.Box++-- | A sample table contains no fieds.+data SampleTable = SampleTable++-- | Create a hint media header data box.+sampleTable :: Boxes ts -> Box (ContainerBox SampleTable ts)+sampleTable = containerBox ()++instance IsBox SampleTable where+ type BoxContent SampleTable = ()+type instance BoxTypeSymbol SampleTable = "stbl"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/SampleToChunk.hs view
@@ -0,0 +1,32 @@+-- | The chunks into which samples are grouped, can vary in size, as can the+-- samples within a chunk. This compact table contains the info to find the+-- chunk and offset within of a sample.+module Data.ByteString.IsoBaseFileFormat.Boxes.SampleToChunk where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | An alias for the box content type.+type SampleToChunk = FullBox SampleToChunkTable 0++-- | Define an entry of the compact table. An entry represents a group of chunks+-- of the same size and the same 'SampleDescription'. The @first_chunk@ is an+-- index of the first chunk in a sequence of chunks correspondiing to an entry.+-- The very first entry has an index of 1.+type StscEntry =+ U32 "first_chunk" :+ U32 "samples_per_chunk" :+ U32 "sample_description_index"++-- | A compact table to map decoding time to sample number.+newtype SampleToChunkTable =+ SampleToChunkTable (ListContent (U32 "entry_count") StscEntry)+ deriving (Default, IsBoxContent)++-- | Create a hint media header data box.+sampleToChunk :: [StscEntry] -> Box SampleToChunk+sampleToChunk = fullBox 0 . SampleToChunkTable . ListContent++instance IsBox SampleToChunkTable++type instance BoxTypeSymbol SampleToChunkTable = "stsc"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/SegmentType.hs view
@@ -0,0 +1,25 @@+module Data.ByteString.IsoBaseFileFormat.Boxes.SegmentType where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | Segment Type Box+instance IsBox SegmentType where+ type BoxContent SegmentType = SegmentType+type instance BoxTypeSymbol SegmentType = "styp"++-- | Create a 'SegmentTypeBox' from a major brand, a minor version and a list of+-- compatible brands+segmentTypeBox :: SegmentType -> Box SegmentType+segmentTypeBox = Box++-- | Contents of a 'styp' box are some 'FourCc' /brands/ and a version.+data SegmentType =+ SegmentType {majorBrand :: !FourCc -- TODO use U32String+ ,minorVersion :: !Word32 -- TODO use U32String+ ,compatibleBrands :: ![FourCc]} -- TODO use U32String++instance IsBoxContent SegmentType where+ boxSize (SegmentType maj _ver comps) = boxSize maj + 4 + sum (boxSize <$> comps)+ boxBuilder (SegmentType maj ver comps) =+ boxBuilder maj <> word32BE ver <> mconcat (boxBuilder <$> comps)
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/Skip.hs view
@@ -0,0 +1,22 @@+-- | A filler box with a specific size.+module Data.ByteString.IsoBaseFileFormat.Boxes.Skip where++import qualified Data.ByteString as B+import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | Contents of a 'skip' box are just any number of filler bytes.+newtype Skip = Skip B.ByteString++instance IsBox Skip where+ type BoxContent Skip = Skip++type instance BoxTypeSymbol Skip = "skip"++-- | Create a 'Skip' with a given size.+skipBox :: Skip -> Box Skip+skipBox = Box++instance IsBoxContent Skip where+ boxSize (Skip bs) = fromIntegral $ B.length bs+ boxBuilder (Skip bs) = byteString bs
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/SoundMediaHeader.hs view
@@ -0,0 +1,26 @@+-- | Media-independent properties of a tracks sound content.+module Data.ByteString.IsoBaseFileFormat.Boxes.SoundMediaHeader where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Boxes.Handler+import Data.ByteString.IsoBaseFileFormat.Boxes.SpecificMediaHeader+import Data.ByteString.IsoBaseFileFormat.ReExports++type instance MediaHeaderFor 'AudioTrack = SoundMediaHeader++-- | Sound header data box.+newtype SoundMediaHeader where+ SoundMediaHeader+ :: Template (I16 "balance") 0+ :+ Constant (U16 "reserved") 0+ -> SoundMediaHeader+ deriving (Default, IsBoxContent)++-- | Create a sound media header data box.+soundMediaHeader :: SoundMediaHeader -> Box (FullBox SoundMediaHeader 0)+soundMediaHeader = fullBox 0++instance IsBox SoundMediaHeader+type instance BoxTypeSymbol SoundMediaHeader = "smhd"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/SpecificMediaHeader.hs view
@@ -0,0 +1,11 @@+-- | Select the specific media header from the 'HandlerType'+module Data.ByteString.IsoBaseFileFormat.Boxes.SpecificMediaHeader where++--+-- import Data.ByteString.IsoBaseFileFormat.Boxes.VideoMediaHeader+import Data.ByteString.IsoBaseFileFormat.Boxes.Handler+--++-- | An open type family to select the specific media header from the+-- 'HandlerType'+type family MediaHeaderFor (t :: HandlerType)
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/TimeToSample.hs view
@@ -0,0 +1,28 @@+-- | A table assigns each video frame or audio sample block, this is useful for+-- seeking in complex media files.+module Data.ByteString.IsoBaseFileFormat.Boxes.TimeToSample where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | An alias for the box content type.+type TimeToSample = FullBox TimeToSampleTable 0++-- | Define an entry of the compact table, that specifes the namber of samples+-- with the same time delta. (All this to help random seeking in media).+type SttsEntry = U32 "sample_count" :+ U32 "sample_delta"++-- | A compact table to map decoding time to sample number.+newtype TimeToSampleTable =+ TimeToSampleTable (ListContent (U32 "entry_count") SttsEntry)+ deriving (Default, IsBoxContent)++-- | Create a hint media header data box.+timeToSample :: [SttsEntry] -> Box TimeToSample+timeToSample = fullBox 0 . TimeToSampleTable . ListContent++instance IsBox TimeToSampleTable++type instance BoxTypeSymbol TimeToSampleTable = "stts"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/Track.hs view
@@ -0,0 +1,18 @@+-- | Meta data for a presentation of a /movie/.+module Data.ByteString.IsoBaseFileFormat.Boxes.Track where++import Data.ByteString.IsoBaseFileFormat.Box++-- * @trak@ Box+-- | Compose a 'Track' box from the given boxes.+track+ :: Boxes ts -> Box (ContainerBox Track ts)+track = containerBox ()++-- | Container box for tracks.+data Track++instance IsBox Track where+ type BoxContent Track = ()++type instance BoxTypeSymbol Track = "trak"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackExtends.hs view
@@ -0,0 +1,62 @@+-- | Default values for duration, size and other metadata of track fragments.+-- This might allow a more efficient transfer and decoding of media.+module Data.ByteString.IsoBaseFileFormat.Boxes.TrackExtends where++import Data.ByteString.IsoBaseFileFormat.Box++import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.ReExports++-- * @trex@ Box++-- | Construct a 'TrackExtends' box, with all durations and sizes set to @0@.+-- Only the track index and the index into the sample description table is+-- passed set.+trackExtendsUnknownDuration :: Word32 -> Word32 -> Box (FullBox TrackExtends 0)+trackExtendsUnknownDuration trackId descrIndex = trackExtends trackId descrIndex+ 0 0 0 0 0 0 0 False 0++-- | Construct a 'TrackExtends' box from all its parameters+trackExtends+ :: Word32 -- ^ Track ID+ -> Word32 -- ^ Sample Description Index+ -> Word32 -- ^ Sample Duration+ -> Word32 -- ^ Sample Size+ -> B 2 -- ^ is leading+ -> B 2 -- ^ sample depends on+ -> B 2 -- ^ sample is depended on+ -> B 2 -- ^ sample has redundancy+ -> B 3 -- ^ padding value+ -> Bool -- ^ is non sync sample+ -> Word16 -- ^ sample degradation priority+ -> Box (FullBox TrackExtends 0)+trackExtends =+ wrapBitBuilderBox (fullBox 0 . MkTrackExtends) (Proxy @TrackExtendsBody)+++newtype TrackExtends where+ MkTrackExtends :: BuilderBox -> TrackExtends+ deriving (IsBoxContent)++instance IsBox TrackExtends++type instance BoxTypeSymbol TrackExtends = "trex"++-- | Defaults for movie fragment - the content+type TrackExtendsBody =+ "track_ID" @: FieldU32+ .+: "default_sample_description_index" @: FieldU32+ .+: "default_sample_duration" @: FieldU32+ .+: "default_sample_size" @: FieldU32+ .+: TrackExtendsDefaultSampleFlags++type TrackExtendsDefaultSampleFlags =+ "reserved" @: Field 4 := 0+ .+: "is_leading" @: Field 2+ .+: "sample_depends_on" @: Field 2+ .+: "sample_is_depended_on" @: Field 2+ .+: "sample_has_redundancy" @: Field 2+ .+: "sample_padding_value" @: Field 3+ .+: "sample_is_non_sync_sample" @: Flag+ .+: "sample_degradation_priority" @: FieldU16+ .+: 'EmptyBitRecord
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackFragBaseMediaDecodeTime.hs view
@@ -0,0 +1,32 @@+-- | Sum of the total base media time in the timescale from the movie header box.+module Data.ByteString.IsoBaseFileFormat.Boxes.TrackFragBaseMediaDecodeTime where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Util.Time+import GHC.TypeLits++-- * @tfdt@ Box++-- | Construct a 'TrackFragBaseMediaDecodeTime box.+trackFragBaseMediaDecodeTime -- TODO allow 64 bit variant+ :: KnownNat version+ => TS version "track-fragment-base-media-decode-time"+ -> Box (FullBox (TrackFragBaseMediaDecodeTime version) version)+trackFragBaseMediaDecodeTime = fullBox 0 . TrackFragBaseMediaDecodeTime++-- | Track fragment media base decode time+newtype TrackFragBaseMediaDecodeTime (version :: Nat) where+ TrackFragBaseMediaDecodeTime+ :: TS version "track-fragment-base-media-decode-time"+ -> TrackFragBaseMediaDecodeTime version+ deriving (IsBoxContent)++instance IsBox (TrackFragBaseMediaDecodeTime v)++type instance BoxTypeSymbol (TrackFragBaseMediaDecodeTime v) = "tfdt"++-- | Return the static size of the empty box+trackFragBaseMediaDecodeTimeStaticSize64 :: Num a => a+trackFragBaseMediaDecodeTimeStaticSize64 =+ fromBoxSize 0 (boxSize (trackFragBaseMediaDecodeTime (TSv1 0)))
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackFragment.hs view
@@ -0,0 +1,23 @@+-- | Meta data for fragments of a track inside a movie fragment.+module Data.ByteString.IsoBaseFileFormat.Boxes.TrackFragment where++import Data.ByteString.IsoBaseFileFormat.Box++-- * @traf@ Box++-- | Compose a 'TrackFragment' box from the given boxes.+trackFragment+ :: Boxes ts -> Box (ContainerBox TrackFragment ts)+trackFragment = containerBox ()++-- | Container box for tracks.+data TrackFragment++instance IsBox TrackFragment where+ type BoxContent TrackFragment = ()++type instance BoxTypeSymbol TrackFragment = "traf"++-- | Return the static size of the empty box+trackFragmentStaticSize :: Num a => a+trackFragmentStaticSize = fromBoxSize 0 (boxSize (trackFragment NoBoxes))
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackFragmentHeader.hs view
@@ -0,0 +1,33 @@+-- | Meta data for a presentation of a /movie fragment/.+module Data.ByteString.IsoBaseFileFormat.Boxes.TrackFragmentHeader where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.Default++-- * @tfhd@ Box++-- | Construct a 'TrackFragmentHeader' box.++trackFragmentHeader -- TODO allow 64 bit variant+ :: TrackFragmentHeader -> Box (FullBox TrackFragmentHeader 0)+trackFragmentHeader =+ fullBox 0x020000 -- TODO box is hard coded for file offset calculations in the 'trun' box++-- | Track fragment meta data+-- TODO box is hard coded for file offset calculations in the 'trun' box+newtype TrackFragmentHeader where+ TrackFragmentHeader ::+ Template (U32 "track_ID") 0x001+ -> TrackFragmentHeader+ -- TODO all optional fields currently omitted for offset calculations in the 'trun' box+ deriving (IsBoxContent, Default)++instance IsBox TrackFragmentHeader++type instance BoxTypeSymbol TrackFragmentHeader = "tfhd"++-- | Return the static size of the empty box+trackFragmentHeaderStaticSize :: Num a => a+trackFragmentHeaderStaticSize = fromBoxSize 0 (boxSize (trackFragmentHeader def))
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackHeader.hs view
@@ -0,0 +1,71 @@+-- | Track header box+module Data.ByteString.IsoBaseFileFormat.Boxes.TrackHeader+ (trackHeader, TrackHeader(..), TrackHeaderFlags(..)+ , type TrackHeaderTimes , type TrackHeaderTimesV0 , type TrackHeaderTimesV1 ) where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Util.Versioned+import Data.ByteString.IsoBaseFileFormat.Util.Time+import Data.ByteString.IsoBaseFileFormat.ReExports++-- * @tkhd@ Box+-- | Create a 'TrackHeader' box.+trackHeader+ :: forall version . ( KnownNat version )+ => TrackHeaderFlags+ -> TrackHeader version+ -> Box (FullBox (TrackHeader version) version)+trackHeader flags = fullBox flags'+ where+ flags' = case flags of+ TrackDisabled -> 0+ TrackEnabled -> 1+ TrackInMovie -> 3+ TrackInMovieAndPreview -> 7++data TrackHeaderFlags =+ TrackDisabled+ | TrackEnabled+ | TrackInMovie+ | TrackInMovieAndPreview+++-- | Track meta data, indexed by a version.+newtype TrackHeader (version :: Nat) where+ TrackHeader ::+ Versioned TrackHeaderTimesV0 TrackHeaderTimesV1 version+ :+ Constant (I32Arr "reserved" 2) '[0, 0]+ :+ Template (I16 "layer") 0+ :+ Template (I16 "alternate_group") 0+ :+ Template (I16 "volume") 256+ :+ Constant (I16 "reserved") 0+ :+ Template (I32Arr "matrix" 9)+ '[65536, 0, 0, 0, 65536, 0, 0, 0, 1073741824]+ :+ I32 "width"+ :+ I32 "height"+ -> TrackHeader version+ deriving (IsBoxContent)++-- | Time and timing information about a track (32bit version).+type TrackHeaderTimesV0 = TrackHeaderTimes+ (Scalar Word32)+ (TS32 "duration")++-- | Time and timing information about a track (64bit version).+type TrackHeaderTimesV1 = TrackHeaderTimes+ (Scalar Word64)+ (TS64 "duration")++-- | Time and timing information about a track.+type TrackHeaderTimes uint dur =+ uint "creation_time"+ :+ uint "modification_time"+ :+ U32 "track_ID"+ :+ Constant (U32 "reserved") 0+ :+ dur++instance IsBox (TrackHeader v)++type instance BoxTypeSymbol (TrackHeader v) = "tkhd"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/TrackRun.hs view
@@ -0,0 +1,80 @@+-- | A list of sample offsets and sizes of a track. This tells the decoder where+-- stuff inside an 'mdat' box is, and also where the 'mdat' box /starts/, which+-- is the reason why currently there are several TODOs concercning the offset+-- calculation.+module Data.ByteString.IsoBaseFileFormat.Boxes.TrackRun (trackRunIso5, TrackRun) where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports+import qualified Data.ByteString as BS++-- | Create a track run box. First parameter is the accumulated offset to the+-- /moof/ box. This is needed for data offset calculation. Then follows a list+-- of __sample-duration__ and __sample-sizes__. All samples are flagged+-- /independent/.+trackRunIso5 :: Int32 -> [(Word32, BS.ByteString)] -> Box TrackRun+trackRunIso5 !mdatOffset !samples = Box c+ where+ !c = TrackRun (header <> body)+ where+ !header = h (fromIntegral sampleCount) dataOffset+ where+ !dataOffset = 8 -- box size field + fourcc code of this very box+ + mdatOffset + 8 -- +8 because we want to skip the box header od the mdat box+ + fromIntegral+ ((headerStaticSize `div` 8)+ + sampleCount * (sampleStaticSize `div` 8))+ !h = bitBuilderBox (Proxy @(Header TrackRunFlagsIso5))+ !headerStaticSize = (natVal (Proxy @(BitRecordSize (Header TrackRunFlagsIso5))))+ !sampleStaticSize = (natVal (Proxy @(BitRecordSize (Sample TrackRunFlagsIso5))))+ !sampleCount = fromIntegral (length samples)++ !body = mconcat $ s <$> samples+ where+ s (!duration, !bs) = bitBuilderBox (Proxy @(Sample TrackRunFlagsIso5)) duration size+ where+ !size = fromIntegral (BS.length bs)++newtype TrackRun where+ TrackRun :: BuilderBox -> TrackRun+ deriving (IsBoxContent)++instance IsBox TrackRun+type instance BoxTypeSymbol TrackRun = "trun"++-- class MkTrackRunArgs (t :: IsA (TrackRunFlags sop fsp sdp ssp sfp sop)) where+-- data TrRunArgs (t :: IsA (TrackRunFlags sop fsp sdp ssp sfp sop))++data TrackRunFlags+ (dataOffsetPresent :: Bool)+ (firstSampleFlagsPresent :: Bool)+ (sampleDurationPresent :: Bool)+ (sampleSizePresent :: Bool)+ (sampleFlagsPresent :: Bool)+ (sampleCompositionTimeOffsetPresent :: Bool)++data TrackRunFlagsIso5+ :: IsA (TrackRunFlags 'True 'False 'True 'True 'True 'False)++type Header (t :: IsA (TrackRunFlags dop fsp sdp ssp sfp sctop)) =+ "version" @: FieldU8 := 0 -- TODO allow version 1+ .+: Field 12 := 0+ .+: "sample-scto-present" @: Flag := sctop+ .+: "sample-flags-present" @: Flag := sfp+ .+: "sample-size-present" @: Flag := ssp+ .+: "sample-duration-present" @: Flag := sdp+ .+: Field 5 := 0+ .+: "first-sample-flag-present" @: Flag := fsp+ .+: Flag := 'False+ .+: "data-offset-preset" @: Flag := dop+ .+: "sample-count" @: FieldU32+ .+: WhenR dop+ ('BitRecordMember ("data-offset" @: FieldI32))+ :+: WhenR fsp+ ('BitRecordMember ("first-sample-flags" @: FieldU32))+++type Sample (t :: IsA (TrackRunFlags dop fsp sdp ssp sfp sctop)) =+ WhenR sdp ('BitRecordMember ("sample-duration" @: FieldU32))+ :+: WhenR ssp ('BitRecordMember ("sample-size" @: FieldU32))+ :+: WhenR sfp ('BitRecordMember ("sample-flags" @: FieldU32 := 0x02000000)) -- TODO allow flags as in TrackExtends
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/VideoMediaHeader.hs view
@@ -0,0 +1,26 @@+-- | Media-independent properties of a tracks video content.+module Data.ByteString.IsoBaseFileFormat.Boxes.VideoMediaHeader where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.Boxes.Handler+import Data.ByteString.IsoBaseFileFormat.Boxes.SpecificMediaHeader+import Data.ByteString.IsoBaseFileFormat.ReExports++type instance MediaHeaderFor 'VideoTrack = VideoMediaHeader++-- | Video header data box.+newtype VideoMediaHeader where+ VideoMediaHeader+ :: Template (U16 "graphicsmode") 0+ :+ Template (U16Arr "opcolor" 3) '[0,0,0]+ -> VideoMediaHeader+ deriving (Default, IsBoxContent)++-- | Create a video media header data box.+videoMediaHeader :: VideoMediaHeader -> Box (FullBox VideoMediaHeader 0)+videoMediaHeader = fullBox 1++instance IsBox VideoMediaHeader+type instance BoxTypeSymbol VideoMediaHeader = "vmhd"
+ src/Data/ByteString/IsoBaseFileFormat/Boxes/VisualSampleEntry.hs view
@@ -0,0 +1,170 @@+{-# LANGUAGE UndecidableInstances #-}+-- | Detailed visual sample description.+module Data.ByteString.IsoBaseFileFormat.Boxes.VisualSampleEntry where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Boxes.Handler+import Data.ByteString.IsoBaseFileFormat.ReExports+import qualified Data.Text as T++-- | Fields if visual sample entries.+-- A @depth@ of 0x0018 means colour image with no alpha.+-- The @horizresolution@ and @vertresolution@ of 0x00480000 means 72 dpi.+-- The @frame_count@ indicates the number of video frames per sample.+newtype VideoSampleEntry c where+ VideoSampleEntry+ :: U16 "pre_defined"+ :+ Constant (U16 "reserved") 0+ :+ U16 "width"+ :+ U16 "height"+ :+ Template (U32 "horizresolution") 0x00480000 -- TODO fix point+ :+ Template (U32 "vertresolution") 0x00480000 -- TODO fix point+ :+ Constant (U32 "reserved") 0+ :+ Template (U16 "frame_count") 1+ :+ FixSizeText 32 "compressorname"+ :+ Template (U16 "depth") 0x0018+ :+ Template (I16 "pre_defined") 65535+ :+ Maybe (Box CleanAperture)+ :+ Maybe (Box PixelAspectRatio)+ :+ [Box SomeColourInformation]+ :+ c+ -> VideoSampleEntry c+ deriving (IsBoxContent, Default)++type instance GetHandlerType (VideoSampleEntry c) = 'VideoTrack+type instance BoxTypeSymbol (VideoSampleEntry c) = BoxTypeSymbol c++instance IsBoxContent [Box SomeColourInformation] where+ boxSize = sum . fmap boxSize+ boxBuilder = fold . fmap boxBuilder++-- * Clean Aperture sub box++-- | Construct a 'CleanAperture' (sub-) 'Box'+cleanAperture :: CleanAperture -> Box CleanAperture+cleanAperture = Box++-- | The clean aperture settings+newtype CleanAperture where+ CleanAperture+ :: U32 "cleanApertureWidthN"+ :+ U32 "cleanApertureWidthD"+ :+ U32 "cleanApertureHeightN"+ :+ U32 "cleanApertureHeightD"+ :+ U32 "horizOffN"+ :+ U32 "horizOffD"+ :+ U32 "vertOffN"+ :+ U32 "vertOffD"+ -> CleanAperture+ deriving (Default, IsBoxContent)++type instance BoxTypeSymbol CleanAperture = "clap"+instance IsBox CleanAperture++-- * Pixel aspect ratio sub box++-- | Construct a 'PixelAspectRatio' (sub-) 'Box'+pixelAspectRatio :: PixelAspectRatio -> Box PixelAspectRatio+pixelAspectRatio = Box++-- | The pixel aspect ratio.+newtype PixelAspectRatio where+ PixelAspectRatio+ :: U32 "hSpacing"+ :+ U32 "vSpacing"+ -> PixelAspectRatio+ deriving (Default, IsBoxContent)++type instance BoxTypeSymbol PixelAspectRatio = "pasp"+instance IsBox PixelAspectRatio++-- * Colour information sub box++-- | Construct a 'ColourInformation' (sub-) 'Box'+colourInformation+ :: ColourType p -> ColourInformation p+colourInformation = ColourInformation . (Constant :+)++-- | Construct a 'ColourInformation' (sub-) 'Box' from 'OnScreenColours'+onScreenColourInformation+ :: ColourType 'OnScreenColours -> ColourInformation 'OnScreenColours+onScreenColourInformation = colourInformation++-- | Construct a 'ColourInformation' (sub-) 'Box' from 'RestrictedICCProfile'+restrictedICCProfileColourInformation+ :: ColourType 'RestrictedICCProfile -> ColourInformation 'RestrictedICCProfile+restrictedICCProfileColourInformation = colourInformation++-- | Construct a 'ColourInformation' (sub-) 'Box' from 'UnrestrictedICCProfile'+unrestrictedICCProfileColourInformation+ :: ColourType 'UnrestrictedICCProfile -> ColourInformation 'UnrestrictedICCProfile+unrestrictedICCProfileColourInformation = colourInformation++type instance BoxTypeSymbol SomeColourInformation = "pasp"+instance IsBox SomeColourInformation++-- | A wrapper that hides the concrete 'ColourTypeProfile' of a+-- 'ColourInformation'.+data SomeColourInformation where+ SomeColourInformation+ :: forall (profile :: ColourTypeProfile)+ . IsBoxContent (ColourInformation profile)+ => !(ColourInformation profile)+ -> SomeColourInformation+instance IsBoxContent SomeColourInformation where+ boxSize (SomeColourInformation c) = boxSize c+ boxBuilder (SomeColourInformation c) = boxBuilder c++-- | Profile dependent colour information+newtype ColourInformation (profile :: ColourTypeProfile) where+ ColourInformation+ :: Constant (U32Text "colour_type") (ColourTypeCode profile)+ :+ ColourType profile+ -> ColourInformation profile++deriving instance+ (Default (ColourType profile))+ => Default (ColourInformation profile)+deriving instance+ (KnownSymbol (ColourTypeCode profile),IsBoxContent (ColourType profile))+ => IsBoxContent (ColourInformation profile)++-- | Colour type profiles+data ColourTypeProfile =+ -- | PTM_COLOR_INFO from A.7.2 of ISO/IEC 29199-2, mind the full range flag.+ OnScreenColours |+ -- | A restricted ICC.1 (2010) profile+ RestrictedICCProfile |+ -- | An unrestricted IEC ISO-15076 part 1, ICC.1 (2010) profile+ UnrestrictedICCProfile++-- | Profile dependent colour information family+type family+ ColourType (p :: ColourTypeProfile) where+ ColourType 'OnScreenColours =+ U16 "colour_primaries"+ :+ U16 "transfer_characteristics"+ :+ U16 "matrix_coefficients"+ :+ FullRangeFlag+ ColourType 'RestrictedICCProfile =+ T.Text -- TODO+ ColourType 'UnrestrictedICCProfile =+ T.Text -- TODO++-- | The full range flag, note the different bit layout compared to+-- PTM_COLOR_INFO in ISO 29199-2.+data FullRangeFlag = IsFullRange | IsNotFullRange+instance IsBoxContent FullRangeFlag where+ boxSize _ = 1+ boxBuilder IsFullRange = word8 128+ boxBuilder IsNotFullRange = word8 0+instance Default FullRangeFlag where+ def = IsFullRange++-- | Return the color type four letter code for a 'ColourTypeProfile'.+type family+ ColourTypeCode (p :: ColourTypeProfile) :: Symbol where+ ColourTypeCode 'OnScreenColours = "nclx"+ ColourTypeCode 'RestrictedICCProfile = "rICC"+ ColourTypeCode 'UnrestrictedICCProfile = "prof"
+ src/Data/ByteString/IsoBaseFileFormat/Brands/Dash.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE UndecidableInstances #-}+-- | Predefined Box composition matching the @dash@ brand. TODO this is an+-- incomplete, special-purpose variant of this brand, serving my personal,+-- educational, current need.+-- This is a convenient way of building documents of that kind.+module Data.ByteString.IsoBaseFileFormat.Brands.Dash+ (Dash, dash)+ where+import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.MediaFile+import Data.ByteString.IsoBaseFileFormat.Boxes+import Data.ByteString.IsoBaseFileFormat.ReExports+++-- TODO rename dash to iso5+-- | A phantom type to indicate this branding. Version can be 0 or 1 it is used+-- in some boxes to switch between 32/64 bits.+data Dash (version :: Nat)++-- | A constant to indicate the 'Dash' brand with version 0+dash :: Proxy (Dash 0)+dash = Proxy++-- | A 'BoxLayout' which contains the stuff needed for the 'dash' brand.+-- TODO add iso1 iso2 iso3 iso5 isom formats+instance IsMediaFileFormat (Dash v) where+ type BoxLayout (Dash v) =+ OneOf '[ MovieLayout v+ , SegmentLayout]++type MovieLayout v =+ Boxes+ '[ OM_ FileType+ , OM_ Skip+ , OM Movie+ '[ OM_ (MovieHeader v)+ , SomeMandatoryX+ (OneOf '[ TrackLayout v 'VideoTrack+ , TrackLayout v 'AudioTrack+ , TrackLayout v 'HintTrack+ , TrackLayout v 'TimedMetaDataTrack+ , TrackLayout v 'AuxilliaryVideoTrack])+ , OO MovieExtends+ '[ OO_ (MovieExtendsHeader v)+ , OM_ TrackExtends ]+ ]+ ]++type TrackLayout version handlerType =+ (ContainerBox Track+ '[ OM_ (TrackHeader version)+ , OM Media+ '[ OM_ (MediaHeader version)+ , OM_ (Handler handlerType)+ , OM MediaInformation+ '[ OneOf '[ OM_ (MediaHeaderFor handlerType)+ , OM_ NullMediaHeader]+ , OM DataInformation+ '[ OM DataReference+ '[ SomeMandatoryX+ (OneOf '[ OM_ DataEntryUrl+ , OM_ DataEntryUrn])]]+ , OM SampleTable+ '[ OM SampleDescription+ '[ SomeMandatoryX (MatchSampleEntry handlerType) ]+ , OM_ TimeToSample+ , OM_ SampleToChunk+ , OM_ SampleSize+ , OneOf '[ OM_ ChunkOffset32+ , OM_ ChunkOffset64 ]+ ]+ ]+ ]+ ])++type SegmentLayout =+ Boxes '[ OM_ SegmentType+ , OM MovieFragment+ '[ OM_ MovieFragmentHeader+ , OM TrackFragment+ '[ OM_ TrackFragmentHeader+ , OO_ (TrackFragBaseMediaDecodeTime 1)+ , SO_ TrackRun+ ]+ ]+ , SM_ MediaData+ ]
+ src/Data/ByteString/IsoBaseFileFormat/MediaFile.hs view
@@ -0,0 +1,18 @@+-- | Brand/Box-validation+module Data.ByteString.IsoBaseFileFormat.MediaFile+ where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.TypeLayout+import Data.ByteString.IsoBaseFileFormat.ReExports++-- TODO move this to general module merge with Box.hs+-- | A class that describes (on the type level) how a box can be nested into+-- other boxes (see 'Boxes).+class IsMediaFileFormat brand where+ -- | The layout that an IsBoxContent instance has to have, before 'packMedia' accepts it+ type BoxLayout brand+ mediaBuilder+ :: forall t proxy . (IsBoxContent t, IsRuleConform t (BoxLayout brand) ~ 'True)+ => proxy brand -> t -> Builder+ mediaBuilder _ t = boxBuilder t
+ src/Data/ByteString/IsoBaseFileFormat/ReExports.hs view
@@ -0,0 +1,41 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}++-- | A module of re-exports and orphan instances+module Data.ByteString.IsoBaseFileFormat.ReExports (module X) where+++import Data.Bits as X+import qualified Data.ByteString as B+import Data.ByteString.Builder as X+import Data.ByteString.IsoBaseFileFormat.Util.TypeLayout as X+import Data.Default as X+import Data.Foldable as X (fold)+import Data.Int as X+import Data.Kind as X (Constraint,+ Type)+import Data.Kind.Extra as X+import Data.Maybe as X+import Data.Monoid as X+import Data.Proxy as X+import Data.String as X+import Data.Tagged as X+import qualified Data.Text as T+import Data.Type.Bool as X+import Data.Type.BitRecords as X+import Data.Type.Equality as X+import Data.Type.Pretty as X+import Data.Word as X+import GHC.TypeLits as X+import Test.TypeSpecCrazy as X hiding+ (type Not)+import Text.Printf as X+import Debug.Trace as X++instance Default B.ByteString where+ def = mempty++instance Default T.Text where+ def = ""++instance Default c => Default (Tagged s c) where+ def = Tagged def
+ src/Data/ByteString/IsoBaseFileFormat/Util/BoxFields.hs view
@@ -0,0 +1,305 @@+{-# LANGUAGE UndecidableInstances #-}+-- | Mini EDSL for labelled box fields. The boxfields can be 'Scalar' or+-- 'ScalarArray's.+module Data.ByteString.IsoBaseFileFormat.Util.BoxFields+ where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.Singletons++import Data.Singletons.Prelude.List+import qualified Data.Vector as Vec+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import qualified Data.ByteString as B+import Numeric.Natural+++-- * Scalar box fields++type U64 label = Scalar Word64 label++type I64 label = Scalar Int64 label++u64 :: Word64 -> U64 label+u64 = Scalar++i64 :: Int64 -> I64 label+i64 = Scalar++type U32 label = Scalar Word32 label+type I32 label = Scalar Int32 label++u32 :: Word32 -> U32 label+u32 = Scalar++i32 :: Int32 -> I32 label+i32 = Scalar++type U16 label = Scalar Word16 label++type I16 label = Scalar Int16 label++u16 :: Word16 -> U16 label+u16 = Scalar++i16 :: Int16 -> I16 label+i16 = Scalar++type U8 label = Scalar Word8 label++type I8 label = Scalar Int8 label++u8 :: Word8 -> U8 label+u8 = Scalar++i8 :: Int8 -> I8 label+i8 = Scalar++-- | A numeric box field with a type level label. Note that it has a 'Num'+-- instance. Use the type aliases above, e.g.+-- 'U8','I8','U16','I16','U32','I32','U64','I64' from above. Use either the+-- smart constructors, e.g. 'u8','i8','u16','i16','u32','i32','u64','i64' or the+-- 'Num' instance, whereas the constructors might give a bit more safety.+newtype Scalar scalartype (label :: k) =+ Scalar {fromScalar :: scalartype}+ deriving (Show, Read, Ord, Eq, Num, Default)++-- | Relabel a scalar value, e.g. convert a @Scalar U32 "foo"@ to a @Scalar U32+-- "bar"@.+relabelScalar :: Scalar t l -> Scalar t l'+relabelScalar (Scalar !x) = Scalar x++instance IsBoxContent (Scalar Word8 label) where+ boxSize _ = 1+ boxBuilder (Scalar !v) = word8 v++instance IsBoxContent (Scalar Word16 label) where+ boxSize _ = 2+ boxBuilder (Scalar !v) = word16BE v++instance IsBoxContent (Scalar Word32 label) where+ boxSize _ = 4+ boxBuilder (Scalar !v) = word32BE v++instance IsBoxContent (Scalar Word64 label) where+ boxSize _ = 8+ boxBuilder (Scalar !v) = word64BE v++instance IsBoxContent (Scalar Int8 label) where+ boxSize _ = 1+ boxBuilder (Scalar !v) = int8 v++instance IsBoxContent (Scalar Int16 label) where+ boxSize _ = 2+ boxBuilder (Scalar !v) = int16BE v++instance IsBoxContent (Scalar Int32 label) where+ boxSize _ = 4+ boxBuilder (Scalar !v) = int32BE v++instance IsBoxContent (Scalar Int64 label) where+ boxSize _ = 8+ boxBuilder (Scalar !v) = int64BE v++instance (KnownNat scalar,Num o) => FromTypeLit (Scalar o label) scalar where+ fromTypeLit _ = Scalar $ fromIntegral $ natVal (Proxy :: Proxy scalar)++-- * Array fields++type U64Arr label size = ScalarArray label size Word64++u64Arr :: (KnownNat size,KnownSymbol label)+ => [Word64] -> U64Arr label size+u64Arr = fromList++type I64Arr label size = ScalarArray label size Int64++i64Arr :: (KnownNat size,KnownSymbol label)+ => [Int64] -> I64Arr label size+i64Arr = fromList++type U32Arr label size = ScalarArray label size Word32++u32Arr :: (KnownNat size,KnownSymbol label)+ => [Word32] -> U32Arr label size+u32Arr = fromList++type I32Arr label size = ScalarArray label size Int32++i32Arr :: (KnownNat size,KnownSymbol label)+ => [Int32] -> I32Arr label size+i32Arr = fromList++type U16Arr label size = ScalarArray label size Word16++u16Arr :: (KnownNat size,KnownSymbol label)+ => [Word16] -> U16Arr label size+u16Arr = fromList++type I16Arr label size = ScalarArray label size Int16++i16Arr :: (KnownNat size,KnownSymbol label)+ => [Int16] -> I16Arr label size+i16Arr = fromList++type U8Arr label size = ScalarArray label size Word8++u8Arr :: (KnownNat size,KnownSymbol label)+ => [Word8] -> U8Arr label size+u8Arr = fromList++type I8Arr label size = ScalarArray label size Int8++i8Arr :: (KnownNat size,KnownSymbol label)+ => [Int8] -> I8Arr label size+i8Arr = fromList++-- | A box field that is an array of 'Scalar's with a type level label. Use the+-- type aliases, e.g.+-- 'U8Arr','I8Arr','U16Arr','I16Arr','U32Arr','I32Arr','U64Arr','I64Arr' from+-- above. Use the smart constructors, e.g.+-- 'u8Arr','i8Arr','u16Arr','i16Arr','u32Arr','i32Arr','u64Arr','i64Arr' .+newtype ScalarArray (label :: k) (len :: Nat) o where+ ScalarArray :: Vec.Vector o -> ScalarArray label n o+ deriving (Show,Eq)++instance (Default o,KnownNat (len :: Nat))+ => Default (ScalarArray label len o) where+ def = ScalarArray $ Vec.replicate (fromIntegral (natVal (Proxy @len))) def++instance (Num o,IsBoxContent (Scalar o label))+ => IsBoxContent (ScalarArray label len o) where+ boxSize (ScalarArray !vec) =+ fromIntegral (Vec.length vec) * boxSize (Scalar 0 :: Scalar o label)+ boxBuilder (ScalarArray !vec) =+ Vec.foldl' mappend+ mempty+ (Vec.map (boxBuilder . mkScalar) vec)+ where mkScalar :: o -> Scalar o label+ !mkScalar = Scalar++-- | Internal function+fromList :: forall label n o.+ (KnownSymbol label,KnownNat n)+ => [o] -> ScalarArray label n o+fromList l =+ ScalarArray $+ if natVal (Proxy @n) /= toInteger (length l)+ then+ error $ printf "Invalid number of array elements for array %s. Got length: %d elments, expected %d."+ (show (symbolVal (Proxy :: Proxy label)))+ (length l)+ (natVal (Proxy :: Proxy n))+ else+ Vec.fromList l++-- * Constant fields++-- | Wrapper around a field, e.g. a 'Scalar' or 'ScalarArray', with a type level+-- value. The wrapped content must implement 'FromTypeLit'. To get the value of+-- a 'Constant' use 'fromTypeLit'.+data Constant o v where+ Constant :: Constant o v++instance (IsBoxContent o,FromTypeLit o v) => IsBoxContent (Constant o v) where+ boxSize = boxSize . fromTypeLit+ boxBuilder = boxBuilder . fromTypeLit++instance Default (Constant o v) where+ def = Constant++-- * Template Fields++-- | Fields with default values that can be overriden with custom value. Like+-- 'Constant' this is a wrapper around a field, e.g. a 'Scalar' or+-- 'ScalarArray', with a type level default value. The wrapped content must+-- implement 'FromTypeLit'.+data Template o v where -- TODO replace with newtype and replace the 'Template'+ -- case with the 'Default' instance.+ Template :: Template o v+ Custom :: !o -> Template o v++instance Default (Template o v) where+ def = Template++-- | Get a value from a 'Template'.+templateValue :: FromTypeLit o v => Template o v -> o+templateValue d@Template = fromTypeLit d+templateValue (Custom !v) = v++instance (IsBoxContent o,FromTypeLit o v) => IsBoxContent (Template o v) where+ boxSize = boxSize . templateValue+ boxBuilder = boxBuilder . templateValue++-- * Conversion from type-level numbers and lists to values++-- | Types that can be constructed from type level value representations.+class FromTypeLit o v where+ fromTypeLit :: proxy o v -> o++instance forall arr o len (label :: Symbol) .+ (KnownSymbol label,SingI arr,Num o,SingKind [Nat],KnownNat len,len ~ Length arr)+ => FromTypeLit (ScalarArray label len o) (arr :: [Nat]) where+ fromTypeLit _ =+ let s = sing :: Sing arr+ vs :: [Natural]+ !vs = fromSing s+ vs' :: [o]+ !vs' = fromIntegral <$> vs+ in (fromList vs' :: ScalarArray label len o)++instance KnownSymbol str => FromTypeLit T.Text (str :: Symbol) where+ fromTypeLit = T.pack . symbolVal++-- * String/Text field types++-- | A fixed size string, the first byte is the string length, after the String,+-- the field is padded with @0@ bytes. The string must be in UTF8 format.+newtype FixSizeText (len :: Nat) (label :: Symbol) where+ FixSizeText :: T.Text -> FixSizeText len label++-- | A constraint that matches type level numbers that are valid text sizes for+-- 'FixSizeText's.+type IsTextSize len = (KnownNat len, 1 <= len, len <= 255)++instance IsTextSize len => IsBoxContent (FixSizeText len label) where+ boxSize _ = fromIntegral (natVal (Proxy :: Proxy len))+ boxBuilder (FixSizeText !t) =+ let+ -- leave room for the size byte+ !maxSize = fromIntegral (natVal (Proxy :: Proxy len) - 1)+ !displayableText = B.take maxSize (T.encodeUtf8 t)+ !displayableTextSize = B.length displayableText+ !paddingSize = max 0 (maxSize - displayableTextSize)+ in word8 (fromIntegral displayableTextSize)+ <> byteString displayableText+ <> fold (replicate paddingSize (word8 0))++instance IsTextSize len => Default (FixSizeText len label) where+ def = FixSizeText ""++-- | Four character strings embedded in a uint32.+newtype U32Text (label :: Symbol) where+ U32Text :: Word32 -> U32Text label++instance IsString (U32Text label) where+ fromString !str = U32Text $+ let cw !s !c = (0xFF .&. fromIntegral (fromEnum c)) `shiftL` s+ in case str of+ [] -> 0x20202020+ [!a] -> cw 24 a .|. 0x00202020+ [!a,!b] -> cw 24 a .|. cw 16 b .|. 0x00002020+ [!a,!b,!c] -> cw 24 a .|. cw 16 b .|. cw 8 c .|. 0x20+ (!a : (!b) : (!c) : (!d) :_) -> cw 24 a .|. cw 16 b .|. cw 8 c .|. cw 0 d++instance IsBoxContent (U32Text label) where+ boxSize _ = 4+ boxBuilder (U32Text !t) = word32BE t++instance KnownSymbol str => FromTypeLit (U32Text label) (str :: Symbol) where+ fromTypeLit = fromString . symbolVal++instance Default (U32Text label) where+ def = U32Text 0x20202020
+ src/Data/ByteString/IsoBaseFileFormat/Util/FullBox.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE UndecidableInstances #-}+-- | Full Boxes+module Data.ByteString.IsoBaseFileFormat.Util.FullBox+ (FullBox(..), fullBox, BoxFlags(..))+ where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports++-- | A 'FullBox' contains an extra version and a flags field. In this+-- implementation it is wrapped around the rest of the box content. This+-- enforces that the 'FullBox' header fields are always at the beginning - at+-- least as long as this module hides the 'FullBox' constructor ;)+data FullBox t (version :: Nat) where+ FullBox :: (KnownNat version, IsBox t)+ => !(BoxFlags 24)+ -> !(BoxContent t)+ -> FullBox t version++instance (KnownNat version, IsBox t, Default (BoxContent t))+ => Default (FullBox t version) where+ def = FullBox 0 def++instance (KnownNat v, IsBox t) => IsBox (FullBox t v) where+ type BoxContent (FullBox t v) = FullBox t v++type instance BoxTypeSymbol (FullBox t v) = BoxTypeSymbol t++instance (IsBox t, KnownNat v) => IsBoxContent (FullBox t v) where+ boxSize (FullBox f c) = 1 + boxSize f + boxSize c+ boxBuilder (FullBox f c) =+ word8 (fromIntegral (natVal (Proxy :: Proxy v)))+ <> boxBuilder f+ <> boxBuilder c++-- | Create a 'FullBox' from a 'BoxVersion' and 'BoxFlags'+fullBox+ :: (KnownNat v, IsBox t)+ => BoxFlags 24 -> BoxContent t -> Box (FullBox t v)+fullBox f c = Box (FullBox f c)++-- | In addition to a version there can be 24 bits for custom flags etc in+-- a 'FullBox'.+newtype BoxFlags bits =+ BoxFlags Integer+ deriving (Eq,Show,Num)++-- | Internal function that creates a bit mask with all bits in a 'BoxFlags' set+-- to 1.+boxFlagBitMask :: KnownNat bits+ => BoxFlags bits -> Integer+boxFlagBitMask px = 2 ^ natVal px - 1++-- | Internal function that masks-out all bits higher than 'bits'.+cropBits :: KnownNat bits+ => BoxFlags bits -> BoxFlags bits+cropBits f@(BoxFlags b) = BoxFlags (b .&. boxFlagBitMask f)++-- | Get the number of bytes required to store a number of bits.+instance KnownNat bits => IsBoxContent (BoxFlags bits) where+ boxSize f =+ let minBytes = fromInteger $ natVal f `div` 8+ modBytes = fromInteger $ natVal f `mod` 8+ in BoxSize $ minBytes + signum modBytes+ boxBuilder f@(BoxFlags b) =+ let bytes =+ let (BoxSize bytes') = boxSize f+ in fromIntegral bytes'+ wordSeq n+ | n <= bytes =+ word8 (fromIntegral (shiftR b ((bytes - n) * 8) .&. 255)) <>+ wordSeq (n + 1)+ | otherwise = mempty+ in wordSeq 1++instance KnownNat bits => Bits (BoxFlags bits) where+ (.&.) (BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ l .&. r+ (.|.) (BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ l .&. r+ xor (BoxFlags l) (BoxFlags r) = cropBits $ BoxFlags $ xor l r+ complement (BoxFlags x) = cropBits $ BoxFlags $ complement x+ shift (BoxFlags x) = cropBits . BoxFlags . shift x+ rotateL = error "TODO rotateL"+ rotateR = error "TODO rotateR"+ bitSize = fromInteger . natVal+ bitSizeMaybe = Just . fromInteger . natVal+ isSigned _ = False+ testBit f n =+ let (BoxFlags b) = cropBits f+ in testBit b n+ bit = cropBits . BoxFlags . bit+ popCount f =+ let (BoxFlags b) = cropBits f+ in popCount b+ zeroBits = BoxFlags 0
+ src/Data/ByteString/IsoBaseFileFormat/Util/Time.hs view
@@ -0,0 +1,140 @@+-- | Time and timing utilities.+module Data.ByteString.IsoBaseFileFormat.Util.Time+ (referenceTime, utcToMp4, mp4CurrentTime, durationFromSeconds,+ oneSecond32, oneSecond64, diffTimeToTicks,ticksToDiffTime,+ TimeScale(..), Timing, TS(..), type TS32, type TS64, Ticks(..), Ticks32(..))+ where++import Data.Time.Clock+import Data.Time.Calendar+import Data.Ratio+import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.IsoBaseFileFormat.Util.Versioned+import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.Typeable+import Foreign.Storable++-- * Absolute Dates+-- | According to the standard, fields with absolute dates and times are in+-- seconds since 1904/01/01 at midnight (UTC). This is this reference time.+referenceTime :: UTCTime+referenceTime =+ let startDay = fromGregorian 1904 1 1+ startTime = 0+ in UTCTime startDay startTime++-- | Convert a 'UTCTime' to a number of seconds since 'referenceTime'.+utcToMp4 :: Num t+ => UTCTime -> t+utcToMp4 u =+ let picoSecondsDiff = toRational $ diffUTCTime u referenceTime+ picoSecondsDiffNumerator = numerator picoSecondsDiff+ picoSecondsDiffDenominator = denominator picoSecondsDiff+ secondsSinceReferenceTime =+ div picoSecondsDiffNumerator picoSecondsDiffDenominator+ in fromIntegral secondsSinceReferenceTime+++-- | Convert a 'NominalDiffTime' to the number of 'Ticks' with respect+-- to a given 'TimeScale'.+diffTimeToTicks :: Integral t+ => NominalDiffTime -> TimeScale -> t+diffTimeToTicks !diff (TimeScale !scale) =+ round (diff * (fromIntegral scale))++-- | Convert a 'NominalDiffTime' to the number of 'Ticks' with respect+-- to a given 'TimeScale'.+ticksToDiffTime :: Integral t+ => t -> TimeScale -> NominalDiffTime+ticksToDiffTime !t (TimeScale !scale) =+ fromRational (toInteger t % toInteger scale)+++-- | Get the current time as number of seconds since 'referenceTime'+mp4CurrentTime :: Num t+ => IO t+mp4CurrentTime = utcToMp4 <$> getCurrentTime++-- * Time-Scale and Durations+-- | Default time-scale value+-- Based on history and tradition this value is @90000@.+-- MPEG-2 TS defines a single clock for each program, running at 27MHz. The+-- timescale of MPEG-2 TS Hint Tracks should be divisable by 90000.+newtype TimeScale = TimeScale {fromTimeScale :: Word32}+ deriving (Show,Eq,Num,Bounded,Ord,Bits,Integral,Typeable,Storable,Enum,Real)++instance Default TimeScale where+ def = TimeScale 90000++instance IsBoxContent TimeScale where+ boxSize = boxSize . fromTimeScale+ boxBuilder = boxBuilder . fromTimeScale++-- | Utility function to convert seconds (Integers) to any 'Num' using a+-- 'TimeScale', Since 'Scalar' has a 'Num' instance this can be used to generate+-- @duration@ fields.+durationFromSeconds :: Num t+ => TimeScale -> Integer -> t+durationFromSeconds timeScale seconds =+ let timeScaleI = fromIntegral timeScale+ in timeScaleI * fromInteger seconds++-- | Utility function to generate the equivalent of one second (@1 s@)+oneSecond32 = Scalar . flip durationFromSeconds 1+oneSecond32 :: TimeScale -> TS32 label++-- | Utility function to generate the equivalent of one second (@1 s@)+oneSecond64 :: TimeScale -> TS64 label+oneSecond64 = Scalar . flip durationFromSeconds 1++-- | A type that denotes a time relative to a 'TimeScale' which is included in+-- its type. 'Ticks' is the number of time units passed, where each time unit+-- has a physical duration of @timescale * 1/s@ i.e. @timescale@ 'Ticks' last+-- about @1 s@.+-- TODO use this instead of raw Word32s+newtype Ticks (timeScale :: Nat) = MkTicks {fromTicks :: Word64}+ deriving (Show,Eq,Num,Bounded,Ord,Bits,Integral,Typeable,Storable,Enum,Real)++instance IsBoxContent (Ticks n) where+ boxSize = boxSize . fromTicks+ boxBuilder = boxBuilder . fromTicks++newtype Ticks32 (timeScale :: Nat) = MkTicks32 {fromTicks32 :: Word32}+ deriving (Show,Eq,Num,Bounded,Ord,Bits,Integral,Typeable,Storable,Enum,Real)++instance IsBoxContent (Ticks32 n) where+ boxSize = boxSize . fromTicks32+ boxBuilder = boxBuilder . fromTicks32++-- | Time and timing information about a movie.+--+-- The creation/modification times are in seconds since midnight, Jan. 1, 1904,+-- in UTC time. Time scale declares the time coordinate system, it specifies the+-- number of time units that pass one second. The time coordinate system is used+-- by e.g. the duration field, which by the way contains the duration of the+-- longest track, if known, or simply the equivalent of 1s.+type Timing (version :: Nat) = Versioned TimingV0 TimingV1 version++type TS32 = Scalar Word32+type TS64 = Scalar Word64++data TS (version :: Nat) (label :: Symbol) where+ TSv0 :: !Word32 -> TS 0 label+ TSv1 :: !Word64 -> TS 1 label++instance IsBoxContent (TS v n) where+ boxSize (TSv0 _t) = 4+ boxSize (TSv1 _t) = 8+ boxBuilder (TSv0 !t) = word32BE t+ boxBuilder (TSv1 !t) = word64BE t++type TimingV0 = TimingImpl (Scalar Word32) (TS 0 "duration")++type TimingV1 = TimingImpl (Scalar Word64) (TS 1 "duration")++type TimingImpl uint dur =+ uint "creation_time"+ :+ uint "modification_time"+ :+ TimeScale+ :+ dur
+ src/Data/ByteString/IsoBaseFileFormat/Util/TypeLayout.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE UndecidableInstances #-}++module Data.ByteString.IsoBaseFileFormat.Util.TypeLayout where++import Data.Kind+import Data.Type.Bool+import Data.Type.Equality+import GHC.TypeLits++----+type family IsRuleConform (b :: k) (r :: l) :: Bool+----+data TopLevel :: Type -> Type+type instance IsRuleConform t (TopLevel rule) = IsRuleConform t rule+----+data OneOf :: [Type] -> Type+type instance IsRuleConform t (OneOf '[]) = 'False+type instance IsRuleConform t (OneOf (r ': rs)) =+ IsRuleConform t r || IsRuleConform t (OneOf rs)+----+data MatchSymbol :: Symbol -> Type+type instance IsRuleConform b (MatchSymbol fourcc) = ToSymbol b == fourcc+type family ToSymbol t :: Symbol+----+data OnceOptionalX t+data SomeOptionalX t+data SomeMandatoryX t+type instance IsRuleConform (bs :: [Type]) (sq :: [Type]) = IsSequence bs sq+type family+ IsSequence (bs :: [k]) (rs :: [j]) :: Bool+ where+ IsSequence '[] '[] = 'True+ IsSequence (b ': bs) '[] = 'False+ --+ IsSequence '[] (OnceOptionalX r ': rs) = IsSequence '[] rs+ IsSequence (b ': bs) (OnceOptionalX r ': rs) =+ If (IsRuleConform b r)+ (IsSequence bs rs)+ (IsSequence (b ': bs) rs)+ --+ IsSequence '[] (SomeOptionalX r ': rs) = IsSequence '[] rs+ IsSequence (b ': bs) (SomeOptionalX r ': rs) =+ If (IsRuleConform b r)+ (IsSequence bs (SomeOptionalX r ': rs))+ (IsSequence (b ': bs) rs )+ --+ IsSequence '[] (SomeMandatoryX r ': rs) = 'False+ IsSequence (b ': bs) (SomeMandatoryX r ': rs) =+ IsRuleConform b r && IsSequence bs (SomeOptionalX r ': rs)+ --+ IsSequence '[] (r ': rs) = 'False+ IsSequence (b ': bs) (r ': rs) =+ IsRuleConform b r && IsSequence bs rs
+ src/Data/ByteString/IsoBaseFileFormat/Util/Versioned.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE UndecidableInstances #-}+-- | A binary version 0/1 field with seperate content for each version.+module Data.ByteString.IsoBaseFileFormat.Util.Versioned+ (Versioned(..))+ where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports++-- TODO rewrite/cleanup the versioned mess++-- | Two alternative representations based on a /version/ index.+-- Use this for box content that can be either 32 or 64 bit.+data Versioned v0 v1 (version :: Nat) where+ V0 :: IsBoxContent v0 => !v0 -> Versioned v0 v1 0+ V1 :: IsBoxContent v1 => !v1 -> Versioned v0 v1 1++instance (version ~ 0,IsBoxContent v0,Default v0) => Default (Versioned v0 v1 (version :: Nat)) where+ def = V0 def++instance IsBoxContent (Versioned v0 v1 version) where+ boxSize (V0 c) = boxSize c+ boxSize (V1 c) = boxSize c+ boxBuilder (V0 c) = boxBuilder c+ boxBuilder (V1 c) = boxBuilder c
+ src/Data/ByteString/Mp4/AacInitSegment.hs view
@@ -0,0 +1,102 @@+-- | A single-track AAC audio streaming utility wrapping 'AudioFile' for+-- streaming via e.g. DASH.+module Data.ByteString.Mp4.AacInitSegment+ ( AacInitSegment(..)+ , BinaryAacInitSegment(..)+ , buildAacInitSegment+ ) where++import qualified Data.ByteString as BS+import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Boxes+import Data.ByteString.IsoBaseFileFormat.Brands.Dash as X+import Data.ByteString.IsoBaseFileFormat.MediaFile as X+import Data.ByteString.IsoBaseFileFormat.ReExports as X+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields as X+import Data.ByteString.IsoBaseFileFormat.Util.Time+import Data.ByteString.IsoBaseFileFormat.Util.Versioned+import qualified Data.ByteString.Lazy as BL+import Data.ByteString.Mp4.Boxes.AudioSpecificConfig+import Data.ByteString.Mp4.Boxes.Mp4AudioSampleEntry as X+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE++-- | Initialisation segment parameters of an aac audio stream mp4 file.+data AacInitSegment = AacInitSegment+ { creationTime :: !(TS32 "creation_time")+ , trackName :: !String+ , useHeAac :: !Bool+ , sampleRate :: !SamplingFreqTable+ , channelConfig :: !ChannelConfigTable+ }++instance Show BinaryAacInitSegment where+ show (BinaryAacInitSegment !d) =+ printf "INIT SEGMENT - size: %14d" (BS.length d)++-- | Encoded, binary MP4 AAC init segment (strict).+newtype BinaryAacInitSegment = BinaryAacInitSegment+ { fromBinaryAacInitSegment :: BS.ByteString+ }++-- | Convert an 'AacInitSegment' record to a generic 'BinaryAacInitSegment'.+buildAacInitSegment :: AacInitSegment -> BinaryAacInitSegment+buildAacInitSegment =+ BinaryAacInitSegment . BL.toStrict . toLazyByteString . build++-- | Convert an 'AacInitSegment' record to a generic 'BinaryAacInitSegment'.+build :: AacInitSegment -> Builder+build AacInitSegment {..} =+ let timeScaleForSampleRate = TimeScale (sampleRateToNumber sampleRate)+ in mediaBuilder dash $+ -- TODO must be iso5 for the way we use elementary stream descriptors+ fileTypeBox (FileType "iso5" 0 ["isom", "iso5", "dash", "mp42"]) :.+ skipBox+ (Skip+ (TE.encodeUtf8+ (T.pack "Lindenbaum GmbH, isobmff, Sven Heyll 2019"))) :|+ movie+ (movieHeader+ (MovieHeader $+ V0 (creationTime :+ relabelScalar creationTime :+ def :+ TSv0 0) :++ def :++ def :++ def :++ def :++ def :++ def :++ Custom (Scalar 2)) :.+ track+ (trackHeader+ TrackInMovieAndPreview+ (TrackHeader $+ V0+ (creationTime :+ relabelScalar creationTime :+ 1 :+ Constant :++ 0) :++ def) :|+ media+ (mediaHeader+ (MediaHeader $+ V0+ (creationTime :+ relabelScalar creationTime :++ timeScaleForSampleRate :++ TSv0 0) :++ def) :.+ handler (namedAudioTrackHandler (T.pack trackName)) :|+ mediaInformation+ (soundMediaHeader (SoundMediaHeader def) :.+ (dataInformation $: localMediaDataReference) :|+ sampleTable+ ((sampleDescription $:+ audioSampleEntry+ 1+ (aacAudioSampleEntrySimple+ useHeAac+ sampleRate+ channelConfig+ (Scalar 16)) :.+ timeToSample [] :.+ sampleToChunk [] :.+ fixedSampleSize 0 0 :|+ chunkOffset32 []))))) :|+ (movieExtends $: trackExtendsUnknownDuration 1 1))
+ src/Data/ByteString/Mp4/AudioStreaming.hs view
@@ -0,0 +1,216 @@+-- | A single-track AAC audio streaming utility wrapping 'AudioFile' for+-- streaming via e.g. DASH.+module Data.ByteString.Mp4.AudioStreaming+ ( Segment(..)+ , StreamingContext+ , AacMp4TrackFragment(..)+ , numberOfChannels+ , buildAacMp4TrackFragment+ , getStreamConfig, getStreamBaseTime, getStreamSequence+ , addToBaseTime, getSegmentDuration+ , streamInitINTERNAL_TESTING, streamInitUtc, streamNextSample, streamFlush+ , module X)++where++import Data.Time.Clock+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Boxes+import Data.ByteString.IsoBaseFileFormat.Brands.Dash as X+import Data.ByteString.IsoBaseFileFormat.MediaFile as X+import Data.ByteString.IsoBaseFileFormat.ReExports as X+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields as X+import Data.ByteString.IsoBaseFileFormat.Util.Time as X+import Data.ByteString.Mp4.Boxes.AudioSpecificConfig as X+import Data.ByteString.Mp4.Boxes.Mp4AudioSampleEntry as X+import Data.ByteString.Mp4.AacInitSegment as X++-- | Contains the configuration and state for the creation of a DASH audio+-- stream.+data StreamingContext =+ StreamingContext { acConfig :: !AacInitSegment+ , acSegmentDuration :: !Word64 -- (in ticks)+ , acSequence :: !Word32+ , acBaseTime :: !Word64+ , acSegments :: ![(Word32, BS.ByteString)]+ }++-- | Contains a sample in the ISO14496 style interpretation, i.e.+-- a smallish buffer of e.g. 20ms audio data or a single video frame.+-- A sample has some kind of time or at least order associated to it.+-- TODO not right now, add it+--+-- Also a sample has a duration measured as the sampleCount. TODO make this a+-- real data type, and possible refactor this to be a seperate issue from+-- filling stream gaps and determining the offsets and the decoding time stamp.+data Segment =+ Segment+ { segmentSequence :: !Word32+ , segmentTime :: !Word64+ , segmentData :: !BS.ByteString+ }++-- | Media fragment segment parameters of an aac audio stream mp4 file.+data AacMp4TrackFragment =+ AacMp4TrackFragment { fragmentSampleSequence :: !Word32+ , fragmentBaseMediaDecodeTime :: !Word64+ , fragmentSamples :: ![(Word32, BS.ByteString)]+ }++numberOfChannels :: Num a => StreamingContext -> a+numberOfChannels = channelConfigToNumber . channelConfig . acConfig++addToBaseTime :: StreamingContext -> NominalDiffTime -> StreamingContext+addToBaseTime !sc !dt =+ sc { acSequence = newSequence+ , acBaseTime = diffTimeToTicks newBaseTime timeScale}+ where+ !timeScale = getAacMp4StreamConfigTimeScale (acConfig sc)+ !newSequence = round (newBaseTime / segmentDuration)+ where+ !segmentDuration = ticksToDiffTime (acSegmentDuration sc) timeScale+ !newBaseTime = dt + baseTime+ where+ !baseTime = ticksToDiffTime (acBaseTime sc) timeScale+++-- | Return the 'AacInitSegment' from an 'StreamingContext'+getStreamConfig :: StreamingContext -> AacInitSegment+getStreamConfig StreamingContext{..} = acConfig++-- | Return the current base decoding time+getStreamBaseTime :: StreamingContext -> Word64+getStreamBaseTime StreamingContext{..} = acBaseTime++-- | Return the current sequence number+getStreamSequence :: StreamingContext -> Word32+getStreamSequence StreamingContext{..} = acSequence++instance Show Segment where+ show (Segment !s !t !d) =+ printf "SEGMENT - seq: %14d - time: %14d - size: %14d" s t (BS.length d)+++getSegmentDuration :: StreamingContext -> NominalDiffTime+getSegmentDuration !sc = segmentDuration+ where+ !segmentDuration = ticksToDiffTime (acSegmentDuration sc) timeScale+ !timeScale = getAacMp4StreamConfigTimeScale (acConfig sc)++sampleCountDuration :: AacInitSegment -> Word32 -> Word64+sampleCountDuration (AacInitSegment _ _ _ _ _c) r =+ fromIntegral r -- `div` channelConfigToNumber c TODO clean this whole mess up and remove all Word32/Word64++getAacMp4StreamConfigTimeScale :: AacInitSegment -> TimeScale+getAacMp4StreamConfigTimeScale AacInitSegment{..} =+ TimeScale (sampleRateToNumber sampleRate)++-- | Initiate the 'StreamingContext' and create the /MP4 init segment/.+-- This lives in 'IO' because it read the current time from the real world.+streamInitINTERNAL_TESTING+ :: String+ -> NominalDiffTime+ -> Bool+ -> SamplingFreqTable+ -> ChannelConfigTable+ -> IO (BinaryAacInitSegment, StreamingContext)+streamInitINTERNAL_TESTING !trackTitle !segmentDuration !sbr !rate !channels = do+ t <- mp4CurrentTime+ let !cfg = AacInitSegment t trackTitle sbr rate channels+ !dur = diffTimeToTicks segmentDuration timeScale+ !timeScale = getAacMp4StreamConfigTimeScale cfg+ return ( buildAacInitSegment cfg+ , StreamingContext cfg dur 0 0 [])++-- | Initiate the 'StreamingContext' and create the /MP4 init segment/.+-- This lives in 'IO' because it read the current time from the real world.+streamInitUtc -- TODO remove 'streamInit' in favor of this ??+ :: String+ -> UTCTime+ -> UTCTime+ -> NominalDiffTime+ -> Bool+ -> SamplingFreqTable+ -> ChannelConfigTable+ -> (BinaryAacInitSegment, StreamingContext)+streamInitUtc !trackTitle !availabilityStartTime !refTime !segmentDuration !sbr !rate !channels =+ let !cfg = AacInitSegment t0 trackTitle sbr rate channels+ !t0 = utcToMp4 availabilityStartTime+ !secondsSinceRef = diffUTCTime availabilityStartTime refTime+ !dur = diffTimeToTicks segmentDuration timeScale+ !timeScale = getAacMp4StreamConfigTimeScale cfg+ in ( buildAacInitSegment cfg+ , addToBaseTime (StreamingContext cfg dur 0 0 []) secondsSinceRef)++-- | Enqueue a sample, if enough samples are accumulated, generate the next segment+streamNextSample+ :: Word32+ -> BS.ByteString+ -> StreamingContext+ -> (Maybe Segment, StreamingContext)+streamNextSample !sampleCount !sample !ctx@StreamingContext{..} =+ let !segments = (sampleCount, sample) : acSegments+ !currentEndTime = sampleCountDuration acConfig (sum (fst <$> segments)) + acBaseTime+ !nextBaseTime = (fromIntegral acSequence + 1) * acSegmentDuration64+ !acSegmentDuration64 = fromIntegral acSegmentDuration+ in+ if currentEndTime >= nextBaseTime then+ let !ctx' =+ ctx { acSequence = fromIntegral (currentEndTime `div` acSegmentDuration64)+ , acBaseTime = currentEndTime+ , acSegments = []+ }+ -- Output to write into fragment:+ !tf = buildAacMp4TrackFragment $+ AacMp4TrackFragment acSequence acBaseTime (reverse segments)++ in (Just (Segment acSequence acBaseTime (BL.toStrict (toLazyByteString tf))), ctx')+ else+ (Nothing, ctx{ acSegments = segments })++-- | Enqueue a sample, if enough samples are accumulated, generate the next segment+streamFlush+ :: StreamingContext+ -> (Maybe Segment, StreamingContext)+streamFlush !ctx@StreamingContext{..} =+ let currentEndTime =+ sampleCountDuration acConfig (sum (fst <$> acSegments)) + acBaseTime+ in+ if not (null acSegments) then+ let !tf = buildAacMp4TrackFragment $+ AacMp4TrackFragment acSequence acBaseTime (reverse acSegments)+ !ctx' = ctx { acSequence = fromIntegral+ (currentEndTime `div` fromIntegral acSegmentDuration)+ , acBaseTime = currentEndTime+ , acSegments = []}+ in (Just (Segment acSequence acBaseTime (BL.toStrict (toLazyByteString tf))), ctx')+ else (Nothing, ctx)++-- | Convert a 'AacMp4TrackFragment record to a generic 'Boxes' collection.+buildAacMp4TrackFragment+ :: AacMp4TrackFragment -> Builder+buildAacMp4TrackFragment AacMp4TrackFragment{..} =+ mediaBuilder dash+ ( styp+ :. movieFragment+ ( mfhd+ :| trackFragment+ ( tfhd+ :. tfdt+ :| trun))+ :| mdat)+ where+ !styp = segmentTypeBox (SegmentType "msdh" 0 ["msdh","dash"])+ !mfhd = movieFragmentHeader (MovieFragmentHeader (Scalar fragmentSampleSequence))+ !tfhd = trackFragmentHeader def+ !tfdt = trackFragBaseMediaDecodeTime (TSv1 fragmentBaseMediaDecodeTime)+ !trun = trackRunIso5 mdatOffset fragmentSamples+ where+ !mdatOffset = movieFragmentStaticSize+ + movieFragmentHeaderStaticSize+ + trackFragmentStaticSize+ + trackFragmentHeaderStaticSize+ + trackFragBaseMediaDecodeTimeStaticSize64+ !mdat = mediaData (MediaData (BS.concat (snd <$> fragmentSamples)))
+ src/Data/ByteString/Mp4/Boxes/AudioSpecificConfig.hs view
@@ -0,0 +1,301 @@+{-# LANGUAGE UndecidableInstances #-}+-- | @mp4a@ Audio sample entry according to ISO 14496-14+module Data.ByteString.Mp4.Boxes.AudioSpecificConfig where++import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.ByteString.Mp4.Boxes.DecoderSpecificInfo++-- * Decoder Configuration for ISO 14496-3 (Audio)++-- | A audio config using 'AudioConfigAacMinimal' for AAC-LC.+type AudioConfigAacLc freq channels =+ AudioConfigAacMinimal 'AacLc DefaultGASpecificConfig freq channels++-- | A audio config using 'AudioConfigSbrExplicitHierachical' for HE-AAC (v1) in+-- dual rate mode.+type AudioConfigHeAac freq channels =+ AudioConfigSbrExplicitHierachical+ 'AacLc DefaultGASpecificConfig freq channels freq++-- | A minimalistic audio config without sync and error protection+data AudioConfigAacMinimal+ :: AudioObjectTypeId+ -> IsAn AudioSubConfig+ -> IsAn (EnumOf SamplingFreqTable)+ -> IsAn (EnumOf ChannelConfigTable)+ -> IsA (DecoderSpecificInfo 'AudioIso14496_3 'AudioStream)++type instance+ Eval (AudioConfigAacMinimal+ aoId+ subCfg+ freq+ channels) =+ ('MkDecoderSpecificInfo+ (AudioConfigCommon aoId freq channels (BitRecordOfAudioSubConfig subCfg)))++-- | A audio config with SBR signalled explicit and hierachical+data AudioConfigSbrExplicitHierachical+ :: AudioObjectTypeId+ -> IsAn AudioSubConfig+ -> IsAn (EnumOf SamplingFreqTable)+ -> IsAn (EnumOf ChannelConfigTable)+ -> IsAn (EnumOf SamplingFreqTable) -- extension SamplingFrequency+ -> IsA (DecoderSpecificInfo 'AudioIso14496_3 'AudioStream)++type instance+ Eval+ (AudioConfigSbrExplicitHierachical aoId subCfg freq channels+ extFreq)+ =+ 'MkDecoderSpecificInfo+ (AudioConfigCommon 'Sbr freq channels+ (BitRecordOfEnum extFreq :+:+ AudioObjectTypeRec aoId :+: BitRecordOfAudioSubConfig subCfg))++-- | Common header for audio specific config+type AudioConfigCommon aoId samplingFrequencyIndex channels rest =+ AudioObjectTypeRec aoId+ :+: BitRecordOfEnum samplingFrequencyIndex+ :+: BitRecordOfEnum channels+ :+: rest++-- ** Audio Object Type++data AudioObjectTypeId =+ AacMain -- ^ ISO 14496-4 subpart 4+ | AacLc -- ^ ISO 14496-4 subpart 4+ | AacSsr -- ^ ISO 14496-4 subpart 4+ | AacLtp -- ^ ISO 14496-4 subpart 4+ | Sbr -- ^ ISO 14496-4 subpart 4+ | AacScalable -- ^ ISO 14496-4 subpart 4+ | TwinVq -- ^ ISO 14496-4 subpart 4+ | Celp -- ^ ISO 14496-4 subpart 3+ | Hvxc -- ^ ISO 14496-4 subpart 2+ | AoReserved1+ | AoReserved2+ | Ttsi -- ^ ISO 14496-4 subpart 6+ | MainSunthetic -- ^ ISO 14496-4 subpart 5+ | WavetableSynthesis -- ^ ISO 14496-4 subpart 5+ | GeneralMidi -- ^ ISO 14496-4 subpart 5+ | AlgorithmicSynthesisAndAudioFx -- ^ ISO 14496-4 subpart 5+ | ErAacLc -- ^ ISO 14496-4 subpart 4+ | AoReserved3+ | ErAacLtp -- ^ ISO 14496-4 subpart 4+ | ErAacScalable -- ^ ISO 14496-4 subpart 4+ | ErTwinVq -- ^ ISO 14496-4 subpart 4+ | ErBsac -- ^ ISO 14496-4 subpart 4+ | ErAacLd -- ^ ISO 14496-4 subpart 4+ | ErCelp -- ^ ISO 14496-4 subpart 3+ | ErHvxc -- ^ ISO 14496-4 subpart 2+ | ErHiln -- ^ ISO 14496-4 subpart 7+ | ErParametric -- ^ ISO 14496-4 subpart 2 or 7+ | Ssc -- ^ ISO 14496-4 subpart 8+ | AoReserved4+ | AoReserved5+ | AoCustom+ | AoLayer1 -- ^ ISO 14496-4 subpart 9+ | AoLayer2 -- ^ ISO 14496-4 subpart 9+ | AoLayer3 -- ^ ISO 14496-4 subpart 9+ | AoDst -- ^ ISO 14496-4 subpart 10+ | AotInvalid++type instance FromEnum AudioObjectTypeId 'AotInvalid = 0+type instance FromEnum AudioObjectTypeId 'AacMain = 1+type instance FromEnum AudioObjectTypeId 'AacLc = 2+type instance FromEnum AudioObjectTypeId 'AacSsr = 3+type instance FromEnum AudioObjectTypeId 'AacLtp = 4+type instance FromEnum AudioObjectTypeId 'Sbr = 5+type instance FromEnum AudioObjectTypeId 'AacScalable = 6+type instance FromEnum AudioObjectTypeId 'TwinVq = 7+type instance FromEnum AudioObjectTypeId 'Celp = 8+type instance FromEnum AudioObjectTypeId 'Hvxc = 9+type instance FromEnum AudioObjectTypeId 'AoReserved1 = 10+type instance FromEnum AudioObjectTypeId 'AoReserved2 = 11+type instance FromEnum AudioObjectTypeId 'Ttsi = 12+type instance FromEnum AudioObjectTypeId 'MainSunthetic = 13+type instance FromEnum AudioObjectTypeId 'WavetableSynthesis = 14+type instance FromEnum AudioObjectTypeId 'GeneralMidi = 15+type instance FromEnum AudioObjectTypeId 'AlgorithmicSynthesisAndAudioFx = 16+type instance FromEnum AudioObjectTypeId 'ErAacLc = 17+type instance FromEnum AudioObjectTypeId 'AoReserved3 = 18+type instance FromEnum AudioObjectTypeId 'ErAacLtp = 19+type instance FromEnum AudioObjectTypeId 'ErAacScalable = 20+type instance FromEnum AudioObjectTypeId 'ErTwinVq = 21+type instance FromEnum AudioObjectTypeId 'ErBsac = 22+type instance FromEnum AudioObjectTypeId 'ErAacLd = 23+type instance FromEnum AudioObjectTypeId 'ErCelp = 24+type instance FromEnum AudioObjectTypeId 'ErHvxc = 25+type instance FromEnum AudioObjectTypeId 'ErHiln = 26+type instance FromEnum AudioObjectTypeId 'ErParametric = 27+type instance FromEnum AudioObjectTypeId 'Ssc = 28+type instance FromEnum AudioObjectTypeId 'AoReserved4 = 29+type instance FromEnum AudioObjectTypeId 'AoReserved5 = 30+type instance FromEnum AudioObjectTypeId 'AoCustom = 31+type instance FromEnum AudioObjectTypeId 'AoLayer1 = 32+type instance FromEnum AudioObjectTypeId 'AoLayer2 = 33+type instance FromEnum AudioObjectTypeId 'AoLayer3 = 34+type instance FromEnum AudioObjectTypeId 'AoDst = 35++type AudioObjectTypeRec n =+ AudioObjectTypeField1 (FromEnum AudioObjectTypeId n)+ .+: AudioObjectTypeField2 (FromEnum AudioObjectTypeId n)++type family AudioObjectTypeField1 (n :: Nat)+ :: IsA (BitRecordField ('MkFieldBits :: BitField (B 5) Nat 5)) where+ AudioObjectTypeField1 n =+ If (n <=? 30) (Field 5 := n) (Field 5 := 31)++type family AudioObjectTypeField2 (n :: Nat) :: BitRecord where+ AudioObjectTypeField2 n =+ If (n <=? 30) 'EmptyBitRecord ('BitRecordMember (Field 6 := (n - 31)))++-- *** Sampling Frequency++type SamplingFreq = ExtEnum SamplingFreqTable 4 'SFCustom (Field 24)++data SamplingFreqTable =+ SF96000+ | SF88200+ | SF64000+ | SF48000+ | SF44100+ | SF32000+ | SF24000+ | SF22050+ | SF16000+ | SF12000+ | SF11025+ | SF8000+ | SF7350+ | SFReserved1+ | SFReserved2+ | SFCustom++type instance FromEnum SamplingFreqTable 'SF96000 = 0+type instance FromEnum SamplingFreqTable 'SF88200 = 1+type instance FromEnum SamplingFreqTable 'SF64000 = 2+type instance FromEnum SamplingFreqTable 'SF48000 = 3+type instance FromEnum SamplingFreqTable 'SF44100 = 4+type instance FromEnum SamplingFreqTable 'SF32000 = 5+type instance FromEnum SamplingFreqTable 'SF24000 = 6+type instance FromEnum SamplingFreqTable 'SF22050 = 7+type instance FromEnum SamplingFreqTable 'SF16000 = 8+type instance FromEnum SamplingFreqTable 'SF12000 = 9+type instance FromEnum SamplingFreqTable 'SF11025 = 0xa+type instance FromEnum SamplingFreqTable 'SF8000 = 0xb+type instance FromEnum SamplingFreqTable 'SF7350 = 0xc+type instance FromEnum SamplingFreqTable 'SFReserved1 = 0xd+type instance FromEnum SamplingFreqTable 'SFReserved2 = 0xe+type instance FromEnum SamplingFreqTable 'SFCustom = 0xf++sampleRateToNumber :: Num a => SamplingFreqTable -> a+sampleRateToNumber SF96000 = 96000+sampleRateToNumber SF88200 = 88200+sampleRateToNumber SF64000 = 64000+sampleRateToNumber SF48000 = 48000+sampleRateToNumber SF44100 = 44100+sampleRateToNumber SF32000 = 32000+sampleRateToNumber SF24000 = 24000+sampleRateToNumber SF22050 = 22050+sampleRateToNumber SF16000 = 16000+sampleRateToNumber SF12000 = 12000+sampleRateToNumber SF11025 = 11025+sampleRateToNumber SF8000 = 8000+sampleRateToNumber SF7350 = 7350+sampleRateToNumber _ = 0++sampleRateToEnum :: SamplingFreqTable -> EnumValue SamplingFreqTable+sampleRateToEnum SF96000 = MkEnumValue (Proxy @'SF96000)+sampleRateToEnum SF88200 = MkEnumValue (Proxy @'SF88200)+sampleRateToEnum SF64000 = MkEnumValue (Proxy @'SF64000)+sampleRateToEnum SF48000 = MkEnumValue (Proxy @'SF48000)+sampleRateToEnum SF44100 = MkEnumValue (Proxy @'SF44100)+sampleRateToEnum SF32000 = MkEnumValue (Proxy @'SF32000)+sampleRateToEnum SF24000 = MkEnumValue (Proxy @'SF24000)+sampleRateToEnum SF22050 = MkEnumValue (Proxy @'SF22050)+sampleRateToEnum SF16000 = MkEnumValue (Proxy @'SF16000)+sampleRateToEnum SF12000 = MkEnumValue (Proxy @'SF12000)+sampleRateToEnum SF11025 = MkEnumValue (Proxy @'SF11025)+sampleRateToEnum SF8000 = MkEnumValue (Proxy @'SF8000)+sampleRateToEnum SF7350 = MkEnumValue (Proxy @'SF7350)+sampleRateToEnum SFReserved1 = MkEnumValue (Proxy @'SFReserved1)+sampleRateToEnum SFReserved2 = MkEnumValue (Proxy @'SFReserved2)+sampleRateToEnum SFCustom = MkEnumValue (Proxy @'SFCustom)++-- *** Channel Config (Mono, Stereo, 7-1 Surround, ...)++type ChannelConfig = FixedEnum ChannelConfigTable 4++data ChannelConfigTable =+ GasChannelConfig+ | SingleChannel+ | ChannelPair+ | SinglePair+ | SinglePairSingle+ | SinglePairPair+ | SinglePairPairLfe+ | SinglePairPairPairLfe++type instance FromEnum ChannelConfigTable 'GasChannelConfig = 0+type instance FromEnum ChannelConfigTable 'SingleChannel = 1+type instance FromEnum ChannelConfigTable 'ChannelPair = 2+type instance FromEnum ChannelConfigTable 'SinglePair = 3+type instance FromEnum ChannelConfigTable 'SinglePairSingle = 4+type instance FromEnum ChannelConfigTable 'SinglePairPair = 5+type instance FromEnum ChannelConfigTable 'SinglePairPairLfe = 6+type instance FromEnum ChannelConfigTable 'SinglePairPairPairLfe = 7++channelConfigToNumber :: Num a => ChannelConfigTable -> a+channelConfigToNumber GasChannelConfig = 0+channelConfigToNumber SingleChannel = 1+channelConfigToNumber ChannelPair = 2+channelConfigToNumber SinglePair = 3+channelConfigToNumber SinglePairSingle = 4+channelConfigToNumber SinglePairPair = 5+channelConfigToNumber SinglePairPairLfe = 6+channelConfigToNumber SinglePairPairPairLfe = 7++channelConfigToEnum :: ChannelConfigTable -> EnumValue ChannelConfigTable+channelConfigToEnum GasChannelConfig = MkEnumValue (Proxy @'GasChannelConfig)+channelConfigToEnum SingleChannel = MkEnumValue (Proxy @'SingleChannel)+channelConfigToEnum ChannelPair = MkEnumValue (Proxy @'ChannelPair)+channelConfigToEnum SinglePair = MkEnumValue (Proxy @'SinglePair)+channelConfigToEnum SinglePairSingle = MkEnumValue (Proxy @'SinglePairSingle)+channelConfigToEnum SinglePairPair = MkEnumValue (Proxy @'SinglePairPair)+channelConfigToEnum SinglePairPairLfe = MkEnumValue (Proxy @'SinglePairPairLfe)+channelConfigToEnum SinglePairPairPairLfe = MkEnumValue (Proxy @'SinglePairPairPairLfe)++-- ** More Specific audio decoder config++data AudioSubConfig :: Type++type family BitRecordOfAudioSubConfig (x :: IsA AudioSubConfig) :: BitRecord++data GASpecificConfig+ (frameLenFlag :: IsA (FieldValue "frameLenFlag" Bool))+ (coreCoderDelay :: Maybe (IsA (FieldValue "coreCoderDelay" Nat)))+ (extension :: IsA GASExtension)+ :: IsA AudioSubConfig++type DefaultGASpecificConfig =+ GASpecificConfig (StaticFieldValue "frameLenFlag" 'False) 'Nothing MkGASExtension++type instance Eval (GASpecificConfig fl cd ext)+ = TypeError ('Text "AudioSubConfig is abstract!")+++type instance+ BitRecordOfAudioSubConfig (GASpecificConfig fl cd ext) =+ ( Flag :~ fl+ .+: FlagJust cd+ .+: Field 14 :+? cd+ :+: BitRecordOfGASExtension ext+ )++-- | TODO implment that GAS extensions+data GASExtension+data MkGASExtension :: IsA GASExtension++type BitRecordOfGASExtension (x :: IsA GASExtension) =+ 'BitRecordMember ("has-gas-extension" @: Flag := 'False)
+ src/Data/ByteString/Mp4/Boxes/BaseDescriptor.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE UndecidableInstances #-}+module Data.ByteString.Mp4.Boxes.BaseDescriptor where++import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.ByteString.Mp4.Boxes.Expandable++-- * Static base constructor++-- | Abstract class of /descriptors/ as recognized by ISO/IEC 14496-1 (Systems).+-- A specifc descriptor is identified by the 'ClassTag'.+data Descriptor :: ClassTag n -> Type where+ MkDescriptor :: BitRecord -> Descriptor tag++-- TODO ok... this fixed the current problem in DecoderSpecificInfo .. but remove this instances ... or the above ... or ... I dunno++data BitRecordOfDescriptor :: IsA (Descriptor c :-> BitRecord)++type instance+ BitRecordOfDescriptor $~ ('MkDescriptor body :: Descriptor (tag :: ClassTag tagInd)) =+ FieldU8 := tagInd+ .+: Eval (StaticExpandableContent body)++type family GetClassTag (c :: ClassTag n) :: Nat where+ GetClassTag (c :: ClassTag n) = n++-- | Base Descriptor Class Tags TODO rename to xxxTag+data ClassTag (tag :: Nat) where+ ObjectDescr :: ClassTag 0x01+ InitialObjectDescr :: ClassTag 0x02+ ES_Descr :: ClassTag 0x03+ DecoderConfigDescr :: ClassTag 0x04+ DecSpecificInfo :: ClassTag 0x05+ SLConfigDescr :: ClassTag 0x06+ ContentIdentDescr :: ClassTag 0x07+ SupplContentIdentDescr :: ClassTag 0x08+ IPI_DescrPointer :: ClassTag 0x09+ IPMP_DescrPointer :: ClassTag 0x0A+ IPMP_Descr :: ClassTag 0x0B+ QoS_Descr :: ClassTag 0x0C+ RegistrationDescr :: ClassTag 0x0D+ ES_ID_Ref :: ClassTag 0x0F+ MP4_IOD_ :: ClassTag 0x10+ MP4_OD_ :: ClassTag 0x11+ IPL_DescrPointerRef :: ClassTag 0x12+ ExtensionProfileLevelDescr :: ClassTag 0x13+ ProfileLevelIndicationIndexDescr :: ClassTag 0x14+ ContentClassificationDescr :: ClassTag 0x40+ KeyWordDescr :: ClassTag 0x41+ RatingDescr :: ClassTag 0x42+ LanguageDescr :: ClassTag 0x43+ ShortTextualDescr :: ClassTag 0x44+ ExpandedTextualDescr :: ClassTag 0x45+ ContentCreatorNameDescr :: ClassTag 0x46+ ContentCreationDateDescr :: ClassTag 0x47+ OCICreatorNameDescr :: ClassTag 0x48+ OCICreationDateDescr :: ClassTag 0x49+ SmpteCameraPositionDescr :: ClassTag 0x4A+ SegmentDescr :: ClassTag 0x4B+ MediaTimeDescr :: ClassTag 0x4C+ IPMP_ToolsListDescr :: ClassTag 0x60+ IPMP_Tool :: ClassTag 0x61+ M4MuxTimingDescr :: ClassTag 0x62+ M4MuxCodeTableDescr :: ClassTag 0x63+ ExtSLConfigDescr :: ClassTag 0x64+ M4MuxBufferSizeDescr :: ClassTag 0x65+ M4MuxIdentDescr :: ClassTag 0x66+ DependencyPointer :: ClassTag 0x67+ DependencyMarker :: ClassTag 0x68+ M4MuxChannelDescr :: ClassTag 0x69+ ExtDescrTag :: forall (n :: Nat) . (0x6A <= n, n <= 0xFE) => ClassTag n+ OCIDescrTag :: forall (n :: Nat) . (0x40 <= n, n <= 0x5F) => ClassTag n
+ src/Data/ByteString/Mp4/Boxes/DecoderConfigDescriptor.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE UndecidableInstances #-}+module Data.ByteString.Mp4.Boxes.DecoderConfigDescriptor where++import Data.ByteString.Mp4.Boxes.BaseDescriptor+import Data.ByteString.Mp4.Boxes.DecoderSpecificInfo+import Data.Type.BitRecords+import Data.Kind.Extra+import GHC.TypeLits++-- | Information about what decoder is required for the an elementary stream.+-- The stream type indicates the general category of the stream and.+data DecoderConfigDescriptor+ (ot :: ObjectTypeIndication)+ (st :: StreamType)+ :: [IsA (DecoderSpecificInfo ot st)]+ -> [IsA (Descriptor 'ProfileLevelIndicationIndexDescr)]+ -> IsA (Descriptor 'DecoderConfigDescr)++type instance Eval (DecoderConfigDescriptor ot st di ps) =+ 'MkDescriptor (DecoderConfigDescriptorBody ot st di ps)++type family+ DecoderConfigDescriptorBody+ ot st+ (di :: [IsA (DecoderSpecificInfo ot st)])+ (ps :: [IsA (Descriptor 'ProfileLevelIndicationIndexDescr)])+ :: BitRecord+ where+ DecoderConfigDescriptorBody ot st di ps =+ BitRecordOfEnum (SetEnum "objectTypeIndication" ObjectTypeIndicationEnum ot)+ :+: BitRecordOfEnum (SetEnum "objectTypeIndication" StreamTypeEnum st)+ :+: "upstream" @: Flag+ .+: "reserved" @: Field 1 := 1+ .+: "bufferSizeDB" @: Field 24+ .+: "maxBitrate" @: FieldU32+ .+: "avgBitrate" @: FieldU32+ .+: Eval (BitRecordOfList+ (DescriptorOfDecoderSpecificInfo+ :^>>>: BitRecordOfDescriptor)+ (di ?:: LengthIn 0 1))+ :+: Eval (BitRecordOfList+ (Extract :>>>: BitRecordOfDescriptor)+ (ps ?:: LengthIn 0 255))++-- ** 'ProfileLevelIndicationIndexDescriptor'++data ProfileLevelIndicationIndexDescriptor+ :: IsA (FieldValue "profileLevelIndicationIndex" Nat)+ -> IsA (Descriptor 'ProfileLevelIndicationIndexDescr)++type instance Eval (ProfileLevelIndicationIndexDescriptor val) =+ 'MkDescriptor+ ('BitRecordMember (FieldU8 :~ val))
+ src/Data/ByteString/Mp4/Boxes/DecoderSpecificInfo.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE UndecidableInstances #-}+module Data.ByteString.Mp4.Boxes.DecoderSpecificInfo where++import Data.ByteString.Mp4.Boxes.BaseDescriptor+import Data.Type.BitRecords+import Data.Kind.Extra+import Data.Kind (type Type)++-- * Abstract class for opaque object- and stream type dependent decoder+-- settings.++-- | Base type of decoders+data DecoderSpecificInfo :: ObjectTypeIndication -> StreamType -> Type where+ MkDecoderSpecificInfo :: BitRecord -> DecoderSpecificInfo o s++data DescriptorOfDecoderSpecificInfo+ :: IsA (DecoderSpecificInfo ot st :-> Descriptor 'DecSpecificInfo)++type instance DescriptorOfDecoderSpecificInfo $~ 'MkDecoderSpecificInfo body =+ 'MkDescriptor body++type ObjectTypeIndicationEnum = FixedEnum ObjectTypeIndication 8++data ObjectTypeIndication =+ SystemsIso14496_1_a+ | SystemsIso14496_1_b+ | InteractionStreamObjInd+ | SystemsIso14496_1_ExtendedBifs+ | SystemsIso14496_1_Afx+ | FontDataStream+ | SynthesizedTextureStream+ | StreamingTextStream+ | VisualIso14496_2+ | VisualH264+ | VisualH264ParameterSets+ | AudioIso14496_3+ | VisualIso13818_2_SimpleProfile+ | VisualIso13818_2_MainProfile+ | VisualIso13818_2_SnrProfile+ | VisualIso13818_2_SpatialProfile+ | VisualIso13818_2_HighProfile+ | VisualIso13818_2_422Profile+ | AudioIso13818_7_MainProfile+ | AudioIso13818_7_LowComplexityProfile+ | AudioIso13818_7_ScalableSamplingRateProfile+ | AudioIso13818_3+ | VisualIso11172_2+ | AudioIso11172_3+ | VisualIso10918_1+ | VisualIso15444_1+ | NoObjectTypeSpecified++type instance FromEnum ObjectTypeIndication 'SystemsIso14496_1_a = 0x01+type instance FromEnum ObjectTypeIndication 'SystemsIso14496_1_b = 0x02+type instance FromEnum ObjectTypeIndication 'InteractionStreamObjInd = 0x03+type instance FromEnum ObjectTypeIndication 'SystemsIso14496_1_ExtendedBifs = 0x04+type instance FromEnum ObjectTypeIndication 'SystemsIso14496_1_Afx = 0x05+type instance FromEnum ObjectTypeIndication 'FontDataStream = 0x06+type instance FromEnum ObjectTypeIndication 'SynthesizedTextureStream = 0x07+type instance FromEnum ObjectTypeIndication 'StreamingTextStream = 0x08+type instance FromEnum ObjectTypeIndication 'VisualIso14496_2 = 0x20+type instance FromEnum ObjectTypeIndication 'VisualH264 = 0x21+type instance FromEnum ObjectTypeIndication 'VisualH264ParameterSets = 0x22+type instance FromEnum ObjectTypeIndication 'AudioIso14496_3 = 0x40+type instance FromEnum ObjectTypeIndication 'VisualIso13818_2_SimpleProfile = 0x60+type instance FromEnum ObjectTypeIndication 'VisualIso13818_2_MainProfile = 0x61+type instance FromEnum ObjectTypeIndication 'VisualIso13818_2_SnrProfile = 0x62+type instance FromEnum ObjectTypeIndication 'VisualIso13818_2_SpatialProfile = 0x63+type instance FromEnum ObjectTypeIndication 'VisualIso13818_2_HighProfile = 0x64+type instance FromEnum ObjectTypeIndication 'VisualIso13818_2_422Profile = 0x65+type instance FromEnum ObjectTypeIndication 'AudioIso13818_7_MainProfile = 0x66+type instance FromEnum ObjectTypeIndication 'AudioIso13818_7_LowComplexityProfile = 0x67+type instance FromEnum ObjectTypeIndication 'AudioIso13818_7_ScalableSamplingRateProfile = 0x68+type instance FromEnum ObjectTypeIndication 'AudioIso13818_3 = 0x69+type instance FromEnum ObjectTypeIndication 'VisualIso11172_2 = 0x6A+type instance FromEnum ObjectTypeIndication 'AudioIso11172_3 = 0x6B+type instance FromEnum ObjectTypeIndication 'VisualIso10918_1 = 0x6C+type instance FromEnum ObjectTypeIndication 'VisualIso15444_1 = 0x6E+type instance FromEnum ObjectTypeIndication 'NoObjectTypeSpecified = 0xFF++-- * Stream Type++type family+ GetStreamType (t :: k) :: StreamType++type StreamTypeEnum = FixedEnum StreamType 6++data StreamType =+ ObjectDescriptorStream+ | ClockReferenceStream+ | SceneDescriptionStream_Iso14496_11+ | VisualStream+ | AudioStream+ | Mpeg7Stream+ | IpmpStream+ | ObjectContentInfoStream+ | MpegJStream+ | InteractionStream+ | IpmpToolStream_Iso14496_13++type instance FromEnum StreamType 'ObjectDescriptorStream = 1+type instance FromEnum StreamType 'ClockReferenceStream = 2+type instance FromEnum StreamType 'SceneDescriptionStream_Iso14496_11 = 3+type instance FromEnum StreamType 'VisualStream = 4+type instance FromEnum StreamType 'AudioStream = 5+type instance FromEnum StreamType 'Mpeg7Stream = 6+type instance FromEnum StreamType 'IpmpStream = 7+type instance FromEnum StreamType 'ObjectContentInfoStream = 8+type instance FromEnum StreamType 'MpegJStream = 9+type instance FromEnum StreamType 'InteractionStream = 0xa+type instance FromEnum StreamType 'IpmpToolStream_Iso14496_13 = 0xb
+ src/Data/ByteString/Mp4/Boxes/ElementaryStreamDescriptor.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}+module Data.ByteString.Mp4.Boxes.ElementaryStreamDescriptor where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Util.FullBox+import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.ByteString.Mp4.Boxes.BaseDescriptor+import Data.ByteString.Mp4.Boxes.SyncLayerConfigDescriptor++-- * Esd Box++type EsdBox = Box (FullBox Esd 0)+newtype Esd = Esd BuilderBox deriving (IsBoxContent)+instance IsBox Esd++type instance BoxTypeSymbol Esd = "esds"++esdBox :: forall (record :: IsA (Descriptor 'ES_Descr)) (rendered :: BitRecord) .+ ( BitStringBuilderHoley (Proxy rendered) EsdBox+ , rendered ~ (RenderEsDescr record))+ => Proxy record -> ToBitStringBuilder (Proxy rendered) EsdBox+esdBox =+ toFunction+ . esdBoxHoley++esdBoxHoley :: forall (record :: IsA (Descriptor 'ES_Descr)) r (rendered :: BitRecord) .+ ( BitStringBuilderHoley (Proxy rendered) r+ , rendered ~ (RenderEsDescr record)+ )+ => Proxy record -> FunctionBuilder EsdBox r (ToBitStringBuilder (Proxy rendered) r)+esdBoxHoley _p =+ mapAccumulator (fullBox 0 . Esd) $+ bitBuilderBoxHoley (Proxy @rendered)++type RenderEsDescr (d :: IsA (Descriptor 'ES_Descr)) =+ BitRecordOfDescriptor $~ (Eval d)++-- * Esd Record++data ESDescriptor+ :: IsA (FieldValue "esId" Nat)+ -> Maybe (IsA (FieldValue "depEsId" Nat))+ -- TODO Improve the custom field and also the sizedstring API+ -> Maybe (IsA (BitRecordField ('MkFieldCustom :: BitField ASizedString ASizedString (urlSize :: Nat))))+ -> Maybe (IsA (FieldValue "ocrEsId" Nat))+ -> IsA (FieldValue "streamPrio" Nat)+ -> IsA (Descriptor 'DecoderConfigDescr)+ -> IsA (Descriptor 'SLConfigDescr)+ -> IsA (Descriptor 'ES_Descr)++-- | ISO-14496-14 section 3.1.2 defines restrictions of the elementary stream+-- descriptor.+-- TODO seperate this and other modules so theres the same seperation as in between+-- the parts of the standard.+type ESDescriptorMp4File esId decConfigDescr =+ ESDescriptor esId 'Nothing 'Nothing+ 'Nothing DefaultStreamPrio+ decConfigDescr Mp4SyncLayerDescriptor++type DefaultEsId = StaticFieldValue "esId" 1+type DefaultStreamPrio = StaticFieldValue "streamPrio" 0++type instance+ Eval (ESDescriptor esId depEsId url ocrEsId streamPrio decConfig slConfig) =+ 'MkDescriptor+ ("esId" @: FieldU16 :~ esId+ .+: "depEsIdFlag" @: FlagJust depEsId+ .+: "urlFlag" @: FlagJust url+ .+: "ocrEsIdFlag" @: FlagJust ocrEsId+ .+: "streamPriority" @: Field 5 :~ streamPrio+ .+: ("depEsId" @: FieldU16 :+? depEsId)+ :+: (Eval (OptionalRecordOf (Fun1 RecordField) url))+ :+: ("ocrEsId" @: FieldU16 :+? ocrEsId)+ :+: (BitRecordOfDescriptor $~ Eval decConfig)+ :+: (BitRecordOfDescriptor $~ Eval slConfig)++ -- TODO add the rest of the ESDescriptor+ )
+ src/Data/ByteString/Mp4/Boxes/Expandable.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}++module Data.ByteString.Mp4.Boxes.Expandable where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.ReExports++-- * Static Expandable++data StaticExpandableContent :: BitRecord -> IsA BitRecord++type StaticExpandableContentMaxBits = 32++type instance Eval (StaticExpandableContent record) =+ (ExpandableSize+ (ShiftR StaticExpandableContentMaxBits (BitRecordSize record) 3))+ :+: record++type family ExpandableSize (s :: Nat) :: BitRecord where+ ExpandableSize 0 = 'EmptyBitRecord+ ExpandableSize s =+ If (s <=? 127)+ (ExpandableSizeLastChunk s)+ (ExpandableSizeNext (ShiftR StaticExpandableContentMaxBits s 7)+ :+: ExpandableSizeLastChunk s)++type ExpandableSizeLastChunk (s :: Nat) = Field 1 := 0 .+. Field 7 := s++type family ExpandableSizeNext (s :: Nat) :: BitRecord where+ ExpandableSizeNext 0 = 'EmptyBitRecord+ ExpandableSizeNext s =+ If (s <=? 127)+ (ExpandableSizeNextChunk s)+ (ExpandableSizeNext (ShiftR StaticExpandableContentMaxBits s 7)+ :+: ExpandableSizeNextChunk s)++type ExpandableSizeNextChunk (s :: Nat) = Field 1 := 1 .+. Field 7 := s+++-- * Runtime-value Expandable+-- TODO remove "runtime" Expandable?+newtype Expandable t where+ Expandable :: t -> Expandable t++instance IsBoxContent t => IsBoxContent (Expandable t) where+ boxSize (Expandable x) = expandableSizeSize (boxSize x) + boxSize x+ boxBuilder (Expandable x) = expandableSizeBuilder (boxSize x) <> boxBuilder x++expandableSizeSize :: BoxSize -> BoxSize+expandableSizeSize UnlimitedSize = error "Unlimited size not supported by expandable"+expandableSizeSize (BoxSize s)+ | s >= 2^(28 :: Int) = error "Expandable size >= 2^(28 :: Int)"+ | s >= 2^(21 :: Int) = 4+ | s >= 2^(14 :: Int) = 3+ | s >= 2^(7 :: Int) = 2+ | otherwise = 1++expandableSizeBuilder :: BoxSize -> Builder+expandableSizeBuilder UnlimitedSize = error "Unlimited size not supported by expandable"+expandableSizeBuilder (BoxSize s)+ | s >= 2 ^( 28 :: Int) = error "Expandable size >= 2^(28 :: Int)"+ | s >= 2 ^( 21 :: Int) = word8 (fromIntegral (0x80 .|. (s `unsafeShiftR` 21))) <>+ word8 (fromIntegral (0x80 .|. ((s `unsafeShiftR` 14) .&. 0x7F))) <>+ word8 (fromIntegral (0x80 .|. ((s `unsafeShiftR` 7) .&. 0x7F))) <>+ word8 (fromIntegral (s .&. 0x7F))+ | s >= 2 ^( 14 :: Int) = word8 (fromIntegral (0x80 .|. (s `unsafeShiftR` 14))) <>+ word8 (fromIntegral (0x80 .|. ((s `unsafeShiftR` 7) .&. 0x7F))) <>+ word8 (fromIntegral (s .&. 0x7F))+ | s >= 2 ^( 7 :: Int) = word8 (fromIntegral (0x80 .|. (s `unsafeShiftR` 7))) <>+ word8 (fromIntegral (s .&. 0x7F))+ | otherwise = word8 (fromIntegral s)
+ src/Data/ByteString/Mp4/Boxes/Mp4AudioSampleEntry.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE UndecidableInstances #-}+-- | @mp4a@ Audio sample entry according to ISO 14496-14+module Data.ByteString.Mp4.Boxes.Mp4AudioSampleEntry where++import Data.ByteString.IsoBaseFileFormat.Box+import Data.ByteString.IsoBaseFileFormat.Boxes+import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.ByteString.IsoBaseFileFormat.Util.BoxFields+import Data.ByteString.Mp4.Boxes.ElementaryStreamDescriptor+import Data.ByteString.Mp4.Boxes.DecoderSpecificInfo+import Data.ByteString.Mp4.Boxes.DecoderConfigDescriptor+import Data.ByteString.Mp4.Boxes.AudioSpecificConfig++-- | A /body/ for 'AudioSampleEntry'. This 'IsBoxContent' with an+-- 'ElementaryStreamDescriptor' for ISO-14496-3 audio, with audio decoder+-- specific info.++-- | Create an 'AudioSampleEntry' with an 'AudioEsd'+audioSampleEntry+ :: U16 "data_reference_index"+ -> AudioSampleEntry AudioEsd+ -> Box (SampleEntry (AudioSampleEntry AudioEsd))+audioSampleEntry drefIndex ase =+ sampleEntry drefIndex ase++type instance BoxTypeSymbol AudioEsd = "mp4a"++-- | Create an mp4 audio elementary stream descriptor full box+aacAudioSampleEntrySimple+ :: Bool+ -> SamplingFreqTable+ -> ChannelConfigTable+ -> U16 "samplesize"+ -> AudioSampleEntry AudioEsd+aacAudioSampleEntrySimple sbr sf cc sampleSize =+ AudioSampleEntry+ (Constant+ :+ Custom (Scalar (channelConfigToNumber cc))+ :+ Custom sampleSize+ :+ 0+ :+ Constant+ :+ Custom (Scalar (sampleRateToNumber sf * 65536))+ :+ mkAudioEsdAacLcOrHeAac sbr sf cc)++mkAudioEsdAacLcOrHeAac+ :: Bool+ -> SamplingFreqTable+ -> ChannelConfigTable+ -> AudioEsd+mkAudioEsdAacLcOrHeAac False sf cc =+ AudioEsd+ (esdBox+ (Proxy @Mp4AacLcEsDescriptor)+ False 0 0 0+ (sampleRateToEnum sf)+ (channelConfigToEnum cc))+mkAudioEsdAacLcOrHeAac True sf cc =+ AudioEsd+ (esdBox+ (Proxy @Mp4HeAacEsDescriptor)+ False 0 0 0+ (sampleRateToEnum sf)+ (channelConfigToEnum cc)+ (sampleRateToEnum sf))++-- | Consists of an 'ElementaryStreamDescriptor' derived from a 'DecoderSpecificInfo'.+newtype AudioEsd =+ AudioEsd EsdBox+ deriving (IsBoxContent)++type Mp4AacLcEsDescriptor =+ ESDescriptorMp4File DefaultEsId+ (Mp4AacAudioDecoderConfigDescriptor+ (AudioConfigAacLc+ (EnumParam "samplingFreq" SamplingFreq)+ (EnumParam "channelConfig" ChannelConfig)))++type Mp4HeAacEsDescriptor =+ ESDescriptorMp4File DefaultEsId+ (Mp4AacAudioDecoderConfigDescriptor+ (AudioConfigHeAac+ (EnumParam "samplingFreq" SamplingFreq)+ (EnumParam "channelConfig" ChannelConfig)))++type Mp4AacAudioDecoderConfigDescriptor cfg =+ DecoderConfigDescriptor+ 'AudioIso14496_3+ 'AudioStream+ '[cfg]+ '[]
+ src/Data/ByteString/Mp4/Boxes/SyncLayerConfigDescriptor.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE UndecidableInstances #-}+module Data.ByteString.Mp4.Boxes.SyncLayerConfigDescriptor where++import Data.ByteString.IsoBaseFileFormat.ReExports+import Data.ByteString.Mp4.Boxes.BaseDescriptor++data Mp4SyncLayerDescriptor :: IsA (Descriptor 'SLConfigDescr)++-- | In the holy scripture, ISO-14496-14 section 3.1.2, it is written that there+-- shall be restrictions on the elementary stream descriptor, in there it says:+-- Thou shall use only __two__ as the value for the __predefined__ field in the+-- blessed __SLDescriptor__. Not one, this is a value not big enough, nor three,+-- this value is too much. The righteous one ever only uses __two__. Only a fool+-- will use __256__.+type instance Eval Mp4SyncLayerDescriptor =+ 'MkDescriptor ('BitRecordMember ("predefined" @: FieldU8 := 0x02))
+ src/Data/Kind/Extra.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE UndecidableInstances #-}+-- | Kind-level utilities to guide and simplify programming at the type level+module Data.Kind.Extra+ ( type A(..)+ , type IsA+ , type IsAn+ , type Return+ , type Pure+ , type Eval+ , type (:->)+ , type Id+ , type Apply+ , type (^*^)+ , type ($~)+ , type (:>>=:)+ , type (:>>>:)+ , type (:^>>>:)+ , type (:>>>^:)+ , type (:^>>>^:)+ , type Extract+ , type Optional+ , type FoldMap+ , type Fun1+ , type Fun2+ , type Fun3+ , type Fun4+ ) where++import Data.Kind (type Type)++-- * Open Promoted Types++-- | A group of types that all 'Eval' into the same type. This+-- is an /open/ and /extensible/ alternative to defining an algebraic data type+-- and using the promoted constructors. 'A' goes hand-in-hand with 'IsA' for+-- better readability.+--+-- For example:+--+-- @+-- data PrettyPrinter c where+-- RenderText :: Symbol -> PrettyPrinter Symbol+-- WithColor :: Color -> PrettyPrinter c -> PrettyPrinter c+--+-- data Color = Black | White+--+-- data ColoredText :: Color -> Symbol -> 'IsA' (PrettyPrinter Symbol)+--+-- type instance 'Eval' (ColoredText c txt) = 'WithColor c ('RenderText txt)+-- @+data A :: forall foo . foo -> Type where+ MkA :: A foo++-- | Type alias for 'A' such that @data Point2 x y :: A Vec2 -> Type@ becomes+-- @data Point2 x y :: IsA Vec2@+type IsA (foo :: k) = (A foo -> Type :: Type)++-- | An alias to 'IsA'+type IsAn (oo :: k) = (IsA oo :: Type)++-- | An open type family to turn /symbolic/ type representations created with+-- 'A' or 'IsA' into the actual types.+type family Eval (t :: A foo -> Type) :: foo++-- | A type @foo@, @'IsA' foo@.+data Pure (f :: o) :: IsAn o+type instance Eval (Pure f) = f+type Return f = Pure f++-- | A symbolic type-level function.+data (:->) foo bar++-- | An open family of functions from @foo@ to @bar@+type family (f :: IsA (foo :-> bar)) $~ (x :: foo) :: bar+infixl 0 $~++-- | An alias for '$~'+type Apply f x = f $~ x++-- | Identity+data Id :: IsA (foo :-> foo)+type instance Id $~ x = x++-- | Symbolic function application+type (^*^) (f :: IsA (foo :-> bar)) (x :: IsA foo) = f $~ (Eval x)+infixl 0 ^*^++-- | Compose functions+data (:>>>:) :: IsA (good :-> better) -> IsA (better :-> best) -> IsA (good :-> best)+infixl 1 :>>>:+type instance (f :>>>: g) $~ x = g $~ (f $~ x)++-- | Eval Input & Compose+data (:^>>>:) :: IsA (good :-> better) -> IsA (better :-> best) -> IsA (IsA good :-> best)+infixl 1 :^>>>:+type instance (f :^>>>: g) $~ x = g $~ (f $~ Eval x)++-- | Compose and 'Return'+data (:>>>^:) :: IsA (good :-> better) -> IsA (better :-> best) -> IsA (good :-> IsA best)+infixl 1 :>>>^:+type instance (f :>>>^: g) $~ x = Return (g $~ (f $~ x))++-- | Eval compose and return+data (:^>>>^:) :: IsA (good :-> better) -> IsA (better :-> best) -> IsA (IsA good :-> IsA best)+infixl 1 :^>>>^:+type instance (f :^>>>^: g) $~ x = Return (g $~ (f $~ Eval x))++-- | A function that applies 'Eval'+data Extract :: IsA (IsA x :-> x)+type instance Extract $~ x = Eval x++-- | Eval and ApplyCompose functions+data (:>>=:) :: IsA foo -> IsA (foo :-> IsA bar) -> IsA bar+infixl 1 :>>=:+type instance Eval (x :>>=: f) = Eval (f $~ Eval x)++-- | Either use the value from @Just@ or return a fallback value(types(kinds))+data Optional :: IsA t -> IsA (s :-> IsA t) -> IsA (Maybe s :-> IsA t)++type instance (Optional fallback f) $~ ('Just s) = f $~ s+type instance (Optional fallback f) $~ 'Nothing = fallback++-- | Map over the elements of a list and fold the result.+type family+ FoldMap+ (append :: IsA (bar :-> IsA (bar :-> bar)))+ (zero :: bar)+ (f :: IsA (foo :-> bar))+ (xs :: [(foo :: Type)]) :: (bar :: Type) where+ FoldMap append zero f '[] = zero+ FoldMap append zero f (x ': xs) = append $~ (f $~ x) $~ FoldMap append zero f xs++-- TODONT safe coercions (with undecidable instances) could be done only with a+-- type-level equivilant of a type class dictionary, A good place for that+-- might be 'A'. For now I only had trouble with 'CoerceTo' because it is open+-- and the compiler often used up __all__ main memory when an instance was+-- missing.++-- | Like @TyCon1@ from Data.Singletons+data Fun1 :: (a -> IsA b)+ -> IsA (a :-> IsA b)+type instance (Fun1 f) $~ x = (f x)++data Fun2 :: (a -> b -> IsA c)+ -> IsA (a :-> IsA (b :-> IsA c))+type instance (Fun2 f) $~ x = Fun1 (f x)++data Fun3 :: (a -> b -> c -> IsA d)+ -> IsA (a :-> IsA (b :-> IsA (c :-> IsA d)))+type instance (Fun3 f) $~ x = Fun2 (f x)++data Fun4 :: (a -> b -> c -> d -> IsAn e)+ -> IsA (a :-> IsA (b :-> IsA (c :-> IsA (d :-> IsAn e))))+type instance (Fun4 f) $~ x = Fun3 (f x)
+ src/Data/Type/BitRecords.hs view
@@ -0,0 +1,22 @@+module Data.Type.BitRecords+ ( module Data.Type.BitRecords.Arithmetic+ , module Data.Type.BitRecords.Assert+ , module Data.Type.BitRecords.Builder.BitBuffer+ , module Data.FunctionBuilder+ , module Data.Type.BitRecords.Builder.LazyByteStringBuilder+ , module Data.Type.BitRecords.Core+ , module Data.Type.BitRecords.Enum+ , module Data.Type.BitRecords.Sized+ , module Data.Type.BitRecords.SizedString+ )+where++import Data.Type.BitRecords.Arithmetic+import Data.Type.BitRecords.Assert+import Data.Type.BitRecords.Builder.BitBuffer+import Data.FunctionBuilder+import Data.Type.BitRecords.Builder.LazyByteStringBuilder+import Data.Type.BitRecords.Core+import Data.Type.BitRecords.Enum+import Data.Type.BitRecords.Sized+import Data.Type.BitRecords.SizedString
+ src/Data/Type/BitRecords/Arithmetic.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE UndecidableInstances #-}+module Data.Type.BitRecords.Arithmetic where++import Data.Type.Bool+import GHC.TypeLits++-- | Efficient 'Mod' operation for power of 2 values. Note that x must be+-- representable by 'ModPow2Bits' bits.+type ModPow2 value power = FromBits (TakeLastN power (ToBits value ModPow2Bits))++type TakeLastN n xs = TakeLastNImplRev n xs '[]++type family TakeLastNImplRev (n :: Nat) (xs :: [t]) (acc :: [t]) :: [t] where+ TakeLastNImplRev n '[] acc = TakeLastNImplTakeNRev n acc '[]+ TakeLastNImplRev n (x ': xs) acc =+ TakeLastNImplRev n xs (x ': acc)++type family TakeLastNImplTakeNRev (n :: Nat) (rs :: [t]) (acc :: [t]) :: [t] where+ TakeLastNImplTakeNRev n '[] acc = acc+ TakeLastNImplTakeNRev 0 rs acc = acc+ TakeLastNImplTakeNRev n (r ': rs) acc = TakeLastNImplTakeNRev (n-1) rs (r ': acc)+++-- | Maximum number of bits an argument @x@ of 'ModPow2' may occupy.+type ModPow2Bits = 32++-- * Bit manipulation++type family TestHighBit (x :: Nat) (n :: Nat) :: Bool where+ TestHighBit x n = ((2 ^ n) <=? x) -- x > 2^n++type ToBits x n = ToBits_ x n 'False+type family ToBits_ (x :: Nat) (n :: Nat) (started :: Bool) :: [Bool] where+ ToBits_ x 0 started = '[]+ ToBits_ x n started = ToBitsInner (TestHighBit x (n - 1)) x (n - 1) started+type family+ ToBitsInner (highBitSet :: Bool) (x :: Nat) (n :: Nat) (started :: Bool) :: [Bool] where+ ToBitsInner 'True x n started = 'True ': ToBits_ (x - 2^n) n 'True+ ToBitsInner 'False x n 'False = ToBits_ x n 'False+ ToBitsInner 'False x n 'True = 'False ': ToBits_ x n 'True++type FromBits bits = FromBits_ bits 0+type family FromBits_ (bits :: [Bool]) (acc :: Nat) :: Nat where+ FromBits_ '[] acc = acc+ FromBits_ ('False ': rest) acc = FromBits_ rest (acc + acc)+ FromBits_ ('True ': rest) acc = FromBits_ rest (1 + acc + acc)++type family+ ShiftBitsR (bits :: [Bool]) (n :: Nat) :: [Bool] where+ ShiftBitsR bits 0 = bits+ ShiftBitsR '[] n = '[]+ ShiftBitsR '[e] 1 = '[]+ ShiftBitsR (e ': rest) 1 = e ': ShiftBitsR rest 1+ ShiftBitsR (e ': rest) n = ShiftBitsR (ShiftBitsR (e ': rest) 1) (n - 1)++type family+ GetMostSignificantBitIndex (highestBit :: Nat) (n :: Nat) :: Nat where+ GetMostSignificantBitIndex 0 n = 1+ GetMostSignificantBitIndex highestBit n =+ If (2 ^ (highestBit + 1) <=? n)+ (TypeError ('Text "number to big: "+ ':<>: 'ShowType n+ ':<>: 'Text " >= "+ ':<>: 'ShowType (2 ^ (highestBit + 1))))+ (If (2 ^ highestBit <=? n)+ highestBit+ (GetMostSignificantBitIndex (highestBit - 1) n))++-- | Shift a type level natural to the right. This useful for division by powers+-- of two.+type family+ ShiftR (xMaxBits :: Nat) (x :: Nat) (bits :: Nat) :: Nat where+ ShiftR xMaxBits x n =+ FromBits+ (ShiftBitsR+ (ToBits x+ (1 + GetMostSignificantBitIndex xMaxBits x))+ n)
+ src/Data/Type/BitRecords/Assert.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE UndecidableInstances #-}+-- | Type - level assertion utility leveraging custom 'TypeError's+module Data.Type.BitRecords.Assert+ ( type Assert, type Assertion, type Check+ , type (?::)+ , type (@&&)+ , type NatLE+ , type NatGE+ , type NatIn+ , type LengthIn+ , type CountElementsForLengthIn+ ) where++import GHC.TypeLits+import Data.Kind (type Type)+import Data.Type.Bool++-- | Assert that a given type satisfies some condition defined via 'Check'+-- instances. This is only an alias for 'Assert'+type (?::) x cond = Assert cond x+infixr 0 ?::++-- | Apply an 'Assertion' to some value and return that value if the assertion+-- holds, otherwise throw a 'TypeError'.+type family Assert (cond :: Assertion a) (x :: a) :: a where+ Assert cond x = ProcessCheckResult (Check cond x) x++-- | Make an assertion by creating a phantom data type which contains @TyFun a+-- (Maybe ErrorMessage)@ as last parameter. 'Check' is used by 'Assert' the+-- /apply/ the 'TyFun's which are specialized in this definition to have the+-- return type @Maybe ErrorMessage@.+type Assertion a = TyFun a (Maybe ErrorMessage) -> Type++-- | Apply 'Assertion's to the actual values.+type family Check (f :: Assertion a) (x :: a) :: Maybe ErrorMessage++type family ProcessCheckResult (r :: Maybe ErrorMessage) (x :: a) :: a where+ ProcessCheckResult 'Nothing x = x+ ProcessCheckResult ('Just blah) x =+ TypeError ('Text "Assertion on value " ':<>: 'ShowType x ':<>: 'Text " failed:" ':$$: blah)++-- | Shamelessly stolen from the singletons library.+data TyFun :: Type -> Type -> Type++-- | Assert that two assertions both hold+data (@&&) :: Assertion a -> Assertion a -> Assertion a++type instance Check (a1 @&& a2) x =+ (ProcessCheckResult (Check a2 x)+ (ProcessCheckResult (Check a1 x) x))++-- | Assert that a 'Nat' is greater than or equal to an other 'Nat'.+data NatGE :: Nat -> Assertion Nat++type instance Check (NatGE n) x =+ If (n <=? x) 'Nothing ('Just ( 'Text "Natural too small: " ':<>: 'ShowType x+ ':$$: 'Text "Required: >=" ':<>: 'ShowType n))++-- | Assert that a 'Nat' is less than or equal to an other 'Nat'.+data NatLE :: Nat -> Assertion Nat++type instance Check (NatLE n) x =+ If (x <=? n) 'Nothing ('Just ( 'Text "Natural too big: " ':<>: 'ShowType x+ ':$$: 'Text "Required: <=" ':<>: 'ShowType n))+++-- | Assert that a 'Nat' is in a specific range.+data NatIn :: Nat -> Nat -> Assertion Nat++type instance Check (NatIn from to) n =+ CheckNatInRange (n <=? to) (from <=? n) from to n++type family+ CheckNatInRange+ (lt :: Bool) (gt :: Bool) (from :: Nat) (to :: Nat) (n :: Nat) :: Maybe ErrorMessage where+ CheckNatInRange 'True 'True from to x = 'Nothing+ CheckNatInRange c1 c2 from to x =+ 'Just ('Text "Natural out of range: " ':<>: 'ShowType x+ ':<>: 'Text " not in "+ ':<>: 'ShowType from ':<>: 'Text " .. " ':<>: 'ShowType to)++-- | Assert that a list's length falls into a given range.+-- This is generalized from actual lists; everything with a 'CountElementsForLengthIn'+-- instance can be asserted.+data LengthIn :: Nat -> Nat -> Assertion a++type instance Check (LengthIn from to) xs = Check (NatIn from to) (CountElementsForLengthIn xs)++type family CountElementsForLengthIn (xs :: k) :: Nat+type instance CountElementsForLengthIn ('[]) = 0+type instance CountElementsForLengthIn (x ': xs) = 1 + CountElementsForLengthIn xs
+ src/Data/Type/BitRecords/Builder/BitBuffer.hs view
@@ -0,0 +1,161 @@+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}+module Data.Type.BitRecords.Builder.BitBuffer+ ( type BitStringMaxLength+ , type ModuloBitStringMaxLength+ , bitStringMaxLength+ , bitStringMaxLengthBytes+ , BitString()+ , bitStringContent+ , bitStringLength+ , isBitStringEmpty+ , bitStringSpaceLeft+ , bitString+ , emptyBitString+ , bitStringProxyLength+ , BitStringBuilderChunk()+ , bitStringBuilderChunkContent+ , bitStringBuilderChunkLength+ , isBitStringBuilderChunkEmpty+ , bitStringBuilderChunkSpaceLeft+ , bitStringBuilderChunk+ , emptyBitStringBuilderChunk+ , bufferBits+ , type KnownChunkSize+ ) where++import Data.Proxy+import Data.Bits+import Data.Word+import Data.Kind ( Constraint )+import GHC.TypeLits++-- | The maximum number of bits a 'BitBuffer' can hold.+type BitStringMaxLength = 64++-- | Calculate the modulus of a number and the 'BitStringMaxLength'.+type family ModuloBitStringMaxLength (len :: Nat) :: Nat where+ ModuloBitStringMaxLength len = len `Mod` BitStringMaxLength++-- | The maximum number of bits a 'BitBuffer' can hold.+bitStringMaxLength :: Num a => a+bitStringMaxLength = 64++-- | The maximum number of bytes a 'BitBuffer' can hold.+bitStringMaxLengthBytes :: Word64+bitStringMaxLengthBytes = 8++-- | A string of bits with a given length (but always @<= 'bitStringMaxLength'@.+-- The number of bits must be smaller that 'bitStringMaxLength'.+data BitString = BitString !Word64 !Int++bitStringContent :: BitString -> Word64+bitStringContent (BitString !c _) =+ c++bitStringLength :: BitString -> Int+bitStringLength (BitString _ !len) =+ len++isBitStringEmpty :: BitString -> Bool+isBitStringEmpty (BitString _ !len) =+ len == 0++bitStringSpaceLeft :: BitString -> Int+bitStringSpaceLeft (BitString _ !len) =+ bitStringMaxLength - len++-- | Create a 'BitString' containing @len@ bits from LSB to MSB, properly+-- masked, such that only @len@ least significant bits are kept..+bitString :: Int -> Word64 -> BitString+bitString !len !b = BitString (let !s = bitStringMaxLength - len in ((b `unsafeShiftL` s) `unsafeShiftR` s)) len++-- | Create an empty 'BitString'.+emptyBitString :: BitString+emptyBitString = BitString 0 0++-- | A buffer for 64 bits, such that the bits are written MSB to LSB.+--+-- > type TwoFields = "f0" @: Field m .+. "f1" @: Field n+--+-- Writes:+-- @ MSB LSB+-- Bit: |k .. k-(m+1)|k-m .. k-(m+n+1)|k-(m+n) .. 0|+-- Value: |------f0------|--------f1--------|XXXXXXXXXXXXXX|+-- @+--+-- Where @k@ is the current bit offset.+-- The input values are expected to be in the order of the fields, i.e.:+--+-- @+-- toFunction $ bitStringBuilderHoley (Proxy :: Proxy TwoFields) 1 2+-- @+--+-- Will result in:+-- @ MSB LSB+-- Bit: |k .. k-(m+1)|k-m .. k-(m+n+1)| k-(m+n) .. 0|+-- Value: |0 .. 1|0 .. 10| X .. X|+-- @+data BitStringBuilderChunk = BitStringBuilderChunk !Word64 !Int++bitStringBuilderChunkContent :: BitStringBuilderChunk -> Word64+bitStringBuilderChunkContent (BitStringBuilderChunk !c _) =+ c++bitStringBuilderChunkLength :: BitStringBuilderChunk -> Int+bitStringBuilderChunkLength (BitStringBuilderChunk _ !len) =+ len++isBitStringBuilderChunkEmpty :: BitStringBuilderChunk -> Bool+isBitStringBuilderChunkEmpty (BitStringBuilderChunk _ !len) =+ len == 0++bitStringBuilderChunkSpaceLeft :: BitStringBuilderChunk -> Int+bitStringBuilderChunkSpaceLeft (BitStringBuilderChunk _ !len) =+ bitStringMaxLength - len++-- | Create a 'BitStringBuilderChunk' containing @len@ bits from LSB to MSB, properly+-- masked, such that only @len@ least significant bits are kept..+bitStringBuilderChunk :: Word64 -> Int -> BitStringBuilderChunk+bitStringBuilderChunk !b !len = BitStringBuilderChunk b len++-- | Create an empty 'BitStringBuilderChunk'.+emptyBitStringBuilderChunk :: BitStringBuilderChunk+emptyBitStringBuilderChunk = BitStringBuilderChunk 0 0++-- | Create a 'BitStringBuilderChunk' with a length given by a 'Proxy' to a type level+-- 'Nat'.+bitStringProxyLength :: (KnownChunkSize n) => Proxy n -> Word64 -> BitString+bitStringProxyLength !plen !v = bitString fieldLen v+ where+ !fieldLen = fromIntegral (natVal plen)+++-- | Copy bits starting at a specific offset from one @a@ the the other.+-- Set bits starting from the most significant bit to the least.+-- For example @writeBits m 1 <> writeBits n 2@ would result in:+--+-- @+-- MSB LSB+-- Bit: |k .. k-(m+1)|k-m .. k-(m+n+1)| k-(m+n) .. 0|+-- Value: |0 .. 1|0 .. 10| ... |+-- -> -> -> (direction of writing)+-- @+--+bufferBits :: BitString -- ^ The value to write (in the lower @length@ bits).+ -> BitStringBuilderChunk -- ^ The input to write to+ -> (BitString, BitStringBuilderChunk) -- ^ The remaining bits that did not fit+ -- in the buffer and the output buffer.+bufferBits (BitString !bits !len) (BitStringBuilderChunk !buff !offset) =+ let !spaceAvailable = bitStringMaxLength - offset+ !writeLen = min spaceAvailable len+ !writeOffset = spaceAvailable - writeLen+ !restLen = len - writeLen+ !restBits = bits .&. (1 `unsafeShiftL` restLen - 1)+ !buff' = buff .|.+ (bits `unsafeShiftR` restLen `unsafeShiftL` writeOffset)+ in+ (BitString restBits restLen, BitStringBuilderChunk buff' (offset + writeLen))++type family KnownChunkSize (s :: Nat) :: Constraint where+ KnownChunkSize size = (KnownNat size, size <= BitStringMaxLength)
+ src/Data/Type/BitRecords/Builder/LazyByteStringBuilder.hs view
@@ -0,0 +1,315 @@+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TypeApplications #-}+{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}+module Data.Type.BitRecords.Builder.LazyByteStringBuilder where++import Data.Type.BitRecords.Builder.BitBuffer+import Data.FunctionBuilder+import Data.Type.BitRecords.Core+import Data.Word+import Data.Int+import Data.Bits+import Data.Kind.Extra+import Data.Proxy+import GHC.TypeLits+import Data.Monoid+import Control.Category+import Prelude hiding ((.), id)+import qualified Data.ByteString.Builder as SB+import qualified Data.ByteString.Lazy as B+import qualified Data.ByteString as SB+import Text.Printf++-- | A wrapper around a builder derived from a 'BitStringBuilderState'+data BuilderBox where+ MkBuilderBox :: !Word64 -> !SB.Builder -> BuilderBox++instance Semigroup BuilderBox where+ (MkBuilderBox !ls !lb) <> (MkBuilderBox !rs !rb) =+ MkBuilderBox (ls + rs) (lb <> rb)++instance Monoid BuilderBox where+ mempty = MkBuilderBox 0 mempty++-- | Create a 'SB.Builder' from a 'BitRecord' and store it in a 'BuilderBox'+bitBuilderBox ::+ forall (record :: BitRecord) .+ BitStringBuilderHoley (Proxy record) BuilderBox+ => Proxy record+ -> ToBitStringBuilder (Proxy record) BuilderBox+bitBuilderBox = toFunction . bitBuilderBoxHoley++-- | Like 'bitBuilderBox', but 'toFunction' the result and accept as an additional+-- parameter a wrapper function to wrap the final result (the 'BuilderBox') and+-- 'toFunction' the whole machiner.+wrapBitBuilderBox ::+ forall (record :: BitRecord) wrapped .+ BitStringBuilderHoley (Proxy record) wrapped+ => (BuilderBox -> wrapped)+ -> Proxy record+ -> ToBitStringBuilder (Proxy record) wrapped+wrapBitBuilderBox !f !p = toFunction (mapAccumulator f (bitBuilderBoxHoley p))++-- | Create a 'SB.Builder' from a 'BitRecord' and store it in a 'BuilderBox';+-- return a 'FunctionBuilder' monoid that does that on 'toFunction'+bitBuilderBoxHoley ::+ forall (record :: BitRecord) r .+ BitStringBuilderHoley (Proxy record) r+ => Proxy record+ -> FunctionBuilder BuilderBox r (ToBitStringBuilder (Proxy record) r)+bitBuilderBoxHoley !p =+ let fromBitStringBuilder !h =+ let (BitStringBuilderState !builder _ !wsize) =+ flushBitStringBuilder+ $ appBitStringBuilder h initialBitStringBuilderState+ !out = MkBuilderBox wsize builder+ in out+ in mapAccumulator fromBitStringBuilder (bitStringBuilderHoley p)++-- * Low-level interface to building 'BitRecord's and other things++newtype BitStringBuilder =+ BitStringBuilder {unBitStringBuilder :: Dual (Endo BitStringBuilderState)}+ deriving (Monoid, Semigroup)++runBitStringBuilder+ :: BitStringBuilder -> SB.Builder+runBitStringBuilder !w =+ getBitStringBuilderStateBuilder $+ flushBitStringBuilder $ appBitStringBuilder w initialBitStringBuilderState++bitStringBuilder :: (BitStringBuilderState -> BitStringBuilderState)+ -> BitStringBuilder+bitStringBuilder = BitStringBuilder . Dual . Endo++appBitStringBuilder :: BitStringBuilder+ -> BitStringBuilderState+ -> BitStringBuilderState+appBitStringBuilder !w = appEndo (getDual (unBitStringBuilder w))++data BitStringBuilderState where+ BitStringBuilderState ::+ !SB.Builder -> !BitStringBuilderChunk -> !Word64 -> BitStringBuilderState++getBitStringBuilderStateBuilder+ :: BitStringBuilderState -> SB.Builder+getBitStringBuilderStateBuilder (BitStringBuilderState !builder _ _) = builder++initialBitStringBuilderState+ :: BitStringBuilderState+initialBitStringBuilderState =+ BitStringBuilderState mempty emptyBitStringBuilderChunk 0++-- | Write the partial buffer contents using any number of 'word8' The unwritten+-- parts of the bittr buffer are at the top. If the+--+-- > 63 ... (63-off-1)(63-off) ... 0+-- > ^^^^^^^^^^^^^^^^^^^+-- > Relevant bits start to the top!+--+flushBitStringBuilder+ :: BitStringBuilderState -> BitStringBuilderState+flushBitStringBuilder (BitStringBuilderState !bldr !buff !totalSize) =+ BitStringBuilderState (writeRestBytes bldr 0)+ emptyBitStringBuilderChunk+ totalSize'+ where !off = bitStringBuilderChunkLength buff+ !off_ = (fromIntegral off :: Word64)+ !totalSize' = totalSize + signum (off_ `rem` 8) + (off_ `div` 8)+ !part = bitStringBuilderChunkContent buff+ -- write bytes from msb to lsb until the offset is reached+ -- > 63 ... (63-off-1)(63-off) ... 0+ -- > ^^^^^^^^^^^^^^^^^^^+ -- > AAAAAAAABBBBBBBBCCC00000+ -- > |byte A| byte B| byte C|+ writeRestBytes !bldr' !flushOffset =+ if off <= flushOffset+ then bldr'+ else let !flushOffset' = flushOffset + 8+ !bldr'' =+ bldr' <>+ SB.word8 (fromIntegral+ ((part `unsafeShiftR`+ (bitStringMaxLength - flushOffset')) .&.+ 0xFF))+ in writeRestBytes bldr'' flushOffset'++-- | Write all the bits, in chunks, filling and writing the 'BitString'+-- in the 'BitStringBuilderState' as often as necessary.+appendBitString :: BitString -> BitStringBuilder+appendBitString !x' =+ bitStringBuilder $+ \(BitStringBuilderState !builder !buff !totalSizeIn) -> go x' builder buff totalSizeIn+ where go !x !builder !buff !totalSize+ | bitStringLength x == 0 = BitStringBuilderState builder buff totalSize+ | otherwise =+ let (!rest, !buff') = bufferBits x buff+ in if bitStringBuilderChunkSpaceLeft buff' > 0+ then BitStringBuilderState builder buff' totalSize+ else let !nextBuilder =+ builder <>+ SB.word64BE (bitStringBuilderChunkContent buff')+ !totalSize' = totalSize + bitStringMaxLengthBytes+ in go rest nextBuilder emptyBitStringBuilderChunk totalSize'++-- | Write all the b*y*tes, into the 'BitStringBuilderState' this allows general+-- purposes non-byte aligned builders.+appendStrictByteString :: SB.ByteString -> BitStringBuilder+appendStrictByteString !sb =+ foldMap (appendBitString . bitString 8 . fromIntegral) (SB.unpack sb)++runBitStringBuilderHoley+ :: FunctionBuilder BitStringBuilder SB.Builder a -> a+runBitStringBuilderHoley (FB !x) = x runBitStringBuilder++-- * 'BitString' construction from 'BitRecord's++class BitStringBuilderHoley a r where+ type ToBitStringBuilder a r+ type ToBitStringBuilder a r = r+ bitStringBuilderHoley :: a -> FunctionBuilder BitStringBuilder r (ToBitStringBuilder a r)++instance BitStringBuilderHoley BitString r where+ bitStringBuilderHoley = immediate . appendBitString++-- ** 'BitRecordField' instances++type family UnsignedDemoteRep i where+ UnsignedDemoteRep Int8 = Word8+ UnsignedDemoteRep Int16 = Word16+ UnsignedDemoteRep Int32 = Word32+ UnsignedDemoteRep Int64 = Word64++-- *** Labbeled Fields++instance+ forall nested l a .+ ( BitStringBuilderHoley (Proxy nested) a )+ => BitStringBuilderHoley (Proxy (LabelF l nested)) a where+ type ToBitStringBuilder (Proxy (LabelF l nested)) a =+ ToBitStringBuilder (Proxy nested) a+ bitStringBuilderHoley _ = bitStringBuilderHoley (Proxy @nested)++-- **** Bool++instance forall f a . (BitRecordFieldSize f ~ 1) =>+ BitStringBuilderHoley (Proxy (f := 'True)) a where+ bitStringBuilderHoley _ = immediate (appendBitString (bitString 1 1))++instance forall f a . (BitRecordFieldSize f ~ 1) =>+ BitStringBuilderHoley (Proxy (f := 'False)) a where+ bitStringBuilderHoley _ = immediate (appendBitString (bitString 1 0))++instance forall a .+ BitStringBuilderHoley (Proxy (MkField 'MkFieldFlag)) a where+ type ToBitStringBuilder (Proxy (MkField 'MkFieldFlag)) a = Bool -> a+ bitStringBuilderHoley _ =+ addParameter (appendBitString . bitString 1 . (\ !t -> if t then 1 else 0))++-- **** Bits++instance forall (s :: Nat) a . (KnownChunkSize s) =>+ BitStringBuilderHoley (Proxy (MkField ('MkFieldBits :: BitField (B s) Nat s))) a where+ type ToBitStringBuilder (Proxy (MkField ('MkFieldBits :: BitField (B s) Nat s))) a = B s -> a+ bitStringBuilderHoley _ = addParameter (appendBitString . bitStringProxyLength (Proxy @s) . unB)++-- **** Naturals++instance forall a .+ BitStringBuilderHoley (Proxy (MkField 'MkFieldU64)) a where+ type ToBitStringBuilder (Proxy (MkField 'MkFieldU64)) a = Word64 -> a+ bitStringBuilderHoley _ = addParameter (appendBitString . bitString 64)++instance forall a .+ BitStringBuilderHoley (Proxy (MkField 'MkFieldU32)) a where+ type ToBitStringBuilder (Proxy (MkField 'MkFieldU32)) a = Word32 -> a+ bitStringBuilderHoley _ = addParameter (appendBitString . bitString 32 . fromIntegral)++instance forall a .+ BitStringBuilderHoley (Proxy (MkField 'MkFieldU16)) a where+ type ToBitStringBuilder (Proxy (MkField 'MkFieldU16)) a = Word16 -> a+ bitStringBuilderHoley _ = addParameter (appendBitString . bitString 16 . fromIntegral)++instance forall a .+ BitStringBuilderHoley (Proxy (MkField 'MkFieldU8)) a where+ type ToBitStringBuilder (Proxy (MkField 'MkFieldU8)) a = Word8 -> a+ bitStringBuilderHoley _ = addParameter (appendBitString . bitString 8 . fromIntegral)++-- **** Signed++instance forall a .+ BitStringBuilderHoley (Proxy (MkField 'MkFieldI64)) a where+ type ToBitStringBuilder (Proxy (MkField 'MkFieldI64)) a = Int64 -> a+ bitStringBuilderHoley _ = addParameter (appendBitString . bitString 64 . fromIntegral @Int64 @Word64)++instance forall a .+ BitStringBuilderHoley (Proxy (MkField 'MkFieldI32)) a where+ type ToBitStringBuilder (Proxy (MkField 'MkFieldI32)) a = Int32 -> a+ bitStringBuilderHoley _ = addParameter (appendBitString . bitString 32 . fromIntegral . fromIntegral @Int32 @Word32)++instance forall a .+ BitStringBuilderHoley (Proxy (MkField 'MkFieldI16)) a where+ type ToBitStringBuilder (Proxy (MkField 'MkFieldI16)) a = Int16 -> a+ bitStringBuilderHoley _ = addParameter (appendBitString . bitString 16 . fromIntegral . fromIntegral @Int16 @Word16)++instance forall a .+ BitStringBuilderHoley (Proxy (MkField 'MkFieldI8)) a where+ type ToBitStringBuilder (Proxy (MkField 'MkFieldI8)) a = Int8 -> a+ bitStringBuilderHoley _ = addParameter (appendBitString . bitString 8 . fromIntegral . fromIntegral @Int8 @Word8)+++instance forall (f :: IsA (BitRecordField (t :: BitField rt Nat len))) (v :: Nat) a . (KnownNat v, BitStringBuilderHoley (Proxy f) a, ToBitStringBuilder (Proxy f) a ~ (rt -> a), Num rt) =>+ BitStringBuilderHoley (Proxy (f := v)) a where+ bitStringBuilderHoley _ = fillParameter (bitStringBuilderHoley (Proxy @f)) (fromIntegral (natVal (Proxy @v)))++instance forall v f a x . (KnownNat v, BitStringBuilderHoley (Proxy f) a, ToBitStringBuilder (Proxy f) a ~ (x -> a), Num x) =>+ BitStringBuilderHoley (Proxy (f := ('PositiveNat v))) a where+ bitStringBuilderHoley _ = fillParameter (bitStringBuilderHoley (Proxy @f)) (fromIntegral (natVal (Proxy @v)))+++instance forall v f a x . (KnownNat v, BitStringBuilderHoley (Proxy f) a, ToBitStringBuilder (Proxy f) a ~ (x -> a), Num x) =>+ BitStringBuilderHoley (Proxy (f := ('NegativeNat v))) a where+ bitStringBuilderHoley _ = fillParameter (bitStringBuilderHoley (Proxy @f)) (fromIntegral (-1 * (natVal (Proxy @v))))++-- ** 'BitRecord' instances++instance forall (r :: IsA BitRecord) a . BitStringBuilderHoley (Proxy (Eval r)) a =>+ BitStringBuilderHoley (Proxy r) a where+ type ToBitStringBuilder (Proxy r) a =+ ToBitStringBuilder (Proxy (Eval r)) a+ bitStringBuilderHoley _ = bitStringBuilderHoley (Proxy @(Eval r))++-- *** 'BitRecordMember'++instance forall f a . BitStringBuilderHoley (Proxy f) a => BitStringBuilderHoley (Proxy ('BitRecordMember f)) a where+ type ToBitStringBuilder (Proxy ('BitRecordMember f)) a = ToBitStringBuilder (Proxy f) a+ bitStringBuilderHoley _ = bitStringBuilderHoley (Proxy @f)++-- *** 'AppendedBitRecords'++instance forall l r a .+ (BitStringBuilderHoley (Proxy l) (ToBitStringBuilder (Proxy r) a)+ , BitStringBuilderHoley (Proxy r) a)+ => BitStringBuilderHoley (Proxy ('BitRecordAppend l r)) a where+ type ToBitStringBuilder (Proxy ('BitRecordAppend l r)) a =+ ToBitStringBuilder (Proxy l) (ToBitStringBuilder (Proxy r) a)+ bitStringBuilderHoley _ = bitStringBuilderHoley (Proxy @l) . bitStringBuilderHoley (Proxy @r)++-- *** 'EmptyBitRecord' and '...Pretty'++instance BitStringBuilderHoley (Proxy 'EmptyBitRecord) a where+ bitStringBuilderHoley _ = id++-- ** Tracing/Debug Printing++-- | Print a 'SB.Builder' to a space seperated series of hexa-decimal bytes.+printBuilder :: SB.Builder -> String+printBuilder b =+ ("<< " ++) $+ (++ " >>") $ unwords $ printf "%0.2x" <$> B.unpack (SB.toLazyByteString b)++bitStringPrinter+ :: BitStringBuilderHoley a String+ => a -> ToBitStringBuilder a String+bitStringPrinter =+ toFunction . mapAccumulator (printBuilder . runBitStringBuilder) . bitStringBuilderHoley
+ src/Data/Type/BitRecords/Core.hs view
@@ -0,0 +1,345 @@+{-# LANGUAGE UndecidableInstances #-}+module Data.Type.BitRecords.Core where++import Data.Int+import Data.Kind (Type)+import Data.Kind.Extra+import Data.Proxy+import Data.Type.Pretty+import Data.Word+import Data.Bits+import GHC.TypeLits+import Text.Printf++-- * Bit-Records++-- ** Bit-Record Type++-- | 'BitRecordField's assembly+data BitRecord where+ EmptyBitRecord :: BitRecord+ BitRecordMember :: IsA (BitRecordField t) -> BitRecord+ BitRecordAppend :: BitRecord -> BitRecord -> BitRecord+ -- TODO MissingBitRecord :: ErrorMessage -> BitRecord++-- | A conditional 'BitRecord'+type family WhenR (b :: Bool) (x :: BitRecord) :: BitRecord where+ WhenR 'False r = 'EmptyBitRecord+ WhenR 'True r = r++-- *** Basic Accessor++-- | Get the number of bits in a 'BitRecord'+type family BitRecordSize (x :: BitRecord) :: Nat where+ BitRecordSize 'EmptyBitRecord = 0+ BitRecordSize ('BitRecordMember f) = BitRecordFieldSize f+ BitRecordSize ('BitRecordAppend l r) = BitRecordSize l + BitRecordSize r++-- | Get the total number of members in a record.+type family BitRecordMemberCount (b :: BitRecord) :: Nat where+ BitRecordMemberCount 'EmptyBitRecord = 0+ BitRecordMemberCount ('BitRecordMember f) = 1+ BitRecordMemberCount ('BitRecordAppend l r) = BitRecordMemberCount l + BitRecordMemberCount r++-- | Get the size of the record.+getRecordSizeFromProxy+ :: forall px (rec :: BitRecord) . KnownNat (BitRecordSize rec) => px rec -> Integer+getRecordSizeFromProxy _ = natVal (Proxy :: Proxy (BitRecordSize rec))++-- | Either use the value from @Just@ or return a 'EmptyBitRecord' value(types(kinds))+type OptionalRecordOf (f :: IsA (s :-> IsA BitRecord)) (x :: Maybe s) =+ (Optional (Pure 'EmptyBitRecord) f $~ x :: IsA BitRecord)++-- ** Record composition++-- | Combine two 'BitRecord's to form a new 'BitRecord'. If the parameters are+-- not of type 'BitRecord' they will be converted.+data (:+^) (l :: BitRecord) (r :: IsA BitRecord) :: IsA BitRecord+infixl 3 :+^+type instance Eval (l :+^ r) = l `Append` Eval r++-- | Combine two 'BitRecord's to form a new 'BitRecord'. If the parameters are+-- not of type 'BitRecord' they will be converted.+data (:^+) (l :: IsA BitRecord) (r :: BitRecord) :: IsA BitRecord+infixl 3 :^++type instance Eval (l :^+ r) = Eval l `Append` r++-- | Combine two 'BitRecord's to form a new 'BitRecord'. If the parameters are+-- not of type 'BitRecord' they will be converted.+type (:+:) (l :: BitRecord) (r :: BitRecord) = ((l `Append` r) :: BitRecord)+infixl 3 :+:++type family Append (l :: BitRecord) (r :: BitRecord) :: BitRecord where+ Append l 'EmptyBitRecord = l+ Append 'EmptyBitRecord r = r+ Append l r = 'BitRecordAppend l r++-- | Append a 'BitRecord' and a 'BitRecordField'+type (:+.) (r :: BitRecord)+ (f :: IsA (BitRecordField t1))+ = Append r ('BitRecordMember f)+infixl 6 :+.++-- | Append a 'BitRecordField' and a 'BitRecord'+type (.+:) (f :: IsA (BitRecordField t1))+ (r :: BitRecord)+ = Append ('BitRecordMember f) r+infixr 6 .+:++-- | Append a 'BitRecordField' and a 'BitRecordField' forming a 'BitRecord' with+-- two members.+type (.+.) (l :: IsA (BitRecordField t1))+ (r :: IsA (BitRecordField t2))+ = Append ('BitRecordMember l) ('BitRecordMember r)+infixr 6 .+.++-- | Set a field to either a static, compile time, value or a dynamic, runtime value.+type family (:~)+ (field :: IsA (BitRecordField (t :: BitField (rt :: Type) (st :: k) (len :: Nat))))+ (value :: IsA (FieldValue (label :: Symbol) st))+ :: IsA (BitRecordField t) where+ fld :~ StaticFieldValue l v = (l @: fld) := v+ fld :~ RuntimeFieldValue l = l @: fld+infixl 7 :~++-- | Like ':~' but for a 'Maybe' parameter. In case of 'Just' it behaves like ':~'+-- in case of 'Nothing' it return an 'EmptyBitRecord'.+type family (:~?)+ (fld :: IsA (BitRecordField (t :: BitField (rt :: Type) (st :: k) (len :: Nat))))+ (value :: Maybe (IsA (FieldValue (label :: Symbol) st)))+ :: IsA BitRecord where+ fld :~? ('Just v) = RecordField (fld :~ v)+ fld :~? 'Nothing = Pure 'EmptyBitRecord+infixl 7 :~?++-- | Like ':~' but for a 'Maybe' parameter. In case of 'Just' it behaves like ':~'+-- in case of 'Nothing' it return an 'EmptyBitRecord'.+type family (:+?)+ (fld :: IsA (BitRecordField (t :: BitField (rt :: Type) (st :: k) (len :: Nat))))+ (value :: Maybe (IsA (FieldValue (label :: Symbol) st)))+ :: BitRecord where+ fld :+? ('Just v) = 'BitRecordMember (fld :~ v)+ fld :+? 'Nothing = 'EmptyBitRecord+infixl 7 :+?++-- | The field value parameter for ':~', either a static, compile time, value or+-- a dynamic, runtime value.+data FieldValue :: Symbol -> staticRep -> Type+data StaticFieldValue (label :: Symbol) :: staticRep -> IsA (FieldValue label staticRep)+data RuntimeFieldValue (label :: Symbol) :: IsA (FieldValue label staticRep)++-- *** Record Arrays and Repitition++-- | An array of records with a fixed number of elements, NOTE: this type is+-- actually not really necessary since 'ReplicateRecord' exists, but this allows+-- to have a different 'showRecord' output.+data RecArray :: BitRecord -> Nat -> IsA BitRecord++type r ^^ n = RecArray r n+infixl 5 ^^++type instance Eval (RecArray (r :: BitRecord) n ) = RecArrayToBitRecord r n++-- | Repeat a bit record @n@ times.+type family RecArrayToBitRecord (r :: BitRecord) (n :: Nat) :: BitRecord where+ RecArrayToBitRecord r 0 = 'EmptyBitRecord+ RecArrayToBitRecord r 1 = r+ RecArrayToBitRecord r n = Append r (RecArrayToBitRecord r (n - 1))++-- *** Lists of Records++-- | Let type level lists also be records+data+ BitRecordOfList+ (f :: IsA (foo :-> BitRecord))+ (xs :: [foo])+ :: IsA BitRecord++type instance Eval (BitRecordOfList f xs) =+ FoldMap BitRecordAppendFun 'EmptyBitRecord f xs++type BitRecordAppendFun = Fun1 BitRecordAppendFun_+data BitRecordAppendFun_ :: BitRecord -> IsA (BitRecord :-> BitRecord)+type instance (BitRecordAppendFun_ l) $~ r = Append l r++-- *** Maybe Record++-- | Either use the value from @Just@ or return a 'EmptyBitRecord' value(types(kinds))+data OptionalRecord :: Maybe BitRecord -> IsA BitRecord+type instance Eval (OptionalRecord ('Just t)) = t+type instance Eval (OptionalRecord 'Nothing) = 'EmptyBitRecord++-- ** Field ADT++-- | A family of bit fields.+--+-- A bit field always has a size, i.e. the number of bits it uses, as well as a+-- term level value type and a type level value type. It also has an optional+-- label, and an optional value assigned to it.+data BitRecordField :: BitField rt st len -> Type++-- | A bit record field with a number of bits+data MkField t :: IsA (BitRecordField t)++-- **** Setting a Label++-- | A bit record field with a number of bits+data LabelF :: Symbol -> IsA (BitRecordField t) -> IsA (BitRecordField t)++-- | A field with a label assigned to it.+type+ (l :: Symbol) @:(f :: IsA (BitRecordField (t :: BitField rt (st :: stk) size)))+ = (LabelF l f :: IsA (BitRecordField t))+infixr 8 @:++-- **** Assignment++-- | A field with a value set at compile time.+data (:=) :: forall st (t :: BitField rt st size) .+ IsA (BitRecordField t)+ -> st+ -> IsA (BitRecordField t)+infixl 7 :=++-- | Types of this kind define the basic type of a 'BitRecordField'. Sure, this+-- could have been an open type, but really, how many actual useful field types+-- exist? Well, from a global perspective, uncountable infinite, but the focus+-- of this library is to blast out bits over the network, using usual Haskell+-- libraries, and hence, there is actually only very little reason to+-- differentiate types of record fields, other than what low-level library+-- function to apply and how to pretty print the field.+data BitField+ (runtimeRep :: Type)+ (staticRep :: k)+ (bitCount :: Nat)+ where+ MkFieldFlag :: BitField Bool Bool 1+ MkFieldBits :: forall (n :: Nat) . BitField (B n) Nat n+ MkFieldBitsXXL :: forall (n :: Nat) . BitField Integer Nat n+ MkFieldU8 :: BitField Word8 Nat 8+ MkFieldU16 :: BitField Word16 Nat 16+ MkFieldU32 :: BitField Word32 Nat 32+ MkFieldU64 :: BitField Word64 Nat 64+ MkFieldI8 :: BitField Int8 SignedNat 8+ MkFieldI16 :: BitField Int16 SignedNat 16+ MkFieldI32 :: BitField Int32 SignedNat 32+ MkFieldI64 :: BitField Int64 SignedNat 64+ -- TODO refactor this MkFieldCustom, it caused a lot of trouble!+ MkFieldCustom :: BitField rt st n++type Flag = MkField 'MkFieldFlag+type Field n = MkField ('MkFieldBits :: BitField (B n) Nat n)+type FieldU8 = MkField 'MkFieldU8+type FieldU16 = MkField 'MkFieldU16+type FieldU32 = MkField 'MkFieldU32+type FieldU64 = MkField 'MkFieldU64+type FieldI8 = MkField 'MkFieldI8+type FieldI16 = MkField 'MkFieldI16+type FieldI32 = MkField 'MkFieldI32+type FieldI64 = MkField 'MkFieldI64++-- | A data type for 'Field' and 'MkFieldBits', that is internally a 'Word64'.+-- It carries the number of relevant bits in its type.+newtype B (size :: Nat) = B {unB :: Word64}+ deriving (Read,Show,Num,Integral,Bits,FiniteBits,Eq,Ord,Bounded,Enum,Real)++instance (PrintfArg Word64, n <= 64) => PrintfArg (B n) where+ formatArg (B x) = formatArg x+ parseFormat (B x) = parseFormat x++-- | A signed field value.+data SignedNat where+ PositiveNat :: Nat -> SignedNat+ NegativeNat :: Nat -> SignedNat++-- *** Composed Fields++-- | A Flag (1-bit) that is true if the type level maybe is 'Just'.+type family FlagJust (a :: Maybe (v :: Type)) :: IsA (BitRecordField 'MkFieldFlag) where+ FlagJust ('Just x) = Flag := 'True+ FlagJust 'Nothing = Flag := 'False++-- | A Flag (1-bit) that is true if the type level maybe is 'Nothing'.+type family FlagNothing (a :: Maybe (v :: Type)) :: IsA (BitRecordField 'MkFieldFlag) where+ FlagNothing ('Just x) = Flag := 'False+ FlagNothing 'Nothing = Flag := 'True++-- | An optional field in a bit record+data MaybeField :: Maybe (IsA (BitRecordField t)) -> IsA BitRecord+type instance Eval (MaybeField ('Just fld)) = 'BitRecordMember fld+type instance Eval (MaybeField 'Nothing) = 'EmptyBitRecord++-- | A 'BitRecordField' can be used as 'BitRecordMember'+data RecordField :: IsA (BitRecordField t) -> IsA BitRecord+type instance Eval (RecordField f) = 'BitRecordMember f++-- | Calculate the size as a number of bits from a 'BitRecordField'+type family BitRecordFieldSize (x :: IsA (BitRecordField t)) where+ BitRecordFieldSize (x :: IsA (BitRecordField (t :: BitField rt st size))) = size+++-- * Field and Record PrettyType Instances++-- | Render @rec@ to a pretty, human readable form. Internally this is a wrapper+-- around 'ptShow' using 'PrettyRecord'.+showARecord+ :: forall proxy (rec :: IsA BitRecord) . PrettyTypeShow (PrettyRecord (Eval rec))+ => proxy rec -> String+showARecord _ = showPretty (Proxy :: Proxy (PrettyRecord (Eval rec)))++-- | Render @rec@ to a pretty, human readable form. Internally this is a wrapper+-- around 'ptShow' using 'PrettyRecord'.+showRecord+ :: forall proxy (rec :: BitRecord) . PrettyTypeShow (PrettyRecord rec)+ => proxy rec -> String+showRecord _ = showPretty (Proxy :: Proxy (PrettyRecord rec))++type instance ToPretty (rec :: BitRecord) = PrettyRecord rec++type family PrettyRecord (rec :: BitRecord) :: PrettyType where+ PrettyRecord ('BitRecordMember m) = PrettyField m+ PrettyRecord ' EmptyBitRecord = 'PrettyNewline+ PrettyRecord ('BitRecordAppend l r) = PrettyRecord l <$$> PrettyRecord r++type instance ToPretty (f :: IsA (BitRecordField t)) = PrettyField f++type family PrettyField (f :: IsA (BitRecordField (t :: BitField (rt :: Type) (st :: Type) (size :: Nat)))) :: PrettyType where+ PrettyField (MkField t) = PrettyFieldType t+ PrettyField ((f :: IsA (BitRecordField t)) := v) =+ PrettyField f <+> PutStr ":=" <+> PrettyFieldValue t v+ PrettyField (LabelF l f) = l <:> PrettyField f++type family PrettyFieldType (t :: BitField (rt :: Type) (st :: Type) (size :: Nat)) :: PrettyType where+ PrettyFieldType ('MkFieldFlag) = PutStr "boolean"+ PrettyFieldType ('MkFieldBits :: BitField (B (s :: Nat)) Nat s) = PutStr "bits" <++> PrettyParens (PutNat s)+ PrettyFieldType ('MkFieldBitsXXL :: BitField Integer Nat (s :: Nat)) = PutStr "bits-XXL" <++> PrettyParens (PutNat s)+ PrettyFieldType ('MkFieldU64) = PutStr "U64"+ PrettyFieldType ('MkFieldU32) = PutStr "U32"+ PrettyFieldType ('MkFieldU16) = PutStr "U16"+ PrettyFieldType ('MkFieldU8) = PutStr "U8"+ PrettyFieldType ('MkFieldI64) = PutStr "I64"+ PrettyFieldType ('MkFieldI32) = PutStr "I32"+ PrettyFieldType ('MkFieldI16) = PutStr "I16"+ PrettyFieldType ('MkFieldI8) = PutStr "I8"+ PrettyFieldType ('MkFieldCustom :: BitField rt ct size) = ToPretty rt <++> PrettyParens (PutNat size)++type family PrettyFieldValue (t :: BitField (rt :: Type) (st :: Type) (size :: Nat)) (v :: st) :: PrettyType where+ PrettyFieldValue ('MkFieldFlag) 'True = PutStr "yes"+ PrettyFieldValue ('MkFieldFlag) 'False = PutStr "no"+ PrettyFieldValue ('MkFieldBits :: BitField (B (s :: Nat)) Nat s) v =+ 'PrettyNat 'PrettyUnpadded ('PrettyPrecision s) 'PrettyBit v <+> PrettyParens (("hex" <:> PutHex v) <+> ("dec" <:> PutNat v))+ PrettyFieldValue ('MkFieldU8) v = ("hex" <:> PutHex8 v) <+> PrettyParens ("dec" <:> PutNat v)+ PrettyFieldValue ('MkFieldU16) v = ("hex" <:> PutHex16 v) <+> PrettyParens ("dec" <:> PutNat v)+ PrettyFieldValue ('MkFieldU32) v = ("hex" <:> PutHex32 v) <+> PrettyParens ("dec" <:> PutNat v)+ PrettyFieldValue ('MkFieldU64) v = ("hex" <:> PutHex64 v) <+> PrettyParens ("dec" <:> PutNat v)+ PrettyFieldValue ('MkFieldI8) ('PositiveNat v) = ("hex" <:> (PutStr "+" <++> PutHex8 v)) <+> PrettyParens ("dec" <:> (PutStr "+" <++> PutNat v))+ PrettyFieldValue ('MkFieldI16) ('PositiveNat v) = ("hex" <:> (PutStr "+" <++> PutHex16 v)) <+> PrettyParens ("dec" <:> (PutStr "+" <++> PutNat v))+ PrettyFieldValue ('MkFieldI32) ('PositiveNat v) = ("hex" <:> (PutStr "+" <++> PutHex32 v)) <+> PrettyParens ("dec" <:> (PutStr "+" <++> PutNat v))+ PrettyFieldValue ('MkFieldI64) ('PositiveNat v) = ("hex" <:> (PutStr "+" <++> PutHex64 v)) <+> PrettyParens ("dec" <:> (PutStr "+" <++> PutNat v))+ PrettyFieldValue ('MkFieldI8) ('NegativeNat v) = ("hex" <:> (PutStr "-" <++> PutHex8 v)) <+> PrettyParens ("dec" <:> (PutStr "-" <++> PutNat v))+ PrettyFieldValue ('MkFieldI16) ('NegativeNat v) = ("hex" <:> (PutStr "-" <++> PutHex16 v)) <+> PrettyParens ("dec" <:> (PutStr "-" <++> PutNat v))+ PrettyFieldValue ('MkFieldI32) ('NegativeNat v) = ("hex" <:> (PutStr "-" <++> PutHex32 v)) <+> PrettyParens ("dec" <:> (PutStr "-" <++> PutNat v))+ PrettyFieldValue ('MkFieldI64) ('NegativeNat v) = ("hex" <:> (PutStr "-" <++> PutHex64 v)) <+> PrettyParens ("dec" <:> (PutStr "-" <++> PutNat v))+ PrettyFieldValue ('MkFieldCustom :: BitField rt ct size) v = PrettyCustomFieldValue rt ct size v++type family PrettyCustomFieldValue (rt :: Type) (st :: Type) (size :: Nat) (v :: st) :: PrettyType
+ src/Data/Type/BitRecords/Enum.hs view
@@ -0,0 +1,148 @@+{-# LANGUAGE UndecidableInstances #-}+module Data.Type.BitRecords.Enum where++import Data.Type.BitRecords.Core+import Data.Type.BitRecords.Builder.BitBuffer+import Data.FunctionBuilder+import Data.Type.BitRecords.Builder.LazyByteStringBuilder+import Data.Proxy+import Data.Word+import Data.Kind ( Type )+import GHC.TypeLits+import Data.Kind.Extra+import Data.Type.Pretty++-- * BitRecordFields containing /enum/-like types++-- | Wrapper around a type that can be represented as a short number, indexing+-- the clauses of the (sum) type.+data EnumOf enum where+ MkEnumOf+ ::IsAn (EnumField enum size)+ -> IsA (FieldValue label enum)+ -> BitRecord+ -> EnumOf enum++type BitRecordOfEnum (e :: IsAn (EnumOf enum))+ = (RenderEnumOf (Eval e) :: BitRecord)++type family RenderEnumOf (e :: EnumOf enum) :: BitRecord where+ RenderEnumOf ('MkEnumOf mainField mainFieldVal extra) =+ (BitRecordFieldOfEnumField mainField) :~ mainFieldVal .+: extra++-- | Physical representation of an 'EnumOf', this is an abstract type+data EnumField (enum :: Type) (size :: Nat)++type BitRecordFieldOfEnumField (x :: IsA (EnumField e s))+ = MkField ( 'MkFieldCustom :: BitField (EnumValue e) e s)++-- | A fixed size 'EnumField'+data FixedEnum (enum :: Type) (size :: Nat) :: IsAn (EnumField enum size)++-- | An enum that can be extended with an additional 'BitRecordField', following+-- the regular enum field; the extension is optional, i.e. only if the+-- /regular/ field contains a special value (e.g. 0xff).+data ExtEnum (enum :: Type)+ (size :: Nat)+ (extInd :: enum)+ (extField :: IsA (BitRecordField (t :: BitField rt0 (st0 :: k0) len0)))+ :: IsAn (EnumField enum size)++-- | Create an 'EnumOf' that sets an enum to a static value.+data SetEnum (l :: Symbol) (ef :: IsAn (EnumField enum size)) (v :: enum) :: IsAn (EnumOf enum)++type instance Eval (SetEnum (l :: Symbol) (ei :: IsAn (EnumField enum size)) value) =+ 'MkEnumOf+ ei+ (StaticFieldValue l value)+ 'EmptyBitRecord++-- | Create an 'EnumOf' that sets the enum to a runtime value.+data EnumParam+ (label :: Symbol)+ (ef :: IsAn (EnumField (enum :: Type) (size :: Nat)))+ :: IsAn (EnumOf enum)+type instance Eval (EnumParam label (ei :: IsAn (EnumField enum size))) =+ 'MkEnumOf+ ei+ (RuntimeFieldValue label)+ 'EmptyBitRecord++-- | Create an 'EnumOf' that sets an extended enum to an extended static value.+data SetEnumAlt (l :: Symbol) (ef :: IsAn (EnumField (enum :: Type) (size :: Nat))) (v :: k)+ :: IsAn (EnumOf enum)++type instance Eval (SetEnumAlt (l :: Symbol) (ExtEnum enum size extInd extField) value) =+ -- TODO maybe enrich the demoteRep type of 'MkField??+ 'MkEnumOf+ (ExtEnum enum size extInd extField)+ (StaticFieldValue l extInd)+ ('BitRecordMember (extField := value))++type instance Eval (SetEnumAlt (l :: Symbol) (FixedEnum enum size) value) =+ TypeError ('Text "Cannot assign an 'extended' value to the 'FixedEnum' "+ ':<>: 'ShowType enum)++-- | Create an 'EnumOf' that sets the extended enum to a runtime value.+data EnumParamAlt+ (label :: Symbol)+ (ef :: IsAn (EnumField (enum :: Type) (size :: Nat)))+ :: IsAn (EnumOf enum)++type instance Eval (EnumParamAlt label (ExtEnum enum size extInd extField)) =+ 'MkEnumOf+ (ExtEnum enum size extInd extField)+ (StaticFieldValue label extInd)+ ('BitRecordMember (extField :~ RuntimeFieldValue label))++type instance Eval (EnumParamAlt label (FixedEnum enum size)) =+ TypeError ('Text "Cannot assign an extension value to the FixedEnum "+ ':<>: 'ShowType enum)++-- ** Composing BitRecords with enum fields++-- | Return the numeric /index/ of an entry in a table. This emulates 'fromEnum' a bit.+type family FromEnum enum (entry :: enum) :: Nat++-- | An enum value supplied at runtime.+data EnumValue e where+ MkEnumValue ::KnownNat (FromEnum e v) => Proxy (v :: e) -> EnumValue e++-- | Create an 'EnumValue' from a 'Proxy'. TODO remove?+enumValueProxy :: KnownNat (FromEnum e v) => Proxy (v :: e) -> EnumValue e+enumValueProxy = MkEnumValue++fromEnumValue :: EnumValue e -> Word64+fromEnumValue (MkEnumValue p) = enumValue p+ where+ enumValue+ :: forall proxy (v :: enum)+ . KnownNat (FromEnum enum v)+ => proxy v+ -> Word64+ enumValue _ = fromIntegral (natVal (Proxy @(FromEnum enum v)))++instance+ forall (size :: Nat) r e (v :: e) (f :: IsA (BitRecordField ('MkFieldCustom :: BitField (EnumValue e) e size))) .+ (KnownNat (FromEnum e v), KnownChunkSize size) =>+ BitStringBuilderHoley (Proxy (f := v)) r where+ bitStringBuilderHoley _ = immediate+ (appendBitString+ (bitStringProxyLength (Proxy @size)+ (fromIntegral (natVal (Proxy @(FromEnum e v))))+ )+ )++instance+ forall (size :: Nat) r e .+ (KnownChunkSize size) =>+ BitStringBuilderHoley (Proxy (MkField ('MkFieldCustom :: BitField (EnumValue e) e size))) r+ where+ type ToBitStringBuilder (Proxy (MkField ('MkFieldCustom :: BitField (EnumValue e) e size))) r =+ EnumValue e -> r+ bitStringBuilderHoley _ = addParameter+ (appendBitString . bitStringProxyLength (Proxy @size) . fromEnumValue)++type instance ToPretty (EnumValue e) = PutStr "<<enum>>"+type instance PrettyCustomFieldValue (EnumValue e) e size (v :: e) =+ PutNat (FromEnum e v) <+> ("hex" <:> PutHex (FromEnum e v)) <+> ("bin" <:> PutBits (FromEnum e v))
+ src/Data/Type/BitRecords/Sized.hs view
@@ -0,0 +1,73 @@+-- | Size Fields+{-# LANGUAGE UndecidableInstances #-}+module Data.Type.BitRecords.Sized+ ( type Sized, type Sized8, type Sized16, type Sized32, type Sized64+ , type SizedField, type SizedField8, type SizedField16, type SizedField32, type SizedField64+ , type SizeFieldValue)+ where++import Data.Type.Pretty+import Data.Type.BitRecords.Core+import Data.Word+import GHC.TypeLits+import Data.Kind.Extra+import Data.Kind (type Type)++-- | A record with a /size/ member, and a nested record that can be counted+-- using 'SizeFieldValue'.+data Sized+ (sf :: IsA (BitRecordField (t :: BitField (rt :: Type) Nat (size :: Nat))))+ (r :: BitRecord)+ :: IsA BitRecord+type instance Eval (Sized sf r) =+ "size" @: sf := SizeFieldValue r .+: r++-- | A convenient alias for a 'Sized' with an 'FieldU8' size field.+type Sized8 t = Sized FieldU8 t++-- | A convenient alias for a 'Sized' with an 'FieldU16' size field.+type Sized16 t = Sized FieldU16 t++-- | A convenient alias for a 'Sized' with an 'FieldU32' size field.+type Sized32 t = Sized FieldU32 t++-- | A convenient alias for a 'Sized' with an 'FieldU64' size field.+type Sized64 t = Sized FieldU64 t++-- | A record with a /size/ member, and a nested field that can be counted+-- using 'SizeFieldValue'.+data SizedField+ (sf :: IsA (BitRecordField (t :: BitField (rt :: Type) Nat (size :: Nat))))+ (r :: IsA (BitRecordField (u :: BitField (rt' :: Type) (st' :: k0) (len0 :: Nat))))+ :: IsA BitRecord+type instance Eval (SizedField sf r) =+ "size" @: sf := SizeFieldValue r .+. r++-- | A convenient alias for a 'SizedField' with an 'FieldU8' size field.+type SizedField8 t = SizedField FieldU8 t++-- | A convenient alias for a 'SizedField' with an 'FieldU16' size field.+type SizedField16 t = SizedField FieldU16 t++-- | A convenient alias for a 'SizedField' with an 'FieldU32' size field.+type SizedField32 t = SizedField FieldU32 t++-- | A convenient alias for a 'SizedField' with an 'FieldU64' size field.+type SizedField64 t = SizedField FieldU64 t++-- | For something to be augmented by a size field there must be an instance of+-- this family to generate the value of the size field, e.g. by counting the+-- elements.+type family SizeFieldValue (c :: k) :: Nat++type instance SizeFieldValue (b :: BitRecord) = BitRecordMemberCount b+type instance SizeFieldValue (f := v) = SizeFieldValue v+type instance SizeFieldValue (LabelF l f) = SizeFieldValue f+type instance SizeFieldValue (MkField (t :: BitField (rt:: Type) (st::k) (size::Nat))) = size++type family PrintHexIfPossible t (s :: Nat) :: PrettyType where+ PrintHexIfPossible Word64 s = PutHex64 s+ PrintHexIfPossible Word32 s = PutHex32 s+ PrintHexIfPossible Word16 s = PutHex16 s+ PrintHexIfPossible Word8 s = PutHex8 s+ PrintHexIfPossible x s = TypeError ('Text "Invalid size field type: " ':<>: 'ShowType x)
+ src/Data/Type/BitRecords/SizedString.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE UndecidableInstances #-}++module Data.Type.BitRecords.SizedString+ (SizedString()+ ,ASizedString()+ ,utf8)+ where++import Data.Type.BitRecords.Core+import Data.Type.BitRecords.Sized+import Data.FunctionBuilder+import Data.Type.BitRecords.Builder.LazyByteStringBuilder+import qualified Language.Haskell.TH as TH+import qualified Language.Haskell.TH.Quote as TH+import GHC.TypeLits+import Data.Type.Pretty+import qualified Data.ByteString as B+import qualified Data.Text as T+import qualified Data.Text.Encoding as E+import Data.Proxy+import Data.Kind (type Type)+import Data.Kind.Extra++-- TODO Refactor++-- * String Fields++-- | A type level symbol paired with a type level length, that determines how+-- many characters of the symbol may be used. The first parameter defines the+-- length field.+type SizedString str bytes =+ MkField ('MkFieldCustom :: BitField ASizedString ASizedString (8 * bytes)) := 'MkASizedString str bytes++data ASizedString where+ MkASizedString :: Symbol -> Nat -> ASizedString++type instance+ SizeFieldValue ('MkASizedString str byteCount) = byteCount++type instance+ ToPretty ASizedString = PutStr "utf-8"++type instance PrettyCustomFieldValue ASizedString ASizedString s sr =+ ToPretty sr++type instance+ ToPretty ('MkASizedString str byteCount) =+ PrettySurrounded (PutStr "<<") (PutStr ">>") (PutStr str)+ <+> PutStr "[" <++> PutNat byteCount <++> PutStr " Bytes]"++-- | Create a 'SizedString' from a utf-8 string+utf8 :: TH.QuasiQuoter+utf8 = TH.QuasiQuoter undefined undefined mkSizedStr undefined+ where mkSizedStr :: String -> TH.Q TH.Type+ mkSizedStr str =+ do let strT = TH.LitT (TH.StrTyLit str)+ byteCount =+ fromIntegral (B.length (E.encodeUtf8 (T.pack str)))+ byteCountT = TH.LitT (TH.NumTyLit byteCount)+ return $+ TH.PromotedT ''SizedString `TH.AppT` strT `TH.AppT` byteCountT++instance+ forall (size :: Nat)+ (str :: Symbol)+ (bytes :: Nat)+ (r :: Type)+ (f :: IsA (BitRecordField ('MkFieldCustom :: BitField ASizedString ASizedString size))) .+ (KnownSymbol str)+ => BitStringBuilderHoley (Proxy (f := 'MkASizedString str bytes)) r+ where+ bitStringBuilderHoley _ =+ immediate (appendStrictByteString+ (E.encodeUtf8 (T.pack (symbolVal (Proxy @str)))))
+ stack.yaml view
@@ -0,0 +1,13 @@+resolver: lts-13.3++packages:+- '.'++extra-deps:+- pretty-types-0.3.0.1+- type-spec-0.4.0.0+- function-builder-0.1.0.1++flags: {}++extra-package-dbs: []