import { SignatureGenerator } from './algorithm.js';
import { recognizeBytes } from 'shazamio-core';
import { default as fetch } from 'node-fetch';
import { s16LEToSamplesArray } from './utils.js';
import fs from 'fs';
import { readFileSync } from 'fs';
import { Request, ShazamURLS } from './requests.js';
import { convertfile, tomp3 } from './to_pcm.js';
const TIME_ZONE = 'Europe/Paris';
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
}).toUpperCase();
}
export class Endpoint {
constructor(timezone) {
this.timezone = timezone;
}
url() {
return `${Endpoint.SCHEME}://${Endpoint.HOSTNAME}/discovery/v5/en/US/iphone/-/tag/${uuidv4()}/${uuidv4()}`;
}
params() {
return {
'sync': 'true',
'webv3': 'true',
'sampling': 'true',
'connected': '',
'shazamapiversion': 'v3',
'sharehub': 'true',
'hubv5minorversion': 'v5.1',
'hidelb': 'true',
'video': 'v3'
};
}
headers(language = 'en') {
return Request.headers(language);
}
async sendRecognizeRequest(url, body, language = 'en') {
//@ts-ignore
return await (await fetch(url, { body, headers: this.headers(language), method: 'POST' })).json();
}
async formatAndSendRecognizeRequest(signature, language = 'en') {
const data = {
'timezone': this.timezone,
'signature': {
'uri': signature.encodeToUri(),
'samplems': Math.round(signature.numberSamples / signature.sampleRateHz * 1000)
},
'timestamp': new Date().getTime(),
'context': {},
'geolocation': {}
};
const url = new URL(this.url());
Object.entries(this.params()).forEach(([a, b]) => url.searchParams.append(a, b));
const response = await this.sendRecognizeRequest(url.toString(), JSON.stringify(data), language);
if ((response === null || response === void 0 ? void 0 : response.matches.length) === 0)
return null;
return response;
}
}
Endpoint.SCHEME = 'https';
Endpoint.HOSTNAME = 'amp.shazam.com';
/**
* @class Shazam
*/
export class Shazam {
constructor(timeZone) {
this.endpoint = new Endpoint(timeZone !== null && timeZone !== void 0 ? timeZone : TIME_ZONE);
}
headers(language = 'en') {
return Request.headers(language);
}
/**
* @deprecated
* Recognise a song from an audio file
* @param {string} path the path to the file
* @param {Boolean} minimal false for full track data, true for simplified form
* @param {string} language Song language
* @returns {ShazamRoot | null}
*/
async fromFilePath(path, minimal = false, language = 'en') {
await convertfile(path);
const data = fs.readFileSync('node_shazam_temp.pcm');
const conv = s16LEToSamplesArray(data);
fs.unlinkSync('node_shazam_temp.pcm');
const recognise = minimal ? await this.recognizeSongMinimal(conv, language) : await this.recognizeSong(conv, language);
return recognise;
}
/**
* @deprecated
* Recognise a song from a video file
* @param {string} path the path to the file
* @param {Boolean} minimal false for full track data, true for simplified form
* @param {string} language Song language
* @returns {ShazamRoot | null}
*/
async fromVideoFile(path, minimal = false, language = 'en') {
await tomp3(path);
const res = await this.fromFilePath('node_shazam_temp.mp3', minimal, language);
fs.unlinkSync('node_shazam_temp.mp3');
return res;
}
/**
* @deprecated
* Recognise a song from Samples Array
* @param {number[]} samples Samples array
* @param {string} language Song language
*/
async recognizeSong(samples, language = 'en', callback) {
const response = await this.fullRecognizeSong(samples, callback, language);
if (!response)
return null;
return response;
}
/**
* @deprecated
* Recognise a song from Samples Array and return minial info
* @param {number[]} samples Samples array
* @param {string} language Song language
*/
async recognizeSongMinimal(samples, language = 'en', callback) {
var _a, _b;
const response = await this.fullRecognizeSong(samples, callback, language);
if (!response)
return null;
const trackData = response.track, mainSection = trackData.sections.find((e) => e.type === 'SONG');
const title = trackData.title, artist = trackData.subtitle, album = (_a = mainSection.metadata.find(e => e.title === 'Album')) === null || _a === void 0 ? void 0 : _a.text, year = (_b = mainSection.metadata.find(e => e.title === 'Released')) === null || _b === void 0 ? void 0 : _b.text;
return { title, artist, album, year };
}
async fullRecognizeSong(samples, callback, language = 'en') {
callback === null || callback === void 0 ? void 0 : callback('generating');
const generator = this.createSignatureGenerator(samples);
while (true) {
callback === null || callback === void 0 ? void 0 : callback('generating');
const signature = generator.getNextSignature();
if (!signature) {
break;
}
callback === null || callback === void 0 ? void 0 : callback('transmitting');
const results = await this.endpoint.formatAndSendRecognizeRequest(signature, language);
if (results !== null)
return results;
}
return null;
}
/**
* Recognise a song from a file
* @param {string} path the path to the file
* @param {string} language Song language
* @param {boolean} minimal return minimal info
* @returns {ShazamRoot | null}
*/
async recognise(path, language = 'en-US', minimal = false) {
var _a, _b;
const signatures = recognizeBytes(readFileSync(path), 0, Number.MAX_SAFE_INTEGER);
let response;
for (let i = Math.floor(Math.random() * (signatures.length / 2)); i < signatures.length; i += 4) {
const data = {
'timezone': this.endpoint.timezone,
'signature': {
'uri': signatures[i].uri,
'samplems': signatures[i].samplems
},
'timestamp': new Date().getTime(),
'context': {},
'geolocation': {}
};
const url = new URL(this.endpoint.url());
Object.entries(this.endpoint.params()).forEach(([a, b]) => url.searchParams.append(a, b));
response = await this.endpoint.sendRecognizeRequest(url.toString(), JSON.stringify(data), language);
if ((response === null || response === void 0 ? void 0 : response.matches.length) === 0)
continue;
break;
}
for (const sig of signatures)
sig.free();
if (!response)
return null;
if ((response === null || response === void 0 ? void 0 : response.matches.length) === 0)
return null;
if (minimal) {
const trackData = response.track, mainSection = trackData.sections.find((e) => e.type === 'SONG');
const title = trackData.title, artist = trackData.subtitle, album = (_a = mainSection.metadata.find(e => e.title === 'Album')) === null || _a === void 0 ? void 0 : _a.text, year = (_b = mainSection.metadata.find(e => e.title === 'Released')) === null || _b === void 0 ? void 0 : _b.text;
return { title, artist, album, year };
}
return response;
}
createSignatureGenerator(samples) {
const signatureGenerator = new SignatureGenerator();
signatureGenerator.feedInput(samples);
return signatureGenerator;
}
/**
* Most shazamed tracks globally
* @param {string} language Song language
* @param {string} endpoint_country Endpoint country
* @param {string} limit limit to how many tracks are fetched
* @param {string} offset the offset to start fetching from
*/
async top_tracks_global(language = 'en-US', endpoint_country = 'GB', limit = '10', offset = '0') {
const url = ShazamURLS.top_tracks_global(language, endpoint_country, limit, offset);
return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
}
/**
* Most shazamed tracks for a country
* @param {string} language Song language
* @param {string} endpoint_country Endpoint country
* @param {string} country_code ISO country code for the country
* @param {string} limit limit to how many tracks are fetched
* @param {string} offset the offset to start fetching from
*/
async top_tracks_country(language, endpoint_country, country_code, limit, offset) {
const url = ShazamURLS.top_tracks_country(language, endpoint_country, country_code, limit, offset);
return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
}
/**
* Most shazamed tracks for a city
* @param {string} language Song language
* @param {string} endpoint_country Endpoint country
* @param {string} city_id Shazam city id
* @param {string} limit limit to how many tracks are fetched
* @param {string} offset the offset to start fetching from
*/
async top_tracks_city(language, endpoint_country, city_id, limit, offset) {
const url = ShazamURLS.top_tracks_city(language, endpoint_country, city_id, limit, offset);
return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
}
/**
* Info about a track
* @param {string} language Song language
* @param {string} endpoint_country Endpoint country
* @param {string} track_id Shazam track id
*/
async track_info(language, endpoint_country, track_id) {
const url = ShazamURLS.track_info(language, endpoint_country, track_id);
return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
}
/**
* List locations
*/
async list_locations() {
const url = ShazamURLS.locations();
return await (await fetch(url, { headers: this.headers(), method: 'GET' })).json();
}
/**
* Most shazamed tracks globally for a genre
* @param {string} language Song language
* @param {string} endpoint_country Endpoint country
* @param {string} genre Genre to search
* @param {string} limit limit to how many tracks are fetched
* @param {string} offset the offset to start fetching from
*/
async top_genre_tracks_world(language, endpoint_country, genre, limit, offset) {
const url = ShazamURLS.genre_world(language, endpoint_country, genre, limit, offset);
return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
}
/**
* Most shazamed tracks for a country
* @param {string} language Song language
* @param {string} endpoint_country Endpoint country
* @param {string} country ISO country code for the country
* @param {string} genre Genre to search
* @param {string} limit limit to how many tracks are fetched
* @param {string} offset the offset to start fetching from
*/
async top_genre_tracks_country(language, endpoint_country, country, genre, limit, offset) {
const url = ShazamURLS.genre_country(language, endpoint_country, country, genre, limit, offset);
return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
}
/**
* Related songs for a track
* @param {string} language Song language
* @param {string} endpoint_country Endpoint country
* @param {string} track_id Shazam track id
* @param {string} limit limit to how many tracks are fetched
* @param {string} offset the offset to start fetching from
*/
async related_songs(language, endpoint_country, track_id, offset, limit) {
const url = ShazamURLS.related_songs(language, endpoint_country, track_id, offset, limit);
return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
}
/**
* Search artist by name
* @param {string} language Song language
* @param {string} endpoint_country Endpoint country
* @param {string} query Artist name
* @param {string} limit limit to how many tracks are fetched
* @param {string} offset the offset to start fetching from
*/
async search_artist(language, endpoint_country, query, limit, offset) {
const url = ShazamURLS.search_artist(language, endpoint_country, query, limit, offset);
return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
}
/**
* Search artist by id
* @param {string} endpoint_country Endpoint country
* @param {string} artist_id Artist ID
*/
async search_artist_v2(endpoint_country, artist_id) {
const url = ShazamURLS.search_artist_v2(endpoint_country, artist_id);
return await (await fetch(url, { headers: this.headers(), method: 'GET' })).json();
}
/**
* Albums by an artist
* @param {string} endpoint_country Endpoint country
* @param {string} artist_id Shazam artist id
* @param {string} limit limit to how many tracks are fetched
* @param {string} offset the offset to start fetching from
*/
async artist_albums(endpoint_country, artist_id, limit, offset) {
const url = ShazamURLS.artist_albums(endpoint_country, artist_id, limit, offset);
return await (await fetch(url, { headers: this.headers(), method: 'GET' })).json();
}
/**
* Search music on shazam
* @param {string} language Song language
* @param {string} endpoint_country Endpoint country
* @param {string} query Query to search
* @param {string} limit limit to how many tracks are fetched
* @param {string} offset the offset to start fetching from
*/
async search_music(language, endpoint_country, query, limit, offset) {
const url = ShazamURLS.search_music(language, endpoint_country, query, limit, offset);
return await (await fetch(url, { headers: this.headers(language), method: 'GET' })).json();
}
/**
* Get number of times a track was shazamed
* @param {string} track Track ID
*/
async listen_count(track) {
const url = ShazamURLS.listening_counter(track);
return await (await fetch(url, { headers: this.headers(), method: 'GET' })).json();
}
}
Shazam.MAX_TIME_SCEONDS = 8;