JuanBohorquez commited on
Commit
fb1970b
·
1 Parent(s): 3d4de2d

Update: dockerfile and main.py

Browse files
Files changed (3) hide show
  1. Dockerfile +8 -6
  2. app.py +0 -54
  3. app/main.py +134 -0
Dockerfile CHANGED
@@ -1,11 +1,13 @@
1
- FROM python:3.9
2
 
3
- WORKDIR /code
4
 
5
- COPY ./requirements.txt /code/requirements.txt
 
6
 
7
- RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
8
 
9
- COPY . .
10
 
11
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
1
+ FROM python:3.10-slim
2
 
3
+ WORKDIR /app
4
 
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
 
8
+ COPY app ./app
9
 
10
+ EXPOSE 7860
11
 
12
+
13
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
app.py DELETED
@@ -1,54 +0,0 @@
1
- import os
2
- import requests
3
- from fastapi import FastAPI
4
-
5
- app = FastAPI()
6
-
7
-
8
- @app.get("/")
9
- def home():
10
- return {"app": "ok"}
11
-
12
- @app.get("/test")
13
- def test():
14
- payload = {
15
- 'tester': 'test'
16
- }
17
- st, id = create_synthesia_video(payload)
18
- return {"status": st, "id": id}
19
-
20
-
21
- def create_synthesia_video(payload:dict) -> tuple:
22
- """ Sends POST requests to create video from template in Synthesia.
23
- :param dict payload: Request payload (title, templateId, templateData)
24
- :returns (tuple staus, video_id)
25
- """
26
-
27
- url = "https://juanbohorquez-clip-generator-v3.hf.space/api/webhook"
28
- tester = os.environ["TESTER"]
29
- headers = {
30
- 'Authorization': f"Bearer {tester}",
31
- 'Content-Type': 'application/json'
32
- }
33
- title = payload.get('title', '')
34
- status, video_id = 500, None
35
- try:
36
- response = requests.post(url, json=payload, headers=headers)
37
- status = response.status_code
38
- if response.ok:
39
- data = response.json()
40
- video_id = data.get('id', None)
41
- else:
42
- print(f"Failed to create {title}. Status: {status}, Reason: {response.reason}")
43
-
44
- except requests.exceptions.ConnectionError as conn_err:
45
- print(f"Error creating {title}. Connection error occurred: {conn_err}")
46
- except requests.exceptions.Timeout as timeout_err:
47
- print(f"Error creating {title}. Timeout error occurred: {timeout_err}")
48
- except ValueError as json_err:
49
- print(f"Error creating {title}. JSON decoding error: {json_err}")
50
- except Exception as e:
51
- print(f"Error creating {title}. Error: {e}")
52
-
53
- finally:
54
- return status, video_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/main.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uvicorn
3
+ import requests
4
+ import hashlib
5
+ import hmac
6
+ import time
7
+ import logging
8
+ from fastapi import FastAPI, Request, HTTPException
9
+ # from dotenv import load_dotenv # DEV
10
+ # load_dotenv() # DEV
11
+
12
+ log = logging.getLogger('uvicorn.error')
13
+
14
+ app = FastAPI()
15
+
16
+ @app.get("/")
17
+ def home():
18
+ return {"app": "ok"}
19
+
20
+
21
+ @app.post("/synthesia_webhook")
22
+ async def synthesia_webhook(request: Request):
23
+ """
24
+ Webhook to handle Synthesia events. Verifies signature, processes payload,
25
+ and forwards data to app API while logging issues for failures.
26
+ """
27
+ try:
28
+ # Extract and validate headers
29
+ request_timestamp = request.headers.get("Synthesia-Timestamp")
30
+ request_signature = request.headers.get("Synthesia-Signature")
31
+ if not request_timestamp or not request_signature:
32
+ raise HTTPException(status_code=401, detail="Unauthorized: Missing required headers")
33
+
34
+ # Validate the signature
35
+ request_body = (await request.body()).decode("utf-8")
36
+ message = f"{request_timestamp}.{request_body}"
37
+ if not validate_signature(message, request_signature):
38
+ raise HTTPException(status_code=401, detail="Unauthorized: Invalid signature")
39
+
40
+ # Parse and validate payload
41
+ request_data = await request.json()
42
+ data = request_data.get('data', {})
43
+ title = data.get('title', "Unknown")
44
+ video_status = request_data.get('type', 'video.failed')
45
+
46
+ if not data or video_status not in ['video.failed', 'video.completed']:
47
+ log.error(f"Unexpected payload received: {request_data}")
48
+ raise HTTPException(status_code=400, detail="Bad request: Invalid payload structure")
49
+
50
+ # Forward data to app's API
51
+ payload = {'data': data, 'video_status': video_status}
52
+ status = await forward_video_data(payload)
53
+ if status >= 400:
54
+ log.warning(f"Failed to forward data for video '{title}' with status {status}")
55
+
56
+ except HTTPException as http_exc:
57
+ log.error(f"HTTP exception occurred: {http_exc.detail}")
58
+ raise http_exc
59
+
60
+ except Exception as e:
61
+ log.error(f"Unexpected error: {e}")
62
+ raise HTTPException(status_code=500, detail="Internal server error")
63
+
64
+ return {"message": "ok"}
65
+
66
+
67
+
68
+ def get_signature(message):
69
+ key = os.getenv('SYNTHESIA_WEBHOOK_SECRET', '')
70
+ signature = None
71
+ try:
72
+ signature = hmac.new(
73
+ key.encode("utf-8"),
74
+ message.encode("utf-8"),
75
+ hashlib.sha256,
76
+ ).hexdigest()
77
+ except Exception as e:
78
+ log.error(f"Error getting signature: {e}")
79
+
80
+ finally:
81
+ return signature
82
+
83
+
84
+ def validate_signature(message, request_signature):
85
+ valid = False
86
+ try:
87
+ system_signature = get_signature(message)
88
+ log.info((system_signature, request_signature))
89
+ valid = system_signature == request_signature
90
+ if not valid:
91
+ log.warning("Invalid Synthesia signature!")
92
+
93
+ except Exception as e:
94
+ log.error(f"Error validating Synthesia signature: {e}")
95
+
96
+ finally:
97
+ return valid
98
+
99
+
100
+ async def forward_video_data(payload:dict) -> int:
101
+ """ Sends POST requests to create video from template in Synthesia.
102
+ :param dict payload: Request payload (title, templateId, templateData)
103
+ :returns int status
104
+ """
105
+
106
+ status = 500
107
+ app_key = os.getenv('APP_KEY', None)
108
+ url = os.getenv('APP_URL', None)
109
+ headers = {
110
+ 'Authorization': f"Bearer {app_key}",
111
+ 'Content-Type': 'application/json'
112
+ }
113
+ title = payload.get('title', '')
114
+ try:
115
+ response = requests.post(url, json=payload, headers=headers)
116
+ status = response.status_code
117
+ if response.ok:
118
+ data = response.json()
119
+ video_id = data.get('id', None)
120
+ else:
121
+ log.error(f"Failed to create {title}. Status: {status}, Reason: {response.reason}")
122
+
123
+ except requests.exceptions.ConnectionError as conn_err:
124
+ log.error(f"Error creating {title}. Connection error occurred: {conn_err}")
125
+ except requests.exceptions.Timeout as timeout_err:
126
+ log.error(f"Error creating {title}. Timeout error occurred: {timeout_err}")
127
+ except ValueError as json_err:
128
+ log.error(f"Error creating {title}. JSON decoding error: {json_err}")
129
+ except Exception as e:
130
+ log.error(f"Error creating {title}. Error: {e}")
131
+
132
+ finally:
133
+ return status
134
+