@mtillmann/chapters 0.1.7 → 0.1.8

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.d.ts CHANGED
@@ -70,7 +70,7 @@ declare abstract class Base implements MediaItem {
70
70
  static create(input?: string | MediaItem, duration?: number): MediaItem;
71
71
  from(input?: string | MediaItem): MediaItem;
72
72
  detect(inputString: string): boolean;
73
- test(data: object): {
73
+ test(data: Record<string, any>): {
74
74
  errors: string[];
75
75
  };
76
76
  bump(keepDuration?: boolean): void;
@@ -82,8 +82,8 @@ declare abstract class Base implements MediaItem {
82
82
  parse(string: string): void;
83
83
  toString(pretty?: boolean, exportOptions?: {}): string;
84
84
  applyChapterMinLength(seconds: number): object;
85
- addChapterAt(index: number, chapter?: object): number;
86
- addChapterAtTime(time: number | string, chapter?: object): boolean;
85
+ addChapterAt(index: number, chapter?: Partial<Chapter>): number;
86
+ addChapterAtTime(time: number | string, chapter?: Partial<Chapter>): boolean;
87
87
  rebuildChapterTitles(template?: string): void;
88
88
  ensureTitle(index: number): string;
89
89
  getChapterTitle(index: number, template?: string): string;
@@ -129,6 +129,16 @@ declare class AppleHLS extends Base {
129
129
  toString(pretty?: boolean): string;
130
130
  }
131
131
 
132
+ declare class Audible extends Base {
133
+ filename: string;
134
+ mimeType: string;
135
+ test(data: Record<string, any>): {
136
+ errors: string[];
137
+ };
138
+ parse(string: string): void;
139
+ toString(pretty?: boolean): string;
140
+ }
141
+
132
142
  declare class ChaptersJson extends Base {
133
143
  supportsPrettyPrint: boolean;
134
144
  }
@@ -179,6 +189,39 @@ declare class MP4Chaps extends Base {
179
189
  toString(): string;
180
190
  }
181
191
 
192
+ declare class TextGeneric extends Base {
193
+ filename: string;
194
+ mimeType: string;
195
+ formats: Record<string, Record<string, any>>;
196
+ detect(inputString: string): boolean;
197
+ parse(string: string): void;
198
+ toString(pretty?: boolean, exportOptions?: string | Record<string, any>): string;
199
+ }
200
+
201
+ declare class PodcastPage extends TextGeneric {
202
+ filename: string;
203
+ mimeType: string;
204
+ detect(inputString: string): boolean;
205
+ toString(): string;
206
+ }
207
+
208
+ declare class Podigee extends Base {
209
+ filename: string;
210
+ supportsPrettyPrint: boolean;
211
+ test(data: object[]): {
212
+ errors: string[];
213
+ };
214
+ parse(string: string): void;
215
+ toString(pretty?: boolean): string;
216
+ }
217
+
218
+ declare class PodigeeText extends TextGeneric {
219
+ filename: string;
220
+ mimeType: string;
221
+ detect(inputString: string): boolean;
222
+ toString(): string;
223
+ }
224
+
182
225
  declare class PodloveJson extends Base {
183
226
  filename: string;
184
227
  mimeType: string;
@@ -224,6 +267,13 @@ declare class Scenecut extends Base {
224
267
  toString(pretty?: boolean, exportOptions?: {}): string;
225
268
  }
226
269
 
270
+ declare class ShowNotes extends TextGeneric {
271
+ filename: string;
272
+ mimeType: string;
273
+ detect(inputString: string): boolean;
274
+ toString(): string;
275
+ }
276
+
227
277
  declare class ShutterEDL extends Base {
228
278
  detect(inputString: string): boolean;
229
279
  decodeTime(timeString: string): string;
@@ -232,6 +282,27 @@ declare class ShutterEDL extends Base {
232
282
  toString(): string;
233
283
  }
234
284
 
285
+ declare class SpotifyA extends TextGeneric {
286
+ filename: string;
287
+ mimeType: string;
288
+ detect(inputString: string): boolean;
289
+ toString(): string;
290
+ }
291
+
292
+ declare class SpotifyB extends TextGeneric {
293
+ filename: string;
294
+ mimeType: string;
295
+ detect(inputString: string): boolean;
296
+ toString(): string;
297
+ }
298
+
299
+ declare class TransistorFM extends TextGeneric {
300
+ filename: string;
301
+ mimeType: string;
302
+ detect(inputString: string): boolean;
303
+ toString(): string;
304
+ }
305
+
235
306
  declare class VorbisComment extends MKVMergeSimple {
236
307
  filename: string;
237
308
  mimeType: string;
@@ -246,11 +317,10 @@ declare class WebVTT extends Base {
246
317
  toString(): string;
247
318
  }
248
319
 
249
- declare class Youtube extends Base {
320
+ declare class Youtube extends TextGeneric {
250
321
  filename: string;
251
322
  mimeType: string;
252
323
  detect(inputString: string): boolean;
253
- parse(string: string): void;
254
324
  toString(): string;
255
325
  }
256
326
 
@@ -294,4 +364,4 @@ declare namespace util {
294
364
  export { util_Float as Float, util_Floats as Floats, util_Int as Int, util_Ints as Ints, util_NPTToSeconds as NPTToSeconds, util_enforceMilliseconds as enforceMilliseconds, util_escapeRegExpCharacters as escapeRegExpCharacters, util_formatBytes as formatBytes, util_hash as hash, util_indenter as indenter, util_secondsToNPT as secondsToNPT, util_secondsToTimestamp as secondsToTimestamp, util_stringToLines as stringToLines, util_timestampToSeconds as timestampToSeconds, util_toSeconds as toSeconds, util_zeroPad as zeroPad };
295
365
  }
296
366
 
297
- export { AppleChapters, AppleHLS, AutoFormat, type Chapter, ChaptersJson, FFMetadata, FFMpegInfo, MKVMergeSimple, MKVMergeXML, MP4Chaps, MatroskaXML, type MediaItem, type MediaItemMeta, PodloveJson, PodloveSimpleChapters, PySceneDetect, Scenecut, ShutterEDL, util as Util, VorbisComment, WebVTT, Youtube };
367
+ export { AppleChapters, AppleHLS, Audible, AutoFormat, type Chapter, ChaptersJson, FFMetadata, FFMpegInfo, MKVMergeSimple, MKVMergeXML, MP4Chaps, MatroskaXML, type MediaItem, type MediaItemMeta, PodcastPage, Podigee, PodigeeText, PodloveJson, PodloveSimpleChapters, PySceneDetect, Scenecut, ShowNotes, ShutterEDL, SpotifyA, SpotifyB, TransistorFM, util as Util, VorbisComment, WebVTT, Youtube };
package/dist/index.js CHANGED
@@ -909,38 +909,88 @@ var WebVTT = class extends Base {
909
909
  }
910
910
  };
911
911
 
912
- // src/Formats/Youtube.ts
913
- var Youtube = class extends Base {
914
- filename = "youtube-chapters.txt";
912
+ // src/Formats/TextGeneric.ts
913
+ var TextGeneric = class extends Base {
914
+ filename = "chapters.txt";
915
915
  mimeType = "text/plain";
916
+ formats = {
917
+ spotifya: {
918
+ string: (title, start) => `(${start}) ${title}`,
919
+ hours: "default"
920
+ },
921
+ spotifyb: {
922
+ string: (title, start) => `${start}-${title}`,
923
+ hours: "default"
924
+ },
925
+ youtube: {
926
+ string: (title, start) => `${start} ${title}`,
927
+ hours: "whenOverOneHour",
928
+ enforceZeroStartTime: true
929
+ },
930
+ shownotes: {
931
+ string: (title, start) => [`(${start})`, "", title, ""].join("\n"),
932
+ hours: "default"
933
+ },
934
+ transistorfm: {
935
+ string: (title, start) => `${start} - ${title}`,
936
+ hours: "default"
937
+ },
938
+ podigeetext: {
939
+ string: (title, start) => `${start} - ${title}`,
940
+ hours: "always"
941
+ },
942
+ podcastpage: {
943
+ string: (title, start) => `(${start}) - ${title}`,
944
+ hours: "default"
945
+ }
946
+ };
916
947
  detect(inputString) {
917
- return /^0?0:00(:00)?\s/.test(inputString.trim());
948
+ return /^\(?(?<ts>\d?\d:\d\d(?::\d\d)?)\)?[\s-]+(?<title>[^\n]+)/.test(inputString.trim());
918
949
  }
919
950
  parse(string) {
920
951
  if (!this.detect(string)) {
921
- throw new Error("Youtube Chapters *MUST* begin with (0)0:00(:00), received: " + string.substr(0, 10) + "...");
952
+ throw new Error("Invalid format, see documentation for supported formats");
922
953
  }
923
- this.chapters = stringToLines(string).map((line) => {
924
- const l = line.split(" ");
925
- const timestamp = String(l.shift());
954
+ const matches = [...string.matchAll(/^\(?(?<ts>\d?\d:\d\d(?::\d\d)?)\)?[\s-]+(?<title>[^\n]+)/gm)];
955
+ this.chapters = matches.map((match) => {
926
956
  return {
927
- startTime: timestampToSeconds(timestamp),
928
- title: l.join(" ")
957
+ startTime: timestampToSeconds(match.groups.ts),
958
+ title: match.groups.title
929
959
  };
930
960
  });
931
961
  }
932
- toString() {
933
- const options = {
934
- milliseconds: false,
935
- hours: this.chapters.at(-1).startTime > 3600
936
- };
962
+ toString(pretty = false, exportOptions = "youtube") {
963
+ const formatKey = typeof exportOptions === "string" ? exportOptions : exportOptions.format;
964
+ if (!(formatKey in this.formats)) {
965
+ throw new Error("Invalid format: " + formatKey);
966
+ }
967
+ const template = this.formats[formatKey];
968
+ const exceedsOneHour = this.chapters.at(-1).startTime > 3600;
937
969
  return this.chapters.map((chapter, index) => {
938
- const startTime = index === 0 && chapter.startTime !== 0 ? 0 : chapter.startTime;
939
- return `${secondsToTimestamp(startTime, options)} ${this.ensureTitle(index)}`;
970
+ const startTime = template.enforceZeroStartTime && index === 0 && chapter.startTime !== 0 ? 0 : chapter.startTime;
971
+ let hours;
972
+ if (startTime > 3600 || template.hours === "always" || template.hours === "whenOverOneHour" && exceedsOneHour) {
973
+ hours = true;
974
+ } else {
975
+ hours = false;
976
+ }
977
+ return template.string(chapter.title, secondsToTimestamp(startTime, { hours }));
940
978
  }).join("\n");
941
979
  }
942
980
  };
943
981
 
982
+ // src/Formats/Youtube.ts
983
+ var Youtube = class extends TextGeneric {
984
+ filename = "youtube-chapters.txt";
985
+ mimeType = "text/plain";
986
+ detect(inputString) {
987
+ return /^(?<ts>\d?\d:\d\d(?::\d\d)?) [^-](?<title>[^\n]+)/.test(inputString.trim());
988
+ }
989
+ toString() {
990
+ return super.toString(false, { format: "youtube" });
991
+ }
992
+ };
993
+
944
994
  // src/Formats/ShutterEDL.ts
945
995
  var ShutterEDL = class extends Base {
946
996
  // this format is based on the shutter encoder edl format
@@ -1364,6 +1414,7 @@ var Audible = class extends Base {
1364
1414
 
1365
1415
  // src/Formats/Podigee.ts
1366
1416
  var Podigee = class extends Base {
1417
+ filename = "podigee-chapters.json";
1367
1418
  supportsPrettyPrint = true;
1368
1419
  test(data) {
1369
1420
  if (!Array.isArray(data)) {
@@ -1417,6 +1468,78 @@ var Podigee = class extends Base {
1417
1468
  }
1418
1469
  };
1419
1470
 
1471
+ // src/Formats/PodigeeText.ts
1472
+ var PodigeeText = class extends TextGeneric {
1473
+ filename = "podigee-chapters.txt";
1474
+ mimeType = "text/plain";
1475
+ detect(inputString) {
1476
+ return /^(?<ts>\d?\d:\d\d(?::\d\d)?) - (?<title>[^\n]+)/.test(inputString.trim());
1477
+ }
1478
+ toString() {
1479
+ return super.toString(false, { format: "podigeetext" });
1480
+ }
1481
+ };
1482
+
1483
+ // src/Formats/ShowNotes.ts
1484
+ var ShowNotes = class extends TextGeneric {
1485
+ filename = "shownote-chapters.txt";
1486
+ mimeType = "text/plain";
1487
+ detect(inputString) {
1488
+ return /^\((?<ts>\d?\d:\d\d(?::\d\d)?)\)\n\n(?<title>[^\n]+)/.test(inputString.trim());
1489
+ }
1490
+ toString() {
1491
+ return super.toString(false, { format: "shownotes" });
1492
+ }
1493
+ };
1494
+
1495
+ // src/Formats/SpotifyA.ts
1496
+ var SpotifyA = class extends TextGeneric {
1497
+ filename = "spotify-chapters.txt";
1498
+ mimeType = "text/plain";
1499
+ detect(inputString) {
1500
+ return /^\((?<ts>\d?\d:\d\d(?::\d\d)?)\) [^-](?<title>[^\n]+)/.test(inputString.trim());
1501
+ }
1502
+ toString() {
1503
+ return super.toString(false, { format: "spotifya" });
1504
+ }
1505
+ };
1506
+
1507
+ // src/Formats/SpotifyB.ts
1508
+ var SpotifyB = class extends TextGeneric {
1509
+ filename = "spotify-chapters.txt";
1510
+ mimeType = "text/plain";
1511
+ detect(inputString) {
1512
+ return /^(?<ts>\d?\d:\d\d(?::\d\d)?)-(?<title>[^\n]+)/.test(inputString.trim());
1513
+ }
1514
+ toString() {
1515
+ return super.toString(false, { format: "spotifyb" });
1516
+ }
1517
+ };
1518
+
1519
+ // src/Formats/PodcastPage.ts
1520
+ var PodcastPage = class extends TextGeneric {
1521
+ filename = "podcastpage-chapters.txt";
1522
+ mimeType = "text/plain";
1523
+ detect(inputString) {
1524
+ return /^\((?<ts>\d?\d:\d\d(?::\d\d)?)\) - (?<title>[^\n]+)/.test(inputString.trim());
1525
+ }
1526
+ toString() {
1527
+ return super.toString(false, { format: "podcastpage" });
1528
+ }
1529
+ };
1530
+
1531
+ // src/Formats/TransistorFM.ts
1532
+ var TransistorFM = class extends TextGeneric {
1533
+ filename = "transistorfm-chapters.txt";
1534
+ mimeType = "text/plain";
1535
+ detect(inputString) {
1536
+ return /^(?<ts>\d?\d:\d\d(?::\d\d)?) - (?<title>[^\n]+)/.test(inputString.trim());
1537
+ }
1538
+ toString() {
1539
+ return super.toString(false, { format: "transistorfm" });
1540
+ }
1541
+ };
1542
+
1420
1543
  // src/Formats/AutoFormat.ts
1421
1544
  var classMap = {
1422
1545
  chaptersjson: ChaptersJson,
@@ -1437,7 +1560,13 @@ var classMap = {
1437
1560
  applehls: AppleHLS,
1438
1561
  scenecut: Scenecut,
1439
1562
  audible: Audible,
1440
- podigee: Podigee
1563
+ podigee: Podigee,
1564
+ podigeetext: PodigeeText,
1565
+ shownotes: ShowNotes,
1566
+ spotifya: SpotifyA,
1567
+ spotifyb: SpotifyB,
1568
+ podcastpage: PodcastPage,
1569
+ transistorfm: TransistorFM
1441
1570
  };
1442
1571
  var AutoFormat = {
1443
1572
  classMap,
@@ -1477,6 +1606,7 @@ var AutoFormat = {
1477
1606
  export {
1478
1607
  AppleChapters,
1479
1608
  AppleHLS,
1609
+ Audible,
1480
1610
  AutoFormat,
1481
1611
  ChaptersJson,
1482
1612
  FFMetadata,
@@ -1485,11 +1615,18 @@ export {
1485
1615
  MKVMergeXML,
1486
1616
  MP4Chaps,
1487
1617
  MatroskaXML,
1618
+ PodcastPage,
1619
+ Podigee,
1620
+ PodigeeText,
1488
1621
  PodloveJson,
1489
1622
  PodloveSimpleChapters,
1490
1623
  PySceneDetect,
1491
1624
  Scenecut,
1625
+ ShowNotes,
1492
1626
  ShutterEDL,
1627
+ SpotifyA,
1628
+ SpotifyB,
1629
+ TransistorFM,
1493
1630
  util_exports as Util,
1494
1631
  VorbisComment,
1495
1632
  WebVTT,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mtillmann/chapters",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "library that manages and converts chapters of multiple formats",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/readme.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # chapters
6
6
 
7
- This is the core library of the [chaptertool](https://github.com/Mtillmann/chaptertool) project and provides the functionality to handle the formats below.
7
+ This is the core library of [chaptertool](https://github.com/Mtillmann/chaptertool) and [chapconv](https://github.com/Mtillmann/chapconv/) and provides the functionality to handle the formats below.
8
8
 
9
9
  [Click here to open the web GUI](https://mtillmann.github.io/chaptertool) or go install chaptertool locally for CLI use.
10
10
 
@@ -31,6 +31,11 @@ This is the core library of the [chaptertool](https://github.com/Mtillmann/chapt
31
31
  | AppleHLS | Apple HLS Chapters | applehls | `json` | [spec](https://developer.apple.com/documentation/http-live-streaming/providing-javascript-object-notation-json-chapters), partial support |
32
32
  | Scenecut | Scenecut format | scenecut | `json` | [source](https://github.com/slhck/scenecut-extractor#:~:text=cuts%20in%20JSON-,format,-%3A) |
33
33
  | Audible | Audible Chapter Format | audible | `json` | [source](./audible-chapter-spec.md) |
34
+ | Spotify | Spotify Formats A/B | spotifya\|spotifyb | `txt` | [see](misc-text-chapter-spec.md) |
35
+ | Podcastpage | Podcastpage Format | podcastpage | `txt` | [see](misc-text-chapter-spec.md) |
36
+ | Podigee Text | Podigee Text Format | podigeetext | `txt` | [see](misc-text-chapter-spec.md) |
37
+ | TransistorFM | TransistorFM Chapter Format | transistorfm | `txt` | [see](misc-text-chapter-spec.md) |
38
+ | "Show Notes Chapters" | Unknown Shownotes Format | shownotes | `txt` | [see](misc-text-chapter-spec.md) |
34
39
 
35
40
  ## Installation
36
41
 
@@ -86,7 +91,7 @@ const chapters = (new ChaptersJson(3600)).from(input)
86
91
  ```
87
92
 
88
93
  > the constructor will not accept any input due to javascript's order of initialization which prevents the parse method
89
- > from having access to cerain locally defined properties and methods.
94
+ > from having access to certain locally defined properties and methods.
90
95
 
91
96
  ### `static create (input?: string | MediaItem): MediaItem`
92
97
 
@@ -97,7 +102,7 @@ const chapters = MatroskaXML.create(input)
97
102
  // chapters is now an instance of MatroskaXML
98
103
 
99
104
  const chapterString = WebVTT.create(chapters).toString()
100
- // chapterString is now a string representation of the chapters
105
+ // chapterString is now a WebVTT string representation of the chapters
101
106
  ```
102
107
 
103
108
  ### `from (input?: string | MediaItem): MediaItem`
@@ -112,11 +117,11 @@ Converts the media item to another format.
112
117
 
113
118
  Adds a chapter.
114
119
 
115
- ### `addChapterAt (index: number, chapter: object = {}): number`
120
+ ### `addChapterAt (index: number, chapter: Partial<Chapter> = {}): number`
116
121
 
117
122
  Adds a chapter at the given index.
118
123
 
119
- ### `addChapterAtTime (time: number | string, chapter: object = {}): boolean`
124
+ ### `addChapterAtTime (time: number | string, chapter: Partial<Chapter> = {}): boolean`
120
125
 
121
126
  Adds a chapter at the given time.
122
127