trackparse 0.2.1 → 0.3.2

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.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  // src/data.generated.ts
4
- var SPEC_VERSION = "0.2.1";
4
+ var SPEC_VERSION = "0.3.2";
5
5
  var descriptorsData = {
6
6
  "descriptors": [
7
7
  "radio",
@@ -475,6 +475,19 @@ function isLetterAt(s, index) {
475
475
  if (cp === void 0) return false;
476
476
  return LETTER.test(String.fromCodePoint(cp));
477
477
  }
478
+ function isLetterBefore(s, index) {
479
+ const low = s.charCodeAt(index - 1);
480
+ const high = s.charCodeAt(index - 2);
481
+ const pair = low >= 56320 && low <= 57343 && high >= 55296 && high <= 56319;
482
+ const start = pair ? index - 2 : index - 1;
483
+ return start >= 0 && isLetterAt(s, start);
484
+ }
485
+ var UPPER = /^\p{Lu}$/u;
486
+ function isUpperAt(s, index) {
487
+ const cp = s.codePointAt(index);
488
+ if (cp === void 0) return false;
489
+ return UPPER.test(String.fromCodePoint(cp));
490
+ }
478
491
  function asciiLower(s) {
479
492
  let out = "";
480
493
  for (let i = 0; i < s.length; i++) {
@@ -1687,7 +1700,7 @@ function prelude(normalized, requested, ctx) {
1687
1700
  let str = stripPlatformSuffixes(normalized, ctx, junk);
1688
1701
  const hadPlatform = junk.length > 0;
1689
1702
  let mode;
1690
- if (requested === "clean" || requested === "youtube" || requested === "filename") {
1703
+ if (requested === "clean" || requested === "youtube" || requested === "filename" || requested === "tracklist") {
1691
1704
  mode = requested;
1692
1705
  } else if (extensionOf(str, ctx)) {
1693
1706
  mode = "filename";
@@ -1749,7 +1762,7 @@ function splitYoutubePipes(skel, ctx, junk) {
1749
1762
  return { working: { text: "", base: 0 }, pipeTitle: null };
1750
1763
  }
1751
1764
 
1752
- // src/prefix.ts
1765
+ // src/digits.ts
1753
1766
  function digitRun(s, from) {
1754
1767
  let i = from;
1755
1768
  while (isDigit(s[i])) i++;
@@ -1760,6 +1773,8 @@ function twoDigitsUpTo59(s, at) {
1760
1773
  const n = Number(s.slice(at, at + 2));
1761
1774
  return n <= 59 ? n : null;
1762
1775
  }
1776
+
1777
+ // src/prefix.ts
1763
1778
  function matchTime(s, at) {
1764
1779
  const lead = digitRun(s, at);
1765
1780
  if (lead < 1 || lead > 2 || s[at + lead] !== ":") return null;
@@ -1877,6 +1892,8 @@ function splitAt(s, cutStart, cutEnd) {
1877
1892
  function findSeparator(input, ctx) {
1878
1893
  const { skel, mode } = input;
1879
1894
  const segments = splitAtSpacedDashes(skel, true);
1895
+ if (mode === "tracklist")
1896
+ return tracklistSeparator(skel, segments, input.listContext === true, ctx);
1880
1897
  const { remaining, peeled } = peelSuffixes(segments, false, ctx);
1881
1898
  if (remaining.length >= 2) {
1882
1899
  const first = remaining[0];
@@ -1893,6 +1910,46 @@ function findSeparator(input, ctx) {
1893
1910
  if (rest.text.length > 0 && artist === null) warn(ctx, "noSeparator");
1894
1911
  return { artist, title: rest, split: false, peeled };
1895
1912
  }
1913
+ function tracklistSeparator(skel, segments, listContext, ctx) {
1914
+ if (segments.length >= 2) {
1915
+ const first = segments[0];
1916
+ const second = segments[1];
1917
+ const lastSeg = segments[segments.length - 1];
1918
+ const artist = first.skel.text.length > 0 ? first.skel : null;
1919
+ const title = sliceSkel(skel, second.start, lastSeg.end);
1920
+ return { artist, title, split: artist !== null, peeled: [] };
1921
+ }
1922
+ const rest = segments[0].skel;
1923
+ const asym = asymmetricDash(rest.text);
1924
+ if (asym >= 0) {
1925
+ warn(ctx, "asymmetricDashSplit");
1926
+ const parts = splitAt(rest, asym, asym + 1);
1927
+ return { ...parts, split: parts.artist !== null, peeled: [] };
1928
+ }
1929
+ const underscore = rest.text.indexOf("_-_");
1930
+ if (underscore >= 0) {
1931
+ warn(ctx, "unspacedDashSplit");
1932
+ const parts = splitAt(rest, underscore, underscore + 3);
1933
+ return { ...parts, split: parts.artist !== null, peeled: [] };
1934
+ }
1935
+ if (listContext) {
1936
+ const tight = tightUpperDash(rest.text);
1937
+ if (tight >= 0) {
1938
+ warn(ctx, "unspacedDashSplit");
1939
+ const parts = splitAt(rest, tight, tight + 1);
1940
+ return { ...parts, split: parts.artist !== null, peeled: [] };
1941
+ }
1942
+ }
1943
+ if (rest.text.length > 0) warn(ctx, "noSeparator");
1944
+ return { artist: null, title: rest, split: false, peeled: [] };
1945
+ }
1946
+ function tightUpperDash(t) {
1947
+ for (let i = t.length - 2; i >= 1; i--) {
1948
+ if (t[i] !== "-") continue;
1949
+ if (isLetterBefore(t, i) && isUpperAt(t, i + 1)) return i;
1950
+ }
1951
+ return -1;
1952
+ }
1896
1953
  function splitWithoutSpacedDash(s, mode, ctx) {
1897
1954
  const asym = asymmetricDash(s.text);
1898
1955
  if (asym >= 0) {
@@ -2167,17 +2224,27 @@ function uploaderSkel(uploader) {
2167
2224
  if (u.length === 0) return null;
2168
2225
  return scan(u, UPLOADER_BASE).skel;
2169
2226
  }
2170
- function run(input, options, t) {
2227
+ function stage(input, options, t) {
2171
2228
  const ctx = makeCtx(t, options);
2172
2229
  const norm = normalize(input);
2173
2230
  const requested = options.mode ?? "auto";
2174
2231
  const pre = prelude(norm, requested, ctx);
2175
2232
  const track = emptyTrack(typeof input === "string" ? input : "", pre.mode);
2176
- if (norm.length === 0) return track;
2233
+ if (norm.length === 0) {
2234
+ return { ctx, options, pre, track, working: null };
2235
+ }
2177
2236
  const prefix = stripPrefixes(pre.working.text, pre.working.base, pre.mode, ctx);
2178
2237
  track.timestamp = prefix.timestamp;
2179
2238
  track.position = prefix.position;
2180
2239
  const working = skelOf({ text: prefix.rest, base: prefix.base }, ctx);
2240
+ return { ctx, options, pre, track, working };
2241
+ }
2242
+ function run(input, options, t, listContext = false) {
2243
+ return finish(stage(input, options, t), listContext);
2244
+ }
2245
+ function finish(st, listContext) {
2246
+ const { ctx, options, pre, track, working } = st;
2247
+ if (working === null) return track;
2181
2248
  const out = emptyExtracted();
2182
2249
  let artistSkel;
2183
2250
  let titleSkel;
@@ -2188,7 +2255,7 @@ function run(input, options, t) {
2188
2255
  relaxed = artistSkel !== null;
2189
2256
  } else {
2190
2257
  const sep = findSeparator(
2191
- { skel: working, mode: pre.mode, uploader: uploaderSkel(options.uploader) },
2258
+ { skel: working, mode: pre.mode, uploader: uploaderSkel(options.uploader), listContext },
2192
2259
  ctx
2193
2260
  );
2194
2261
  addPeeled(out, sep.peeled);
package/dist/index.d.cts CHANGED
@@ -1,89 +1,7 @@
1
- declare const SPEC_VERSION = "0.2.1";
1
+ import { P as ParsedTrack, A as Artist, F as FormatOptions, a as ParseOptions } from './types-DqJSn18b.cjs';
2
+ export { b as ArtistRole, c as ArtistSource, d as Flags, J as Junk, e as JunkKind, K as KeywordOptions, M as Mode, f as ModeOption, g as Position, T as Timestamp, V as Version, h as VersionDelimiter, i as VersionType, W as Warning } from './types-DqJSn18b.cjs';
2
3
 
3
- /** Public types. They mirror spec/schema/parsed-track.schema.json and options.schema.json. */
4
- type Mode = "clean" | "youtube" | "filename";
5
- type ModeOption = Mode | "auto";
6
- type ArtistRole = "primary" | "featured" | "remixer" | "producer";
7
- type ArtistSource = "artist" | "title" | "version";
8
- interface Artist {
9
- name: string;
10
- role: ArtistRole;
11
- /** Raw joiner / marker text that preceded the name in its credit list; `null` for the first name. */
12
- joiner: string | null;
13
- source: ArtistSource;
14
- }
15
- type VersionType = "remix" | "bootleg" | "vip" | "edit" | "flip" | "refix" | "rework" | "mashup" | "blend" | "dub" | "mix" | "extended" | "radio" | "club" | "original" | "instrumental" | "acapella" | "live" | "acoustic" | "remaster" | "demo" | "reprise" | "cover" | "version" | "spedUp" | "slowed" | "nightcore";
16
- type VersionDelimiter = "(" | "[" | "{" | "-";
17
- interface Version {
18
- type: VersionType;
19
- raw: string;
20
- artists: Artist[];
21
- modifiers: string[];
22
- descriptor: string | null;
23
- year: number | null;
24
- unknownArtist: boolean;
25
- delimiter: VersionDelimiter;
26
- }
27
- type JunkKind = "video" | "audio" | "lyrics" | "quality" | "promo" | "platform" | "label" | "genre" | "other";
28
- interface Junk {
29
- raw: string;
30
- kind: JunkKind;
31
- }
32
- type Warning = "noSeparator" | "ambiguousSeparator" | "unspacedDashSplit" | "asymmetricDashSplit" | "bySplit" | "quotedTitleSplit" | "ambiguousMixCredit" | "unbalancedBrackets";
33
- interface Flags {
34
- explicit: boolean;
35
- clean: boolean;
36
- unknownArtist: boolean;
37
- unknownTitle: boolean;
38
- }
39
- interface Position {
40
- raw: string;
41
- number: number;
42
- }
43
- interface Timestamp {
44
- raw: string;
45
- seconds: number;
46
- }
47
- interface ParsedTrack {
48
- input: string;
49
- mode: Mode;
50
- position: Position | null;
51
- timestamp: Timestamp | null;
52
- artists: Artist[];
53
- title: string;
54
- fullTitle: string;
55
- versions: Version[];
56
- year: number | null;
57
- flags: Flags;
58
- junk: Junk[];
59
- warnings: Warning[];
60
- }
61
- interface KeywordOptions {
62
- /** Extra version heads: key → version type. */
63
- versionHeads?: Record<string, VersionType>;
64
- descriptors?: string[];
65
- genres?: string[];
66
- /** Extra junk phrases: phrase → kind. */
67
- junk?: Record<string, JunkKind>;
68
- featMarkers?: string[];
69
- }
70
- interface ParseOptions {
71
- mode?: ModeOption;
72
- /** youtube mode: channel name used as the artist when no separator is found (R6.8). */
73
- uploader?: string;
74
- /** Names that must never be split (R7.3a). */
75
- knownArtists?: string[];
76
- splitAnd?: "auto" | "always" | "never";
77
- keywords?: KeywordOptions;
78
- }
79
- interface FormatOptions {
80
- feat?: "source" | "artist" | "title" | "omit";
81
- featMarker?: string;
82
- joiners?: "original" | "canonical";
83
- versions?: "all" | "none";
84
- producers?: boolean;
85
- position?: boolean;
86
- }
4
+ declare const SPEC_VERSION = "0.3.2";
87
5
 
88
6
  declare function format(track: ParsedTrack, options?: FormatOptions): string;
89
7
  /** All credited names: track artists plus every version's remixers, deduped by R0.4 key. */
@@ -101,4 +19,4 @@ declare function createParser(base?: ParseOptions): Parser;
101
19
  declare function parse(input: string, options?: ParseOptions): ParsedTrack;
102
20
  declare function parseArtists(input: string, options?: ParseOptions): Artist[];
103
21
 
104
- export { type Artist, type ArtistRole, type ArtistSource, type Flags, type FormatOptions, type Junk, type JunkKind, type KeywordOptions, type Mode, type ModeOption, type ParseOptions, type ParsedTrack, type Parser, type Position, SPEC_VERSION, type Timestamp, type Version, type VersionDelimiter, type VersionType, type Warning, allArtists, createParser, format, normalize, parse, parseArtists };
22
+ export { Artist, FormatOptions, ParseOptions, ParsedTrack, type Parser, SPEC_VERSION, allArtists, createParser, format, normalize, parse, parseArtists };
package/dist/index.d.ts CHANGED
@@ -1,89 +1,7 @@
1
- declare const SPEC_VERSION = "0.2.1";
1
+ import { P as ParsedTrack, A as Artist, F as FormatOptions, a as ParseOptions } from './types-DqJSn18b.js';
2
+ export { b as ArtistRole, c as ArtistSource, d as Flags, J as Junk, e as JunkKind, K as KeywordOptions, M as Mode, f as ModeOption, g as Position, T as Timestamp, V as Version, h as VersionDelimiter, i as VersionType, W as Warning } from './types-DqJSn18b.js';
2
3
 
3
- /** Public types. They mirror spec/schema/parsed-track.schema.json and options.schema.json. */
4
- type Mode = "clean" | "youtube" | "filename";
5
- type ModeOption = Mode | "auto";
6
- type ArtistRole = "primary" | "featured" | "remixer" | "producer";
7
- type ArtistSource = "artist" | "title" | "version";
8
- interface Artist {
9
- name: string;
10
- role: ArtistRole;
11
- /** Raw joiner / marker text that preceded the name in its credit list; `null` for the first name. */
12
- joiner: string | null;
13
- source: ArtistSource;
14
- }
15
- type VersionType = "remix" | "bootleg" | "vip" | "edit" | "flip" | "refix" | "rework" | "mashup" | "blend" | "dub" | "mix" | "extended" | "radio" | "club" | "original" | "instrumental" | "acapella" | "live" | "acoustic" | "remaster" | "demo" | "reprise" | "cover" | "version" | "spedUp" | "slowed" | "nightcore";
16
- type VersionDelimiter = "(" | "[" | "{" | "-";
17
- interface Version {
18
- type: VersionType;
19
- raw: string;
20
- artists: Artist[];
21
- modifiers: string[];
22
- descriptor: string | null;
23
- year: number | null;
24
- unknownArtist: boolean;
25
- delimiter: VersionDelimiter;
26
- }
27
- type JunkKind = "video" | "audio" | "lyrics" | "quality" | "promo" | "platform" | "label" | "genre" | "other";
28
- interface Junk {
29
- raw: string;
30
- kind: JunkKind;
31
- }
32
- type Warning = "noSeparator" | "ambiguousSeparator" | "unspacedDashSplit" | "asymmetricDashSplit" | "bySplit" | "quotedTitleSplit" | "ambiguousMixCredit" | "unbalancedBrackets";
33
- interface Flags {
34
- explicit: boolean;
35
- clean: boolean;
36
- unknownArtist: boolean;
37
- unknownTitle: boolean;
38
- }
39
- interface Position {
40
- raw: string;
41
- number: number;
42
- }
43
- interface Timestamp {
44
- raw: string;
45
- seconds: number;
46
- }
47
- interface ParsedTrack {
48
- input: string;
49
- mode: Mode;
50
- position: Position | null;
51
- timestamp: Timestamp | null;
52
- artists: Artist[];
53
- title: string;
54
- fullTitle: string;
55
- versions: Version[];
56
- year: number | null;
57
- flags: Flags;
58
- junk: Junk[];
59
- warnings: Warning[];
60
- }
61
- interface KeywordOptions {
62
- /** Extra version heads: key → version type. */
63
- versionHeads?: Record<string, VersionType>;
64
- descriptors?: string[];
65
- genres?: string[];
66
- /** Extra junk phrases: phrase → kind. */
67
- junk?: Record<string, JunkKind>;
68
- featMarkers?: string[];
69
- }
70
- interface ParseOptions {
71
- mode?: ModeOption;
72
- /** youtube mode: channel name used as the artist when no separator is found (R6.8). */
73
- uploader?: string;
74
- /** Names that must never be split (R7.3a). */
75
- knownArtists?: string[];
76
- splitAnd?: "auto" | "always" | "never";
77
- keywords?: KeywordOptions;
78
- }
79
- interface FormatOptions {
80
- feat?: "source" | "artist" | "title" | "omit";
81
- featMarker?: string;
82
- joiners?: "original" | "canonical";
83
- versions?: "all" | "none";
84
- producers?: boolean;
85
- position?: boolean;
86
- }
4
+ declare const SPEC_VERSION = "0.3.2";
87
5
 
88
6
  declare function format(track: ParsedTrack, options?: FormatOptions): string;
89
7
  /** All credited names: track artists plus every version's remixers, deduped by R0.4 key. */
@@ -101,4 +19,4 @@ declare function createParser(base?: ParseOptions): Parser;
101
19
  declare function parse(input: string, options?: ParseOptions): ParsedTrack;
102
20
  declare function parseArtists(input: string, options?: ParseOptions): Artist[];
103
21
 
104
- export { type Artist, type ArtistRole, type ArtistSource, type Flags, type FormatOptions, type Junk, type JunkKind, type KeywordOptions, type Mode, type ModeOption, type ParseOptions, type ParsedTrack, type Parser, type Position, SPEC_VERSION, type Timestamp, type Version, type VersionDelimiter, type VersionType, type Warning, allArtists, createParser, format, normalize, parse, parseArtists };
22
+ export { Artist, FormatOptions, ParseOptions, ParsedTrack, type Parser, SPEC_VERSION, allArtists, createParser, format, normalize, parse, parseArtists };