Spaces:
Sleeping
Sleeping
Upload 6 files
Browse files- main copy 2.py +188 -0
- main copy.py +171 -0
- main.py +254 -219
- static/simple.css +84 -86
- templates/index copy.html +420 -0
- templates/index.html +487 -419
main copy 2.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import io
|
| 3 |
+
import base64
|
| 4 |
+
from flask import Flask, request, render_template, jsonify
|
| 5 |
+
from google import genai
|
| 6 |
+
from google.genai import types
|
| 7 |
+
from werkzeug.utils import secure_filename
|
| 8 |
+
import requests
|
| 9 |
+
import time
|
| 10 |
+
import tempfile
|
| 11 |
+
|
| 12 |
+
# Initialize Flask app
|
| 13 |
+
app = Flask(__name__)
|
| 14 |
+
|
| 15 |
+
# Initialize Gemini client
|
| 16 |
+
client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY", "AIzaSyCg9NGsLygb0sVKpviMkgV4eMPLd9nXW7w"))
|
| 17 |
+
|
| 18 |
+
# Set up generation config
|
| 19 |
+
generate_content_config = types.GenerateContentConfig(
|
| 20 |
+
temperature=1.0,
|
| 21 |
+
top_p=0.95,
|
| 22 |
+
top_k=40,
|
| 23 |
+
max_output_tokens=8192,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
# Initialize the model
|
| 27 |
+
model_name = "gemini-2.5-flash"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@app.route("/")
|
| 31 |
+
def index():
|
| 32 |
+
return render_template("index.html")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@app.route("/convert", methods=["POST"])
|
| 36 |
+
def convert_audio():
|
| 37 |
+
if "file" not in request.files:
|
| 38 |
+
return jsonify({"error": "No file part"}), 400
|
| 39 |
+
|
| 40 |
+
file = request.files["file"]
|
| 41 |
+
if file.filename == "":
|
| 42 |
+
return jsonify({"error": "No selected file"}), 400
|
| 43 |
+
|
| 44 |
+
if file:
|
| 45 |
+
try:
|
| 46 |
+
# Determine mime type based on file extension
|
| 47 |
+
mime_type = None
|
| 48 |
+
if file.filename.lower().endswith('.mp3'):
|
| 49 |
+
mime_type = 'audio/mpeg'
|
| 50 |
+
elif file.filename.lower().endswith('.wav'):
|
| 51 |
+
mime_type = 'audio/wav'
|
| 52 |
+
elif file.filename.lower().endswith('.m4a'):
|
| 53 |
+
mime_type = 'audio/x-m4a'
|
| 54 |
+
elif file.filename.lower().endswith('.ogg'):
|
| 55 |
+
mime_type = 'audio/ogg'
|
| 56 |
+
else:
|
| 57 |
+
return jsonify({"error": "Unsupported file type"}), 400
|
| 58 |
+
|
| 59 |
+
# Save the file temporarily
|
| 60 |
+
temp_dir = tempfile.mkdtemp()
|
| 61 |
+
temp_path = os.path.join(temp_dir, secure_filename(file.filename))
|
| 62 |
+
file.save(temp_path)
|
| 63 |
+
|
| 64 |
+
# Upload file to Gemini with mime type
|
| 65 |
+
with open(temp_path, 'rb') as f:
|
| 66 |
+
uploaded_file = client.files.upload(file=f,config={'mime_type': mime_type})
|
| 67 |
+
|
| 68 |
+
# Clean up temporary file
|
| 69 |
+
os.remove(temp_path)
|
| 70 |
+
os.rmdir(temp_dir)
|
| 71 |
+
|
| 72 |
+
# Create content for Gemini using uploaded file
|
| 73 |
+
prompt = """Extract the Gujarati lyrics from the provided audio file and output them using the following HTML template. Ensure that each line of the lyrics is correctly inserted into the template and that any placeholders are replaced with the appropriate content from the file.
|
| 74 |
+
|
| 75 |
+
```
|
| 76 |
+
<html>
|
| 77 |
+
<head>
|
| 78 |
+
<title>Bhaktisudha</title>
|
| 79 |
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
| 80 |
+
<link href="/static/simple.css" rel="stylesheet" type="text/css" />
|
| 81 |
+
<style>
|
| 82 |
+
</style>
|
| 83 |
+
</head>
|
| 84 |
+
<body>
|
| 85 |
+
<div class="main">
|
| 86 |
+
<div class="gtitlev3">
|
| 87 |
+
{Title of the file}
|
| 88 |
+
</div>
|
| 89 |
+
<div class="gpara">
|
| 90 |
+
Line1 <br/>
|
| 91 |
+
Line2 <br/>
|
| 92 |
+
</div>
|
| 93 |
+
<div class="chend">
|
| 94 |
+
*****
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
</body>
|
| 98 |
+
</html>
|
| 99 |
+
```"""
|
| 100 |
+
|
| 101 |
+
# Generate response
|
| 102 |
+
response = ""
|
| 103 |
+
for chunk in client.models.generate_content_stream(
|
| 104 |
+
model=model_name,
|
| 105 |
+
contents=[uploaded_file, prompt],
|
| 106 |
+
config=generate_content_config,
|
| 107 |
+
):
|
| 108 |
+
response += chunk.text
|
| 109 |
+
|
| 110 |
+
# Clean up the response
|
| 111 |
+
lyrics = response.replace("```html", "").replace("```", "")
|
| 112 |
+
return jsonify({"lyrics": lyrics})
|
| 113 |
+
|
| 114 |
+
except Exception as e:
|
| 115 |
+
return jsonify({"error": str(e)}), 500
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
@app.route("/convert-youtube", methods=["POST"])
|
| 119 |
+
def convert_youtube():
|
| 120 |
+
try:
|
| 121 |
+
data = request.get_json()
|
| 122 |
+
url = data.get("url")
|
| 123 |
+
if not url:
|
| 124 |
+
return jsonify({"error": "No URL provided"}), 400
|
| 125 |
+
|
| 126 |
+
# Create content for Gemini
|
| 127 |
+
contents = [
|
| 128 |
+
types.Content(
|
| 129 |
+
role="user",
|
| 130 |
+
parts=[
|
| 131 |
+
types.Part(
|
| 132 |
+
file_data=types.FileData(
|
| 133 |
+
file_uri=url,
|
| 134 |
+
mime_type="video/*",
|
| 135 |
+
)
|
| 136 |
+
),
|
| 137 |
+
types.Part.from_text(
|
| 138 |
+
text="""Extract the Gujarati lyrics from the provided video and output them using the following HTML template. Ensure that each line of the lyrics is correctly inserted into the template and that any placeholders are replaced with the appropriate content from the file.
|
| 139 |
+
|
| 140 |
+
```
|
| 141 |
+
<html>
|
| 142 |
+
<head>
|
| 143 |
+
<title>Bhaktisudha</title>
|
| 144 |
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
| 145 |
+
<link href="/static/simple.css" rel="stylesheet" type="text/css" />
|
| 146 |
+
<style>
|
| 147 |
+
</style>
|
| 148 |
+
</head>
|
| 149 |
+
<body>
|
| 150 |
+
<div class="main">
|
| 151 |
+
<div class="gtitlev3">
|
| 152 |
+
{Title of the file}
|
| 153 |
+
</div>
|
| 154 |
+
<div class="gpara">
|
| 155 |
+
Line1 <br/>
|
| 156 |
+
Line2 <br/>
|
| 157 |
+
</div>
|
| 158 |
+
<div class="chend">
|
| 159 |
+
*****
|
| 160 |
+
</div>
|
| 161 |
+
</div>
|
| 162 |
+
</body>
|
| 163 |
+
</html>
|
| 164 |
+
```"""
|
| 165 |
+
),
|
| 166 |
+
],
|
| 167 |
+
),
|
| 168 |
+
]
|
| 169 |
+
|
| 170 |
+
# Generate response
|
| 171 |
+
response = ""
|
| 172 |
+
for chunk in client.models.generate_content_stream(
|
| 173 |
+
model=model_name,
|
| 174 |
+
contents=contents,
|
| 175 |
+
config=generate_content_config,
|
| 176 |
+
):
|
| 177 |
+
response += chunk.text
|
| 178 |
+
|
| 179 |
+
# Clean up the response
|
| 180 |
+
lyrics = response.replace("```html", "").replace("```", "")
|
| 181 |
+
return jsonify({"lyrics": lyrics})
|
| 182 |
+
|
| 183 |
+
except Exception as e:
|
| 184 |
+
return jsonify({"error": str(e)})
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
if __name__ == "__main__":
|
| 188 |
+
app.run(debug=True, host="0.0.0.0", port=7860)
|
main copy.py
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import io
|
| 3 |
+
from flask import Flask, request, render_template, jsonify
|
| 4 |
+
import google.generativeai as genai
|
| 5 |
+
from werkzeug.utils import secure_filename
|
| 6 |
+
import requests
|
| 7 |
+
import time
|
| 8 |
+
from google.generativeai import types
|
| 9 |
+
|
| 10 |
+
# Initialize Flask app
|
| 11 |
+
app = Flask(__name__)
|
| 12 |
+
|
| 13 |
+
# Configure Gemini API
|
| 14 |
+
genai.configure(api_key="AIzaSyCg9NGsLygb0sVKpviMkgV4eMPLd9nXW7w")
|
| 15 |
+
|
| 16 |
+
# Set up generation config
|
| 17 |
+
generation_config = {
|
| 18 |
+
"temperature": 1,
|
| 19 |
+
"top_p": 0.95,
|
| 20 |
+
"top_k": 40,
|
| 21 |
+
"max_output_tokens": 8192,
|
| 22 |
+
"response_mime_type": "text/plain",
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
# Initialize the model
|
| 26 |
+
model = genai.GenerativeModel(
|
| 27 |
+
model_name="gemini-2.0-flash",
|
| 28 |
+
generation_config=generation_config,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
def upload_to_gemini(file_bytes, mime_type=None):
|
| 32 |
+
"""Uploads the given file bytes to Gemini."""
|
| 33 |
+
file = genai.upload_file(file_bytes, mime_type=mime_type)
|
| 34 |
+
print(f"Uploaded file as: {file.uri}")
|
| 35 |
+
return file
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@app.route('/')
|
| 39 |
+
def index():
|
| 40 |
+
return render_template('index.html')
|
| 41 |
+
|
| 42 |
+
@app.route('/convert', methods=['POST'])
|
| 43 |
+
def convert_audio():
|
| 44 |
+
if 'file' not in request.files:
|
| 45 |
+
return jsonify({'error': 'No file part'}), 400
|
| 46 |
+
|
| 47 |
+
file = request.files['file']
|
| 48 |
+
if file.filename == '':
|
| 49 |
+
return jsonify({'error': 'No selected file'}), 400
|
| 50 |
+
|
| 51 |
+
if file:
|
| 52 |
+
try:
|
| 53 |
+
# Read file bytes directly from the request
|
| 54 |
+
file_bytes = io.BytesIO(file.read())
|
| 55 |
+
|
| 56 |
+
# Determine mime type based on file extension
|
| 57 |
+
mime_type = 'audio/mpeg' if file.filename.endswith('.mp3') else \
|
| 58 |
+
'audio/wav' if file.filename.endswith('.wav') else \
|
| 59 |
+
'audio/x-m4a' if file.filename.endswith('.m4a') else \
|
| 60 |
+
'audio/ogg' if file.filename.endswith('.ogg') else None
|
| 61 |
+
|
| 62 |
+
# Upload to Gemini
|
| 63 |
+
gemini_file = upload_to_gemini(file_bytes, mime_type=mime_type)
|
| 64 |
+
|
| 65 |
+
# Start chat session with the uploaded file
|
| 66 |
+
chat_session = model.start_chat(
|
| 67 |
+
history=[
|
| 68 |
+
{
|
| 69 |
+
"role": "user",
|
| 70 |
+
"parts": [gemini_file],
|
| 71 |
+
},
|
| 72 |
+
]
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
# Generate lyrics
|
| 76 |
+
response = chat_session.send_message('''Extract the Gujarati lyrics from the provided audio file and output them using the following HTML template. Ensure that each line of the lyrics is correctly inserted into the template and that any placeholders are replaced with the appropriate content from the file.
|
| 77 |
+
|
| 78 |
+
```
|
| 79 |
+
<html>
|
| 80 |
+
<head>
|
| 81 |
+
<title>Bhaktisudha</title>
|
| 82 |
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
| 83 |
+
<link href="/static/simple.css" rel="stylesheet" type="text/css" />
|
| 84 |
+
<style>
|
| 85 |
+
</style>
|
| 86 |
+
</head>
|
| 87 |
+
<body>
|
| 88 |
+
<div class="main">
|
| 89 |
+
<div class="gtitlev3">
|
| 90 |
+
{Title of the file}
|
| 91 |
+
</div>
|
| 92 |
+
<div class="gpara">
|
| 93 |
+
Line1 <br/>
|
| 94 |
+
Line2 <br/>
|
| 95 |
+
</div>
|
| 96 |
+
<div class="chend">
|
| 97 |
+
*****
|
| 98 |
+
</div>
|
| 99 |
+
</div>
|
| 100 |
+
</body>
|
| 101 |
+
</html>
|
| 102 |
+
```''')
|
| 103 |
+
lyrics = response.text.replace("```html","").replace("```","")
|
| 104 |
+
return jsonify({'lyrics': lyrics})
|
| 105 |
+
|
| 106 |
+
except Exception as e:
|
| 107 |
+
return jsonify({'error': str(e)}), 500
|
| 108 |
+
|
| 109 |
+
@app.route('/convert-youtube', methods=['POST'])
|
| 110 |
+
def convert_youtube():
|
| 111 |
+
try:
|
| 112 |
+
data = request.get_json()
|
| 113 |
+
url = data.get('url')
|
| 114 |
+
if not url:
|
| 115 |
+
return jsonify({'error': 'No URL provided'}), 400
|
| 116 |
+
|
| 117 |
+
# Download audio from YouTube
|
| 118 |
+
audio_data = download_from_youtube(url)
|
| 119 |
+
|
| 120 |
+
# Create a file-like object from the audio data
|
| 121 |
+
file_bytes = io.BytesIO(audio_data)
|
| 122 |
+
|
| 123 |
+
# Upload to Gemini
|
| 124 |
+
gemini_file = upload_to_gemini(file_bytes, mime_type='audio/mpeg')
|
| 125 |
+
|
| 126 |
+
# Start chat session with the uploaded file
|
| 127 |
+
chat_session = model.start_chat(
|
| 128 |
+
history=[
|
| 129 |
+
{
|
| 130 |
+
"role": "user",
|
| 131 |
+
"parts": [gemini_file],
|
| 132 |
+
},
|
| 133 |
+
]
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
# Generate lyrics
|
| 137 |
+
response = chat_session.send_message('''Extract the Gujarati lyrics from the provided audio file and output them using the following HTML template. Ensure that each line of the lyrics is correctly inserted into the template and that any placeholders are replaced with the appropriate content from the file.
|
| 138 |
+
|
| 139 |
+
```
|
| 140 |
+
<html>
|
| 141 |
+
<head>
|
| 142 |
+
<title>Bhaktisudha</title>
|
| 143 |
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
| 144 |
+
<link href="/static/simple.css" rel="stylesheet" type="text/css" />
|
| 145 |
+
<style>
|
| 146 |
+
</style>
|
| 147 |
+
</head>
|
| 148 |
+
<body>
|
| 149 |
+
<div class="main">
|
| 150 |
+
<div class="gtitlev3">
|
| 151 |
+
{Title of the file}
|
| 152 |
+
</div>
|
| 153 |
+
<div class="gpara">
|
| 154 |
+
Line1 <br/>
|
| 155 |
+
Line2 <br/>
|
| 156 |
+
</div>
|
| 157 |
+
<div class="chend">
|
| 158 |
+
*****
|
| 159 |
+
</div>
|
| 160 |
+
</div>
|
| 161 |
+
</body>
|
| 162 |
+
</html>
|
| 163 |
+
```''')
|
| 164 |
+
lyrics = response.text.replace("```html","").replace("```","")
|
| 165 |
+
return jsonify({'lyrics': lyrics})
|
| 166 |
+
|
| 167 |
+
except Exception as e:
|
| 168 |
+
return jsonify({'error': str(e)})
|
| 169 |
+
|
| 170 |
+
if __name__ == '__main__':
|
| 171 |
+
app.run(debug=True, host='0.0.0.0', port=7860)
|
main.py
CHANGED
|
@@ -1,219 +1,254 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import io
|
| 3 |
-
import base64
|
| 4 |
-
from flask import Flask, request, render_template, jsonify
|
| 5 |
-
from google import genai
|
| 6 |
-
from google.genai import types
|
| 7 |
-
from werkzeug.utils import secure_filename
|
| 8 |
-
import requests
|
| 9 |
-
import time
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
@app.route(
|
| 36 |
-
def convert_audio():
|
| 37 |
-
if
|
| 38 |
-
return jsonify({
|
| 39 |
-
|
| 40 |
-
file = request.files[
|
| 41 |
-
if file.filename ==
|
| 42 |
-
return jsonify({
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
# Determine mime type based on file extension
|
| 50 |
-
mime_type =
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
<
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
lyrics = response.
|
| 145 |
-
return jsonify({
|
| 146 |
-
|
| 147 |
-
except Exception as e:
|
| 148 |
-
return jsonify({
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
<
|
| 175 |
-
<
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import io
|
| 3 |
+
import base64
|
| 4 |
+
from flask import Flask, request, render_template, jsonify
|
| 5 |
+
from google import genai
|
| 6 |
+
from google.genai import types
|
| 7 |
+
from werkzeug.utils import secure_filename
|
| 8 |
+
import requests
|
| 9 |
+
import time
|
| 10 |
+
import tempfile
|
| 11 |
+
|
| 12 |
+
# Initialize Flask app
|
| 13 |
+
app = Flask(__name__)
|
| 14 |
+
|
| 15 |
+
# Initialize Gemini client
|
| 16 |
+
client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY", "AIzaSyCg9NGsLygb0sVKpviMkgV4eMPLd9nXW7w"))
|
| 17 |
+
|
| 18 |
+
# Set up generation config
|
| 19 |
+
generate_content_config = types.GenerateContentConfig(
|
| 20 |
+
temperature=1.0,
|
| 21 |
+
top_p=0.95,
|
| 22 |
+
top_k=40,
|
| 23 |
+
max_output_tokens=8192,
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
# Initialize the model
|
| 27 |
+
model_name = "gemini-2.5-flash"
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@app.route("/")
|
| 31 |
+
def index():
|
| 32 |
+
return render_template("index.html")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@app.route("/convert", methods=["POST"])
|
| 36 |
+
def convert_audio():
|
| 37 |
+
if "file" not in request.files:
|
| 38 |
+
return jsonify({"error": "No file part"}), 400
|
| 39 |
+
|
| 40 |
+
file = request.files["file"]
|
| 41 |
+
if file.filename == "":
|
| 42 |
+
return jsonify({"error": "No selected file"}), 400
|
| 43 |
+
|
| 44 |
+
# Get the mode type from form data
|
| 45 |
+
mode_type = request.form.get("type", "music") # Default to music mode
|
| 46 |
+
|
| 47 |
+
if file:
|
| 48 |
+
try:
|
| 49 |
+
# Determine mime type based on file extension
|
| 50 |
+
mime_type = None
|
| 51 |
+
if file.filename.lower().endswith('.mp3'):
|
| 52 |
+
mime_type = 'audio/mpeg'
|
| 53 |
+
elif file.filename.lower().endswith('.wav'):
|
| 54 |
+
mime_type = 'audio/wav'
|
| 55 |
+
elif file.filename.lower().endswith('.m4a'):
|
| 56 |
+
mime_type = 'audio/x-m4a'
|
| 57 |
+
elif file.filename.lower().endswith('.ogg'):
|
| 58 |
+
mime_type = 'audio/ogg'
|
| 59 |
+
else:
|
| 60 |
+
return jsonify({"error": "Unsupported file type"}), 400
|
| 61 |
+
|
| 62 |
+
# Save the file temporarily
|
| 63 |
+
temp_dir = tempfile.mkdtemp()
|
| 64 |
+
temp_path = os.path.join(temp_dir, secure_filename(file.filename))
|
| 65 |
+
file.save(temp_path)
|
| 66 |
+
|
| 67 |
+
# Upload file to Gemini with mime type
|
| 68 |
+
with open(temp_path, 'rb') as f:
|
| 69 |
+
uploaded_file = client.files.upload(file=f,config={'mime_type': mime_type})
|
| 70 |
+
|
| 71 |
+
# Clean up temporary file
|
| 72 |
+
os.remove(temp_path)
|
| 73 |
+
os.rmdir(temp_dir)
|
| 74 |
+
|
| 75 |
+
# Choose prompt based on mode
|
| 76 |
+
if mode_type == "speech":
|
| 77 |
+
prompt = """Extract the Gujarati speech content from the provided audio file and output it using the following HTML template. Format the content into meaningful paragraphs instead of line breaks. Ensure that each paragraph flows naturally and represents coherent thoughts or topics from the speech.
|
| 78 |
+
|
| 79 |
+
```
|
| 80 |
+
<html>
|
| 81 |
+
<head>
|
| 82 |
+
<title>Pravachan</title>
|
| 83 |
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
| 84 |
+
<link href="/static/simple.css" rel="stylesheet" type="text/css" />
|
| 85 |
+
<style>
|
| 86 |
+
</style>
|
| 87 |
+
</head>
|
| 88 |
+
<body>
|
| 89 |
+
<div class="main">
|
| 90 |
+
<div class="gtitlev3">
|
| 91 |
+
{Title of the audio content}
|
| 92 |
+
</div>
|
| 93 |
+
<div class="gpara">
|
| 94 |
+
<p>First paragraph of the speech content...</p>
|
| 95 |
+
<p>Second paragraph of the speech content...</p>
|
| 96 |
+
<p>Continue with more paragraphs as needed...</p>
|
| 97 |
+
</div>
|
| 98 |
+
<div class="chend">
|
| 99 |
+
*****
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
</body>
|
| 103 |
+
</html>
|
| 104 |
+
```"""
|
| 105 |
+
else: # Default to music mode
|
| 106 |
+
prompt = """Extract the Gujarati lyrics from the provided audio file and output them using the following HTML template. Ensure that each line of the lyrics is correctly inserted into the template and that any placeholders are replaced with the appropriate content from the file.
|
| 107 |
+
|
| 108 |
+
```
|
| 109 |
+
<html>
|
| 110 |
+
<head>
|
| 111 |
+
<title>Bhaktisudha</title>
|
| 112 |
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
| 113 |
+
<link href="/static/simple.css" rel="stylesheet" type="text/css" />
|
| 114 |
+
<style>
|
| 115 |
+
</style>
|
| 116 |
+
</head>
|
| 117 |
+
<body>
|
| 118 |
+
<div class="main">
|
| 119 |
+
<div class="gtitlev3">
|
| 120 |
+
{Title of the file}
|
| 121 |
+
</div>
|
| 122 |
+
<div class="gpara">
|
| 123 |
+
Line1 <br/>
|
| 124 |
+
Line2 <br/>
|
| 125 |
+
</div>
|
| 126 |
+
<div class="chend">
|
| 127 |
+
*****
|
| 128 |
+
</div>
|
| 129 |
+
</div>
|
| 130 |
+
</body>
|
| 131 |
+
</html>
|
| 132 |
+
```"""
|
| 133 |
+
|
| 134 |
+
# Generate response
|
| 135 |
+
response = ""
|
| 136 |
+
for chunk in client.models.generate_content_stream(
|
| 137 |
+
model=model_name,
|
| 138 |
+
contents=[uploaded_file, prompt],
|
| 139 |
+
config=generate_content_config,
|
| 140 |
+
):
|
| 141 |
+
response += chunk.text
|
| 142 |
+
|
| 143 |
+
# Clean up the response
|
| 144 |
+
lyrics = response.replace("```html", "").replace("```", "")
|
| 145 |
+
return jsonify({"lyrics": lyrics})
|
| 146 |
+
|
| 147 |
+
except Exception as e:
|
| 148 |
+
return jsonify({"error": str(e)}), 500
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
@app.route("/convert-youtube", methods=["POST"])
|
| 152 |
+
def convert_youtube():
|
| 153 |
+
try:
|
| 154 |
+
data = request.get_json()
|
| 155 |
+
url = data.get("url")
|
| 156 |
+
mode_type = data.get("type", "music") # Get mode type, default to music
|
| 157 |
+
|
| 158 |
+
if not url:
|
| 159 |
+
return jsonify({"error": "No URL provided"}), 400
|
| 160 |
+
|
| 161 |
+
# Choose prompt based on mode
|
| 162 |
+
if mode_type == "speech":
|
| 163 |
+
prompt_text = """Extract the Gujarati speech content from the provided video and output it using the following HTML template. Format the content into meaningful paragraphs instead of line breaks. Ensure that each paragraph flows naturally and represents coherent thoughts or topics from the speech.
|
| 164 |
+
|
| 165 |
+
```
|
| 166 |
+
<html>
|
| 167 |
+
<head>
|
| 168 |
+
<title>Pravachan</title>
|
| 169 |
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
| 170 |
+
<link href="/static/simple.css" rel="stylesheet" type="text/css" />
|
| 171 |
+
<style>
|
| 172 |
+
</style>
|
| 173 |
+
</head>
|
| 174 |
+
<body>
|
| 175 |
+
<div class="main">
|
| 176 |
+
<div class="gtitlev3">
|
| 177 |
+
{Title of the video content}
|
| 178 |
+
</div>
|
| 179 |
+
<div class="gpara">
|
| 180 |
+
<p>First paragraph of the speech content...</p>
|
| 181 |
+
<p>Second paragraph of the speech content...</p>
|
| 182 |
+
<p>Continue with more paragraphs as needed...</p>
|
| 183 |
+
</div>
|
| 184 |
+
<div class="chend">
|
| 185 |
+
*****
|
| 186 |
+
</div>
|
| 187 |
+
</div>
|
| 188 |
+
</body>
|
| 189 |
+
</html>
|
| 190 |
+
```"""
|
| 191 |
+
else: # Default to music mode
|
| 192 |
+
prompt_text = """Extract the Gujarati lyrics from the provided video and output them using the following HTML template. Ensure that each line of the lyrics is correctly inserted into the template and that any placeholders are replaced with the appropriate content from the file.
|
| 193 |
+
|
| 194 |
+
```
|
| 195 |
+
<html>
|
| 196 |
+
<head>
|
| 197 |
+
<title>Bhaktisudha</title>
|
| 198 |
+
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
|
| 199 |
+
<link href="/static/simple.css" rel="stylesheet" type="text/css" />
|
| 200 |
+
<style>
|
| 201 |
+
</style>
|
| 202 |
+
</head>
|
| 203 |
+
<body>
|
| 204 |
+
<div class="main">
|
| 205 |
+
<div class="gtitlev3">
|
| 206 |
+
{Title of the file}
|
| 207 |
+
</div>
|
| 208 |
+
<div class="gpara">
|
| 209 |
+
Line1 <br/>
|
| 210 |
+
Line2 <br/>
|
| 211 |
+
</div>
|
| 212 |
+
<div class="chend">
|
| 213 |
+
*****
|
| 214 |
+
</div>
|
| 215 |
+
</div>
|
| 216 |
+
</body>
|
| 217 |
+
</html>
|
| 218 |
+
```"""
|
| 219 |
+
|
| 220 |
+
# Create content for Gemini
|
| 221 |
+
contents = [
|
| 222 |
+
types.Content(
|
| 223 |
+
role="user",
|
| 224 |
+
parts=[
|
| 225 |
+
types.Part(
|
| 226 |
+
file_data=types.FileData(
|
| 227 |
+
file_uri=url,
|
| 228 |
+
mime_type="video/*",
|
| 229 |
+
)
|
| 230 |
+
),
|
| 231 |
+
types.Part.from_text(text=prompt_text),
|
| 232 |
+
],
|
| 233 |
+
),
|
| 234 |
+
]
|
| 235 |
+
|
| 236 |
+
# Generate response
|
| 237 |
+
response = ""
|
| 238 |
+
for chunk in client.models.generate_content_stream(
|
| 239 |
+
model=model_name,
|
| 240 |
+
contents=contents,
|
| 241 |
+
config=generate_content_config,
|
| 242 |
+
):
|
| 243 |
+
response += chunk.text
|
| 244 |
+
|
| 245 |
+
# Clean up the response
|
| 246 |
+
lyrics = response.replace("```html", "").replace("```", "")
|
| 247 |
+
return jsonify({"lyrics": lyrics})
|
| 248 |
+
|
| 249 |
+
except Exception as e:
|
| 250 |
+
return jsonify({"error": str(e)})
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
if __name__ == "__main__":
|
| 254 |
+
app.run(debug=True, host="0.0.0.0", port=7860)
|
static/simple.css
CHANGED
|
@@ -1,87 +1,85 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
.
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
.
|
| 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 |
-
padding-
|
| 46 |
-
padding-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
font-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
.
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
height:
|
| 66 |
-
font-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
/* #31475b */
|
| 86 |
-
;
|
| 87 |
}
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
font-family: 'Inter', sans-serif;
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
.dropzone {
|
| 6 |
+
border: 2px dashed #e5e7eb;
|
| 7 |
+
border-radius: 0.5rem;
|
| 8 |
+
transition: all 0.3s ease;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
.dropzone:hover,
|
| 12 |
+
.dropzone.dragover {
|
| 13 |
+
border-color: #3b82f6;
|
| 14 |
+
background-color: rgba(59, 130, 246, 0.05);
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
.pulse {
|
| 18 |
+
animation: pulse 2s infinite;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
@keyframes pulse {
|
| 22 |
+
0% {
|
| 23 |
+
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
70% {
|
| 27 |
+
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
100% {
|
| 31 |
+
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
.glass-morphism {
|
| 36 |
+
background: rgba(255, 255, 255, 0.7);
|
| 37 |
+
backdrop-filter: blur(10px);
|
| 38 |
+
-webkit-backdrop-filter: blur(10px);
|
| 39 |
+
border: 1px solid rgba(255, 255, 255, 0.18);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
.gtitlev3 {
|
| 43 |
+
padding-top: 0.25rem;
|
| 44 |
+
padding-bottom: 0.25rem;
|
| 45 |
+
padding-left: 0.5rem;
|
| 46 |
+
padding-right: 0.5rem;
|
| 47 |
+
margin-bottom: 0.75rem;
|
| 48 |
+
border-radius: 0.375rem;
|
| 49 |
+
font-size: 1.5rem;
|
| 50 |
+
line-height: 2rem;
|
| 51 |
+
font-weight: 700;
|
| 52 |
+
text-align: center;
|
| 53 |
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
| 54 |
+
background-color: rgb(255 248 220);
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
.chend {
|
| 58 |
+
padding-left: 1.25rem;
|
| 59 |
+
padding-right: 1.25rem;
|
| 60 |
+
margin-top: 0.75rem;
|
| 61 |
+
border-radius: 0.375rem;
|
| 62 |
+
width: fit-content;
|
| 63 |
+
height: 1.5rem;
|
| 64 |
+
font-size: 1.875rem;
|
| 65 |
+
line-height: 2.25rem;
|
| 66 |
+
font-weight: 700;
|
| 67 |
+
text-align: center;
|
| 68 |
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
|
| 69 |
+
color: rgb(49 71 91);
|
| 70 |
+
background-color: rgb(255 248 220);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.gpara {
|
| 74 |
+
font-weight: 600;
|
| 75 |
+
color: rgb(49 71 91)
|
| 76 |
+
/* #31475b */
|
| 77 |
+
;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.gparabhajan3 {
|
| 81 |
+
font-weight: 600;
|
| 82 |
+
color: rgb(49 71 91)
|
| 83 |
+
/* #31475b */
|
| 84 |
+
;
|
|
|
|
|
|
|
| 85 |
}
|
templates/index copy.html
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
<title>Audio to Lyrics Converter</title>
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
| 10 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
+
<style>
|
| 12 |
+
* {
|
| 13 |
+
font-family: 'Inter', sans-serif;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
.dropzone {
|
| 17 |
+
border: 2px dashed #e5e7eb;
|
| 18 |
+
border-radius: 0.5rem;
|
| 19 |
+
transition: all 0.3s ease;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
.dropzone:hover,
|
| 23 |
+
.dropzone.dragover {
|
| 24 |
+
border-color: #3b82f6;
|
| 25 |
+
background-color: rgba(59, 130, 246, 0.05);
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.pulse {
|
| 29 |
+
animation: pulse 2s infinite;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
@keyframes pulse {
|
| 33 |
+
0% {
|
| 34 |
+
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
70% {
|
| 38 |
+
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
100% {
|
| 42 |
+
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.glass-morphism {
|
| 47 |
+
background: rgba(255, 255, 255, 0.7);
|
| 48 |
+
backdrop-filter: blur(10px);
|
| 49 |
+
-webkit-backdrop-filter: blur(10px);
|
| 50 |
+
border: 1px solid rgba(255, 255, 255, 0.18);
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
.gtitlev3 {
|
| 54 |
+
padding-top: 0.25rem;
|
| 55 |
+
padding-bottom: 0.25rem;
|
| 56 |
+
padding-left: 0.5rem;
|
| 57 |
+
padding-right: 0.5rem;
|
| 58 |
+
margin-bottom: 0.75rem;
|
| 59 |
+
border-radius: 0.375rem;
|
| 60 |
+
font-size: 1.5rem;
|
| 61 |
+
line-height: 2rem;
|
| 62 |
+
font-weight: 700;
|
| 63 |
+
text-align: center;
|
| 64 |
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 #0000000f;
|
| 65 |
+
background-color: #fff8dc;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.chend {
|
| 69 |
+
padding-left: 1.25rem;
|
| 70 |
+
padding-right: 1.25rem;
|
| 71 |
+
margin-top: 0.75rem;
|
| 72 |
+
border-radius: 0.375rem;
|
| 73 |
+
width: fit-content;
|
| 74 |
+
height: 1.5rem;
|
| 75 |
+
font-size: 1.875rem;
|
| 76 |
+
line-height: 2.25rem;
|
| 77 |
+
font-weight: 700;
|
| 78 |
+
text-align: center;
|
| 79 |
+
box-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px 0 #0000000f;
|
| 80 |
+
color: rgb(49 71 91);
|
| 81 |
+
background-color: #fff8dc;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.gpara {
|
| 85 |
+
font-weight: 600;
|
| 86 |
+
color: rgb(49 71 91)
|
| 87 |
+
/* #31475b */
|
| 88 |
+
;
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.gparabhajan3 {
|
| 92 |
+
font-weight: 600;
|
| 93 |
+
color: rgb(49 71 91)
|
| 94 |
+
/* #31475b */
|
| 95 |
+
;
|
| 96 |
+
}
|
| 97 |
+
</style>
|
| 98 |
+
</head>
|
| 99 |
+
|
| 100 |
+
<body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen">
|
| 101 |
+
<div class="container mx-auto px-4 py-12 max-w-4xl md:max-w-[90%]">
|
| 102 |
+
<header class="text-center mb-12">
|
| 103 |
+
<h1 class="text-4xl font-bold text-gray-800 mb-2">
|
| 104 |
+
<span class="bg-clip-text text-transparent bg-gradient-to-r from-blue-600 to-indigo-600">Audio to
|
| 105 |
+
Lyrics</span>
|
| 106 |
+
Converter
|
| 107 |
+
</h1>
|
| 108 |
+
<p class="text-gray-600 max-w-xl mx-auto">Transform your audio files into beautifully formatted lyrics with
|
| 109 |
+
AI-powered transcription</p>
|
| 110 |
+
</header>
|
| 111 |
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
| 112 |
+
<div class="glass-morphism col-span-1 md:col-span-1 rounded-xl shadow-xl p-8 mb-8 h-fit">
|
| 113 |
+
<!-- Add tab buttons -->
|
| 114 |
+
<div class="flex mb-6 bg-gray-100 rounded-lg p-1">
|
| 115 |
+
<button id="fileTabBtn" class="flex-1 py-2 px-4 rounded-md bg-white shadow-sm text-blue-600 font-medium">
|
| 116 |
+
<i data-feather="file" class="inline mr-2"></i>File
|
| 117 |
+
</button>
|
| 118 |
+
<button id="youtubeTabBtn" class="flex-1 py-2 px-4 rounded-md text-gray-600 font-medium">
|
| 119 |
+
<i data-feather="youtube" class="inline mr-2"></i>YouTube
|
| 120 |
+
</button>
|
| 121 |
+
</div>
|
| 122 |
+
|
| 123 |
+
<!-- File upload section -->
|
| 124 |
+
<div id="fileUploadSection">
|
| 125 |
+
<div class="dropzone p-8 mb-6 flex flex-col items-center justify-center" id="dropzone">
|
| 126 |
+
<i data-feather="music" class="text-blue-500 mb-4" style="width: 48px; height: 48px;"></i>
|
| 127 |
+
<label class="block text-gray-700 font-medium mb-2">Upload Audio File</label>
|
| 128 |
+
<p class="text-sm text-gray-500 mb-4">Drag & drop your file here or click to browse</p>
|
| 129 |
+
<input type="file" id="audioFile" accept="audio/*" class="hidden">
|
| 130 |
+
<button id="browseBtn"
|
| 131 |
+
class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-full transition duration-300 flex items-center">
|
| 132 |
+
<i data-feather="upload-cloud" class="mr-2"></i>
|
| 133 |
+
Browse Files
|
| 134 |
+
</button>
|
| 135 |
+
</div>
|
| 136 |
+
<div class="mb-6">
|
| 137 |
+
<div class="bg-gray-50 rounded-lg p-4 flex items-center" id="fileInfoContainer"
|
| 138 |
+
style="display: none;">
|
| 139 |
+
<div
|
| 140 |
+
class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mr-3 flex-shrink-0">
|
| 141 |
+
<i data-feather="file" class="text-blue-600"></i>
|
| 142 |
+
</div>
|
| 143 |
+
<div class="flex-grow">
|
| 144 |
+
<p class="font-medium text-gray-800" id="fileName">No file selected</p>
|
| 145 |
+
<p class="text-sm text-gray-500" id="fileSize"></p>
|
| 146 |
+
</div>
|
| 147 |
+
<button id="removeFileBtn"
|
| 148 |
+
class="text-gray-400 hover:text-red-500 transition-colors duration-300">
|
| 149 |
+
<i data-feather="x-circle"></i>
|
| 150 |
+
</button>
|
| 151 |
+
</div>
|
| 152 |
+
|
| 153 |
+
<audio id="audioPlayer" controls class="w-full mt-4 rounded-lg" hidden>
|
| 154 |
+
Your browser does not support the audio element.
|
| 155 |
+
</audio>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
|
| 159 |
+
<!-- YouTube link section -->
|
| 160 |
+
<div id="youtubeLinkSection" class="hidden">
|
| 161 |
+
<div class="mb-6">
|
| 162 |
+
<label class="block text-gray-700 font-medium mb-2">YouTube URL</label>
|
| 163 |
+
<input type="text" id="youtubeUrl"
|
| 164 |
+
class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
| 165 |
+
placeholder="https://www.youtube.com/watch?v=...">
|
| 166 |
+
<p id="youtubeError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid YouTube URL</p>
|
| 167 |
+
</div>
|
| 168 |
+
</div>
|
| 169 |
+
|
| 170 |
+
<button id="convertBtn"
|
| 171 |
+
class="w-full bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white font-semibold py-3 px-4 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed transition duration-300 flex items-center justify-center pulse"
|
| 172 |
+
disabled>
|
| 173 |
+
<i data-feather="zap" class="mr-2"></i>
|
| 174 |
+
Convert to Lyrics
|
| 175 |
+
</button>
|
| 176 |
+
|
| 177 |
+
<div id="loadingIndicator" class="hidden mt-6 text-center">
|
| 178 |
+
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
| 179 |
+
<p class="mt-2 text-blue-600 font-medium">Processing your audio file...</p>
|
| 180 |
+
<p class="text-sm text-gray-500">This might take a moment depending on file size</p>
|
| 181 |
+
</div>
|
| 182 |
+
</div>
|
| 183 |
+
|
| 184 |
+
<div class="glass-morphism col-span-1 md:col-span-3 rounded-xl shadow-xl p-8">
|
| 185 |
+
<div class="flex items-center justify-between mb-4">
|
| 186 |
+
<h3 class="font-bold text-gray-800 flex items-center">
|
| 187 |
+
<i data-feather="file-text" class="mr-2 text-blue-600"></i>
|
| 188 |
+
Lyrics Output
|
| 189 |
+
</h3>
|
| 190 |
+
<button id="downloadBtn"
|
| 191 |
+
class="hidden bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 rounded-full transition duration-300 flex items-center text-sm">
|
| 192 |
+
<i data-feather="download-cloud" class="mr-1"></i>
|
| 193 |
+
Download
|
| 194 |
+
</button>
|
| 195 |
+
</div>
|
| 196 |
+
|
| 197 |
+
<iframe id="lyricsOutputIframe"
|
| 198 |
+
class="p-6 bg-white rounded-lg min-h-[300px] w-full whitespace-pre-line border border-gray-100 text-gray-700 overflow-auto max-h-[500px] md:max-h-[1000px] h-[500px] md:h-[500px]"
|
| 199 |
+
srcdoc="No lyrics available yet. Upload an audio file and click convert."
|
| 200 |
+
></iframe>
|
| 201 |
+
</div>
|
| 202 |
+
</div>
|
| 203 |
+
|
| 204 |
+
<footer class="mt-12 text-center text-gray-500 text-sm">
|
| 205 |
+
<p>Created by Jagrat Patel</p>
|
| 206 |
+
<p class="mt-1">© 2025 Audio to Lyrics Converter</p>
|
| 207 |
+
</footer>
|
| 208 |
+
</div>
|
| 209 |
+
|
| 210 |
+
<script>
|
| 211 |
+
// Initialize Feather icons
|
| 212 |
+
feather.replace();
|
| 213 |
+
|
| 214 |
+
const audioFile = document.getElementById('audioFile');
|
| 215 |
+
const browseBtn = document.getElementById('browseBtn');
|
| 216 |
+
const audioPlayer = document.getElementById('audioPlayer');
|
| 217 |
+
const convertBtn = document.getElementById('convertBtn');
|
| 218 |
+
const lyricsOutput = document.getElementById('lyricsOutputIframe');
|
| 219 |
+
const loadingIndicator = document.getElementById('loadingIndicator');
|
| 220 |
+
const downloadBtn = document.getElementById('downloadBtn');
|
| 221 |
+
const dropzone = document.getElementById('dropzone');
|
| 222 |
+
const fileInfoContainer = document.getElementById('fileInfoContainer');
|
| 223 |
+
const fileName = document.getElementById('fileName');
|
| 224 |
+
const fileSize = document.getElementById('fileSize');
|
| 225 |
+
const removeFileBtn = document.getElementById('removeFileBtn');
|
| 226 |
+
|
| 227 |
+
// Handle browse button click
|
| 228 |
+
browseBtn.addEventListener('click', () => {
|
| 229 |
+
audioFile.click();
|
| 230 |
+
});
|
| 231 |
+
|
| 232 |
+
// Format file size
|
| 233 |
+
function formatFileSize(bytes) {
|
| 234 |
+
if (bytes === 0) return '0 Bytes';
|
| 235 |
+
const k = 1024;
|
| 236 |
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
| 237 |
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
| 238 |
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
// Display file info
|
| 242 |
+
function displayFileInfo(file) {
|
| 243 |
+
fileName.textContent = file.name;
|
| 244 |
+
fileSize.textContent = formatFileSize(file.size);
|
| 245 |
+
fileInfoContainer.style.display = 'flex';
|
| 246 |
+
|
| 247 |
+
// Enable convert button and show audio player
|
| 248 |
+
convertBtn.disabled = false;
|
| 249 |
+
convertBtn.classList.add('pulse');
|
| 250 |
+
|
| 251 |
+
const audioUrl = URL.createObjectURL(file);
|
| 252 |
+
audioPlayer.src = audioUrl;
|
| 253 |
+
audioPlayer.hidden = false;
|
| 254 |
+
|
| 255 |
+
// Reset output area
|
| 256 |
+
lyricsOutput.srcdoc = "No lyrics available yet. Upload an audio file and click convert.";
|
| 257 |
+
downloadBtn.classList.add('hidden');
|
| 258 |
+
}
|
| 259 |
+
|
| 260 |
+
// Handle file selection
|
| 261 |
+
audioFile.addEventListener('change', (e) => {
|
| 262 |
+
const file = e.target.files[0];
|
| 263 |
+
if (file) {
|
| 264 |
+
displayFileInfo(file);
|
| 265 |
+
}
|
| 266 |
+
});
|
| 267 |
+
|
| 268 |
+
// Handle remove file button
|
| 269 |
+
removeFileBtn.addEventListener('click', () => {
|
| 270 |
+
audioFile.value = '';
|
| 271 |
+
fileInfoContainer.style.display = 'none';
|
| 272 |
+
audioPlayer.hidden = true;
|
| 273 |
+
audioPlayer.src = '';
|
| 274 |
+
convertBtn.disabled = true;
|
| 275 |
+
convertBtn.classList.remove('pulse');
|
| 276 |
+
lyricsOutput.srcdoc = "No lyrics available yet. Upload an audio file and click convert.";
|
| 277 |
+
downloadBtn.classList.add('hidden');
|
| 278 |
+
});
|
| 279 |
+
|
| 280 |
+
// Handle drag and drop
|
| 281 |
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
| 282 |
+
dropzone.addEventListener(eventName, (e) => {
|
| 283 |
+
e.preventDefault();
|
| 284 |
+
e.stopPropagation();
|
| 285 |
+
}, false);
|
| 286 |
+
});
|
| 287 |
+
|
| 288 |
+
['dragenter', 'dragover'].forEach(eventName => {
|
| 289 |
+
dropzone.addEventListener(eventName, () => {
|
| 290 |
+
dropzone.classList.add('dragover');
|
| 291 |
+
}, false);
|
| 292 |
+
});
|
| 293 |
+
|
| 294 |
+
['dragleave', 'drop'].forEach(eventName => {
|
| 295 |
+
dropzone.addEventListener(eventName, () => {
|
| 296 |
+
dropzone.classList.remove('dragover');
|
| 297 |
+
}, false);
|
| 298 |
+
});
|
| 299 |
+
|
| 300 |
+
dropzone.addEventListener('drop', (e) => {
|
| 301 |
+
const file = e.dataTransfer.files[0];
|
| 302 |
+
if (file && file.type.startsWith('audio/')) {
|
| 303 |
+
audioFile.files = e.dataTransfer.files;
|
| 304 |
+
displayFileInfo(file);
|
| 305 |
+
}
|
| 306 |
+
}, false);
|
| 307 |
+
|
| 308 |
+
// Handle convert button click
|
| 309 |
+
convertBtn.addEventListener('click', async () => {
|
| 310 |
+
const isYoutubeMode = !youtubeLinkSection.classList.contains('hidden');
|
| 311 |
+
|
| 312 |
+
if (isYoutubeMode) {
|
| 313 |
+
const youtubeLink = youtubeUrl.value;
|
| 314 |
+
if (!isValidYouTubeUrl(youtubeLink)) {
|
| 315 |
+
alert('Please enter a valid YouTube URL');
|
| 316 |
+
return;
|
| 317 |
+
}
|
| 318 |
+
} else {
|
| 319 |
+
const file = audioFile.files[0];
|
| 320 |
+
if (!file) {
|
| 321 |
+
alert('Please select an audio file first');
|
| 322 |
+
return;
|
| 323 |
+
}
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
// Show loading indicator
|
| 327 |
+
loadingIndicator.classList.remove('hidden');
|
| 328 |
+
convertBtn.disabled = true;
|
| 329 |
+
convertBtn.classList.remove('pulse');
|
| 330 |
+
|
| 331 |
+
try {
|
| 332 |
+
let response;
|
| 333 |
+
if (isYoutubeMode) {
|
| 334 |
+
response = await fetch('/convert-youtube', {
|
| 335 |
+
method: 'POST',
|
| 336 |
+
headers: {
|
| 337 |
+
'Content-Type': 'application/json',
|
| 338 |
+
},
|
| 339 |
+
body: JSON.stringify({ url: youtubeUrl.value })
|
| 340 |
+
});
|
| 341 |
+
} else {
|
| 342 |
+
const formData = new FormData();
|
| 343 |
+
formData.append('file', audioFile.files[0]);
|
| 344 |
+
response = await fetch('/convert', {
|
| 345 |
+
method: 'POST',
|
| 346 |
+
body: formData
|
| 347 |
+
});
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
const result = await response.json();
|
| 351 |
+
|
| 352 |
+
if (response.ok) {
|
| 353 |
+
lyricsOutput.srcdoc = result.lyrics;
|
| 354 |
+
downloadBtn.classList.remove('hidden');
|
| 355 |
+
} else {
|
| 356 |
+
lyricsOutput.srcdoc = `Error: ${result.error}`;
|
| 357 |
+
}
|
| 358 |
+
} catch (error) {
|
| 359 |
+
lyricsOutput.srcdoc = `Error: ${error.message}`;
|
| 360 |
+
} finally {
|
| 361 |
+
loadingIndicator.classList.add('hidden');
|
| 362 |
+
convertBtn.disabled = false;
|
| 363 |
+
}
|
| 364 |
+
});
|
| 365 |
+
|
| 366 |
+
// Handle download button click
|
| 367 |
+
downloadBtn.addEventListener('click', () => {
|
| 368 |
+
const lyrics = lyricsOutput.srcdoc;
|
| 369 |
+
const blob = new Blob([lyrics], { type: 'text/html' });
|
| 370 |
+
const url = URL.createObjectURL(blob);
|
| 371 |
+
|
| 372 |
+
const a = document.createElement('a');
|
| 373 |
+
a.href = url;
|
| 374 |
+
a.download = 'lyrics.html';
|
| 375 |
+
document.body.appendChild(a);
|
| 376 |
+
a.click();
|
| 377 |
+
document.body.removeChild(a);
|
| 378 |
+
URL.revokeObjectURL(url);
|
| 379 |
+
});
|
| 380 |
+
|
| 381 |
+
// Add new JavaScript for YouTube functionality
|
| 382 |
+
const fileTabBtn = document.getElementById('fileTabBtn');
|
| 383 |
+
const youtubeTabBtn = document.getElementById('youtubeTabBtn');
|
| 384 |
+
const fileUploadSection = document.getElementById('fileUploadSection');
|
| 385 |
+
const youtubeLinkSection = document.getElementById('youtubeLinkSection');
|
| 386 |
+
const youtubeUrl = document.getElementById('youtubeUrl');
|
| 387 |
+
const youtubeError = document.getElementById('youtubeError');
|
| 388 |
+
|
| 389 |
+
fileTabBtn.addEventListener('click', () => {
|
| 390 |
+
fileTabBtn.classList.add('bg-white', 'shadow-sm', 'text-blue-600');
|
| 391 |
+
youtubeTabBtn.classList.remove('bg-white', 'shadow-sm', 'text-blue-600');
|
| 392 |
+
fileUploadSection.classList.remove('hidden');
|
| 393 |
+
youtubeLinkSection.classList.add('hidden');
|
| 394 |
+
convertBtn.disabled = !audioFile.files.length;
|
| 395 |
+
});
|
| 396 |
+
|
| 397 |
+
youtubeTabBtn.addEventListener('click', () => {
|
| 398 |
+
youtubeTabBtn.classList.add('bg-white', 'shadow-sm', 'text-blue-600');
|
| 399 |
+
fileTabBtn.classList.remove('bg-white', 'shadow-sm', 'text-blue-600');
|
| 400 |
+
youtubeLinkSection.classList.remove('hidden');
|
| 401 |
+
fileUploadSection.classList.add('hidden');
|
| 402 |
+
convertBtn.disabled = !isValidYouTubeUrl(youtubeUrl.value);
|
| 403 |
+
});
|
| 404 |
+
|
| 405 |
+
function isValidYouTubeUrl(url) {
|
| 406 |
+
const youtubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+/;
|
| 407 |
+
return youtubeRegex.test(url);
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
youtubeUrl.addEventListener('input', () => {
|
| 411 |
+
const isValid = isValidYouTubeUrl(youtubeUrl.value);
|
| 412 |
+
youtubeError.classList.toggle('hidden', isValid);
|
| 413 |
+
convertBtn.disabled = !isValid;
|
| 414 |
+
if (isValid) convertBtn.classList.add('pulse');
|
| 415 |
+
else convertBtn.classList.remove('pulse');
|
| 416 |
+
});
|
| 417 |
+
</script>
|
| 418 |
+
</body>
|
| 419 |
+
|
| 420 |
+
</html>
|
templates/index.html
CHANGED
|
@@ -1,420 +1,488 @@
|
|
| 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 |
-
<title>Audio to Lyrics Converter</title>
|
| 8 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
| 10 |
-
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
-
<style>
|
| 12 |
-
* {
|
| 13 |
-
font-family: 'Inter', sans-serif;
|
| 14 |
-
}
|
| 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 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
</
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
<
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
</div>
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
}
|
| 259 |
-
|
| 260 |
-
//
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
}
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
}
|
| 307 |
-
|
| 308 |
-
// Handle
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
</html>
|
|
|
|
| 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 |
+
<title>Audio to Lyrics Converter</title>
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
| 10 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
+
<style>
|
| 12 |
+
* {
|
| 13 |
+
font-family: 'Inter', sans-serif;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
.glass-morphism {
|
| 17 |
+
background: rgba(255, 255, 255, 0.7);
|
| 18 |
+
backdrop-filter: blur(10px);
|
| 19 |
+
-webkit-backdrop-filter: blur(10px);
|
| 20 |
+
border: 1px solid rgba(255, 255, 255, 0.18);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
.gtitlev3 {
|
| 24 |
+
padding-top: 0.25rem;
|
| 25 |
+
padding-bottom: 0.25rem;
|
| 26 |
+
padding-left: 0.5rem;
|
| 27 |
+
padding-right: 0.5rem;
|
| 28 |
+
margin-bottom: 0.75rem;
|
| 29 |
+
border-radius: 0.375rem;
|
| 30 |
+
font-size: 1.5rem;
|
| 31 |
+
line-height: 2rem;
|
| 32 |
+
font-weight: 700;
|
| 33 |
+
text-align: center;
|
| 34 |
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 #0000000f;
|
| 35 |
+
background-color: #fff8dc;
|
| 36 |
+
color: rgb(49 71 91)
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
.chend {
|
| 40 |
+
padding-left: 1.25rem;
|
| 41 |
+
padding-right: 1.25rem;
|
| 42 |
+
margin-top: 0.75rem;
|
| 43 |
+
border-radius: 0.375rem;
|
| 44 |
+
width: fit-content;
|
| 45 |
+
height: 1.5rem;
|
| 46 |
+
font-size: 1.875rem;
|
| 47 |
+
line-height: 2.25rem;
|
| 48 |
+
font-weight: 700;
|
| 49 |
+
text-align: center;
|
| 50 |
+
box-shadow: 0 1px 3px 0 #0000001a, 0 1px 2px 0 #0000000f;
|
| 51 |
+
color: rgb(49 71 91);
|
| 52 |
+
background-color: #fff8dc;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.gpara {
|
| 56 |
+
font-weight: 600;
|
| 57 |
+
color: rgb(49 71 91)
|
| 58 |
+
/* #31475b */
|
| 59 |
+
;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
.gparabhajan3 {
|
| 63 |
+
font-weight: 600;
|
| 64 |
+
color: rgb(49 71 91)
|
| 65 |
+
/* #31475b */
|
| 66 |
+
;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.dropzone {
|
| 70 |
+
border: 2px dashed #374151;
|
| 71 |
+
border-radius: 0.5rem;
|
| 72 |
+
transition: all 0.3s ease;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
.dropzone:hover,
|
| 76 |
+
.dropzone.dragover {
|
| 77 |
+
border-color: #f63b3b;
|
| 78 |
+
background-color: rgba(246, 59, 59, 0.1);
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.pulse {
|
| 82 |
+
animation: pulse 2s infinite;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
@keyframes pulse {
|
| 86 |
+
0% {
|
| 87 |
+
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.4);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
70% {
|
| 91 |
+
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
100% {
|
| 95 |
+
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
/* Custom scrollbar */
|
| 100 |
+
::-webkit-scrollbar {
|
| 101 |
+
width: 8px;
|
| 102 |
+
}
|
| 103 |
+
::-webkit-scrollbar-track {
|
| 104 |
+
background: #1f2937;
|
| 105 |
+
}
|
| 106 |
+
::-webkit-scrollbar-thumb {
|
| 107 |
+
background: #4b5563;
|
| 108 |
+
border-radius: 4px;
|
| 109 |
+
}
|
| 110 |
+
::-webkit-scrollbar-thumb:hover {
|
| 111 |
+
background: #6b7280;
|
| 112 |
+
}
|
| 113 |
+
</style>
|
| 114 |
+
</head>
|
| 115 |
+
|
| 116 |
+
<body class="bg-stone-900 text-stone-300">
|
| 117 |
+
<div class="flex flex-col h-screen">
|
| 118 |
+
<header class="flex items-center justify-between p-4 h-16 border-b border-stone-800 flex-shrink-0">
|
| 119 |
+
<div class="flex items-center space-x-4">
|
| 120 |
+
<svg class="w-8 h-8 text-stone-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 6l12-3"></path></svg>
|
| 121 |
+
<h1 class="text-xl font-bold text-stone-100">Audio to Lyrics</h1>
|
| 122 |
+
</div>
|
| 123 |
+
<div class="text-sm text-stone-500">v1.0.0</div>
|
| 124 |
+
</header>
|
| 125 |
+
|
| 126 |
+
<div class="flex flex-grow overflow-hidden">
|
| 127 |
+
<!-- Left Sidebar -->
|
| 128 |
+
<div class="w-96 bg-stone-900 p-6 overflow-y-auto flex-shrink-0 border-r border-stone-800">
|
| 129 |
+
<h2 class="text-lg font-semibold text-stone-200 mb-4">Select Source</h2>
|
| 130 |
+
|
| 131 |
+
<!-- Mode Toggle -->
|
| 132 |
+
<div class="mb-6">
|
| 133 |
+
<div class="flex items-center justify-between mb-2">
|
| 134 |
+
<span class="text-sm font-medium text-stone-300">Mode</span>
|
| 135 |
+
</div>
|
| 136 |
+
<div class="flex bg-stone-800 rounded-lg p-1">
|
| 137 |
+
<button id="musicModeBtn" class="flex-1 py-2 px-3 rounded-md bg-stone-700 shadow-sm text-stone-100 font-medium text-sm">
|
| 138 |
+
<i data-feather="music" class="inline mr-2 w-4 h-4"></i>Music
|
| 139 |
+
</button>
|
| 140 |
+
<button id="speechModeBtn" class="flex-1 py-2 px-3 rounded-md text-stone-400 font-medium text-sm">
|
| 141 |
+
<i data-feather="mic" class="inline mr-2 w-4 h-4"></i>Speech
|
| 142 |
+
</button>
|
| 143 |
+
</div>
|
| 144 |
+
</div>
|
| 145 |
+
|
| 146 |
+
<!-- Tab buttons -->
|
| 147 |
+
<div class="flex mb-6 bg-stone-800 rounded-lg p-1">
|
| 148 |
+
<button id="fileTabBtn" class="flex-1 py-2 px-4 rounded-md bg-stone-700 shadow-sm text-stone-100 font-medium">
|
| 149 |
+
<i data-feather="file" class="inline mr-2 w-4 h-4"></i>File
|
| 150 |
+
</button>
|
| 151 |
+
<button id="youtubeTabBtn" class="flex-1 py-2 px-4 rounded-md text-stone-400 font-medium">
|
| 152 |
+
<i data-feather="youtube" class="inline mr-2 w-4 h-4"></i>YouTube
|
| 153 |
+
</button>
|
| 154 |
+
</div>
|
| 155 |
+
|
| 156 |
+
<!-- File upload section -->
|
| 157 |
+
<div id="fileUploadSection">
|
| 158 |
+
<div class="dropzone p-6 mb-6 flex flex-col items-center justify-center text-center" id="dropzone">
|
| 159 |
+
<i data-feather="music" class="text-stone-500 mb-4" style="width: 40px; height: 40px;"></i>
|
| 160 |
+
<p class="text-sm text-stone-400 mb-4">Drag & drop or</p>
|
| 161 |
+
<input type="file" id="audioFile" accept="audio/*" class="hidden">
|
| 162 |
+
<button id="browseBtn"
|
| 163 |
+
class="bg-stone-600 hover:bg-stone-700 text-white font-medium py-2 px-4 rounded-md transition duration-300 flex items-center text-sm">
|
| 164 |
+
<i data-feather="folder" class="mr-2 w-4 h-4"></i>
|
| 165 |
+
Browse File
|
| 166 |
+
</button>
|
| 167 |
+
</div>
|
| 168 |
+
<div class="mb-6">
|
| 169 |
+
<div class="bg-stone-800 rounded-lg p-3 flex items-center" id="fileInfoContainer"
|
| 170 |
+
style="display: none;">
|
| 171 |
+
<div
|
| 172 |
+
class="w-10 h-10 bg-stone-700 rounded-lg flex items-center justify-center mr-3 flex-shrink-0">
|
| 173 |
+
<i data-feather="file" class="text-stone-400"></i>
|
| 174 |
+
</div>
|
| 175 |
+
<div class="flex-grow overflow-hidden">
|
| 176 |
+
<p class="font-medium text-stone-200 text-sm truncate" id="fileName">No file selected</p>
|
| 177 |
+
<p class="text-xs text-stone-400" id="fileSize"></p>
|
| 178 |
+
</div>
|
| 179 |
+
<button id="removeFileBtn"
|
| 180 |
+
class="text-stone-500 hover:text-red-500 transition-colors duration-300 ml-2 flex-shrink-0">
|
| 181 |
+
<i data-feather="x" class="w-5 h-5"></i>
|
| 182 |
+
</button>
|
| 183 |
+
</div>
|
| 184 |
+
|
| 185 |
+
<audio id="audioPlayer" controls class="w-full mt-4 rounded-lg" hidden>
|
| 186 |
+
Your browser does not support the audio element.
|
| 187 |
+
</audio>
|
| 188 |
+
</div>
|
| 189 |
+
</div>
|
| 190 |
+
|
| 191 |
+
<!-- YouTube link section -->
|
| 192 |
+
<div id="youtubeLinkSection" class="hidden">
|
| 193 |
+
<div class="mb-6">
|
| 194 |
+
<label class="block text-stone-400 font-medium mb-2 text-sm">YouTube URL</label>
|
| 195 |
+
<input type="text" id="youtubeUrl"
|
| 196 |
+
class="w-full px-4 py-2 bg-stone-800 border border-stone-700 rounded-lg focus:outline-none focus:ring-2 focus:ring-stone-500 focus:border-transparent"
|
| 197 |
+
placeholder="https://www.youtube.com/watch?v=...">
|
| 198 |
+
<p id="youtubeError" class="text-red-500 text-sm mt-1 hidden">Please enter a valid YouTube URL</p>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
|
| 202 |
+
<button id="convertBtn"
|
| 203 |
+
class="w-full bg-gradient-to-br from-stone-500 to-zinc-600 hover:from-red-600 hover:to-red-700 text-white font-semibold py-3 px-4 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed transition duration-300 flex items-center justify-center"
|
| 204 |
+
disabled>
|
| 205 |
+
<i data-feather="zap" class="mr-2"></i>
|
| 206 |
+
Convert to Lyrics
|
| 207 |
+
</button>
|
| 208 |
+
|
| 209 |
+
<div id="loadingIndicator" class="hidden mt-6 text-center">
|
| 210 |
+
<div class="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-red-500"></div>
|
| 211 |
+
<p class="mt-2 text-stone-400 font-medium">Processing...</p>
|
| 212 |
+
<p class="text-sm text-stone-500">This might take a moment.</p>
|
| 213 |
+
</div>
|
| 214 |
+
</div>
|
| 215 |
+
|
| 216 |
+
<!-- Main Content -->
|
| 217 |
+
<div class="flex-grow h-[calc(100vh-4rem)] bg-stone-800/50 p-6 md:p-8 overflow-y-auto">
|
| 218 |
+
<div class="flex items-center justify-between mb-4">
|
| 219 |
+
<h3 class="text-lg font-semibold text-stone-200 flex items-center">
|
| 220 |
+
<i data-feather="file-text" class="mr-2 text-stone-400"></i>
|
| 221 |
+
Lyrics Output
|
| 222 |
+
</h3>
|
| 223 |
+
<button id="downloadBtn"
|
| 224 |
+
class="hidden bg-green-600 hover:bg-green-700 text-white font-medium py-2 px-4 rounded-md transition duration-300 flex items-center text-sm">
|
| 225 |
+
<i data-feather="download" class="mr-1 w-4 h-4"></i>
|
| 226 |
+
Download
|
| 227 |
+
</button>
|
| 228 |
+
</div>
|
| 229 |
+
|
| 230 |
+
<iframe id="lyricsOutputIframe"
|
| 231 |
+
class="bg-stone-900 rounded-lg min-h-[300px] max-h-[calc(100vh-14rem)] h-full w-full whitespace-pre-line border border-stone-700 text-stone-300 overflow-auto"
|
| 232 |
+
srcdoc="<body style='background-color: #fffff6; color: #9ca3af; font-family: Inter, sans-serif; display: flex; justify-content: center; align-items: center; height: 100%;'>No lyrics available yet.</body>"
|
| 233 |
+
></iframe>
|
| 234 |
+
</div>
|
| 235 |
+
</div>
|
| 236 |
+
</div>
|
| 237 |
+
|
| 238 |
+
<script>
|
| 239 |
+
// Initialize Feather icons
|
| 240 |
+
feather.replace();
|
| 241 |
+
|
| 242 |
+
const audioFile = document.getElementById('audioFile');
|
| 243 |
+
const browseBtn = document.getElementById('browseBtn');
|
| 244 |
+
const audioPlayer = document.getElementById('audioPlayer');
|
| 245 |
+
const convertBtn = document.getElementById('convertBtn');
|
| 246 |
+
const lyricsOutput = document.getElementById('lyricsOutputIframe');
|
| 247 |
+
const loadingIndicator = document.getElementById('loadingIndicator');
|
| 248 |
+
const downloadBtn = document.getElementById('downloadBtn');
|
| 249 |
+
const dropzone = document.getElementById('dropzone');
|
| 250 |
+
const fileInfoContainer = document.getElementById('fileInfoContainer');
|
| 251 |
+
const fileName = document.getElementById('fileName');
|
| 252 |
+
const fileSize = document.getElementById('fileSize');
|
| 253 |
+
const removeFileBtn = document.getElementById('removeFileBtn');
|
| 254 |
+
|
| 255 |
+
// Handle browse button click
|
| 256 |
+
browseBtn.addEventListener('click', () => {
|
| 257 |
+
audioFile.click();
|
| 258 |
+
});
|
| 259 |
+
|
| 260 |
+
// Format file size
|
| 261 |
+
function formatFileSize(bytes) {
|
| 262 |
+
if (bytes === 0) return '0 Bytes';
|
| 263 |
+
const k = 1024;
|
| 264 |
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
| 265 |
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
| 266 |
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
// Display file info
|
| 270 |
+
function displayFileInfo(file) {
|
| 271 |
+
fileName.textContent = file.name;
|
| 272 |
+
fileSize.textContent = formatFileSize(file.size);
|
| 273 |
+
fileInfoContainer.style.display = 'flex';
|
| 274 |
+
|
| 275 |
+
// Enable convert button and show audio player
|
| 276 |
+
convertBtn.disabled = false;
|
| 277 |
+
convertBtn.classList.add('pulse');
|
| 278 |
+
|
| 279 |
+
const audioUrl = URL.createObjectURL(file);
|
| 280 |
+
audioPlayer.src = audioUrl;
|
| 281 |
+
audioPlayer.hidden = false;
|
| 282 |
+
|
| 283 |
+
// Reset output area
|
| 284 |
+
lyricsOutput.srcdoc = "<body style='background-color: #fffff6; color: #9ca3af; font-family: Inter, sans-serif; display: flex; justify-content: center; align-items: center; height: 100%;'>Ready to convert.</body>";
|
| 285 |
+
downloadBtn.classList.add('hidden');
|
| 286 |
+
}
|
| 287 |
+
|
| 288 |
+
// Handle file selection
|
| 289 |
+
audioFile.addEventListener('change', (e) => {
|
| 290 |
+
const file = e.target.files[0];
|
| 291 |
+
if (file) {
|
| 292 |
+
displayFileInfo(file);
|
| 293 |
+
}
|
| 294 |
+
});
|
| 295 |
+
|
| 296 |
+
// Handle remove file button
|
| 297 |
+
removeFileBtn.addEventListener('click', () => {
|
| 298 |
+
audioFile.value = '';
|
| 299 |
+
fileInfoContainer.style.display = 'none';
|
| 300 |
+
audioPlayer.hidden = true;
|
| 301 |
+
audioPlayer.src = '';
|
| 302 |
+
convertBtn.disabled = true;
|
| 303 |
+
convertBtn.classList.remove('pulse');
|
| 304 |
+
lyricsOutput.srcdoc = "<body style='background-color: #fffff6; color: #9ca3af; font-family: Inter, sans-serif; display: flex; justify-content: center; align-items: center; height: 100%;'>No lyrics available yet.</body>";
|
| 305 |
+
downloadBtn.classList.add('hidden');
|
| 306 |
+
});
|
| 307 |
+
|
| 308 |
+
// Handle drag and drop
|
| 309 |
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
| 310 |
+
dropzone.addEventListener(eventName, (e) => {
|
| 311 |
+
e.preventDefault();
|
| 312 |
+
e.stopPropagation();
|
| 313 |
+
}, false);
|
| 314 |
+
});
|
| 315 |
+
|
| 316 |
+
['dragenter', 'dragover'].forEach(eventName => {
|
| 317 |
+
dropzone.addEventListener(eventName, () => {
|
| 318 |
+
dropzone.classList.add('dragover');
|
| 319 |
+
}, false);
|
| 320 |
+
});
|
| 321 |
+
|
| 322 |
+
['dragleave', 'drop'].forEach(eventName => {
|
| 323 |
+
dropzone.addEventListener(eventName, () => {
|
| 324 |
+
dropzone.classList.remove('dragover');
|
| 325 |
+
}, false);
|
| 326 |
+
});
|
| 327 |
+
|
| 328 |
+
dropzone.addEventListener('drop', (e) => {
|
| 329 |
+
const file = e.dataTransfer.files[0];
|
| 330 |
+
if (file && file.type.startsWith('audio/')) {
|
| 331 |
+
audioFile.files = e.dataTransfer.files;
|
| 332 |
+
displayFileInfo(file);
|
| 333 |
+
}
|
| 334 |
+
}, false);
|
| 335 |
+
|
| 336 |
+
// Handle convert button click
|
| 337 |
+
convertBtn.addEventListener('click', async () => {
|
| 338 |
+
const isYoutubeMode = !youtubeLinkSection.classList.contains('hidden');
|
| 339 |
+
|
| 340 |
+
if (isYoutubeMode) {
|
| 341 |
+
const youtubeLink = youtubeUrl.value;
|
| 342 |
+
if (!isValidYouTubeUrl(youtubeLink)) {
|
| 343 |
+
alert('Please enter a valid YouTube URL');
|
| 344 |
+
return;
|
| 345 |
+
}
|
| 346 |
+
} else {
|
| 347 |
+
const file = audioFile.files[0];
|
| 348 |
+
if (!file) {
|
| 349 |
+
alert('Please select an audio file first');
|
| 350 |
+
return;
|
| 351 |
+
}
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
// Show loading indicator
|
| 355 |
+
loadingIndicator.classList.remove('hidden');
|
| 356 |
+
convertBtn.disabled = true;
|
| 357 |
+
convertBtn.classList.remove('pulse');
|
| 358 |
+
|
| 359 |
+
try {
|
| 360 |
+
let response;
|
| 361 |
+
if (isYoutubeMode) {
|
| 362 |
+
response = await fetch('/convert-youtube', {
|
| 363 |
+
method: 'POST',
|
| 364 |
+
headers: {
|
| 365 |
+
'Content-Type': 'application/json',
|
| 366 |
+
},
|
| 367 |
+
body: JSON.stringify({
|
| 368 |
+
url: youtubeUrl.value,
|
| 369 |
+
type: currentMode
|
| 370 |
+
})
|
| 371 |
+
});
|
| 372 |
+
} else {
|
| 373 |
+
const formData = new FormData();
|
| 374 |
+
formData.append('file', audioFile.files[0]);
|
| 375 |
+
formData.append('type', currentMode);
|
| 376 |
+
response = await fetch('/convert', {
|
| 377 |
+
method: 'POST',
|
| 378 |
+
body: formData
|
| 379 |
+
});
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
const result = await response.json();
|
| 383 |
+
|
| 384 |
+
if (response.ok) {
|
| 385 |
+
lyricsOutput.srcdoc = `<body style='background-color: #fffff6; color: #1e293b; font-family: Inter, sans-serif; padding: 1rem;'>${result.lyrics}</body>`;
|
| 386 |
+
downloadBtn.classList.remove('hidden');
|
| 387 |
+
} else {
|
| 388 |
+
lyricsOutput.srcdoc = `<body style='background-color: #fffff6; color: #f87171; font-family: Inter, sans-serif; padding: 1rem;'>Error: ${result.error}</body>`;
|
| 389 |
+
}
|
| 390 |
+
} catch (error) {
|
| 391 |
+
lyricsOutput.srcdoc = `<body style='background-color: #fffff6; color: #f87171; font-family: Inter, sans-serif; padding: 1rem;'>Error: ${error.message}</body>`;
|
| 392 |
+
} finally {
|
| 393 |
+
loadingIndicator.classList.add('hidden');
|
| 394 |
+
convertBtn.disabled = false;
|
| 395 |
+
}
|
| 396 |
+
});
|
| 397 |
+
|
| 398 |
+
// Handle download button click
|
| 399 |
+
downloadBtn.addEventListener('click', () => {
|
| 400 |
+
const lyrics = lyricsOutput.srcdoc;
|
| 401 |
+
const blob = new Blob([lyrics], { type: 'text/html' });
|
| 402 |
+
const url = URL.createObjectURL(blob);
|
| 403 |
+
|
| 404 |
+
const a = document.createElement('a');
|
| 405 |
+
a.href = url;
|
| 406 |
+
a.download = 'lyrics.html';
|
| 407 |
+
document.body.appendChild(a);
|
| 408 |
+
a.click();
|
| 409 |
+
document.body.removeChild(a);
|
| 410 |
+
URL.revokeObjectURL(url);
|
| 411 |
+
});
|
| 412 |
+
|
| 413 |
+
// Add new JavaScript for YouTube functionality
|
| 414 |
+
const fileTabBtn = document.getElementById('fileTabBtn');
|
| 415 |
+
const youtubeTabBtn = document.getElementById('youtubeTabBtn');
|
| 416 |
+
const fileUploadSection = document.getElementById('fileUploadSection');
|
| 417 |
+
const youtubeLinkSection = document.getElementById('youtubeLinkSection');
|
| 418 |
+
const youtubeUrl = document.getElementById('youtubeUrl');
|
| 419 |
+
const youtubeError = document.getElementById('youtubeError');
|
| 420 |
+
|
| 421 |
+
// Mode toggle elements
|
| 422 |
+
const musicModeBtn = document.getElementById('musicModeBtn');
|
| 423 |
+
const speechModeBtn = document.getElementById('speechModeBtn');
|
| 424 |
+
let currentMode = 'music'; // Default mode
|
| 425 |
+
|
| 426 |
+
// Mode toggle handlers
|
| 427 |
+
musicModeBtn.addEventListener('click', () => {
|
| 428 |
+
currentMode = 'music';
|
| 429 |
+
musicModeBtn.classList.add('bg-stone-700', 'text-stone-100');
|
| 430 |
+
musicModeBtn.classList.remove('text-stone-400');
|
| 431 |
+
speechModeBtn.classList.remove('bg-stone-700', 'text-stone-100');
|
| 432 |
+
speechModeBtn.classList.add('text-stone-400');
|
| 433 |
+
});
|
| 434 |
+
|
| 435 |
+
speechModeBtn.addEventListener('click', () => {
|
| 436 |
+
currentMode = 'speech';
|
| 437 |
+
speechModeBtn.classList.add('bg-stone-700', 'text-stone-100');
|
| 438 |
+
speechModeBtn.classList.remove('text-stone-400');
|
| 439 |
+
musicModeBtn.classList.remove('bg-stone-700', 'text-stone-100');
|
| 440 |
+
musicModeBtn.classList.add('text-stone-400');
|
| 441 |
+
});
|
| 442 |
+
|
| 443 |
+
fileTabBtn.addEventListener('click', () => {
|
| 444 |
+
fileTabBtn.classList.add('bg-stone-700', 'text-stone-100');
|
| 445 |
+
fileTabBtn.classList.remove('text-stone-400');
|
| 446 |
+
youtubeTabBtn.classList.remove('bg-stone-700', 'text-stone-100');
|
| 447 |
+
youtubeTabBtn.classList.add('text-stone-400');
|
| 448 |
+
|
| 449 |
+
fileUploadSection.classList.remove('hidden');
|
| 450 |
+
youtubeLinkSection.classList.add('hidden');
|
| 451 |
+
|
| 452 |
+
const hasFile = audioFile.files.length > 0;
|
| 453 |
+
convertBtn.disabled = !hasFile;
|
| 454 |
+
if (hasFile) convertBtn.classList.add('pulse');
|
| 455 |
+
else convertBtn.classList.remove('pulse');
|
| 456 |
+
});
|
| 457 |
+
|
| 458 |
+
youtubeTabBtn.addEventListener('click', () => {
|
| 459 |
+
youtubeTabBtn.classList.add('bg-stone-700', 'text-stone-100');
|
| 460 |
+
youtubeTabBtn.classList.remove('text-stone-400');
|
| 461 |
+
fileTabBtn.classList.remove('bg-stone-700', 'text-stone-100');
|
| 462 |
+
fileTabBtn.classList.add('text-stone-400');
|
| 463 |
+
|
| 464 |
+
youtubeLinkSection.classList.remove('hidden');
|
| 465 |
+
fileUploadSection.classList.add('hidden');
|
| 466 |
+
|
| 467 |
+
const isValid = isValidYouTubeUrl(youtubeUrl.value);
|
| 468 |
+
convertBtn.disabled = !isValid;
|
| 469 |
+
if (isValid) convertBtn.classList.add('pulse');
|
| 470 |
+
else convertBtn.classList.remove('pulse');
|
| 471 |
+
});
|
| 472 |
+
|
| 473 |
+
function isValidYouTubeUrl(url) {
|
| 474 |
+
const youtubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+/;
|
| 475 |
+
return youtubeRegex.test(url);
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
youtubeUrl.addEventListener('input', () => {
|
| 479 |
+
const isValid = isValidYouTubeUrl(youtubeUrl.value);
|
| 480 |
+
youtubeError.classList.toggle('hidden', isValid);
|
| 481 |
+
convertBtn.disabled = !isValid;
|
| 482 |
+
if (isValid) convertBtn.classList.add('pulse');
|
| 483 |
+
else convertBtn.classList.remove('pulse');
|
| 484 |
+
});
|
| 485 |
+
</script>
|
| 486 |
+
</body>
|
| 487 |
+
|
| 488 |
</html>
|