mediacatch 0.0.1__tar.gz
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-0.0.1/.github/workflows/check.yaml +34 -0
- mediacatch-0.0.1/.github/workflows/publish.yaml +33 -0
- mediacatch-0.0.1/.github/workflows/test.yaml +34 -0
- mediacatch-0.0.1/.gitignore +151 -0
- mediacatch-0.0.1/PKG-INFO +56 -0
- mediacatch-0.0.1/README.md +11 -0
- mediacatch-0.0.1/mediacatch/__init__.py +17 -0
- mediacatch-0.0.1/mediacatch/commands/__init__.py +13 -0
- mediacatch-0.0.1/mediacatch/commands/cli.py +31 -0
- mediacatch-0.0.1/mediacatch/commands/vision.py +157 -0
- mediacatch-0.0.1/mediacatch/utils/__init__.py +0 -0
- mediacatch-0.0.1/mediacatch/utils/general.py +38 -0
- mediacatch-0.0.1/mediacatch/vision/__init__.py +2 -0
- mediacatch-0.0.1/mediacatch/vision/result.py +49 -0
- mediacatch-0.0.1/mediacatch/vision/upload.py +57 -0
- mediacatch-0.0.1/pyproject.toml +140 -0
- mediacatch-0.0.1/tests/__init__.py +0 -0
- mediacatch-0.0.1/tests/test_main.py +2 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Quality Check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
quality:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v3
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v4
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.10"
|
|
23
|
+
cache: "pip"
|
|
24
|
+
cache-dependency-path: pyproject.toml
|
|
25
|
+
- uses: actions/cache@v3
|
|
26
|
+
id: cache
|
|
27
|
+
with:
|
|
28
|
+
path: ${{ env.pythonLocation }}
|
|
29
|
+
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-check
|
|
30
|
+
- name: Install build dependencies
|
|
31
|
+
if: steps.cache.outputs.cache-hit != 'true'
|
|
32
|
+
run: pip install ".[dev]"
|
|
33
|
+
- name: Run library checks
|
|
34
|
+
run: ruff .
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types:
|
|
6
|
+
- released
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v3
|
|
13
|
+
- name: Set up Python
|
|
14
|
+
uses: actions/setup-python@v4
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.10"
|
|
17
|
+
cache: "pip"
|
|
18
|
+
cache-dependency-path: pyproject.toml
|
|
19
|
+
- uses: actions/cache@v3
|
|
20
|
+
id: cache
|
|
21
|
+
with:
|
|
22
|
+
path: ${{ env.pythonLocation }}
|
|
23
|
+
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-publish
|
|
24
|
+
- name: Install build dependencies
|
|
25
|
+
if: steps.cache.outputs.cache-hit != 'true'
|
|
26
|
+
run: pip install --upgrade build hatchling pkginfo
|
|
27
|
+
- name: Build distribution
|
|
28
|
+
run: python -m build
|
|
29
|
+
- name: Publish
|
|
30
|
+
uses: pypa/gh-action-pypi-publish@v1.6.4
|
|
31
|
+
with:
|
|
32
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
33
|
+
verbose: true
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: Unit Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
quality:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v3
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v4
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.10"
|
|
23
|
+
cache: "pip"
|
|
24
|
+
cache-dependency-path: pyproject.toml
|
|
25
|
+
- uses: actions/cache@v3
|
|
26
|
+
id: cache
|
|
27
|
+
with:
|
|
28
|
+
path: ${{ env.pythonLocation }}
|
|
29
|
+
key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-test
|
|
30
|
+
- name: Install build dependencies
|
|
31
|
+
if: steps.cache.outputs.cache-hit != 'true'
|
|
32
|
+
run: pip install ".[test]"
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: pytest
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
data
|
|
2
|
+
|
|
3
|
+
# Byte-compiled / optimized / DLL files
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
*$py.class
|
|
7
|
+
|
|
8
|
+
# C extensions
|
|
9
|
+
*.so
|
|
10
|
+
|
|
11
|
+
# Distribution / packaging
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
.eggs/
|
|
19
|
+
lib/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
pip-wheel-metadata/
|
|
26
|
+
share/python-wheels/
|
|
27
|
+
*.egg-info/
|
|
28
|
+
.installed.cfg
|
|
29
|
+
*.egg
|
|
30
|
+
MANIFEST
|
|
31
|
+
|
|
32
|
+
# PyInstaller
|
|
33
|
+
# Usually these files are written by a python script from a template
|
|
34
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
35
|
+
*.manifest
|
|
36
|
+
*.spec
|
|
37
|
+
|
|
38
|
+
# Installer logs
|
|
39
|
+
pip-log.txt
|
|
40
|
+
pip-delete-this-directory.txt
|
|
41
|
+
|
|
42
|
+
# Unit test / coverage reports
|
|
43
|
+
htmlcov/
|
|
44
|
+
.tox/
|
|
45
|
+
.nox/
|
|
46
|
+
.coverage
|
|
47
|
+
.coverage.*
|
|
48
|
+
.cache
|
|
49
|
+
nosetests.xml
|
|
50
|
+
coverage.xml
|
|
51
|
+
*.cover
|
|
52
|
+
*.py,cover
|
|
53
|
+
.hypothesis/
|
|
54
|
+
.pytest_cache/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
local_settings.py
|
|
63
|
+
db.sqlite3
|
|
64
|
+
db.sqlite3-journal
|
|
65
|
+
|
|
66
|
+
# Flask stuff:
|
|
67
|
+
instance/
|
|
68
|
+
.webassets-cache
|
|
69
|
+
|
|
70
|
+
# Scrapy stuff:
|
|
71
|
+
.scrapy
|
|
72
|
+
|
|
73
|
+
# Sphinx documentation
|
|
74
|
+
docs/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
target/
|
|
78
|
+
|
|
79
|
+
# Jupyter Notebook
|
|
80
|
+
.ipynb_checkpoints
|
|
81
|
+
|
|
82
|
+
# IPython
|
|
83
|
+
profile_default/
|
|
84
|
+
ipython_config.py
|
|
85
|
+
|
|
86
|
+
# pyenv
|
|
87
|
+
.python-version
|
|
88
|
+
|
|
89
|
+
# pipenv
|
|
90
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
91
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
92
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
93
|
+
# install all needed dependencies.
|
|
94
|
+
#Pipfile.lock
|
|
95
|
+
|
|
96
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
97
|
+
__pypackages__/
|
|
98
|
+
|
|
99
|
+
# Celery stuff
|
|
100
|
+
celerybeat-schedule
|
|
101
|
+
celerybeat.pid
|
|
102
|
+
|
|
103
|
+
# SageMath parsed files
|
|
104
|
+
*.sage.py
|
|
105
|
+
|
|
106
|
+
# Environments
|
|
107
|
+
.env
|
|
108
|
+
.venv
|
|
109
|
+
env/
|
|
110
|
+
venv/
|
|
111
|
+
ENV/
|
|
112
|
+
env.bak/
|
|
113
|
+
venv.bak/
|
|
114
|
+
|
|
115
|
+
# Spyder project settings
|
|
116
|
+
.spyderproject
|
|
117
|
+
.spyproject
|
|
118
|
+
|
|
119
|
+
# Rope project settings
|
|
120
|
+
.ropeproject
|
|
121
|
+
|
|
122
|
+
# mkdocs documentation
|
|
123
|
+
/site
|
|
124
|
+
|
|
125
|
+
# mypy
|
|
126
|
+
.mypy_cache/
|
|
127
|
+
.dmypy.json
|
|
128
|
+
dmypy.json
|
|
129
|
+
|
|
130
|
+
# Pyre type checker
|
|
131
|
+
.pyre/
|
|
132
|
+
.ruff_cache
|
|
133
|
+
|
|
134
|
+
### VisualStudioCode ###
|
|
135
|
+
.vscode/*
|
|
136
|
+
.vscode/settings.json
|
|
137
|
+
.vscode/tasks.json
|
|
138
|
+
.vscode/launch.json
|
|
139
|
+
.vscode/extensions.json
|
|
140
|
+
.vscode/*.code-snippets
|
|
141
|
+
|
|
142
|
+
# Local History for Visual Studio Code
|
|
143
|
+
.history/
|
|
144
|
+
|
|
145
|
+
# Built Visual Studio Code Extensions
|
|
146
|
+
*.vsix
|
|
147
|
+
|
|
148
|
+
### VisualStudioCode Patch ###
|
|
149
|
+
# Ignore all local history of files
|
|
150
|
+
.history
|
|
151
|
+
.ionide
|
|
@@ -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,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,140 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project.urls]
|
|
6
|
+
Documentation = "https://github.com/mediacatch/mediacatch#readme"
|
|
7
|
+
Issues = "https://github.com/mediacatch/mediacatch/issues"
|
|
8
|
+
Source = "https://github.com/mediacatch/mediacatch"
|
|
9
|
+
|
|
10
|
+
[tool.hatch.version]
|
|
11
|
+
path = "mediacatch/__init__.py"
|
|
12
|
+
|
|
13
|
+
[project]
|
|
14
|
+
name = "mediacatch"
|
|
15
|
+
description = "Python package for easily interfacing with MediaCatch APIs and data formats."
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
requires-python = ">=3.10"
|
|
18
|
+
license = "MIT"
|
|
19
|
+
authors = [
|
|
20
|
+
{ name = "Mathias Nielsen", email = "mathias@mediacatch.io" },
|
|
21
|
+
{ name = "Frederik Haarslev", email = "frederik@mediacatch.io" },
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Topic :: Internet",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
"Topic :: Software Development :: Libraries",
|
|
28
|
+
"Topic :: Software Development",
|
|
29
|
+
"Framework :: FastAPI",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"License :: OSI Approved :: MIT License",
|
|
32
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
33
|
+
"Programming Language :: Python :: 3.10",
|
|
34
|
+
]
|
|
35
|
+
dynamic = ["version"]
|
|
36
|
+
scripts = { mediacatch = "mediacatch.commands.cli:main" }
|
|
37
|
+
dependencies = [
|
|
38
|
+
"requests",
|
|
39
|
+
"opencv-python-headless",
|
|
40
|
+
"pydantic",
|
|
41
|
+
"python-dotenv",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
test = ["pytest", "ruff", "black", "isort", "mypy", "hatch"]
|
|
46
|
+
dev = ["ruff", "black", "isort", "mypy", "hatch"]
|
|
47
|
+
docs = [
|
|
48
|
+
"mkdocs",
|
|
49
|
+
"mkdocs-material",
|
|
50
|
+
"mkdocstrings",
|
|
51
|
+
"mkdocstrings-python",
|
|
52
|
+
"mkdocs-autorefs",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
# Exclude a variety of commonly ignored directories.
|
|
57
|
+
exclude = [
|
|
58
|
+
".bzr",
|
|
59
|
+
".direnv",
|
|
60
|
+
".eggs",
|
|
61
|
+
".git",
|
|
62
|
+
".git-rewrite",
|
|
63
|
+
".hg",
|
|
64
|
+
".ipynb_checkpoints",
|
|
65
|
+
".mypy_cache",
|
|
66
|
+
".nox",
|
|
67
|
+
".pants.d",
|
|
68
|
+
".pyenv",
|
|
69
|
+
".pytest_cache",
|
|
70
|
+
".pytype",
|
|
71
|
+
".ruff_cache",
|
|
72
|
+
".svn",
|
|
73
|
+
".tox",
|
|
74
|
+
".venv",
|
|
75
|
+
".vscode",
|
|
76
|
+
"__pypackages__",
|
|
77
|
+
"_build",
|
|
78
|
+
"buck-out",
|
|
79
|
+
"build",
|
|
80
|
+
"dist",
|
|
81
|
+
"node_modules",
|
|
82
|
+
"site-packages",
|
|
83
|
+
"venv",
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# Same as Black.
|
|
87
|
+
line-length = 100
|
|
88
|
+
indent-width = 4
|
|
89
|
+
|
|
90
|
+
# Assume Python 3.10
|
|
91
|
+
target-version = "py310"
|
|
92
|
+
|
|
93
|
+
[tool.ruff.lint]
|
|
94
|
+
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
|
95
|
+
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
|
96
|
+
# McCabe complexity (`C901`) by default.
|
|
97
|
+
select = ["E4", "E7", "E9", "F"]
|
|
98
|
+
ignore = []
|
|
99
|
+
|
|
100
|
+
# Allow fix for all enabled rules (when `--fix`) is provided.
|
|
101
|
+
fixable = ["ALL"]
|
|
102
|
+
unfixable = []
|
|
103
|
+
|
|
104
|
+
# Allow unused variables when underscore-prefixed.
|
|
105
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
106
|
+
|
|
107
|
+
[tool.ruff.format]
|
|
108
|
+
# Like Black, use single quotes for strings.
|
|
109
|
+
quote-style = "single"
|
|
110
|
+
|
|
111
|
+
# Like Black, indent with spaces, rather than tabs.
|
|
112
|
+
indent-style = "space"
|
|
113
|
+
|
|
114
|
+
# Like Black, respect magic trailing commas.
|
|
115
|
+
skip-magic-trailing-comma = false
|
|
116
|
+
|
|
117
|
+
# Like Black, automatically detect the appropriate line ending.
|
|
118
|
+
line-ending = "auto"
|
|
119
|
+
|
|
120
|
+
# Enable auto-formatting of code examples in docstrings. Markdown,
|
|
121
|
+
# reStructuredText code/literal blocks and doctests are all supported.
|
|
122
|
+
#
|
|
123
|
+
# This is currently disabled by default, but it is planned for this
|
|
124
|
+
# to be opt-out in the future.
|
|
125
|
+
docstring-code-format = false
|
|
126
|
+
|
|
127
|
+
# Set the line length limit used when formatting code snippets in
|
|
128
|
+
# docstrings.
|
|
129
|
+
#
|
|
130
|
+
# This only has an effect when the `docstring-code-format` setting is
|
|
131
|
+
# enabled.
|
|
132
|
+
docstring-code-line-length = "dynamic"
|
|
133
|
+
|
|
134
|
+
[tool.ruff.per-file-ignores]
|
|
135
|
+
"__init__.py" = ["F401"]
|
|
136
|
+
|
|
137
|
+
[tool.pytest.ini_options]
|
|
138
|
+
addopts = "-ra"
|
|
139
|
+
testpaths = ["tests"]
|
|
140
|
+
pythonpath = ['.']
|
|
File without changes
|