@danything/arib 2.4.0 → 2.5.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/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  日本のデジタル放送 (ISDB-T / ISDB-S / ISDB-C、ARIB 規格) の MPEG-TS と、4K の放送の MMT/TLV を TypeScript で読むライブラリです。
4
4
  録画サーバ [denpa](https://github.com/danything/denpa) の中で使っているものを切り出しました。
5
5
 
6
- - PSI (`psi`): パケットの切り出し、セクションの組み立て、PAT / PMT / SDT / NIT。衛星はネットワークの全部の TS の NIT と、ほかの TS の SDT もそろえて読む。ケーブルは SDT だけで局をそろえる
6
+ - PSI (`psi`): パケットの切り出し、セクションの組み立て、PAT / PMT / SDT / NIT。衛星は、ネットワークにある全部の TS を載せた NIT と、ほかの TS の SDT もそろえて読む。ケーブルは SDT だけで局をそろえる
7
7
  - 映像・音声の PES と ADTS を取り出す (`pes`)
8
8
  - 番組表の EIT[p/f] と EIT[schedule] を、ジャンル・音声・拡張形式イベント・シリーズ記述子まで読む。放送の時刻 (TDT / TOT) もここ (`eit`)
9
9
  - ARIB STD-B24 の 8 単位符号 (JIS X 0213 の 1 面・2 面まで) と ARIB 外字を文字列にする (`aribtext`、`aribtext-gaiji`)
@@ -13,7 +13,7 @@
13
13
  - 1 つの局だけを TS のまま抜き出す (`service-filter`)
14
14
  - 4K の放送 (MMT/TLV。ARIB STD-B32・B60) の土台として、TLV パケットの切り出し (`tlv`)、IP とヘッダー圧縮した IP から UDP の中身を取り出す処理 (`tlv-ip`)、MMTP のヘッダーとスクランブルの印、信号メッセージの分割と集約 (`mmtp`)
15
15
  - 4K の放送の表は、TLV-NIT・PLT・MPT・MH-SDT・MH-TOT (`mmt-si`)。局の一覧と、局ごとの CA_system_id (MPT のアクセス制御記述子) をそろえて読む `TlvServiceReader` もここ (TS の `psi` の `ServiceReader` に当たる)。番組表の MH-EIT は TS と同じ形式の番組にする (`mh-eit`)。1 つの局だけを TLV のまま抜き出す (`mmt-service-filter`)
16
- - 4K の放送の映像 (HEVC)・音声 (AAC)・字幕 (TTML) のアクセスユニットを、MPT の記述子から付けた PTS・DTS (90 kHz) と一緒に取り出す (`mmt-media`)。音声は LATM を外した AAC のフレームと AudioSpecificConfig、映像は NAL ユニットの並び。字幕の TTML は、TS の字幕と同じ字幕の文字の配置にする (`ttml`)。スクランブルの復号は無い
16
+ - 4K の放送の映像 (HEVC)・音声 (AAC)・字幕 (TTML) のアクセスユニットを、MPT の記述子から付けた PTS・DTS (90 kHz) と一緒に取り出す (`mmt-media`)。音声は LATM を外した AAC のフレームと AudioSpecificConfig、映像は NAL ユニットの並び。字幕の TTML は、TS の字幕と同じ形式 (字の置き場所まで決めた JSON) にする (`ttml`)。スクランブルの復号は無い
17
17
  - 試験用の TS (PSI・EIT・字幕・カルーセル・映像と音声) を組み立てる (`synth`、`synth-caption`、`synth-av`)
18
18
  - 試験用の TLV (TLV・IP・MMTP・MMT-SI と、MPU の中身・時刻の記述子・LATM) を組み立てる (`synth-mmt`)。本物の HEVC と AAC を時刻を進めて流し続ける (`synth-mmt-av`)
19
19
  - ほかのモジュールが使う道具として、バイト列 (`bytes`)、ビット単位の読み取り (`bits`)、言語の名前 (`language`)、文字の表 (`b24-tables`)、字幕の外字の画像をなめらかにする処理 (`drcs-smooth`)
package/dist/ait.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * AIT (Application Information Table) を読む。**Hybridcast の在り処。**
2
+ * AIT (Application Information Table) を読む。**Hybridcast の在りか。**
3
3
  *
4
4
  * データ放送 (BML) と違って、**Hybridcast のアプリは放送では送られていません**。
5
5
  * 送られているのはこの表だけで、中身は「どこにアプリがあるか」のアドレス (URL) です。
@@ -11,9 +11,9 @@
11
11
  * └ simple_application_location_descriptor (その先のパス) → アプリの URL
12
12
  * ```
13
13
  *
14
- * ここは読んで在ることを言うだけです。開くのはデータ放送が起動を求めたときで
14
+ * ここはアプリの URL を読むだけです。開くのはデータ放送が起動を求めたときで
15
15
  * (画面の `HybridcastFrame`)、受信機の API は用意できる範囲だけ。放送局のサーバが
16
- * 認証された受信機を期待するものは、denpa 側の努力では越えられません
16
+ * 認証された受信機を求めるアプリは、denpa では動きません
17
17
  * ([docs/stream.md](https://github.com/danything/denpa/blob/main/docs/stream.md#hybridcast))。
18
18
  */
19
19
  /**
package/dist/ait.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * AIT (Application Information Table) を読む。**Hybridcast の在り処。**
2
+ * AIT (Application Information Table) を読む。**Hybridcast の在りか。**
3
3
  *
4
4
  * データ放送 (BML) と違って、**Hybridcast のアプリは放送では送られていません**。
5
5
  * 送られているのはこの表だけで、中身は「どこにアプリがあるか」のアドレス (URL) です。
@@ -11,9 +11,9 @@
11
11
  * └ simple_application_location_descriptor (その先のパス) → アプリの URL
12
12
  * ```
13
13
  *
14
- * ここは読んで在ることを言うだけです。開くのはデータ放送が起動を求めたときで
14
+ * ここはアプリの URL を読むだけです。開くのはデータ放送が起動を求めたときで
15
15
  * (画面の `HybridcastFrame`)、受信機の API は用意できる範囲だけ。放送局のサーバが
16
- * 認証された受信機を期待するものは、denpa 側の努力では越えられません
16
+ * 認証された受信機を求めるアプリは、denpa では動きません
17
17
  * ([docs/stream.md](https://github.com/danything/denpa/blob/main/docs/stream.md#hybridcast))。
18
18
  */
19
19
  import { decodeAribText } from "./aribtext.js";
@@ -501,4 +501,5 @@ export const DRCS_REPLACE = new Map([
501
501
  ['c42450812c184aee2ae01cc5e39ae957', 0x1f4fb], // denpa: ラジオ 📻 (72x72・16 階調)。テレ東 2026-11-01 のアニメの字幕で 1 本に 28 回
502
502
  ['06cb56043b9c4006bcfbe07cc831feaf', 0x1f50a], // denpa: 音の出ているスピーカー 🔊 (72x72・16 階調)。上と同じ番組で 4 回 (テレビ朝日・TBS のものと画像が違う)
503
503
  ['063c95566807d5e7b51ab706426bedf9', 0x1f4f1], // denpa: 携帯電話 📱 (72x72・16 階調)。上と同じ番組で 4 回 (NHK総合のものと画像が違う)
504
+ ['44cbc7dc0af84f6ab857f00a9bc2560a', 0x1f4fa], // denpa: テレビ 📺 (72x72・16 階調)。日テレ 2026-11-14 のらんま1/2 の字幕で 1 本に 2 回 (テレビ朝日のテレビと画像が違う)
504
505
  ]);
@@ -69,6 +69,13 @@ export interface ModuleFile {
69
69
  export interface PMTMessage {
70
70
  type: "pmt";
71
71
  components: ComponentPMT[];
72
+ /**
73
+ * PMT にある音声 (stream_type 0x0f の AAC) の ES の部品タグを、PMT の順に並べたもの。**部品タグの無い ES は null で並ぶ**
74
+ * (bml.ts の `pmtAudios`)。`components` は部品タグの無い ES を落とすので、何本目の音声かはこちらで数える。
75
+ * 画面の再生 (pes.ts の `readPmt`) と ffmpeg の `0:a:<n>` も、部品タグに関係なく PMT の順に数える。
76
+ * これを足す前に録った `.bml.jsonl` の PMT には無い
77
+ */
78
+ audios?: (number | null)[];
72
79
  }
73
80
  export interface ModuleDownloadedMessage {
74
81
  type: "moduleDownloaded";
package/dist/bml.d.ts CHANGED
@@ -33,6 +33,11 @@ export declare function parseBxmlInfo(data: Uint8Array): AdditionalAribBXMLInfo;
33
33
  * `BmlDecoder` の外からも使う。denpa のライブは、カルーセルをデコードする前に入口の部品の自動起動の指定をこれで読む
34
34
  */
35
35
  export declare function parsePmt(section: Uint8Array): ComponentPMT[];
36
+ /**
37
+ * PMT にある音声 (AAC) の ES の部品タグを、PMT の順に並べる (`PMTMessage.audios`)。**部品タグの無い ES は null で数に入れる。**
38
+ * `parsePmt` と違って落とさないので、何本目の音声かが画面の再生 (pes.ts の `readPmt`) と ffmpeg の `0:a:<n>` にそろう
39
+ */
40
+ export declare function pmtAudios(section: Uint8Array): (number | null)[];
36
41
  /**
37
42
  * 1 局に絞った TS を渡すと、データ放送の通知が出てくる。
38
43
  *
package/dist/bml.js CHANGED
@@ -23,7 +23,7 @@ import { u16, u32 } from "./bytes.js";
23
23
  import { dataEventIdOf, MAX_MODULE_SIZE, ModuleBuilder, parseDdb, parseDii, TABLE_DII, TABLE_STREAM_DESCRIPTOR, } from "./dsmcc.js";
24
24
  import { parseTimeTable, PID_TIME } from "./eit.js";
25
25
  import { parseMediaType, readEntity } from "./multipart.js";
26
- import { descriptors, packetPid, PacketStream, pmtStreams, PmtTracker, SectionAssembler, } from "./psi.js";
26
+ import { descriptors, packetPid, PacketStream, pmtStreams, PmtTracker, SectionAssembler, STREAM_AAC, } from "./psi.js";
27
27
  const DESC_STREAM_IDENTIFIER = 0x52;
28
28
  /** データ符号化方式記述子 (STD-B10 第 2 部 6.2.20) */
29
29
  const DESC_DATA_COMPONENT = 0xfd;
@@ -139,6 +139,23 @@ export function parsePmt(section) {
139
139
  }
140
140
  return components;
141
141
  }
142
+ /**
143
+ * PMT にある音声 (AAC) の ES の部品タグを、PMT の順に並べる (`PMTMessage.audios`)。**部品タグの無い ES は null で数に入れる。**
144
+ * `parsePmt` と違って落とさないので、何本目の音声かが画面の再生 (pes.ts の `readPmt`) と ffmpeg の `0:a:<n>` にそろう
145
+ */
146
+ export function pmtAudios(section) {
147
+ const audios = [];
148
+ for (const [streamType, , info] of pmtStreams(section)) {
149
+ if (streamType !== STREAM_AAC)
150
+ continue;
151
+ let componentId = null;
152
+ for (const [tag, descriptor] of descriptors(info))
153
+ if (tag === DESC_STREAM_IDENTIFIER && descriptor.length >= 1)
154
+ componentId = descriptor[0];
155
+ audios.push(componentId);
156
+ }
157
+ return audios;
158
+ }
142
159
  /** `at` の下位 1 ビットと続く 4 バイトで 33 ビット (STC・NPT) */
143
160
  const u33 = (data, at) => (data[at] & 1) * 0x100000000 + u32(data, at + 1);
144
161
  /** ストリーム記述子 (table_id 0x3D)。NPT と イベントメッセージが載っている */
@@ -252,14 +269,15 @@ export class BmlDecoder {
252
269
  const components = parsePmt(section);
253
270
  if (components.length === 0)
254
271
  return;
272
+ const audios = pmtAudios(section);
255
273
  /*
256
274
  * 放送は同じ表を何度も送る。**変わったときだけ配る。** 受け側は
257
275
  * PMT が来るたびに画面を組み直すので、そのまま通すと出っ放しになる
258
276
  */
259
- const shape = JSON.stringify(components);
277
+ const shape = JSON.stringify([components, audios]);
260
278
  if (this.sent !== shape) {
261
279
  this.sent = shape;
262
- this.send({ type: "pmt", components });
280
+ this.send({ type: "pmt", components, audios });
263
281
  }
264
282
  for (const component of components) {
265
283
  // カルーセルが流れているのは、BML の符号化方式が付いた ES だけ
package/dist/eit.d.ts CHANGED
@@ -70,7 +70,7 @@ export interface EitEvent {
70
70
  /** 尺 (ms)。未定なら null。番組表に入れられるのは決まっているものだけ */
71
71
  duration: number | null;
72
72
  isFree: boolean;
73
- /** 1=まもなく 2=停止中 3=一時停止 4=放送中。p/f の「いま」を見分けるのに使う */
73
+ /** 0=未定義 1=非実行中 2=数秒以内に開始 3=停止中 4=放送中 (STD-B10)。p/f の「いま」を見分けるのに使う */
74
74
  runningStatus: number;
75
75
  name: string;
76
76
  description: string;
@@ -292,7 +292,7 @@ export declare class EpgReader {
292
292
  *
293
293
  * そこで 0 のときは**セクションの番号で決める**。p/f は仕様で
294
294
  * **section 0 が現在・section 1 が次**と決まっていて、こちらは局の都合で
295
- * 変わらない。1 (まもなく) や 2 (停止中) は今までどおり入れない。
295
+ * 変わらない。1〜3 (非実行中・数秒以内に開始・停止中) は今までどおり入れない。
296
296
  */
297
297
  private onAir;
298
298
  /**
package/dist/eit.js CHANGED
@@ -652,7 +652,7 @@ export class EpgReader {
652
652
  *
653
653
  * そこで 0 のときは**セクションの番号で決める**。p/f は仕様で
654
654
  * **section 0 が現在・section 1 が次**と決まっていて、こちらは局の都合で
655
- * 変わらない。1 (まもなく) や 2 (停止中) は今までどおり入れない。
655
+ * 変わらない。1〜3 (非実行中・数秒以内に開始・停止中) は今までどおり入れない。
656
656
  */
657
657
  onAir(section, event) {
658
658
  if (event.runningStatus === 4)
package/dist/mh-eit.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 4K の放送 (MMT/TLV) の番組表 MH-EIT (ARIB STD-B60) を、TS の番組表と同じ `EitEvent` に読む。
3
3
  *
4
- * 並びは TS の EIT と同じで、違うのは次の所 (実機のケーブルの BS4K で確かめた)。
4
+ * 並びは TS の EIT と同じで、違うのは次の所 (ケーブルで受けた実機の BS4K で確かめた)。
5
5
  *
6
6
  * - TS の transport_stream_id の位置に tlv_stream_id が入る。`EitEvent.transportStreamId` にはそれを入れる
7
7
  * - table_id は p/f が 0x8B、予定の基本が 0x8C から、詳細が 0x94 から。**TS の番号 (0x4E・0x50・0x58) に直して返す**
@@ -22,7 +22,7 @@ export declare function parseMhEit(section: Uint8Array): EitSection | null;
22
22
  * 流れてくる TLV のバイト列から番組表を溜める。**読み方のほかは TS の `EpgReader` と同じ**
23
23
  * (揃ったかの数え方、別の表で来るものの混ぜ方、p/f の「いま」)。
24
24
  *
25
- * MH-EIT は自分の TLV のストリームの局のぶんだけが来る (実機のケーブルの BS4K で確かめた)。
25
+ * MH-EIT は自分の TLV ストリームの局のぶんだけ来る (ケーブルで受けた実機の BS4K で確かめた)。
26
26
  * TS の `other` (ほかの TS の局の番組表) に当たるものは無い
27
27
  */
28
28
  export declare class MmtEpgReader extends EpgReader {
package/dist/mh-eit.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 4K の放送 (MMT/TLV) の番組表 MH-EIT (ARIB STD-B60) を、TS の番組表と同じ `EitEvent` に読む。
3
3
  *
4
- * 並びは TS の EIT と同じで、違うのは次の所 (実機のケーブルの BS4K で確かめた)。
4
+ * 並びは TS の EIT と同じで、違うのは次の所 (ケーブルで受けた実機の BS4K で確かめた)。
5
5
  *
6
6
  * - TS の transport_stream_id の位置に tlv_stream_id が入る。`EitEvent.transportStreamId` にはそれを入れる
7
7
  * - table_id は p/f が 0x8B、予定の基本が 0x8C から、詳細が 0x94 から。**TS の番号 (0x4E・0x50・0x58) に直して返す**
@@ -232,7 +232,7 @@ export function parseMhEit(section) {
232
232
  * 流れてくる TLV のバイト列から番組表を溜める。**読み方のほかは TS の `EpgReader` と同じ**
233
233
  * (揃ったかの数え方、別の表で来るものの混ぜ方、p/f の「いま」)。
234
234
  *
235
- * MH-EIT は自分の TLV のストリームの局のぶんだけが来る (実機のケーブルの BS4K で確かめた)。
235
+ * MH-EIT は自分の TLV ストリームの局のぶんだけ来る (ケーブルで受けた実機の BS4K で確かめた)。
236
236
  * TS の `other` (ほかの TS の局の番組表) に当たるものは無い
237
237
  */
238
238
  export class MmtEpgReader extends EpgReader {
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * **時刻はパケットには無く、MPT の記述子にある。** MPU タイムスタンプ記述子が MPU の最初の提示時刻を、
9
9
  * MPU 拡張タイムスタンプ記述子がアクセスユニットごとのずれを、これから来る MPU のぶんまで並べる
10
- * (実機のケーブルの BS4K で確かめた。映像は 1 MPU に 32 コマ、音声は 4 フレーム)。
10
+ * (ケーブルで受けた実機の BS4K で確かめた。映像は 1 MPU に 32 コマ、音声は 4 フレーム)。
11
11
  * 時刻は NTP の 64 ビットで来るので、ここで **90 kHz** に直す (TS の PTS と同じ物差し。一周はしない)。
12
12
  *
13
13
  * 中身の形式は次のとおり。実機の録りは映像・音声がスクランブルされていて中身を読めないので、どれも推定。
package/dist/mmt-media.js CHANGED
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * **時刻はパケットには無く、MPT の記述子にある。** MPU タイムスタンプ記述子が MPU の最初の提示時刻を、
9
9
  * MPU 拡張タイムスタンプ記述子がアクセスユニットごとのずれを、これから来る MPU のぶんまで並べる
10
- * (実機のケーブルの BS4K で確かめた。映像は 1 MPU に 32 コマ、音声は 4 フレーム)。
10
+ * (ケーブルで受けた実機の BS4K で確かめた。映像は 1 MPU に 32 コマ、音声は 4 フレーム)。
11
11
  * 時刻は NTP の 64 ビットで来るので、ここで **90 kHz** に直す (TS の PTS と同じ物差し。一周はしない)。
12
12
  *
13
13
  * 中身の形式は次のとおり。実機の録りは映像・音声がスクランブルされていて中身を読めないので、どれも推定。
@@ -9,7 +9,7 @@
9
9
  * (PLT・MPT・番組表・ECM)、その局の MPT が挙げるアセット (映像・音声・字幕) の MPU
10
10
  * - 落とす … 詰め物、ほかの局の MPT とそのアセット、PLT がほかの局だけを挙げている並び
11
11
  *
12
- * 実機のケーブルの BS4K は、1 つの TLV のストリームに局が 1 つ (IP の並びも 1 つ) だったので、たいていは
12
+ * ケーブルで受けた実機の BS4K は、1 つの TLV ストリームに局が 1 つ (IP の並びも 1 つ) だったので、たいていは
13
13
  * 詰め物のほかは全部残る。AMT (局ごとの IP の宛先) は 0.8 秒の間に来なかったので使わない。
14
14
  * MPT は 0.1 秒おきに来ていた
15
15
  */
@@ -9,7 +9,7 @@
9
9
  * (PLT・MPT・番組表・ECM)、その局の MPT が挙げるアセット (映像・音声・字幕) の MPU
10
10
  * - 落とす … 詰め物、ほかの局の MPT とそのアセット、PLT がほかの局だけを挙げている並び
11
11
  *
12
- * 実機のケーブルの BS4K は、1 つの TLV のストリームに局が 1 つ (IP の並びも 1 つ) だったので、たいていは
12
+ * ケーブルで受けた実機の BS4K は、1 つの TLV ストリームに局が 1 つ (IP の並びも 1 つ) だったので、たいていは
13
13
  * 詰め物のほかは全部残る。AMT (局ごとの IP の宛先) は 0.8 秒の間に来なかったので使わない。
14
14
  * MPT は 0.1 秒おきに来ていた
15
15
  */
package/dist/mmt-si.d.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * 記述子のタグは 2 バイトで、**タグが 0xF000 未満なら長さは 1 バイト、0xF000 以上なら 2 バイト**
10
10
  *
11
11
  * 局名や番組名の文字は UTF-8 (TS の ARIB の 8 単位符号ではない)。
12
- * 実機のケーブルの BS4K では、PLT・MPT・MH-SDT[other]・MH-EIT・TLV-NIT を読めた。どれもスクランブルされていない。
12
+ * ケーブルで受けた実機の BS4K では、PLT・MPT・MH-SDT[other]・MH-EIT・TLV-NIT を読めた。どれもスクランブルされていない。
13
13
  */
14
14
  import { type FoundService, type Service } from "./psi.js";
15
15
  export declare const TABLE_MPT = 32;
@@ -93,7 +93,7 @@ export interface MhService extends Service {
93
93
  eitPresentFollowing: boolean;
94
94
  }
95
95
  export interface MhSdt {
96
- /** MH-SDT[other] (ほかの TLV のストリームのもの) なら true */
96
+ /** MH-SDT[other] (ほかの TLV ストリームのもの) なら true */
97
97
  other: boolean;
98
98
  tlvStreamId: number;
99
99
  originalNetworkId: number;
@@ -104,7 +104,7 @@ export interface MhSdt {
104
104
  }
105
105
  /** MH-SDT。局名と種類。TS の SDT と同じ並びで、記述子と文字が MMT のもの */
106
106
  export declare function parseMhSdt(section: Uint8Array): MhSdt | null;
107
- /** TLV-NIT の TLV のストリームの 1 件 */
107
+ /** TLV-NIT にある TLV ストリームの 1 件 */
108
108
  export interface TlvNetworkStream {
109
109
  tlvStreamId: number;
110
110
  originalNetworkId: number;
@@ -160,20 +160,40 @@ export declare class MmtTableStream {
160
160
  * TLV にはリモコン番号が無い (TLV-NIT に見当たらない) ので null。
161
161
  *
162
162
  * 局ごとの CA_system_id は、PLT から package (= service_id) の MPT を引き、そのアクセス制御記述子から読む (`caSystemIdsOf`)。
163
- * free_CA_mode は使わない。実機の BS4K は 0 なのに、映像と音声はスクランブルされていた
163
+ * free_CA_mode は使わない (`MhService.isFree`)。
164
+ *
165
+ * MH-SDT[actual] が来なくても、自分の TLV ストリームは TLV-NIT と PLT で分かる (`ownStream`)。
166
+ * ケーブルで受けた実機の BS4K (QC26) を 0.9 秒読んだときは、MH-SDT[other] は来たが MH-SDT[actual] は来なかった。何秒おきに来るかは確かめていない
164
167
  */
165
168
  export declare class TlvServiceReader {
166
169
  private readonly tables;
167
170
  private plt;
171
+ /** これまでに来た TLV-NIT[actual] のセクションにあった TLV ストリーム (tlv_stream_id → ストリーム) */
172
+ private readonly streams;
168
173
  /** package_id (= service_id) → MPT */
169
174
  private readonly mpts;
175
+ /**
176
+ * TLV-NIT[actual]。番号・名前・セクションの番号は最初に来たセクションのもので、`streams` はこれまでに来た全部のセクションの
177
+ * TLV ストリーム (tlv_stream_id ごとに 1 つ。同じ tlv_stream_id は後に来たもので置き換える)
178
+ */
170
179
  network: TlvNetwork | null;
171
180
  transport: MhSdt | null;
172
181
  get complete(): boolean;
173
182
  /** PLT が来て、そこにある package の MPT が全部 1 回は来たか。スキャンで、局ごとの CA_system_id が分かるまで少し長く読むのに使う */
174
183
  get mptsKnown(): boolean;
175
- /** 任意の長さのバイト列を渡す。揃ったら true */
184
+ /**
185
+ * 任意の長さのバイト列を渡す。揃ったら true。
186
+ *
187
+ * 読んだ表は渡されたチャンクの一部を指すことがあり、呼ぶ側はチャンクのバッファを使い回す。**記述子の並び (`descriptors`) と
188
+ * asset_id を持ち続ける MPT と TLV-NIT は、写してから読む**
189
+ */
176
190
  feed(chunk: Uint8Array): boolean;
191
+ /**
192
+ * 自分の TLV ストリーム。TLV-NIT[actual] のストリームのうち、PLT にある package (= service_id) をサービスリスト記述子に持つ最初の 1 つ。
193
+ * 実機では 1 つに決まる。同じ service_id を持つストリームが 2 つあれば、TLV-NIT で先に来たほうを返す。
194
+ * TLV-NIT と PLT のどちらかが来ていないか、当てはまるストリームが無ければ null
195
+ */
196
+ get ownStream(): TlvNetworkStream | null;
177
197
  /** 録画の対象になる種類の局だけ (`psi.SERVICE_TYPES`)。揃うまでは空 */
178
198
  services(): FoundService[];
179
199
  /** その局の MPT のアクセス制御記述子 (package 全体とアセットのもの)。MPT が来なかった局は空 */
package/dist/mmt-si.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * 記述子のタグは 2 バイトで、**タグが 0xF000 未満なら長さは 1 バイト、0xF000 以上なら 2 バイト**
10
10
  *
11
11
  * 局名や番組名の文字は UTF-8 (TS の ARIB の 8 単位符号ではない)。
12
- * 実機のケーブルの BS4K では、PLT・MPT・MH-SDT[other]・MH-EIT・TLV-NIT を読めた。どれもスクランブルされていない。
12
+ * ケーブルで受けた実機の BS4K では、PLT・MPT・MH-SDT[other]・MH-EIT・TLV-NIT を読めた。どれもスクランブルされていない。
13
13
  */
14
14
  import { ascii, u16 } from "./bytes.js";
15
15
  import { parseMjdTime } from "./eit.js";
@@ -389,13 +389,22 @@ export class MmtTableStream {
389
389
  * TLV にはリモコン番号が無い (TLV-NIT に見当たらない) ので null。
390
390
  *
391
391
  * 局ごとの CA_system_id は、PLT から package (= service_id) の MPT を引き、そのアクセス制御記述子から読む (`caSystemIdsOf`)。
392
- * free_CA_mode は使わない。実機の BS4K は 0 なのに、映像と音声はスクランブルされていた
392
+ * free_CA_mode は使わない (`MhService.isFree`)。
393
+ *
394
+ * MH-SDT[actual] が来なくても、自分の TLV ストリームは TLV-NIT と PLT で分かる (`ownStream`)。
395
+ * ケーブルで受けた実機の BS4K (QC26) を 0.9 秒読んだときは、MH-SDT[other] は来たが MH-SDT[actual] は来なかった。何秒おきに来るかは確かめていない
393
396
  */
394
397
  export class TlvServiceReader {
395
398
  tables = new MmtTableStream();
396
399
  plt = null;
400
+ /** これまでに来た TLV-NIT[actual] のセクションにあった TLV ストリーム (tlv_stream_id → ストリーム) */
401
+ streams = new Map();
397
402
  /** package_id (= service_id) → MPT */
398
403
  mpts = new Map();
404
+ /**
405
+ * TLV-NIT[actual]。番号・名前・セクションの番号は最初に来たセクションのもので、`streams` はこれまでに来た全部のセクションの
406
+ * TLV ストリーム (tlv_stream_id ごとに 1 つ。同じ tlv_stream_id は後に来たもので置き換える)
407
+ */
399
408
  network = null;
400
409
  transport = null;
401
410
  get complete() {
@@ -406,24 +415,53 @@ export class TlvServiceReader {
406
415
  return (this.plt !== null &&
407
416
  this.plt.packages.every((entry) => this.mpts.has(entry.packageId)));
408
417
  }
409
- /** 任意の長さのバイト列を渡す。揃ったら true */
418
+ /**
419
+ * 任意の長さのバイト列を渡す。揃ったら true。
420
+ *
421
+ * 読んだ表は渡されたチャンクの一部を指すことがあり、呼ぶ側はチャンクのバッファを使い回す。**記述子の並び (`descriptors`) と
422
+ * asset_id を持ち続ける MPT と TLV-NIT は、写してから読む**
423
+ */
410
424
  feed(chunk) {
411
425
  for (const { table } of this.tables.feed(chunk)) {
412
426
  const tableId = table[0];
413
427
  if (tableId === TABLE_PLT)
414
428
  this.plt = parsePlt(table) ?? this.plt;
415
429
  else if (tableId === TABLE_MPT) {
416
- const mpt = parseMpt(table);
430
+ const mpt = parseMpt(table.slice());
417
431
  if (mpt !== null)
418
432
  this.mpts.set(mpt.packageId, mpt);
419
433
  }
420
- else if (tableId === TABLE_TLV_NIT_ACTUAL && this.network === null)
421
- this.network = parseTlvNit(table);
434
+ else if (tableId === TABLE_TLV_NIT_ACTUAL) {
435
+ const network = parseTlvNit(table.slice());
436
+ if (network === null)
437
+ continue;
438
+ for (const stream of network.streams)
439
+ this.streams.set(stream.tlvStreamId, stream);
440
+ // ネットワークの番号と名前は最初に来たセクションのもの。ストリームは来たセクションの全部
441
+ this.network = {
442
+ ...(this.network ?? network),
443
+ streams: [...this.streams.values()],
444
+ };
445
+ }
422
446
  else if (tableId === TABLE_MH_SDT_ACTUAL && this.transport === null)
423
447
  this.transport = parseMhSdt(table);
424
448
  }
425
449
  return this.complete;
426
450
  }
451
+ /**
452
+ * 自分の TLV ストリーム。TLV-NIT[actual] のストリームのうち、PLT にある package (= service_id) をサービスリスト記述子に持つ最初の 1 つ。
453
+ * 実機では 1 つに決まる。同じ service_id を持つストリームが 2 つあれば、TLV-NIT で先に来たほうを返す。
454
+ * TLV-NIT と PLT のどちらかが来ていないか、当てはまるストリームが無ければ null
455
+ */
456
+ get ownStream() {
457
+ const packages = new Set((this.plt?.packages ?? []).map((entry) => entry.packageId));
458
+ if (packages.size === 0)
459
+ return null;
460
+ for (const stream of this.streams.values())
461
+ if (stream.services.some((service) => packages.has(service.serviceId)))
462
+ return stream;
463
+ return null;
464
+ }
427
465
  /** 録画の対象になる種類の局だけ (`psi.SERVICE_TYPES`)。揃うまでは空 */
428
466
  services() {
429
467
  const transport = this.transport;
@@ -438,6 +476,8 @@ export class TlvServiceReader {
438
476
  networkId: transport.originalNetworkId,
439
477
  transportStreamId: transport.tlvStreamId,
440
478
  remoteControlKeyId: null,
479
+ // 4K の放送に部分受信 (ワンセグ) は無い
480
+ partialReception: false,
441
481
  }));
442
482
  }
443
483
  /** その局の MPT のアクセス制御記述子 (package 全体とアセットのもの)。MPT が来なかった局は空 */
package/dist/mmtp.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * 映像・音声・字幕を運ぶ MPU (payload_type 0) と、局の中身の一覧や番組表を運ぶ信号メッセージ (2)。
6
6
  * どちらかは packet_id ごとに決まっている。
7
7
  *
8
- * **4K の放送では映像と音声がスクランブルされ、信号はされていない** (実機のケーブルの BS4K で確かめた)。
8
+ * **4K の放送では映像と音声がスクランブルされ、信号はされていない** (ケーブルで受けた実機の BS4K で確かめた)。
9
9
  * スクランブルの印はヘッダー拡張にあり (ARIB STD-B61)、MPU のペイロードヘッダー (8 バイト) までは読める。
10
10
  * 復号はエージェントの仕事 (まだ扱わない)。
11
11
  */
@@ -58,7 +58,7 @@ export declare function parseMessage(data: Uint8Array): SignalingMessage | null;
58
58
  * 信号メッセージに入った表を返す。
59
59
  *
60
60
  * - PA メッセージは表の数と、表ごとの (table_id・バージョン・長さ) を先に並べ、そのあとに表を並べる。
61
- * 表の頭は table_id・バージョン・長さ (16 ビット) で、MPT と PLT はこの形式
61
+ * 表の先頭は table_id・バージョン・長さ (16 ビット) で、MPT と PLT はこの形式
62
62
  * - M2 セクションメッセージ・M2 短セクションメッセージ・CA メッセージは、表を 1 つだけ運ぶ。
63
63
  * M2 セクションは TS のセクションと同じ形式で、末尾に CRC32 が付く (確かめるのは `mmt-si.ts`)
64
64
  */
@@ -68,7 +68,7 @@ export declare function messageTables(message: SignalingMessage): Uint8Array[];
68
68
  *
69
69
  * - 長いメッセージ (番組表の MH-EIT や大きな MPT) は、いくつもの MMTP のパケットに分割して送られる。
70
70
  * 欠けは packet_sequence_number (packet_id ごとに 1 ずつ増える) で見る。続いていなければ、組み立て中のものを
71
- * 捨てて次の先頭を待つ。**fragment_counter は当てにしない。** 実機のケーブルの BS4K は分割したものでも
71
+ * 捨てて次の先頭を待つ。**fragment_counter は当てにしない。** ケーブルで受けた実機の BS4K は分割したものでも
72
72
  * いつも 0 を入れていた
73
73
  * - 短いメッセージは、1 つの MMTP のパケットに長さを前に付けて集約される (aggregation_flag)。
74
74
  * 長さは length_extension_flag が立っていれば 32 ビット、立っていなければ 16 ビット
package/dist/mmtp.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * 映像・音声・字幕を運ぶ MPU (payload_type 0) と、局の中身の一覧や番組表を運ぶ信号メッセージ (2)。
6
6
  * どちらかは packet_id ごとに決まっている。
7
7
  *
8
- * **4K の放送では映像と音声がスクランブルされ、信号はされていない** (実機のケーブルの BS4K で確かめた)。
8
+ * **4K の放送では映像と音声がスクランブルされ、信号はされていない** (ケーブルで受けた実機の BS4K で確かめた)。
9
9
  * スクランブルの印はヘッダー拡張にあり (ARIB STD-B61)、MPU のペイロードヘッダー (8 バイト) までは読める。
10
10
  * 復号はエージェントの仕事 (まだ扱わない)。
11
11
  */
@@ -116,7 +116,7 @@ export function parseMessage(data) {
116
116
  * 信号メッセージに入った表を返す。
117
117
  *
118
118
  * - PA メッセージは表の数と、表ごとの (table_id・バージョン・長さ) を先に並べ、そのあとに表を並べる。
119
- * 表の頭は table_id・バージョン・長さ (16 ビット) で、MPT と PLT はこの形式
119
+ * 表の先頭は table_id・バージョン・長さ (16 ビット) で、MPT と PLT はこの形式
120
120
  * - M2 セクションメッセージ・M2 短セクションメッセージ・CA メッセージは、表を 1 つだけ運ぶ。
121
121
  * M2 セクションは TS のセクションと同じ形式で、末尾に CRC32 が付く (確かめるのは `mmt-si.ts`)
122
122
  */
@@ -155,7 +155,7 @@ const PENDING_MOST = 256 * 1024;
155
155
  *
156
156
  * - 長いメッセージ (番組表の MH-EIT や大きな MPT) は、いくつもの MMTP のパケットに分割して送られる。
157
157
  * 欠けは packet_sequence_number (packet_id ごとに 1 ずつ増える) で見る。続いていなければ、組み立て中のものを
158
- * 捨てて次の先頭を待つ。**fragment_counter は当てにしない。** 実機のケーブルの BS4K は分割したものでも
158
+ * 捨てて次の先頭を待つ。**fragment_counter は当てにしない。** ケーブルで受けた実機の BS4K は分割したものでも
159
159
  * いつも 0 を入れていた
160
160
  * - 短いメッセージは、1 つの MMTP のパケットに長さを前に付けて集約される (aggregation_flag)。
161
161
  * 長さは length_extension_flag が立っていれば 32 ビット、立っていなければ 16 ビット
package/dist/pes.js CHANGED
@@ -9,14 +9,13 @@
9
9
  * DOM も WASM も触らないので、ここだけ単体で確かめられる (`pes.test.ts`)。
10
10
  */
11
11
  import { concatBytes, u16 } from "./bytes.js";
12
- import { PACKET, packetPid, PacketStream, PID_PAT, pmtStreams, PmtTracker, } from "./psi.js";
12
+ import { PACKET, packetPid, PacketStream, PID_PAT, pmtStreams, PmtTracker, STREAM_AAC, } from "./psi.js";
13
13
  /** PTS・PCR の刻み (90kHz)。denpa の取り決めの時刻もこの刻み (docs/stream.md#websocket-プロトコル) */
14
14
  export const CLOCK = 90_000;
15
15
  /** PTS の一周 (33 ビット)。26.5 時間 */
16
16
  export const WRAP = 2 ** 33;
17
- /** PMT の stream_type。**放送の映像は MPEG-2、音声は ADTS の AAC** (ARIB STD-B32) */
17
+ /** PMT の stream_type。**放送の映像は MPEG-2、音声は ADTS の AAC** (ARIB STD-B32。AAC は psi.ts の `STREAM_AAC`) */
18
18
  const STREAM_MPEG2 = 0x02;
19
- const STREAM_AAC = 0x0f;
20
19
  /** 5 バイトの PTS / DTS を読む (ISO/IEC 13818-1 2.4.3.7) */
21
20
  export function readStamp(data, at) {
22
21
  return (((data[at] >> 1) & 0x07) * 2 ** 30 +
package/dist/psi.d.ts CHANGED
@@ -8,8 +8,8 @@
8
8
  * - SDT (PID 0x0011) … その TS に入っているサービスの ID と種別。衛星はほかの TS のぶん
9
9
  * (SDT[other]) も流れている (`SdtReader`)
10
10
  *
11
- * 局名も SDT から読む。ARIB の文字符号は独自だが、番組表を自分で集めるなら
12
- * どのみち要るので [aribtext.ts](aribtext.ts) に寄せてある。
11
+ * 局名も SDT から読む。ARIB の文字符号は独自だが、番組表を取得するにも要るので
12
+ * [aribtext.ts](aribtext.ts) に寄せてある。
13
13
  */
14
14
  export declare const PACKET = 188;
15
15
  export declare const SYNC = 71;
@@ -23,8 +23,8 @@ export declare const TABLE_PMT = 2;
23
23
  export declare const TABLE_SDT_ACTUAL = 66;
24
24
  /**
25
25
  * スキャンで残すサービス種別。Mirakurun のスキャンが通しているものと同じで、
26
- * ラジオ (0x02) とデータ (0xc0) も入る。映像の無いものを番組表に並べないのは
27
- * 取り込むほう (denpa の epg.ts の DIGITAL_TV)
26
+ * ラジオ (0x02) とデータ (0xc0) も入る。どの種別を番組表に並べるかは
27
+ * 取り込むほうが決める (denpa の schema.ts の `IMPORTED_SERVICE_TYPES`)
28
28
  */
29
29
  export declare const SERVICE_TYPES: Set<number>;
30
30
  /** MPEG-2 の CRC32。セクション末尾の 4 バイトを含めて回すと 0 になる */
@@ -50,7 +50,7 @@ export interface TransportInfo {
50
50
  }
51
51
  export interface NetworkInfo {
52
52
  networkId: number;
53
- /** 最初の TS のリモコン番号。地上波の NIT は自分の TS 1 つだけを載せる */
53
+ /** TS 情報記述子を最初に載せていた TS のリモコン番号。地上波の NIT は自分の TS 1 つだけを載せる */
54
54
  remoteControlKeyId: number | null;
55
55
  transportStreams: NetworkTransport[];
56
56
  }
@@ -78,7 +78,7 @@ export declare class SectionAssembler {
78
78
  * パケットを 1 つ渡す。組み上がったセクションを返す。
79
79
  *
80
80
  * 返りは**読むだけ**にすること。何も組み上がらないとき (ほとんどのパケット) は
81
- * 使い回しの空配列を返す (関係ないパケットのたびに配列を作っていた)
81
+ * 使い回しの空配列を返す (関係ないパケットのたびに配列を作らないため)
82
82
  */
83
83
  feed(packet: Uint8Array): readonly Uint8Array[];
84
84
  private append;
@@ -120,6 +120,11 @@ export declare class PmtTracker {
120
120
  export declare function descriptors(data: Uint8Array): Generator<[number, Uint8Array]>;
121
121
  /** デジタルコピー制御記述子。EIT の番組ごとと PMT に入る */
122
122
  export declare const DESC_DIGITAL_COPY_CONTROL = 193;
123
+ /**
124
+ * 音声の ES の stream_type (AAC。日本の放送の音声)。主音声の何本目かは、どこでもこの stream_type だけを PMT の順に数える
125
+ * (pes.ts の `readPmt`・bml.ts の `pmtAudios`・データ放送の主音声)。ffmpeg の `0:a:<n>` ともそろう
126
+ */
127
+ export declare const STREAM_AAC = 15;
123
128
  /**
124
129
  * デジタルコピー制御記述子の中身 (タグと長さを除いたもの) から、**コピー禁止か**を読む。
125
130
  *
@@ -181,12 +186,39 @@ export interface TerrestrialDelivery {
181
186
  /** 周波数 (Hz)。中継局ごとに並ぶことがある。放送は 1/7 MHz 単位で書く */
182
187
  frequencies: number[];
183
188
  }
184
- export type Delivery = SatelliteDelivery | TerrestrialDelivery;
189
+ /**
190
+ * ケーブルの分配の記述子 (0x44)。どの周波数で、どう変調して流れているか。
191
+ *
192
+ * **並びは推定。** 有線の告示にある 0x44 の本文は読めていない。同じ系統の有線複数搬送波伝送分配システム記述子 (0xF3。総務省の告示の改正案) は
193
+ * 周波数 (32)・0xFF (8)・多重フレーム型式番号 (4)・外符号 (4)・変調 (8)・シンボルレート (28)・内符号 (4) の並びで、0x44 も同じと見ている。
194
+ * DVB (EN 300 468) では、多重フレーム型式番号の 4 ビットは reserved
195
+ */
196
+ export interface CableDelivery {
197
+ kind: "cable";
198
+ /** 周波数 (Hz)。放送は MHz の BCD 8 桁 (小数 4 桁) で書くので、100 Hz 単位 */
199
+ frequency: number;
200
+ /** 多重フレーム型式番号。0x1 は TSMF (53 スロット・最大 15 TS)、0xF は TS が 1 つの搬送波 */
201
+ frameType: number;
202
+ /** 外符号。0x2 は RS(204,188) */
203
+ fecOuter: number;
204
+ /** 変調。0x03 は 64QAM、0x05 は 256QAM */
205
+ modulation: number;
206
+ /** シンボルレート (シンボル/秒)。ISDB-C は 5274000 */
207
+ symbolRate: number;
208
+ /** 内符号。0xF は内符号なし */
209
+ fecInner: number;
210
+ }
211
+ export type Delivery = SatelliteDelivery | TerrestrialDelivery | CableDelivery;
185
212
  /** NIT の TS の 1 件。サービスの一覧に、どこで流れているかが付く */
186
213
  export interface NetworkTransport extends TransportInfo {
187
214
  delivery: Delivery | null;
188
215
  /** その TS のリモコン番号 (TS 情報記述子)。載せていなければ null */
189
216
  remoteControlKeyId: number | null;
217
+ /**
218
+ * 部分受信の局 (ワンセグ) の service_id (部分受信記述子 0xFB)。地上波の NIT[actual] が自分の TS に載せる。
219
+ * 載せていなければ空
220
+ */
221
+ partialReception: number[];
190
222
  }
191
223
  /** そろった NIT。セクションをつないだもの */
192
224
  export interface Network {
@@ -194,8 +226,12 @@ export interface Network {
194
226
  other: boolean;
195
227
  networkId: number;
196
228
  version: number;
229
+ /** ネットワーク名 (0x40)。載っていなければ null。`NitReader` を通さずに作った表では省いてよい */
230
+ name?: string | null;
197
231
  remoteControlKeyId: number | null;
198
232
  transportStreams: NetworkTransport[];
233
+ /** セクションのバイト列 (section_number の順)。読み方を確かめるために記録に出す (ケーブルのスキャン)。省いてよい */
234
+ sections?: Uint8Array[];
199
235
  }
200
236
  /**
201
237
  * NIT を**全部のセクション**そろえて読む。
@@ -257,6 +293,11 @@ export interface FoundService extends Service {
257
293
  networkId: number;
258
294
  transportStreamId: number;
259
295
  remoteControlKeyId: number | null;
296
+ /**
297
+ * 部分受信の局 (ワンセグ) か。NIT の自分の TS に部分受信記述子 (0xFB) があり、そこに載っていれば true。
298
+ * 記述子の無いネットワーク (BS・CS と、確かめていないケーブル) と NIT を読まなかったときは false
299
+ */
300
+ partialReception: boolean;
260
301
  }
261
302
  /**
262
303
  * 流れてくる TS を読んで、NIT と SDT が揃うまで待つ。
@@ -268,7 +309,7 @@ export interface FoundService extends Service {
268
309
  * **ケーブル (`requireNit: false`) は SDT[actual] だけで揃ったとみなす。** 1 つのネットワークが
269
310
  * 地上波・BS の再送信と自主放送の TS をまとめて流していて、NIT は CATV のネットワークのもの
270
311
  * (局の元の放送とは別)。どの放送の局かは SDT の original_network_id で分かる。
271
- * NIT が先に来ていれば、その TS のリモコン番号だけを使う。
312
+ * NIT が先に来ていれば、その TS のリモコン番号と部分受信の局だけを使う。
272
313
  * PAT に局が 1 つも無い TS (データだけの TS) も揃ったとみなし、局は返さない (`empty`)
273
314
  */
274
315
  export declare class ServiceReader {
@@ -287,6 +328,6 @@ export declare class ServiceReader {
287
328
  get complete(): boolean;
288
329
  /** 任意の長さのバイト列を渡す。揃ったら true */
289
330
  feed(chunk: Uint8Array): boolean;
290
- /** 録るに値するサービスだけ。リモコン番号は NIT のものを配る */
331
+ /** 録画の対象になる種類の局だけ (`SERVICE_TYPES`)。リモコン番号と部分受信の局は NIT から付ける */
291
332
  services(): FoundService[];
292
333
  }
package/dist/psi.js CHANGED
@@ -8,8 +8,8 @@
8
8
  * - SDT (PID 0x0011) … その TS に入っているサービスの ID と種別。衛星はほかの TS のぶん
9
9
  * (SDT[other]) も流れている (`SdtReader`)
10
10
  *
11
- * 局名も SDT から読む。ARIB の文字符号は独自だが、番組表を自分で集めるなら
12
- * どのみち要るので [aribtext.ts](aribtext.ts) に寄せてある。
11
+ * 局名も SDT から読む。ARIB の文字符号は独自だが、番組表を取得するにも要るので
12
+ * [aribtext.ts](aribtext.ts) に寄せてある。
13
13
  */
14
14
  import { decodeAribText } from "./aribtext.js";
15
15
  import { concatBytes, u16 } from "./bytes.js";
@@ -29,15 +29,18 @@ const TABLE_NIT_ACTUAL = 0x40;
29
29
  const TABLE_NIT_OTHER = 0x41;
30
30
  export const TABLE_SDT_ACTUAL = 0x42;
31
31
  const TABLE_SDT_OTHER = 0x46;
32
+ const DESC_NETWORK_NAME = 0x40;
32
33
  const DESC_SERVICE_LIST = 0x41;
33
34
  const DESC_SATELLITE_DELIVERY = 0x43;
35
+ const DESC_CABLE_DELIVERY = 0x44;
34
36
  const DESC_TERRESTRIAL_DELIVERY = 0xfa;
35
37
  const DESC_SERVICE = 0x48;
36
38
  const DESC_TS_INFORMATION = 0xcd;
39
+ const DESC_PARTIAL_RECEPTION = 0xfb;
37
40
  /**
38
41
  * スキャンで残すサービス種別。Mirakurun のスキャンが通しているものと同じで、
39
- * ラジオ (0x02) とデータ (0xc0) も入る。映像の無いものを番組表に並べないのは
40
- * 取り込むほう (denpa の epg.ts の DIGITAL_TV)
42
+ * ラジオ (0x02) とデータ (0xc0) も入る。どの種別を番組表に並べるかは
43
+ * 取り込むほうが決める (denpa の schema.ts の `IMPORTED_SERVICE_TYPES`)
41
44
  */
42
45
  export const SERVICE_TYPES = new Set([
43
46
  0x01, 0x02, 0xa1, 0xa4, 0xa5, 0xad, 0xc0,
@@ -109,7 +112,7 @@ export class SectionAssembler {
109
112
  * パケットを 1 つ渡す。組み上がったセクションを返す。
110
113
  *
111
114
  * 返りは**読むだけ**にすること。何も組み上がらないとき (ほとんどのパケット) は
112
- * 使い回しの空配列を返す (関係ないパケットのたびに配列を作っていた)
115
+ * 使い回しの空配列を返す (関係ないパケットのたびに配列を作らないため)
113
116
  */
114
117
  feed(packet) {
115
118
  // トランスポートエラーが立っているものは信用しない
@@ -311,6 +314,11 @@ export function* descriptors(data) {
311
314
  }
312
315
  /** デジタルコピー制御記述子。EIT の番組ごとと PMT に入る */
313
316
  export const DESC_DIGITAL_COPY_CONTROL = 0xc1;
317
+ /**
318
+ * 音声の ES の stream_type (AAC。日本の放送の音声)。主音声の何本目かは、どこでもこの stream_type だけを PMT の順に数える
319
+ * (pes.ts の `readPmt`・bml.ts の `pmtAudios`・データ放送の主音声)。ffmpeg の `0:a:<n>` ともそろう
320
+ */
321
+ export const STREAM_AAC = 0x0f;
314
322
  /**
315
323
  * デジタルコピー制御記述子の中身 (タグと長さを除いたもの) から、**コピー禁止か**を読む。
316
324
  *
@@ -458,6 +466,23 @@ function terrestrialDelivery(descriptor) {
458
466
  frequencies,
459
467
  };
460
468
  }
469
+ /**
470
+ * ケーブルの分配の記述子を読む。周波数は 8 桁の BCD で、上から 4 桁が MHz (0111.0000 MHz)。
471
+ * シンボルレートは 7 桁で、上から 3 桁が Msym/s (005.2740)
472
+ */
473
+ function cableDelivery(descriptor) {
474
+ if (descriptor.length < 11)
475
+ return null;
476
+ return {
477
+ kind: "cable",
478
+ frequency: bcdDigits(descriptor, 0, 8) * 100,
479
+ frameType: descriptor[5] >> 4,
480
+ fecOuter: descriptor[5] & 0x0f,
481
+ modulation: descriptor[6],
482
+ symbolRate: bcdDigits(descriptor, 7, 7) * 100,
483
+ fecInner: descriptor[10] & 0x0f,
484
+ };
485
+ }
461
486
  /** NIT のセクションを 1 つ読む。actual (0x40) と other (0x41) のどちらも */
462
487
  function parseNitSection(section) {
463
488
  const tableId = section[0];
@@ -469,6 +494,11 @@ function parseNitSection(section) {
469
494
  let at = 10 + networkLength;
470
495
  if (at + 2 > section.length)
471
496
  return null;
497
+ let name = null;
498
+ for (const [tag, descriptor] of descriptors(section.subarray(10, at))) {
499
+ if (tag === DESC_NETWORK_NAME)
500
+ name ??= decodeAribText(descriptor);
501
+ }
472
502
  at += 2; // transport_stream_loop_length
473
503
  let remoteControlKeyId = null;
474
504
  const transportStreams = [];
@@ -482,11 +512,16 @@ function parseNitSection(section) {
482
512
  const services = [];
483
513
  let delivery = null;
484
514
  let remote = null;
515
+ const partialReception = [];
485
516
  for (const [tag, descriptor] of descriptors(body)) {
486
517
  if (tag === DESC_TS_INFORMATION && descriptor.length > 0) {
487
518
  remote ??= descriptor[0];
488
519
  remoteControlKeyId ??= remote;
489
520
  }
521
+ else if (tag === DESC_PARTIAL_RECEPTION) {
522
+ for (let i = 0; i + 2 <= descriptor.length; i += 2)
523
+ partialReception.push(u16(descriptor, i));
524
+ }
490
525
  else if (tag === DESC_SERVICE_LIST) {
491
526
  for (let i = 0; i + 3 <= descriptor.length; i += 3) {
492
527
  services.push({
@@ -502,6 +537,9 @@ function parseNitSection(section) {
502
537
  else if (tag === DESC_TERRESTRIAL_DELIVERY) {
503
538
  delivery = terrestrialDelivery(descriptor) ?? delivery;
504
539
  }
540
+ else if (tag === DESC_CABLE_DELIVERY) {
541
+ delivery = cableDelivery(descriptor) ?? delivery;
542
+ }
505
543
  }
506
544
  transportStreams.push({
507
545
  transportStreamId,
@@ -509,6 +547,7 @@ function parseNitSection(section) {
509
547
  services,
510
548
  delivery,
511
549
  remoteControlKeyId: remote,
550
+ partialReception,
512
551
  });
513
552
  }
514
553
  return {
@@ -517,12 +556,14 @@ function parseNitSection(section) {
517
556
  version: (section[5] >> 1) & 0x1f,
518
557
  sectionNumber: section[6],
519
558
  lastSectionNumber: section[7],
559
+ name,
520
560
  remoteControlKeyId,
521
561
  transportStreams,
562
+ raw: section.slice(),
522
563
  };
523
564
  }
524
565
  /**
525
- * NIT からネットワーク ID とリモコン番号、他の TS の顔ぶれを読む。**セクション 1 つぶん。**
566
+ * NIT からネットワーク ID とリモコン番号、TS の一覧を読む。**セクション 1 つぶん。**
526
567
  * スキャンで「どのネットワークか」を知るにはこれで足りる。ネットワークの全部の TS が要るなら `NitReader`
527
568
  */
528
569
  function parseNit(section) {
@@ -622,9 +663,11 @@ export class NitReader {
622
663
  other: section.other,
623
664
  networkId: section.networkId,
624
665
  version: section.version,
666
+ name: parts.find((part) => part.name !== null)?.name ?? null,
625
667
  remoteControlKeyId: parts.find((part) => part.remoteControlKeyId !== null)
626
668
  ?.remoteControlKeyId ?? null,
627
669
  transportStreams: parts.flatMap((part) => part.transportStreams),
670
+ sections: parts.map((part) => part.raw),
628
671
  });
629
672
  return true;
630
673
  }
@@ -720,7 +763,7 @@ export class SdtReader {
720
763
  * **ケーブル (`requireNit: false`) は SDT[actual] だけで揃ったとみなす。** 1 つのネットワークが
721
764
  * 地上波・BS の再送信と自主放送の TS をまとめて流していて、NIT は CATV のネットワークのもの
722
765
  * (局の元の放送とは別)。どの放送の局かは SDT の original_network_id で分かる。
723
- * NIT が先に来ていれば、その TS のリモコン番号だけを使う。
766
+ * NIT が先に来ていれば、その TS のリモコン番号と部分受信の局だけを使う。
724
767
  * PAT に局が 1 つも無い TS (データだけの TS) も揃ったとみなし、局は返さない (`empty`)
725
768
  */
726
769
  export class ServiceReader {
@@ -763,12 +806,15 @@ export class ServiceReader {
763
806
  }
764
807
  return this.complete;
765
808
  }
766
- /** 録るに値するサービスだけ。リモコン番号は NIT のものを配る */
809
+ /** 録画の対象になる種類の局だけ (`SERVICE_TYPES`)。リモコン番号と部分受信の局は NIT から付ける */
767
810
  services() {
768
811
  const transport = this.transport;
769
812
  if (transport === null)
770
813
  return [];
771
814
  const { transportStreamId, originalNetworkId } = transport;
815
+ const own = this.network?.transportStreams.find((ts) => ts.transportStreamId === transportStreamId &&
816
+ ts.originalNetworkId === originalNetworkId);
817
+ const partial = new Set(own?.partialReception ?? []);
772
818
  const found = (networkId, remoteControlKeyId) => transport.services
773
819
  .filter((service) => SERVICE_TYPES.has(service.serviceType))
774
820
  .map((service) => ({
@@ -776,14 +822,13 @@ export class ServiceReader {
776
822
  networkId,
777
823
  transportStreamId,
778
824
  remoteControlKeyId,
825
+ partialReception: partial.has(service.serviceId),
779
826
  }));
780
827
  if (this.requireNit) {
781
828
  if (this.network === null)
782
829
  return [];
783
830
  return found(this.network.networkId, this.network.remoteControlKeyId);
784
831
  }
785
- const own = this.network?.transportStreams.find((ts) => ts.transportStreamId === transportStreamId &&
786
- ts.originalNetworkId === originalNetworkId);
787
832
  return found(originalNetworkId, own?.remoteControlKeyId ?? null);
788
833
  }
789
834
  }
@@ -66,10 +66,10 @@ export declare class ServiceFilter {
66
66
  /**
67
67
  * **中継の番号 (transport_stream_id)。** PAT を読むまでは null。
68
68
  *
69
- * 絞るのに要るわけではない。**データ放送が受信機に訊いてくる**ので
70
- * 覚えておく。テレ朝の TVer リンクは `browser.getProgramID(4)` で
71
- * これを取り、TVer のサーバへそのまま送る。`null` のまま渡すと文字列の
72
- * "null" が送られて、あちらが断り、画面には `DT-RE` とだけ出る
69
+ * 絞るのに要るわけではない。**データ放送が受信機の API で読む**ので
70
+ * 持っておく。テレ朝の TVer リンクは `browser.getProgramID(4)` で
71
+ * これを取得し、TVer のサーバへそのまま送る。`null` のまま渡すと文字列の
72
+ * "null" が送られて TVer のサーバがエラーを返し、画面には `DT-RE` とだけ出る
73
73
  * ([server/live.ts](https://github.com/danything/denpa/blob/main/src/lib/server/live.ts) の `programInfo`)
74
74
  */
75
75
  get transportStreamId(): number | null;
@@ -137,10 +137,10 @@ export class ServiceFilter {
137
137
  /**
138
138
  * **中継の番号 (transport_stream_id)。** PAT を読むまでは null。
139
139
  *
140
- * 絞るのに要るわけではない。**データ放送が受信機に訊いてくる**ので
141
- * 覚えておく。テレ朝の TVer リンクは `browser.getProgramID(4)` で
142
- * これを取り、TVer のサーバへそのまま送る。`null` のまま渡すと文字列の
143
- * "null" が送られて、あちらが断り、画面には `DT-RE` とだけ出る
140
+ * 絞るのに要るわけではない。**データ放送が受信機の API で読む**ので
141
+ * 持っておく。テレ朝の TVer リンクは `browser.getProgramID(4)` で
142
+ * これを取得し、TVer のサーバへそのまま送る。`null` のまま渡すと文字列の
143
+ * "null" が送られて TVer のサーバがエラーを返し、画面には `DT-RE` とだけ出る
144
144
  * ([server/live.ts](https://github.com/danything/denpa/blob/main/src/lib/server/live.ts) の `programInfo`)
145
145
  */
146
146
  get transportStreamId() {
package/dist/synth-mmt.js CHANGED
@@ -351,7 +351,7 @@ export function mptTable(mpt) {
351
351
  ];
352
352
  return Uint8Array.from([0x20, mpt.version ?? 0, ...be(body.length), ...body]);
353
353
  }
354
- /** MMT の表のセクションの頭 (table_id・長さ・拡張・バージョン・セクション番号)。長さは `withCrc` が埋める */
354
+ /** MMT の表のセクションの先頭 (table_id・長さ・拡張・バージョン・セクション番号)。長さは `withCrc` が埋める */
355
355
  function head(tableId, extension, version = 0) {
356
356
  return [tableId, 0x00, 0x00, ...be(extension), 0xc1 | (version << 1), 0, 0];
357
357
  }
package/dist/synth.d.ts CHANGED
@@ -106,15 +106,25 @@ export declare function eitSection(options: EitOptions): Uint8Array;
106
106
  * 足す記述子は分配の記述子 (`satelliteDeliveryDescriptor`・`terrestrialDeliveryDescriptor`) など
107
107
  */
108
108
  export declare function nitSection(networkId: number, remoteControlKeyId: number | null, streams: [number, number, [number, number][], number[]?][],
109
- /** `other` なら NIT[other] (0x41。ほかのネットワークのもの) */
109
+ /** `other` なら NIT[other] (0x41。ほかのネットワークのもの)。`name` を渡すとネットワーク名の記述子 (0x40) を付ける */
110
110
  options?: SectionPart & {
111
111
  other?: boolean;
112
+ name?: string;
112
113
  }): Uint8Array;
113
114
  /**
114
115
  * 衛星の分配の記述子 (0x43)。周波数は kHz、シンボルレートはシンボル/秒で渡す
115
116
  * (BS-1 は 11727480 kHz・28860000)。東経 110 度・右旋・ISDB-S (変調 0x08) にしておく
116
117
  */
117
118
  export declare function satelliteDeliveryDescriptor(frequency: number, symbolRate?: number): number[];
119
+ /**
120
+ * ケーブルの分配の記述子 (0x44)。周波数は Hz で渡す (111000000)。
121
+ * 既定は TS が 1 つの搬送波 (多重フレーム型式番号 0xF)・RS(204,188)・64QAM・5.274 Msym/s・内符号なし
122
+ */
123
+ export declare function cableDeliveryDescriptor(frequency: number, options?: {
124
+ frameType?: number;
125
+ modulation?: number;
126
+ symbolRate?: number;
127
+ }): number[];
118
128
  /** 地上の分配の記述子 (0xFA)。周波数は Hz で渡す (1/7 MHz 単位に丸める) */
119
129
  export declare function terrestrialDeliveryDescriptor(areaCode: number, frequencies: number[]): number[];
120
130
  /** セクションを TS パケットに詰める。最初のパケットには pointer_field が付く */
@@ -210,7 +220,7 @@ export declare function bmlHead(): Uint8Array[];
210
220
  * 本物と同じく、先頭に Content-Type、境界で区切って各ファイルに Content-Location
211
221
  */
212
222
  export declare function multipartModule(files: [location: string, type: string, body: string][]): Uint8Array;
213
- /** PMT の ES に付ける application_signalling_descriptor (0x6F)。**AIT の在り処の印** */
223
+ /** PMT の ES に付ける application_signalling_descriptor (0x6F)。**AIT の在りかの印** */
214
224
  export declare function aitSignallingDescriptor(applicationType?: number, version?: number): number[];
215
225
  export interface SynthHybridcastApp {
216
226
  organisationId: number;
package/dist/synth.js CHANGED
@@ -250,11 +250,14 @@ export function eitSection(options) {
250
250
  * 足す記述子は分配の記述子 (`satelliteDeliveryDescriptor`・`terrestrialDeliveryDescriptor`) など
251
251
  */
252
252
  export function nitSection(networkId, remoteControlKeyId, streams,
253
- /** `other` なら NIT[other] (0x41。ほかのネットワークのもの) */
253
+ /** `other` なら NIT[other] (0x41。ほかのネットワークのもの)。`name` を渡すとネットワーク名の記述子 (0x40) を付ける */
254
254
  options = {}) {
255
+ const network = options.name === undefined ? [] : encodeSiText(options.name);
256
+ const networkDescriptors = network.length === 0 ? [] : [0x40, network.length, ...network];
255
257
  const body = [
256
258
  ...head(options.other === true ? 0x41 : 0x40, networkId, options),
257
- ...be(0xf000),
259
+ ...be(0xf000 | networkDescriptors.length),
260
+ ...networkDescriptors,
258
261
  ];
259
262
  const loop = [];
260
263
  for (const [transportStreamId, originalNetworkId, services, extra,] of streams) {
@@ -297,6 +300,25 @@ export function satelliteDeliveryDescriptor(frequency, symbolRate = 28_860_000)
297
300
  (symbol[3] & 0xf0) | 0x01,
298
301
  ];
299
302
  }
303
+ /**
304
+ * ケーブルの分配の記述子 (0x44)。周波数は Hz で渡す (111000000)。
305
+ * 既定は TS が 1 つの搬送波 (多重フレーム型式番号 0xF)・RS(204,188)・64QAM・5.274 Msym/s・内符号なし
306
+ */
307
+ export function cableDeliveryDescriptor(frequency, options = {}) {
308
+ const symbol = bcdBytes((options.symbolRate ?? 5_274_000) / 100, 7);
309
+ return [
310
+ 0x44,
311
+ 11,
312
+ ...bcdBytes(frequency / 100, 8),
313
+ 0xff,
314
+ ((options.frameType ?? 0x0f) << 4) | 0x02,
315
+ options.modulation ?? 0x03,
316
+ symbol[0],
317
+ symbol[1],
318
+ symbol[2],
319
+ (symbol[3] & 0xf0) | 0x0f,
320
+ ];
321
+ }
300
322
  /** 地上の分配の記述子 (0xFA)。周波数は Hz で渡す (1/7 MHz 単位に丸める) */
301
323
  export function terrestrialDeliveryDescriptor(areaCode, frequencies) {
302
324
  const body = [
@@ -558,7 +580,7 @@ export function multipartModule(files) {
558
580
  out += `--${boundary}--\r\n`;
559
581
  return new TextEncoder().encode(out);
560
582
  }
561
- /** PMT の ES に付ける application_signalling_descriptor (0x6F)。**AIT の在り処の印** */
583
+ /** PMT の ES に付ける application_signalling_descriptor (0x6F)。**AIT の在りかの印** */
562
584
  export function aitSignallingDescriptor(applicationType = 0x0010, version = 1) {
563
585
  return [0x6f, 0x03, ...be(applicationType), version & 0x1f];
564
586
  }
package/dist/tlv.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * TLV パケット (ARIB STD-B32 第 3 部) を切り出す。4K の放送 (MMT/TLV) の土台で、TS の `PacketStream` と同じ役。
3
3
  *
4
4
  * TLV パケットは `0x7F`・種類 (1 バイト)・長さ (2 バイト) のヘッダーと、長さぶんの中身でできている。
5
- * 長さが毎回違うので、TS のように決まった間隔で同期のバイトを探すことはできない。
5
+ * 長さが毎回違うので、TS のように決まった間隔で同期のバイトを探せない。
6
6
  * エージェントはケーブルの分割 TLV を組み立て直した TLV のバイト列を流す
7
7
  * ([agent.md](https://github.com/danything/denpa/blob/main/docs/agent.md))。
8
8
  */
package/dist/tlv.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * TLV パケット (ARIB STD-B32 第 3 部) を切り出す。4K の放送 (MMT/TLV) の土台で、TS の `PacketStream` と同じ役。
3
3
  *
4
4
  * TLV パケットは `0x7F`・種類 (1 バイト)・長さ (2 バイト) のヘッダーと、長さぶんの中身でできている。
5
- * 長さが毎回違うので、TS のように決まった間隔で同期のバイトを探すことはできない。
5
+ * 長さが毎回違うので、TS のように決まった間隔で同期のバイトを探せない。
6
6
  * エージェントはケーブルの分割 TLV を組み立て直した TLV のバイト列を流す
7
7
  * ([agent.md](https://github.com/danything/denpa/blob/main/docs/agent.md))。
8
8
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danything/arib",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "description": "日本のデジタル放送 (ARIB) の TS と MMT/TLV を読む: PSI・PES・EIT・ARIB の文字・字幕・データ放送、TLV・MMTP・MMT-SI・MH-EIT",
5
5
  "license": "MIT",
6
6
  "repository": {