| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Cinematic Lyric Studio - Mohsin Kamil Credits</title> |
| <style> |
| :root { |
| --bg: #050505; |
| --panel: #111111; |
| --accent: #3498db; |
| --text: #ffffff; |
| } |
| |
| body { |
| background: var(--bg); |
| color: var(--text); |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| margin: 0; |
| display: flex; |
| height: 100vh; |
| overflow: hidden; |
| } |
| |
| |
| .preview-side { |
| flex: 1.2; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| padding: 20px; |
| border-right: 1px solid #222; |
| } |
| |
| #canvas-wrap { |
| width: 100%; |
| max-width: 800px; |
| aspect-ratio: 16 / 9; |
| background: #000; |
| box-shadow: 0 0 50px rgba(0,0,0,1); |
| border: 1px solid #333; |
| border-radius: 4px; |
| overflow: hidden; |
| } |
| |
| canvas { width: 100%; height: 100%; display: block; } |
| |
| .player-ui { |
| width: 100%; |
| max-width: 800px; |
| margin-top: 25px; |
| background: var(--panel); |
| padding: 20px; |
| border-radius: 12px; |
| border: 1px solid #222; |
| } |
| |
| .seek-bar { |
| width: 100%; |
| accent-color: var(--accent); |
| cursor: pointer; |
| margin-bottom: 15px; |
| } |
| |
| .controls { |
| display: flex; |
| align-items: center; |
| gap: 15px; |
| } |
| |
| .btn { |
| padding: 10px 25px; |
| border-radius: 20px; |
| border: none; |
| cursor: pointer; |
| font-weight: 600; |
| transition: 0.2s; |
| } |
| |
| .btn-play { background: #fff; color: #000; min-width: 100px; } |
| .btn-export { background: var(--accent); color: #fff; margin-left: auto; } |
| .btn:hover { transform: scale(1.05); opacity: 0.9; } |
| |
| |
| .editor-side { |
| flex: 0.8; |
| background: var(--panel); |
| display: flex; |
| flex-direction: column; |
| padding: 20px; |
| overflow-y: auto; |
| } |
| |
| .lyric-card { |
| background: #1a1a1a; |
| padding: 12px; |
| border-radius: 10px; |
| margin-bottom: 12px; |
| border: 1px solid #222; |
| } |
| |
| .lyric-card input[type="text"] { |
| width: 100%; |
| background: transparent; |
| border: none; |
| color: #ddd; |
| font-size: 15px; |
| margin-bottom: 8px; |
| outline: none; |
| } |
| |
| .timing-row { |
| display: flex; |
| gap: 8px; |
| align-items: center; |
| font-size: 11px; |
| color: #777; |
| } |
| |
| .timing-row input { |
| width: 55px; |
| background: #000; |
| border: 1px solid #333; |
| color: var(--accent); |
| padding: 4px; |
| border-radius: 4px; |
| } |
| |
| .stamp-btn { |
| background: #333; |
| color: #ccc; |
| border: none; |
| padding: 4px 10px; |
| border-radius: 4px; |
| cursor: pointer; |
| } |
| |
| #exportOverlay { |
| position: fixed; |
| inset: 0; |
| background: rgba(0,0,0,0.95); |
| display: none; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| z-index: 999; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <div id="exportOverlay"> |
| <h2>Exporting 1080p Master...</h2> |
| <div style="width: 300px; height: 6px; background: #222; margin-top: 20px; border-radius: 3px;"> |
| <div id="exportBar" style="width: 0%; height: 100%; background: var(--accent); border-radius: 3px;"></div> |
| </div> |
| </div> |
|
|
| <div class="preview-side"> |
| <div style="margin-bottom: 15px;"> |
| <input type="file" id="audioUpload" accept="audio/*" hidden> |
| <button class="btn btn-play" onclick="document.getElementById('audioUpload').click()">📁 Select Song</button> |
| </div> |
|
|
| <div id="canvas-wrap"> |
| <canvas id="mainCanvas" width="1920" height="1080"></canvas> |
| </div> |
|
|
| <div class="player-ui"> |
| <input type="range" class="seek-bar" id="seekBar" value="0" min="0" step="0.01"> |
| <div class="controls"> |
| <span id="timeDisplay" style="font-family: monospace;">00:00 / 00:00</span> |
| <button class="btn btn-play" id="playBtn">PLAY</button> |
| <button class="btn btn-export" id="exportBtn">EXPORT 1080p</button> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="editor-side" id="editorList"> |
| |
| </div> |
|
|
| <script> |
| const canvas = document.getElementById('mainCanvas'); |
| const ctx = canvas.getContext('2d'); |
| const audio = new Audio(); |
| const playBtn = document.getElementById('playBtn'); |
| const seekBar = document.getElementById('seekBar'); |
| const editorList = document.getElementById('editorList'); |
| |
| |
| let lyrics = [ |
| { start: 6.0, end: 19.0, text: "Lyrics by: Mohsin Kamil" }, |
| { start: 31.5, end: 34.5, text: "Kaash hum na milte..." }, |
| { start: 35.5, end: 40.5, text: "Naa hotey judaa..." }, |
| { start: 41.5, end: 50.5, text: "Milna bhi tu tha Khuda ka faisla." }, |
| { start: 52.0, end: 56.5, text: "Pal do pal mile phir," }, |
| { start: 57.5, end: 62.5, text: "ho gaye judaa..." }, |
| { start: 63.5, end: 76.0, text: "Janey aesa kyon kiya tuu ney Khuda..." }, |
| { start: 77.0, end: 84.0, text: "Kiya thi wajah," }, |
| { start: 84.5, end: 90.0, text: "do na mujh ko ishara..." }, |
| { start: 90.5, end: 96.0, text: "Mein hun tootaa," }, |
| { start: 96.5, end: 105.0, text: "mein hun haara." }, |
| { start: 105.5, end: 113.0, text: "Shayer tha shayad mujhe banana..." }, |
| { start: 113.5, end: 126.0, text: "Isi liye zaroori tha kisi ko bewafa banana..." }, |
| { start: 126.5, end: 132.5, text: "Shayad... mein na tha us ke liye sahi..." }, |
| { start: 133.0, end: 139.0, text: "Shayad... wo na tha acha merey liye..." }, |
| { start: 139.5, end: 149.0, text: "Shayad... na hotey judaa tu bari hoti saza..." }, |
| { start: 149.5, end: 160.0, text: "Shayad... pal do pal mil ke bichadna hi thaa." }, |
| { start: 160.5, end: 165.0, text: "Pal do pal mile phir," }, |
| { start: 165.5, end: 171.0, text: "ho gaye judaa..." }, |
| { start: 171.5, end: 181.0, text: "Janey aesa kyon kiya... Khuda..." } |
| ]; |
| |
| |
| const particles = Array.from({ length: 60 }, () => ({ |
| x: Math.random() * 1920, y: Math.random() * 1080, |
| s: Math.random() * 2 + 1, v: Math.random() * 0.4 + 0.1, o: Math.random() * 0.4 |
| })); |
| |
| function drawParticles() { |
| particles.forEach(p => { |
| p.y -= p.v; if (p.y < 0) p.y = 1080; |
| ctx.fillStyle = `rgba(255,255,255,${p.o})`; |
| ctx.beginPath(); ctx.arc(p.x, p.y, p.s, 0, Math.PI*2); ctx.fill(); |
| }); |
| } |
| |
| |
| function render() { |
| const t = audio.currentTime; |
| ctx.fillStyle = "#000"; ctx.fillRect(0, 0, 1920, 1080); |
| |
| const grad = ctx.createRadialGradient(960, 540, 50, 960, 540, 1100); |
| grad.addColorStop(0, "#0d0e12"); grad.addColorStop(1, "#000"); |
| ctx.fillStyle = grad; ctx.fillRect(0,0,1920,1080); |
| |
| drawParticles(); |
| |
| const active = lyrics.find(l => t >= l.start && t <= l.end); |
| if (active) { |
| const elapsed = t - active.start; |
| const remaining = active.end - t; |
| let alpha = 1; |
| if (elapsed < 1.0) alpha = elapsed / 1.0; |
| if (remaining < 1.0) alpha = remaining / 1.0; |
| |
| ctx.save(); |
| ctx.translate(960, 540); |
| const scale = 1 + (elapsed * 0.012); |
| ctx.scale(scale, scale); |
| |
| ctx.filter = `blur(${(1-alpha)*15}px)`; |
| ctx.textAlign = "center"; ctx.textBaseline = "middle"; |
| |
| |
| if(active.text.includes("Lyrics by")) { |
| ctx.font = "300 45px 'Segoe UI Light', sans-serif"; |
| ctx.letterSpacing = "4px"; |
| } else { |
| ctx.font = "italic 75px 'Georgia'"; |
| ctx.shadowBlur = 20; ctx.shadowColor = "rgba(255,255,255,0.4)"; |
| } |
| |
| ctx.fillStyle = `rgba(255,255,255,${alpha})`; |
| ctx.fillText(active.text, 0, 0); |
| ctx.restore(); |
| } |
| |
| if (!audio.paused || isExporting) requestAnimationFrame(render); |
| } |
| |
| |
| function refreshEditor() { |
| editorList.innerHTML = '<h3>Scene & Lyrics Timeline</h3>'; |
| lyrics.forEach((l, i) => { |
| const div = document.createElement('div'); |
| div.className = 'lyric-card'; |
| div.innerHTML = ` |
| <input type="text" value="${l.text}" oninput="lyrics[${i}].text=this.value"> |
| <div class="timing-row"> |
| S: <input type="number" step="0.1" value="${l.start}" onchange="lyrics[${i}].start=parseFloat(this.value)"> |
| <button class="stamp-btn" onclick="setIn(${i})">⏱️ In</button> |
| E: <input type="number" step="0.1" value="${l.end}" onchange="lyrics[${i}].end=parseFloat(this.value)"> |
| <button class="stamp-btn" onclick="setOut(${i})">⏱️ Out</button> |
| </div> |
| `; |
| editorList.appendChild(div); |
| }); |
| } |
| |
| window.setIn = (i) => { lyrics[i].start = Math.round(audio.currentTime*10)/10; refreshEditor(); }; |
| window.setOut = (i) => { lyrics[i].end = Math.round(audio.currentTime*10)/10; refreshEditor(); }; |
| |
| document.getElementById('audioUpload').onchange = (e) => { |
| audio.src = URL.createObjectURL(e.target.files[0]); |
| audio.onloadedmetadata = () => { seekBar.max = audio.duration; refreshEditor(); render(); }; |
| }; |
| |
| playBtn.onclick = () => { |
| if (audio.paused) { audio.play(); playBtn.innerText = "PAUSE"; render(); } |
| else { audio.pause(); playBtn.innerText = "PLAY"; } |
| }; |
| |
| audio.ontimeupdate = () => { |
| seekBar.value = audio.currentTime; |
| document.getElementById('timeDisplay').innerText = `${fmt(audio.currentTime)} / ${fmt(audio.duration)}`; |
| }; |
| |
| seekBar.oninput = () => { audio.currentTime = seekBar.value; render(); }; |
| function fmt(s) { return new Date(s * 1000).toISOString().substr(14, 5); } |
| |
| |
| let isExporting = false; |
| document.getElementById('exportBtn').onclick = async () => { |
| isExporting = true; audio.pause(); audio.currentTime = 0; |
| document.getElementById('exportOverlay').style.display = 'flex'; |
| |
| const stream = canvas.captureStream(60); |
| const audioCtx = new AudioContext(); |
| const source = audioCtx.createMediaElementSource(audio); |
| const dest = audioCtx.createMediaStreamDestination(); |
| source.connect(dest); source.connect(audioCtx.destination); |
| |
| const recorder = new MediaRecorder(new MediaStream([...stream.getTracks(), ...dest.stream.getTracks()]), { |
| mimeType: 'video/webm;codecs=vp9', videoBitsPerSecond: 20000000 |
| }); |
| |
| let chunks = []; |
| recorder.ondataavailable = e => chunks.push(e.data); |
| recorder.onstop = () => { |
| const a = document.createElement('a'); |
| a.href = URL.createObjectURL(new Blob(chunks, {type:'video/webm'})); |
| a.download = 'Kaash_Hum_Na_Milte_Master.webm'; a.click(); |
| location.reload(); |
| }; |
| |
| recorder.start(); audio.play(); |
| const intv = setInterval(() => { |
| document.getElementById('exportBar').style.width = (audio.currentTime/audio.duration)*100 + '%'; |
| if (audio.ended) { clearInterval(intv); recorder.stop(); } |
| }, 100); |
| }; |
| |
| refreshEditor(); |
| </script> |
| </body> |
| </html> |