@danything/arib 1.0.2 → 1.2.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/synth.js CHANGED
@@ -10,9 +10,17 @@ import { PACKET, SYNC, withCrc } from "./psi.js";
10
10
  export { withCrc };
11
11
  const be = (value) => [(value >> 8) & 0xff, value & 0xff];
12
12
  const be32 = (value) => [...be(value >>> 16), ...be(value & 0xffff)];
13
- /** セクションの頭8バイト (長さは `withCrc` が埋める)。バージョン 0・節 0/0 */
14
- function head(tableId, extension) {
15
- return [tableId, 0x00, 0x00, ...be(extension), 0xc1, 0x00, 0x00];
13
+ /** セクションの頭 8 バイト (長さは `withCrc` が埋める) */
14
+ function head(tableId, extension, part = {}) {
15
+ return [
16
+ tableId,
17
+ 0x00,
18
+ 0x00,
19
+ ...be(extension),
20
+ 0xc1 | (((part.version ?? 0) & 0x1f) << 1),
21
+ part.sectionNumber ?? 0,
22
+ part.lastSectionNumber ?? 0,
23
+ ];
16
24
  }
17
25
  /** PCR の基準部 (90kHz) を適応フィールドに書く (旗は呼ぶ側)。拡張部 (27MHz の端数) は読む側が見ていないので 0 */
18
26
  export function putPcr(packet, pcr) {
@@ -27,8 +35,8 @@ export function putPcr(packet, pcr) {
27
35
  /**
28
36
  * Unicode → JIS X 0208 の区点。**読む側の裏返し。**
29
37
  *
30
- * 表を抱え込まずに、EUC-JP のデコーダを1回だけ総当たりして作る
31
- * (84区 × 94点 = 7896 文字)。テストと偽放送局からしか呼ばないので、
38
+ * 表を抱え込まずに、EUC-JP のデコーダを 1 回だけ総当たりして作る
39
+ * (84 区 × 94 点 = 7896 文字)。テストと偽放送局からしか呼ばないので、
32
40
  * 最初に使うときまで作らない。
33
41
  */
34
42
  let jis = null;
@@ -49,7 +57,7 @@ function jisTable() {
49
57
  return jis;
50
58
  }
51
59
  /**
52
- * 文字列を ARIB STD-B24 の8単位符号にする。
60
+ * 文字列を ARIB STD-B24 の 8 単位符号にする。
53
61
  *
54
62
  * 初期状態 (G0=漢字) から始めて、ASCII のところだけ英数集合へ切り替える。
55
63
  * 本物の放送はもっと凝った切り替え方をするが、**読む側が同じ経路を通れば足りる**。
@@ -73,13 +81,13 @@ export function encodeAribText(text, half = false) {
73
81
  out.push(0x1b, 0x24, 0x42, ...(half ? [0x8a] : []));
74
82
  alnum = false;
75
83
  const kuten = jisTable().get(char);
76
- // 表に無い字は下駄で埋める (2区14点)。長さが変わらないので位置がずれない
84
+ // 表に無い字は下駄で埋める (2 区 14 点)。長さが変わらないので位置がずれない
77
85
  out.push(...be(kuten ?? 0x222e));
78
86
  }
79
87
  return out;
80
88
  }
81
89
  /**
82
- * 番組名・局名 (SI) に書く形式。**ASCII は中型 (MSZ) で挟んで半角で読ませる** — 標準の大きさの
90
+ * 番組名・局名 (SI) に書く形式。**ASCII は中型 (MSZ) で挟んで半角で読ませる。** 標準の大きさの
83
91
  * 英数はテレビが全角で描く (読む側 `aribtext.ts` も全角にする)。全角の英数 (「Venue」) は
84
92
  * 漢字の集合の字として書くので、読めば書いたとおりの文字列に戻る
85
93
  */
@@ -88,14 +96,16 @@ function encodeSiText(text) {
88
96
  }
89
97
  /** 言語の符号 "jpn" (ISO 639-2) */
90
98
  const JPN = [0x6a, 0x70, 0x6e];
91
- /** 長さ1バイトを頭に付けた文字列。SI の記述子はどこもこの形式 */
99
+ /** 長さ 1 バイトを頭に付けた文字列。SI の記述子はどこもこの形式 */
92
100
  const sized = (text) => {
93
101
  const body = encodeSiText(text);
94
102
  return [body.length, ...body];
95
103
  };
96
- export function sdtSection(transportStreamId, originalNetworkId, services) {
104
+ export function sdtSection(transportStreamId, originalNetworkId, services,
105
+ /** `other` なら SDT[other] (0x46。ほかの TS のもの) */
106
+ options = {}) {
97
107
  const body = [
98
- ...head(0x42, transportStreamId),
108
+ ...head(options.other === true ? 0x46 : 0x42, transportStreamId, options),
99
109
  ...be(originalNetworkId),
100
110
  0xff,
101
111
  ];
@@ -107,9 +117,9 @@ export function sdtSection(transportStreamId, originalNetworkId, services) {
107
117
  }
108
118
  return withCrc(body);
109
119
  }
110
- /** 2桁の10進を BCD の1バイトに */
120
+ /** 2 桁の 10 進を BCD の 1 バイトに */
111
121
  const bcd = (value) => ((Math.floor(value / 10) << 4) | (value % 10)) & 0xff;
112
- /** epoch ms → MJD + BCD の5バイト。放送の時刻は日本時間で書く */
122
+ /** epoch ms → MJD + BCD の 5 バイト。放送の時刻は日本時間で書く */
113
123
  function mjdTime(at) {
114
124
  const jst = Math.floor(at / 1000) + 9 * 3600;
115
125
  const mjd = Math.floor(jst / 86400) + 40587;
@@ -121,7 +131,7 @@ function mjdTime(at) {
121
131
  bcd(rest % 60),
122
132
  ];
123
133
  }
124
- /** ms → BCD 3バイトの尺 */
134
+ /** ms → BCD 3 バイトの尺 */
125
135
  function bcdDuration(ms) {
126
136
  const total = Math.floor(ms / 1000);
127
137
  return [
@@ -131,11 +141,11 @@ function bcdDuration(ms) {
131
141
  ];
132
142
  }
133
143
  /**
134
- * シリーズ記述子 (0xD5。ARIB STD-B10 第2部)。
144
+ * シリーズ記述子 (0xD5。ARIB STD-B10 第 2 部)。
135
145
  *
136
146
  * series_id(16) / repeat_label(4) program_pattern(3) expire_date_valid_flag(1) /
137
147
  * expire_date(16) / episode_number(12) last_episode_number(12) / series_name_char。
138
- * 有効期限は載せない (flag 0、日付は全ビット1)
148
+ * 有効期限は載せない (flag 0、日付は全ビット 1)
139
149
  */
140
150
  function seriesDescriptor(series) {
141
151
  const episode = series.episode ?? 0;
@@ -154,7 +164,7 @@ function seriesDescriptor(series) {
154
164
  }
155
165
  /**
156
166
  * デジタルコピー制御記述子 (0xC1)。EIT の番組と PMT に載る。
157
- * 中身は1バイトだけ: 記録制御 (2) 最大ビットレートの有無 (1) 部品ごとの制御の有無 (1) 出力の制御 (4)
167
+ * 中身は 1 バイトだけ: 記録制御 (2) 最大ビットレートの有無 (1) 部品ごとの制御の有無 (1) 出力の制御 (4)
158
168
  */
159
169
  export function copyControlDescriptor(control) {
160
170
  return [0xc1, 0x01, ((control & 0b11) << 6) | 0b0100];
@@ -193,7 +203,7 @@ function eventDescriptors(event) {
193
203
  * simulcast_group_tag / フラグ / 言語 / **放送が付けた名前**。
194
204
  *
195
205
  * フラグは 多言語(1) 主音声(1) 品質(2) 標本化周波数(3) reserved(1)。
196
- * 名前は、種別も言語も同じ音声が2本あるとき**唯一の見分け**になる
206
+ * 名前は、種別も言語も同じ音声が 2 本あるとき**唯一の見分け**になる
197
207
  */
198
208
  const lang = [...(audio.lang ?? "jpn")].map((c) => c.charCodeAt(0));
199
209
  const text = encodeSiText(audio.text ?? "");
@@ -235,21 +245,66 @@ export function eitSection(options) {
235
245
  }
236
246
  return withCrc(body);
237
247
  }
238
- export function nitSection(networkId, remoteControlKeyId, streams) {
239
- const body = [...head(0x40, networkId), ...be(0xf000)];
248
+ /**
249
+ * NIT。TS ごとに `[TSID, ONID, [サービスID, 種別][], 足す記述子]`。
250
+ * 足す記述子は分配の記述子 (`satelliteDeliveryDescriptor`・`terrestrialDeliveryDescriptor`) など
251
+ */
252
+ export function nitSection(networkId, remoteControlKeyId, streams,
253
+ /** `other` なら NIT[other] (0x41。ほかのネットワークのもの) */
254
+ options = {}) {
255
+ const body = [
256
+ ...head(options.other === true ? 0x41 : 0x40, networkId, options),
257
+ ...be(0xf000),
258
+ ];
240
259
  const loop = [];
241
- for (const [transportStreamId, originalNetworkId, services] of streams) {
260
+ for (const [transportStreamId, originalNetworkId, services, extra,] of streams) {
242
261
  const descriptors = [];
243
262
  // TS information descriptor (0xCD)
244
263
  if (remoteControlKeyId !== null)
245
264
  descriptors.push(0xcd, 0x02, remoteControlKeyId, 0x00);
246
265
  const serviceList = services.flatMap(([id, type]) => [...be(id), type]);
247
266
  descriptors.push(0x41, serviceList.length, ...serviceList);
267
+ descriptors.push(...(extra ?? []));
248
268
  loop.push(...be(transportStreamId), ...be(originalNetworkId), ...be(0xf000 | descriptors.length), ...descriptors);
249
269
  }
250
270
  body.push(...be(0xf000 | loop.length), ...loop);
251
271
  return withCrc(body);
252
272
  }
273
+ /** 10 進の整数を `digits` 桁の BCD のバイト列にする (桁が奇数なら最後の半バイトは 0) */
274
+ function bcdBytes(value, digits) {
275
+ const text = String(value).padStart(digits, "0");
276
+ const out = [];
277
+ for (let i = 0; i < digits; i += 2) {
278
+ out.push((Number(text[i]) << 4) | Number(text[i + 1] ?? 0));
279
+ }
280
+ return out;
281
+ }
282
+ /**
283
+ * 衛星の分配の記述子 (0x43)。周波数は kHz、シンボルレートはシンボル/秒で渡す
284
+ * (BS-1 は 11727480 kHz・28860000)。東経 110 度・右旋・ISDB-S (変調 0x08) にしておく
285
+ */
286
+ export function satelliteDeliveryDescriptor(frequency, symbolRate = 28_860_000) {
287
+ const symbol = bcdBytes(symbolRate / 100, 7);
288
+ return [
289
+ 0x43,
290
+ 11,
291
+ ...bcdBytes(frequency / 10, 8),
292
+ ...bcdBytes(1100, 4),
293
+ 0x80 | (3 << 5) | 0x08,
294
+ symbol[0],
295
+ symbol[1],
296
+ symbol[2],
297
+ (symbol[3] & 0xf0) | 0x01,
298
+ ];
299
+ }
300
+ /** 地上の分配の記述子 (0xFA)。周波数は Hz で渡す (1/7 MHz 単位に丸める) */
301
+ export function terrestrialDeliveryDescriptor(areaCode, frequencies) {
302
+ const body = [
303
+ ...be((areaCode << 4) | (1 << 2) | 0x02),
304
+ ...frequencies.flatMap((hz) => be(Math.round((hz * 7) / 1_000_000))),
305
+ ];
306
+ return [0xfa, body.length, ...body];
307
+ }
253
308
  /** セクションを TS パケットに詰める。頭のパケットには pointer_field が付く */
254
309
  export function packetize(pid, section, counter = 0) {
255
310
  const packets = [];
@@ -265,11 +320,11 @@ export function packetize(pid, section, counter = 0) {
265
320
  }
266
321
  return Uint8Array.from(packets);
267
322
  }
268
- /** 部品を1本のバイト列に繋ぐ。組んだパケットを流し込むときの糊 */
323
+ /** 部品を 1 本のバイト列に繋ぐ。組んだパケットを流し込むときの糊 */
269
324
  export function stream(...parts) {
270
325
  return joinBytes(parts);
271
326
  }
272
- /** PCR を1つ運ぶだけのパケット */
327
+ /** PCR を 1 つ運ぶだけのパケット */
273
328
  export function pcrPacket(pid, seconds, counter = 0) {
274
329
  const packet = new Uint8Array(PACKET).fill(0xff);
275
330
  packet[0] = SYNC;
@@ -282,11 +337,11 @@ export function pcrPacket(pid, seconds, counter = 0) {
282
337
  putPcr(packet, seconds * 90_000);
283
338
  return packet;
284
339
  }
285
- /** 放送の実時刻 (TDT) を1パケットに。BML の `getCurrentDateTime` が見る */
340
+ /** 放送の実時刻 (TDT) を 1 パケットに。BML の `getCurrentDateTime` が見る */
286
341
  export function tdtPacket(unixMs) {
287
342
  return packetize(0x0014, Uint8Array.from([0x70, 0x70, 0x05, ...mjdTime(unixMs)]));
288
343
  }
289
- /** PAT。サービスID → PMT の PID */
344
+ /** PAT。サービス ID → PMT の PID */
290
345
  export function patSection(programs, transportStreamId = 1) {
291
346
  const body = head(0x00, transportStreamId);
292
347
  for (const [serviceId, pmtPid] of programs) {
@@ -295,7 +350,7 @@ export function patSection(programs, transportStreamId = 1) {
295
350
  return withCrc(body);
296
351
  }
297
352
  /**
298
- * PMT。ロゴのカルーセルが乗る ES を1つだけ持たせる。
353
+ * PMT。ロゴのカルーセルが乗る ES を 1 つだけ持たせる。
299
354
  * 目印は stream_identifier_descriptor (0x52) の component_tag
300
355
  */
301
356
  export function pmtSection(serviceId, esPid, componentTag) {
@@ -322,7 +377,7 @@ export function pmtSection(serviceId, esPid, componentTag) {
322
377
  * [service-filter.ts](service-filter.ts) を試すのに要る
323
378
  */
324
379
  export function programMap(serviceId, pcrPid,
325
- /** `[種別, PID]`。**3つ目は記述子**の並び (字幕の部品タグなど) */
380
+ /** `[種別, PID]`。**3 つ目は記述子**の並び (字幕の部品タグなど) */
326
381
  streams, ecmPid,
327
382
  /** 番組ぜんたいに掛かる記述子 (program_info) に足すもの。コピー制御 ({@link copyControlDescriptor}) など */
328
383
  extra = []) {
@@ -354,7 +409,7 @@ export function bxmlDescriptor(options = {}) {
354
409
  const { dataComponentId = 0x0c, entryPoint = true } = options;
355
410
  const body = [
356
411
  ...be(dataComponentId),
357
- // 伝送方式00 / 入口 / 自動起動 / 解像度0011 (960x540)
412
+ // 伝送方式 00 / 入口 / 自動起動 / 解像度 0011 (960x540)
358
413
  ...(entryPoint ? [0x33, 0x70] : [0x00, 0xff]),
359
414
  0xf8, // dataEventId 0xF / event_section_flag
360
415
  0xa0, // 即時取得あり / 蓄積不可 / 起動優先
@@ -365,7 +420,7 @@ export function bxmlDescriptor(options = {}) {
365
420
  * 字幕の ES (stream_type 0x06) に付ける記述子。**字幕の目印。**
366
421
  *
367
422
  * 部品タグ (stream_identifier_descriptor。ふつうの字幕は 0x30〜0x37) と、データ符号化方式記述子の
368
- * data_component_id 0x0008 (字幕)。続く1バイトの DMF・時刻制御モードは denpa も ffmpeg も見ない
423
+ * data_component_id 0x0008 (字幕)。続く 1 バイトの DMF・時刻制御モードは denpa も ffmpeg も見ない
369
424
  */
370
425
  export function captionDescriptors(componentTag = 0x30) {
371
426
  return [0x52, 0x01, componentTag, 0xfd, 0x03, 0x00, 0x08, 0x3d];
@@ -378,7 +433,7 @@ function dsmccSection(tableId, message) {
378
433
  * DII。「このモジュールは何バイトで、名前は何か」を伝える。
379
434
  *
380
435
  * **複数のモジュールを載せられる。** 本物の BS はここに `LOGO-05` と
381
- * `CS_LOGO-05` を並べて流していて (実機の `BS15_0` で確認)、1つしか
436
+ * `CS_LOGO-05` を並べて流していて (実機の `BS15:16625` で確認)、1 つしか
382
437
  * 載せられない作りでは CS を取りこぼす経路を試せない。
383
438
  */
384
439
  export function diiSection(downloadId, blockSize, modules,
@@ -430,7 +485,7 @@ transactionId = 1) {
430
485
  ...rest,
431
486
  ]);
432
487
  }
433
- /** DDB。モジュールを割ったブロックを1つ運ぶ */
488
+ /** DDB。モジュールを割ったブロックを 1 つ運ぶ */
434
489
  export function ddbSection(downloadId, moduleId, moduleVersion, blockNumber, block) {
435
490
  return dsmccSection(0x3c, [
436
491
  0x11,
@@ -461,7 +516,7 @@ export function logoModule(logoType, logos) {
461
516
  }
462
517
  /*
463
518
  * --- データ放送のテストで使う定番の割り振り ---
464
- * denpa の bml / data-timeline / recorded-bml の3つのテストが、同じ表と同じ組み立てを
519
+ * denpa の bml / data-timeline / recorded-bml の 3 つのテストが、同じ表と同じ組み立てを
465
520
  * それぞれに書いていた。番号そのものに意味は無く、揃っていることに意味がある。
466
521
  */
467
522
  export const BML_TS = {
@@ -472,7 +527,7 @@ export const BML_TS = {
472
527
  downloadId: 0xf0000001,
473
528
  componentTag: 0x40,
474
529
  };
475
- /** BML の ES が1本だけ載った PMT (`BML_TS` の割り振り) */
530
+ /** BML の ES が 1 本だけ載った PMT (`BML_TS` の割り振り) */
476
531
  export function bmlPmt() {
477
532
  return programMap(BML_TS.service, BML_TS.videoPid, [
478
533
  [0x02, BML_TS.videoPid, [0x52, 0x01, 0x00]],
@@ -491,7 +546,7 @@ export function bmlHead() {
491
546
  ];
492
547
  }
493
548
  /**
494
- * モジュール1つぶんの中身。**放送は multipart/mixed で複数のファイルを詰める。**
549
+ * モジュール 1 つぶんの中身。**放送は multipart/mixed で複数のファイルを詰める。**
495
550
  * 本物と同じく、頭に Content-Type、境界で区切って各ファイルに Content-Location
496
551
  */
497
552
  export function multipartModule(files) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danything/arib",
3
- "version": "1.0.2",
3
+ "version": "1.2.1",
4
4
  "description": "日本のデジタル放送 (ARIB) の TS を読む: PSI・PES・EIT・ARIB の文字・字幕・データ放送",
5
5
  "license": "MIT",
6
6
  "repository": {