Using Flask
Browse files- README.md +1 -1
- app.py +83 -248
- modules/dataset.py +0 -19
- modules/inference.py +0 -11
- requirements.txt +4 -8
- static/script.js +0 -47
- static/style.css +0 -37
- templates/home.html +0 -85
- templates/index.html +97 -70
- templates/login.html +0 -27
README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
colorFrom: blue
|
4 |
colorTo: blue
|
5 |
sdk: gradio
|
|
|
1 |
---
|
2 |
+
title: Utilities
|
3 |
colorFrom: blue
|
4 |
colorTo: blue
|
5 |
sdk: gradio
|
app.py
CHANGED
@@ -1,253 +1,88 @@
|
|
1 |
-
from
|
2 |
-
from
|
3 |
-
import
|
4 |
-
|
5 |
-
|
6 |
-
import
|
7 |
-
import
|
8 |
-
import
|
9 |
-
import
|
10 |
-
|
11 |
-
import
|
12 |
from datetime import datetime as dt
|
13 |
-
from datetime import timedelta
|
14 |
-
from
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
#
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
def process_complete_callback(retcode, **kwargs):
|
73 |
-
if retcode == 0:
|
74 |
-
print("FFmpeg process completed successfully!")
|
75 |
-
else:
|
76 |
-
print("FFmpeg process encountered an error.")
|
77 |
-
|
78 |
-
def transcribe_audio(latest_file, time_counter):
|
79 |
-
print('transcribing ', latest_file)
|
80 |
-
segments, info = wmodel.transcribe(f"{latest_file}", beam_size=beamsize) # beamsize is 2.
|
81 |
-
text = ''
|
82 |
-
|
83 |
-
for segment in segments:
|
84 |
-
text += segment.text
|
85 |
-
transcribed = text.replace('\n', ' ').replace(' ', ' ')
|
86 |
-
if time_counter%5 == 0:
|
87 |
-
transcribed_sents = transcribed.split('. ') # Get the first fullstop break and append to previous para, before adding time code
|
88 |
-
transcribed = transcribed_sents[0] + '\nTime ' + str((dt.now(timezone.utc) + timedelta(hours=local_tz)).strftime('%H:%M:%S')) + '\n' + '. '.join(transcribed_sents[1:])
|
89 |
-
|
90 |
-
time_counter += 1
|
91 |
-
return transcribed, time_counter
|
92 |
-
|
93 |
-
def save_audio(youtube_url):
|
94 |
-
global stream_process, recording, mp3_extraction_process
|
95 |
-
try:
|
96 |
-
streams = streamlink.streams(youtube_url)
|
97 |
-
#if "audio" not in streams:
|
98 |
-
# raise Exception("No audio stream found.")
|
99 |
-
|
100 |
-
stream_url = streams["144p"].url
|
101 |
-
time_counter = 0
|
102 |
-
while recording:
|
103 |
-
# Save audio only into mp3 files
|
104 |
-
|
105 |
-
saved_mp3 = f"mp3/audio_{int(time.time())}.mp3"
|
106 |
-
mp3_extraction_process = (
|
107 |
-
ffmpeg
|
108 |
-
.input(stream_url, t=30)
|
109 |
-
.audio
|
110 |
-
# TODO - change destination url to relevant url
|
111 |
-
.output(saved_mp3)
|
112 |
-
.overwrite_output()
|
113 |
-
.global_args('-loglevel', 'panic')
|
114 |
-
.run_async()
|
115 |
-
)
|
116 |
-
|
117 |
-
print('pid', mp3_extraction_process.pid)
|
118 |
-
# write the pid to pid_file
|
119 |
-
with open(pid_file, 'w') as f: f.write(str(mp3_extraction_process.pid))
|
120 |
-
|
121 |
-
# If there is more than one mp3 file in the folder, transcribe the one that is not being written to
|
122 |
-
mp3files = [f for f in os.listdir('mp3') if f.endswith('.mp3')]
|
123 |
-
if len(mp3files) < 2:
|
124 |
-
print('Sleeping for 30s as only one mp3 file in folder')
|
125 |
-
time.sleep(30)
|
126 |
-
else:
|
127 |
-
starttime = time.time()
|
128 |
-
file_to_transcribe = [f for f in mp3files if f != os.path.basename(saved_mp3)][0]
|
129 |
-
print('Working on ', file_to_transcribe)
|
130 |
-
transcribed, time_counter = transcribe_audio(f'mp3/{file_to_transcribe}', time_counter)
|
131 |
-
os.remove(f'mp3/{file_to_transcribe}')
|
132 |
-
|
133 |
-
update_gdoc(transcribed, gdoc_id)
|
134 |
-
with open(local_transcript, 'a', encoding='utf-8', errors='ignore') as f: f.write(transcribed)
|
135 |
-
|
136 |
-
elapsed_time = time.time() - starttime
|
137 |
-
print('Time to transcribe:', elapsed_time, 'seconds')
|
138 |
-
if elapsed_time < 30:
|
139 |
-
print(f'Sleeping for {30-elapsed_time} as there are more than one mp3 files in folder')
|
140 |
-
time.sleep(30-elapsed_time)
|
141 |
-
#time.sleep(30)
|
142 |
-
|
143 |
-
except Exception as e:
|
144 |
-
recording = False
|
145 |
-
print('exception', str(e))
|
146 |
-
return str(e)
|
147 |
-
|
148 |
-
@app.route("/start_process", methods=["POST"])
|
149 |
-
def start_process():
|
150 |
-
if not os.path.isfile(local_transcript):
|
151 |
-
global recording, stream_process
|
152 |
-
with open(local_transcript, 'a', encoding='utf-8', errors='ignore') as f: f.write('') # Create the local transcript file, which is used as a check to prevent multiple recordings
|
153 |
-
|
154 |
-
youtube_url = request.form.get("url")
|
155 |
-
if not youtube_url:
|
156 |
-
return jsonify({"message": "Please provide a valid YouTube URL."}), 400
|
157 |
-
|
158 |
-
if recording:
|
159 |
-
return jsonify({"message": "A recording is already in progress."}), 400
|
160 |
-
|
161 |
-
print('In start process')
|
162 |
-
recording = True
|
163 |
-
stream_process = threading.Thread(target=save_audio, args=(youtube_url,))
|
164 |
-
stream_process.start()
|
165 |
-
|
166 |
-
return jsonify({"message": "Recording started."}), 200
|
167 |
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
mp3_extraction_process.terminate()
|
182 |
-
mp3_extraction_process = None
|
183 |
-
for f in os.listdir('mp3/'): os.remove(os.path.join('mp3/', f))
|
184 |
-
if os.path.isfile(local_transcript): os.remove(local_transcript)
|
185 |
-
# check if pid_file exists, get the pid inside it and convert to int, and use os.kill to kill it
|
186 |
-
if os.path.isfile(pid_file):
|
187 |
-
with open(pid_file, 'r') as f: pid = int(f.read())
|
188 |
-
try:
|
189 |
-
os.kill(pid, 9) # For linux
|
190 |
-
print("Process terminated successfully in Linux.")
|
191 |
-
except:
|
192 |
-
try:
|
193 |
-
process = subprocess.Popen(["taskkill", "/F", "/PID", str(pid)], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # For Windows
|
194 |
-
process.communicate()
|
195 |
-
print("Process terminated successfully in Windows.")
|
196 |
-
except Exception as e:
|
197 |
-
print("Error:", e)
|
198 |
-
os.remove(pid_file)
|
199 |
-
|
200 |
-
return jsonify({"message": "Recording stopped."}), 200
|
201 |
-
|
202 |
-
@app.route('/google/')
|
203 |
-
def google():
|
204 |
-
CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration'
|
205 |
-
oauth.register(
|
206 |
-
name='google',
|
207 |
-
client_id=GOOGLE_CLIENT_ID,
|
208 |
-
client_secret=GOOGLE_CLIENT_SECRET,
|
209 |
-
server_metadata_url=CONF_URL,
|
210 |
-
client_kwargs={"scope": "openid email profile"}
|
211 |
-
)
|
212 |
-
|
213 |
-
# Redirect to google_auth function/page
|
214 |
-
redirect_uri = url_for('google_auth', _external=True)
|
215 |
-
session['nonce'] = generate_token()
|
216 |
-
return oauth.google.authorize_redirect(redirect_uri, nonce=session['nonce'])
|
217 |
-
|
218 |
-
@app.route('/google/auth/')
|
219 |
-
def google_auth():
|
220 |
-
token = oauth.google.authorize_access_token()
|
221 |
-
user = oauth.google.parse_id_token(token, nonce=session['nonce'])
|
222 |
-
session['user'] = user
|
223 |
-
print('USER', user)
|
224 |
-
# Redirect to home if login successful
|
225 |
-
return redirect('/home')
|
226 |
-
|
227 |
-
def is_not_logged_in():
|
228 |
-
return session.get('user') is None or session.get('nonce') is None
|
229 |
-
|
230 |
-
# decorator to check if user is logged in, used for protected URLs
|
231 |
-
def login_required(f):
|
232 |
-
@wraps(f)
|
233 |
-
def decorated_function(*args, **kwargs):
|
234 |
-
if is_not_logged_in():
|
235 |
-
return redirect('/login')
|
236 |
-
return f(*args, **kwargs)
|
237 |
-
return decorated_function
|
238 |
-
|
239 |
-
@app.route("/home")
|
240 |
-
@login_required
|
241 |
-
def home():
|
242 |
-
return render_template("home.html")
|
243 |
-
|
244 |
-
@app.route("/", methods=["GET"])
|
245 |
-
@app.route("/login", methods=["GET"])
|
246 |
-
def login():
|
247 |
-
if not is_not_logged_in():
|
248 |
-
return redirect("/home")
|
249 |
-
#return render_template("login.html")
|
250 |
-
return render_template("home.html")
|
251 |
|
252 |
if __name__ == "__main__":
|
253 |
app.run(host="0.0.0.0", debug=True, port=7860)
|
|
|
1 |
+
from flask import Flask, render_template, request, jsonify
|
2 |
+
from qdrant_client import QdrantClient
|
3 |
+
from qdrant_client import models
|
4 |
+
import torch.nn.functional as F
|
5 |
+
import torch
|
6 |
+
from torch import Tensor
|
7 |
+
from transformers import AutoTokenizer, AutoModel
|
8 |
+
from qdrant_client.models import Batch, PointStruct
|
9 |
+
from pickle import load, dump
|
10 |
+
import numpy as np
|
11 |
+
import os, time, sys
|
12 |
from datetime import datetime as dt
|
13 |
+
from datetime import timedelta
|
14 |
+
from datetime import timezone
|
15 |
+
|
16 |
+
app = Flask(__name__)
|
17 |
+
|
18 |
+
# Initialize Qdrant Client and other required settings
|
19 |
+
qdrant_api_key = os.environ.get("qdrant_api_key")
|
20 |
+
qdrant_url = os.environ.get("qdrant_url")
|
21 |
+
|
22 |
+
client = QdrantClient(url=qdrant_url, port=443, api_key=qdrant_api_key, prefer_grpc=False)
|
23 |
+
|
24 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
25 |
+
|
26 |
+
def average_pool(last_hidden_states: Tensor,
|
27 |
+
attention_mask: Tensor) -> Tensor:
|
28 |
+
last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
|
29 |
+
return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]
|
30 |
+
|
31 |
+
tokenizer = AutoTokenizer.from_pretrained('intfloat/e5-base-v2')
|
32 |
+
model = AutoModel.from_pretrained('intfloat/e5-base-v2').to(device)
|
33 |
+
|
34 |
+
def e5embed(query):
|
35 |
+
batch_dict = tokenizer(query, max_length=512, padding=True, truncation=True, return_tensors='pt')
|
36 |
+
batch_dict = {k: v.to(device) for k, v in batch_dict.items()}
|
37 |
+
outputs = model(**batch_dict)
|
38 |
+
embeddings = average_pool(outputs.last_hidden_state, batch_dict['attention_mask'])
|
39 |
+
embeddings = F.normalize(embeddings, p=2, dim=1)
|
40 |
+
embeddings = embeddings.cpu().detach().numpy().flatten().tolist()
|
41 |
+
return embeddings
|
42 |
+
|
43 |
+
@app.route("/")
|
44 |
+
def index():
|
45 |
+
return render_template("index.html")
|
46 |
+
|
47 |
+
@app.route("/search", methods=["POST"])
|
48 |
+
def search():
|
49 |
+
query = request.form["query"]
|
50 |
+
topN = 200 # Define your topN value
|
51 |
+
|
52 |
+
|
53 |
+
print('QUERY: ',query)
|
54 |
+
if query.strip().startswith('tilc:'):
|
55 |
+
collection_name = 'tils'
|
56 |
+
qvector = "context"
|
57 |
+
query = query.replace('tilc:', '')
|
58 |
+
elif query.strip().startswith('til:'):
|
59 |
+
collection_name = 'tils'
|
60 |
+
qvector = "title"
|
61 |
+
query = query.replace('til:', '')
|
62 |
+
else: collection_name = 'jks'
|
63 |
+
|
64 |
+
timh = time.time()
|
65 |
+
sq = e5embed(query)
|
66 |
+
print('EMBEDDING TIME: ', time.time() - timh)
|
67 |
+
|
68 |
+
timh = time.time()
|
69 |
+
if collection_name == "jks": results = client.search(collection_name=collection_name, query_vector=sq, with_payload=True, limit=topN)
|
70 |
+
else: results = client.search(collection_name=collection_name, query_vector=(qvector, sq), with_payload=True, limit=100)
|
71 |
+
print('SEARCH TIME: ', time.time() - timh)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
|
73 |
+
print(results[0].payload['text'].split('\n'))
|
74 |
+
try:
|
75 |
+
results = [{"text": x.payload['text'], "date": str(int(x.payload['date'])), "id": x.id} for x in results] # Implement your Qdrant search here
|
76 |
+
return jsonify(results)
|
77 |
+
except:
|
78 |
+
return jsonify([])
|
79 |
+
|
80 |
+
@app.route("/delete_joke", methods=["POST"])
|
81 |
+
def delete_joke():
|
82 |
+
joke_id = request.form["id"]
|
83 |
+
print('Deleting joke no', joke_id)
|
84 |
+
client.delete(collection_name="jks", points_selector=models.PointIdsList(points=[int(joke_id)],),)
|
85 |
+
return jsonify({"deleted": True})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
86 |
|
87 |
if __name__ == "__main__":
|
88 |
app.run(host="0.0.0.0", debug=True, port=7860)
|
modules/dataset.py
DELETED
@@ -1,19 +0,0 @@
|
|
1 |
-
from datasets import load_dataset
|
2 |
-
|
3 |
-
dataset = load_dataset("go_emotions", split="train")
|
4 |
-
|
5 |
-
emotions = dataset.info.features['labels'].feature.names
|
6 |
-
|
7 |
-
def query_emotion(start, end):
|
8 |
-
rows = dataset[start:end]
|
9 |
-
texts, labels = [rows[k] for k in rows.keys()]
|
10 |
-
|
11 |
-
observations = []
|
12 |
-
|
13 |
-
for i, text in enumerate(texts):
|
14 |
-
observations.append({
|
15 |
-
"text": text,
|
16 |
-
"emotion": emotions[labels[i]],
|
17 |
-
})
|
18 |
-
|
19 |
-
return observations
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modules/inference.py
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
from transformers import T5Tokenizer, T5ForConditionalGeneration
|
2 |
-
|
3 |
-
tokenizer = T5Tokenizer.from_pretrained("t5-small")
|
4 |
-
model = T5ForConditionalGeneration.from_pretrained("t5-small")
|
5 |
-
|
6 |
-
|
7 |
-
def infer_t5(input):
|
8 |
-
input_ids = tokenizer(input, return_tensors="pt").input_ids
|
9 |
-
outputs = model.generate(input_ids)
|
10 |
-
|
11 |
-
return tokenizer.decode(outputs[0], skip_special_tokens=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
@@ -1,9 +1,5 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
faster-whisper
|
5 |
-
requests
|
6 |
-
ffmpeg-python
|
7 |
-
Authlib
|
8 |
flask
|
9 |
-
Werkzeug
|
|
|
1 |
+
transformers
|
2 |
+
torch
|
3 |
+
qdrant-client
|
|
|
|
|
|
|
|
|
4 |
flask
|
5 |
+
Werkzeug
|
static/script.js
DELETED
@@ -1,47 +0,0 @@
|
|
1 |
-
$(document).ready(function () {
|
2 |
-
let recording = false;
|
3 |
-
|
4 |
-
$("#startBtn").click(function () {
|
5 |
-
const youtubeUrl = $("#urlInput").val().trim();
|
6 |
-
if (youtubeUrl === "") {
|
7 |
-
showMessage("Please enter a valid YouTube Livestream URL.", "danger");
|
8 |
-
return;
|
9 |
-
}
|
10 |
-
|
11 |
-
// Call the start_process route in Flask
|
12 |
-
$.ajax({
|
13 |
-
type: "POST",
|
14 |
-
url: "/start_process",
|
15 |
-
data: { url: youtubeUrl },
|
16 |
-
success: function (data) {
|
17 |
-
showMessage(data.message, "success");
|
18 |
-
recording = true;
|
19 |
-
},
|
20 |
-
error: function (xhr, status, error) {
|
21 |
-
showMessage("Error: " + xhr.responseText, "danger");
|
22 |
-
},
|
23 |
-
});
|
24 |
-
});
|
25 |
-
|
26 |
-
$("#stopBtn").click(function () {
|
27 |
-
showMessage("Stopping transcription. This may take upto 30 sec.", "success");
|
28 |
-
// Call the stop_process route in Flask
|
29 |
-
$.ajax({
|
30 |
-
type: "POST",
|
31 |
-
url: "/stop_process",
|
32 |
-
success: function (data) {
|
33 |
-
showMessage(data.message, "success");
|
34 |
-
recording = false;
|
35 |
-
},
|
36 |
-
error: function (xhr, status, error) {
|
37 |
-
showMessage("Error: " + xhr.responseText, "danger");
|
38 |
-
},
|
39 |
-
});
|
40 |
-
});
|
41 |
-
|
42 |
-
function showMessage(message, type) {
|
43 |
-
$("#message").html(
|
44 |
-
`<div class="alert alert-${type}" role="alert">${message}</div>`
|
45 |
-
);
|
46 |
-
}
|
47 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/style.css
DELETED
@@ -1,37 +0,0 @@
|
|
1 |
-
#container {
|
2 |
-
width: 600px;
|
3 |
-
margin: 0 auto;
|
4 |
-
text-align: center;
|
5 |
-
}
|
6 |
-
|
7 |
-
#urlInput {
|
8 |
-
width: 500px;
|
9 |
-
}
|
10 |
-
|
11 |
-
button {
|
12 |
-
margin: 10px;
|
13 |
-
}
|
14 |
-
|
15 |
-
#login {
|
16 |
-
display: flex;
|
17 |
-
justify-content: center;
|
18 |
-
flex-direction: row;
|
19 |
-
}
|
20 |
-
|
21 |
-
#loginButton {
|
22 |
-
display: flex;
|
23 |
-
background-color: rgb(255, 255, 255);
|
24 |
-
justify-content: center;
|
25 |
-
align-items: center;
|
26 |
-
width: fit-content;
|
27 |
-
padding: 10px 30px;
|
28 |
-
box-shadow: 1px 1px 1px 1px rgb(170, 170, 170);
|
29 |
-
border-radius: 15px;
|
30 |
-
border: 1px solid rgb(170, 170, 170);
|
31 |
-
}
|
32 |
-
|
33 |
-
#loginButtonText {
|
34 |
-
justify-content: center;
|
35 |
-
padding: 10px;
|
36 |
-
color: rgb(85, 85, 85);
|
37 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
templates/home.html
DELETED
@@ -1,85 +0,0 @@
|
|
1 |
-
<!DOCTYPE html>
|
2 |
-
<html>
|
3 |
-
|
4 |
-
<head>
|
5 |
-
<title>YouTube Livestream Audio Recorder</title>
|
6 |
-
<!-- Add necessary CSS and jQuery libraries -->
|
7 |
-
<style>
|
8 |
-
body {
|
9 |
-
font-family: Roboto, sans-serif;
|
10 |
-
margin: 0;
|
11 |
-
padding: 0;
|
12 |
-
height: 100%;
|
13 |
-
}
|
14 |
-
|
15 |
-
#container {
|
16 |
-
width: 600px;
|
17 |
-
margin: 0 auto;
|
18 |
-
text-align: center;
|
19 |
-
}
|
20 |
-
|
21 |
-
#url {
|
22 |
-
width: 500px;
|
23 |
-
}
|
24 |
-
|
25 |
-
button {
|
26 |
-
margin: 10px;
|
27 |
-
}
|
28 |
-
|
29 |
-
.row, h2 {
|
30 |
-
display: flex;
|
31 |
-
align-items: center;
|
32 |
-
margin: 10px;
|
33 |
-
}
|
34 |
-
|
35 |
-
select, button {
|
36 |
-
padding: 5px;
|
37 |
-
border: 1px solid #ccc;
|
38 |
-
border-radius: 5px;
|
39 |
-
}
|
40 |
-
|
41 |
-
input {
|
42 |
-
padding: 5px;
|
43 |
-
border: 1px solid #ccc;
|
44 |
-
border-radius: 5px;
|
45 |
-
width: 500px;
|
46 |
-
}
|
47 |
-
|
48 |
-
label, #message {
|
49 |
-
margin: 0px 5px 0px 10px;
|
50 |
-
}
|
51 |
-
|
52 |
-
button {
|
53 |
-
background-color: #2196f3;
|
54 |
-
color: white;
|
55 |
-
cursor: pointer;
|
56 |
-
margin: 0px 0px 0px 3px;
|
57 |
-
}
|
58 |
-
|
59 |
-
button:hover {
|
60 |
-
background-color: #1976d2;
|
61 |
-
}
|
62 |
-
</style>
|
63 |
-
</head>
|
64 |
-
|
65 |
-
<body>
|
66 |
-
<div class="container">
|
67 |
-
<h2>Search & Transcribing Utilities</h2>
|
68 |
-
<div class="mb-3 row">
|
69 |
-
<label for="urlInput" class="form-label">Enter YouTube Livestream URL:</label>
|
70 |
-
<input type="text" class="form-control" id="urlInput"
|
71 |
-
placeholder="e.g., https://www.youtube.com/watch?v=YOUR_STREAM_ID">
|
72 |
-
|
73 |
-
<button class="btn btn-primary" id="startBtn">Start</button>
|
74 |
-
<button class="btn btn-danger" id="stopBtn">Stop</button>
|
75 |
-
<div id="message"></div>
|
76 |
-
</div>
|
77 |
-
|
78 |
-
<!-- Add necessary jQuery library -->
|
79 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
80 |
-
<!-- Add custom script for handling button clicks -->
|
81 |
-
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
82 |
-
|
83 |
-
</body>
|
84 |
-
|
85 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
templates/index.html
CHANGED
@@ -1,85 +1,112 @@
|
|
1 |
<!DOCTYPE html>
|
2 |
-
<html>
|
3 |
-
|
4 |
<head>
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
text-align: center;
|
19 |
-
}
|
20 |
-
|
21 |
-
#url {
|
22 |
-
width: 500px;
|
23 |
-
}
|
24 |
-
|
25 |
-
button {
|
26 |
-
margin: 10px;
|
27 |
-
}
|
28 |
-
|
29 |
-
.row, h2 {
|
30 |
display: flex;
|
|
|
31 |
align-items: center;
|
32 |
-
|
33 |
-
}
|
34 |
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
}
|
40 |
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
border
|
45 |
-
|
46 |
-
|
|
|
|
|
47 |
|
48 |
-
|
49 |
-
margin:
|
50 |
-
|
51 |
|
52 |
-
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
color: white;
|
|
|
|
|
55 |
cursor: pointer;
|
56 |
-
margin:
|
57 |
-
|
58 |
-
|
59 |
-
button:hover {
|
60 |
-
background-color: #1976d2;
|
61 |
-
}
|
62 |
-
</style>
|
63 |
</head>
|
64 |
-
|
65 |
<body>
|
66 |
-
|
67 |
-
<
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
<button class="btn btn-primary" id="startBtn">Start</button>
|
74 |
-
<button class="btn btn-danger" id="stopBtn">Stop</button>
|
75 |
-
<div id="message"></div>
|
76 |
-
</div>
|
77 |
-
|
78 |
-
<!-- Add necessary jQuery library -->
|
79 |
-
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
|
80 |
-
<!-- Add custom script for handling button clicks -->
|
81 |
-
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
82 |
|
83 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
</html>
|
|
|
1 |
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
|
|
3 |
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<title>Joke Search</title>
|
6 |
+
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
7 |
+
<style>
|
8 |
+
body {
|
9 |
+
font-family: Arial, sans-serif;
|
10 |
+
}
|
11 |
+
|
12 |
+
h1 {
|
13 |
+
text-align: center;
|
14 |
+
}
|
15 |
+
|
16 |
+
#search-container {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
display: flex;
|
18 |
+
justify-content: center;
|
19 |
align-items: center;
|
20 |
+
}
|
|
|
21 |
|
22 |
+
#query {
|
23 |
+
width: 70%;
|
24 |
+
padding: 10px;
|
25 |
+
}
|
|
|
26 |
|
27 |
+
#search {
|
28 |
+
background-color: #4c72af;
|
29 |
+
color: white;
|
30 |
+
border: none;
|
31 |
+
padding: 10px 20px;
|
32 |
+
cursor: pointer;
|
33 |
+
margin-left: 10px; /* Add margin to separate the query bar and button */
|
34 |
+
}
|
35 |
|
36 |
+
#results {
|
37 |
+
margin: 20px;
|
38 |
+
}
|
39 |
|
40 |
+
.result {
|
41 |
+
border: 1px solid #ccc;
|
42 |
+
padding: 10px;
|
43 |
+
margin: 10px 0;
|
44 |
+
}
|
45 |
+
|
46 |
+
.delete {
|
47 |
+
background-color: #ff0000;
|
48 |
color: white;
|
49 |
+
border: none;
|
50 |
+
padding: 5px 10px;
|
51 |
cursor: pointer;
|
52 |
+
margin: 0 10px;
|
53 |
+
}
|
54 |
+
</style>
|
|
|
|
|
|
|
|
|
55 |
</head>
|
|
|
56 |
<body>
|
57 |
+
<h1>Joke Search</h1>
|
58 |
+
<div id="search-container">
|
59 |
+
<input type="text" id="query" placeholder="Search...">
|
60 |
+
<button id="search">Search</button>
|
61 |
+
</div>
|
62 |
+
<div id="results"></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
|
64 |
+
<script>
|
65 |
+
$(document).ready(function () {
|
66 |
+
function performSearch() {
|
67 |
+
var query = $("#query").val();
|
68 |
+
|
69 |
+
$.post("/search", { query: query }, function (data) {
|
70 |
+
var results = $("#results");
|
71 |
+
results.empty();
|
72 |
+
|
73 |
+
data.forEach(function (result) {
|
74 |
+
var resultElement = $("<div class='result'>" +
|
75 |
+
"<p>" + result.text + "</p>" +
|
76 |
+
"<small>Date: " + result.date + "</small>" +
|
77 |
+
"<button class='delete' data-id='" + result.id + "'>Delete</button>" +
|
78 |
+
"</div>");
|
79 |
+
|
80 |
+
results.append(resultElement);
|
81 |
|
82 |
+
resultElement.find(".delete").on("click", function () {
|
83 |
+
var id = $(this).data("id");
|
84 |
+
var resultButton = $(this);
|
85 |
+
|
86 |
+
$.post("/delete_joke", { id: id }, function (data) {
|
87 |
+
// Handle the delete response if needed
|
88 |
+
|
89 |
+
if (data.deleted) {
|
90 |
+
// Replace the button with "DELETED!" text
|
91 |
+
resultButton.replaceWith("<p class='deleted-text'>DELETED!</p>");
|
92 |
+
}
|
93 |
+
});
|
94 |
+
});
|
95 |
+
});
|
96 |
+
});
|
97 |
+
}
|
98 |
+
|
99 |
+
$("#search").on("click", function () {
|
100 |
+
performSearch();
|
101 |
+
});
|
102 |
+
|
103 |
+
$("#query").on("keydown", function (event) {
|
104 |
+
if (event.key === "Enter") {
|
105 |
+
event.preventDefault(); // Prevent form submission
|
106 |
+
performSearch();
|
107 |
+
}
|
108 |
+
});
|
109 |
+
});
|
110 |
+
</script>
|
111 |
+
</body>
|
112 |
</html>
|
templates/login.html
DELETED
@@ -1,27 +0,0 @@
|
|
1 |
-
<!DOCTYPE html>
|
2 |
-
<html lang="en">
|
3 |
-
|
4 |
-
<head>
|
5 |
-
<meta charset="UTF-8">
|
6 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7 |
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
8 |
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
9 |
-
<link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
|
10 |
-
<link rel="stylesheet" type="text/css" href="{{ url_for('static',filename='style.css') }}">
|
11 |
-
<title>Flask app</title>
|
12 |
-
</head>
|
13 |
-
|
14 |
-
<body>
|
15 |
-
<div id="login">
|
16 |
-
<a href="google/" id="loginButton" style="text-decoration:none;">
|
17 |
-
<img id="google"
|
18 |
-
src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/2048px-Google_%22G%22_Logo.svg.png"
|
19 |
-
alt="Google" style="width:50px;height:50px;">
|
20 |
-
<div id="loginButtonText">
|
21 |
-
Login with Google
|
22 |
-
</div>
|
23 |
-
</a>
|
24 |
-
</div>
|
25 |
-
</body>
|
26 |
-
|
27 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|