Spaces:
Running
Running
File size: 14,174 Bytes
11e0218 1da8ab5 4f6f407 11e0218 309470c 11e0218 4f6f407 e847355 4f6f407 11e0218 4f6f407 309470c e847355 4f6f407 309470c 4f6f407 309470c 4f6f407 e847355 4f6f407 309470c 4f6f407 309470c 4f6f407 e847355 4f6f407 11e0218 309470c 11e0218 309470c 49d15bb 309470c 1da8ab5 309470c 4f6f407 1da8ab5 309470c 11e0218 e847355 df4422d 1da8ab5 df4422d 1da8ab5 df4422d 1da8ab5 df4422d 1da8ab5 df4422d 1da8ab5 df4422d 1da8ab5 df4422d 1da8ab5 df4422d 1da8ab5 df4422d 1da8ab5 df4422d 1da8ab5 e847355 1da8ab5 4f6f407 1da8ab5 e847355 1da8ab5 df4422d e847355 11e0218 1da8ab5 11e0218 1da8ab5 11e0218 df4422d 11e0218 df4422d 11e0218 1da8ab5 11e0218 1da8ab5 11e0218 df4422d 11e0218 4f6f407 11e0218 1da8ab5 e847355 4f6f407 1da8ab5 309470c 1da8ab5 309470c df4422d e847355 11e0218 1da8ab5 e847355 1da8ab5 309470c 1da8ab5 309470c 1da8ab5 e847355 11e0218 1da8ab5 | 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 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 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 | import gradio as gr
import edge_tts
import asyncio
import tempfile
import os
import time
import base64
from datetime import datetime
from pydub import AudioSegment
VOICE = "en-US-AndrewMultilingualNeural"
CHUNK_SIZE = 5000
HF_USER = "Cracked-User"
HF_REPO = "tts-audiobooks"
# ββ Exact same logic as Colab βββββββββββββββββββββββββββββββββββββββββββββββββ
def run_tts(text, custom_name=''):
chunks = [text[i:i+CHUNK_SIZE] for i in range(0, len(text), CHUNK_SIZE)]
total = len(chunks)
parts = []
chunk_times = []
batch_start = time.time()
tmp_dir = tempfile.mkdtemp()
yield None, 'β³ Total chunks: ' + str(total), make_player(None), ''
for i, chunk in enumerate(chunks):
path = tmp_dir + '/chunk_' + str(i).zfill(5) + '.mp3'
if chunk_times:
avg = sum(chunk_times) / len(chunk_times)
eta = int(avg * (total - i))
elapsed = int(time.time() - batch_start)
status = (
'Chunk ' + str(i+1) + '/' + str(total) +
' ETA: ' + str(eta//60) + 'm ' + str(eta%60) + 's' +
' Elapsed: ' + str(elapsed//60) + 'm ' + str(elapsed%60) + 's'
)
else:
status = 'Chunk ' + str(i+1) + '/' + str(total) + ' ETA: calculating...'
yield None, status, make_player(None), ''
try:
async def save(chunk=chunk, path=path):
comm = edge_tts.Communicate(chunk, VOICE)
await comm.save(path)
asyncio.run(save())
parts.append(path)
except Exception as e:
try:
time.sleep(5)
asyncio.run(save())
parts.append(path)
except Exception as e2:
yield None, 'β Failed chunk ' + str(i+1) + ': ' + str(e2), make_player(None), ''
return
chunk_times.append(time.time() - batch_start - sum(chunk_times))
# Log progress to HF so you can check after refresh
avg2 = sum(chunk_times) / len(chunk_times)
eta2 = int(avg2 * (total - i - 1))
elapsed2 = int(time.time() - batch_start)
pct2 = int(((i+1) / total) * 100)
log_msg = (
'π Chunk ' + str(i+1) + ' / ' + str(total) + ' (' + str(pct2) + '%)\n' +
'β±οΈ Elapsed: ' + str(elapsed2//60) + 'm ' + str(elapsed2%60) + 's\n' +
'β³ ETA: ' + str(eta2//60) + 'm ' + str(eta2%60) + 's\n' +
'π Updated: ' + datetime.now().strftime('%H:%M:%S')
)
import threading
threading.Thread(target=log_progress, args=(log_msg, os.environ.get('HF_TOKEN', '')), daemon=True).start()
yield None, 'Merging all chunks...', make_player(None), ''
combined = AudioSegment.empty()
for p in sorted(parts):
if os.path.exists(p):
combined += AudioSegment.from_file(p) + AudioSegment.silent(200)
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
filename = (custom_name.strip().replace(' ', '_') + '.mp3') if custom_name and custom_name.strip() else ('andrew_' + timestamp + '.mp3')
out_path = tempfile.mktemp(suffix='.mp3')
combined.export(out_path, format='mp3', bitrate='192k')
# Cleanup chunks
for p in parts:
try: os.remove(p)
except: pass
total_time = int(time.time() - batch_start)
done_msg = 'DONE! Time: ' + str(total_time//60) + 'm ' + str(total_time%60) + 's'
log_progress('β
DONE! Uploading audio file...\nπ ' + datetime.now().strftime('%H:%M:%S'), os.environ.get('HF_TOKEN', ''))
yield None, done_msg + '\nUploading to HF...', make_player(None), ''
hf_msg = upload_to_hf(out_path, filename)
yield out_path, done_msg, make_player(out_path), hf_msg
# ββ Upload to HF Dataset ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def upload_to_hf(file_path, filename):
try:
from huggingface_hub import HfApi
token = os.environ.get('HF_TOKEN', '')
if not token:
return 'β οΈ No HF_TOKEN secret found.'
api = HfApi()
# Create repo if it doesn't exist
try:
api.create_repo(
repo_id=HF_USER + '/' + HF_REPO,
repo_type='dataset',
private=True,
token=token,
exist_ok=True
)
except:
pass
api.upload_file(
path_or_fileobj=file_path,
path_in_repo='audiobooks/' + filename,
repo_id=HF_USER + '/' + HF_REPO,
repo_type='dataset',
token=token
)
url = 'https://huggingface.co/datasets/' + HF_USER + '/' + HF_REPO
return 'β
Saved!\nπ ' + url
except Exception as e:
return 'β Upload error: ' + str(e)
# ββ Log progress to HF Dataset βββββββββββββββββββββββββββββββββββββββββββββββ
def log_progress(msg, token):
try:
from huggingface_hub import HfApi
if not token:
return
api = HfApi()
log_path = '/tmp/progress.txt'
with open(log_path, 'w') as f:
f.write(msg)
api.upload_file(
path_or_fileobj=log_path,
path_in_repo='progress.txt',
repo_id=HF_USER + '/' + HF_REPO,
repo_type='dataset',
token=token
)
except:
pass
# ββ Custom HTML player ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def make_player(file_path):
if not file_path or not os.path.exists(file_path):
return '<div style="color:#3a3a5a;padding:12px;text-align:center;border:1px dashed #2a2a3a;border-radius:10px;">π§ Player appears here after generation</div>'
with open(file_path, 'rb') as f:
b64 = base64.b64encode(f.read()).decode()
return '''
<div style="background:#0e0e1c;border:1px solid #2a2a3a;border-radius:14px;padding:20px;margin-top:10px;">
<div style="color:#ffd700;font-family:sans-serif;font-weight:700;font-size:0.78rem;
letter-spacing:0.06em;text-transform:uppercase;margin-bottom:12px;">
π§ Preview Player
</div>
<audio id="tts-player" controls style="width:100%;margin-bottom:14px;accent-color:#ffd700;">
<source src="data:audio/mpeg;base64,''' + b64 + '''" type="audio/mpeg">
</audio>
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
<span style="color:#9ca3af;font-size:0.82rem;">Speed:</span>
<button onclick="setSpd(this,0.75)" style="background:#1a1a2e;border:1px solid #3a3a5c;border-radius:8px;color:#9ca3af;padding:5px 12px;cursor:pointer;font-size:0.82rem;">0.75x</button>
<button onclick="setSpd(this,1.0)" style="background:#b8860b;border:none;border-radius:8px;color:white;padding:5px 12px;cursor:pointer;font-size:0.82rem;">1x</button>
<button onclick="setSpd(this,1.25)" style="background:#1a1a2e;border:1px solid #3a3a5c;border-radius:8px;color:#9ca3af;padding:5px 12px;cursor:pointer;font-size:0.82rem;">1.25x</button>
<button onclick="setSpd(this,1.5)" style="background:#1a1a2e;border:1px solid #3a3a5c;border-radius:8px;color:#9ca3af;padding:5px 12px;cursor:pointer;font-size:0.82rem;">1.5x</button>
<button onclick="setSpd(this,1.75)" style="background:#1a1a2e;border:1px solid #3a3a5c;border-radius:8px;color:#9ca3af;padding:5px 12px;cursor:pointer;font-size:0.82rem;">1.75x</button>
<button onclick="setSpd(this,2.0)" style="background:#1a1a2e;border:1px solid #3a3a5c;border-radius:8px;color:#9ca3af;padding:5px 12px;cursor:pointer;font-size:0.82rem;">2x</button>
</div>
<script>
function setSpd(btn, s) {
var p = document.getElementById('tts-player');
if(p) p.playbackRate = s;
btn.closest('div').querySelectorAll('button').forEach(function(b){
b.style.background='#1a1a2e'; b.style.border='1px solid #3a3a5c'; b.style.color='#9ca3af';
});
btn.style.background='#b8860b'; btn.style.border='none'; btn.style.color='white';
}
</script>
</div>'''
def generate_from_text(text, custom_name=''):
if not text.strip():
yield None, 'β Please enter some text!', make_player(None), ''
return
yield from run_tts(text)
def generate_from_file(file, custom_name=''):
if file is None:
yield None, 'β Please upload a .txt file!', make_player(None), ''
return
try:
with open(file, 'r', encoding='utf-8') as f:
text = f.read()
if not text.strip():
yield None, 'β File is empty!', make_player(None), ''
return
yield None, 'π Loaded ' + str(len(text)) + ' chars. Starting...', make_player(None), ''
yield from run_tts(text, custom_name)
except Exception as e:
yield None, 'β Could not read file: ' + str(e), make_player(None), ''
# ββ CSS βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
css = """
@import url('https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=DM+Sans:wght@300;400;500&display=swap');
* { font-family: 'DM Sans', sans-serif; }
body, .gradio-container { background: #07070f !important; color: #e8e8f5 !important; }
.gradio-container { max-width: 900px !important; margin: 0 auto !important; }
h1 { font-family: 'Syne', sans-serif !important; font-weight: 800 !important;
font-size: 2.2rem !important;
background: linear-gradient(135deg, #ffd700, #ff8c00, #ff4500) !important;
-webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important;
text-align: center !important; margin-bottom: 0.2rem !important; }
.subtitle { text-align:center; color:#5a5a7a; font-size:0.88rem; margin-bottom:1.5rem; }
label { font-family:'Syne',sans-serif !important; font-weight:700 !important;
font-size:0.78rem !important; color:#ffd700 !important;
letter-spacing:0.06em !important; text-transform:uppercase !important; }
textarea { background:#0e0e1c !important; border:1px solid #2a2a3a !important;
border-radius:10px !important; color:#e8e8f5 !important; padding:12px !important; font-size:0.95rem !important; }
textarea:focus { border-color:#ffd700 !important; outline:none !important; }
.block { background:#0d0d1a !important; border:1px solid #1a1a2e !important;
border-radius:14px !important; padding:1.2rem !important; }
.btn-gen { background:linear-gradient(135deg,#b8860b,#ff8c00) !important;
border:none !important; border-radius:12px !important; color:white !important;
font-family:'Syne',sans-serif !important; font-weight:700 !important;
font-size:1.05rem !important; padding:16px !important; width:100% !important; }
.btn-gen:hover { opacity:0.85 !important; }
.tip { background:#0e0e1c; border-left:3px solid #ffd700; border-radius:0 8px 8px 0;
padding:10px 14px; font-size:0.82rem; color:#9ca3af; margin:0.5rem 0; }
"""
# ββ GUI βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
with gr.Blocks(css=css, title='Andrew TTS') as demo:
gr.HTML('<h1>ποΈ Andrew TTS</h1>')
gr.HTML('<p class="subtitle">Microsoft Andrew voice Β· Exact Colab quality Β· Auto saves to HF Dataset</p>')
with gr.Tabs():
with gr.Tab('π Paste Text'):
with gr.Row():
with gr.Column(scale=2):
text_input = gr.Textbox(
label='Your Text',
placeholder='Paste your novel chapter or any text here β no limit!',
lines=12
)
name1 = gr.Textbox(label='File Name (optional)', placeholder='e.g. chapter_102 β leave blank for auto name')
btn1 = gr.Button('ποΈ Generate Audio', elem_classes='btn-gen')
with gr.Column(scale=1):
gr.HTML('<div class="tip">β’ No character limit<br>β’ Live progress + ETA<br>β’ Auto saves to HF Dataset forever<br>β’ Speed control 0.75xβ2x</div>')
status1 = gr.Textbox(label='Progress', interactive=False, lines=3)
hf1 = gr.Textbox(label='Storage Status', interactive=False, lines=2)
player1 = gr.HTML(make_player(None))
audio1 = gr.Audio(label='Download Audio', type='filepath')
btn1.click(fn=generate_from_text, inputs=[text_input, name1], outputs=[audio1, status1, player1, hf1])
with gr.Tab('π Upload .txt File'):
with gr.Row():
with gr.Column(scale=2):
file_input = gr.File(label='Upload .txt file', file_types=['.txt'])
name2 = gr.Textbox(label='File Name (optional)', placeholder='e.g. chapter_102 β leave blank for auto name')
btn2 = gr.Button('ποΈ Generate Audio', elem_classes='btn-gen')
with gr.Column(scale=1):
gr.HTML('<div class="tip">β’ Upload any .txt file<br>β’ Live progress + ETA<br>β’ Auto saves to HF Dataset forever<br>β’ Speed control 0.75xβ2x</div>')
status2 = gr.Textbox(label='Progress', interactive=False, lines=3)
hf2 = gr.Textbox(label='Storage Status', interactive=False, lines=2)
player2 = gr.HTML(make_player(None))
audio2 = gr.Audio(label='Download Audio', type='filepath')
btn2.click(fn=generate_from_file, inputs=[file_input, name2], outputs=[audio2, status2, player2, hf2])
demo.launch() |