Skip to content

Commit da830fe

Browse files
authored
Merge pull request #20 from JexPY/feature/GetDataFromUrl
Feature/get data from url
2 parents 821803e + a73e56a commit da830fe

File tree

14 files changed

+195
-56
lines changed

14 files changed

+195
-56
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ certbot/letsecrypt/
88
key.json
99
docker-compose.yml
1010
nginx/sites/app.conf
11-
static/
1211
__pycache__/
1312
*.pyc
1413
.pytest_cache

api/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ WORKDIR $WORKDIR
99
RUN apt-get update && apt-get install -y python3-dev build-essential
1010

1111
# Image modifications
12-
RUN apt-get install -y libtiff5-dev libjpeg62-turbo-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev
12+
RUN apt-get install -y libtiff5-dev libjpeg62-turbo-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev libmagic1
1313

1414
RUN if [ ! -e /lib/libz.so ]; then \
1515
ln -s /usr/lib/x86_64-linux-gnu/libz.so /lib/ \

api/app/.env-example

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# SERVER
22
API_URL=http://localhost/
3-
FILE_MANAGER_BEARER_TOKEN=very_secret
3+
# several separate with ,
4+
FILE_MANAGER_BEARER_TOKEN=very_secret,any_more_secret_tokens
45
# several separate with ,
56
CORS_ORIGINS=http://localhost,http://ff.etomer.io
67

@@ -55,12 +56,13 @@ QR_IMAGE_LOCAL_PATH=static/qr/
5556

5657
# IMAGES
5758
IMAGE_CONVERTING_PREFERED_FORMAT=webp
58-
THUMBNAIL_MAX_WIDHT=320
59+
IMAGE_AllOWED_FILE_FORMAT=png,jpeg,jpg,webp
5960
# make thumbnails or nah True/False
6061
IMAGE_THUMBNAIL=True
62+
THUMBNAIL_MAX_WIDHT=320
6163
# suported:pillow-simd,ffmpeg
6264
# default:pillow-simd
63-
IMAGE_OPTIMIZATION_USING=ffmpeg
65+
IMAGE_OPTIMIZATION_USING=pillow-simd
6466

6567

6668
# VIDEOS

api/app/main.py

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@
1111
import sys
1212

1313
from services.serveUploadedFiles import handle_upload_image_file, handle_multiple_image_file_uploads, handle_upload_video_file
14-
from services.serverQrcode import handle_qr_code
14+
from services.serveQrcode import handle_qr_code
1515
from services.security.customBearerCheck import validate_token
1616
from services.storage.local import response_image_file
17+
from services.serveDataFromUrl import handle_download_data_from_url, handle_multiple_image_file_downloads
1718

1819
load_dotenv()
1920
app = FastAPI(docs_url=None if os.environ.get('docs_url') == 'None' else '/docs', redoc_url=None if os.environ.get('redoc_url') == 'None' else '/redoc')
@@ -50,6 +51,7 @@ def root(
5051
result['cpu_average_load'] = os.getloadavg()
5152
return result
5253

54+
5355
# File size validates NGINX
5456
@app.post("/image", tags=["image"])
5557
async def upload_image_file(
@@ -64,14 +66,23 @@ async def upload_image_file(
6466

6567

6668
@app.post("/images", tags=["image"])
67-
async def upload_image_files(files: List[UploadFile] = File(...), OAuth2AuthorizationCodeBearer = Depends(validate_token)):
69+
async def upload_image_files(
70+
thumbnail: Optional[str] = Query(
71+
os.environ.get('IMAGE_THUMBNAIL'),
72+
description='True/False depending your needs',
73+
regex='^(True|False)$'
74+
),
75+
files: List[UploadFile] = File(...),
76+
OAuth2AuthorizationCodeBearer = Depends(validate_token)
77+
):
6878
fileAmount = len(files)
6979
if fileAmount > int(os.environ.get('MULTIPLE_FILE_UPLOAD_LIMIT')):
7080
raise HTTPException(
7181
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
7282
detail='Amount of files must not be more than {}'.format(os.environ.get('MULTIPLE_FILE_UPLOAD_LIMIT'))
7383
)
74-
return handle_multiple_image_file_uploads(files, fileAmount)
84+
return handle_multiple_image_file_uploads(files, fileAmount, True if thumbnail == 'True' else False)
85+
7586

7687
@app.get("/image", tags=["image"])
7788
async def get_image(
@@ -88,6 +99,7 @@ async def get_image(
8899
):
89100
return response_image_file(image, image_type)
90101

102+
91103
@app.post("/qrImage", tags=["image"])
92104
async def text_to_generate_qr_image(
93105
qr_text: str = Query(
@@ -112,4 +124,53 @@ async def upload_video_file(
112124
),
113125
file: UploadFile = File(..., description='Allows mov, mp4, m4a, 3gp, 3g2, mj2'),
114126
OAuth2AuthorizationCodeBearer = Depends(validate_token)):
115-
return handle_upload_video_file(True if optimize == 'True' else False, file)
127+
return handle_upload_video_file(True if optimize == 'True' else False, file)
128+
129+
130+
@app.get("/imageUrl", tags=["from url"])
131+
async def image_from_url(
132+
image_url: str = Query(
133+
None,
134+
description = "Pass valid image url to upload",
135+
min_length = 5
136+
),
137+
thumbnail: Optional[str] = Query(
138+
os.environ.get('IMAGE_THUMBNAIL'),
139+
description='True/False depending your needs',
140+
regex='^(True|False)$'
141+
),
142+
OAuth2AuthorizationCodeBearer = Depends(validate_token)):
143+
return handle_download_data_from_url(image_url, True if thumbnail == 'True' else False, file_type='image')
144+
145+
146+
@app.get("/imageUrls", tags=["from url"])
147+
async def images_from_urls(
148+
image_urls: List[str] = Query(
149+
None,
150+
description = "Pass valid image urls to upload",
151+
min_length = 5
152+
),
153+
OAuth2AuthorizationCodeBearer = Depends(validate_token)):
154+
fileAmount = len(image_urls)
155+
if fileAmount > int(os.environ.get('MULTIPLE_FILE_UPLOAD_LIMIT')):
156+
raise HTTPException(
157+
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
158+
detail='Amount of files must not be more than {}'.format(os.environ.get('MULTIPLE_FILE_UPLOAD_LIMIT'))
159+
)
160+
return handle_multiple_image_file_downloads(image_urls, fileAmount)
161+
162+
163+
@app.get("/videoUrl", tags=["from url"])
164+
async def video_from_url(
165+
video_url: str = Query(
166+
None,
167+
description = "Pass valid video url to upload",
168+
min_length = 5
169+
),
170+
optimize: Optional[str] = Query(
171+
os.environ.get('VIDEO_OPTIMIZE'),
172+
description='True/False depending your needs default is {}'.format(os.environ.get('VIDEO_OPTIMIZE')),
173+
regex='^(True|False)$'
174+
),
175+
OAuth2AuthorizationCodeBearer = Depends(validate_token)):
176+
return handle_download_data_from_url(video_url, False, True if optimize == 'True' else False, file_type='video')
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import magic
2+
import mimetypes
3+
from pathlib import Path
4+
from fastapi import HTTPException,status
5+
6+
def magic_extensions(file_path: Path):
7+
mime = magic.Magic(mime=True)
8+
detectedExtension = mimetypes.guess_all_extensions(mime.from_buffer(open(file_path, "rb").read(2048)))
9+
if len(detectedExtension) > 0:
10+
return detectedExtension[0]
11+
else:
12+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='File extension not detected')

api/app/services/security/customBearerCheck.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import os
2+
import json
23
import secrets
34
from fastapi.security import HTTPBearer,OAuth2AuthorizationCodeBearer,HTTPBasicCredentials
45
from fastapi import Depends, HTTPException, status
5-
66
security = HTTPBearer()
77

88
def validate_token(credentials: HTTPBasicCredentials = Depends(security)):
9-
correct_token = secrets.compare_digest(credentials.credentials, os.environ.get('FILE_MANAGER_BEARER_TOKEN'))
10-
if not (correct_token):
9+
10+
for eachKey in os.environ.get('FILE_MANAGER_BEARER_TOKEN').split(','):
11+
if secrets.compare_digest(credentials.credentials, eachKey):
12+
return True
13+
else:
1114
raise HTTPException(
1215
status_code=status.HTTP_401_UNAUTHORIZED,
1316
detail="Incorrect token"
1417
)
15-
return True
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import requests
2+
import concurrent.futures
3+
from typing import List
4+
from fastapi import HTTPException,status,Query
5+
from .serveUploadedFiles import handle_upload_image_file,handle_upload_video_file
6+
import sys
7+
8+
def handle_download_data_from_url(url = str, thumbnail = bool, optimize = None, file_type = str):
9+
try:
10+
get_data = requests.get(url, stream = True)
11+
if get_data.status_code == 200:
12+
get_data.raw.decode_content = True
13+
if file_type == 'image':
14+
return handle_upload_image_file(thumbnail, upload_file=None, raw_data_file=get_data)
15+
elif file_type =='video':
16+
return handle_upload_video_file(optimize, upload_file=None, raw_data_file=get_data)
17+
else:
18+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=f'Unsuccessful request for: {url}')
19+
except:
20+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=f'Unable to download data from: {url}')
21+
22+
23+
def handle_multiple_image_file_downloads(URL_FILES: List[Query], workers: int, thumbnail = bool):
24+
# We can use a with statement to ensure threads are cleaned up promptly
25+
with concurrent.futures.ThreadPoolExecutor(max_workers = workers) as executor:
26+
27+
future_to_url = {executor.submit(handle_download_data_from_url, eachFile, thumbnail, file_type = 'image'): eachFile for eachFile in URL_FILES}
28+
result = []
29+
for future in concurrent.futures.as_completed(future_to_url):
30+
try:
31+
result.append(future.result())
32+
except:
33+
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='Multiple upload failed')
34+
return result
File renamed without changes.

api/app/services/serveUploadedFiles.py

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
import shutil
3-
import imghdr
43
import _thread
54
import copy
65
from typing import List
@@ -12,27 +11,40 @@
1211
from fastapi import UploadFile,HTTPException,status
1312
from .storage.googleCloud import upload_image_file_to_google_storage,upload_video_file_to_google_storage
1413
from .storage.s3 import upload_image_file_to_s3_storage,upload_video_file_to_s3_storage
15-
from ffmpeg import probe
14+
from .helpers.detectFileExtension import magic_extensions
15+
import sys
1616

17-
def save_upload_file_tmp(upload_file: UploadFile) -> Path:
17+
def save_upload_file_tmp(upload_file: None, raw_data_file = None) -> Path:
1818
try:
19-
suffix = Path(upload_file.filename).suffix
20-
with NamedTemporaryFile(delete=False, suffix=suffix) as tmp:
21-
shutil.copyfileobj(upload_file.file, tmp)
22-
tmp_path = Path(tmp.name)
19+
if raw_data_file:
20+
with NamedTemporaryFile(delete=False, suffix=None) as tmp:
21+
shutil.copyfileobj(raw_data_file.raw, tmp)
22+
23+
else:
24+
with NamedTemporaryFile(delete=False, suffix=None) as tmp:
25+
shutil.copyfileobj(upload_file.file, tmp)
26+
27+
extension = magic_extensions(Path(tmp.name))
28+
final_temp_file = tmp.name + extension
29+
os.rename(Path(tmp.name), final_temp_file)
30+
2331
except:
2432
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='Impossible to manipulate the file')
2533
finally:
26-
upload_file.file.close()
27-
return tmp_path
34+
if upload_file:
35+
upload_file.file.close()
36+
else:
37+
raw_data_file.close()
38+
return Path(final_temp_file), extension
2839

2940

30-
def handle_upload_image_file(thumbnail, upload_file: UploadFile):
41+
def handle_upload_image_file(thumbnail, upload_file: None, raw_data_file = None):
3142
try:
32-
tmp_path = save_upload_file_tmp(upload_file)
33-
if imghdr.what(tmp_path):
43+
tmp_path, file_extension = save_upload_file_tmp(upload_file, raw_data_file)
44+
45+
if file_extension[1:] in os.environ.get('IMAGE_AllOWED_FILE_FORMAT').split(','):
3446

35-
imagePaths = resize_image(tmp_path, imghdr.what(tmp_path), thumbnail, os.environ.get('IMAGE_CONVERTING_PREFERED_FORMAT'))
47+
imagePaths = resize_image(tmp_path, file_extension[1:], thumbnail, os.environ.get('IMAGE_CONVERTING_PREFERED_FORMAT'))
3648

3749
if os.environ.get('PREFERED_STORAGE') == 'google':
3850
_thread.start_new_thread(upload_image_file_to_google_storage, (copy.deepcopy(imagePaths),))
@@ -49,18 +61,18 @@ def handle_upload_image_file(thumbnail, upload_file: UploadFile):
4961
imagePaths['thumbnail'] = os.environ.get('API_URL') + os.environ.get('IMAGE_THUMBNAIL_LOCAL_PATH') + imagePaths['thumbnail'] if imagePaths.get('thumbnail') else None
5062

5163
imagePaths['storage'] = os.environ.get('PREFERED_STORAGE')
64+
imagePaths['file_name'] = imagePaths['original'].split('/')[-1]
5265
return imagePaths
5366
else:
5467
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='The file format not supported')
5568
finally:
5669
tmp_path.unlink() # Delete the temp file
5770

5871

59-
def handle_multiple_image_file_uploads(FILES: List[UploadFile], workers: int):
72+
def handle_multiple_image_file_uploads(FILES: List[UploadFile], workers: int, thumbnail: bool):
6073
# We can use a with statement to ensure threads are cleaned up promptly
6174
with concurrent.futures.ThreadPoolExecutor(max_workers = workers) as executor:
62-
# Start the load operations and mark each future with its FILES thumbnail is default for this function
63-
future_to_url = {executor.submit(handle_upload_image_file, True, eachFile): eachFile for eachFile in FILES}
75+
future_to_url = {executor.submit(handle_upload_image_file, eachFile, thumbnail): eachFile for eachFile in FILES}
6476
result = []
6577
for future in concurrent.futures.as_completed(future_to_url):
6678
try:
@@ -70,15 +82,12 @@ def handle_multiple_image_file_uploads(FILES: List[UploadFile], workers: int):
7082
return result
7183

7284

73-
def handle_upload_video_file(optimize, upload_file: UploadFile):
85+
def handle_upload_video_file(optimize, upload_file: None, raw_data_file = None):
86+
7487
try:
75-
tmp_path = save_upload_file_tmp(upload_file)
76-
videoFileCheck = probe(tmp_path).get('format')
77-
if videoFileCheck.get('format_name'):
78-
# Checks for video file type also is possible to restict for only mp4 format by checking major_brand
79-
# videoFileCheck.get('tags').get('major_brand') == 'mp42'
80-
if os.environ.get('VIDEO_AllOWED_FILE_FORMAT') in videoFileCheck.get('format_name').split(','):
88+
tmp_path, file_extension = save_upload_file_tmp(upload_file, raw_data_file)
8189

90+
if file_extension[1:] in os.environ.get('VIDEO_AllOWED_FILE_FORMAT').split(','):
8291
videoPaths = video_file_FFMPEG(tmp_path, optimize)
8392

8493
if os.environ.get('PREFERED_STORAGE') == 'google':
@@ -98,10 +107,9 @@ def handle_upload_video_file(optimize, upload_file: UploadFile):
98107
videoPaths['optimized'] = os.environ.get('API_URL') + os.environ.get('VIDEO_OPTIMIZED_LOCAL_PATH') + videoPaths['optimized'] if videoPaths.get('optimized') else None
99108

100109
videoPaths['storage'] = os.environ.get('PREFERED_STORAGE')
110+
videoPaths['file_name'] = videoPaths['original'].split('/')[-1]
101111

102112
return videoPaths
103-
else:
104-
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='Format not granted')
105113
else:
106114
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail='Not valid format')
107115
except:
143 KB
Loading

0 commit comments

Comments
 (0)