Skip to content

Version 2.4.0

Latest

Choose a tag to compare

@Lioncat6 Lioncat6 released this 25 Sep 17:05
· 4 commits to main since this release
6936052

Changes & Improvements

  • Type Refactor
  • API Refactor
  • New Provider: Subvert
  • Fix Bandcamp, Tidal Closes #206
  • Providers page
    image
  • SAMBL Seeder
    image
  • Find page improvements
    • Added parallelization for UPC and ISRC lookups
    • Should be significantly faster
  • Server and Client API Handler
  • Package updates
  • Tons of optimizations, tweaks, and refactors, and bug fixes Closes #205 #150
  • Customizable edit server
    image
  • Add environment variables
    MUSICBRAINZ_DISABLE_RATE_LIMIT=<1 or 0> [optional]
    MUSICBRAINZ_BASE_URL=https://musicbrainz.org [optional]
    BANDCAMP_COOKIE=<string> [optional]
    

Types

//api-types.ts
export type DeepSearchMethod = "most_common" | "name_similarity"
export class DeepSearchData {
    provider: ProviderNamespace
    mbArtists: DeepSearchArtist[]
    albums: AlbumStack[]
    sourceArtist: ArtistObject
}

export class DeepSearchArtist extends PartialArtistObject {
    nameSimilarity: number
    occurrences: number | null
    mostCommonMBID: boolean
}

export class DeepSearchEditData extends DeepSearchData {
    method: DeepSearchMethod
    mbArtist: DeepSearchArtist
}

export class ArtistData {
    providerData: ArtistObject
    mbData?: ArtistObject | null
}

export class ArtistLookupData {
    mbid?: string | null
    provider: ProviderNamespace
    provider_id: string
}

export class URLLookupData {
    albums: ExtendedAlbumObject[] | AlbumObject[]
    tracks: ExtendedTrackObject[]
    artists: ArtistObject[]
    query: string
}

export type ArtistSearchData = Record<string, AggregatedArtist>

export class SAMBLApiError {
    error: string
    code?: number
    details?: string | null
    parameters?: string[]
    request?: string
    url?: string
    provider?: ProviderNamespace
}

export class FindData {
    type: 'UPC' | 'ISRC'
    data: AlbumObject[] | TrackObject[] | ArtistObject[]
    issues: SAMBLApiError[]
}

export class ISRCData {
    isrcs: string[]
}

export class UPCData {
    upcs: string[]
}

export class LangData {
    script: ReleaseScript | null
    language: ReleaseLanguage | null
}

export class ReleaseCountData {
    releaseCount: number
    ownCount: number
    featuredCount?: number | null
}

export interface APITimingStage { name: string, duration: number, provider?: ProviderNamespace, subStages?: APITimingStage[] }

export class APITimingData {
    totalDuration: number;
    stages: APITimingStage[]
}

export class SAMBLAPIResponse<T> {
    error?: SAMBLApiError
    data?: T
    timings: APITimingData
}

export type SingleAlbumData = AlbumStack;

export type PingPongData = "Pong";

export type SAMBLAPIData =
    UPCData |
    ISRCData |
    FindData |
    LangData |
    SingleAlbumData |
    RawAggregateData |
    AggregatedData |
    DeepSearchData |
    AlbumData |
    ArtistData |
    ReleaseCountData |
    ExtendedAlbumData |
    ArtistLookupData |
    URLLookupData |
    PingPongData |
    ArtistSearchData
//provider-types.ts
export type ProviderNamespace = FullProviderNamespace | "musixmatch"

export type FullProviderNamespace = "spotify" | "tidal" | "deezer" | "musicbrainz" | "soundcloud" | "bandcamp" | "applemusic" | "naver" | "qobuz" | "discogs" | "volumo" | "subvert";

export type ObjectType = "partialArtist" | "artist" | "album" | "track" | "label"

/** Incomplete Format List */
export type MediumFormat = "Digital Media" | "CD" | "Vinyl" | "Cassette"

export class GenericObject {
    provider: ProviderNamespace;
    id: string | null;
    name: string;
    url: ExternalUrlData | null;
    type: ObjectType
    mbid?: string | null;
}

export class ImageObject extends GenericObject {
    imageUrl: string | null;
    imageUrlSmall: string | null;
}


export class PartialArtistObject extends ImageObject {
    name: string
    url: ExternalUrlData;
    id: string;
    provider: ProviderNamespace;
    type: "partialArtist" | "artist";
};

export class ArtistObject extends PartialArtistObject {
    bannerUrl: string | null;
    relevance: string;
    info: string;
    genres: string[] | null;
    followers: number | null;
    popularity: number | null;
    type: "artist";
};

export class ExtendedArtistObject extends ArtistObject {
    aliases: string[] | null;
}

export class AlbumObject extends ImageObject {
    provider: ProviderNamespace;
    id: string;
    name: string;
    url: ExternalUrlData;
    albumArtists: PartialArtistObject[];
    artistNames: string[];
    releaseDate: string | null;
    trackCount: number | null;
    albumType: string | null;
    upc: string | null;
    mediums: MediumObject[];
    labels: LabelObject[] | null;
    copyrights: string[] | null;
    genres: string[] | null;
    type: "album";
    script?: ReleaseScript;
    language?: ReleaseLanguage
};

export class LabelObject extends GenericObject {
    catalogNumber?: string | null;
}

export type UrlType = 'album' | 'track' | 'artist' | 'label';

export class UrlData {
    type: UrlType | null;
    id: string | null;
};

export class UrlInfo extends UrlData {
    provider: ProviderNamespace;
}


export class ExternalUrlData {
    url: string;
    urlInfo: UrlInfo | null;
    mbTypes: number[]
}

export class CommonExtends {
    comment: string | null;
    externalUrls: string[] | null;
}

export class ExtendedAlbumObject extends AlbumObject {
    comment: string | null;
    externalUrls: string[] | null;
    hasImage: boolean;
    albumArtists: ArtistObject[];
    override mediums: ExtendedMediumObject[];
};

export class MediumObject {
    /**
     * Medium format, if unspecified, assumed to be "Digital Media"
     */
    format?: MediumFormat | null;
    name?: string | null;
    tracks: TrackObject[];
    number: number | null;
}

export class ExtendedMediumObject extends MediumObject {
    tracks: ExtendedTrackObject[];
}

export class TrackObject extends ImageObject {
    provider: ProviderNamespace;
    id: string | null;
    name: string;
    url: ExternalUrlData | null;
    trackArtists: PartialArtistObject[];
    artistNames: string[];
    albumName: string | null;
    releaseDate: string | null;
    trackNumber: number | null;
    /**
     * Track duration in ms
     */
    duration: number | null;
    isrcs: string[];
    type: "track";
};

export class ExtendedTrackObject extends TrackObject {
    comment?: string | null;
    externalUrls?: string[] | null;
    extraInfo?: {[key: string]: any};
}

export class PagingData {
    count: number | null;
    current: string | number | null;
    next: string | null;
    max?: number | null;
}

export class RawAlbumData extends PagingData {
    albums: any[];
};

export class AlbumData extends PagingData {
    albums: AlbumObject[];
};

export class ExtendedAlbumData extends PagingData {
    albums: ExtendedAlbumObject[];
}

export class UrlMBIDDict {
    [key: string]: string | undefined;
}

export type IdMBIDDict = UrlMBIDDict;
// aggregated-types.ts

export type AlbumIssue = 'noUPC' | 'UPCDiff' | 'missingISRCs' | 'ISRCDiff' | 'trackDiff' | 'noDate' | 'dateDiff' | 'noCover';
export type AlbumStatus = 'green' | 'orange' | 'blue' | 'red';

export type TrackStatus = 'green' | 'orange' | 'blue' | 'grey'; // Track count miss-match doesn't aggregate, so tracks would never be not found

export type TrackIssue = 'noISRC' | 'ISRCDiff' | 'noDuration' | "artistDiff"

/**
 * source - Selected source provider
 * 
 * provider - Supplimental provider data (For pulling links and filling data gaps)
 * 
 * target - Target provider existing data (Usually MusicBrainz)
 */
export type AlbumMatchType = 'source' | 'provider' | 'target'

export class AggregatedArtist extends ArtistObject {
    mbid?: string | null;
}


export class AggregatedLabel extends LabelObject {
    mbid?: string | null;
}

export class AlbumStack {
    status: AlbumStatus;
    albumIssues: AlbumIssue[];
    albums: AlbumMatch[];
    aggregated: AggregatedAlbum;
    // sourceAlbum: AlbumObject;
    // targetAlbum?: AlbumObject;
    // providerAlbums?: AlbumObject[]
}

export class AlbumMatch {
    type: AlbumMatchType;
    album: AlbumObject
}

export class AggregatedAlbum extends AlbumObject{
    mediums: AggregatedMedium[];
    sourceArtist: PartialArtistObject | null;
}

export class AggregatedMedium extends MediumObject {
    override tracks: AggregatedTrack[]
}

export class AggregatedTrack extends TrackObject {
    // sourceArtist: PartialArtistObject | null;
    status: TrackStatus;
    trackIssues: TrackIssue[];
}

export class BasicTrack {
    name: string;
    isrcs: string[];
}

export class AggregatedData {
    albumData: AlbumStack[]
    statusText: string
    green: number
    orange: number
    blue: number
    red: number
    total: number
}

export class RawAggregateData {
    sourceAlbums: AlbumObject[]
    targetAlbums: ExtendedAlbumObject[]
    targetFeaturedAlbums: ExtendedAlbumObject[]
}

New Contributors

Full Changelog: v2.3.12...v2.4.0