const { createCanvas, loadImage, CanvasRenderingContext2D } = require('canvas'); const axios = require('axios'); const fs = require('fs'); // atur aja config nya di sini const config = { title: 'Always Alone', artist: '#Shn', coverUrl: 'https://files.soonex.biz.id/cc0e64906c2c.png', // pakai logo apple langsung appleIconUrl: 'https://img.icons8.com/ios-glyphs/60/ffffff/mac-os.png', currentTime: '2:40', totalTime: '04:11', progressPercent: 53, color1: '#7b2cbf', // ungu color2: '#ff2a5f', // merah ntah apa ini color3: '#ff7e40', // oren }; async function generateMusicCard() { const { title, artist, coverUrl, appleIconUrl, currentTime, totalTime, progressPercent, color1, color2, color3 } = config; const width = 1200; const height = 700; const canvas = createCanvas(width, height); const ctx = canvas.getContext('2d'); // background gradient meSH ctx.fillStyle = '#111'; ctx.fillRect(0, 0, width, height); const grad1 = ctx.createRadialGradient(200, 350, 100, 300, 350, 900); grad1.addColorStop(0, color1); grad1.addColorStop(1, 'rgba(0,0,0,0)'); ctx.fillStyle = grad1; ctx.fillRect(0, 0, width, height); const grad2 = ctx.createRadialGradient(1000, 600, 50, 900, 500, 800); grad2.addColorStop(0, color2); grad2.addColorStop(1, 'rgba(0,0,0,0)'); ctx.fillStyle = grad2; ctx.fillRect(0, 0, width, height); const grad3 = ctx.createRadialGradient(1100, 100, 0, 900, 200, 700); grad3.addColorStop(0, color3); grad3.addColorStop(1, 'rgba(0,0,0,0)'); ctx.fillStyle = grad3; ctx.fillRect(0, 0, width, height); // bagian kartu kaca nya const cardW = 1000; const cardH = 460; const cardX = (width - cardW) / 2; const cardY = (height - cardH) / 2; const cardRadius = 36; ctx.shadowColor = 'rgba(0, 0, 0, 0.4)'; ctx.shadowBlur = 50; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 20; ctx.beginPath(); ctx.roundRect(cardX, cardY, cardW, cardH, cardRadius); ctx.fillStyle = 'rgba(28, 28, 30, 0.65)'; ctx.fill(); ctx.shadowColor = 'transparent'; ctx.shadowBlur = 0; ctx.shadowOffsetY = 0; ctx.lineWidth = 1.5; ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)'; ctx.stroke(); // buat get logo apple dan yang artis nya let coverImg = null; let appleImg = null; try { // download cover nya const coverRes = await axios.get(coverUrl, { responseType: 'arraybuffer', headers: { 'User-Agent': 'Mozilla/5.0' } }); coverImg = await loadImage(Buffer.from(coverRes.data)); // download logo apple const appleRes = await axios.get(appleIconUrl, { responseType: 'arraybuffer' }); appleImg = await loadImage(Buffer.from(appleRes.data)); } catch (e) { console.warn('⚠️ Ada gambar yang gagal didownload:', e.message); } // buat yang foto di kiri itu const albumSize = 360; const albumX = cardX + 50; const albumY = cardY + 50; const albumRadius = 24; ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; ctx.shadowBlur = 25; ctx.shadowOffsetY = 12; ctx.save(); ctx.beginPath(); ctx.roundRect(albumX, albumY, albumSize, albumSize, albumRadius); ctx.fillStyle = '#333'; ctx.fill(); ctx.shadowColor = 'transparent'; ctx.shadowBlur = 0; ctx.shadowOffsetY = 0; ctx.clip(); if (coverImg) { ctx.drawImage(coverImg, albumX, albumY, albumSize, albumSize); } else { ctx.fillStyle = '#444'; ctx.fillRect(albumX, albumY, albumSize, albumSize); ctx.fillStyle = '#fff'; ctx.font = '50px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('🎵', albumX + albumSize/2, albumY + albumSize/2); } ctx.restore(); // buat keterangan artis, judul, dll const detailX = albumX + albumSize + 60; const detailW = cardW - (detailX - cardX) - 50; ctx.textAlign = 'left'; ctx.textBaseline = 'top'; // letak logo apel ny const logoY = albumY + 5; if (appleImg) { // logo apel Ukuran 20x20 ctx.drawImage(appleImg, detailX, logoY - 2, 20, 20); // tks msik d sblh ny ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'; ctx.font = '18px "Helvetica Neue", Helvetica, Arial, sans-serif'; ctx.fillText('Music', detailX + 26, logoY); } else { // fllbck jka gmbr errr ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'; ctx.font = '18px Arial'; ctx.fillText('Apple Music', detailX, logoY); } // tks jdl dn arts const textY = logoY + 40; ctx.fillStyle = '#ffffff'; ctx.font = 'bold 42px "Helvetica Neue", Helvetica, Arial, sans-serif'; ctx.fillText(title.substring(0, 30), detailX, textY); ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; ctx.font = '26px "Helvetica Neue", Helvetica, Arial, sans-serif'; ctx.fillText(artist.substring(0, 40), detailX, textY + 55); // tmbl cntrl const controlY = textY + 160; const controlCenterX = detailX + (detailW / 2); drawIcon(ctx, 'prev', controlCenterX - 85, controlY); drawIcon(ctx, 'pause', controlCenterX, controlY); drawIcon(ctx, 'next', controlCenterX + 85, controlY); // prgrs bad const barY = albumY + albumSize - 25; const barH = 6; ctx.beginPath(); ctx.roundRect(detailX, barY, detailW, barH, barH/2); ctx.fillStyle = 'rgba(255, 255, 255, 0.2)'; ctx.fill(); const fillW = (progressPercent / 100) * detailW; ctx.beginPath(); ctx.roundRect(detailX, barY, fillW, barH, barH/2); ctx.fillStyle = '#ffffff'; ctx.fill(); ctx.beginPath(); ctx.arc(detailX + fillW, barY + barH/2, 8, 0, Math.PI * 2); ctx.fillStyle = '#ffffff'; ctx.fill(); // wktu lgu const timeY = barY + 18; ctx.font = '14px "Helvetica Neue", Helvetica, Arial, sans-serif'; ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; ctx.textAlign = 'left'; ctx.fillText(currentTime, detailX, timeY); ctx.textAlign = 'right'; ctx.fillText('-' + totalTime, detailX + detailW, timeY); // smpn gmbr const buffer = canvas.toBuffer('image/png'); fs.writeFileSync('apple_music.png', buffer); console.log('✅ SELESAI! Gambar Apple & Cover beneran udah muncul sekarang! 🍏🎧'); } // func pmbntu aj buat gmbr icon ny function drawIcon(ctx, type, x, y) { ctx.fillStyle = '#ffffff'; ctx.beginPath(); if (type === 'pause') { const w = 8, h = 34, gap = 12; ctx.roundRect(x - gap/2 - w, y - h/2, w, h, 3); ctx.roundRect(x + gap/2, y - h/2, w, h, 3); } else if (type === 'next') { const size = 22, gap = 18; ctx.moveTo(x - gap, y - size/2); ctx.lineTo(x, y); ctx.lineTo(x - gap, y + size/2); ctx.moveTo(x, y - size/2); ctx.lineTo(x + gap, y); ctx.lineTo(x, y + size/2); ctx.roundRect(x + gap + 2, y - size/2, 4, size, 2); } else if (type === 'prev') { const size = 22, gap = 18; ctx.roundRect(x - gap - 6, y - size/2, 4, size, 2); ctx.moveTo(x - gap, y); ctx.lineTo(x, y - size/2); ctx.lineTo(x, y + size/2); ctx.moveTo(x, y); ctx.lineTo(x + gap, y - size/2); ctx.lineTo(x + gap, y + size/2); } ctx.fill(); ctx.closePath(); } // polyfill roundrect if (!CanvasRenderingContext2D.prototype.roundRect) { CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, radii) { const r = typeof radii === 'number' ? radii : (radii || 0); this.moveTo(x + r, y); this.arcTo(x + w, y, x + w, y + h, r); this.arcTo(x + w, y + h, x, y + h, r); this.arcTo(x, y + h, x, y, r); this.arcTo(x, y, x + w, y, r); this.closePath(); return this; }; } generateMusicCard().catch(err => console.error('❌ Error:', err));