yta-fastapi-docker-motion-generator 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.
- yta_fastapi_docker_motion_generator-0.0.1/LICENSE +19 -0
- yta_fastapi_docker_motion_generator-0.0.1/PKG-INFO +47 -0
- yta_fastapi_docker_motion_generator-0.0.1/README.md +26 -0
- yta_fastapi_docker_motion_generator-0.0.1/pyproject.toml +37 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/__init__.py +6 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/__init__.py +0 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/main.py +12 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/routers/__init__.py +24 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/routers/motion_resources/__init__.py +0 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/routers/motion_resources/images.py +59 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/routers/motion_resources/utils.py +58 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/routers/motion_resources/videos.py +40 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/services/__init__.py +0 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/services/motion_resources/__init__.py +438 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/services/motion_resources/custom.py +106 -0
- yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/services/utils.py +65 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2018 The Python Packaging Authority
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yta-fastapi-docker-motion-generator
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Youtube Autonomous FastAPI Docker Motion Generator Module
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: danialcala94
|
|
7
|
+
Author-email: danielalcalavalera@gmail.com
|
|
8
|
+
Requires-Python: >=3.10,<3.14
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Dist: fastapi (>=0.137.0,<9999.0.0)
|
|
15
|
+
Requires-Dist: uvicorn (>=0.49.0,<9999.0.0)
|
|
16
|
+
Requires-Dist: yta_fastapi_docker_pydantic_models (>=0.0.8,<1.0.0)
|
|
17
|
+
Requires-Dist: yta_general_utils (>=0.5.1,<1.0.0)
|
|
18
|
+
Requires-Dist: yta_programming_env (>=0.3.2,<1.0.0)
|
|
19
|
+
Requires-Dist: yta_web_scraper (>=0.3.7,<1.0.0)
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Youtube Autonomous FastAPI Docker Motion Generator Module
|
|
23
|
+
|
|
24
|
+
The module that is providing the functionality related to the Motion Generator Web that are customized and generated to be downloaded using a scraper, to be lately provided through a FastAPI that is included and isolated in a Docker container but exposed to the internal and general FastAPI.
|
|
25
|
+
|
|
26
|
+
This project will use the `motion-generator-web` project to scrap it.
|
|
27
|
+
|
|
28
|
+
### Endpoints
|
|
29
|
+
|
|
30
|
+
#### GET
|
|
31
|
+
No endpoints by now
|
|
32
|
+
|
|
33
|
+
#### POST
|
|
34
|
+
- `/motion-resources/images/booking-review`
|
|
35
|
+
- `name` - The name of the user that is creating the review
|
|
36
|
+
- `message` - The message we want to show in the review
|
|
37
|
+
|
|
38
|
+
- `/motion-resources/images/simple-progress-bar`
|
|
39
|
+
- `stripes_speed` - The speed of the stripes when loading.
|
|
40
|
+
- `duration` - The duration of the animation
|
|
41
|
+
- `fps` - The fps of the animation
|
|
42
|
+
- `frame` - The frame of the animation we want to download
|
|
43
|
+
|
|
44
|
+
- `/motion-resources/videos/simple-progress-bar`
|
|
45
|
+
- `stripes_speed` - The speed of the stripes when loading.
|
|
46
|
+
- `duration` - The duration of the animation
|
|
47
|
+
- `fps` - The fps of the animation
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Youtube Autonomous FastAPI Docker Motion Generator Module
|
|
2
|
+
|
|
3
|
+
The module that is providing the functionality related to the Motion Generator Web that are customized and generated to be downloaded using a scraper, to be lately provided through a FastAPI that is included and isolated in a Docker container but exposed to the internal and general FastAPI.
|
|
4
|
+
|
|
5
|
+
This project will use the `motion-generator-web` project to scrap it.
|
|
6
|
+
|
|
7
|
+
### Endpoints
|
|
8
|
+
|
|
9
|
+
#### GET
|
|
10
|
+
No endpoints by now
|
|
11
|
+
|
|
12
|
+
#### POST
|
|
13
|
+
- `/motion-resources/images/booking-review`
|
|
14
|
+
- `name` - The name of the user that is creating the review
|
|
15
|
+
- `message` - The message we want to show in the review
|
|
16
|
+
|
|
17
|
+
- `/motion-resources/images/simple-progress-bar`
|
|
18
|
+
- `stripes_speed` - The speed of the stripes when loading.
|
|
19
|
+
- `duration` - The duration of the animation
|
|
20
|
+
- `fps` - The fps of the animation
|
|
21
|
+
- `frame` - The frame of the animation we want to download
|
|
22
|
+
|
|
23
|
+
- `/motion-resources/videos/simple-progress-bar`
|
|
24
|
+
- `stripes_speed` - The speed of the stripes when loading.
|
|
25
|
+
- `duration` - The duration of the animation
|
|
26
|
+
- `fps` - The fps of the animation
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "yta-fastapi-docker-motion-generator"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "Youtube Autonomous FastAPI Docker Motion Generator Module"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "danialcala94", email = "danielalcalavalera@gmail.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10,<3.14"
|
|
10
|
+
|
|
11
|
+
[tool.poetry.dependencies]
|
|
12
|
+
# Mandatory
|
|
13
|
+
yta_web_scraper = { version = ">=0.3.7,<1.0.0", optional = false }
|
|
14
|
+
yta_programming_env = { version = ">=0.3.2,<1.0.0", optional = false }
|
|
15
|
+
yta_general_utils = { version = ">=0.5.1,<1.0.0", optional = false }
|
|
16
|
+
fastapi = { version = ">=0.137.0,<9999.0.0", optional = false }
|
|
17
|
+
uvicorn = { version = ">=0.49.0,<9999.0.0", optional = false }
|
|
18
|
+
yta_fastapi_docker_pydantic_models = { version = ">=0.0.8,<1.0.0", optional = false }
|
|
19
|
+
# Optionalº
|
|
20
|
+
|
|
21
|
+
[tool.poetry]
|
|
22
|
+
packages = [{include = "yta_fastapi_docker_motion_generator", from = "src"}]
|
|
23
|
+
|
|
24
|
+
[tool.poetry.group.dev.dependencies]
|
|
25
|
+
pytest = "^8.3.5"
|
|
26
|
+
httpx = ">=0.28.1"
|
|
27
|
+
yta_testing = ">=0.3.6"
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
markers = [
|
|
31
|
+
"mandatory: mandatory tests for release",
|
|
32
|
+
"additional: exhaustive and demanding tests"
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
37
|
+
build-backend = "poetry.core.masonry.api"
|
yta_fastapi_docker_motion_generator-0.0.1/src/yta_fastapi_docker_motion_generator/app/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from yta_fastapi_docker_motion_generator.app.routers import router as general_router
|
|
2
|
+
from yta_fastapi_docker_motion_generator.app.routers.motion_resources.images import router as motion_resources_images_router
|
|
3
|
+
from yta_fastapi_docker_motion_generator.app.routers.motion_resources.videos import router as motion_resources_videos_router
|
|
4
|
+
from fastapi import FastAPI
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
app = FastAPI()
|
|
8
|
+
|
|
9
|
+
# Include all the routers we have
|
|
10
|
+
app.include_router(general_router)
|
|
11
|
+
app.include_router(motion_resources_images_router)
|
|
12
|
+
app.include_router(motion_resources_videos_router)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check this project to get inspiration about FastAPI:
|
|
3
|
+
- https://github.com/Implosiv3/render-fastapi/blob/master/render-fastapi/routers/download/__init__.py
|
|
4
|
+
"""
|
|
5
|
+
from fastapi import APIRouter
|
|
6
|
+
from fastapi.responses import JSONResponse
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
PREFIX = f''
|
|
10
|
+
|
|
11
|
+
router = APIRouter(
|
|
12
|
+
prefix = PREFIX
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
@router.get('/check-status')
|
|
16
|
+
def route_check_status(
|
|
17
|
+
) -> JSONResponse:
|
|
18
|
+
return JSONResponse(
|
|
19
|
+
{
|
|
20
|
+
'error': False,
|
|
21
|
+
'message': 'Hello World!'
|
|
22
|
+
},
|
|
23
|
+
status_code = 200
|
|
24
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module is based on the `motion-generator-web`
|
|
3
|
+
project that is performing a React web page with
|
|
4
|
+
resources that can be static, downloadable as
|
|
5
|
+
images, or animated, downloadable as images or
|
|
6
|
+
videos.
|
|
7
|
+
"""
|
|
8
|
+
from yta_fastapi_docker_motion_generator.app.routers.motion_resources.utils import create_temp_file_response, get_animation_kwargs_from_request
|
|
9
|
+
from yta_fastapi_docker_motion_generator.app.services.motion_resources.custom import BookingReviewMotionResource, SimpleProgressBarAnimatedMotionResource
|
|
10
|
+
from yta_fastapi_docker_pydantic_models.motion_resources.images import CreateBookingReview, CreateSimpleProgressBarImage
|
|
11
|
+
from fastapi.responses import FileResponse
|
|
12
|
+
from fastapi import APIRouter, BackgroundTasks
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
PREFIX = f'/motion-resources/images'
|
|
16
|
+
|
|
17
|
+
router = APIRouter(
|
|
18
|
+
prefix = PREFIX
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
MEDIA_TYPE = 'image/png'
|
|
22
|
+
|
|
23
|
+
@router.post('/booking-review')
|
|
24
|
+
async def post_booking_review(
|
|
25
|
+
request: CreateBookingReview,
|
|
26
|
+
background_tasks: BackgroundTasks
|
|
27
|
+
) -> FileResponse:
|
|
28
|
+
return create_temp_file_response(
|
|
29
|
+
background_tasks = background_tasks,
|
|
30
|
+
download_callable = lambda temp_dir: BookingReviewMotionResource(
|
|
31
|
+
download_folder = temp_dir
|
|
32
|
+
).download_image(
|
|
33
|
+
name = request.name,
|
|
34
|
+
text = request.message
|
|
35
|
+
),
|
|
36
|
+
output_filename = 'booking_review.png',
|
|
37
|
+
media_type = MEDIA_TYPE
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@router.post('/simple-progress-bar')
|
|
42
|
+
async def post_simple_progress_bar(
|
|
43
|
+
request: CreateSimpleProgressBarImage,
|
|
44
|
+
background_tasks: BackgroundTasks
|
|
45
|
+
) -> FileResponse:
|
|
46
|
+
animation_kwars = get_animation_kwargs_from_request(request)
|
|
47
|
+
|
|
48
|
+
return create_temp_file_response(
|
|
49
|
+
background_tasks = background_tasks,
|
|
50
|
+
download_callable = lambda temp_dir: SimpleProgressBarAnimatedMotionResource(
|
|
51
|
+
download_folder = temp_dir
|
|
52
|
+
).download_image(
|
|
53
|
+
stripes_speed = request.stripes_speed,
|
|
54
|
+
**animation_kwars,
|
|
55
|
+
frame = request.frame,
|
|
56
|
+
),
|
|
57
|
+
output_filename = 'simple_progress_bar.png',
|
|
58
|
+
media_type = MEDIA_TYPE
|
|
59
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from fastapi.responses import FileResponse
|
|
2
|
+
|
|
3
|
+
import tempfile
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def create_temp_file_response(
|
|
8
|
+
background_tasks: 'BackgroundTasks',
|
|
9
|
+
download_callable: callable,
|
|
10
|
+
output_filename: str,
|
|
11
|
+
media_type: str,
|
|
12
|
+
) -> FileResponse:
|
|
13
|
+
"""
|
|
14
|
+
Create a temporary directory to download the
|
|
15
|
+
file by calling the `download_callable` function
|
|
16
|
+
and return a `FileResponse` including it with
|
|
17
|
+
the `media_type` provided.
|
|
18
|
+
|
|
19
|
+
A background task will be added to remove the
|
|
20
|
+
temporary folder once the `FileResponse` has
|
|
21
|
+
been completely sent.
|
|
22
|
+
"""
|
|
23
|
+
temp_dir = tempfile.mkdtemp()
|
|
24
|
+
|
|
25
|
+
background_tasks.add_task(
|
|
26
|
+
shutil.rmtree,
|
|
27
|
+
temp_dir,
|
|
28
|
+
ignore_errors = True
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
generated_file = download_callable(temp_dir)
|
|
32
|
+
|
|
33
|
+
return FileResponse(
|
|
34
|
+
generated_file,
|
|
35
|
+
media_type = media_type,
|
|
36
|
+
filename = output_filename
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_animation_kwargs_from_request(
|
|
41
|
+
request
|
|
42
|
+
) -> dict:
|
|
43
|
+
"""
|
|
44
|
+
Get the animation kwargs that include the
|
|
45
|
+
`duration` and `fps` field if provided in the
|
|
46
|
+
request.
|
|
47
|
+
|
|
48
|
+
This is meant to be sent as `**kwargs` to the
|
|
49
|
+
model method in order to download with that
|
|
50
|
+
specific animation configuration.
|
|
51
|
+
"""
|
|
52
|
+
animation_kwars = {}
|
|
53
|
+
if request.duration is not None:
|
|
54
|
+
animation_kwars['duration'] = request.duration
|
|
55
|
+
if request.fps is not None:
|
|
56
|
+
animation_kwars['fps'] = request.fps
|
|
57
|
+
|
|
58
|
+
return animation_kwars
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module is based on the `motion-generator-web`
|
|
3
|
+
project that is performing a React web page with
|
|
4
|
+
resources that can be static, downloadable as
|
|
5
|
+
images, or animated, downloadable as images or
|
|
6
|
+
videos.
|
|
7
|
+
"""
|
|
8
|
+
from yta_fastapi_docker_motion_generator.app.routers.motion_resources.utils import create_temp_file_response, get_animation_kwargs_from_request
|
|
9
|
+
from yta_fastapi_docker_motion_generator.app.services.motion_resources.custom import SimpleProgressBarAnimatedMotionResource
|
|
10
|
+
from yta_fastapi_docker_pydantic_models.motion_resources.videos import CreateSimpleProgressBarVideo
|
|
11
|
+
from fastapi.responses import FileResponse
|
|
12
|
+
from fastapi import APIRouter, BackgroundTasks
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
PREFIX = f'/motion-resources/videos'
|
|
16
|
+
|
|
17
|
+
router = APIRouter(
|
|
18
|
+
prefix = PREFIX
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
MEDIA_TYPE = 'video/quicktime'
|
|
22
|
+
|
|
23
|
+
@router.post('/simple-progress-bar')
|
|
24
|
+
async def post_simple_progress_bar(
|
|
25
|
+
request: CreateSimpleProgressBarVideo,
|
|
26
|
+
background_tasks: BackgroundTasks
|
|
27
|
+
) -> FileResponse:
|
|
28
|
+
animation_kwars = get_animation_kwargs_from_request(request)
|
|
29
|
+
|
|
30
|
+
return create_temp_file_response(
|
|
31
|
+
background_tasks = background_tasks,
|
|
32
|
+
download_callable = lambda temp_dir: SimpleProgressBarAnimatedMotionResource(
|
|
33
|
+
download_folder = temp_dir
|
|
34
|
+
).download_video(
|
|
35
|
+
stripes_speed = request.stripes_speed,
|
|
36
|
+
**animation_kwars
|
|
37
|
+
),
|
|
38
|
+
output_filename = 'simple_progress_bar.mov',
|
|
39
|
+
media_type = MEDIA_TYPE
|
|
40
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
from yta_fastapi_docker_motion_generator.app.services.utils import press_button_and_wait_downloaded
|
|
2
|
+
from yta_web_scraper.chrome import ChromeScraper
|
|
3
|
+
from yta_web_scraper.chrome.dataclasses.options_argument import NoSandboxChromeOptionsArgument, DisableDevShmUsageChromeOptionsArgument
|
|
4
|
+
from yta_web_scraper.chrome.dataclasses.experimental_options import SetDefaultDownloadDirectoryChromeExperimentalOption, SetDefaultSavefileDirectoryChromeExperimentalOption, DownloadDirectoryUpgradeChromeExperimentalOption, PromptForDownloadChromeExperimentalOption, SafebrowsingEnabledChromeExperimentalOption
|
|
5
|
+
from yta_general_utils.url.dataclasses import UrlParameters, UrlParameter
|
|
6
|
+
from yta_programming_env import Environment
|
|
7
|
+
from yta_validation.parameter import ParameterValidator
|
|
8
|
+
from typing import Union
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
TODO: This code could be refactored because the
|
|
16
|
+
`download_image` and `download_video` have a lot
|
|
17
|
+
of code that is duplicated. Please, do it.
|
|
18
|
+
"""
|
|
19
|
+
class MotionResource:
|
|
20
|
+
"""
|
|
21
|
+
A resource from the 'motion-generator-web' project
|
|
22
|
+
that can be scrapped to download it as an image.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
component_name: str,
|
|
28
|
+
do_use_gui: bool = False,
|
|
29
|
+
download_image_button_id: str = 'download-png',
|
|
30
|
+
# TODO: This hardcoded has to be changed
|
|
31
|
+
download_folder: str = 'C:/Users/dania/Downloads/',
|
|
32
|
+
timeout: float = 60.0
|
|
33
|
+
):
|
|
34
|
+
# self.url: str = 'http://localhost:5173/editor-playground'
|
|
35
|
+
# This below is for docker
|
|
36
|
+
self.url: str = Environment.get_env('MOTION_RESOURCES_DOCKER_ENDPOINT')
|
|
37
|
+
# This below is for local
|
|
38
|
+
# self.url: str = Environment.get_current_project_env('MOTION_RESOURCES_DOCKER_ENDPOINT')
|
|
39
|
+
"""
|
|
40
|
+
The url in which the resource is located.
|
|
41
|
+
"""
|
|
42
|
+
self.component_name: str = component_name
|
|
43
|
+
"""
|
|
44
|
+
The name of the component that identifies this
|
|
45
|
+
resource in the website and is able to load it
|
|
46
|
+
in order to modify and download it.
|
|
47
|
+
"""
|
|
48
|
+
self.do_use_gui: bool = do_use_gui
|
|
49
|
+
"""
|
|
50
|
+
Internal boolean variable to indicate if the
|
|
51
|
+
scraper will show or not the GUI when scrapping.
|
|
52
|
+
"""
|
|
53
|
+
self._download_image_button_id: str = download_image_button_id
|
|
54
|
+
"""
|
|
55
|
+
*For internal use only*
|
|
56
|
+
|
|
57
|
+
The id of the button to download the resource as
|
|
58
|
+
an image.
|
|
59
|
+
"""
|
|
60
|
+
self._download_folder: str = download_folder
|
|
61
|
+
"""
|
|
62
|
+
*For internal use only*
|
|
63
|
+
|
|
64
|
+
The name of the folder in which the scraper will
|
|
65
|
+
download the files.
|
|
66
|
+
"""
|
|
67
|
+
self.timeout: float = timeout
|
|
68
|
+
"""
|
|
69
|
+
The maximum amount of seconds that the scraper
|
|
70
|
+
will wait until the file is downloaded before
|
|
71
|
+
exiting with an error.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
download_path = os.path.abspath(download_folder)
|
|
75
|
+
|
|
76
|
+
additional_options = [
|
|
77
|
+
NoSandboxChromeOptionsArgument(),
|
|
78
|
+
DisableDevShmUsageChromeOptionsArgument(),
|
|
79
|
+
# The ones below are to control the downloads
|
|
80
|
+
SetDefaultDownloadDirectoryChromeExperimentalOption(download_path),
|
|
81
|
+
# SetDefaultSavefileDirectoryChromeExperimentalOption(download_path),
|
|
82
|
+
PromptForDownloadChromeExperimentalOption(False),
|
|
83
|
+
DownloadDirectoryUpgradeChromeExperimentalOption(True),
|
|
84
|
+
SafebrowsingEnabledChromeExperimentalOption(True)
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
self.scraper: ChromeScraper = ChromeScraper.init(
|
|
88
|
+
do_use_gui = do_use_gui,
|
|
89
|
+
additional_options = additional_options
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def _get_url(
|
|
93
|
+
self,
|
|
94
|
+
parameters: Union[UrlParameters, None]
|
|
95
|
+
) -> str:
|
|
96
|
+
"""
|
|
97
|
+
*For internal use only*
|
|
98
|
+
|
|
99
|
+
Get the url but including the `parameters`
|
|
100
|
+
provided.
|
|
101
|
+
"""
|
|
102
|
+
ParameterValidator.validate_instance_of('parameters', parameters, UrlParameters)
|
|
103
|
+
|
|
104
|
+
parameters = (
|
|
105
|
+
''
|
|
106
|
+
if parameters is None else
|
|
107
|
+
f'?{parameters.encoded}'
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return f'{self.url}{parameters}'
|
|
111
|
+
|
|
112
|
+
def _load(
|
|
113
|
+
self,
|
|
114
|
+
parameters: Union[UrlParameters, None] = None
|
|
115
|
+
) -> None:
|
|
116
|
+
"""
|
|
117
|
+
*For internal use only*
|
|
118
|
+
|
|
119
|
+
Navigates to the web page when not yet on it.
|
|
120
|
+
"""
|
|
121
|
+
url = self._get_url(parameters)
|
|
122
|
+
|
|
123
|
+
if self.scraper.current_url != url:
|
|
124
|
+
self.scraper.go_to_web_and_wait_until_loaded(url)
|
|
125
|
+
|
|
126
|
+
def reload(
|
|
127
|
+
self
|
|
128
|
+
) -> bool:
|
|
129
|
+
"""
|
|
130
|
+
Force a refresh in the web page to reload it.
|
|
131
|
+
"""
|
|
132
|
+
# TODO: It was previously like this below:
|
|
133
|
+
# return self.scraper.go_to_web_and_wait_until_loaded(self.url)
|
|
134
|
+
# Force to wait until completely loaded
|
|
135
|
+
return self.scraper.go_to_web_and_wait_until_loaded(self.scraper.current_url)
|
|
136
|
+
|
|
137
|
+
def _set_fields(
|
|
138
|
+
self,
|
|
139
|
+
fields: dict
|
|
140
|
+
) -> None:
|
|
141
|
+
"""
|
|
142
|
+
*For internal use only*
|
|
143
|
+
|
|
144
|
+
Set the `fields` provided for the web resource
|
|
145
|
+
by using the `.setField(name, value)` method
|
|
146
|
+
exposed in the web.
|
|
147
|
+
"""
|
|
148
|
+
# # Wait until 'setField' is available
|
|
149
|
+
# from yta_web_scraper.chrome import WebDriverWait
|
|
150
|
+
|
|
151
|
+
# WebDriverWait(self.scraper.driver, 30).until(
|
|
152
|
+
# lambda d: d.execute_script(
|
|
153
|
+
# "return typeof window.setField === 'function'"
|
|
154
|
+
# )
|
|
155
|
+
# )
|
|
156
|
+
|
|
157
|
+
for field_key in fields:
|
|
158
|
+
self.scraper.execute_script(
|
|
159
|
+
f'window.setField(arguments[0], arguments[1])',
|
|
160
|
+
field_key,
|
|
161
|
+
fields[field_key]
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def _set_animation_properties(
|
|
165
|
+
self,
|
|
166
|
+
duration: float = 1.0,
|
|
167
|
+
fps: int = 60
|
|
168
|
+
) -> None:
|
|
169
|
+
"""
|
|
170
|
+
*For internal use only*
|
|
171
|
+
|
|
172
|
+
Configure the video `duration` and `fps`
|
|
173
|
+
to download it later with these properties.
|
|
174
|
+
"""
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
def _set_frame(
|
|
178
|
+
self,
|
|
179
|
+
frame: int
|
|
180
|
+
) -> None:
|
|
181
|
+
"""
|
|
182
|
+
*For internal use only*
|
|
183
|
+
|
|
184
|
+
Configure the video `frame` in which we want
|
|
185
|
+
to position the resource. Useful when we want
|
|
186
|
+
to obtain an image of a specific frame.
|
|
187
|
+
"""
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
def _download(
|
|
191
|
+
self,
|
|
192
|
+
fields: dict,
|
|
193
|
+
download_button_id: str,
|
|
194
|
+
duration: float = 1.0,
|
|
195
|
+
fps: int = 60,
|
|
196
|
+
frame: int = 0,
|
|
197
|
+
output_filename: Union[str, None] = None
|
|
198
|
+
) -> 'Path':
|
|
199
|
+
"""
|
|
200
|
+
*For internal use only*
|
|
201
|
+
|
|
202
|
+
Download the resource as an image or as a video
|
|
203
|
+
depending on the `download_button_id` provided.
|
|
204
|
+
"""
|
|
205
|
+
# 1st. Go to the webpage
|
|
206
|
+
self._load(
|
|
207
|
+
parameters = UrlParameters([
|
|
208
|
+
UrlParameter(
|
|
209
|
+
name = 'component',
|
|
210
|
+
value = self.component_name
|
|
211
|
+
)
|
|
212
|
+
])
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# 2nd. Set fields if needed
|
|
216
|
+
self._set_fields(fields)
|
|
217
|
+
|
|
218
|
+
# 3rd. Set animation properties
|
|
219
|
+
self._set_animation_properties(
|
|
220
|
+
duration = duration,
|
|
221
|
+
fps = fps
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# 4th. Set frame if needed
|
|
225
|
+
self._set_frame(frame)
|
|
226
|
+
|
|
227
|
+
# 5th. Download the resource
|
|
228
|
+
button = self.scraper.find_element_by_id_waiting(download_button_id)
|
|
229
|
+
downloaded_file_path: 'Path' = press_button_and_wait_downloaded(
|
|
230
|
+
button = button,
|
|
231
|
+
download_dir = self._download_folder,
|
|
232
|
+
timeout = self.timeout
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# 6th. Move to the 'output_filename' if provided
|
|
236
|
+
output_path = (
|
|
237
|
+
shutil.move(
|
|
238
|
+
downloaded_file_path,
|
|
239
|
+
# TODO: What if the extension is wrong (?)
|
|
240
|
+
output_filename
|
|
241
|
+
)
|
|
242
|
+
if output_filename else
|
|
243
|
+
downloaded_file_path
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
return output_path
|
|
247
|
+
|
|
248
|
+
def download_image(
|
|
249
|
+
self,
|
|
250
|
+
fields: dict,
|
|
251
|
+
output_filename: Union[str, None] = None
|
|
252
|
+
) -> str:
|
|
253
|
+
"""
|
|
254
|
+
Download the resource as an image with the
|
|
255
|
+
given `fields` and stored as the
|
|
256
|
+
`output_filename` provided.
|
|
257
|
+
"""
|
|
258
|
+
return self._download(
|
|
259
|
+
fields = fields,
|
|
260
|
+
download_button_id = self._download_image_button_id,
|
|
261
|
+
duration = None,
|
|
262
|
+
fps = None,
|
|
263
|
+
output_filename = output_filename
|
|
264
|
+
)
|
|
265
|
+
# 1st. Go to the webpage
|
|
266
|
+
self._load(
|
|
267
|
+
parameters = UrlParameters([
|
|
268
|
+
UrlParameter(
|
|
269
|
+
name = 'component',
|
|
270
|
+
value = self.component_name
|
|
271
|
+
)
|
|
272
|
+
])
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# 2nd. Set fields if needed
|
|
276
|
+
self._set_fields(fields)
|
|
277
|
+
|
|
278
|
+
# 3rd. Ask for the download
|
|
279
|
+
button = self.scraper.find_element_by_id_waiting(self._download_image_button_id)
|
|
280
|
+
downloaded_file_path: 'Path' = press_button_and_wait_downloaded(
|
|
281
|
+
button = button,
|
|
282
|
+
download_dir = self._download_folder,
|
|
283
|
+
timeout = self.timeout
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# 4th. Move to the 'output_filename' if provided
|
|
287
|
+
output_path = (
|
|
288
|
+
shutil.move(
|
|
289
|
+
downloaded_file_path,
|
|
290
|
+
# TODO: What if the extension is wrong (?)
|
|
291
|
+
output_filename
|
|
292
|
+
)
|
|
293
|
+
if output_filename else
|
|
294
|
+
downloaded_file_path
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
return output_path
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class AnimatedMotionResource(MotionResource):
|
|
301
|
+
"""
|
|
302
|
+
A resource from the 'motion-generator-web' project
|
|
303
|
+
that can be scrapped to download it as an image or
|
|
304
|
+
a video.
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
def __init__(
|
|
308
|
+
self,
|
|
309
|
+
component_name: str,
|
|
310
|
+
do_use_gui: bool = False,
|
|
311
|
+
download_image_button_id: str = 'download-png',
|
|
312
|
+
download_video_button_id: str = 'download-video',
|
|
313
|
+
# TODO: This hardcoded has to be changed
|
|
314
|
+
download_folder: str = 'C:/Users/dania/Downloads/',
|
|
315
|
+
timeout: float = 60.0
|
|
316
|
+
):
|
|
317
|
+
super().__init__(
|
|
318
|
+
component_name = component_name,
|
|
319
|
+
do_use_gui = do_use_gui,
|
|
320
|
+
download_image_button_id = download_image_button_id,
|
|
321
|
+
download_folder = download_folder,
|
|
322
|
+
timeout = timeout
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
self._download_video_button_id: str = download_video_button_id
|
|
326
|
+
"""
|
|
327
|
+
*For internal use only*
|
|
328
|
+
|
|
329
|
+
The id of the button to download the resource as
|
|
330
|
+
a video.
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
def _set_animation_properties(
|
|
334
|
+
self,
|
|
335
|
+
duration: float = 1.0,
|
|
336
|
+
fps: int = 60
|
|
337
|
+
) -> None:
|
|
338
|
+
"""
|
|
339
|
+
*For internal use only*
|
|
340
|
+
|
|
341
|
+
Configure the video `duration` and `fps`
|
|
342
|
+
to download it later with these properties.
|
|
343
|
+
"""
|
|
344
|
+
self.scraper.execute_script(f'window.configureAnimation({{fps: {fps}, duration: {duration}}})')
|
|
345
|
+
|
|
346
|
+
def _set_frame(
|
|
347
|
+
self,
|
|
348
|
+
frame: int
|
|
349
|
+
) -> None:
|
|
350
|
+
"""
|
|
351
|
+
*For internal use only*
|
|
352
|
+
|
|
353
|
+
Configure the video `frame` in which we want
|
|
354
|
+
to position the resource. Useful when we want
|
|
355
|
+
to obtain an image of a specific frame.
|
|
356
|
+
"""
|
|
357
|
+
self.scraper.execute_script(
|
|
358
|
+
f'window.setFrame(arguments[0])',
|
|
359
|
+
frame
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
def download_image(
|
|
363
|
+
self,
|
|
364
|
+
fields: dict,
|
|
365
|
+
duration: float = 1.0,
|
|
366
|
+
fps: int = 60,
|
|
367
|
+
frame: int = 0,
|
|
368
|
+
output_filename: Union[str, None] = None
|
|
369
|
+
):
|
|
370
|
+
return super()._download(
|
|
371
|
+
fields = fields,
|
|
372
|
+
download_button_id = self._download_image_button_id,
|
|
373
|
+
duration = duration,
|
|
374
|
+
fps = fps,
|
|
375
|
+
frame = frame,
|
|
376
|
+
output_filename = output_filename
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
def download_video(
|
|
380
|
+
self,
|
|
381
|
+
fields: dict,
|
|
382
|
+
duration: float = 1.0,
|
|
383
|
+
fps: int = 60,
|
|
384
|
+
output_filename: Union[str, None] = None
|
|
385
|
+
) -> str:
|
|
386
|
+
"""
|
|
387
|
+
Download the resource as a video of
|
|
388
|
+
`duration` seconds, with `fps`, the
|
|
389
|
+
given `fields` and stored as the
|
|
390
|
+
`output_filename` provided.
|
|
391
|
+
"""
|
|
392
|
+
return super()._download(
|
|
393
|
+
fields = fields,
|
|
394
|
+
download_button_id = self._download_video_button_id,
|
|
395
|
+
duration = duration,
|
|
396
|
+
fps = fps,
|
|
397
|
+
frame = 0,
|
|
398
|
+
output_filename = output_filename
|
|
399
|
+
)
|
|
400
|
+
# 1st. Go to the webpage
|
|
401
|
+
self._load(
|
|
402
|
+
parameters = UrlParameters([
|
|
403
|
+
UrlParameter(
|
|
404
|
+
name = 'component',
|
|
405
|
+
value = self.component_name
|
|
406
|
+
)
|
|
407
|
+
])
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# 2nd. Set fields if needed
|
|
411
|
+
self._set_fields(fields)
|
|
412
|
+
|
|
413
|
+
# 3rd. Set animation properties
|
|
414
|
+
self._set_animation_properties(
|
|
415
|
+
duration = duration,
|
|
416
|
+
fps = fps
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
# 3rd. Ask for the download
|
|
420
|
+
button = self.scraper.find_element_by_id_waiting(self._download_video_button_id)
|
|
421
|
+
downloaded_file_path: 'Path' = press_button_and_wait_downloaded(
|
|
422
|
+
button = button,
|
|
423
|
+
download_dir = self._download_folder,
|
|
424
|
+
timeout = self.timeout
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
# TODO: This should be a parameter in a reusable method
|
|
428
|
+
# TODO: Why if respecting not the extension (?)
|
|
429
|
+
# output_filename = f'test_files/{downloaded_file_path.name}'
|
|
430
|
+
|
|
431
|
+
# TODO: Not sure if this will work in Docker
|
|
432
|
+
# 4th. Move to the 'output_filename' provided
|
|
433
|
+
shutil.move(
|
|
434
|
+
downloaded_file_path,
|
|
435
|
+
output_filename
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
return output_filename
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from yta_fastapi_docker_motion_generator.app.services.motion_resources import MotionResource, AnimatedMotionResource
|
|
2
|
+
from yta_programming_env import Environment
|
|
3
|
+
from typing import Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BookingReviewMotionResource(MotionResource):
|
|
7
|
+
"""
|
|
8
|
+
A static resource that is a Booking Review.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
download_folder: str = 'C:/Users/dania/Downloads/',
|
|
14
|
+
timeout: float = 60.0
|
|
15
|
+
):
|
|
16
|
+
super().__init__(
|
|
17
|
+
component_name = 'booking-review',
|
|
18
|
+
do_use_gui = False,
|
|
19
|
+
download_image_button_id = 'download-png',
|
|
20
|
+
download_folder = download_folder,
|
|
21
|
+
timeout = timeout
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def download_image(
|
|
25
|
+
self,
|
|
26
|
+
name: str = 'María',
|
|
27
|
+
country: str = 'España',
|
|
28
|
+
score: str = '9.2',
|
|
29
|
+
scoreLabel: str = 'Fantástico',
|
|
30
|
+
title: str = 'Excelente ubicación',
|
|
31
|
+
text: str = 'El hotel estaba muy limpio, el personal fue muy amable y la ubicación era perfecta para visitar el centro de la ciudad.',
|
|
32
|
+
date: str = '14 de mayo de 2026',
|
|
33
|
+
output_filename: Union[str, None] = None
|
|
34
|
+
):
|
|
35
|
+
fields = {
|
|
36
|
+
'name': name,
|
|
37
|
+
'country': country,
|
|
38
|
+
'score': score,
|
|
39
|
+
'scoreLabel': scoreLabel,
|
|
40
|
+
'title': title,
|
|
41
|
+
'text': text,
|
|
42
|
+
'date': date
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return super().download_image(
|
|
46
|
+
fields = fields,
|
|
47
|
+
output_filename = output_filename
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SimpleProgressBarAnimatedMotionResource(AnimatedMotionResource):
|
|
52
|
+
"""
|
|
53
|
+
An animated progress bar
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
download_folder: str = 'C:/Users/dania/Downloads/',
|
|
59
|
+
timeout: float = 60.0
|
|
60
|
+
):
|
|
61
|
+
super().__init__(
|
|
62
|
+
component_name = 'simple-progress-bar',
|
|
63
|
+
do_use_gui = False,
|
|
64
|
+
download_image_button_id = 'download-png',
|
|
65
|
+
download_video_button_id = 'download-video',
|
|
66
|
+
download_folder = download_folder,
|
|
67
|
+
timeout = timeout
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def download_image(
|
|
71
|
+
self,
|
|
72
|
+
stripes_speed: float = 1.0,
|
|
73
|
+
duration: float = 1.0,
|
|
74
|
+
fps: int = 60,
|
|
75
|
+
frame: int = 0,
|
|
76
|
+
output_filename: Union[str, None] = None,
|
|
77
|
+
):
|
|
78
|
+
fields = {
|
|
79
|
+
'stripesSpeed': stripes_speed,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return super().download_image(
|
|
83
|
+
fields = fields,
|
|
84
|
+
duration = duration,
|
|
85
|
+
fps = fps,
|
|
86
|
+
frame = frame,
|
|
87
|
+
output_filename = output_filename
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def download_video(
|
|
91
|
+
self,
|
|
92
|
+
stripes_speed: float = 1.0,
|
|
93
|
+
duration: float = 1.0,
|
|
94
|
+
fps: int = 60,
|
|
95
|
+
output_filename: Union[str, None] = None,
|
|
96
|
+
):
|
|
97
|
+
fields = {
|
|
98
|
+
'stripesSpeed': stripes_speed,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return super().download_video(
|
|
102
|
+
fields = fields,
|
|
103
|
+
duration = duration,
|
|
104
|
+
fps = fps,
|
|
105
|
+
output_filename = output_filename
|
|
106
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def press_button_and_wait_downloaded(
|
|
7
|
+
button: 'WebElement',
|
|
8
|
+
download_dir: str,
|
|
9
|
+
timeout: float = 60.0
|
|
10
|
+
) -> Path:
|
|
11
|
+
"""
|
|
12
|
+
Press the `button` and waits until the file
|
|
13
|
+
has been downloaded.
|
|
14
|
+
|
|
15
|
+
This method will check the files that were
|
|
16
|
+
existing before pressing the button and will
|
|
17
|
+
wait until the new one appears (and it is a
|
|
18
|
+
complete download and not a temporary file).
|
|
19
|
+
|
|
20
|
+
The waiting process will last `timeout`
|
|
21
|
+
seconds as maximum.
|
|
22
|
+
|
|
23
|
+
This method will return the downloaded file
|
|
24
|
+
path.
|
|
25
|
+
"""
|
|
26
|
+
downloads = Path(download_dir)
|
|
27
|
+
|
|
28
|
+
files_before_download = {
|
|
29
|
+
f.name
|
|
30
|
+
for f in downloads.iterdir()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
button.click()
|
|
34
|
+
time_elapsed = 0.0
|
|
35
|
+
time_sleep_interval = 0.5
|
|
36
|
+
|
|
37
|
+
while True:
|
|
38
|
+
current = list(downloads.iterdir())
|
|
39
|
+
|
|
40
|
+
new_files = [
|
|
41
|
+
f
|
|
42
|
+
for f in current
|
|
43
|
+
if f.name not in files_before_download
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
files_completed = [
|
|
47
|
+
f for f in new_files
|
|
48
|
+
if (
|
|
49
|
+
not f.name.endswith('.crdownload') and
|
|
50
|
+
not f.name.endswith('.tmp')
|
|
51
|
+
)
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
if files_completed:
|
|
55
|
+
downloaded_file = files_completed[0]
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
time.sleep(time_sleep_interval)
|
|
59
|
+
time_elapsed += time_sleep_interval
|
|
60
|
+
|
|
61
|
+
if (time_elapsed >= timeout):
|
|
62
|
+
# TODO: Raise an exception (?)
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
return downloaded_file
|