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 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,2 @@
1
+ from mediacatch.vision.result import wait_for_result
2
+ from mediacatch.vision.upload import upload
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.25.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ mediacatch = mediacatch.commands.cli:main