GET
/tracks/:id/waveformMảng peaks sóng âm ở số cột tuỳ chọn, kèm khối analysis.
pointsintegerSố cột sóng âm cần lấy, từ 16 đến 512, mặc định 120. Server tự gộp trung bình để đúng số cột bạn yêu cầu.API chỉ đọc để lấy danh sách nhạc, nghệ sĩ và thể loại từ kho XuniBOX. Mỗi chủ đề dưới đây là một trang riêng, có đường dẫn riêng để bạn chia sẻ cho đội kỹ thuật.
Mỗi bài nhạc được phân tích một lần lúc admin đăng lên, kết quả lưu sẵn trong cơ sở dữ liệu. Nhờ vậy bạn dựng được sóng âm và ghép nhạc theo nhịp mà không phải tải file nhạc về để tự phân tích — tiết kiệm băng thông và CPU của cả hai bên.
/tracks/:id/waveformMảng peaks sóng âm ở số cột tuỳ chọn, kèm khối analysis.
pointsintegerSố cột sóng âm cần lấy, từ 16 đến 512, mặc định 120. Server tự gộp trung bình để đúng số cột bạn yêu cầu.curl --request GET \
--url 'https://xunibox.vn/api/public/tracks/550e8400-e29b-41d4-a716-446655440000/waveform?points=64' \
--header 'X-API-Key: xnb_live_your_key'HTTP/1.1 200 OK
Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400
ETag: "9c1f0a72"
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"duration": "3:24",
"points": 64,
"scale": "0-100",
"peaks": [4, 11, 28, 46, 63, 71, 68, 55, 41, 33, 29, 38, 57, 74, 88, 92, 85, 70, 52, 40, "…"],
"analysis": {
"bpm": 128,
"musical_key": "A minor",
"energy": 0.74,
"loudness_db": -8.3
}
}If-None-Match để nhận 304 và không tốn băng thông.| Trường | Kiểu / khoảng | Ý nghĩa & cách dùng |
|---|---|---|
| peaks | number[] thang 0–100 | Biên độ trung bình từng đoạn. Chia cho 100 rồi nhân chiều cao khung để vẽ cột. |
| points | integer | Số phần tử thực tế trong peaks. Bằng 0 nếu bài chưa được phân tích sóng âm. |
| scale | string | Luôn là "0-100" ở phiên bản v1. Đọc trường này thay vì hardcode để an toàn với v2. |
| duration | string | Thời lượng bản đầy đủ dạng m:ss. Lưu ý đoạn preview ngắn hơn giá trị này. |
| analysis.bpm | number | null | Số nhịp mỗi phút. Dùng để khớp nhịp cắt cảnh video. |
| analysis.musical_key | string | null | Tông nhạc, ví dụ "A minor". Hữu ích khi nối nhiều bài trong một video. |
| analysis.energy | number 0–1 | null | Mức sôi động. Dưới 0.35 là chill, trên 0.7 là mạnh. |
| analysis.loudness_db | number | null | Độ to trung bình tính bằng dB. Dùng để chuẩn hoá âm lượng khi trộn với tiếng gốc. |
analysis đều có thể là null với bài cũ đăng trước khi hệ thống phân tích được bật. Luôn kiểm tra null trước khi tính toán, đừng giả định lúc nào cũng có BPM.// Vẽ sóng âm bằng canvas từ /tracks/:id/waveform — không cần tải file nhạc.
async function drawWaveform(canvas, trackId, points = 120) {
const { peaks, analysis, duration } = await fetch(
`/api/xunibox/tracks/${trackId}/waveform?points=${points}`,
).then((r) => r.json());
const ctx = canvas.getContext("2d");
const width = canvas.width;
const height = canvas.height;
const barWidth = width / peaks.length;
ctx.clearRect(0, 0, width, height);
peaks.forEach((peak, index) => { // peak nằm trong thang 0-100
const barHeight = Math.max(2, (peak / 100) * height);
const gradient = ctx.createLinearGradient(0, 0, 0, height);
gradient.addColorStop(0, "#ff4d2d");
gradient.addColorStop(1, "#ff9425");
ctx.fillStyle = gradient;
ctx.fillRect(index * barWidth, (height - barHeight) / 2, barWidth * 0.7, barHeight);
});
console.log(duration, analysis.bpm, analysis.musical_key); // "3:24", 128, "A minor"
}Đây là phần tạo khác biệt cho một bộ chọn nhạc: thay vì bắt người dùng nghe thử hàng chục bài, hãy gợi ý sẵn theo độ dài video và không khí họ muốn.
// Ghép nhạc theo nhịp: chọn bài có BPM khớp độ dài cảnh quay.
// Ví dụ video 15 giây, muốn cắt cảnh 4 lần đều nhau => 1 cảnh / 3.75s
// => BPM lý tưởng = 60 / 3.75 * 4 ≈ 64, hoặc bội số 128.
function pickByTempo(tracks, targetBpm, tolerance = 6) {
return tracks
.filter((track) => track.bpm != null)
.map((track) => {
// Chấp nhận cả bội số / ước số: 64 và 128 cảm giác cùng nhịp.
const candidates = [track.bpm, track.bpm / 2, track.bpm * 2];
const distance = Math.min(...candidates.map((bpm) => Math.abs(bpm - targetBpm)));
return { track, distance };
})
.filter((item) => item.distance <= tolerance)
.sort((a, b) => a.distance - b.distance)
.map((item) => item.track);
}
// Ghép theo cảm xúc: energy 0-1, càng cao càng sôi động.
const chill = tracks.filter((t) => (t.energy ?? 0) < 0.35);
const hype = tracks.filter((t) => (t.energy ?? 0) > 0.7);
// Chuẩn hoá âm lượng khi trộn nhạc nền với tiếng gốc của video:
// gain_dB = mục_tiêu - loudness_db, ví dụ mục tiêu -14 LUFS.
const gainDb = -14 - (track.loudness_db ?? -14);| Mục tiêu của người dùng | Bộ lọc gợi ý | Nhãn hiển thị |
|---|---|---|
| Vlog đời thường | energy 0.25–0.55, bpm 80–110 | Nhẹ nhàng |
| Chuyển cảnh nhanh, nhảy | energy > 0.7, bpm 120–140 | Sôi động |
| Nội dung cảm xúc, kể chuyện | energy < 0.35, musical_key chứa 'minor' | Sâu lắng |
| Quảng cáo sản phẩm | energy 0.5–0.75, commercial_use_allowed = true | Thương hiệu |
Gửi thông tin sản phẩm để admin XuniBOX xét duyệt và cấp API key.