Spaces:
Build error
Build error
Alex Volkov
commited on
Commit
•
cb23f19
1
Parent(s):
fb5c5d4
language change
Browse filesSigned-off-by: Alex Volkov <alex.volkov@fundbox.com>
- .DS_Store +0 -0
- .vscode/settings.json +5 -0
- app.py +76 -61
- download.py +47 -25
- environment.yml +8 -0
- fonts/.DS_Store +0 -0
- static/css/main.css +97 -0
.DS_Store
ADDED
Binary file (8.2 kB). View file
|
|
.vscode/settings.json
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"files.associations": {
|
3 |
+
"*.yaml": "home-assistant"
|
4 |
+
}
|
5 |
+
}
|
app.py
CHANGED
@@ -5,6 +5,7 @@ from download import download_generator
|
|
5 |
import anvil.media
|
6 |
import os
|
7 |
import dotenv
|
|
|
8 |
|
9 |
from utils.apis import render_api_elements
|
10 |
from utils.utils import get_args
|
@@ -19,108 +20,122 @@ gradio_share: bool = args.get("public")
|
|
19 |
model_size: str = args.get("model")
|
20 |
preload_model: str = args.get("preload")
|
21 |
|
|
|
|
|
|
|
|
|
|
|
22 |
url_input = gr.Textbox(label="Youtube/Twitter/etc video URL (supports many services)", value='https://twitter.com/starsonxh/status/1552945347194142720', lines=1, elem_id="url_input")
|
23 |
# download_status = gr.Textbox(label="Status:", value='', lines=1, elem_id="download_status")
|
24 |
-
download_status = gr.Checkbox(label="
|
25 |
translate_action = gr.Checkbox(label="Auto translate to english", elem_id='translate_toggle', interactive=True, value=True)
|
26 |
init_video = gr.Video(label="Upload video manually", visible=True, interactive=True, mirror_webcam=False)
|
27 |
init_audio = gr.Audio(label="Downloaded audio", visible=False)
|
28 |
-
output_text = gr.Textbox(label="Output text", lines=5, visible=False, max_lines=10, interactive=True)
|
29 |
sub_video = gr.Video(label="Subbed video", visible=False, mirror_webcam=False)
|
30 |
|
31 |
|
32 |
-
|
33 |
-
|
34 |
-
for response in download_generator(url, translate_action):
|
35 |
updates_object = {}
|
36 |
-
updates_object[download_status] = gr.update(label=f"
|
37 |
meta = response.get('meta')
|
|
|
38 |
if 'video' in response:
|
39 |
updates_object[init_video] = gr.update(visible=True, value=response["video"],
|
40 |
label=f"Init Video: {meta['id']}.{meta['ext']}")
|
41 |
updates_object[init_audio] = gr.update(visible=True, value=response["audio"],
|
42 |
label=f"Extracted audio : {meta['id']}.mp3")
|
|
|
|
|
43 |
if 'whisper_result' in response:
|
44 |
updates_object[output_text] = gr.update(value=response['whisper_result'].get('srt'), visible=True,
|
45 |
-
label=f"Subtitles
|
|
|
|
|
|
|
46 |
if 'sub_video' in response:
|
47 |
updates_object[sub_video] = gr.update(visible=True, value=response["sub_video"],
|
48 |
label=f"Subbed video: {meta['id']}_translated.mp4")
|
|
|
49 |
|
|
|
50 |
yield updates_object
|
51 |
|
52 |
-
def
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
|
56 |
subtitled_video = False
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
right: 13px;
|
63 |
-
top: 40px;
|
64 |
-
}
|
65 |
-
#url_input{
|
66 |
-
font-size: 40px !important;
|
67 |
-
}
|
68 |
-
#download_status{
|
69 |
-
font-size: 40px !important;
|
70 |
-
}
|
71 |
-
|
72 |
-
#input_row{
|
73 |
-
position: relative;
|
74 |
-
}
|
75 |
-
#url_input_group .gr-form:nth-child(2){
|
76 |
-
position:relative
|
77 |
-
}
|
78 |
-
#translate_toggle{
|
79 |
-
position: absolute;
|
80 |
-
right: 0;
|
81 |
-
width: auto;
|
82 |
-
flex: none;
|
83 |
-
}
|
84 |
-
|
85 |
-
#fake_ass_group{
|
86 |
-
display:none;
|
87 |
-
visibility: hidden;
|
88 |
-
position:absolute;
|
89 |
-
pointer-events: none;
|
90 |
-
}
|
91 |
-
|
92 |
-
"""
|
93 |
-
with gr.Blocks(css=css+"") as demo:
|
94 |
-
gr.Markdown('# Vid Translator 0.1 - get english subtitles for videos in any language')
|
95 |
-
gr.Markdown('### Link to a tweet, youtube or other video and get a translated video with @openAi #whisper, built by [@altryne](https://twitter.com/altryne/)')
|
96 |
-
gr.Markdown('### This is used as the backend for [@vidtranslator](https://twitter.com/vidtranslator/)')
|
97 |
with gr.Row(elem_id="input_row"):
|
98 |
with gr.Group() as group:
|
99 |
url_input.render()
|
100 |
action_btn = gr.Button(elem_id='submit', variant='primary', value="Translate")
|
101 |
-
|
102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
103 |
with gr.Row():
|
104 |
with gr.Column():
|
105 |
-
|
106 |
init_video.render()
|
107 |
init_audio.render()
|
|
|
|
|
|
|
108 |
with gr.Column():
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
|
|
117 |
action_btn.click(fn=predownload, inputs=inputs, outputs=outputs, api_name='predownload')
|
118 |
url_input.submit(fn=predownload, inputs=inputs, outputs=outputs)
|
119 |
-
|
|
|
120 |
|
121 |
translate_action.change(fn=lambda x: {action_btn: gr.update(value=f"Translate" if x else "Transcribe")},
|
122 |
inputs=[translate_action], outputs=[action_btn])
|
123 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
# Render imported buttons for API bindings
|
125 |
render_api_elements(url_input,download_status, output_text, sub_video)
|
126 |
|
|
|
5 |
import anvil.media
|
6 |
import os
|
7 |
import dotenv
|
8 |
+
from whisper.tokenizer import LANGUAGES, TO_LANGUAGE_CODE
|
9 |
|
10 |
from utils.apis import render_api_elements
|
11 |
from utils.utils import get_args
|
|
|
20 |
model_size: str = args.get("model")
|
21 |
preload_model: str = args.get("preload")
|
22 |
|
23 |
+
|
24 |
+
LANG_CHOICES = sorted([x.capitalize() for x in LANGUAGES.values()])
|
25 |
+
LANG_CHOICES.insert(0, "Autodetect")
|
26 |
+
|
27 |
+
|
28 |
url_input = gr.Textbox(label="Youtube/Twitter/etc video URL (supports many services)", value='https://twitter.com/starsonxh/status/1552945347194142720', lines=1, elem_id="url_input")
|
29 |
# download_status = gr.Textbox(label="Status:", value='', lines=1, elem_id="download_status")
|
30 |
+
download_status = gr.Checkbox(label="", elem_id="download_status", interactive=False)
|
31 |
translate_action = gr.Checkbox(label="Auto translate to english", elem_id='translate_toggle', interactive=True, value=True)
|
32 |
init_video = gr.Video(label="Upload video manually", visible=True, interactive=True, mirror_webcam=False)
|
33 |
init_audio = gr.Audio(label="Downloaded audio", visible=False)
|
34 |
+
output_text = gr.Textbox(label="Output text", lines=5, visible=False, max_lines=10, interactive=True, elem_id="output_text")
|
35 |
sub_video = gr.Video(label="Subbed video", visible=False, mirror_webcam=False)
|
36 |
|
37 |
|
38 |
+
def predownload(url, translate_action, source_language):
|
39 |
+
files = []
|
40 |
+
for response in download_generator(url, translate_action, source_language):
|
41 |
updates_object = {}
|
42 |
+
updates_object[download_status] = gr.update(label=f"{response.get('message')}")
|
43 |
meta = response.get('meta')
|
44 |
+
|
45 |
if 'video' in response:
|
46 |
updates_object[init_video] = gr.update(visible=True, value=response["video"],
|
47 |
label=f"Init Video: {meta['id']}.{meta['ext']}")
|
48 |
updates_object[init_audio] = gr.update(visible=True, value=response["audio"],
|
49 |
label=f"Extracted audio : {meta['id']}.mp3")
|
50 |
+
files.append(response["video"])
|
51 |
+
files.append(response["audio"])
|
52 |
if 'whisper_result' in response:
|
53 |
updates_object[output_text] = gr.update(value=response['whisper_result'].get('srt'), visible=True,
|
54 |
+
label=f"Subtitles transcribed from {response['whisper_result'].get('language')} (detected language)")
|
55 |
+
if 'srt_path' in response:
|
56 |
+
files.append(response["srt_path"])
|
57 |
+
|
58 |
if 'sub_video' in response:
|
59 |
updates_object[sub_video] = gr.update(visible=True, value=response["sub_video"],
|
60 |
label=f"Subbed video: {meta['id']}_translated.mp4")
|
61 |
+
files.append(response["sub_video"])
|
62 |
|
63 |
+
updates_object[output_file] = gr.update(value=files, visible=len(files) > 0, label=f"Output Files")
|
64 |
yield updates_object
|
65 |
|
66 |
+
def correct_subtitles(url, output_text):
|
67 |
+
for response in download_generator(url, corrected_subtitles=output_text):
|
68 |
+
updates_object = {}
|
69 |
+
updates_object[download_status] = gr.update(label=f"STATUS: {response.get('message')}")
|
70 |
+
if 'sub_video' in response:
|
71 |
+
updates_object[sub_video] = gr.update(visible=True, value=response["sub_video"],
|
72 |
+
label=f"Corrected subtitles")
|
73 |
+
yield updates_object
|
74 |
|
75 |
|
76 |
subtitled_video = False
|
77 |
+
|
78 |
+
with gr.Blocks(css='@import "file=static/css/main.css";', theme='darkpeach', title='Vid Translator Studio') as demo:
|
79 |
+
gr.HTML('<h1 class="main-title">VidTranslator Studio 0.1</h1>')
|
80 |
+
gr.HTML("<h2 class='secondary'>Automatic social media video translation from 99 languages</h2>")
|
81 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
with gr.Row(elem_id="input_row"):
|
83 |
with gr.Group() as group:
|
84 |
url_input.render()
|
85 |
action_btn = gr.Button(elem_id='submit', variant='primary', value="Translate")
|
86 |
+
gr.StatusTracker()
|
87 |
+
with gr.Row(elem_id="second_row"):
|
88 |
+
source_language = gr.Dropdown(choices=LANG_CHOICES,
|
89 |
+
label="Source Language",
|
90 |
+
value='Autodetect',
|
91 |
+
interactive=True, elem_id="source_language")
|
92 |
+
download_status.render()
|
93 |
+
translate_action.render()
|
94 |
+
|
95 |
with gr.Row():
|
96 |
with gr.Column():
|
|
|
97 |
init_video.render()
|
98 |
init_audio.render()
|
99 |
+
with gr.Row():
|
100 |
+
output_file = gr.Files(label="Output Files", visible=False)
|
101 |
+
|
102 |
with gr.Column():
|
103 |
+
output_text.render()
|
104 |
+
correct_btn = gr.Button("Correct subtitles")
|
105 |
+
|
106 |
+
with gr.Column():
|
107 |
+
sub_video.render()
|
108 |
+
|
109 |
+
|
110 |
+
outputs = [download_status, init_video, init_audio, output_text, sub_video, output_file ]
|
111 |
+
inputs = [url_input, translate_action, source_language]
|
112 |
action_btn.click(fn=predownload, inputs=inputs, outputs=outputs, api_name='predownload')
|
113 |
url_input.submit(fn=predownload, inputs=inputs, outputs=outputs)
|
114 |
+
|
115 |
+
correct_btn.click(fn=correct_subtitles, inputs=[url_input, output_text], outputs=[download_status, output_text, sub_video])
|
116 |
|
117 |
translate_action.change(fn=lambda x: {action_btn: gr.update(value=f"Translate" if x else "Transcribe")},
|
118 |
inputs=[translate_action], outputs=[action_btn])
|
119 |
|
120 |
+
gr.HTML("""<div class='footer'>
|
121 |
+
<div class="relative">
|
122 |
+
<div class="absolute inset-0 flex items-center" aria-hidden="true">
|
123 |
+
<div class="w-full border-t border-gray-300"></div>
|
124 |
+
</div>
|
125 |
+
<div class="relative flex justify-center">
|
126 |
+
<span class="bg-white px-2 text-sm text-gray-500">Continue</span>
|
127 |
+
</div>
|
128 |
+
</div>
|
129 |
+
|
130 |
+
<a href='https://twitter.com/altryne/'><img src='https://img.shields.io/badge/%40altryne-follow-green' alt=''></a>
|
131 |
+
</div>""")
|
132 |
+
|
133 |
+
def init_video_manual_upload(url, init_video):
|
134 |
+
print(url)
|
135 |
+
print(init_video)
|
136 |
+
|
137 |
+
init_video.change(fn=init_video_manual_upload, inputs=[url_input, init_video], outputs=[])
|
138 |
+
|
139 |
# Render imported buttons for API bindings
|
140 |
render_api_elements(url_input,download_status, output_text, sub_video)
|
141 |
|
download.py
CHANGED
@@ -11,7 +11,7 @@ import tempfile
|
|
11 |
import json
|
12 |
import argparse
|
13 |
import whisper
|
14 |
-
from whisper.tokenizer import LANGUAGES
|
15 |
import ffmpeg
|
16 |
from utils.subs import bake_subs
|
17 |
from utils.utils import get_args
|
@@ -27,9 +27,9 @@ if preload_model:
|
|
27 |
print("Preloading model")
|
28 |
model = whisper.load_model(model_size)
|
29 |
|
30 |
-
def download_generator(url, translate_action=True):
|
31 |
|
32 |
-
|
33 |
yield {"message": f"Checking {url} for videos"}
|
34 |
try:
|
35 |
meta = check_download(url)
|
@@ -43,32 +43,46 @@ def download_generator(url, translate_action=True):
|
|
43 |
yield {"message": f"{e}"}
|
44 |
return
|
45 |
|
46 |
-
|
47 |
-
yield {"message": f"Starting download with URL {url}, this may take a while"}
|
48 |
-
|
49 |
try:
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
yield {"message": f"Downloaded video and extracted audio", "video": video, "audio": audio, "meta": meta}
|
|
|
52 |
except Exception as e:
|
53 |
os.chdir(original_dir)
|
54 |
yield {"message": f"{e}"}
|
55 |
raise e
|
56 |
|
57 |
-
|
58 |
-
yield {"message": f"[PLEASE WAIT] Starting whisper transcribe with {meta['id']}.mp3"}
|
59 |
-
try:
|
60 |
-
whisper_result = transcribe(audio, translate_action)
|
61 |
-
srt_path = tempdir / f"{meta['id']}.srt"
|
62 |
-
with open(srt_path, "w", encoding="utf-8") as srt:
|
63 |
-
write_srt(whisper_result["segments"], file=srt)
|
64 |
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
|
73 |
### Step 4 : Bake subtitles into video with ffmpeg
|
74 |
yield {"message": f"[PLEASE WAIT] baking subtitles into video"}
|
@@ -150,14 +164,22 @@ def check_download(url):
|
|
150 |
else:
|
151 |
return meta
|
152 |
|
153 |
-
def transcribe(audio, translate_action=True):
|
154 |
-
|
|
|
155 |
global model
|
156 |
if not preload_model:
|
157 |
model = whisper.load_model(model_size)
|
158 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
159 |
output["language"] = LANGUAGES[output["language"]]
|
160 |
-
output['segments'] = [{"id": 0, "seek": 0, "start": 0.0, "end": 3, "text": " [AI
|
161 |
print(f'Finished transcribe from {output["language"]}', output["text"])
|
162 |
return output
|
163 |
|
|
|
11 |
import json
|
12 |
import argparse
|
13 |
import whisper
|
14 |
+
from whisper.tokenizer import LANGUAGES, TO_LANGUAGE_CODE
|
15 |
import ffmpeg
|
16 |
from utils.subs import bake_subs
|
17 |
from utils.utils import get_args
|
|
|
27 |
print("Preloading model")
|
28 |
model = whisper.load_model(model_size)
|
29 |
|
30 |
+
def download_generator(url, translate_action=True, source_language='Autodetect', corrected_subtitles=None):
|
31 |
|
32 |
+
# Step 1 : check if video is available
|
33 |
yield {"message": f"Checking {url} for videos"}
|
34 |
try:
|
35 |
meta = check_download(url)
|
|
|
43 |
yield {"message": f"{e}"}
|
44 |
return
|
45 |
|
46 |
+
# Step 2 : Download video and extract audio
|
|
|
|
|
47 |
try:
|
48 |
+
# check if we already have the folder and the main files
|
49 |
+
if(tempdir.is_dir() and (tempdir/f"{meta['id']}.{meta['ext']}").is_file() and (tempdir/f"{meta['id']}.mp3").is_file()):
|
50 |
+
yield {"message": f"Using cached files"}
|
51 |
+
video = str((tempdir/f"{meta['id']}.{meta['ext']}").resolve())
|
52 |
+
audio = str((tempdir/f"{meta['id']}.mp3").resolve())
|
53 |
+
else:
|
54 |
+
yield {"message": f"Starting download with URL {url}, this may take a while"}
|
55 |
+
meta, video, audio = download(url, tempdir)
|
56 |
yield {"message": f"Downloaded video and extracted audio", "video": video, "audio": audio, "meta": meta}
|
57 |
+
|
58 |
except Exception as e:
|
59 |
os.chdir(original_dir)
|
60 |
yield {"message": f"{e}"}
|
61 |
raise e
|
62 |
|
63 |
+
srt_path = tempdir / f"{meta['id']}.srt"
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
|
65 |
+
if not corrected_subtitles:
|
66 |
+
### Step 3 : Transcribe with whisper
|
67 |
+
yield {"message": f"[PLEASE WAIT] Starting whisper transcribe with {meta['id']}.mp3"}
|
68 |
+
try:
|
69 |
+
whisper_result = transcribe(audio, translate_action, source_language)
|
70 |
+
|
71 |
+
with open(srt_path, "w", encoding="utf-8") as srt:
|
72 |
+
write_srt(whisper_result["segments"], file=srt)
|
73 |
+
|
74 |
+
whisper_result["srt"] = Path(srt_path).read_text()
|
75 |
+
yield {"message": f"Transcribe successful", "whisper_result": whisper_result, "meta": meta, "srt_path": srt_path}
|
76 |
+
except Exception as e:
|
77 |
+
os.chdir(original_dir)
|
78 |
+
yield {"message": f"{e}"}
|
79 |
+
raise e
|
80 |
+
else:
|
81 |
+
### step 3.5 : use corrected subtitles
|
82 |
+
yield {"message": f"Using corrected subtitles"}
|
83 |
+
with open(srt_path, "w", encoding="utf-8") as srt:
|
84 |
+
srt.write(corrected_subtitles)
|
85 |
+
yield {"message": f"Transcribe successful", "srt_path": srt_path, "meta": meta}
|
86 |
|
87 |
### Step 4 : Bake subtitles into video with ffmpeg
|
88 |
yield {"message": f"[PLEASE WAIT] baking subtitles into video"}
|
|
|
164 |
else:
|
165 |
return meta
|
166 |
|
167 |
+
def transcribe(audio, translate_action=True, language='Autodetect'):
|
168 |
+
task = "translate" if translate_action else "transcribe"
|
169 |
+
print(f'Starting {task} with whisper size {model_size}')
|
170 |
global model
|
171 |
if not preload_model:
|
172 |
model = whisper.load_model(model_size)
|
173 |
+
props = {
|
174 |
+
"task": task,
|
175 |
+
}
|
176 |
+
if language != 'Autodetect':
|
177 |
+
props["language"] = TO_LANGUAGE_CODE[language.lower()]
|
178 |
+
|
179 |
+
output = model.transcribe(audio, task=task)
|
180 |
+
|
181 |
output["language"] = LANGUAGES[output["language"]]
|
182 |
+
output['segments'] = [{"id": 0, "seek": 0, "start": 0.0, "end": 3, "text": " [AI transcription]"}] + output['segments']
|
183 |
print(f'Finished transcribe from {output["language"]}', output["text"])
|
184 |
return output
|
185 |
|
environment.yml
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: vidnightly
|
2 |
+
channels:
|
3 |
+
- defaults
|
4 |
+
dependencies:
|
5 |
+
- pytorch
|
6 |
+
- torchvision
|
7 |
+
- torchaudio
|
8 |
+
prefix: /opt/homebrew/Caskroom/miniconda/base/envs/vidnightly
|
fonts/.DS_Store
ADDED
Binary file (6.15 kB). View file
|
|
static/css/main.css
ADDED
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@700&display=swap');
|
2 |
+
|
3 |
+
#download_status input[type=checkbox] {
|
4 |
+
display: none;
|
5 |
+
}
|
6 |
+
#download_status label{
|
7 |
+
height: 100%;
|
8 |
+
}
|
9 |
+
.main-title{
|
10 |
+
color: #FF7A7A;
|
11 |
+
font-family: 'Poppins', sans-serif;
|
12 |
+
font-size: 4.5em;
|
13 |
+
line-height: 1.25;
|
14 |
+
font-weight: 700;
|
15 |
+
background-image: linear-gradient(45deg, #695EE6 0%, #FF7A7A 85%);
|
16 |
+
-webkit-background-clip: text;
|
17 |
+
-webkit-text-fill-color: transparent;
|
18 |
+
}
|
19 |
+
.secondary{
|
20 |
+
color: #FF7A7A;
|
21 |
+
font-family: 'Poppins', sans-serif;
|
22 |
+
font-size: 2em;
|
23 |
+
line-height: 1.25;
|
24 |
+
font-weight: 700;
|
25 |
+
background-image: linear-gradient(45deg, #695EE6 0%, #FF7A7A 85%);
|
26 |
+
-webkit-background-clip: text;
|
27 |
+
-webkit-text-fill-color: transparent;
|
28 |
+
}
|
29 |
+
#submit{
|
30 |
+
position: absolute;
|
31 |
+
flex:0 !important;
|
32 |
+
width: 120px;
|
33 |
+
right: 13px;
|
34 |
+
top: 40px;
|
35 |
+
border-radius: 0 5px 5px 5px !important;
|
36 |
+
}
|
37 |
+
#url_input{
|
38 |
+
font-size: 20px !important;
|
39 |
+
}
|
40 |
+
#download_status label{
|
41 |
+
font-size: 18px !important;
|
42 |
+
}
|
43 |
+
#second_row>.gr-form{
|
44 |
+
border-top-left-radius: 0px !important;
|
45 |
+
border-top-right-radius: 0px !important;
|
46 |
+
}
|
47 |
+
|
48 |
+
#input_row{
|
49 |
+
position: relative;
|
50 |
+
}
|
51 |
+
#url_input_group .gr-form:nth-child(2){
|
52 |
+
position:relative
|
53 |
+
}
|
54 |
+
#url_input textarea{
|
55 |
+
font-size: 20px !important;
|
56 |
+
}
|
57 |
+
|
58 |
+
#source_language{
|
59 |
+
flex-grow: revert
|
60 |
+
}
|
61 |
+
|
62 |
+
#translate_toggle{
|
63 |
+
flex-grow: revert;
|
64 |
+
min-width: 200px;
|
65 |
+
}
|
66 |
+
#translate_toggle label{
|
67 |
+
height: 100%;
|
68 |
+
}
|
69 |
+
|
70 |
+
/*
|
71 |
+
#translate_toggle{
|
72 |
+
position: absolute;
|
73 |
+
right: 0;
|
74 |
+
width: auto;
|
75 |
+
flex: none;
|
76 |
+
background: transparent
|
77 |
+
}
|
78 |
+
*/
|
79 |
+
.wrap.absolute{
|
80 |
+
position: relative !important;
|
81 |
+
opacity: 100% !important;
|
82 |
+
}
|
83 |
+
|
84 |
+
#fake_ass_group{
|
85 |
+
display:none;
|
86 |
+
visibility: hidden;
|
87 |
+
position:absolute;
|
88 |
+
pointer-events: none;
|
89 |
+
}
|
90 |
+
#output_text label{
|
91 |
+
font-size: 20px !important;
|
92 |
+
white-space: pre-wrap;
|
93 |
+
}
|
94 |
+
|
95 |
+
footer{
|
96 |
+
display: none !important;
|
97 |
+
}
|