@omelhorsite/sdk 0.12.2 → 0.15.1
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/dist/index.js
CHANGED
|
@@ -11076,6 +11076,12 @@ class AuthSessionsNamespace extends Resource {
|
|
|
11076
11076
|
async changeEmailStart(newEmail, options = {}) {
|
|
11077
11077
|
return this.http.post("/users/update_email_start", { email: newEmail }, options);
|
|
11078
11078
|
}
|
|
11079
|
+
async verifyEmailStart(options = {}) {
|
|
11080
|
+
return this.http.post("/users/verify_email_start", undefined, options);
|
|
11081
|
+
}
|
|
11082
|
+
async verifyEmailComplete(code, options = {}) {
|
|
11083
|
+
return this.http.post("/users/verify_email_end", { code }, options);
|
|
11084
|
+
}
|
|
11079
11085
|
async changeEmailComplete(input, options = {}) {
|
|
11080
11086
|
return this.http.post("/users/update_email_end", {
|
|
11081
11087
|
email: input.email,
|
|
@@ -13515,9 +13521,8 @@ var MUSIC_ASSISTANT_MAX_ACTIONS = 10;
|
|
|
13515
13521
|
var MUSIC_ASSISTANT_MAX_BODY_BYTES = 256 * 1024;
|
|
13516
13522
|
var MUSIC_ASSISTANT_READ_ONLY_AFTER_MS = 2 * 24 * 60 * 60 * 1000;
|
|
13517
13523
|
var MUSIC_ASSISTANT_TIMEOUT_MS = 90000;
|
|
13518
|
-
var MUSIC_DJ_HOURLY_CAP =
|
|
13524
|
+
var MUSIC_DJ_HOURLY_CAP = 90;
|
|
13519
13525
|
var MUSIC_DJ_TIMEOUT_MS = 120000;
|
|
13520
|
-
var MUSIC_DJ_BATCH_SIZE = 4;
|
|
13521
13526
|
|
|
13522
13527
|
class MusicJamsNamespace extends Resource {
|
|
13523
13528
|
async list(options = {}) {
|
|
@@ -13608,33 +13613,31 @@ class MusicAssistantNamespace extends Resource {
|
|
|
13608
13613
|
}
|
|
13609
13614
|
|
|
13610
13615
|
class MusicDjNamespace extends Resource {
|
|
13611
|
-
|
|
13612
|
-
const body = { next_song_id: input.nextSongId };
|
|
13613
|
-
if (input.previousSongId !== undefined && input.previousSongId !== null) {
|
|
13614
|
-
body["previous_song_id"] = input.previousSongId;
|
|
13615
|
-
}
|
|
13616
|
-
return this.http.post("/music_dj", body, {
|
|
13617
|
-
...options,
|
|
13618
|
-
timeoutMs: options.timeoutMs ?? MUSIC_DJ_TIMEOUT_MS,
|
|
13619
|
-
retry: options.retry ?? false
|
|
13620
|
-
});
|
|
13621
|
-
}
|
|
13622
|
-
batch(input = {}, options = {}) {
|
|
13616
|
+
next(input = {}, options = {}) {
|
|
13623
13617
|
const body = {};
|
|
13618
|
+
if (input.sessionId !== undefined)
|
|
13619
|
+
body["session_id"] = input.sessionId;
|
|
13624
13620
|
if (input.request !== undefined)
|
|
13625
13621
|
body["request"] = input.request;
|
|
13626
|
-
if (input.
|
|
13627
|
-
body["
|
|
13628
|
-
if (input.
|
|
13629
|
-
body["
|
|
13630
|
-
if (input.
|
|
13631
|
-
body["
|
|
13632
|
-
return this.http.post("/music_dj/
|
|
13622
|
+
if (input.skippedSongId !== undefined)
|
|
13623
|
+
body["skipped_song_id"] = input.skippedSongId;
|
|
13624
|
+
if (input.speak !== undefined)
|
|
13625
|
+
body["speak"] = input.speak;
|
|
13626
|
+
if (input.restart !== undefined)
|
|
13627
|
+
body["restart"] = input.restart;
|
|
13628
|
+
return this.http.post("/music_dj/next", body, {
|
|
13633
13629
|
...options,
|
|
13634
13630
|
timeoutMs: options.timeoutMs ?? MUSIC_DJ_TIMEOUT_MS,
|
|
13635
13631
|
retry: options.retry ?? false
|
|
13636
13632
|
});
|
|
13637
13633
|
}
|
|
13634
|
+
async session(options = {}) {
|
|
13635
|
+
const body = await this.http.get("/music_dj/session", options);
|
|
13636
|
+
return body.session ?? null;
|
|
13637
|
+
}
|
|
13638
|
+
async end(options = {}) {
|
|
13639
|
+
await this.http.delete("/music_dj/session", options);
|
|
13640
|
+
}
|
|
13638
13641
|
}
|
|
13639
13642
|
|
|
13640
13643
|
class MusicSocialNamespace extends Resource {
|
|
@@ -13737,7 +13740,8 @@ var SONG_FILTER_COLUMNS = Object.freeze([
|
|
|
13737
13740
|
"album",
|
|
13738
13741
|
"position",
|
|
13739
13742
|
"year",
|
|
13740
|
-
"artist"
|
|
13743
|
+
"artist",
|
|
13744
|
+
"language"
|
|
13741
13745
|
]);
|
|
13742
13746
|
var SONG_ORDER_COLUMNS = Object.freeze([
|
|
13743
13747
|
"id",
|
|
@@ -13868,6 +13872,13 @@ class MusicSongsNamespace extends Resource {
|
|
|
13868
13872
|
async deleteSeparation(id, options = {}) {
|
|
13869
13873
|
await this.http.delete(`/songs/${encodeURIComponent(String(id))}/separation`, options);
|
|
13870
13874
|
}
|
|
13875
|
+
async matchCandidates(id, params = {}, options = {}) {
|
|
13876
|
+
const body = await this.http.get(`/songs/${encodeURIComponent(String(id))}/match_candidates`, { ...options, query: { limit: params.limit } });
|
|
13877
|
+
return body.items ?? [];
|
|
13878
|
+
}
|
|
13879
|
+
async rematch(id, input = {}, options = {}) {
|
|
13880
|
+
return this.http.post(`/songs/${encodeURIComponent(String(id))}/rematch`, input.sourceUrl === undefined ? {} : { source_url: input.sourceUrl }, options);
|
|
13881
|
+
}
|
|
13871
13882
|
async listLiked(params = {}, options = {}) {
|
|
13872
13883
|
const before = params.before instanceof Date ? params.before.toISOString() : params.before;
|
|
13873
13884
|
const rows = await this.http.get("/liked_songs", {
|
|
@@ -13925,7 +13936,13 @@ class MusicSongsNamespace extends Resource {
|
|
|
13925
13936
|
return listQuery(params, at, {
|
|
13926
13937
|
order: at === undefined ? undefined : "created_at:asc",
|
|
13927
13938
|
search: { title: params.title },
|
|
13928
|
-
exactSearch: {
|
|
13939
|
+
exactSearch: {
|
|
13940
|
+
album: params.album,
|
|
13941
|
+
year: params.year,
|
|
13942
|
+
id: params.ids,
|
|
13943
|
+
artist: params.artist,
|
|
13944
|
+
language: params.language
|
|
13945
|
+
},
|
|
13929
13946
|
top: params.artistRole === undefined ? {} : { artist_role: params.artistRole }
|
|
13930
13947
|
});
|
|
13931
13948
|
}
|
|
@@ -16270,7 +16287,6 @@ export {
|
|
|
16270
16287
|
MUSIC_PRESIGNED_URL_TTL_MS,
|
|
16271
16288
|
MUSIC_DJ_TIMEOUT_MS,
|
|
16272
16289
|
MUSIC_DJ_HOURLY_CAP,
|
|
16273
|
-
MUSIC_DJ_BATCH_SIZE,
|
|
16274
16290
|
MUSIC_ASSISTANT_TIMEOUT_MS,
|
|
16275
16291
|
MUSIC_ASSISTANT_READ_ONLY_AFTER_MS,
|
|
16276
16292
|
MUSIC_ASSISTANT_MAX_BODY_BYTES,
|
|
@@ -30,6 +30,13 @@ export interface User extends BaseRecord {
|
|
|
30
30
|
readonly country_code?: string | null;
|
|
31
31
|
/** `en`, `pt` or `lv`: the language of the emails sent to this account. Own account only. */
|
|
32
32
|
readonly language?: string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Whether the address on the account was proven by an emailed code: the same
|
|
35
|
+
* fact the id token's `email_verified` carries. Own account only. `false`
|
|
36
|
+
* on accounts older than the emailed sign-up code until they confirm once
|
|
37
|
+
* with `sessions.verifyEmailStart` / `verifyEmailComplete`.
|
|
38
|
+
*/
|
|
39
|
+
readonly email_verified?: boolean;
|
|
33
40
|
readonly email_is_public?: boolean;
|
|
34
41
|
readonly gender_is_public?: boolean;
|
|
35
42
|
readonly library_public?: boolean;
|
|
@@ -692,6 +692,26 @@ export declare class AuthSessionsNamespace extends Resource {
|
|
|
692
692
|
* rejected at write time - already taken, malformed. Both codes are
|
|
693
693
|
* consumed by then and the flow restarts at {@link changeEmailStart}.
|
|
694
694
|
*/
|
|
695
|
+
/**
|
|
696
|
+
* `POST /users/verify_email_start` - emails a code to the address already on
|
|
697
|
+
* the account, so an account that never proved its mailbox (one older than
|
|
698
|
+
* the emailed sign-up code) can. Requires a live session; an account that is
|
|
699
|
+
* already verified answers `200` without sending anything.
|
|
700
|
+
*
|
|
701
|
+
* Same shared `*_start` throttle as the other code-issuing calls.
|
|
702
|
+
*
|
|
703
|
+
* @throws {OmsAuthError} 401 without a live session.
|
|
704
|
+
*/
|
|
705
|
+
verifyEmailStart(options?: RequestOptions): Promise<string>;
|
|
706
|
+
/**
|
|
707
|
+
* `POST /users/verify_email_end` - presents the code; answers `200` with the
|
|
708
|
+
* updated {@link User}, `email_verified` now `true`.
|
|
709
|
+
*
|
|
710
|
+
* @throws {OmsAuthError} 401 without a live session.
|
|
711
|
+
* @throws {OmsApiError} 404 `"Invalid Verification"` when the code is wrong,
|
|
712
|
+
* expired or burned.
|
|
713
|
+
*/
|
|
714
|
+
verifyEmailComplete(code: string, options?: RequestOptions): Promise<User>;
|
|
695
715
|
changeEmailComplete(input: ChangeEmailInput, options?: RequestOptions): Promise<User>;
|
|
696
716
|
/**
|
|
697
717
|
* `POST /users/destroy_start` - emails a deletion code to the address on the
|
|
@@ -501,57 +501,99 @@ export interface SendMusicAssistantMessageInput {
|
|
|
501
501
|
readonly player?: MusicAssistantPlayerContext;
|
|
502
502
|
}
|
|
503
503
|
/**
|
|
504
|
-
*
|
|
504
|
+
* Turns per user per hour on `POST /music_dj/next`.
|
|
505
505
|
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
*
|
|
509
|
-
*
|
|
506
|
+
* A turn is one song, so an hour of listening spends about 17 of them and the
|
|
507
|
+
* rest of the budget absorbs skipping. The counter is incremented by EVERY
|
|
508
|
+
* request - including the ones it then refuses with `429` - so a retry loop
|
|
509
|
+
* drives the count further past the cap and can never recover inside the hour,
|
|
510
|
+
* which is why {@link MusicDjNamespace} passes `retry: false` unless you
|
|
511
|
+
* override it.
|
|
510
512
|
*/
|
|
511
|
-
export declare const MUSIC_DJ_HOURLY_CAP =
|
|
513
|
+
export declare const MUSIC_DJ_HOURLY_CAP = 90;
|
|
512
514
|
/**
|
|
513
|
-
* Default deadline for one DJ
|
|
514
|
-
*
|
|
515
|
+
* Default deadline for one DJ turn, in milliseconds. Choosing the song and
|
|
516
|
+
* speaking it takes well past the client's 60 s default.
|
|
515
517
|
*/
|
|
516
518
|
export declare const MUSIC_DJ_TIMEOUT_MS = 120000;
|
|
517
|
-
/**
|
|
518
|
-
export
|
|
519
|
-
/** `
|
|
520
|
-
|
|
521
|
-
/** The script as text, at most 320 characters. Worth showing while audio loads. */
|
|
522
|
-
readonly text: string;
|
|
519
|
+
/** One entry in a DJ session's transcript. */
|
|
520
|
+
export interface MusicDjTurn {
|
|
521
|
+
/** `"dj"` when he played or spoke, `"listener"` when they asked for something. */
|
|
522
|
+
readonly role: "dj" | "listener";
|
|
523
523
|
/**
|
|
524
|
-
*
|
|
525
|
-
*
|
|
524
|
+
* What was said. `null` on a DJ turn where he stayed quiet - he speaks on
|
|
525
|
+
* the first song, whenever the listener asks for something, and every third
|
|
526
|
+
* song after that, so most turns carry a song and no words.
|
|
526
527
|
*/
|
|
527
|
-
readonly
|
|
528
|
-
/**
|
|
529
|
-
readonly
|
|
528
|
+
readonly text: string | null;
|
|
529
|
+
/** The song that turn played, when it played one. */
|
|
530
|
+
readonly song_id: number | null;
|
|
531
|
+
readonly created_at: Timestamp;
|
|
530
532
|
}
|
|
531
|
-
/** `
|
|
532
|
-
export interface
|
|
533
|
+
/** `GET /music_dj/session`: the session still on air, if there is one. */
|
|
534
|
+
export interface MusicDjSession {
|
|
535
|
+
readonly id: number;
|
|
536
|
+
/** The listener's standing request. It holds until they ask for something else. */
|
|
537
|
+
readonly request: string | null;
|
|
538
|
+
readonly turns: MusicDjTurn[];
|
|
533
539
|
/**
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
* {@link
|
|
537
|
-
* empty set, so this is never `[]`.
|
|
540
|
+
* Every song the transcript refers to, in the full `GET /songs` shape, so a
|
|
541
|
+
* client can rebuild the session without a lookup per turn. Unordered: match
|
|
542
|
+
* them to {@link MusicDjTurn.song_id}.
|
|
538
543
|
*/
|
|
539
544
|
readonly songs: Song[];
|
|
540
545
|
}
|
|
541
|
-
/**
|
|
542
|
-
export interface
|
|
543
|
-
|
|
546
|
+
/** `POST /music_dj/next`: the next song, and the words for it when there are any. */
|
|
547
|
+
export interface MusicDjNext {
|
|
548
|
+
readonly session_id: number;
|
|
549
|
+
/** What to play now. Never one the session already played. */
|
|
550
|
+
readonly song: Song;
|
|
551
|
+
/** Two to five words naming this stretch of the show, for a screen. */
|
|
552
|
+
readonly theme: string | null;
|
|
553
|
+
/**
|
|
554
|
+
* The spoken link, present ONLY on the turns where he speaks. Its absence is
|
|
555
|
+
* the normal case, not an error: play the song straight away.
|
|
556
|
+
*/
|
|
557
|
+
readonly text?: string;
|
|
558
|
+
/**
|
|
559
|
+
* The same words spoken, base64, NOT a data URL. Decode with
|
|
560
|
+
* {@link musicDjAudioBytes} or wrap with {@link musicDjAudioDataUrl}.
|
|
561
|
+
*
|
|
562
|
+
* Absent when he stayed quiet AND when the voice service failed while he had
|
|
563
|
+
* something to say - in that second case `text` is there without it, and the
|
|
564
|
+
* song still plays. Never block playback on this field.
|
|
565
|
+
*/
|
|
566
|
+
readonly audio_base64?: string;
|
|
567
|
+
/** Container of the decoded bytes. `"wav"` today, and typed wide on purpose. */
|
|
568
|
+
readonly format?: string;
|
|
569
|
+
}
|
|
570
|
+
/** Everything `POST /music_dj/next` accepts. All of it optional. */
|
|
571
|
+
export interface MusicDjNextInput {
|
|
572
|
+
/**
|
|
573
|
+
* Continue this session. Omit to continue the caller's own session, which
|
|
574
|
+
* the server finds by itself - a session goes stale after two hours of
|
|
575
|
+
* silence and the next turn starts a fresh one.
|
|
576
|
+
*/
|
|
577
|
+
readonly sessionId?: number;
|
|
578
|
+
/**
|
|
579
|
+
* A steer ("something calmer", "fados"), truncated to 300 characters.
|
|
580
|
+
*
|
|
581
|
+
* STANDING ORDERS, not a one-off: it holds for every following turn until
|
|
582
|
+
* another request replaces it, and it makes him speak on this turn even if
|
|
583
|
+
* it was not his turn to.
|
|
584
|
+
*/
|
|
544
585
|
readonly request?: string;
|
|
586
|
+
/** The song the listener just skipped, as a direction to leave. */
|
|
587
|
+
readonly skippedSongId?: SongId;
|
|
545
588
|
/**
|
|
546
|
-
*
|
|
547
|
-
*
|
|
548
|
-
*
|
|
589
|
+
* Make him speak on this turn even if it was not his turn to.
|
|
590
|
+
*
|
|
591
|
+
* This is the DJ button: the listener wants to hear him now. It changes
|
|
592
|
+
* nothing about which song comes next.
|
|
549
593
|
*/
|
|
550
|
-
readonly
|
|
551
|
-
/**
|
|
552
|
-
readonly
|
|
553
|
-
/** Which set of the session this is, so the script can vary its opening. */
|
|
554
|
-
readonly batchIndex?: number;
|
|
594
|
+
readonly speak?: boolean;
|
|
595
|
+
/** Start a new session even though one is still on air. */
|
|
596
|
+
readonly restart?: boolean;
|
|
555
597
|
}
|
|
556
598
|
/**
|
|
557
599
|
* Jams over HTTP. The realtime half lives on the WebSocket stream and is not in
|
|
@@ -887,12 +929,16 @@ export declare class MusicAssistantNamespace extends Resource {
|
|
|
887
929
|
ask(messages: MusicAssistantMessage[], player?: MusicAssistantPlayerContext, options?: RequestOptions): Promise<MusicAssistantAnswer>;
|
|
888
930
|
}
|
|
889
931
|
/**
|
|
890
|
-
* "O Melhor DJ": a
|
|
891
|
-
*
|
|
932
|
+
* "O Melhor DJ": a radio session for one listener, one song at a time.
|
|
933
|
+
*
|
|
934
|
+
* The server holds the session - what played, what was skipped, what he said,
|
|
935
|
+
* what the listener asked for - so a client only ever says what just happened
|
|
936
|
+
* and gets the next song back. Ask for one when the current song ends, when
|
|
937
|
+
* the listener skips, or when they want something else.
|
|
892
938
|
*
|
|
893
|
-
*
|
|
939
|
+
* {@link next} passes `retry: false` by default. That is not caution about
|
|
894
940
|
* duplicates - the transport does not replay a `POST` anyway - it is about the
|
|
895
|
-
* one thing it DOES replay: a `429`. The hourly cap
|
|
941
|
+
* one thing it DOES replay: a `429`. The hourly cap counts every request
|
|
896
942
|
* including the refused ones, so waiting out a `Retry-After` and asking again
|
|
897
943
|
* pushes the count further past the cap and cannot succeed inside the hour.
|
|
898
944
|
* Pass `retry: {}` to opt back in if you are sure the `429` came from the
|
|
@@ -900,57 +946,46 @@ export declare class MusicAssistantNamespace extends Resource {
|
|
|
900
946
|
*/
|
|
901
947
|
export declare class MusicDjNamespace extends Resource {
|
|
902
948
|
/**
|
|
903
|
-
* `POST /music_dj` - the
|
|
949
|
+
* `POST /music_dj/next` - the next song, chosen with the whole session in
|
|
950
|
+
* view.
|
|
904
951
|
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
*
|
|
952
|
+
* `next({})` is a valid cold start: with no session on air the server opens
|
|
953
|
+
* one. The answer always carries a `song`; `text` and `audio_base64` come
|
|
954
|
+
* only on the turns where he speaks (the first song, after a request, and
|
|
955
|
+
* every third song), so treat their absence as normal and play the song.
|
|
909
956
|
*
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
* is what lets the script say goodbye to the outgoing track.
|
|
957
|
+
* A song the session already played is never returned, which is what lets a
|
|
958
|
+
* client keep the whole session in its queue and walk back through it.
|
|
913
959
|
*
|
|
914
|
-
*
|
|
915
|
-
* {@link MUSIC_DJ_TIMEOUT_MS}.
|
|
916
|
-
*
|
|
917
|
-
* playing and drop it on the boundary, ideally over the outgoing
|
|
918
|
-
* instrumental.
|
|
960
|
+
* Choosing takes seconds and speaking takes more; the deadline defaults to
|
|
961
|
+
* {@link MUSIC_DJ_TIMEOUT_MS}. Ask for the next song while the current one
|
|
962
|
+
* is still playing, or the listener hears the gap.
|
|
919
963
|
*
|
|
920
964
|
* @throws {OmsError} `401 "Session required"` when unauthenticated.
|
|
921
|
-
* @throws {OmsError} `404 "Song not found"` for either id.
|
|
922
965
|
* @throws {OmsError} `429 "DJ limit reached, try again later"` past
|
|
923
966
|
* {@link MUSIC_DJ_HOURLY_CAP}.
|
|
924
|
-
* @throws {OmsError} `502 "DJ is unavailable right now"` when
|
|
925
|
-
*
|
|
926
|
-
*
|
|
927
|
-
* say them.
|
|
967
|
+
* @throws {OmsError} `502 "DJ is unavailable right now"` when no song could
|
|
968
|
+
* be chosen - including the honest case where the library has nothing left
|
|
969
|
+
* that the session has not already played.
|
|
928
970
|
*/
|
|
929
|
-
|
|
930
|
-
readonly nextSongId: SongId;
|
|
931
|
-
readonly previousSongId?: SongId | null;
|
|
932
|
-
}, options?: RequestOptions): Promise<MusicDjInterstitial>;
|
|
971
|
+
next(input?: MusicDjNextInput, options?: RequestOptions): Promise<MusicDjNext>;
|
|
933
972
|
/**
|
|
934
|
-
* `
|
|
935
|
-
* introducing it, from one model call.
|
|
973
|
+
* `GET /music_dj/session` - the session still on air, or `null`.
|
|
936
974
|
*
|
|
937
|
-
*
|
|
938
|
-
*
|
|
939
|
-
*
|
|
940
|
-
*
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
*
|
|
945
|
-
* the last 60 are read anyway.
|
|
946
|
-
*
|
|
947
|
-
* `songs` comes back in play order, in the full `GET /songs` shape, already
|
|
948
|
-
* scoped to what the caller may play. It is never empty.
|
|
975
|
+
* `null` is the ordinary answer for someone who has not listened today: a
|
|
976
|
+
* session goes stale after two hours of silence and stops being served here.
|
|
977
|
+
* Use it to rebuild the transcript when a screen opens, not to decide
|
|
978
|
+
* whether {@link next} may be called - it always may.
|
|
979
|
+
*/
|
|
980
|
+
session(options?: RequestOptions): Promise<MusicDjSession | null>;
|
|
981
|
+
/**
|
|
982
|
+
* `DELETE /music_dj/session` - end the session on air.
|
|
949
983
|
*
|
|
950
|
-
*
|
|
951
|
-
*
|
|
984
|
+
* Idempotent: with nothing on air it still answers `204`. The next
|
|
985
|
+
* {@link next} starts a session with no memory of this one, which is the
|
|
986
|
+
* point - it is how a listener says "forget what I asked for".
|
|
952
987
|
*/
|
|
953
|
-
|
|
988
|
+
end(options?: RequestOptions): Promise<void>;
|
|
954
989
|
}
|
|
955
990
|
/**
|
|
956
991
|
* The `music.social` entry point, holding the five families as sub-namespaces.
|
|
@@ -1045,7 +1080,9 @@ export declare function musicStorageAffords(usage: MusicStorageUsage, bytes: num
|
|
|
1045
1080
|
* slightly short audio is a better failure than one that throws inside a
|
|
1046
1081
|
* playback callback.
|
|
1047
1082
|
*/
|
|
1048
|
-
export declare function musicDjAudioBytes(clip:
|
|
1083
|
+
export declare function musicDjAudioBytes(clip: {
|
|
1084
|
+
readonly audio_base64: string;
|
|
1085
|
+
}): Uint8Array;
|
|
1049
1086
|
/**
|
|
1050
1087
|
* Wraps a DJ clip as a `data:` URI, for a player that takes a URI rather than
|
|
1051
1088
|
* bytes - which is most of them.
|
|
@@ -1054,4 +1091,7 @@ export declare function musicDjAudioBytes(clip: Pick<MusicDjInterstitial, "audio
|
|
|
1054
1091
|
* in memory, so this is cheap in every sense that matters at this size. It is
|
|
1055
1092
|
* not a URL anything can fetch twice - it is the bytes, spelled differently.
|
|
1056
1093
|
*/
|
|
1057
|
-
export declare function musicDjAudioDataUrl(clip:
|
|
1094
|
+
export declare function musicDjAudioDataUrl(clip: {
|
|
1095
|
+
readonly audio_base64: string;
|
|
1096
|
+
readonly format?: string;
|
|
1097
|
+
}): string;
|
|
@@ -60,6 +60,7 @@ import { Resource } from "../../http";
|
|
|
60
60
|
import type { ListParams } from "../../listing";
|
|
61
61
|
import { type BaseRecord, type FileInput, type FileOutput, type Id, type NativeFile, type Paginated, type RequestOptions, type Timestamp } from "../../types";
|
|
62
62
|
import type { VocalSeparation } from "../tools/vocalSeparation";
|
|
63
|
+
import type { SongImport } from "./imports";
|
|
63
64
|
/**
|
|
64
65
|
* Primary key of a song. An **integer**, unlike most ids in this API.
|
|
65
66
|
*
|
|
@@ -113,7 +114,7 @@ export interface SongArtistCredit extends Omit<BaseRecord, "id"> {
|
|
|
113
114
|
readonly compressed_image_fs_node_id?: Id | null;
|
|
114
115
|
}
|
|
115
116
|
/** Where a track came from. */
|
|
116
|
-
export type SongSourceKind = "upload" | "yt_dlp" | "spotify_sync";
|
|
117
|
+
export type SongSourceKind = "upload" | "yt_dlp" | "spotify_sync" | "rematch";
|
|
117
118
|
/**
|
|
118
119
|
* A track in the library.
|
|
119
120
|
*
|
|
@@ -142,6 +143,35 @@ export interface Song extends Omit<BaseRecord, "id"> {
|
|
|
142
143
|
readonly source_id: string | null;
|
|
143
144
|
/** Recording identifier, when the importer resolved one. */
|
|
144
145
|
readonly isrc: string | null;
|
|
146
|
+
/**
|
|
147
|
+
* ISO 639-1 code of the language SUNG on the track (`"es"`, `"pt"`, `"ja"`),
|
|
148
|
+
* or `null`.
|
|
149
|
+
*
|
|
150
|
+
* Derived, not declared: the server runs a detector over the stored lyrics,
|
|
151
|
+
* and falls back to unambiguous Last.fm tags (`"j-pop"`, `"fado"`) when
|
|
152
|
+
* there are none. So `null` means "not known yet" - a song whose lyrics were
|
|
153
|
+
* never fetched, or an instrumental - and NEVER "no language". Treat it as
|
|
154
|
+
* unknown rather than quietly dropping it from a language view.
|
|
155
|
+
*/
|
|
156
|
+
readonly language: string | null;
|
|
157
|
+
/**
|
|
158
|
+
* Lowercased Last.fm tags for the track, most-voted first, at most six.
|
|
159
|
+
*
|
|
160
|
+
* Crowd tags, not a controlled vocabulary: expect `"rock"` and
|
|
161
|
+
* `"female vocalists"` in the same list, and expect `[]` for anything
|
|
162
|
+
* obscure. The server drops the junk ones ("seen live", "albums i own") and
|
|
163
|
+
* falls back to the artist's tags when the track has none of its own, which
|
|
164
|
+
* is why two songs by one artist often carry identical lists.
|
|
165
|
+
*/
|
|
166
|
+
readonly tags: string[];
|
|
167
|
+
/**
|
|
168
|
+
* Tempo in beats per minute, or `null` when it is not known.
|
|
169
|
+
*
|
|
170
|
+
* Comes from the recording's own registry entry, matched by ISRC, so a
|
|
171
|
+
* track with no ISRC never gets one and a remix is never given the
|
|
172
|
+
* original's tempo. `null` is common and means nothing about the music.
|
|
173
|
+
*/
|
|
174
|
+
readonly bpm: number | null;
|
|
145
175
|
readonly original_filename: string | null;
|
|
146
176
|
readonly audio_codec: string | null;
|
|
147
177
|
readonly audio_bitrate_kbps: number | null;
|
|
@@ -254,6 +284,48 @@ export interface SongSeparationStatus {
|
|
|
254
284
|
*/
|
|
255
285
|
readonly job: VocalSeparation | null;
|
|
256
286
|
}
|
|
287
|
+
/** Providers a track can be matched against. */
|
|
288
|
+
export type SongMatchSource = "youtube" | "soundcloud" | "bandcamp";
|
|
289
|
+
/**
|
|
290
|
+
* One take the matcher found for a track, scored the way the import scores it.
|
|
291
|
+
*
|
|
292
|
+
* A candidate with `score: null` was REJECTED and `reject_reason` says why
|
|
293
|
+
* ("title says 'cover', query did not"); it is returned anyway, because the
|
|
294
|
+
* reason is often the whole answer to "why did I get this recording". Rejected
|
|
295
|
+
* candidates can still be passed to {@link MusicSongsNamespace.rematch}: a
|
|
296
|
+
* deliberate choice is not second-guessed.
|
|
297
|
+
*/
|
|
298
|
+
export interface SongMatchCandidate {
|
|
299
|
+
readonly source: SongMatchSource;
|
|
300
|
+
readonly title: string | null;
|
|
301
|
+
readonly uploader: string | null;
|
|
302
|
+
/** The page to import from. Feed it back as `sourceUrl` to take this one. */
|
|
303
|
+
readonly url: string | null;
|
|
304
|
+
/** Absent for providers whose search does not carry a runtime. */
|
|
305
|
+
readonly duration_s: number | null;
|
|
306
|
+
readonly thumbnail: string | null;
|
|
307
|
+
readonly source_id: string | null;
|
|
308
|
+
/** Higher is a better match; `null` means rejected. */
|
|
309
|
+
readonly score: number | null;
|
|
310
|
+
readonly reject_reason: string | null;
|
|
311
|
+
/** Where the provider's own search ranked it, before scoring. */
|
|
312
|
+
readonly rank: number;
|
|
313
|
+
/** True for the source the song's audio already came from. */
|
|
314
|
+
readonly current: boolean;
|
|
315
|
+
}
|
|
316
|
+
/** Arguments for {@link MusicSongsNamespace.matchCandidates}. */
|
|
317
|
+
export interface ListSongMatchCandidatesParams {
|
|
318
|
+
/** Candidates per provider. Clamped to 1..20; defaults to 8. */
|
|
319
|
+
readonly limit?: number;
|
|
320
|
+
}
|
|
321
|
+
/** Arguments for {@link MusicSongsNamespace.rematch}. */
|
|
322
|
+
export interface RematchSongInput {
|
|
323
|
+
/**
|
|
324
|
+
* The page to download from, normally a {@link SongMatchCandidate.url}.
|
|
325
|
+
* Omit to re-run the matcher over the track's original search terms.
|
|
326
|
+
*/
|
|
327
|
+
readonly sourceUrl?: string;
|
|
328
|
+
}
|
|
257
329
|
/** A liked track. The join row, with the whole song inlined. */
|
|
258
330
|
export interface LikedSong extends Omit<BaseRecord, "id"> {
|
|
259
331
|
/** Integer primary key of the like itself. NOT the song id. */
|
|
@@ -402,7 +474,7 @@ export interface MusicExternalSearchResult {
|
|
|
402
474
|
* `artist` is on the list but is not a column - see
|
|
403
475
|
* {@link ListSongsParams.artist}.
|
|
404
476
|
*/
|
|
405
|
-
export declare const SONG_FILTER_COLUMNS: readonly ["id", "created_at", "updated_at", "title", "album", "position", "year", "artist"];
|
|
477
|
+
export declare const SONG_FILTER_COLUMNS: readonly ["id", "created_at", "updated_at", "title", "album", "position", "year", "artist", "language"];
|
|
406
478
|
/** Columns the backend will accept in `modifiers[order]`. */
|
|
407
479
|
export declare const SONG_ORDER_COLUMNS: readonly string[];
|
|
408
480
|
/** Filters shared by `GET /songs` and `GET /songs/albums`. */
|
|
@@ -460,6 +532,15 @@ export interface SongFilters {
|
|
|
460
532
|
* excluded even if they also appear as a feature.
|
|
461
533
|
*/
|
|
462
534
|
readonly artistRole?: SongArtistRole;
|
|
535
|
+
/**
|
|
536
|
+
* Exact language code, or a list of them, sent as `exact_search[language]`.
|
|
537
|
+
*
|
|
538
|
+
* Matches {@link Song.language}, with the same caveat: rows the server could
|
|
539
|
+
* not classify hold `null` and are NOT returned by any code you ask for.
|
|
540
|
+
* Pass `null` to get exactly those - the unclassified pile - which is a
|
|
541
|
+
* different question from "songs with no words".
|
|
542
|
+
*/
|
|
543
|
+
readonly language?: string | string[] | null;
|
|
463
544
|
}
|
|
464
545
|
/** Arguments for {@link MusicSongsNamespace.list}. */
|
|
465
546
|
export interface ListSongsParams extends SongFilters, ListParams<(typeof SONG_FILTER_COLUMNS)[number]> {
|
|
@@ -901,6 +982,49 @@ export declare class MusicSongsNamespace extends Resource {
|
|
|
901
982
|
* @throws {OmsAuthError} 401 when the song is not yours.
|
|
902
983
|
*/
|
|
903
984
|
deleteSeparation(id: SongId, options?: RequestOptions): Promise<void>;
|
|
985
|
+
/**
|
|
986
|
+
* `GET /songs/:id/match_candidates` - every take the matcher can find for
|
|
987
|
+
* this track, across all three providers, scored and ordered best first.
|
|
988
|
+
*
|
|
989
|
+
* This is the answer to "this is the wrong recording". A track imported by
|
|
990
|
+
* artist and title is matched, not looked up, and the classic wrong result is
|
|
991
|
+
* a cover: same runtime, the original artist's name in its title, and nothing
|
|
992
|
+
* about it that a duration check can catch. The pool shows what the matcher
|
|
993
|
+
* chose between, with the rejects kept and labelled.
|
|
994
|
+
*
|
|
995
|
+
* Candidates are searched live on three providers, so this is SLOW - seconds,
|
|
996
|
+
* not milliseconds - and worth its own timeout. Nothing is cached: two calls
|
|
997
|
+
* a minute apart can return different pools.
|
|
998
|
+
*
|
|
999
|
+
* Scores are comparable within one response and meaningless across two.
|
|
1000
|
+
*
|
|
1001
|
+
* @throws {OmsApiError} 404 for a song that is not yours, 502 when the
|
|
1002
|
+
* downloader is unreachable.
|
|
1003
|
+
*/
|
|
1004
|
+
matchCandidates(id: SongId, params?: ListSongMatchCandidatesParams, options?: RequestOptions): Promise<SongMatchCandidate[]>;
|
|
1005
|
+
/**
|
|
1006
|
+
* `POST /songs/:id/rematch` - re-download this track onto the same record.
|
|
1007
|
+
*
|
|
1008
|
+
* The song row survives: its id, its credits, and every playlist entry, like
|
|
1009
|
+
* and play event that points at it. Only the audio is replaced, along with
|
|
1010
|
+
* the fields that describe the file (runtime, codec, source). Any vocal
|
|
1011
|
+
* separation is thrown away, because the stems came out of audio the song no
|
|
1012
|
+
* longer has.
|
|
1013
|
+
*
|
|
1014
|
+
* With a `sourceUrl` the pick is taken as given - no duration or cover check
|
|
1015
|
+
* stands between you and the recording you named. Without one the matcher
|
|
1016
|
+
* re-runs over the track's original search terms, which is the useful move
|
|
1017
|
+
* after a bad match rather than a repeat of it.
|
|
1018
|
+
*
|
|
1019
|
+
* Returns the import to poll through `music.imports`; the swap lands when it
|
|
1020
|
+
* reaches `complete`.
|
|
1021
|
+
*
|
|
1022
|
+
* @throws {OmsAuthError} 401 when the song is not yours.
|
|
1023
|
+
* @throws {OmsApiError} 400 for a `sourceUrl` that is not http(s), or when no
|
|
1024
|
+
* `sourceUrl` is given and the track has no search terms to re-run (an
|
|
1025
|
+
* uploaded file, for one - there is nothing to match it against).
|
|
1026
|
+
*/
|
|
1027
|
+
rematch(id: SongId, input?: RematchSongInput, options?: RequestOptions): Promise<SongImport>;
|
|
904
1028
|
/**
|
|
905
1029
|
* `GET /liked_songs` - the caller's likes, newest first, with each song
|
|
906
1030
|
* inlined in full.
|
package/package.json
CHANGED