mediacatch 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mediacatch might be problematic. Click here for more details.
- mediacatch/__init__.py +17 -0
- mediacatch/commands/__init__.py +13 -0
- mediacatch/commands/cli.py +31 -0
- mediacatch/commands/vision.py +157 -0
- mediacatch/utils/__init__.py +0 -0
- mediacatch/utils/general.py +38 -0
- mediacatch/vision/__init__.py +2 -0
- mediacatch/vision/result.py +49 -0
- mediacatch/vision/upload.py +57 -0
- mediacatch-0.0.1.dist-info/METADATA +56 -0
- mediacatch-0.0.1.dist-info/RECORD +13 -0
- mediacatch-0.0.1.dist-info/WHEEL +4 -0
- mediacatch-0.0.1.dist-info/entry_points.txt +2 -0
mediacatch/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from dotenv import load_dotenv
|
|
6
|
+
|
|
7
|
+
logging.basicConfig(
|
|
8
|
+
format='[%(asctime)s][%(levelname)s][%(name)s] %(message)s',
|
|
9
|
+
datefmt='%d/%m/%Y-%H:%M:%S',
|
|
10
|
+
handlers=[logging.StreamHandler(sys.stdout)],
|
|
11
|
+
level=logging.INFO,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
load_dotenv()
|
|
15
|
+
mediacatch_api_key = os.environ['MEDIACATCH-API-KEY']
|
|
16
|
+
|
|
17
|
+
__version__ = '0.0.1'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from argparse import ArgumentParser
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaseCLICommand(ABC):
|
|
6
|
+
@staticmethod
|
|
7
|
+
@abstractmethod
|
|
8
|
+
def register_subcommand(parser: ArgumentParser):
|
|
9
|
+
raise NotImplementedError()
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def run(self):
|
|
13
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
|
2
|
+
|
|
3
|
+
from mediacatch.commands.vision import VisionCLI
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main():
|
|
7
|
+
parser = ArgumentParser(
|
|
8
|
+
prog='MediaCatch CLI tool',
|
|
9
|
+
usage='mediacatch <command> [<args>]',
|
|
10
|
+
description='MediaCatch CLI tool',
|
|
11
|
+
formatter_class=ArgumentDefaultsHelpFormatter,
|
|
12
|
+
)
|
|
13
|
+
commands_parser = parser.add_subparsers(help='mediacatch command helpers')
|
|
14
|
+
|
|
15
|
+
# Register commands
|
|
16
|
+
VisionCLI.register_subcommand(commands_parser)
|
|
17
|
+
|
|
18
|
+
# Let's go
|
|
19
|
+
args = parser.parse_args()
|
|
20
|
+
|
|
21
|
+
if not hasattr(args, 'func'):
|
|
22
|
+
parser.print_help()
|
|
23
|
+
exit(1)
|
|
24
|
+
|
|
25
|
+
# Run
|
|
26
|
+
service = args.func(args)
|
|
27
|
+
service.run()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
if __name__ == '__main__':
|
|
31
|
+
main()
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from argparse import ArgumentParser
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from pprint import pprint
|
|
6
|
+
|
|
7
|
+
from mediacatch.commands import BaseCLICommand
|
|
8
|
+
from mediacatch.vision import upload, wait_for_result
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger('mediacatch.cli.vision')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def vision_cli_factory(args):
|
|
14
|
+
return VisionCLI(
|
|
15
|
+
file_path=args.file_path,
|
|
16
|
+
type=args.type,
|
|
17
|
+
save_result=args.save_result,
|
|
18
|
+
fps=args.fps,
|
|
19
|
+
tolerance=args.tolerance,
|
|
20
|
+
min_levensthein_ratio=args.min_levensthein_ratio,
|
|
21
|
+
min_bbox_iou=args.min_bbox_iou,
|
|
22
|
+
min_text_confidence=args.min_text_confidence,
|
|
23
|
+
max_text_confidence=args.max_text_confidence,
|
|
24
|
+
max_text_length=args.max_text_length,
|
|
25
|
+
moving_text_threshold=args.moving_text_threshold,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class VisionCLI(BaseCLICommand):
|
|
30
|
+
@staticmethod
|
|
31
|
+
def register_subcommand(parser: ArgumentParser):
|
|
32
|
+
vision_parser: ArgumentParser = parser.add_parser(
|
|
33
|
+
'vision', help='CLI tool to run inference with MediaCatch Vision API'
|
|
34
|
+
)
|
|
35
|
+
vision_parser.add_argument(
|
|
36
|
+
'file_path', type=str, help='Path to the file to run inference on'
|
|
37
|
+
)
|
|
38
|
+
vision_parser.add_argument(
|
|
39
|
+
'type',
|
|
40
|
+
type=str,
|
|
41
|
+
choices=['ocr', 'face'],
|
|
42
|
+
help='Type of inference to run on the file',
|
|
43
|
+
)
|
|
44
|
+
vision_parser.add_argument(
|
|
45
|
+
'--save-result',
|
|
46
|
+
type=str,
|
|
47
|
+
default=None,
|
|
48
|
+
help='Save result to a file',
|
|
49
|
+
)
|
|
50
|
+
vision_parser.add_argument(
|
|
51
|
+
'--fps',
|
|
52
|
+
type=int,
|
|
53
|
+
default=None,
|
|
54
|
+
help='FPS for the OCR results',
|
|
55
|
+
)
|
|
56
|
+
vision_parser.add_argument(
|
|
57
|
+
'--tolerance',
|
|
58
|
+
type=int,
|
|
59
|
+
default=None,
|
|
60
|
+
help='Tolerance for the OCR results',
|
|
61
|
+
)
|
|
62
|
+
vision_parser.add_argument(
|
|
63
|
+
'--min-levensthein-ratio',
|
|
64
|
+
type=float,
|
|
65
|
+
default=None,
|
|
66
|
+
help='Minimum Levenshtein ratio for the OCR results',
|
|
67
|
+
)
|
|
68
|
+
vision_parser.add_argument(
|
|
69
|
+
'--min-bbox-iou',
|
|
70
|
+
type=float,
|
|
71
|
+
default=None,
|
|
72
|
+
help='Minimum bounding box IOU for the OCR results',
|
|
73
|
+
)
|
|
74
|
+
vision_parser.add_argument(
|
|
75
|
+
'--min-text-confidence',
|
|
76
|
+
type=float,
|
|
77
|
+
default=None,
|
|
78
|
+
help='Minimum text confidence for the OCR results',
|
|
79
|
+
)
|
|
80
|
+
vision_parser.add_argument(
|
|
81
|
+
'--max-text-confidence',
|
|
82
|
+
type=float,
|
|
83
|
+
default=None,
|
|
84
|
+
help='Maximum text confidence for the OCR results',
|
|
85
|
+
)
|
|
86
|
+
vision_parser.add_argument(
|
|
87
|
+
'--max-text-length',
|
|
88
|
+
type=int,
|
|
89
|
+
default=None,
|
|
90
|
+
help='Maximum text length for the OCR results',
|
|
91
|
+
)
|
|
92
|
+
vision_parser.add_argument(
|
|
93
|
+
'--moving-text-threshold',
|
|
94
|
+
type=int,
|
|
95
|
+
default=None,
|
|
96
|
+
help='Moving text threshold for the OCR results',
|
|
97
|
+
)
|
|
98
|
+
vision_parser.set_defaults(func=vision_cli_factory)
|
|
99
|
+
|
|
100
|
+
def __init__(
|
|
101
|
+
self,
|
|
102
|
+
file_path: str,
|
|
103
|
+
type: str,
|
|
104
|
+
save_result: str | None = None,
|
|
105
|
+
fps: int | None = None,
|
|
106
|
+
tolerance: int | None = None,
|
|
107
|
+
min_levensthein_ratio: float | None = None,
|
|
108
|
+
min_bbox_iou: float | None = None,
|
|
109
|
+
min_text_confidence: float | None = None,
|
|
110
|
+
max_text_confidence: float | None = None,
|
|
111
|
+
max_text_length: int | None = None,
|
|
112
|
+
moving_text_threshold: int | None = None,
|
|
113
|
+
) -> None:
|
|
114
|
+
self.file_path = file_path
|
|
115
|
+
self.type = type
|
|
116
|
+
self.save_result = save_result
|
|
117
|
+
self.fps = fps
|
|
118
|
+
self.tolerance = tolerance
|
|
119
|
+
self.min_levensthein_ratio = min_levensthein_ratio
|
|
120
|
+
self.min_bbox_iou = min_bbox_iou
|
|
121
|
+
self.min_text_confidence = min_text_confidence
|
|
122
|
+
self.max_text_confidence = max_text_confidence
|
|
123
|
+
self.max_text_length = max_text_length
|
|
124
|
+
self.moving_text_threshold = moving_text_threshold
|
|
125
|
+
|
|
126
|
+
def run(self) -> None:
|
|
127
|
+
# Create extra parameters
|
|
128
|
+
extra = {}
|
|
129
|
+
for k, val in (
|
|
130
|
+
('fps', self.fps),
|
|
131
|
+
('tolerance', self.tolerance),
|
|
132
|
+
('min_levensthein_ratio', self.min_levensthein_ratio),
|
|
133
|
+
('min_bbox_iou', self.min_bbox_iou),
|
|
134
|
+
('min_text_confidence', self.min_text_confidence),
|
|
135
|
+
('max_text_confidence', self.max_text_confidence),
|
|
136
|
+
('max_text_length', self.max_text_length),
|
|
137
|
+
('moving_text_threshold', self.moving_text_threshold),
|
|
138
|
+
):
|
|
139
|
+
if val is not None:
|
|
140
|
+
extra[k] = val
|
|
141
|
+
|
|
142
|
+
# Upload file to MediaCatch Vision API
|
|
143
|
+
file_id = upload(self.file_path, self.type, extra=extra)
|
|
144
|
+
|
|
145
|
+
# Wait for result
|
|
146
|
+
result = wait_for_result(file_id)
|
|
147
|
+
if not result:
|
|
148
|
+
logger.error('Failed to get result from MediaCatch Vision API')
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
logger.info('Results:')
|
|
152
|
+
pprint(result)
|
|
153
|
+
|
|
154
|
+
# Save result to a file
|
|
155
|
+
if self.save_result:
|
|
156
|
+
Path(self.save_result).write_text(json.dumps(result, indent=4))
|
|
157
|
+
logger.info(f'Result saved to {self.save_result}')
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger('mediacatch.utils.general')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_data_from_url(url: str) -> dict:
|
|
11
|
+
"""Get data from a URL.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
url (str): The URL to get data from.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
dict: Data dictionary from url.
|
|
18
|
+
"""
|
|
19
|
+
logger.info(f'Getting data from {url}')
|
|
20
|
+
response = requests.get(url)
|
|
21
|
+
assert response.status_code == 200, f'Failed to get data from {url}: {response}'
|
|
22
|
+
return response.json()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_data_from_json(json_file: str) -> dict:
|
|
26
|
+
"""Load data from a JSON file.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
json_file (str): JSON file with data.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
dict: Data dictionary from JSON file.
|
|
33
|
+
"""
|
|
34
|
+
logger.info(f'Loading data from {json_file}')
|
|
35
|
+
assert os.path.isfile(json_file), f'File {json_file} does not exist'
|
|
36
|
+
with open(json_file, 'r') as f:
|
|
37
|
+
result = json.load(f)
|
|
38
|
+
return result
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger('mediacatch.vision.result')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def wait_for_result(
|
|
12
|
+
file_id: str,
|
|
13
|
+
url: str = 'https://api.mediacatch.io/vision',
|
|
14
|
+
timeout: int = 3600,
|
|
15
|
+
delay: int = 30,
|
|
16
|
+
) -> dict[str, Any] | None:
|
|
17
|
+
"""Wait for result from a URL.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
file_id (str): The file ID to get the result from.
|
|
21
|
+
url (str): The URL to get the result from.
|
|
22
|
+
timeout (int, optional): Timeout for waiting in seconds. Defaults to 3600.
|
|
23
|
+
delay (int, optional): Delay between each request. Defaults to 5.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
dict[str, Any] | None: Dictionary with the result from the URL or None if failed.
|
|
27
|
+
"""
|
|
28
|
+
result_url = f'{url}/result/{file_id}'
|
|
29
|
+
logger.info(f'Waiting for result from {result_url}')
|
|
30
|
+
start_time = time.time()
|
|
31
|
+
end_time = start_time + timeout
|
|
32
|
+
while time.time() < end_time:
|
|
33
|
+
try:
|
|
34
|
+
response = requests.get(result_url)
|
|
35
|
+
response.raise_for_status()
|
|
36
|
+
result = response.json()
|
|
37
|
+
elapsed_time = time.time() - start_time
|
|
38
|
+
logger.info(f'Got result from {result_url} in {elapsed_time:.2f} seconds')
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
except (requests.RequestException, json.JSONDecodeError):
|
|
42
|
+
time.sleep(delay)
|
|
43
|
+
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logger.error(f'Failed to get result from {result_url}: {e}')
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
logger.error(f'Timeout waiting for result from {result_url}, give up')
|
|
49
|
+
return None
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Literal
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from mediacatch import mediacatch_api_key
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger('mediacatch.vision.upload')
|
|
10
|
+
|
|
11
|
+
headers = {
|
|
12
|
+
'accept': 'application/json',
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
'X-API-KEY': f'{mediacatch_api_key}',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def upload(
|
|
19
|
+
fpath: str,
|
|
20
|
+
type: Literal['ocr', 'face'],
|
|
21
|
+
url: str = 'https://api.mediacatch.io/vision',
|
|
22
|
+
extra: dict[str, Any] = {},
|
|
23
|
+
) -> str:
|
|
24
|
+
"""Upload a file to MediaCatch Vision API.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
fpath (str): File path.
|
|
28
|
+
type (Literal['ocr', 'face']): Type of inference to run on the file.
|
|
29
|
+
url (str, optional): URL to the vision API. Defaults to 'https://api.mediacatch.io/vision'.
|
|
30
|
+
extra (dict[str, Any], optional): Dictonary with extra parameters. Defaults to {}.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
str: File ID.
|
|
34
|
+
"""
|
|
35
|
+
logger.info(f'Uploading file {fpath} to MediaCatch Vision API')
|
|
36
|
+
|
|
37
|
+
# Get presigned URL
|
|
38
|
+
data = {'filename': fpath, 'type': type, 'extra': extra}
|
|
39
|
+
response = requests.post(f'{url}/upload/', headers=headers, data=json.dumps(data))
|
|
40
|
+
assert (
|
|
41
|
+
response.status_code == 201
|
|
42
|
+
), f'Failed to upload file {fpath} to MediaCatch Vision API: {response.text}'
|
|
43
|
+
response_data = response.json()
|
|
44
|
+
file_id = response_data['file_id']
|
|
45
|
+
|
|
46
|
+
# Upload file to storage
|
|
47
|
+
with open(fpath, 'rb') as f:
|
|
48
|
+
files = {'file': (response_data['fields']['key'], f)}
|
|
49
|
+
response = requests.post(response_data['url'], data=response_data['fields'], files=files)
|
|
50
|
+
|
|
51
|
+
# Mark file as uploaded
|
|
52
|
+
data = {'file_id': file_id}
|
|
53
|
+
response = requests.post(f'{url}/upload/complete/', headers=headers, data=json.dumps(data))
|
|
54
|
+
assert response.status_code == 200, f'Failed to mark file {fpath} as uploaded: {response}'
|
|
55
|
+
|
|
56
|
+
logger.info(f'File {fpath} uploaded with ID {file_id}')
|
|
57
|
+
return file_id
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: mediacatch
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python package for easily interfacing with MediaCatch APIs and data formats.
|
|
5
|
+
Project-URL: Documentation, https://github.com/mediacatch/mediacatch#readme
|
|
6
|
+
Project-URL: Issues, https://github.com/mediacatch/mediacatch/issues
|
|
7
|
+
Project-URL: Source, https://github.com/mediacatch/mediacatch
|
|
8
|
+
Author-email: Mathias Nielsen <mathias@mediacatch.io>, Frederik Haarslev <frederik@mediacatch.io>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Classifier: Framework :: FastAPI
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Topic :: Internet
|
|
16
|
+
Classifier: Topic :: Software Development
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: opencv-python-headless
|
|
22
|
+
Requires-Dist: pydantic
|
|
23
|
+
Requires-Dist: python-dotenv
|
|
24
|
+
Requires-Dist: requests
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: black; extra == 'dev'
|
|
27
|
+
Requires-Dist: hatch; extra == 'dev'
|
|
28
|
+
Requires-Dist: isort; extra == 'dev'
|
|
29
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
31
|
+
Provides-Extra: docs
|
|
32
|
+
Requires-Dist: mkdocs; extra == 'docs'
|
|
33
|
+
Requires-Dist: mkdocs-autorefs; extra == 'docs'
|
|
34
|
+
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
35
|
+
Requires-Dist: mkdocstrings; extra == 'docs'
|
|
36
|
+
Requires-Dist: mkdocstrings-python; extra == 'docs'
|
|
37
|
+
Provides-Extra: test
|
|
38
|
+
Requires-Dist: black; extra == 'test'
|
|
39
|
+
Requires-Dist: hatch; extra == 'test'
|
|
40
|
+
Requires-Dist: isort; extra == 'test'
|
|
41
|
+
Requires-Dist: mypy; extra == 'test'
|
|
42
|
+
Requires-Dist: pytest; extra == 'test'
|
|
43
|
+
Requires-Dist: ruff; extra == 'test'
|
|
44
|
+
Description-Content-Type: text/markdown
|
|
45
|
+
|
|
46
|
+
# MediaCatch
|
|
47
|
+
|
|
48
|
+
Python package for easily interfacing with MediaCatch APIs and data formats.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
TBD.
|
|
53
|
+
|
|
54
|
+
## Getting Started
|
|
55
|
+
|
|
56
|
+
TBD.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
mediacatch/__init__.py,sha256=lEBSAh9l0XQjT2AkMs5BlwOSN87eGQMSk7BH7-tRHu8,356
|
|
2
|
+
mediacatch/commands/__init__.py,sha256=vTLJOd4n5aVy9Z_fG4kZLzcr9kuSylR4666bo3S0L_E,302
|
|
3
|
+
mediacatch/commands/cli.py,sha256=guEeLrCLAh__HxplDdSDOYE8uWdD633ko9xtJD7bvoI,727
|
|
4
|
+
mediacatch/commands/vision.py,sha256=IxslHvk3pYXMaPfH5t9Y_Hq56Tm0IguqsA9EOgaa2xU,5215
|
|
5
|
+
mediacatch/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
mediacatch/utils/general.py,sha256=E4emyI2jN1u7ZrBDL5wHhOpiHJ8jPyl-GzaZTvuXB0M,906
|
|
7
|
+
mediacatch/vision/__init__.py,sha256=hX_1CPm81KVuP5kqoIwLbf2Mas35cfWLV_kHIxPuUCg,97
|
|
8
|
+
mediacatch/vision/result.py,sha256=wY5RaOWJxd9hhc93GJHqTzfYatOt4-viHWuHKE3AT9E,1546
|
|
9
|
+
mediacatch/vision/upload.py,sha256=SSfwdI6pk5DomvR_MwI5NyKWTVcgmUw8cz_AE6_kR5o,1867
|
|
10
|
+
mediacatch-0.0.1.dist-info/METADATA,sha256=2lKp1o189fz8_E_I8dLIIz40rn-RSHrwaZV3sfjb6w8,2009
|
|
11
|
+
mediacatch-0.0.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
12
|
+
mediacatch-0.0.1.dist-info/entry_points.txt,sha256=gYOMlPb97KioFnzzNDzDhLBcM-AXwTTah-amIzl_kac,60
|
|
13
|
+
mediacatch-0.0.1.dist-info/RECORD,,
|