├── ShazamAPI ├── __init__.py ├── api.py ├── signature_format.py └── algorithm.py ├── .editorconfig ├── .gitignore ├── README.md ├── LICENSE ├── pyproject.toml ├── .flakeheaven.toml ├── .flakeheaven-baseline └── poetry.lock /ShazamAPI/__init__.py: -------------------------------------------------------------------------------- 1 | from .api import Shazam -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.py] 10 | indent_style = space 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | # Packages 4 | *.egg 5 | !/tests/**/*.egg 6 | /*.egg-info 7 | /dist/* 8 | build 9 | _build 10 | .cache 11 | *.so 12 | 13 | # Installer logs 14 | pip-log.txt 15 | 16 | # Unit test / coverage reports 17 | .coverage 18 | .tox 19 | .pytest_cache 20 | 21 | .DS_Store 22 | .idea/* 23 | .python-version 24 | .vscode/* 25 | 26 | /test.py 27 | /test_*.* 28 | 29 | MANIFEST.in 30 | /setup.py 31 | 32 | .venv 33 | /releases/* 34 | pip-wheel-metadata 35 | 36 | .env 37 | .local.db 38 | 39 | logs/* 40 | !logs/.gitkeep 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shazam Api 2 | 3 | ### Install 4 | ``` 5 | pip3 install ShazamAPI 6 | ``` 7 | Also you need to install ffmpeg and ffprobe then add it to path 8 | 9 | ### Usage 10 | 11 | ```python 12 | from ShazamAPI import Shazam 13 | 14 | with open('filename.mp3', 'rb') as fp: 15 | mp3_file_content_to_recognize = fp.read() 16 | 17 | recognize_generator = Shazam().recognize_song(mp3_file_content_to_recognize) 18 | # or just: 19 | # recognize_generator = Shazam().recognize_song('filename.mp3') 20 | for (offset, resp) in recognize_generator: 21 | print(offset, resp) 22 | ``` 23 | 24 | ### Credits to: 25 | 26 | https://github.com/marin-m/SongRec 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Numenorean 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "ShazamAPI" 3 | version = "0.0.2" 4 | description = "Fully reverse engeenired Shazam API" 5 | authors = [ 6 | "Numenorean <41187266@users.noreply.github.com>", 7 | "Alexander Pushkov ", 8 | ] 9 | license = "MIT" 10 | 11 | [tool.poetry.dependencies] 12 | python = "^3.7" 13 | requests = "^2.27.1" 14 | pydub = "^0.25.1" 15 | numpy = "^1.21.1" 16 | 17 | [tool.poetry.dev-dependencies] 18 | pydub-stubs = { version = "^0.25.1", python = "^3.8" } 19 | mypy = "^0.942" 20 | wemake-python-styleguide = "^0.16.1" 21 | add-trailing-comma = "^2.2.2" 22 | isort = "^5.10.1" 23 | rope = "^0.23.0" 24 | autopep8 = "^1.6.0" 25 | ipython = { version = "^8.2.0", python = "^3.8" } 26 | flakeheaven = "^0.11.1" 27 | typing-extensions = { version = "^4.1.1", python = "< 3.8" } 28 | 29 | [tool.flakeheaven] 30 | base = [".flakeheaven.toml"] 31 | 32 | [tool.mypy] 33 | plugins = "pydantic.mypy,sqlmypy" 34 | 35 | [tool.isort] 36 | multi_line_output = 3 37 | include_trailing_comma = true 38 | use_parentheses = true 39 | 40 | [tool.pydocstyle] 41 | convention = "google" 42 | 43 | [build-system] 44 | requires = ["poetry-core>=1.0.0"] 45 | build-backend = "poetry.core.masonry.api" 46 | -------------------------------------------------------------------------------- /.flakeheaven.toml: -------------------------------------------------------------------------------- 1 | [tool.flakeheaven] 2 | format = "wemake" 3 | show_source = true 4 | statistics = false 5 | doctests = true 6 | exclude = [".venv"] 7 | baseline = ".flakeheaven-baseline" 8 | 9 | accept_encodings = "utf-8" 10 | max_complexity = 6 11 | max_line_length = 80 12 | 13 | [tool.flakeheaven.plugins] 14 | "flake8-bandit" = ["+*"] 15 | "flake8-broken-line" = ["+*"] 16 | "flake8-bugbear" = ["+*"] 17 | "flake8-commas" = ["+*"] 18 | "flake8-comprehensions" = ["+*"] 19 | # "flake8-darglint" = ["+*"] # Potentially useful 20 | "flake8-debugger" = ["+*"] 21 | # "flake8-docstrings" = ["+*"] # Potentially useful 22 | # "flake8-eradicate" = ["+*"] # False positives 23 | "flake8-isort" = ["+*"] 24 | "flake8-quotes" = ["+*"] 25 | 26 | # We use Napoleon syntax (https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html) 27 | # which doesn't play well with https://github.com/peterjc/flake8-rst-docstrings: 28 | # "flake8-rst-docstrings" = ["+*"] 29 | 30 | "flake8-string-format" = ["+*"] 31 | mccabe = ["+*"] 32 | "pep8-naming" = ["+*"] 33 | pyflakes = ["+*"] 34 | 35 | pycodestyle = ["+*", 36 | # Outdated rule (see https://www.flake8rules.com/rules/W503.html) 37 | "-W503", # Line break occurred before a binary operator 38 | ] 39 | 40 | "wemake-python-styleguide" = [ 41 | "+*", 42 | 43 | # These are purely subjective, the other ones will come with explanations: 44 | "-WPS110", # Found wrong variable name 45 | "-WPS125", # Found builtin shadowing (shadowing id is not a crime :-) 46 | "-WPS300", # Found local folder import 47 | "-WPS332", # Found walrus operator 48 | "-WPS348", # Found a line that starts with a dot 49 | "-WPS430", # Found nested function 50 | "-WPS507", # Found useless `len()` compare 51 | "-WPS602", # Found using `@staticmethod` 52 | 53 | # Doesn't play well with f-strings: 54 | "-WPS226", # Found string constant over-use 55 | 56 | # These two fail to detect new scope for control variables: 57 | "-WPS440", # Found block variables overlap 58 | "-WPS441", # Found control variable used after block 59 | ] 60 | -------------------------------------------------------------------------------- /ShazamAPI/api.py: -------------------------------------------------------------------------------- 1 | import time 2 | import types 3 | import uuid 4 | from io import BytesIO 5 | from typing import BinaryIO, Generator, Tuple, Union 6 | 7 | try: 8 | from typing import Final # noqa: WPS433 9 | except ImportError: 10 | from typing_extensions import Final # noqa: WPS433, WPS440 11 | 12 | import requests 13 | from pydub import AudioSegment 14 | 15 | from .algorithm import SignatureGenerator 16 | from .signature_format import DecodedMessage 17 | 18 | LANG: Final = 'ru' 19 | REGION: Final = 'RU' 20 | TIMEZONE: Final = 'Europe/Moscow' 21 | 22 | API_URL_TEMPLATE: Final = ( 23 | 'https://amp.shazam.com/discovery/v5' 24 | + '/{lang}/{region}/iphone/-/tag/{uuid_a}/{uuid_b}' 25 | ) 26 | BASE_HEADERS: Final = types.MappingProxyType({ 27 | 'X-Shazam-Platform': 'IPHONE', 28 | 'X-Shazam-AppVersion': '14.1.0', 29 | 'Accept': '*/*', 30 | 'Accept-Encoding': 'gzip, deflate', 31 | 'User-Agent': 'Shazam/3685 CFNetwork/1197 Darwin/20.0.0', 32 | }) 33 | PARAMS: Final = types.MappingProxyType({ 34 | 'sync': 'true', 35 | 'webv3': 'true', 36 | 'sampling': 'true', 37 | 'connected': '', 38 | 'shazamapiversion': 'v3', 39 | 'sharehub': 'true', 40 | 'hubv5minorversion': 'v5.1', 41 | 'hidelb': 'true', 42 | 'video': 'v3', 43 | }) 44 | 45 | NORMALIZED_SAMPLE_WIDTH: Final = 2 46 | NORMALIZED_FRAME_RATE: Final = 16000 47 | NORMALIZED_CHANNELS: Final = 1 48 | 49 | 50 | class Shazam(object): 51 | max_time_seconds = 8 52 | 53 | def __init__( 54 | self, 55 | lang: str = LANG, 56 | region: str = REGION, 57 | timezone: str = TIMEZONE, 58 | ): 59 | self.lang = lang 60 | self.region = region 61 | self.timezone = timezone 62 | 63 | def recognize_song( 64 | self, audio: Union[bytes, BinaryIO, AudioSegment], 65 | ) -> Generator[Tuple[float, dict], None, None]: 66 | audio = self.normalize_audio_data(audio) 67 | signature_generator = self.create_signature_generator(audio) 68 | for signature in signature_generator: 69 | results = self.send_recognize_request(signature) 70 | current_offset = int( 71 | signature_generator.samples_processed // NORMALIZED_FRAME_RATE, 72 | ) 73 | 74 | yield current_offset, results 75 | 76 | def normalize_audio_data( 77 | self, audio: Union[bytes, BinaryIO, AudioSegment], 78 | ) -> AudioSegment: 79 | """ 80 | Reads audio to pydub.AudioSegment (if it is not one already), then sets 81 | sample width, frame rate and channels required by Shazam API. 82 | """ 83 | if isinstance(audio, bytes): 84 | audio = AudioSegment.from_file(BytesIO(audio)) 85 | elif not isinstance(audio, AudioSegment): 86 | audio = AudioSegment.from_file(audio) 87 | 88 | audio = audio.set_sample_width(NORMALIZED_SAMPLE_WIDTH) 89 | audio = audio.set_frame_rate(NORMALIZED_FRAME_RATE) 90 | audio = audio.set_channels(NORMALIZED_CHANNELS) 91 | return audio # noqa: WPS331 92 | 93 | def create_signature_generator( 94 | self, audio: AudioSegment, 95 | ) -> SignatureGenerator: 96 | """ 97 | Creates a SignatureGenerator instance for given audio segment. 98 | """ 99 | signature_generator = SignatureGenerator() 100 | signature_generator.feed_input(audio.get_array_of_samples()) 101 | signature_generator.MAX_TIME_SECONDS = self.max_time_seconds 102 | 103 | # TODO: what 12, 3, 16, 6 mean here? :thinking: 104 | if audio.duration_seconds > 12 * 3: 105 | signature_generator.samples_processed += NORMALIZED_FRAME_RATE * ( 106 | int(audio.duration_seconds / 16) - 6 107 | ) 108 | 109 | return signature_generator 110 | 111 | def send_recognize_request(self, sig: DecodedMessage) -> dict: 112 | data = { 113 | 'timezone': self.timezone, 114 | 'signature': { 115 | 'uri': sig.encode_to_uri(), 116 | 'samplems': int( 117 | sig.number_samples / sig.sample_rate_hz * 1000, 118 | ), 119 | }, 120 | 'timestamp': int(time.time() * 1000), 121 | 'context': {}, 122 | 'geolocation': {}, 123 | } 124 | resp = requests.post( 125 | API_URL_TEMPLATE.format( 126 | lang=self.lang, 127 | region=self.region, 128 | uuid_a=str(uuid.uuid4()).upper(), 129 | uuid_b=str(uuid.uuid4()).upper(), 130 | ), 131 | params=PARAMS, 132 | headers={ 133 | **BASE_HEADERS, 134 | 'Accept-Language': self.lang, 135 | }, 136 | json=data, 137 | ) 138 | return resp.json() 139 | -------------------------------------------------------------------------------- /.flakeheaven-baseline: -------------------------------------------------------------------------------- 1 | 0d80518d480f7c627cbf43d00ee6d9e4 2 | 2356827a549498b0eab1b53cb06942bb 3 | ed297bd70f6ff95389d56eab58516bd2 4 | ff458e6c3c2a1f41823e5cc1ad558f6d 5 | 13033fa225b91af66c893ddbc900f057 6 | fda78bd4ef694e14cd49382bf7b5d99b 7 | de9c6ca60d34516bb34ae3c8ab94a44f 8 | 0959d1c40ee7f50bbcd98137d2ecff4f 9 | 89880f8b05d7ea8df86d11885a935f59 10 | ff392d859779b74100b8b82fe31e955c 11 | 2d16bc13096605275a358dad54a6cef3 12 | fcd4e95af77fda5b5cb2fb41bc220834 13 | 777fcd694ba41c1ce5161eeda450c463 14 | 25d14b2f791e2bde81b641606d7a48b0 15 | 7aaeaf84457aad820a928683e74fa72f 16 | 670ac1c3b4519be656d75c8a7394ed5b 17 | 73cbae941b6e217cad3f1fb0e6f0e556 18 | c66109a308059d83eed9664503ef0248 19 | 777fcd694ba41c1ce5161eeda450c463 20 | 7aaeaf84457aad820a928683e74fa72f 21 | 25d14b2f791e2bde81b641606d7a48b0 22 | 44b21f30c03a29d37a40db71bea7511b 23 | b4ba14c8294041465652995d88c0afa4 24 | 8f0d75a06317a1d40526b2a6dd80af5d 25 | 0cd34c3f0f7c75ec1c634f2518855f50 26 | e6b7cd3acf6aa00c9e1d9adc2c1adec7 27 | 514d038227091259151aa93e1bd0bc41 28 | 3426f5e4878a37363309b173211f754a 29 | 2663d611f9111479acf12157352380c5 30 | 631a8a2f3328bb4362cc02307cbe2c41 31 | a48891c7564fcdbc94dc765dcf00c392 32 | d74b22fa8818ce20710bb51d9f6f0a70 33 | 3f76e4c46c70a75db31fc3a0d1b42f3f 34 | 7257ca408d9ea3eb343fbd3be9c97c4f 35 | 083723b9ca622f7e7886132c05dd61ab 36 | d33bddf0baf57bd56508064469789393 37 | 7f8584a8bd07326f59abaaf3d13d17ae 38 | 5dc0e2871f01e4a734153efbe74239a8 39 | 362776c9d558de6c5c59c466c97589bc 40 | 0b268bb4b93633b0a576009c08bedc94 41 | 31f7a3b1af85207a624cdeebfd3794ce 42 | 8385b9806a6ffb532a916cbf9299ef31 43 | 50c7568463bf4298336420bd72930b38 44 | ecc5a383cf17fb0deb101b745a8f5d45 45 | f06e3ecad898fa4d0f930249730ceb2d 46 | 76ebab63926d6a709f550257f6094df7 47 | c1f94b1362e61724a6e337b082ca5458 48 | af7737c707920ee6739f691382f90dc1 49 | 1c84020149cd349bc5c0ea975999c1bd 50 | 92d962ad3ffa27d52258c0986a539a31 51 | 9bdc8e84b81d745ed9b5539688f06697 52 | 069c3eb1336ad71e972a4f0c4fcaab1d 53 | 775278e2ce30410db60b1807d68399d1 54 | dd780a481013aada74221396327d1b47 55 | 0b5dc2d491a51abd93c935b067402668 56 | 105a5ba0042f17dca1717c7e92ed2e4f 57 | 4a068a64d85a60e07c74a224e7356b37 58 | 25be52e9819d15dec2f39389f674c728 59 | 207dfdd1c852d54aa66536f6b0559f98 60 | d9742506e09f2f9db0b97e3b35449355 61 | f0babfad35ba30f7a4148e020c570b69 62 | 66c89fa3a5853af892051e5d3acf0cf9 63 | 7d2cd6b66b2f25a11abd3d16053beaea 64 | 022f8219ccee7d73d8f7ae6fb3982a4f 65 | 335a3c77e68689372ee2239e6cf43996 66 | 81144df1288cb5250e4fc5b521f96aa2 67 | 1078a99f3effbc30e2363a21d81b0ac6 68 | 876def331e0dc85f194586c7ae91ad68 69 | f6bb2e10fd45ed2766e3a3b51597fdf8 70 | 138b1c9bc24a14ca80f939749c03c144 71 | fa210fd2f1ad0e3a23128a7b0b93c64e 72 | ab706c1c2901bcae611691dde8eeec9c 73 | ff8ed8bfc56aa918b53d2cfb70f52d6f 74 | e143b26916f8b0ada03db122402b9a1b 75 | 52f1d987bac78404488fdefbab7f0048 76 | 1078a99f3effbc30e2363a21d81b0ac6 77 | 4a3f3578ba71d7150e31c540a0f72b11 78 | 879207201bca48c3a97054199bb02d09 79 | 65bc0deb15143f205a89aa981ae81e0b 80 | 7153a97b0b73cfe2a821c76f84dc5067 81 | 981d16a6f11130c1b5e714bfe593d850 82 | 15e9a30cf5f427b61fc844dfa977263a 83 | a932bc15fd1759b857d514ee5f7724bb 84 | 0a782922c85072d9ee727362c1a6b8f5 85 | 145477f013a7ea3f81cffeabf7f2cb78 86 | 3340ceffd54a81bf3f039f2f1df3743c 87 | 643e673bf1bd54870c844011b2e0507c 88 | 5047b1a51ee040352eb418fbc9ef5c1c 89 | 188d93b0779ad5beced43e303ea1dfe2 90 | f52ba8d9122695be1c4f075aea488ea4 91 | f52ba8d9122695be1c4f075aea488ea4 92 | f985f769917b1c488ff6f2529f0b45a0 93 | 38be7d756f177e0be7e3a135f9a28172 94 | 176d48d80e5cf8ebd6007ca0da2c5d72 95 | 5be57a00afe4638e8e292bd38f16280c 96 | a78fd195637ce99d6c1dacca786a427c 97 | a78fd195637ce99d6c1dacca786a427c 98 | 843d88eab65dfa5f2d81ab0ebb1001ad 99 | 843d88eab65dfa5f2d81ab0ebb1001ad 100 | 31cc180ec87e132989978c095bc066eb 101 | 50edc13771ee108b2506f09d6720e6ec 102 | 0054ac262a026fe98119163db80cfd64 103 | 7570c9a33b4a701490311d1c9d435300 104 | 5be57a00afe4638e8e292bd38f16280c 105 | 843d88eab65dfa5f2d81ab0ebb1001ad 106 | 843d88eab65dfa5f2d81ab0ebb1001ad 107 | a78fd195637ce99d6c1dacca786a427c 108 | a78fd195637ce99d6c1dacca786a427c 109 | 54458965d736080d39cda37eda24e93b 110 | 00b99e33b7b4859538079139407f18eb 111 | 7679eef25dd0f07f11b08ab32269e2a6 112 | 812fe9c5b8530af8f9b3c91f6dac386a 113 | 4b658535569887f59344fd753cab7439 114 | 96a95e7b44120fcdfa37b4d0d904ad1c 115 | 8e333d4417ed26a17c4de260b93ac5d4 116 | 149d3eda093d1cf69b890d06016af6c0 117 | 889549efbc257d17471a88b36bb1b361 118 | b5f906cb94fdeea4c86a49bb28fc6a4b 119 | 1c8ee8c0faa61dc35f9eb4153869e6cf 120 | bc1e62e1320b4e43e002d0d51243dac6 121 | 0030c5c71b1c73e566961c356cd72b4b 122 | ef75735f2ab9a7ab368b9ea076924c73 123 | 4f7fad95179f229fe3ad515ebf42880d 124 | 0d0720d6a5530cb9b9cdc7ad7d7f8ead 125 | 0d0720d6a5530cb9b9cdc7ad7d7f8ead 126 | 0d0720d6a5530cb9b9cdc7ad7d7f8ead 127 | 0d0720d6a5530cb9b9cdc7ad7d7f8ead 128 | a5df438b72a88ee75c07c896384ba990 129 | 9fcb337acc9183cd8e4c195b4989c1e2 130 | 9fcb337acc9183cd8e4c195b4989c1e2 131 | 9fcb337acc9183cd8e4c195b4989c1e2 132 | 5201506d7802de08f39dd76eb56b515a 133 | 5201506d7802de08f39dd76eb56b515a 134 | 5201506d7802de08f39dd76eb56b515a 135 | e049a83eae56187508e22ecb520d47fc 136 | e049a83eae56187508e22ecb520d47fc 137 | e049a83eae56187508e22ecb520d47fc 138 | e48981a0638580ff6ec1ab39ea3e5370 139 | de35141218b4f1ef37c084fcc62a9dc8 140 | d23e081bdf8dcd5486bf28b6bc8a0f3d 141 | d23e081bdf8dcd5486bf28b6bc8a0f3d 142 | 59247b23f312b0cd520678b6e130ebb0 143 | 3a2d6bffc6ba141be375d521f0c673d5 144 | 0f5e6d0f32849650c25982d92874b274 145 | fb4943822907a4d7c3487e0781069ab0 146 | fb4943822907a4d7c3487e0781069ab0 147 | 0f5e6d0f32849650c25982d92874b274 148 | 1808d9c4bda270fe9ccf2f7056ce81e4 149 | 65ed7027799c2038dce03857afd6535d 150 | 95649996d85d481ffc33c44ce6ab945a 151 | 51fbc352ec91d3378af1d3f614408b41 152 | 594fd0395c0792de29efc8697835fc9d 153 | 40c7acc480015e2e4067f8bd23ac7c47 154 | 93796fb7d18d91e05f709007c7cd65ee 155 | 8381b46a0b61263a27e68193a260b3fc 156 | 6a5670270cfa4f2b3b8f9afe98ea02a6 157 | ee2c5196ca69520d1da824fe9b181ec7 158 | 4737fd174b711b26b0c755d1a612d9d5 159 | f459996abc0678c9e8a91f6131bb1e1e 160 | cde333accddaae854d022e0ca489fda2 161 | 5976f19f94c1116b51e5db0336a1340b 162 | 5f5ba938a2f43250ed69e904c54c8cfb 163 | a815caec9b869a4b2b1a85e67292abe8 164 | 8c840223b490f329a19f16d684334f27 165 | 0e4a2cd876b997d7dffc5d3e03c072ac 166 | 7d199fe61ea0b6c01bf6e14e5a63c5ca 167 | 6b2f4134e7e66f15252b10b646d9c210 168 | 54a4db85e5374e93bbd8fd3dbb538802 169 | dcf9e6dfd95bbf5190c8d1b70f1dec17 170 | 571a0c31a4af8378f07161af3a9402be 171 | dd1b39149b23fb38d281b5d9c45423cb 172 | f21272431750f91eb48ba85e0196774d 173 | 38b38619d2cd7f4578479e1a989adaeb 174 | c23a4a152ac54b694fa967c081a97b0b 175 | 1d0ad70aae45591846ea641cfc31b82f 176 | ae281b4563d3bd1fe9a25c6307227ed9 177 | 414ba533d675abf99df0fa6eb1f2e3d5 178 | ca2468f9e0a2f21c93ca0cfe2a39d80f 179 | d7d4af7b104f454cc68a59604aa02daf 180 | 240d12fc3155bef920bd7829ab0f4b3d 181 | 90f15d82f8737afeb3c162df1b02559a 182 | 9fe14dc53d8eedc9e11da605148f778c 183 | 02c0b23d5dc0e4ec38f924b9a5a1bcd2 184 | d260079c44bed0c5a7b9f6b91e22dcef 185 | 951091a644b60cc84a531a61d4d1aadf 186 | 2b1fd89fef83a24de600d6bb6998a5e6 187 | fca4c6710b588d61670953f7c72546cc 188 | 2d91d8d203c78dd95176f7f58ff5ebd6 189 | 95a323f49a33c00d5b1932823bc906d0 190 | d09953ffed9d16bf1de2692671f95c19 191 | 4ad8379c9493e6da502552b6b8a71fcf 192 | 4ad8379c9493e6da502552b6b8a71fcf 193 | 4ad8379c9493e6da502552b6b8a71fcf 194 | 1b44933c9e49a81a350cb8ac7e8236ea 195 | 8eaf7287a99b608b7a890da698ec2578 196 | 72010de3d27c7f36ec56864fef57e105 197 | 5e071f8371fa82679cfd15cde635157a 198 | 458b9a7ac08ca01cd33748c0c3aa25b8 199 | 9416cfccc5e92d4d3da46ce97b426bd3 200 | 7fca7b51632da4711f46956016574f15 201 | 55eb6a246c2b9f0cc83a62256900ab03 202 | 55eb6a246c2b9f0cc83a62256900ab03 203 | 1af987cac7f32aea6ae26ed265a4641f 204 | bbb995ce07b8420d4b0c93d827bdafd3 205 | 23e536a4365c465f6c0ee038c314dfaa 206 | 3b1f0a4378a2508ec725740d2b7464ba 207 | 6b3670a87d89f68fc390e3d1d3bfde32 208 | 1f1d515be1a1b52d10bfba4434357466 209 | c68fae7f15bd754689af00e81ca44073 210 | f9b0e8aa045a0849691dc71375ce5675 211 | 68b1bbe5d33cfead7dd720b9b84f95a4 212 | bf2c431fdee993de4ff556d28e62d2ef 213 | bc6cfdb427eb827a6a7af12f9ae0bef6 214 | 51f4d0a004459016bb445cadc96dd57c 215 | -------------------------------------------------------------------------------- /ShazamAPI/signature_format.py: -------------------------------------------------------------------------------- 1 | import ctypes 2 | import types 3 | from base64 import b64decode, b64encode 4 | from binascii import crc32 5 | from enum import IntEnum 6 | from io import BytesIO 7 | from math import exp, sqrt 8 | from typing import Dict, List 9 | 10 | try: 11 | from typing import Final # noqa: WPS433 12 | except ImportError: 13 | from typing_extensions import Final # noqa: WPS433, WPS440 14 | 15 | 16 | DATA_URI_PREFIX: Final = 'data:audio/vnd.shazam.sig;base64,' 17 | 18 | HEADER_SIZE: Final = 48 19 | HEADER_MAGIC1: Final = 0xCAFE2580 20 | HEADER_MAGIC2: Final = 0x94119C00 21 | HEADER_MAGIC3: Final = ((15 << 19) + 0x40000) 22 | 23 | SHIFTED_SAMPLE_RATE_FROM_ID: Final = types.MappingProxyType({ 24 | (1 << 27): 8000, 25 | (2 << 27): 11025, 26 | (3 << 27): 16000, 27 | (4 << 27): 32000, 28 | (5 << 27): 44100, 29 | (6 << 27): 48000, 30 | }) 31 | SHIFTED_SAMPLE_RATE_TO_ID: Final = types.MappingProxyType({ 32 | rate_hz: rate_id 33 | for rate_id, rate_hz in SHIFTED_SAMPLE_RATE_FROM_ID.items() 34 | }) 35 | 36 | 37 | class FrequencyBand(IntEnum): 38 | # Enum keys are frequency ranges in Hzs 39 | band_0_250 = -1 # Nothing above 250 Hz is actually stored 40 | band_250_520 = 0 41 | band_520_1450 = 1 42 | band_1450_3500 = 2 43 | band_3500_5500 = 3 # This one (3.5 KHz - 5.5 KHz) should not be used in legacy mode 44 | 45 | 46 | class RawSignatureHeader(ctypes.LittleEndianStructure): 47 | _pack = True 48 | _fields_ = [ # noqa: WPS120 49 | # Fixed HEADER_MAGIC1 - 0xCAFE2580: 50 | ('magic1', ctypes.c_uint32), 51 | 52 | # CRC-32 for all of the following (so excluding these first 8 bytes): 53 | ('crc32', ctypes.c_uint32), 54 | 55 | # Total size of the message, minus the size of the current header (which is 48 bytes): 56 | ('size_minus_header', ctypes.c_uint32), 57 | 58 | # Fixed HEADER_MAGIC2 - 0x94119C00: 59 | ('magic2', ctypes.c_uint32), 60 | 61 | # Void: 62 | ('void1', ctypes.c_uint32 * 3), 63 | 64 | # A member of SAMPLE_RATE_TO_ID (usually 3 << 27 for 16000 Hz): 65 | ('shifted_sample_rate_id', ctypes.c_uint32), 66 | 67 | # Void, or maybe used only in "rolling window" mode?: 68 | ('void2', ctypes.c_uint32 * 2), 69 | 70 | # int(number_of_samples + sample_rate * 0.24) - As the sample rate is 71 | # known thanks to the field above, it can be inferred and substracted 72 | # so that we obtain the number of samples, and from the number of 73 | # samples and sample rate we can obtain the length of the recording: 74 | ('number_samples_plus_divided_sample_rate', ctypes.c_uint32), 75 | 76 | # Calculated as ((15 << 19) + 0x40000) = 0x007C0000 - seems pretty 77 | # constant, may be different in the "SigType.STREAMING" mode: 78 | ('magic3', ctypes.c_uint32), 79 | ] 80 | 81 | 82 | class FrequencyPeak(object): 83 | def __init__( 84 | self, 85 | fft_pass_number: int, 86 | peak_magnitude: int, 87 | corrected_peak_frequency_bin: int, 88 | sample_rate_hz: int, 89 | ): 90 | self.fft_pass_number = fft_pass_number 91 | self.peak_magnitude = peak_magnitude 92 | self.corrected_peak_frequency_bin = corrected_peak_frequency_bin 93 | self.sample_rate_hz = sample_rate_hz 94 | 95 | def get_frequency_hz(self) -> float: 96 | return self.corrected_peak_frequency_bin * (self.sample_rate_hz / 2 / 1024 / 64) 97 | # ^ Convert back a FFT bin to a frequency, given a 16 KHz sample rate, 98 | # 1024 useful bins and the multiplication by 64 made before storing the 99 | # information 100 | 101 | def get_amplitude_pcm(self) -> float: 102 | return sqrt( 103 | exp((self.peak_magnitude - 6144) / 1477.3) 104 | * (1 << 17) / 2, 105 | ) / 1024 106 | # ^ Not sure about this calculation but gives small enough numbers 107 | 108 | def get_seconds(self) -> float: 109 | return (self.fft_pass_number * 128) / self.sample_rate_hz 110 | # ^ Assume that new FFT bins are emitted every 128 samples, on a 111 | # standard 16 KHz sample rate basis. 112 | 113 | 114 | class DecodedMessage(object): 115 | @classmethod 116 | def decode_from_binary(cls, data: bytes): 117 | result = cls() 118 | 119 | buf = BytesIO(data) 120 | 121 | buf.seek(8) 122 | checksummable_data = buf.read() 123 | buf.seek(0) 124 | 125 | # Read and check the header 126 | 127 | header = RawSignatureHeader() 128 | buf.readinto(header) 129 | 130 | # Not checking for HEADER_MAGIC3 because it might be different 131 | if header.magic1 != HEADER_MAGIC1 or header.magic2 != HEADER_MAGIC2: 132 | raise ValueError('Wrong magic string specified in header') 133 | 134 | if header.size_minus_header != len(data) - HEADER_SIZE: 135 | raise ValueError('Wrong size specified in header') 136 | 137 | if crc32(checksummable_data) & 0xFFFFFFFF != header.crc32: 138 | raise ValueError('Wrong checksum specified in header') 139 | 140 | result.sample_rate_hz: int = SHIFTED_SAMPLE_RATE_FROM_ID[header.shifted_sample_rate_id] 141 | result.number_samples: int = int( 142 | header.number_samples_plus_divided_sample_rate 143 | - result.sample_rate_hz * 0.24, 144 | ) 145 | 146 | # Read the type-length-value sequence that follows the header 147 | 148 | # The first chunk is fixed and has no value, but instead just repeats 149 | # the length of the message size minus the header: 150 | if ( 151 | int.from_bytes(buf.read(4), 'little') != 0x40000000 152 | or int.from_bytes(buf.read(4), 'little') != len(data) - HEADER_SIZE 153 | ): 154 | raise ValueError('Unexpected first chunk format') 155 | 156 | # Then, lists of frequency peaks for respective bands follow: 157 | result.frequency_band_to_sound_peaks: Dict[FrequencyBand, List[FrequencyPeak]] = {} 158 | 159 | while True: 160 | tlv_header = buf.read(8) 161 | if not tlv_header: 162 | break 163 | 164 | frequency_band_id = int.from_bytes(tlv_header[:4], 'little') 165 | frequency_peaks_size = int.from_bytes(tlv_header[4:], 'little') 166 | frequency_peaks_padding = -frequency_peaks_size % 4 167 | frequency_peaks_buf = BytesIO(buf.read(frequency_peaks_size)) 168 | buf.read(frequency_peaks_padding) 169 | 170 | # Decode frequency peaks: 171 | frequency_band = FrequencyBand(frequency_band_id - 0x60030040) 172 | fft_pass_number = 0 173 | result.frequency_band_to_sound_peaks[frequency_band] = [] 174 | 175 | while True: 176 | raw_fft_pass: bytes = frequency_peaks_buf.read(1) 177 | if not raw_fft_pass: 178 | break 179 | 180 | fft_pass_offset: int = raw_fft_pass[0] 181 | if fft_pass_offset == 0xFF: 182 | fft_pass_number = int.from_bytes(frequency_peaks_buf.read(4), 'little') 183 | continue 184 | else: 185 | fft_pass_number += fft_pass_offset 186 | 187 | peak_magnitude = int.from_bytes(frequency_peaks_buf.read(2), 'little') 188 | corrected_peak_frequency_bin = int.from_bytes(frequency_peaks_buf.read(2), 'little') 189 | 190 | result.frequency_band_to_sound_peaks[frequency_band].append( 191 | FrequencyPeak(fft_pass_number, peak_magnitude, corrected_peak_frequency_bin, result.sample_rate_hz), 192 | ) 193 | 194 | return result 195 | 196 | @classmethod 197 | def decode_from_uri(cls, uri: str): 198 | if not uri.startswith(DATA_URI_PREFIX): 199 | raise ValueError('Not a valid audio/vnd.shazam.sig data: URI') 200 | 201 | return cls.decode_from_binary(b64decode(uri.replace(DATA_URI_PREFIX, '', 1))) 202 | 203 | def encode_to_json(self) -> dict: 204 | """ 205 | Encode the current object to a readable JSON format, for debugging 206 | purposes. 207 | """ 208 | return { 209 | 'sample_rate_hz': self.sample_rate_hz, 210 | 'number_samples': self.number_samples, 211 | '_seconds': self.number_samples / self.sample_rate_hz, 212 | 'frequency_band_to_peaks': { 213 | frequency_band.name.strip('_'): [ 214 | { 215 | 'fft_pass_number': frequency_peak.fft_pass_number, 216 | 'peak_magnitude': frequency_peak.peak_magnitude, 217 | 'corrected_peak_frequency_bin': frequency_peak.corrected_peak_frequency_bin, 218 | '_frequency_hz': frequency_peak.get_frequency_hz(), 219 | '_amplitude_pcm': frequency_peak.get_amplitude_pcm(), 220 | '_seconds': frequency_peak.get_seconds(), 221 | } 222 | for frequency_peak in frequency_peaks 223 | ] 224 | for frequency_band, frequency_peaks in sorted(self.frequency_band_to_sound_peaks.items()) 225 | }, 226 | } 227 | 228 | def encode_to_binary(self) -> bytes: 229 | header = RawSignatureHeader() 230 | header.magic1 = HEADER_MAGIC1 231 | header.magic2 = HEADER_MAGIC2 232 | header.shifted_sample_rate_id = SHIFTED_SAMPLE_RATE_TO_ID[self.sample_rate_hz] 233 | header.magic3 = HEADER_MAGIC3 234 | header.number_samples_plus_divided_sample_rate = int(self.number_samples + self.sample_rate_hz * 0.24) 235 | 236 | contents_buf = BytesIO() 237 | for frequency_band, frequency_peaks in sorted(self.frequency_band_to_sound_peaks.items()): 238 | peaks_buf = BytesIO() 239 | fft_pass_number = 0 240 | 241 | # NOTE: Correctly filtering and sorting the peaks within the members 242 | # of "self.frequency_band_to_sound_peaks" is the responsability of the 243 | # caller 244 | 245 | for frequency_peak in frequency_peaks: 246 | if frequency_peak.fft_pass_number < fft_pass_number: 247 | raise ValueError('frequency_peak.fft_pass_number < fft_pass_number') 248 | 249 | if frequency_peak.fft_pass_number - fft_pass_number >= 255: 250 | peaks_buf.write(b'\xff') 251 | peaks_buf.write((frequency_peak.fft_pass_number).to_bytes(4, 'little')) 252 | fft_pass_number = frequency_peak.fft_pass_number 253 | 254 | peaks_buf.write(bytes([frequency_peak.fft_pass_number - fft_pass_number])) 255 | peaks_buf.write((frequency_peak.peak_magnitude).to_bytes(2, 'little')) 256 | peaks_buf.write((frequency_peak.corrected_peak_frequency_bin).to_bytes(2, 'little')) 257 | 258 | fft_pass_number = frequency_peak.fft_pass_number 259 | 260 | contents_buf.write((0x60030040 + int(frequency_band)).to_bytes(4, 'little')) 261 | contents_buf.write(len(peaks_buf.getvalue()).to_bytes(4, 'little')) 262 | contents_buf.write(peaks_buf.getvalue()) 263 | contents_buf.write(b'\x00' * (-len(peaks_buf.getvalue()) % 4)) 264 | 265 | # Below, write the full message as a binary stream 266 | 267 | header.size_minus_header = len(contents_buf.getvalue()) + 8 268 | 269 | buf = BytesIO() 270 | buf.write(bytes(header)) # We will rewrite it just after in order to include the final CRC-32 271 | 272 | buf.write((0x40000000).to_bytes(4, 'little')) 273 | buf.write((len(contents_buf.getvalue()) + 8).to_bytes(4, 'little')) 274 | 275 | buf.write(contents_buf.getvalue()) 276 | 277 | buf.seek(8) 278 | header.crc32 = crc32(buf.read()) & 0xFFFFFFFF 279 | buf.seek(0) 280 | buf.write(bytes(header)) 281 | 282 | return buf.getvalue() 283 | 284 | def encode_to_uri(self) -> str: 285 | return DATA_URI_PREFIX + b64encode(self.encode_to_binary()).decode('ascii') 286 | -------------------------------------------------------------------------------- /ShazamAPI/algorithm.py: -------------------------------------------------------------------------------- 1 | from copy import copy 2 | from typing import Any, List, Optional 3 | 4 | from numpy import array as nparray 5 | from numpy import fft, hanning, log, maximum 6 | 7 | from .signature_format import ( 8 | DecodedMessage, 9 | FrequencyBand, 10 | FrequencyPeak, 11 | ) 12 | 13 | HANNING_MATRIX = hanning(2050)[1:-1] # Wipe trailing and leading zeroes 14 | 15 | 16 | class RingBuffer(list): # noqa: WPS600 17 | def __init__(self, buffer_size: int, default_value: Any = None): 18 | if default_value is not None: 19 | super().__init__([copy(default_value) for _ in range(buffer_size)]) 20 | else: 21 | super().__init__([None] * buffer_size) 22 | 23 | self.position: int = 0 24 | self.buffer_size: int = buffer_size 25 | self.num_written: int = 0 26 | 27 | def append(self, value: Any): 28 | self[self.position] = value 29 | self.position += 1 30 | self.position %= self.buffer_size 31 | self.num_written += 1 32 | 33 | 34 | class SignatureGenerator(object): 35 | def __init__(self): 36 | # Used when storing input that will be processed when requiring to 37 | # generate a signature: 38 | self.input_pending_processing: List[int] = [] # Signed 16-bits, 16 KHz mono samples to be processed 39 | self.samples_processed: int = 0 # Number of samples processed out of "self.input_pending_processing" 40 | 41 | # Used when processing input: 42 | self.ring_buffer_of_samples: RingBuffer[int] = RingBuffer(buffer_size=2048, default_value=0) 43 | self.spread_ffts_output: RingBuffer[List[float]] = RingBuffer(buffer_size=256, default_value=[0] * 1025) 44 | 45 | # Used when processing input. Lists of 1025 floats, premultiplied with 46 | # a Hanning function before being passed through FFT, computed from the 47 | # ring buffer every new 128 samples: 48 | self.fft_outputs: RingBuffer[List[float]] = RingBuffer(buffer_size=256, default_value=[float(0)] * 1025) 49 | 50 | # How much data to send to Shazam at once? 51 | self.MAX_TIME_SECONDS = 3.1 52 | self.MAX_PEAKS = 255 53 | 54 | # The object that will hold information about the next fingerpring to 55 | # be produced: 56 | self.next_signature = DecodedMessage() 57 | self.next_signature.sample_rate_hz = 16000 58 | self.next_signature.number_samples = 0 59 | self.next_signature.frequency_band_to_sound_peaks = {} 60 | 61 | def __next__(self) -> DecodedMessage: 62 | sig = self.get_next_signature() 63 | if sig is None: 64 | raise StopIteration 65 | 66 | return sig 67 | 68 | def __iter__(self): 69 | return self 70 | 71 | def feed_input(self, s16le_mono_samples: List[int]): 72 | """ 73 | Add data to be generated a signature for, which will be processed when 74 | self.get_next_signature() is called. This function expects signed 75 | 16-bit 16 KHz mono PCM samples. 76 | """ 77 | self.input_pending_processing += s16le_mono_samples 78 | 79 | def get_next_signature(self) -> Optional[DecodedMessage]: 80 | """ 81 | Consume some of the samples fed to self.feed_input(), and return a 82 | Shazam signature (DecodedMessage object) to be sent to servers once 83 | "enough data has been gathered". 84 | 85 | Except if there are no more samples to be consumed, in this case we 86 | will return None. 87 | """ 88 | if len(self.input_pending_processing) - self.samples_processed < 128: 89 | return None 90 | 91 | while ( 92 | len(self.input_pending_processing) - self.samples_processed >= 128 93 | and ( 94 | self.next_signature.number_samples / self.next_signature.sample_rate_hz < self.MAX_TIME_SECONDS 95 | or sum( 96 | len(peaks) for peaks in self.next_signature.frequency_band_to_sound_peaks.values() 97 | ) < self.MAX_PEAKS 98 | ) 99 | ): 100 | self.process_input(self.input_pending_processing[self.samples_processed:self.samples_processed + 128]) 101 | self.samples_processed += 128 102 | 103 | returned_signature = self.next_signature 104 | 105 | self.next_signature = DecodedMessage() 106 | self.next_signature.sample_rate_hz = 16000 107 | self.next_signature.number_samples = 0 108 | self.next_signature.frequency_band_to_sound_peaks = {} 109 | 110 | self.ring_buffer_of_samples: RingBuffer[int] = RingBuffer(buffer_size=2048, default_value=0) 111 | self.fft_outputs: RingBuffer[List[float]] = RingBuffer(buffer_size=256, default_value=[float(0)] * 1025) 112 | self.spread_ffts_output: RingBuffer[List[float]] = RingBuffer(buffer_size=256, default_value=[0] * 1025) 113 | 114 | return returned_signature 115 | 116 | def process_input(self, s16le_mono_samples: List[int]): 117 | self.next_signature.number_samples += len(s16le_mono_samples) 118 | for position_of_chunk in range(0, len(s16le_mono_samples), 128): 119 | self.do_fft(s16le_mono_samples[position_of_chunk:position_of_chunk + 128]) 120 | self.do_peak_spreading_and_recognition() 121 | 122 | def do_fft(self, batch: List[int]): 123 | """ 124 | Params: 125 | batch: batch of 128 s16le mono samples 126 | """ 127 | self.ring_buffer_of_samples[ # noqa: WPS362 128 | self.ring_buffer_of_samples.position: 129 | self.ring_buffer_of_samples.position + len(batch) 130 | ] = batch 131 | 132 | self.ring_buffer_of_samples.position += len(batch) 133 | self.ring_buffer_of_samples.position %= 2048 134 | self.ring_buffer_of_samples.num_written += len(batch) 135 | 136 | excerpt_from_ring_buffer: list = ( 137 | self.ring_buffer_of_samples[self.ring_buffer_of_samples.position:] 138 | + self.ring_buffer_of_samples[:self.ring_buffer_of_samples.position] 139 | ) 140 | 141 | # The premultiplication of the array is for applying a windowing 142 | # function before the DFT (slighty rounded Hanning without zeros at 143 | # edges): 144 | fft_results: nparray = fft.rfft( 145 | HANNING_MATRIX * excerpt_from_ring_buffer, 146 | ) 147 | 148 | if ( 149 | len(fft_results) != 1025 150 | or len(excerpt_from_ring_buffer) != 2048 151 | or len(HANNING_MATRIX) != 2048 152 | ): 153 | # TODO: need a better explanation? 154 | raise RuntimeError('Fast Fourier Transform gone horribly wrong') 155 | 156 | fft_results = (fft_results.real ** 2 + fft_results.imag ** 2) / (1 << 17) 157 | fft_results = maximum(fft_results, 1e-10) 158 | self.fft_outputs.append(fft_results) 159 | 160 | def do_peak_spreading_and_recognition(self): 161 | self.do_peak_spreading() 162 | if self.spread_ffts_output.num_written >= 46: 163 | self.do_peak_recognition() 164 | 165 | def do_peak_spreading(self): 166 | origin_last_fft: List[float] = self.fft_outputs[self.fft_outputs.position - 1] 167 | spread_last_fft: List[float] = list(origin_last_fft) 168 | for position in range(1025): 169 | # Perform frequency-domain spreading of peak values: 170 | if position < 1023: 171 | spread_last_fft[position] = max(spread_last_fft[position:position + 3]) 172 | 173 | # Perform time-domain spreading of peak values: 174 | max_value = spread_last_fft[position] 175 | for former_fft_num in [-1, -3, -6]: 176 | former_fft_output = self.spread_ffts_output[ 177 | (self.spread_ffts_output.position + former_fft_num) 178 | % self.spread_ffts_output.buffer_size 179 | ] 180 | former_fft_output[position] = max_value = max(former_fft_output[position], max_value) 181 | 182 | # Save output locally: 183 | self.spread_ffts_output.append(spread_last_fft) 184 | 185 | def do_peak_recognition(self): 186 | fft_minus_46 = self.fft_outputs[(self.fft_outputs.position - 46) % self.fft_outputs.buffer_size] 187 | fft_minus_49 = self.spread_ffts_output[(self.spread_ffts_output.position - 49) % self.spread_ffts_output.buffer_size] 188 | 189 | for bin_position in range(10, 1015): 190 | # Ensure that the bin is large enough to be a peak 191 | if ( 192 | fft_minus_46[bin_position] >= 1 / 64 193 | and fft_minus_46[bin_position] >= fft_minus_49[bin_position - 1] 194 | ): 195 | # Ensure that it is frequency-domain local minimum: 196 | max_neighbor_in_fft_minus_49 = 0 197 | for neighbor_offset in (*range(-10, -3, 3), -3, 1, *range(2, 9, 3)): 198 | max_neighbor_in_fft_minus_49 = max( 199 | fft_minus_49[bin_position + neighbor_offset], 200 | max_neighbor_in_fft_minus_49, 201 | ) 202 | 203 | if fft_minus_46[bin_position] > max_neighbor_in_fft_minus_49: 204 | # Ensure that it is a time-domain local minimum: 205 | max_neighbor_in_other_adjacent_ffts = max_neighbor_in_fft_minus_49 206 | 207 | for other_offset in (-53, -45, *range(165, 201, 7), *range(214, 250, 7)): 208 | max_neighbor_in_other_adjacent_ffts = max( 209 | self.spread_ffts_output[ 210 | (self.spread_ffts_output.position + other_offset) % 211 | self.spread_ffts_output.buffer_size 212 | ][bin_position - 1], 213 | max_neighbor_in_other_adjacent_ffts, 214 | ) 215 | 216 | if fft_minus_46[bin_position] > max_neighbor_in_other_adjacent_ffts: 217 | # This is a peak, store the peak: 218 | fft_number = self.spread_ffts_output.num_written - 46 219 | 220 | peak_magnitude = log(max(1 / 64, fft_minus_46[bin_position])) * 1477.3 + 6144 221 | peak_magnitude_before = log(max(1 / 64, fft_minus_46[bin_position - 1])) * 1477.3 + 6144 222 | peak_magnitude_after = log(max(1 / 64, fft_minus_46[bin_position + 1])) * 1477.3 + 6144 223 | 224 | peak_variation_1 = peak_magnitude * 2 - peak_magnitude_before - peak_magnitude_after 225 | peak_variation_2 = (peak_magnitude_after - peak_magnitude_before) * 32 / peak_variation_1 226 | 227 | corrected_peak_frequency_bin = bin_position * 64 + peak_variation_2 228 | 229 | if peak_variation_1 <= 0: 230 | # TODO: need a better explanation? 231 | raise RuntimeError('peak_variation_1 is not positive') 232 | 233 | frequency_hz = corrected_peak_frequency_bin * (16000 / 2 / 1024 / 64) 234 | if frequency_hz < 250: # noqa: WPS223, WPS432 235 | continue 236 | elif frequency_hz < 520: # noqa: WPS432 237 | band = FrequencyBand.band_250_520 238 | elif frequency_hz < 1450: # noqa: WPS432 239 | band = FrequencyBand.band_520_1450 240 | elif frequency_hz < 3500: # noqa: WPS432 241 | band = FrequencyBand.band_1450_3500 242 | elif frequency_hz <= 5500: # noqa: WPS432 243 | band = FrequencyBand.band_3500_5500 244 | else: 245 | continue 246 | 247 | if band not in self.next_signature.frequency_band_to_sound_peaks: 248 | self.next_signature.frequency_band_to_sound_peaks[band] = [] 249 | 250 | self.next_signature.frequency_band_to_sound_peaks[band].append( 251 | FrequencyPeak( 252 | fft_number, 253 | int(peak_magnitude), 254 | int(corrected_peak_frequency_bin), 255 | 16000, 256 | ), 257 | ) 258 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "add-trailing-comma" 3 | version = "2.2.2" 4 | description = "Automatically add trailing commas to calls and literals" 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=3.7" 8 | 9 | [package.dependencies] 10 | tokenize-rt = ">=3.0.1" 11 | 12 | [[package]] 13 | name = "appnope" 14 | version = "0.1.3" 15 | description = "Disable App Nap on macOS >= 10.9" 16 | category = "dev" 17 | optional = false 18 | python-versions = "*" 19 | 20 | [[package]] 21 | name = "astor" 22 | version = "0.8.1" 23 | description = "Read/rewrite/write Python ASTs" 24 | category = "dev" 25 | optional = false 26 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 27 | 28 | [[package]] 29 | name = "asttokens" 30 | version = "2.0.5" 31 | description = "Annotate AST trees with source code positions" 32 | category = "dev" 33 | optional = false 34 | python-versions = "*" 35 | 36 | [package.dependencies] 37 | six = "*" 38 | 39 | [package.extras] 40 | test = ["astroid", "pytest"] 41 | 42 | [[package]] 43 | name = "attrs" 44 | version = "21.4.0" 45 | description = "Classes Without Boilerplate" 46 | category = "dev" 47 | optional = false 48 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 49 | 50 | [package.extras] 51 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] 52 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 53 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] 54 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] 55 | 56 | [[package]] 57 | name = "autopep8" 58 | version = "1.6.0" 59 | description = "A tool that automatically formats Python code to conform to the PEP 8 style guide" 60 | category = "dev" 61 | optional = false 62 | python-versions = "*" 63 | 64 | [package.dependencies] 65 | pycodestyle = ">=2.8.0" 66 | toml = "*" 67 | 68 | [[package]] 69 | name = "backcall" 70 | version = "0.2.0" 71 | description = "Specifications for callback functions passed in to an API" 72 | category = "dev" 73 | optional = false 74 | python-versions = "*" 75 | 76 | [[package]] 77 | name = "bandit" 78 | version = "1.7.4" 79 | description = "Security oriented static analyser for python code." 80 | category = "dev" 81 | optional = false 82 | python-versions = ">=3.7" 83 | 84 | [package.dependencies] 85 | colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} 86 | GitPython = ">=1.0.1" 87 | PyYAML = ">=5.3.1" 88 | stevedore = ">=1.20.0" 89 | 90 | [package.extras] 91 | test = ["coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml", "beautifulsoup4 (>=4.8.0)", "pylint (==1.9.4)"] 92 | toml = ["toml"] 93 | yaml = ["pyyaml"] 94 | 95 | [[package]] 96 | name = "certifi" 97 | version = "2021.10.8" 98 | description = "Python package for providing Mozilla's CA Bundle." 99 | category = "main" 100 | optional = false 101 | python-versions = "*" 102 | 103 | [[package]] 104 | name = "charset-normalizer" 105 | version = "2.0.12" 106 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 107 | category = "main" 108 | optional = false 109 | python-versions = ">=3.5.0" 110 | 111 | [package.extras] 112 | unicode_backport = ["unicodedata2"] 113 | 114 | [[package]] 115 | name = "colorama" 116 | version = "0.4.4" 117 | description = "Cross-platform colored terminal text." 118 | category = "dev" 119 | optional = false 120 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 121 | 122 | [[package]] 123 | name = "darglint" 124 | version = "1.8.1" 125 | description = "A utility for ensuring Google-style docstrings stay up to date with the source code." 126 | category = "dev" 127 | optional = false 128 | python-versions = ">=3.6,<4.0" 129 | 130 | [[package]] 131 | name = "decorator" 132 | version = "5.1.1" 133 | description = "Decorators for Humans" 134 | category = "dev" 135 | optional = false 136 | python-versions = ">=3.5" 137 | 138 | [[package]] 139 | name = "docutils" 140 | version = "0.18.1" 141 | description = "Docutils -- Python Documentation Utilities" 142 | category = "dev" 143 | optional = false 144 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 145 | 146 | [[package]] 147 | name = "entrypoints" 148 | version = "0.4" 149 | description = "Discover and load entry points from installed packages." 150 | category = "dev" 151 | optional = false 152 | python-versions = ">=3.6" 153 | 154 | [[package]] 155 | name = "eradicate" 156 | version = "2.0.0" 157 | description = "Removes commented-out code." 158 | category = "dev" 159 | optional = false 160 | python-versions = "*" 161 | 162 | [[package]] 163 | name = "executing" 164 | version = "0.8.3" 165 | description = "Get the currently executing AST node of a frame, and other information" 166 | category = "dev" 167 | optional = false 168 | python-versions = "*" 169 | 170 | [[package]] 171 | name = "flake8" 172 | version = "4.0.1" 173 | description = "the modular source code checker: pep8 pyflakes and co" 174 | category = "dev" 175 | optional = false 176 | python-versions = ">=3.6" 177 | 178 | [package.dependencies] 179 | importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} 180 | mccabe = ">=0.6.0,<0.7.0" 181 | pycodestyle = ">=2.8.0,<2.9.0" 182 | pyflakes = ">=2.4.0,<2.5.0" 183 | 184 | [[package]] 185 | name = "flake8-bandit" 186 | version = "3.0.0" 187 | description = "Automated security testing with bandit and flake8." 188 | category = "dev" 189 | optional = false 190 | python-versions = ">=3.6" 191 | 192 | [package.dependencies] 193 | bandit = ">=1.7.3" 194 | flake8 = "*" 195 | flake8-polyfill = "*" 196 | pycodestyle = "*" 197 | 198 | [[package]] 199 | name = "flake8-broken-line" 200 | version = "0.4.0" 201 | description = "Flake8 plugin to forbid backslashes for line breaks" 202 | category = "dev" 203 | optional = false 204 | python-versions = ">=3.6,<4.0" 205 | 206 | [package.dependencies] 207 | flake8 = ">=3.5,<5" 208 | 209 | [[package]] 210 | name = "flake8-bugbear" 211 | version = "22.3.23" 212 | description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." 213 | category = "dev" 214 | optional = false 215 | python-versions = ">=3.6" 216 | 217 | [package.dependencies] 218 | attrs = ">=19.2.0" 219 | flake8 = ">=3.0.0" 220 | 221 | [package.extras] 222 | dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit"] 223 | 224 | [[package]] 225 | name = "flake8-commas" 226 | version = "2.1.0" 227 | description = "Flake8 lint for trailing commas." 228 | category = "dev" 229 | optional = false 230 | python-versions = "*" 231 | 232 | [package.dependencies] 233 | flake8 = ">=2" 234 | 235 | [[package]] 236 | name = "flake8-comprehensions" 237 | version = "3.8.0" 238 | description = "A flake8 plugin to help you write better list/set/dict comprehensions." 239 | category = "dev" 240 | optional = false 241 | python-versions = ">=3.7" 242 | 243 | [package.dependencies] 244 | flake8 = ">=3.0,<3.2.0 || >3.2.0" 245 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 246 | 247 | [[package]] 248 | name = "flake8-debugger" 249 | version = "4.0.0" 250 | description = "ipdb/pdb statement checker plugin for flake8" 251 | category = "dev" 252 | optional = false 253 | python-versions = ">=3.6" 254 | 255 | [package.dependencies] 256 | flake8 = ">=3.0" 257 | pycodestyle = "*" 258 | six = "*" 259 | 260 | [[package]] 261 | name = "flake8-docstrings" 262 | version = "1.6.0" 263 | description = "Extension for flake8 which uses pydocstyle to check docstrings" 264 | category = "dev" 265 | optional = false 266 | python-versions = "*" 267 | 268 | [package.dependencies] 269 | flake8 = ">=3" 270 | pydocstyle = ">=2.1" 271 | 272 | [[package]] 273 | name = "flake8-eradicate" 274 | version = "1.2.0" 275 | description = "Flake8 plugin to find commented out code" 276 | category = "dev" 277 | optional = false 278 | python-versions = ">=3.6,<4.0" 279 | 280 | [package.dependencies] 281 | attrs = "*" 282 | eradicate = ">=2.0,<3.0" 283 | flake8 = ">=3.5,<5" 284 | 285 | [[package]] 286 | name = "flake8-isort" 287 | version = "4.1.1" 288 | description = "flake8 plugin that integrates isort ." 289 | category = "dev" 290 | optional = false 291 | python-versions = "*" 292 | 293 | [package.dependencies] 294 | flake8 = ">=3.2.1,<5" 295 | isort = ">=4.3.5,<6" 296 | testfixtures = ">=6.8.0,<7" 297 | 298 | [package.extras] 299 | test = ["pytest-cov"] 300 | 301 | [[package]] 302 | name = "flake8-polyfill" 303 | version = "1.0.2" 304 | description = "Polyfill package for Flake8 plugins" 305 | category = "dev" 306 | optional = false 307 | python-versions = "*" 308 | 309 | [package.dependencies] 310 | flake8 = "*" 311 | 312 | [[package]] 313 | name = "flake8-quotes" 314 | version = "3.3.1" 315 | description = "Flake8 lint for quotes." 316 | category = "dev" 317 | optional = false 318 | python-versions = "*" 319 | 320 | [package.dependencies] 321 | flake8 = "*" 322 | 323 | [[package]] 324 | name = "flake8-rst-docstrings" 325 | version = "0.2.5" 326 | description = "Python docstring reStructuredText (RST) validator" 327 | category = "dev" 328 | optional = false 329 | python-versions = ">=3.6" 330 | 331 | [package.dependencies] 332 | flake8 = ">=3.0.0" 333 | pygments = "*" 334 | restructuredtext-lint = "*" 335 | 336 | [[package]] 337 | name = "flake8-string-format" 338 | version = "0.3.0" 339 | description = "string format checker, plugin for flake8" 340 | category = "dev" 341 | optional = false 342 | python-versions = "*" 343 | 344 | [package.dependencies] 345 | flake8 = "*" 346 | 347 | [[package]] 348 | name = "flakeheaven" 349 | version = "0.11.1" 350 | description = "FlakeHeaven is a [Flake8](https://gitlab.com/pycqa/flake8) wrapper to make it cool." 351 | category = "dev" 352 | optional = false 353 | python-versions = ">=3.6.2,<4.0.0" 354 | 355 | [package.dependencies] 356 | colorama = "*" 357 | entrypoints = "*" 358 | flake8 = ">=4.0.1,<5.0.0" 359 | importlib-metadata = {version = ">=1.0,<2.0", markers = "python_version < \"3.8\""} 360 | pygments = "*" 361 | toml = "*" 362 | urllib3 = "*" 363 | 364 | [package.extras] 365 | docs = ["alabaster", "pygments-github-lexers", "recommonmark", "sphinx"] 366 | 367 | [[package]] 368 | name = "gitdb" 369 | version = "4.0.9" 370 | description = "Git Object Database" 371 | category = "dev" 372 | optional = false 373 | python-versions = ">=3.6" 374 | 375 | [package.dependencies] 376 | smmap = ">=3.0.1,<6" 377 | 378 | [[package]] 379 | name = "gitpython" 380 | version = "3.1.27" 381 | description = "GitPython is a python library used to interact with Git repositories" 382 | category = "dev" 383 | optional = false 384 | python-versions = ">=3.7" 385 | 386 | [package.dependencies] 387 | gitdb = ">=4.0.1,<5" 388 | typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} 389 | 390 | [[package]] 391 | name = "idna" 392 | version = "3.3" 393 | description = "Internationalized Domain Names in Applications (IDNA)" 394 | category = "main" 395 | optional = false 396 | python-versions = ">=3.5" 397 | 398 | [[package]] 399 | name = "importlib-metadata" 400 | version = "1.7.0" 401 | description = "Read metadata from Python packages" 402 | category = "dev" 403 | optional = false 404 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 405 | 406 | [package.dependencies] 407 | zipp = ">=0.5" 408 | 409 | [package.extras] 410 | docs = ["sphinx", "rst.linker"] 411 | testing = ["packaging", "pep517", "importlib-resources (>=1.3)"] 412 | 413 | [[package]] 414 | name = "ipython" 415 | version = "8.2.0" 416 | description = "IPython: Productive Interactive Computing" 417 | category = "dev" 418 | optional = false 419 | python-versions = ">=3.8" 420 | 421 | [package.dependencies] 422 | appnope = {version = "*", markers = "sys_platform == \"darwin\""} 423 | backcall = "*" 424 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 425 | decorator = "*" 426 | jedi = ">=0.16" 427 | matplotlib-inline = "*" 428 | pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} 429 | pickleshare = "*" 430 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 431 | pygments = ">=2.4.0" 432 | stack-data = "*" 433 | traitlets = ">=5" 434 | 435 | [package.extras] 436 | all = ["black", "Sphinx (>=1.3)", "ipykernel", "nbconvert", "nbformat", "ipywidgets", "notebook", "ipyparallel", "qtconsole", "pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "numpy (>=1.19)", "pandas", "trio"] 437 | black = ["black"] 438 | doc = ["Sphinx (>=1.3)"] 439 | kernel = ["ipykernel"] 440 | nbconvert = ["nbconvert"] 441 | nbformat = ["nbformat"] 442 | notebook = ["ipywidgets", "notebook"] 443 | parallel = ["ipyparallel"] 444 | qtconsole = ["qtconsole"] 445 | test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] 446 | test_extra = ["pytest (<7.1)", "pytest-asyncio", "testpath", "curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "trio"] 447 | 448 | [[package]] 449 | name = "isort" 450 | version = "5.10.1" 451 | description = "A Python utility / library to sort Python imports." 452 | category = "dev" 453 | optional = false 454 | python-versions = ">=3.6.1,<4.0" 455 | 456 | [package.extras] 457 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 458 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 459 | colors = ["colorama (>=0.4.3,<0.5.0)"] 460 | plugins = ["setuptools"] 461 | 462 | [[package]] 463 | name = "jedi" 464 | version = "0.18.1" 465 | description = "An autocompletion tool for Python that can be used for text editors." 466 | category = "dev" 467 | optional = false 468 | python-versions = ">=3.6" 469 | 470 | [package.dependencies] 471 | parso = ">=0.8.0,<0.9.0" 472 | 473 | [package.extras] 474 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] 475 | testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] 476 | 477 | [[package]] 478 | name = "matplotlib-inline" 479 | version = "0.1.3" 480 | description = "Inline Matplotlib backend for Jupyter" 481 | category = "dev" 482 | optional = false 483 | python-versions = ">=3.5" 484 | 485 | [package.dependencies] 486 | traitlets = "*" 487 | 488 | [[package]] 489 | name = "mccabe" 490 | version = "0.6.1" 491 | description = "McCabe checker, plugin for flake8" 492 | category = "dev" 493 | optional = false 494 | python-versions = "*" 495 | 496 | [[package]] 497 | name = "mypy" 498 | version = "0.942" 499 | description = "Optional static typing for Python" 500 | category = "dev" 501 | optional = false 502 | python-versions = ">=3.6" 503 | 504 | [package.dependencies] 505 | mypy-extensions = ">=0.4.3" 506 | tomli = ">=1.1.0" 507 | typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} 508 | typing-extensions = ">=3.10" 509 | 510 | [package.extras] 511 | dmypy = ["psutil (>=4.0)"] 512 | python2 = ["typed-ast (>=1.4.0,<2)"] 513 | reports = ["lxml"] 514 | 515 | [[package]] 516 | name = "mypy-extensions" 517 | version = "0.4.3" 518 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 519 | category = "dev" 520 | optional = false 521 | python-versions = "*" 522 | 523 | [[package]] 524 | name = "numpy" 525 | version = "1.21.1" 526 | description = "NumPy is the fundamental package for array computing with Python." 527 | category = "main" 528 | optional = false 529 | python-versions = ">=3.7" 530 | 531 | [[package]] 532 | name = "parso" 533 | version = "0.8.3" 534 | description = "A Python Parser" 535 | category = "dev" 536 | optional = false 537 | python-versions = ">=3.6" 538 | 539 | [package.extras] 540 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] 541 | testing = ["docopt", "pytest (<6.0.0)"] 542 | 543 | [[package]] 544 | name = "pbr" 545 | version = "5.8.1" 546 | description = "Python Build Reasonableness" 547 | category = "dev" 548 | optional = false 549 | python-versions = ">=2.6" 550 | 551 | [[package]] 552 | name = "pep8-naming" 553 | version = "0.12.1" 554 | description = "Check PEP-8 naming conventions, plugin for flake8" 555 | category = "dev" 556 | optional = false 557 | python-versions = "*" 558 | 559 | [package.dependencies] 560 | flake8 = ">=3.9.1" 561 | flake8-polyfill = ">=1.0.2,<2" 562 | 563 | [[package]] 564 | name = "pexpect" 565 | version = "4.8.0" 566 | description = "Pexpect allows easy control of interactive console applications." 567 | category = "dev" 568 | optional = false 569 | python-versions = "*" 570 | 571 | [package.dependencies] 572 | ptyprocess = ">=0.5" 573 | 574 | [[package]] 575 | name = "pickleshare" 576 | version = "0.7.5" 577 | description = "Tiny 'shelve'-like database with concurrency support" 578 | category = "dev" 579 | optional = false 580 | python-versions = "*" 581 | 582 | [[package]] 583 | name = "prompt-toolkit" 584 | version = "3.0.29" 585 | description = "Library for building powerful interactive command lines in Python" 586 | category = "dev" 587 | optional = false 588 | python-versions = ">=3.6.2" 589 | 590 | [package.dependencies] 591 | wcwidth = "*" 592 | 593 | [[package]] 594 | name = "ptyprocess" 595 | version = "0.7.0" 596 | description = "Run a subprocess in a pseudo terminal" 597 | category = "dev" 598 | optional = false 599 | python-versions = "*" 600 | 601 | [[package]] 602 | name = "pure-eval" 603 | version = "0.2.2" 604 | description = "Safely evaluate AST nodes without side effects" 605 | category = "dev" 606 | optional = false 607 | python-versions = "*" 608 | 609 | [package.extras] 610 | tests = ["pytest"] 611 | 612 | [[package]] 613 | name = "pycodestyle" 614 | version = "2.8.0" 615 | description = "Python style guide checker" 616 | category = "dev" 617 | optional = false 618 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 619 | 620 | [[package]] 621 | name = "pydocstyle" 622 | version = "6.1.1" 623 | description = "Python docstring style checker" 624 | category = "dev" 625 | optional = false 626 | python-versions = ">=3.6" 627 | 628 | [package.dependencies] 629 | snowballstemmer = "*" 630 | 631 | [package.extras] 632 | toml = ["toml"] 633 | 634 | [[package]] 635 | name = "pydub" 636 | version = "0.25.1" 637 | description = "Manipulate audio with an simple and easy high level interface" 638 | category = "main" 639 | optional = false 640 | python-versions = "*" 641 | 642 | [[package]] 643 | name = "pydub-stubs" 644 | version = "0.25.1.0" 645 | description = "Stub-only package containing type information for pydub" 646 | category = "dev" 647 | optional = false 648 | python-versions = ">=3.8,<4.0" 649 | 650 | [[package]] 651 | name = "pyflakes" 652 | version = "2.4.0" 653 | description = "passive checker of Python programs" 654 | category = "dev" 655 | optional = false 656 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 657 | 658 | [[package]] 659 | name = "pygments" 660 | version = "2.11.2" 661 | description = "Pygments is a syntax highlighting package written in Python." 662 | category = "dev" 663 | optional = false 664 | python-versions = ">=3.5" 665 | 666 | [[package]] 667 | name = "pyyaml" 668 | version = "6.0" 669 | description = "YAML parser and emitter for Python" 670 | category = "dev" 671 | optional = false 672 | python-versions = ">=3.6" 673 | 674 | [[package]] 675 | name = "requests" 676 | version = "2.27.1" 677 | description = "Python HTTP for Humans." 678 | category = "main" 679 | optional = false 680 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 681 | 682 | [package.dependencies] 683 | certifi = ">=2017.4.17" 684 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} 685 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} 686 | urllib3 = ">=1.21.1,<1.27" 687 | 688 | [package.extras] 689 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 690 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] 691 | 692 | [[package]] 693 | name = "restructuredtext-lint" 694 | version = "1.4.0" 695 | description = "reStructuredText linter" 696 | category = "dev" 697 | optional = false 698 | python-versions = "*" 699 | 700 | [package.dependencies] 701 | docutils = ">=0.11,<1.0" 702 | 703 | [[package]] 704 | name = "rope" 705 | version = "0.23.0" 706 | description = "a python refactoring library..." 707 | category = "dev" 708 | optional = false 709 | python-versions = "*" 710 | 711 | [package.extras] 712 | dev = ["build", "pytest", "pytest-timeout"] 713 | 714 | [[package]] 715 | name = "six" 716 | version = "1.16.0" 717 | description = "Python 2 and 3 compatibility utilities" 718 | category = "dev" 719 | optional = false 720 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 721 | 722 | [[package]] 723 | name = "smmap" 724 | version = "5.0.0" 725 | description = "A pure Python implementation of a sliding window memory map manager" 726 | category = "dev" 727 | optional = false 728 | python-versions = ">=3.6" 729 | 730 | [[package]] 731 | name = "snowballstemmer" 732 | version = "2.2.0" 733 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." 734 | category = "dev" 735 | optional = false 736 | python-versions = "*" 737 | 738 | [[package]] 739 | name = "stack-data" 740 | version = "0.2.0" 741 | description = "Extract data from python stack frames and tracebacks for informative displays" 742 | category = "dev" 743 | optional = false 744 | python-versions = "*" 745 | 746 | [package.dependencies] 747 | asttokens = "*" 748 | executing = "*" 749 | pure-eval = "*" 750 | 751 | [package.extras] 752 | tests = ["pytest", "typeguard", "pygments", "littleutils", "cython"] 753 | 754 | [[package]] 755 | name = "stevedore" 756 | version = "3.5.0" 757 | description = "Manage dynamic plugins for Python applications" 758 | category = "dev" 759 | optional = false 760 | python-versions = ">=3.6" 761 | 762 | [package.dependencies] 763 | importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} 764 | pbr = ">=2.0.0,<2.1.0 || >2.1.0" 765 | 766 | [[package]] 767 | name = "testfixtures" 768 | version = "6.18.5" 769 | description = "A collection of helpers and mock objects for unit tests and doc tests." 770 | category = "dev" 771 | optional = false 772 | python-versions = "*" 773 | 774 | [package.extras] 775 | build = ["setuptools-git", "wheel", "twine"] 776 | docs = ["sphinx", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"] 777 | test = ["pytest (>=3.6)", "pytest-cov", "pytest-django", "zope.component", "sybil", "twisted", "mock", "django (<2)", "django"] 778 | 779 | [[package]] 780 | name = "tokenize-rt" 781 | version = "4.2.1" 782 | description = "A wrapper around the stdlib `tokenize` which roundtrips." 783 | category = "dev" 784 | optional = false 785 | python-versions = ">=3.6.1" 786 | 787 | [[package]] 788 | name = "toml" 789 | version = "0.10.2" 790 | description = "Python Library for Tom's Obvious, Minimal Language" 791 | category = "dev" 792 | optional = false 793 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 794 | 795 | [[package]] 796 | name = "tomli" 797 | version = "2.0.1" 798 | description = "A lil' TOML parser" 799 | category = "dev" 800 | optional = false 801 | python-versions = ">=3.7" 802 | 803 | [[package]] 804 | name = "traitlets" 805 | version = "5.1.1" 806 | description = "Traitlets Python configuration system" 807 | category = "dev" 808 | optional = false 809 | python-versions = ">=3.7" 810 | 811 | [package.extras] 812 | test = ["pytest"] 813 | 814 | [[package]] 815 | name = "typed-ast" 816 | version = "1.5.2" 817 | description = "a fork of Python 2 and 3 ast modules with type comment support" 818 | category = "dev" 819 | optional = false 820 | python-versions = ">=3.6" 821 | 822 | [[package]] 823 | name = "typing-extensions" 824 | version = "4.1.1" 825 | description = "Backported and Experimental Type Hints for Python 3.6+" 826 | category = "dev" 827 | optional = false 828 | python-versions = ">=3.6" 829 | 830 | [[package]] 831 | name = "urllib3" 832 | version = "1.26.9" 833 | description = "HTTP library with thread-safe connection pooling, file post, and more." 834 | category = "main" 835 | optional = false 836 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 837 | 838 | [package.extras] 839 | brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] 840 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 841 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 842 | 843 | [[package]] 844 | name = "wcwidth" 845 | version = "0.2.5" 846 | description = "Measures the displayed width of unicode strings in a terminal" 847 | category = "dev" 848 | optional = false 849 | python-versions = "*" 850 | 851 | [[package]] 852 | name = "wemake-python-styleguide" 853 | version = "0.16.1" 854 | description = "The strictest and most opinionated python linter ever" 855 | category = "dev" 856 | optional = false 857 | python-versions = ">=3.6,<4.0" 858 | 859 | [package.dependencies] 860 | astor = ">=0.8,<0.9" 861 | attrs = "*" 862 | darglint = ">=1.2,<2.0" 863 | flake8 = ">=3.7,<5" 864 | flake8-bandit = ">=2.1,<4" 865 | flake8-broken-line = ">=0.3,<0.5" 866 | flake8-bugbear = ">=20.1,<23.0" 867 | flake8-commas = ">=2.0,<3.0" 868 | flake8-comprehensions = ">=3.1,<4.0" 869 | flake8-debugger = ">=4.0,<5.0" 870 | flake8-docstrings = ">=1.3,<2.0" 871 | flake8-eradicate = ">=1.0,<2.0" 872 | flake8-isort = ">=4.0,<5.0" 873 | flake8-quotes = ">=3.0,<4.0" 874 | flake8-rst-docstrings = ">=0.2,<0.3" 875 | flake8-string-format = ">=0.3,<0.4" 876 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 877 | pep8-naming = ">=0.11,<0.13" 878 | pygments = ">=2.4,<3.0" 879 | typing_extensions = ">=3.6,<5.0" 880 | 881 | [[package]] 882 | name = "zipp" 883 | version = "3.8.0" 884 | description = "Backport of pathlib-compatible object wrapper for zip files" 885 | category = "dev" 886 | optional = false 887 | python-versions = ">=3.7" 888 | 889 | [package.extras] 890 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 891 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] 892 | 893 | [metadata] 894 | lock-version = "1.1" 895 | python-versions = "^3.7" 896 | content-hash = "1298f5719cdc9c909cb1ba110c0878fe7c58cf895b651ff8c0423699e885056e" 897 | 898 | [metadata.files] 899 | add-trailing-comma = [ 900 | {file = "add_trailing_comma-2.2.2-py2.py3-none-any.whl", hash = "sha256:a6b4f97ee4b528763392d98d872b24b3347cf2039a33926235b0c2ae0ae00c25"}, 901 | {file = "add_trailing_comma-2.2.2.tar.gz", hash = "sha256:6b3e91a87a572d263c8bc85898902a2ecc1773f432da28d1ceaf507ea20dbf0e"}, 902 | ] 903 | appnope = [ 904 | {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, 905 | {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, 906 | ] 907 | astor = [ 908 | {file = "astor-0.8.1-py2.py3-none-any.whl", hash = "sha256:070a54e890cefb5b3739d19f30f5a5ec840ffc9c50ffa7d23cc9fc1a38ebbfc5"}, 909 | {file = "astor-0.8.1.tar.gz", hash = "sha256:6a6effda93f4e1ce9f618779b2dd1d9d84f1e32812c23a29b3fff6fd7f63fa5e"}, 910 | ] 911 | asttokens = [ 912 | {file = "asttokens-2.0.5-py2.py3-none-any.whl", hash = "sha256:0844691e88552595a6f4a4281a9f7f79b8dd45ca4ccea82e5e05b4bbdb76705c"}, 913 | {file = "asttokens-2.0.5.tar.gz", hash = "sha256:9a54c114f02c7a9480d56550932546a3f1fe71d8a02f1bc7ccd0ee3ee35cf4d5"}, 914 | ] 915 | attrs = [ 916 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 917 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 918 | ] 919 | autopep8 = [ 920 | {file = "autopep8-1.6.0-py2.py3-none-any.whl", hash = "sha256:ed77137193bbac52d029a52c59bec1b0629b5a186c495f1eb21b126ac466083f"}, 921 | {file = "autopep8-1.6.0.tar.gz", hash = "sha256:44f0932855039d2c15c4510d6df665e4730f2b8582704fa48f9c55bd3e17d979"}, 922 | ] 923 | backcall = [ 924 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 925 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 926 | ] 927 | bandit = [ 928 | {file = "bandit-1.7.4-py3-none-any.whl", hash = "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"}, 929 | {file = "bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"}, 930 | ] 931 | certifi = [ 932 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, 933 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, 934 | ] 935 | charset-normalizer = [ 936 | {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, 937 | {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, 938 | ] 939 | colorama = [ 940 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 941 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 942 | ] 943 | darglint = [ 944 | {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, 945 | {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, 946 | ] 947 | decorator = [ 948 | {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, 949 | {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, 950 | ] 951 | docutils = [ 952 | {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, 953 | {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, 954 | ] 955 | entrypoints = [ 956 | {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"}, 957 | {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, 958 | ] 959 | eradicate = [ 960 | {file = "eradicate-2.0.0.tar.gz", hash = "sha256:27434596f2c5314cc9b31410c93d8f7e8885747399773cd088d3adea647a60c8"}, 961 | ] 962 | executing = [ 963 | {file = "executing-0.8.3-py2.py3-none-any.whl", hash = "sha256:d1eef132db1b83649a3905ca6dd8897f71ac6f8cac79a7e58a1a09cf137546c9"}, 964 | {file = "executing-0.8.3.tar.gz", hash = "sha256:c6554e21c6b060590a6d3be4b82fb78f8f0194d809de5ea7df1c093763311501"}, 965 | ] 966 | flake8 = [ 967 | {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, 968 | {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, 969 | ] 970 | flake8-bandit = [ 971 | {file = "flake8_bandit-3.0.0-py2.py3-none-any.whl", hash = "sha256:61b617f4f7cdaa0e2b1e6bf7b68afb2b619a227bb3e3ae00dd36c213bd17900a"}, 972 | {file = "flake8_bandit-3.0.0.tar.gz", hash = "sha256:54d19427e6a8d50322a7b02e1841c0a7c22d856975f3459803320e0e18e2d6a1"}, 973 | ] 974 | flake8-broken-line = [ 975 | {file = "flake8-broken-line-0.4.0.tar.gz", hash = "sha256:771aab5aa0997666796fed249d0e48e6c01cdfeca8c95521eea28a38b7ced4c7"}, 976 | {file = "flake8_broken_line-0.4.0-py3-none-any.whl", hash = "sha256:e9c522856862239a2c7ef2c1de0276fa598572aa864bd4e9c7efc2a827538515"}, 977 | ] 978 | flake8-bugbear = [ 979 | {file = "flake8-bugbear-22.3.23.tar.gz", hash = "sha256:e0dc2a36474490d5b1a2d57f9e4ef570abc09f07cbb712b29802e28a2367ff19"}, 980 | {file = "flake8_bugbear-22.3.23-py3-none-any.whl", hash = "sha256:ec5ec92195720cee1589315416b844ffa5e82f73a78e65329e8055322df1e939"}, 981 | ] 982 | flake8-commas = [ 983 | {file = "flake8-commas-2.1.0.tar.gz", hash = "sha256:940441ab8ee544df564ae3b3f49f20462d75d5c7cac2463e0b27436e2050f263"}, 984 | {file = "flake8_commas-2.1.0-py2.py3-none-any.whl", hash = "sha256:ebb96c31e01d0ef1d0685a21f3f0e2f8153a0381430e748bf0bbbb5d5b453d54"}, 985 | ] 986 | flake8-comprehensions = [ 987 | {file = "flake8-comprehensions-3.8.0.tar.gz", hash = "sha256:8e108707637b1d13734f38e03435984f6b7854fa6b5a4e34f93e69534be8e521"}, 988 | {file = "flake8_comprehensions-3.8.0-py3-none-any.whl", hash = "sha256:9406314803abe1193c064544ab14fdc43c58424c0882f6ff8a581eb73fc9bb58"}, 989 | ] 990 | flake8-debugger = [ 991 | {file = "flake8-debugger-4.0.0.tar.gz", hash = "sha256:e43dc777f7db1481db473210101ec2df2bd39a45b149d7218a618e954177eda6"}, 992 | {file = "flake8_debugger-4.0.0-py3-none-any.whl", hash = "sha256:82e64faa72e18d1bdd0000407502ebb8ecffa7bc027c62b9d4110ce27c091032"}, 993 | ] 994 | flake8-docstrings = [ 995 | {file = "flake8-docstrings-1.6.0.tar.gz", hash = "sha256:9fe7c6a306064af8e62a055c2f61e9eb1da55f84bb39caef2b84ce53708ac34b"}, 996 | {file = "flake8_docstrings-1.6.0-py2.py3-none-any.whl", hash = "sha256:99cac583d6c7e32dd28bbfbef120a7c0d1b6dde4adb5a9fd441c4227a6534bde"}, 997 | ] 998 | flake8-eradicate = [ 999 | {file = "flake8-eradicate-1.2.0.tar.gz", hash = "sha256:acaa1b6839ff00d284b805c432fdfa6047262bd15a5504ec945797e87b4de1fa"}, 1000 | {file = "flake8_eradicate-1.2.0-py3-none-any.whl", hash = "sha256:51dc660d0c1c1ed93af0f813540bbbf72ab2d3466c14e3f3bac371c618b6042f"}, 1001 | ] 1002 | flake8-isort = [ 1003 | {file = "flake8-isort-4.1.1.tar.gz", hash = "sha256:d814304ab70e6e58859bc5c3e221e2e6e71c958e7005239202fee19c24f82717"}, 1004 | {file = "flake8_isort-4.1.1-py3-none-any.whl", hash = "sha256:c4e8b6dcb7be9b71a02e6e5d4196cefcef0f3447be51e82730fb336fff164949"}, 1005 | ] 1006 | flake8-polyfill = [ 1007 | {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, 1008 | {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, 1009 | ] 1010 | flake8-quotes = [ 1011 | {file = "flake8-quotes-3.3.1.tar.gz", hash = "sha256:633adca6fb8a08131536af0d750b44d6985b9aba46f498871e21588c3e6f525a"}, 1012 | ] 1013 | flake8-rst-docstrings = [ 1014 | {file = "flake8-rst-docstrings-0.2.5.tar.gz", hash = "sha256:4fe93f997dea45d9d3c8bd220f12f0b6c359948fb943b5b48021a3f927edd816"}, 1015 | {file = "flake8_rst_docstrings-0.2.5-py3-none-any.whl", hash = "sha256:b99d9041b769b857efe45a448dc8c71b1bb311f9cacbdac5de82f96498105082"}, 1016 | ] 1017 | flake8-string-format = [ 1018 | {file = "flake8-string-format-0.3.0.tar.gz", hash = "sha256:65f3da786a1461ef77fca3780b314edb2853c377f2e35069723348c8917deaa2"}, 1019 | {file = "flake8_string_format-0.3.0-py2.py3-none-any.whl", hash = "sha256:812ff431f10576a74c89be4e85b8e075a705be39bc40c4b4278b5b13e2afa9af"}, 1020 | ] 1021 | flakeheaven = [ 1022 | {file = "flakeheaven-0.11.1-py3-none-any.whl", hash = "sha256:5614165d95bf26c46af22db4e0ccc90e475c286f902c473de16730fad4735e5d"}, 1023 | {file = "flakeheaven-0.11.1.tar.gz", hash = "sha256:20f5a573b8e85be5a73fed2e3967f7ab8b5e77714a5898d8b634dd31a0b75f9b"}, 1024 | ] 1025 | gitdb = [ 1026 | {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, 1027 | {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, 1028 | ] 1029 | gitpython = [ 1030 | {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, 1031 | {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, 1032 | ] 1033 | idna = [ 1034 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 1035 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 1036 | ] 1037 | importlib-metadata = [ 1038 | {file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"}, 1039 | {file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"}, 1040 | ] 1041 | ipython = [ 1042 | {file = "ipython-8.2.0-py3-none-any.whl", hash = "sha256:1b672bfd7a48d87ab203d9af8727a3b0174a4566b4091e9447c22fb63ea32857"}, 1043 | {file = "ipython-8.2.0.tar.gz", hash = "sha256:70e5eb132cac594a34b5f799bd252589009905f05104728aea6a403ec2519dc1"}, 1044 | ] 1045 | isort = [ 1046 | {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, 1047 | {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, 1048 | ] 1049 | jedi = [ 1050 | {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, 1051 | {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, 1052 | ] 1053 | matplotlib-inline = [ 1054 | {file = "matplotlib-inline-0.1.3.tar.gz", hash = "sha256:a04bfba22e0d1395479f866853ec1ee28eea1485c1d69a6faf00dc3e24ff34ee"}, 1055 | {file = "matplotlib_inline-0.1.3-py3-none-any.whl", hash = "sha256:aed605ba3b72462d64d475a21a9296f400a19c4f74a31b59103d2a99ffd5aa5c"}, 1056 | ] 1057 | mccabe = [ 1058 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 1059 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 1060 | ] 1061 | mypy = [ 1062 | {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, 1063 | {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, 1064 | {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, 1065 | {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, 1066 | {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, 1067 | {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, 1068 | {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, 1069 | {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, 1070 | {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, 1071 | {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, 1072 | {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, 1073 | {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, 1074 | {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, 1075 | {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, 1076 | {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, 1077 | {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, 1078 | {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, 1079 | {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, 1080 | {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, 1081 | {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, 1082 | {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, 1083 | {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, 1084 | {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, 1085 | ] 1086 | mypy-extensions = [ 1087 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 1088 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 1089 | ] 1090 | numpy = [ 1091 | {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"}, 1092 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"}, 1093 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"}, 1094 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"}, 1095 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"}, 1096 | {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"}, 1097 | {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"}, 1098 | {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"}, 1099 | {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"}, 1100 | {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"}, 1101 | {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"}, 1102 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"}, 1103 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"}, 1104 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"}, 1105 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"}, 1106 | {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"}, 1107 | {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"}, 1108 | {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"}, 1109 | {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"}, 1110 | {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"}, 1111 | {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"}, 1112 | {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"}, 1113 | {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"}, 1114 | {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"}, 1115 | {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"}, 1116 | {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"}, 1117 | {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"}, 1118 | {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"}, 1119 | ] 1120 | parso = [ 1121 | {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, 1122 | {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, 1123 | ] 1124 | pbr = [ 1125 | {file = "pbr-5.8.1-py2.py3-none-any.whl", hash = "sha256:27108648368782d07bbf1cb468ad2e2eeef29086affd14087a6d04b7de8af4ec"}, 1126 | {file = "pbr-5.8.1.tar.gz", hash = "sha256:66bc5a34912f408bb3925bf21231cb6f59206267b7f63f3503ef865c1a292e25"}, 1127 | ] 1128 | pep8-naming = [ 1129 | {file = "pep8-naming-0.12.1.tar.gz", hash = "sha256:bb2455947757d162aa4cad55dba4ce029005cd1692f2899a21d51d8630ca7841"}, 1130 | {file = "pep8_naming-0.12.1-py2.py3-none-any.whl", hash = "sha256:4a8daeaeb33cfcde779309fc0c9c0a68a3bbe2ad8a8308b763c5068f86eb9f37"}, 1131 | ] 1132 | pexpect = [ 1133 | {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, 1134 | {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, 1135 | ] 1136 | pickleshare = [ 1137 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 1138 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 1139 | ] 1140 | prompt-toolkit = [ 1141 | {file = "prompt_toolkit-3.0.29-py3-none-any.whl", hash = "sha256:62291dad495e665fca0bda814e342c69952086afb0f4094d0893d357e5c78752"}, 1142 | {file = "prompt_toolkit-3.0.29.tar.gz", hash = "sha256:bd640f60e8cecd74f0dc249713d433ace2ddc62b65ee07f96d358e0b152b6ea7"}, 1143 | ] 1144 | ptyprocess = [ 1145 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, 1146 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, 1147 | ] 1148 | pure-eval = [ 1149 | {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, 1150 | {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, 1151 | ] 1152 | pycodestyle = [ 1153 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 1154 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 1155 | ] 1156 | pydocstyle = [ 1157 | {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, 1158 | {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, 1159 | ] 1160 | pydub = [ 1161 | {file = "pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6"}, 1162 | {file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"}, 1163 | ] 1164 | pydub-stubs = [ 1165 | {file = "pydub-stubs-0.25.1.0.tar.gz", hash = "sha256:57ff97da5faf221eda0f508e62a187766342490e07a450c63a76ca7e970da4da"}, 1166 | {file = "pydub_stubs-0.25.1.0-py3-none-any.whl", hash = "sha256:7ff185c40838dfdfb344222ad6d962e99682fa2787557d771023af8c24593ddc"}, 1167 | ] 1168 | pyflakes = [ 1169 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 1170 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 1171 | ] 1172 | pygments = [ 1173 | {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, 1174 | {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, 1175 | ] 1176 | pyyaml = [ 1177 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 1178 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 1179 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 1180 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 1181 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 1182 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 1183 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 1184 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 1185 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 1186 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 1187 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 1188 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 1189 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 1190 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 1191 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 1192 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 1193 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 1194 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 1195 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 1196 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 1197 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 1198 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 1199 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 1200 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 1201 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 1202 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 1203 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 1204 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 1205 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 1206 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 1207 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 1208 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 1209 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 1210 | ] 1211 | requests = [ 1212 | {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, 1213 | {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, 1214 | ] 1215 | restructuredtext-lint = [ 1216 | {file = "restructuredtext_lint-1.4.0.tar.gz", hash = "sha256:1b235c0c922341ab6c530390892eb9e92f90b9b75046063e047cacfb0f050c45"}, 1217 | ] 1218 | rope = [ 1219 | {file = "rope-0.23.0-py3-none-any.whl", hash = "sha256:edf2ed3c9b35a8814752ffd3ea55b293c791e5087e252461de898e953cf9c146"}, 1220 | {file = "rope-0.23.0.tar.gz", hash = "sha256:f87662c565086d660fc855cc07f37820267876634c3e9e51bddb32ff51547268"}, 1221 | ] 1222 | six = [ 1223 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1224 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1225 | ] 1226 | smmap = [ 1227 | {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, 1228 | {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, 1229 | ] 1230 | snowballstemmer = [ 1231 | {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, 1232 | {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, 1233 | ] 1234 | stack-data = [ 1235 | {file = "stack_data-0.2.0-py3-none-any.whl", hash = "sha256:999762f9c3132308789affa03e9271bbbe947bf78311851f4d485d8402ed858e"}, 1236 | {file = "stack_data-0.2.0.tar.gz", hash = "sha256:45692d41bd633a9503a5195552df22b583caf16f0b27c4e58c98d88c8b648e12"}, 1237 | ] 1238 | stevedore = [ 1239 | {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, 1240 | {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, 1241 | ] 1242 | testfixtures = [ 1243 | {file = "testfixtures-6.18.5-py2.py3-none-any.whl", hash = "sha256:7de200e24f50a4a5d6da7019fb1197aaf5abd475efb2ec2422fdcf2f2eb98c1d"}, 1244 | {file = "testfixtures-6.18.5.tar.gz", hash = "sha256:02dae883f567f5b70fd3ad3c9eefb95912e78ac90be6c7444b5e2f46bf572c84"}, 1245 | ] 1246 | tokenize-rt = [ 1247 | {file = "tokenize_rt-4.2.1-py2.py3-none-any.whl", hash = "sha256:08a27fa032a81cf45e8858d0ac706004fcd523e8463415ddf1442be38e204ea8"}, 1248 | {file = "tokenize_rt-4.2.1.tar.gz", hash = "sha256:0d4f69026fed520f8a1e0103aa36c406ef4661417f20ca643f913e33531b3b94"}, 1249 | ] 1250 | toml = [ 1251 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1252 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1253 | ] 1254 | tomli = [ 1255 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1256 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1257 | ] 1258 | traitlets = [ 1259 | {file = "traitlets-5.1.1-py3-none-any.whl", hash = "sha256:2d313cc50a42cd6c277e7d7dc8d4d7fedd06a2c215f78766ae7b1a66277e0033"}, 1260 | {file = "traitlets-5.1.1.tar.gz", hash = "sha256:059f456c5a7c1c82b98c2e8c799f39c9b8128f6d0d46941ee118daace9eb70c7"}, 1261 | ] 1262 | typed-ast = [ 1263 | {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, 1264 | {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, 1265 | {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, 1266 | {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, 1267 | {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, 1268 | {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, 1269 | {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, 1270 | {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, 1271 | {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, 1272 | {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, 1273 | {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, 1274 | {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, 1275 | {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, 1276 | {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, 1277 | {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, 1278 | {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, 1279 | {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, 1280 | {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, 1281 | {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, 1282 | {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, 1283 | {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, 1284 | {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, 1285 | {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, 1286 | {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, 1287 | ] 1288 | typing-extensions = [ 1289 | {file = "typing_extensions-4.1.1-py3-none-any.whl", hash = "sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"}, 1290 | {file = "typing_extensions-4.1.1.tar.gz", hash = "sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42"}, 1291 | ] 1292 | urllib3 = [ 1293 | {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, 1294 | {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, 1295 | ] 1296 | wcwidth = [ 1297 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 1298 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 1299 | ] 1300 | wemake-python-styleguide = [ 1301 | {file = "wemake-python-styleguide-0.16.1.tar.gz", hash = "sha256:4fcd78dd55732679b5fc8bc37fd7e04bbaa5cdc1b1a829ad265e8f6b0d853cf6"}, 1302 | {file = "wemake_python_styleguide-0.16.1-py3-none-any.whl", hash = "sha256:202c22ecfee1f5caf0555048602cd52f2435cd57903e6b0cd46b5aaa3f652140"}, 1303 | ] 1304 | zipp = [ 1305 | {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, 1306 | {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, 1307 | ] 1308 | --------------------------------------------------------------------------------