beatlyze 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # beatlyze
2
+
3
+ JavaScript/TypeScript SDK for the [Beatlyze](https://beatlyze.dev) audio analysis API.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install beatlyze
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```javascript
14
+ const { Beatlyze } = require("beatlyze");
15
+
16
+ const bz = new Beatlyze("bz_your_api_key");
17
+
18
+ // Analyze from URL (waits for result by default)
19
+ const result = await bz.analyzeUrl("https://example.com/track.mp3");
20
+ console.log(result.bpm); // 128.4
21
+ console.log(result.key); // "A"
22
+ console.log(result.energy); // 0.78
23
+
24
+ // Submit without waiting
25
+ const job = await bz.analyzeUrl("https://example.com/track.mp3", { wait: false });
26
+ console.log(job.job_id);
27
+
28
+ // Check result later
29
+ const analysis = await bz.getAnalysis(job.job_id);
30
+
31
+ // Batch (up to 10 URLs)
32
+ const jobs = await bz.analyzeBatch([
33
+ "https://example.com/a.mp3",
34
+ "https://example.com/b.mp3",
35
+ ]);
36
+
37
+ // Check usage
38
+ const usage = await bz.getUsage();
39
+ console.log(`${usage.count}/${usage.limit} analyses used`);
40
+ ```
41
+
42
+ ## TypeScript
43
+
44
+ Full type definitions included. Import types directly:
45
+
46
+ ```typescript
47
+ import { Beatlyze, AnalysisResult, BeatlyzeError } from "beatlyze";
48
+ ```
49
+
50
+ ## License
51
+
52
+ MIT
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "beatlyze",
3
+ "version": "0.1.0",
4
+ "description": "JavaScript/TypeScript SDK for the Beatlyze audio analysis API.",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "files": ["src/"],
8
+ "keywords": ["audio", "music", "analysis", "bpm", "key", "beatlyze"],
9
+ "license": "MIT",
10
+ "homepage": "https://beatlyze.dev",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/mg0024/beatlyze-analyzer"
14
+ },
15
+ "engines": {
16
+ "node": ">=18"
17
+ }
18
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,78 @@
1
+ export interface AnalysisResult {
2
+ bpm: number;
3
+ bpm_confidence?: number;
4
+ key: string;
5
+ scale: string;
6
+ key_notation: string;
7
+ key_confidence?: number;
8
+ energy: number;
9
+ danceability: number;
10
+ valence: number;
11
+ loudness_lufs: number;
12
+ mood_tags: string[];
13
+ genre_suggestions: string[];
14
+ duration_seconds: number;
15
+ time_signature?: number;
16
+ sections?: Array<{
17
+ start_time: number;
18
+ duration: number;
19
+ key: string;
20
+ loudness: number;
21
+ energy: number;
22
+ }>;
23
+ }
24
+
25
+ export interface JobStatus {
26
+ job_id: string;
27
+ status: "pending" | "processing" | "completed" | "failed";
28
+ result?: AnalysisResult;
29
+ error?: string;
30
+ created_at?: string;
31
+ completed_at?: string;
32
+ }
33
+
34
+ export interface UsageInfo {
35
+ month: string;
36
+ count: number;
37
+ limit: number;
38
+ tier: string;
39
+ remaining: number;
40
+ }
41
+
42
+ export interface AnalyzeOptions {
43
+ webhookUrl?: string;
44
+ wait?: boolean;
45
+ idempotencyKey?: string;
46
+ }
47
+
48
+ export interface GetAnalysisOptions {
49
+ full?: boolean;
50
+ }
51
+
52
+ export interface BatchOptions {
53
+ webhookUrl?: string;
54
+ }
55
+
56
+ export interface BeatlyzeOptions {
57
+ baseUrl?: string;
58
+ pollInterval?: number;
59
+ maxWait?: number;
60
+ }
61
+
62
+ export class BeatlyzeError extends Error {
63
+ statusCode: number;
64
+ detail: string;
65
+ constructor(statusCode: number, detail: string);
66
+ }
67
+
68
+ export class Beatlyze {
69
+ constructor(apiKey: string, options?: BeatlyzeOptions);
70
+ analyzeUrl(url: string, options?: AnalyzeOptions): Promise<AnalysisResult>;
71
+ analyzeUrl(
72
+ url: string,
73
+ options: AnalyzeOptions & { wait: false }
74
+ ): Promise<JobStatus>;
75
+ getAnalysis(jobId: string, options?: GetAnalysisOptions): Promise<JobStatus>;
76
+ getUsage(): Promise<UsageInfo>;
77
+ analyzeBatch(urls: string[], options?: BatchOptions): Promise<JobStatus[]>;
78
+ }
package/src/index.js ADDED
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Beatlyze JavaScript SDK — audio analysis from any URL or file.
3
+ *
4
+ * @example
5
+ * const { Beatlyze } = require("beatlyze");
6
+ * const bz = new Beatlyze("bz_your_api_key");
7
+ * const result = await bz.analyzeUrl("https://example.com/track.mp3");
8
+ * console.log(result.bpm, result.key);
9
+ */
10
+
11
+ class BeatlyzeError extends Error {
12
+ constructor(statusCode, detail) {
13
+ super(`HTTP ${statusCode}: ${detail}`);
14
+ this.name = "BeatlyzeError";
15
+ this.statusCode = statusCode;
16
+ this.detail = detail;
17
+ }
18
+ }
19
+
20
+ class Beatlyze {
21
+ /**
22
+ * @param {string} apiKey - Your Beatlyze API key (starts with "bz_").
23
+ * @param {object} [options]
24
+ * @param {string} [options.baseUrl="https://api.beatlyze.dev"]
25
+ * @param {number} [options.pollInterval=2000] - Polling interval in ms.
26
+ * @param {number} [options.maxWait=300000] - Max wait time in ms.
27
+ */
28
+ constructor(apiKey, options = {}) {
29
+ this.apiKey = apiKey;
30
+ this.baseUrl = options.baseUrl || "https://api.beatlyze.dev";
31
+ this.pollInterval = options.pollInterval || 2000;
32
+ this.maxWait = options.maxWait || 300000;
33
+ }
34
+
35
+ async _request(method, path, options = {}) {
36
+ const url = `${this.baseUrl}${path}`;
37
+ const headers = { "X-API-Key": this.apiKey, ...options.headers };
38
+
39
+ const fetchOptions = { method, headers };
40
+
41
+ if (options.json) {
42
+ headers["Content-Type"] = "application/json";
43
+ fetchOptions.body = JSON.stringify(options.json);
44
+ } else if (options.body) {
45
+ fetchOptions.body = options.body;
46
+ }
47
+
48
+ const resp = await fetch(url, fetchOptions);
49
+
50
+ if (!resp.ok) {
51
+ let detail;
52
+ try {
53
+ const body = await resp.json();
54
+ detail = body.detail || JSON.stringify(body);
55
+ } catch {
56
+ detail = await resp.text();
57
+ }
58
+ throw new BeatlyzeError(resp.status, detail);
59
+ }
60
+
61
+ return resp.json();
62
+ }
63
+
64
+ async _waitForResult(jobId) {
65
+ const start = Date.now();
66
+ while (true) {
67
+ const result = await this._request("GET", `/v1/analysis/${jobId}`);
68
+ if (result.status === "completed" || result.status === "failed") {
69
+ if (result.status === "failed") {
70
+ throw new BeatlyzeError(500, result.error || "Analysis failed");
71
+ }
72
+ return result.result || {};
73
+ }
74
+
75
+ if (Date.now() - start > this.maxWait) {
76
+ throw new BeatlyzeError(408, `Timed out waiting for job ${jobId}`);
77
+ }
78
+
79
+ await new Promise((r) => setTimeout(r, this.pollInterval));
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Analyze audio from a URL.
85
+ *
86
+ * @param {string} url - Public URL to an audio file.
87
+ * @param {object} [options]
88
+ * @param {string} [options.webhookUrl] - URL to receive results via POST.
89
+ * @param {boolean} [options.wait=true] - Poll until complete.
90
+ * @param {string} [options.idempotencyKey] - Prevent duplicate submissions.
91
+ * @returns {Promise<object>} Analysis result or job status.
92
+ */
93
+ async analyzeUrl(url, options = {}) {
94
+ const body = { url };
95
+ if (options.webhookUrl) body.webhook_url = options.webhookUrl;
96
+
97
+ const headers = {};
98
+ if (options.idempotencyKey)
99
+ headers["Idempotency-Key"] = options.idempotencyKey;
100
+
101
+ const resp = await this._request("POST", "/v1/analyze/url", {
102
+ json: body,
103
+ headers,
104
+ });
105
+
106
+ if (options.wait === false) return resp;
107
+ return this._waitForResult(resp.job_id);
108
+ }
109
+
110
+ /**
111
+ * Get analysis result for a job.
112
+ *
113
+ * @param {string} jobId - The job UUID.
114
+ * @param {object} [options]
115
+ * @param {boolean} [options.full=false] - Include section-level detail.
116
+ * @returns {Promise<object>}
117
+ */
118
+ async getAnalysis(jobId, options = {}) {
119
+ const suffix = options.full ? "/full" : "";
120
+ return this._request("GET", `/v1/analysis/${jobId}${suffix}`);
121
+ }
122
+
123
+ /**
124
+ * Get current month's usage stats.
125
+ * @returns {Promise<object>} Usage data with month, count, limit, tier.
126
+ */
127
+ async getUsage() {
128
+ return this._request("GET", "/v1/usage");
129
+ }
130
+
131
+ /**
132
+ * Submit a batch of URLs for analysis (max 10).
133
+ *
134
+ * @param {string[]} urls - List of audio URLs.
135
+ * @param {object} [options]
136
+ * @param {string} [options.webhookUrl] - Webhook URL for all items.
137
+ * @returns {Promise<object[]>} List of job status objects.
138
+ */
139
+ async analyzeBatch(urls, options = {}) {
140
+ const items = urls.map((u) =>
141
+ options.webhookUrl ? { url: u, webhook_url: options.webhookUrl } : { url: u }
142
+ );
143
+ const resp = await this._request("POST", "/v1/batch", {
144
+ json: { items },
145
+ });
146
+ return resp.jobs || [];
147
+ }
148
+ }
149
+
150
+ module.exports = { Beatlyze, BeatlyzeError };