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>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|