Profakerr commited on
Commit
70bad18
·
verified ·
1 Parent(s): 1d64d98

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +284 -278
app.py CHANGED
@@ -1,279 +1,285 @@
1
- import onnxruntime as ort
2
- from huggingface_hub import hf_hub_download
3
- import requests
4
- import os
5
- import gradio as gr
6
- import spaces
7
- from typing import Any, List, Callable
8
- import cv2
9
- import insightface
10
- import time
11
- import tempfile
12
- import subprocess
13
- import gfpgan
14
-
15
- print("Installing cuDNN 9")
16
-
17
- import subprocess
18
- import sys
19
-
20
- def get_pip_version(package_name):
21
- try:
22
- result = subprocess.run(
23
- [sys.executable, '-m', 'pip', 'show', package_name],
24
- capture_output=True,
25
- text=True,
26
- check=True
27
- )
28
- output = result.stdout
29
- version_line = next(line for line in output.split('\n') if line.startswith('Version:'))
30
- return version_line.split(': ')[1]
31
- except subprocess.CalledProcessError as e:
32
- print(f"Error executing command: {e}")
33
- return None
34
-
35
- package_name = 'nvidia-cudnn-cu12'
36
- version = get_pip_version(package_name)
37
- print(f"The installed version of {package_name} is: {version}")
38
-
39
- command = "find / -path /proc -prune -o -path /sys -prune -o -name 'libcudnn*' -print"
40
- process = subprocess.run(command, shell=True, text=True, capture_output=True)
41
-
42
- if process.returncode == 0:
43
- print("Search results:\n", process.stdout)
44
- else:
45
- print("An error occurred while executing the command:", process.stderr)
46
-
47
- source_path = '/usr/local/lib/python3.10/site-packages/nvidia/cublas/lib/libcublasLt.so.12'
48
- destination_path = '/usr/local/lib/python3.10/site-packages/nvidia/cudnn/lib/'
49
-
50
- commands = [
51
- ['mv', source_path, destination_path],
52
- ['mv', "/usr/local/lib/python3.10/site-packages/nvidia/cublas/lib/libcublas.so.12", destination_path],
53
- ['mv', "/usr/local/lib/python3.10/site-packages/nvidia/cufft/lib/libcufft.so.11", destination_path],
54
- ['mv', "/usr/local/lib/python3.10/site-packages/nvidia/cufft/lib/libcufftw.so.11", destination_path],
55
- ['mv', "/usr/local/lib/python3.10/site-packages/nvidia/cuda_runtime/lib/libcudart.so.12", destination_path],
56
- ['mv', "/usr/local/lib/python3.10/site-packages/nvidia/cuda_cupti/lib/libcupti.so.12", destination_path],
57
- ['cp', "/usr/local/lib/python3.10/site-packages/nvidia/curand/lib/libcurand.so.10", destination_path],
58
- ['cp', "/usr/local/lib/python3.10/site-packages/nvidia/cusolver/lib/libcusolver.so.11", destination_path],
59
- ['cp', "/usr/local/lib/python3.10/site-packages/nvidia/cusolver/lib/libcusolverMg.so.11", destination_path],
60
- ['cp', "/usr/local/lib/python3.10/site-packages/nvidia/cusparse/lib/libcusparse.so.12", destination_path],
61
- ]
62
-
63
- for command in commands:
64
- subprocess.run(command, check=True)
65
-
66
- command = "find / -path /proc -prune -o -path /sys -prune -o -name 'libcu*' -print"
67
- process = subprocess.run(command, shell=True, text=True, capture_output=True)
68
-
69
- if process.returncode == 0:
70
- print("Search results:\n", process.stdout)
71
- else:
72
- print("An error occurred while executing the command:", process.stderr)
73
-
74
- print("Done")
75
- print("---------------------")
76
- print(ort.get_available_providers())
77
-
78
- def conditional_download(download_directory_path, urls):
79
- if not os.path.exists(download_directory_path):
80
- os.makedirs(download_directory_path)
81
- for url in urls:
82
- filename = url.split('/')[-1]
83
- file_path = os.path.join(download_directory_path, filename)
84
- if not os.path.exists(file_path):
85
- print(f"Downloading {filename}...")
86
- response = requests.get(url, stream=True)
87
- if response.status_code == 200:
88
- with open(file_path, 'wb') as file:
89
- for chunk in response.iter_content(chunk_size=8192):
90
- file.write(chunk)
91
- print(f"{filename} downloaded successfully.")
92
- else:
93
- print(f"Failed to download {filename}. Status code: {response.status_code}")
94
- else:
95
- print(f"{filename} already exists. Skipping download.")
96
-
97
- model_path = hf_hub_download(repo_id="countfloyd/deepfake", filename="inswapper_128.onnx")
98
- conditional_download("./", ['https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth'])
99
-
100
- FACE_SWAPPER = None
101
- FACE_ANALYSER = None
102
- FACE_ENHANCER = None
103
-
104
- @spaces.GPU(duration=300, enable_queue=True)
105
- def process_video(source_path: str, target_path: str, enhance=False, progress=gr.Progress(), output_path='result.mp4') -> None:
106
- def get_face_analyser():
107
- global FACE_ANALYSER
108
- if FACE_ANALYSER is None:
109
- FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l', providers=["CUDAExecutionProvider"])
110
- FACE_ANALYSER.prepare(ctx_id=0, det_size=(640, 640))
111
- return FACE_ANALYSER
112
-
113
- def get_face_enhancer() -> Any:
114
- global FACE_ENHANCER
115
- if FACE_ENHANCER is None:
116
- FACE_ENHANCER = gfpgan.GFPGANer(model_path="./GFPGANv1.4.pth", upscale=2) # type: ignore[attr-defined]
117
- return FACE_ENHANCER
118
-
119
- def get_one_face(frame):
120
- face = get_face_analyser().get(frame)
121
- try:
122
- return min(face, key=lambda x: x.bbox[0])
123
- except ValueError:
124
- return None
125
-
126
- def get_face_swapper():
127
- global FACE_SWAPPER
128
- if FACE_SWAPPER is None:
129
- FACE_SWAPPER = insightface.model_zoo.get_model(model_path, providers=["CUDAExecutionProvider"])
130
- return FACE_SWAPPER
131
-
132
- def swap_face(source_face, target_face, temp_frame):
133
- return get_face_swapper().get(temp_frame, target_face, source_face, paste_back=True)
134
-
135
- def process_frame(source_face, temp_frame, enhance):
136
- target_face = get_one_face(temp_frame)
137
- if target_face:
138
- temp_frame = swap_face(source_face, target_face, temp_frame)
139
- if enhance:
140
- temp_frame = enhance_face(temp_frame)
141
- return temp_frame
142
-
143
- def process_image(source_path: str, target_path: str, output_path: str) -> None:
144
- source_face = get_one_face(cv2.imread(source_path))
145
- target_frame = cv2.imread(target_path)
146
- result = process_frame(source_face, target_frame)
147
- cv2.imwrite(output_path, result)
148
-
149
- def create_temp_directory():
150
- temp_dir = tempfile.mkdtemp()
151
- return temp_dir
152
-
153
- def enhance_face(temp_frame):
154
- _, _, temp_frame = get_face_enhancer().enhance(
155
- temp_frame,
156
- paste_back=True
157
- )
158
- return temp_frame
159
-
160
- def remove_temp_directory(temp_dir):
161
- try:
162
- for filename in os.listdir(temp_dir):
163
- file_path = os.path.join(temp_dir, filename)
164
- if os.path.isfile(file_path):
165
- os.unlink(file_path)
166
- elif os.path.isdir(file_path):
167
- os.rmdir(file_path)
168
- os.rmdir(temp_dir)
169
- except Exception as e:
170
- print(f"Error removing temporary directory: {e}")
171
-
172
- def extract_frames(video_path: str):
173
- video_capture = cv2.VideoCapture(video_path)
174
- if not video_capture.isOpened():
175
- print("Error opening video.")
176
- return []
177
- frames = []
178
- while True:
179
- ret, frame = video_capture.read()
180
- if not ret:
181
- break
182
- frames.append(frame)
183
- video_capture.release()
184
- return frames
185
-
186
- def get_video_fps(video_path: str) -> float:
187
- video_capture = cv2.VideoCapture(video_path)
188
- if not video_capture.isOpened():
189
- raise ValueError("Error opening video.")
190
- fps = video_capture.get(cv2.CAP_PROP_FPS)
191
- video_capture.release()
192
- return fps
193
-
194
- def create_video_from_frames(temp_dir: str, output_video_path: str, fps: float) -> None:
195
- temp_frames_pattern = os.path.join(temp_dir, "frame_%04d.jpg")
196
- ffmpeg_command = [
197
- 'ffmpeg',
198
- '-y',
199
- '-framerate', str(fps),
200
- '-i', temp_frames_pattern,
201
- '-c:v', 'libx264',
202
- '-pix_fmt', 'yuv420p',
203
- '-preset', 'ultrafast',
204
- output_video_path
205
- ]
206
- subprocess.run(ffmpeg_command, check=True)
207
-
208
- def extract_audio(video_path: str, audio_path: str) -> None:
209
- ffmpeg_command = [
210
- 'ffmpeg',
211
- '-y',
212
- '-i', video_path,
213
- '-q:a', '0',
214
- '-map', 'a',
215
- '-preset', 'ultrafast',
216
- audio_path
217
- ]
218
- subprocess.run(ffmpeg_command, check=True)
219
-
220
- def add_audio_to_video(video_path: str, audio_path: str, output_video_path: str) -> None:
221
- ffmpeg_command = [
222
- 'ffmpeg',
223
- '-y',
224
- '-i', video_path,
225
- '-i', audio_path,
226
- '-c:v', 'copy',
227
- '-c:a', 'aac',
228
- '-strict', 'experimental',
229
- '-preset', 'ultrafast',
230
- output_video_path
231
- ]
232
- subprocess.run(ffmpeg_command, check=True)
233
-
234
- def delete_file(file_path: str) -> None:
235
- try:
236
- os.remove(file_path)
237
- except Exception as e:
238
- print(f"Error removing file: {e}")
239
-
240
- def reduce_video(video_path: str, output_video_path: str) -> None:
241
- ffmpeg_command = [
242
- 'ffmpeg',
243
- '-y',
244
- '-i', video_path,
245
- '-vf', "scale='if(gte(iw,ih),720,-1)':'if(gte(iw,ih),-1,720)',pad=ceil(iw/2)*2:ceil(ih/2)*2",
246
- '-preset', 'ultrafast',
247
- '-r', '24',
248
- output_video_path
249
- ]
250
- subprocess.run(ffmpeg_command, check=True)
251
-
252
- temp_dir = create_temp_directory()
253
- video_input = temp_dir + "/input.mp4"
254
- reduce_video(target_path, video_input)
255
-
256
- source_face = get_one_face(cv2.imread(source_path))
257
- frames = extract_frames(video_input)
258
-
259
- for index, frame in progress.tqdm(enumerate(frames), total=len(frames)):
260
- processed_frame = process_frame(source_face, frame, enhance)
261
- frame_filename = os.path.join(temp_dir, f"frame_{index:04d}.jpg")
262
- cv2.imwrite(frame_filename, processed_frame)
263
-
264
- video_path = temp_dir + "/out.mp4"
265
- create_video_from_frames(temp_dir, video_path, get_video_fps(video_input))
266
- audio_path = temp_dir + "/audio.wav"
267
- extract_audio(video_input, audio_path)
268
- add_audio_to_video(video_path, audio_path, output_path)
269
- remove_temp_directory(temp_dir)
270
- return output_path
271
-
272
- app = gr.Interface(
273
- fn=process_video,
274
- inputs=[gr.Image(type='filepath'), gr.Video(), gr.Checkbox(label="Use Face GAN (increase render time)", value=False)],
275
- outputs=[gr.Video()],
276
- description="Videos get downsampled to 720p and 24fps. To modify the code or purchase a modification, send an email to [email protected] to donate to the owner of the space: https://donate.stripe.com/3csg0D0tadXU4mYcMM"
277
- )
278
-
 
 
 
 
 
 
279
  app.launch()
 
1
+
2
+ import os
3
+
4
+ # Set the LD_LIBRARY_PATH
5
+ os.environ['LD_LIBRARY_PATH'] = '/usr/local/lib/python3.10/site-packages/nvidia/cudnn/lib:' + os.environ.get('LD_LIBRARY_PATH', '')
6
+
7
+ # Now you can import your libraries that depend on this path
8
+ import onnxruntime as ort
9
+ from huggingface_hub import hf_hub_download
10
+ import requests
11
+ import gradio as gr
12
+ import spaces
13
+ from typing import Any, List, Callable
14
+ import cv2
15
+ import insightface
16
+ import time
17
+ import tempfile
18
+ import subprocess
19
+ import gfpgan
20
+
21
+ print("Installing cuDNN 9")
22
+
23
+ import subprocess
24
+ import sys
25
+
26
+ def get_pip_version(package_name):
27
+ try:
28
+ result = subprocess.run(
29
+ [sys.executable, '-m', 'pip', 'show', package_name],
30
+ capture_output=True,
31
+ text=True,
32
+ check=True
33
+ )
34
+ output = result.stdout
35
+ version_line = next(line for line in output.split('\n') if line.startswith('Version:'))
36
+ return version_line.split(': ')[1]
37
+ except subprocess.CalledProcessError as e:
38
+ print(f"Error executing command: {e}")
39
+ return None
40
+
41
+ package_name = 'nvidia-cudnn-cu12'
42
+ version = get_pip_version(package_name)
43
+ print(f"The installed version of {package_name} is: {version}")
44
+
45
+ command = "find / -path /proc -prune -o -path /sys -prune -o -name 'libcudnn*' -print"
46
+ process = subprocess.run(command, shell=True, text=True, capture_output=True)
47
+
48
+ if process.returncode == 0:
49
+ print("Search results:\n", process.stdout)
50
+ else:
51
+ print("An error occurred while executing the command:", process.stderr)
52
+
53
+ source_path = '/usr/local/lib/python3.10/site-packages/nvidia/cublas/lib/libcublasLt.so.12'
54
+ destination_path = '/usr/local/lib/python3.10/site-packages/nvidia/cudnn/lib/'
55
+
56
+ commands = [
57
+ ['mv', source_path, destination_path],
58
+ ['mv', "/usr/local/lib/python3.10/site-packages/nvidia/cublas/lib/libcublas.so.12", destination_path],
59
+ ['mv', "/usr/local/lib/python3.10/site-packages/nvidia/cufft/lib/libcufft.so.11", destination_path],
60
+ ['mv', "/usr/local/lib/python3.10/site-packages/nvidia/cufft/lib/libcufftw.so.11", destination_path],
61
+ ['mv', "/usr/local/lib/python3.10/site-packages/nvidia/cuda_runtime/lib/libcudart.so.12", destination_path],
62
+ ['mv', "/usr/local/lib/python3.10/site-packages/nvidia/cuda_cupti/lib/libcupti.so.12", destination_path],
63
+ ['cp', "/usr/local/lib/python3.10/site-packages/nvidia/curand/lib/libcurand.so.10", destination_path],
64
+ ['cp', "/usr/local/lib/python3.10/site-packages/nvidia/cusolver/lib/libcusolver.so.11", destination_path],
65
+ ['cp', "/usr/local/lib/python3.10/site-packages/nvidia/cusolver/lib/libcusolverMg.so.11", destination_path],
66
+ ['cp', "/usr/local/lib/python3.10/site-packages/nvidia/cusparse/lib/libcusparse.so.12", destination_path],
67
+ ]
68
+
69
+ for command in commands:
70
+ subprocess.run(command, check=True)
71
+
72
+ command = "find / -path /proc -prune -o -path /sys -prune -o -name 'libcu*' -print"
73
+ process = subprocess.run(command, shell=True, text=True, capture_output=True)
74
+
75
+ if process.returncode == 0:
76
+ print("Search results:\n", process.stdout)
77
+ else:
78
+ print("An error occurred while executing the command:", process.stderr)
79
+
80
+ print("Done")
81
+ print("---------------------")
82
+ print(ort.get_available_providers())
83
+
84
+ def conditional_download(download_directory_path, urls):
85
+ if not os.path.exists(download_directory_path):
86
+ os.makedirs(download_directory_path)
87
+ for url in urls:
88
+ filename = url.split('/')[-1]
89
+ file_path = os.path.join(download_directory_path, filename)
90
+ if not os.path.exists(file_path):
91
+ print(f"Downloading {filename}...")
92
+ response = requests.get(url, stream=True)
93
+ if response.status_code == 200:
94
+ with open(file_path, 'wb') as file:
95
+ for chunk in response.iter_content(chunk_size=8192):
96
+ file.write(chunk)
97
+ print(f"{filename} downloaded successfully.")
98
+ else:
99
+ print(f"Failed to download {filename}. Status code: {response.status_code}")
100
+ else:
101
+ print(f"{filename} already exists. Skipping download.")
102
+
103
+ model_path = hf_hub_download(repo_id="countfloyd/deepfake", filename="inswapper_128.onnx")
104
+ conditional_download("./", ['https://github.com/TencentARC/GFPGAN/releases/download/v1.3.4/GFPGANv1.4.pth'])
105
+
106
+ FACE_SWAPPER = None
107
+ FACE_ANALYSER = None
108
+ FACE_ENHANCER = None
109
+
110
+ @spaces.GPU(duration=300, enable_queue=True)
111
+ def process_video(source_path: str, target_path: str, enhance=False, progress=gr.Progress(), output_path='result.mp4') -> None:
112
+ def get_face_analyser():
113
+ global FACE_ANALYSER
114
+ if FACE_ANALYSER is None:
115
+ FACE_ANALYSER = insightface.app.FaceAnalysis(name='buffalo_l', providers=["CUDAExecutionProvider"])
116
+ FACE_ANALYSER.prepare(ctx_id=0, det_size=(640, 640))
117
+ return FACE_ANALYSER
118
+
119
+ def get_face_enhancer() -> Any:
120
+ global FACE_ENHANCER
121
+ if FACE_ENHANCER is None:
122
+ FACE_ENHANCER = gfpgan.GFPGANer(model_path="./GFPGANv1.4.pth", upscale=2) # type: ignore[attr-defined]
123
+ return FACE_ENHANCER
124
+
125
+ def get_one_face(frame):
126
+ face = get_face_analyser().get(frame)
127
+ try:
128
+ return min(face, key=lambda x: x.bbox[0])
129
+ except ValueError:
130
+ return None
131
+
132
+ def get_face_swapper():
133
+ global FACE_SWAPPER
134
+ if FACE_SWAPPER is None:
135
+ FACE_SWAPPER = insightface.model_zoo.get_model(model_path, providers=["CUDAExecutionProvider"])
136
+ return FACE_SWAPPER
137
+
138
+ def swap_face(source_face, target_face, temp_frame):
139
+ return get_face_swapper().get(temp_frame, target_face, source_face, paste_back=True)
140
+
141
+ def process_frame(source_face, temp_frame, enhance):
142
+ target_face = get_one_face(temp_frame)
143
+ if target_face:
144
+ temp_frame = swap_face(source_face, target_face, temp_frame)
145
+ if enhance:
146
+ temp_frame = enhance_face(temp_frame)
147
+ return temp_frame
148
+
149
+ def process_image(source_path: str, target_path: str, output_path: str) -> None:
150
+ source_face = get_one_face(cv2.imread(source_path))
151
+ target_frame = cv2.imread(target_path)
152
+ result = process_frame(source_face, target_frame)
153
+ cv2.imwrite(output_path, result)
154
+
155
+ def create_temp_directory():
156
+ temp_dir = tempfile.mkdtemp()
157
+ return temp_dir
158
+
159
+ def enhance_face(temp_frame):
160
+ _, _, temp_frame = get_face_enhancer().enhance(
161
+ temp_frame,
162
+ paste_back=True
163
+ )
164
+ return temp_frame
165
+
166
+ def remove_temp_directory(temp_dir):
167
+ try:
168
+ for filename in os.listdir(temp_dir):
169
+ file_path = os.path.join(temp_dir, filename)
170
+ if os.path.isfile(file_path):
171
+ os.unlink(file_path)
172
+ elif os.path.isdir(file_path):
173
+ os.rmdir(file_path)
174
+ os.rmdir(temp_dir)
175
+ except Exception as e:
176
+ print(f"Error removing temporary directory: {e}")
177
+
178
+ def extract_frames(video_path: str):
179
+ video_capture = cv2.VideoCapture(video_path)
180
+ if not video_capture.isOpened():
181
+ print("Error opening video.")
182
+ return []
183
+ frames = []
184
+ while True:
185
+ ret, frame = video_capture.read()
186
+ if not ret:
187
+ break
188
+ frames.append(frame)
189
+ video_capture.release()
190
+ return frames
191
+
192
+ def get_video_fps(video_path: str) -> float:
193
+ video_capture = cv2.VideoCapture(video_path)
194
+ if not video_capture.isOpened():
195
+ raise ValueError("Error opening video.")
196
+ fps = video_capture.get(cv2.CAP_PROP_FPS)
197
+ video_capture.release()
198
+ return fps
199
+
200
+ def create_video_from_frames(temp_dir: str, output_video_path: str, fps: float) -> None:
201
+ temp_frames_pattern = os.path.join(temp_dir, "frame_%04d.jpg")
202
+ ffmpeg_command = [
203
+ 'ffmpeg',
204
+ '-y',
205
+ '-framerate', str(fps),
206
+ '-i', temp_frames_pattern,
207
+ '-c:v', 'libx264',
208
+ '-pix_fmt', 'yuv420p',
209
+ '-preset', 'ultrafast',
210
+ output_video_path
211
+ ]
212
+ subprocess.run(ffmpeg_command, check=True)
213
+
214
+ def extract_audio(video_path: str, audio_path: str) -> None:
215
+ ffmpeg_command = [
216
+ 'ffmpeg',
217
+ '-y',
218
+ '-i', video_path,
219
+ '-q:a', '0',
220
+ '-map', 'a',
221
+ '-preset', 'ultrafast',
222
+ audio_path
223
+ ]
224
+ subprocess.run(ffmpeg_command, check=True)
225
+
226
+ def add_audio_to_video(video_path: str, audio_path: str, output_video_path: str) -> None:
227
+ ffmpeg_command = [
228
+ 'ffmpeg',
229
+ '-y',
230
+ '-i', video_path,
231
+ '-i', audio_path,
232
+ '-c:v', 'copy',
233
+ '-c:a', 'aac',
234
+ '-strict', 'experimental',
235
+ '-preset', 'ultrafast',
236
+ output_video_path
237
+ ]
238
+ subprocess.run(ffmpeg_command, check=True)
239
+
240
+ def delete_file(file_path: str) -> None:
241
+ try:
242
+ os.remove(file_path)
243
+ except Exception as e:
244
+ print(f"Error removing file: {e}")
245
+
246
+ def reduce_video(video_path: str, output_video_path: str) -> None:
247
+ ffmpeg_command = [
248
+ 'ffmpeg',
249
+ '-y',
250
+ '-i', video_path,
251
+ '-vf', "scale='if(gte(iw,ih),720,-1)':'if(gte(iw,ih),-1,720)',pad=ceil(iw/2)*2:ceil(ih/2)*2",
252
+ '-preset', 'ultrafast',
253
+ '-r', '24',
254
+ output_video_path
255
+ ]
256
+ subprocess.run(ffmpeg_command, check=True)
257
+
258
+ temp_dir = create_temp_directory()
259
+ video_input = temp_dir + "/input.mp4"
260
+ reduce_video(target_path, video_input)
261
+
262
+ source_face = get_one_face(cv2.imread(source_path))
263
+ frames = extract_frames(video_input)
264
+
265
+ for index, frame in progress.tqdm(enumerate(frames), total=len(frames)):
266
+ processed_frame = process_frame(source_face, frame, enhance)
267
+ frame_filename = os.path.join(temp_dir, f"frame_{index:04d}.jpg")
268
+ cv2.imwrite(frame_filename, processed_frame)
269
+
270
+ video_path = temp_dir + "/out.mp4"
271
+ create_video_from_frames(temp_dir, video_path, get_video_fps(video_input))
272
+ audio_path = temp_dir + "/audio.wav"
273
+ extract_audio(video_input, audio_path)
274
+ add_audio_to_video(video_path, audio_path, output_path)
275
+ remove_temp_directory(temp_dir)
276
+ return output_path
277
+
278
+ app = gr.Interface(
279
+ fn=process_video,
280
+ inputs=[gr.Image(type='filepath'), gr.Video(), gr.Checkbox(label="Use Face GAN (increase render time)", value=False)],
281
+ outputs=[gr.Video()],
282
+ description="Videos get downsampled to 720p and 24fps. To modify the code or purchase a modification, send an email to [email protected] to donate to the owner of the space: https://donate.stripe.com/3csg0D0tadXU4mYcMM"
283
+ )
284
+
285
  app.launch()