@danything/arib 2.0.0 → 2.1.0
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 +2 -2
- package/dist/b24caption.js +4 -1
- package/dist/eit.js +10 -2
- package/dist/psi.d.ts +18 -2
- package/dist/psi.js +40 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# @danything/arib
|
|
2
2
|
|
|
3
|
-
日本のデジタル放送 (ISDB-T / ISDB-S、ARIB 規格) の MPEG-TS を TypeScript で読むライブラリです。
|
|
3
|
+
日本のデジタル放送 (ISDB-T / ISDB-S / ISDB-C、ARIB 規格) の MPEG-TS を TypeScript で読むライブラリです。
|
|
4
4
|
録画サーバ [denpa](https://github.com/danything/denpa) の中で使っているものを切り出しました。
|
|
5
5
|
|
|
6
|
-
- PSI (`psi`): パケットの切り出し、セクションの組み立て、PAT / PMT / SDT / NIT。衛星はネットワークの全部の TS の NIT と、ほかの TS の 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`)
|
package/dist/b24caption.js
CHANGED
|
@@ -299,13 +299,16 @@ export class B24CaptionDecoder {
|
|
|
299
299
|
return;
|
|
300
300
|
const found = [];
|
|
301
301
|
for (let i = 0; i < languages; i++) {
|
|
302
|
-
if (at +
|
|
302
|
+
if (at + 5 > data.length)
|
|
303
303
|
return;
|
|
304
304
|
const tag = data[at] >> 5;
|
|
305
305
|
const dmf = data[at] & 0x0f;
|
|
306
306
|
at += 1;
|
|
307
|
+
// DC (表示モード) が 1 バイト挟まる
|
|
307
308
|
if (dmf === 0b1100 || dmf === 0b1101 || dmf === 0b1110)
|
|
308
309
|
at += 1;
|
|
310
|
+
if (at + 4 > data.length)
|
|
311
|
+
return;
|
|
309
312
|
const code = ascii(data, at, 3);
|
|
310
313
|
at += 3; // ISO 639
|
|
311
314
|
const format = data[at] >> 4;
|
package/dist/eit.js
CHANGED
|
@@ -159,10 +159,12 @@ function readDescriptors(event, body) {
|
|
|
159
159
|
for (const [tag, data] of descriptors(body)) {
|
|
160
160
|
switch (tag) {
|
|
161
161
|
case DESC_SHORT_EVENT: {
|
|
162
|
+
if (data.length < 4)
|
|
163
|
+
break;
|
|
162
164
|
const nameLength = data[3];
|
|
163
165
|
const textAt = 4 + nameLength;
|
|
164
166
|
event.name = decodeAribText(data.subarray(4, textAt));
|
|
165
|
-
const textLength = data[textAt];
|
|
167
|
+
const textLength = data[textAt] ?? 0;
|
|
166
168
|
event.description = decodeAribText(data.subarray(textAt + 1, textAt + 1 + textLength));
|
|
167
169
|
break;
|
|
168
170
|
}
|
|
@@ -199,6 +201,9 @@ function readDescriptors(event, body) {
|
|
|
199
201
|
break;
|
|
200
202
|
}
|
|
201
203
|
case DESC_COMPONENT: {
|
|
204
|
+
// 短いものを読むと、種別 0 の「480i」と出る
|
|
205
|
+
if (data.length < 2)
|
|
206
|
+
break;
|
|
202
207
|
const streamContent = data[0] & 0x0f;
|
|
203
208
|
const componentType = data[1];
|
|
204
209
|
event.video = {
|
|
@@ -221,6 +226,9 @@ function readDescriptors(event, body) {
|
|
|
221
226
|
* [残り] text_char … **放送が付けた名前**
|
|
222
227
|
*/
|
|
223
228
|
case DESC_AUDIO_COMPONENT: {
|
|
229
|
+
// 言語の符号まで無いものを読むと、言語が空の音声が 1 本増える
|
|
230
|
+
if (data.length < 9)
|
|
231
|
+
break;
|
|
224
232
|
const flags = data[5];
|
|
225
233
|
const multiLingual = (flags & 0x80) !== 0;
|
|
226
234
|
const langs = [lang(data, 6)];
|
|
@@ -245,7 +253,7 @@ function readDescriptors(event, body) {
|
|
|
245
253
|
case DESC_CA_CONTRACT_INFO: {
|
|
246
254
|
if (data.length < 4)
|
|
247
255
|
break;
|
|
248
|
-
const caSystemId = (data
|
|
256
|
+
const caSystemId = u16(data, 0);
|
|
249
257
|
const numOfComponent = data[2] & 0x0f;
|
|
250
258
|
let at = 3 + numOfComponent;
|
|
251
259
|
if (at >= data.length)
|
package/dist/psi.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TS の土台 (パケットの切り出し・セクションの組み立て・PAT/PMT) と、局の一覧。
|
|
3
3
|
* 局の一覧はチャンネルスキャンで「この物理チャンネルに何が入っているか」を知るために使い、
|
|
4
|
-
* 読むのは NIT と SDT の 2
|
|
4
|
+
* 読むのは NIT と SDT の 2 つ。ケーブルでは、局の無い TS を見分けるのに PAT も読む (`ServiceReader`)。
|
|
5
5
|
*
|
|
6
6
|
* - NIT (PID 0x0010) … ネットワーク ID と、地上波ならリモコン番号。衛星はネットワークの全部の TS
|
|
7
7
|
* (TSID・周波数・サービスの一覧) が載っている (`NitReader`)
|
|
@@ -48,8 +48,9 @@ export interface TransportInfo {
|
|
|
48
48
|
}
|
|
49
49
|
export interface NetworkInfo {
|
|
50
50
|
networkId: number;
|
|
51
|
+
/** 最初の TS のリモコン番号。地上波の NIT は自分の TS 1 つだけを載せる */
|
|
51
52
|
remoteControlKeyId: number | null;
|
|
52
|
-
transportStreams:
|
|
53
|
+
transportStreams: NetworkTransport[];
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
55
56
|
* PSI セクションを組み立てる。
|
|
@@ -182,6 +183,8 @@ export type Delivery = SatelliteDelivery | TerrestrialDelivery;
|
|
|
182
183
|
/** NIT の TS の 1 件。サービスの一覧に、どこで流れているかが付く */
|
|
183
184
|
export interface NetworkTransport extends TransportInfo {
|
|
184
185
|
delivery: Delivery | null;
|
|
186
|
+
/** その TS のリモコン番号 (TS 情報記述子)。載せていなければ null */
|
|
187
|
+
remoteControlKeyId: number | null;
|
|
185
188
|
}
|
|
186
189
|
/** そろった NIT。セクションをつないだもの */
|
|
187
190
|
export interface Network {
|
|
@@ -259,13 +262,26 @@ export interface FoundService extends Service {
|
|
|
259
262
|
* Mirakurun のスキャンと同じで、**両方**揃って初めてそのチャンネルを
|
|
260
263
|
* 「受信できた」とみなす。SDT だけ取れても、どのネットワークのものか
|
|
261
264
|
* 分からないと設定に書けない。
|
|
265
|
+
*
|
|
266
|
+
* **ケーブル (`requireNit: false`) は SDT[actual] だけで揃ったとみなす。** 1 つのネットワークが
|
|
267
|
+
* 地上波・BS の再送信と自主放送の TS をまとめて流していて、NIT は CATV のネットワークのもの
|
|
268
|
+
* (局の元の放送とは別)。どの放送の局かは SDT の original_network_id で分かる。
|
|
269
|
+
* NIT が先に来ていれば、その TS のリモコン番号だけを使う。
|
|
270
|
+
* PAT に局が 1 つも無い TS (データだけの TS) も揃ったとみなし、局は返さない (`empty`)
|
|
262
271
|
*/
|
|
263
272
|
export declare class ServiceReader {
|
|
264
273
|
private readonly nit;
|
|
265
274
|
private readonly sdt;
|
|
275
|
+
private readonly pat;
|
|
266
276
|
private readonly packets;
|
|
277
|
+
private readonly requireNit;
|
|
267
278
|
network: NetworkInfo | null;
|
|
268
279
|
transport: TransportInfo | null;
|
|
280
|
+
/** PAT に局が 1 つも無かった (`requireNit: false` のときだけ読む) */
|
|
281
|
+
empty: boolean;
|
|
282
|
+
constructor({ requireNit }?: {
|
|
283
|
+
requireNit?: boolean;
|
|
284
|
+
});
|
|
269
285
|
get complete(): boolean;
|
|
270
286
|
/** 任意の長さのバイト列を渡す。揃ったら true */
|
|
271
287
|
feed(chunk: Uint8Array): boolean;
|
package/dist/psi.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TS の土台 (パケットの切り出し・セクションの組み立て・PAT/PMT) と、局の一覧。
|
|
3
3
|
* 局の一覧はチャンネルスキャンで「この物理チャンネルに何が入っているか」を知るために使い、
|
|
4
|
-
* 読むのは NIT と SDT の 2
|
|
4
|
+
* 読むのは NIT と SDT の 2 つ。ケーブルでは、局の無い TS を見分けるのに PAT も読む (`ServiceReader`)。
|
|
5
5
|
*
|
|
6
6
|
* - NIT (PID 0x0010) … ネットワーク ID と、地上波ならリモコン番号。衛星はネットワークの全部の TS
|
|
7
7
|
* (TSID・周波数・サービスの一覧) が載っている (`NitReader`)
|
|
@@ -481,11 +481,11 @@ function parseNitSection(section) {
|
|
|
481
481
|
at += 6 + loop;
|
|
482
482
|
const services = [];
|
|
483
483
|
let delivery = null;
|
|
484
|
+
let remote = null;
|
|
484
485
|
for (const [tag, descriptor] of descriptors(body)) {
|
|
485
|
-
if (tag === DESC_TS_INFORMATION &&
|
|
486
|
-
|
|
487
|
-
remoteControlKeyId
|
|
488
|
-
remoteControlKeyId = descriptor[0];
|
|
486
|
+
if (tag === DESC_TS_INFORMATION && descriptor.length > 0) {
|
|
487
|
+
remote ??= descriptor[0];
|
|
488
|
+
remoteControlKeyId ??= remote;
|
|
489
489
|
}
|
|
490
490
|
else if (tag === DESC_SERVICE_LIST) {
|
|
491
491
|
for (let i = 0; i + 3 <= descriptor.length; i += 3) {
|
|
@@ -508,13 +508,13 @@ function parseNitSection(section) {
|
|
|
508
508
|
originalNetworkId,
|
|
509
509
|
services,
|
|
510
510
|
delivery,
|
|
511
|
+
remoteControlKeyId: remote,
|
|
511
512
|
});
|
|
512
513
|
}
|
|
513
514
|
return {
|
|
514
515
|
other: tableId === TABLE_NIT_OTHER,
|
|
515
516
|
networkId: u16(section, 3),
|
|
516
517
|
version: (section[5] >> 1) & 0x1f,
|
|
517
|
-
current: (section[5] & 0x01) !== 0,
|
|
518
518
|
sectionNumber: section[6],
|
|
519
519
|
lastSectionNumber: section[7],
|
|
520
520
|
remoteControlKeyId,
|
|
@@ -716,15 +716,30 @@ export class SdtReader {
|
|
|
716
716
|
* Mirakurun のスキャンと同じで、**両方**揃って初めてそのチャンネルを
|
|
717
717
|
* 「受信できた」とみなす。SDT だけ取れても、どのネットワークのものか
|
|
718
718
|
* 分からないと設定に書けない。
|
|
719
|
+
*
|
|
720
|
+
* **ケーブル (`requireNit: false`) は SDT[actual] だけで揃ったとみなす。** 1 つのネットワークが
|
|
721
|
+
* 地上波・BS の再送信と自主放送の TS をまとめて流していて、NIT は CATV のネットワークのもの
|
|
722
|
+
* (局の元の放送とは別)。どの放送の局かは SDT の original_network_id で分かる。
|
|
723
|
+
* NIT が先に来ていれば、その TS のリモコン番号だけを使う。
|
|
724
|
+
* PAT に局が 1 つも無い TS (データだけの TS) も揃ったとみなし、局は返さない (`empty`)
|
|
719
725
|
*/
|
|
720
726
|
export class ServiceReader {
|
|
721
727
|
nit = new SectionAssembler(PID_NIT);
|
|
722
728
|
sdt = new SectionAssembler(PID_SDT);
|
|
729
|
+
pat = new SectionAssembler(PID_PAT);
|
|
723
730
|
packets = new PacketStream();
|
|
731
|
+
requireNit;
|
|
724
732
|
network = null;
|
|
725
733
|
transport = null;
|
|
734
|
+
/** PAT に局が 1 つも無かった (`requireNit: false` のときだけ読む) */
|
|
735
|
+
empty = false;
|
|
736
|
+
constructor({ requireNit = true } = {}) {
|
|
737
|
+
this.requireNit = requireNit;
|
|
738
|
+
}
|
|
726
739
|
get complete() {
|
|
727
|
-
return this.
|
|
740
|
+
return this.requireNit
|
|
741
|
+
? this.network !== null && this.transport !== null
|
|
742
|
+
: this.transport !== null || this.empty;
|
|
728
743
|
}
|
|
729
744
|
/** 任意の長さのバイト列を渡す。揃ったら true */
|
|
730
745
|
feed(chunk) {
|
|
@@ -739,16 +754,22 @@ export class ServiceReader {
|
|
|
739
754
|
this.transport = parseSdt(section) ?? this.transport;
|
|
740
755
|
}
|
|
741
756
|
}
|
|
757
|
+
if (!this.requireNit && !this.empty && this.transport === null) {
|
|
758
|
+
for (const section of this.pat.feed(packet)) {
|
|
759
|
+
if (section[0] === TABLE_PAT && parsePat(section).size === 0)
|
|
760
|
+
this.empty = true;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
742
763
|
}
|
|
743
764
|
return this.complete;
|
|
744
765
|
}
|
|
745
766
|
/** 録るに値するサービスだけ。リモコン番号は NIT のものを配る */
|
|
746
767
|
services() {
|
|
747
|
-
|
|
768
|
+
const transport = this.transport;
|
|
769
|
+
if (transport === null)
|
|
748
770
|
return [];
|
|
749
|
-
const {
|
|
750
|
-
const
|
|
751
|
-
return this.transport.services
|
|
771
|
+
const { transportStreamId, originalNetworkId } = transport;
|
|
772
|
+
const found = (networkId, remoteControlKeyId) => transport.services
|
|
752
773
|
.filter((service) => SERVICE_TYPES.has(service.serviceType))
|
|
753
774
|
.map((service) => ({
|
|
754
775
|
...service,
|
|
@@ -756,5 +777,13 @@ export class ServiceReader {
|
|
|
756
777
|
transportStreamId,
|
|
757
778
|
remoteControlKeyId,
|
|
758
779
|
}));
|
|
780
|
+
if (this.requireNit) {
|
|
781
|
+
if (this.network === null)
|
|
782
|
+
return [];
|
|
783
|
+
return found(this.network.networkId, this.network.remoteControlKeyId);
|
|
784
|
+
}
|
|
785
|
+
const own = this.network?.transportStreams.find((ts) => ts.transportStreamId === transportStreamId &&
|
|
786
|
+
ts.originalNetworkId === originalNetworkId);
|
|
787
|
+
return found(originalNetworkId, own?.remoteControlKeyId ?? null);
|
|
759
788
|
}
|
|
760
789
|
}
|