@danything/arib 0.0.0-stage → 0.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/LICENSE +82 -0
- package/README.md +65 -2
- package/dist/ait.d.ts +97 -0
- package/dist/ait.js +205 -0
- package/dist/aribtext-gaiji.d.ts +25 -0
- package/dist/aribtext-gaiji.js +69 -0
- package/dist/aribtext.d.ts +31 -0
- package/dist/aribtext.js +314 -0
- package/dist/b24-tables.d.ts +50 -0
- package/dist/b24-tables.js +410 -0
- package/dist/b24caption.d.ts +98 -0
- package/dist/b24caption.js +987 -0
- package/dist/bml-protocol.d.ts +143 -0
- package/dist/bml-protocol.js +12 -0
- package/dist/bml.d.ts +87 -0
- package/dist/bml.js +489 -0
- package/dist/bytes.d.ts +20 -0
- package/dist/bytes.js +45 -0
- package/dist/caption-text.d.ts +122 -0
- package/dist/caption-text.js +39 -0
- package/dist/drcs-smooth.d.ts +52 -0
- package/dist/drcs-smooth.js +163 -0
- package/dist/dsmcc.d.ts +92 -0
- package/dist/dsmcc.js +191 -0
- package/dist/eit.d.ts +316 -0
- package/dist/eit.js +708 -0
- package/dist/language.d.ts +7 -0
- package/dist/language.js +20 -0
- package/dist/multipart.d.ts +43 -0
- package/dist/multipart.js +194 -0
- package/dist/pes.d.ts +99 -0
- package/dist/pes.js +231 -0
- package/dist/psi.d.ts +169 -0
- package/dist/psi.js +497 -0
- package/dist/service-filter.d.ts +95 -0
- package/dist/service-filter.js +248 -0
- package/dist/synth-av.d.ts +51 -0
- package/dist/synth-av.js +298 -0
- package/dist/synth-caption.d.ts +38 -0
- package/dist/synth-caption.js +115 -0
- package/dist/synth.d.ts +183 -0
- package/dist/synth.js +508 -0
- package/package.json +30 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 denpa contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
--------------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
This package includes material derived from the following works.
|
|
26
|
+
|
|
27
|
+
## libaribcaption
|
|
28
|
+
|
|
29
|
+
The caption character tables (src/b24-tables.ts) are derived from libaribcaption
|
|
30
|
+
(b24_conv_tables.hpp, b24_gaiji_table.hpp, b24_colors.cpp, b24_drcs_conv.cpp),
|
|
31
|
+
and the caption decoder (src/b24caption.ts) is a TypeScript port of its
|
|
32
|
+
decoder_impl.cpp. https://github.com/xqq/libaribcaption
|
|
33
|
+
|
|
34
|
+
MIT License
|
|
35
|
+
|
|
36
|
+
Copyright (c) 2022 magicxqq
|
|
37
|
+
|
|
38
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
39
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
40
|
+
in the Software without restriction, including without limitation the rights
|
|
41
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
42
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
43
|
+
furnished to do so, subject to the following conditions:
|
|
44
|
+
|
|
45
|
+
The above copyright notice and this permission notice shall be included in all
|
|
46
|
+
copies or substantial portions of the Software.
|
|
47
|
+
|
|
48
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
49
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
50
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
51
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
52
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
53
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
54
|
+
SOFTWARE.
|
|
55
|
+
|
|
56
|
+
## ariblib
|
|
57
|
+
|
|
58
|
+
A few EPG-only entries of the ARIB gaiji table (src/aribtext-gaiji.ts) follow
|
|
59
|
+
the table that ariblib inherited from epgdump_py (Yasumasa Murakami, 2011).
|
|
60
|
+
https://github.com/youzaka/ariblib
|
|
61
|
+
|
|
62
|
+
The MIT License (MIT)
|
|
63
|
+
|
|
64
|
+
Copyright (c) 2013 youzaka
|
|
65
|
+
|
|
66
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
67
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
68
|
+
in the Software without restriction, including without limitation the rights
|
|
69
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
70
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
71
|
+
furnished to do so, subject to the following conditions:
|
|
72
|
+
|
|
73
|
+
The above copyright notice and this permission notice shall be included in all
|
|
74
|
+
copies or substantial portions of the Software.
|
|
75
|
+
|
|
76
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
77
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
78
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
79
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
80
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
81
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
82
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,66 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @danything/arib
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
日本のデジタル放送 (ISDB-T / ISDB-S、ARIB 規格) の MPEG-TS を TypeScript で読むライブラリです。
|
|
4
|
+
録画サーバー [denpa](https://github.com/danything/denpa) の中で使っているものを切り出しました。
|
|
5
|
+
|
|
6
|
+
- **PSI** — パケットの切り出し、セクションの組み立て、PAT / PMT / SDT / NIT (`psi`)
|
|
7
|
+
- **PES** — 映像・音声の PES と ADTS (`pes`)
|
|
8
|
+
- **番組表** — EIT[p/f] と EIT[schedule]。ジャンル・音声・拡張形式イベント記述子まで (`eit`)
|
|
9
|
+
- **ARIB の文字** — STD-B24 の8単位符号と ARIB 外字 (`aribtext`、`aribtext-gaiji`)
|
|
10
|
+
- **字幕** — STD-B24 の字幕文を、字の置き場所まで決めた JSON にする (`b24caption`、形式は `caption-text`)
|
|
11
|
+
- **データ放送** — DSM-CC のカルーセルからモジュールとファイルを組み立てる (`dsmcc`、`multipart`、`bml`)
|
|
12
|
+
- **Hybridcast の AIT** (`ait`)、**1つの局だけを抜き出す** (`service-filter`)
|
|
13
|
+
|
|
14
|
+
依存するパッケージはありません。
|
|
15
|
+
|
|
16
|
+
## 入れ方
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
npm install @danything/arib
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
ES モジュールだけです。`bml` と `b24caption` は `node:zlib` / `node:crypto` を使うので、
|
|
23
|
+
Node.js・Bun・Deno で動きます。ほかのモジュールはブラウザでも動きます。型は TypeScript 5.7 以降で読めます。
|
|
24
|
+
|
|
25
|
+
## 使い方
|
|
26
|
+
|
|
27
|
+
モジュールごとに読み込みます (`@danything/arib/<名前>`)。
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { EpgReader } from "@danything/arib/eit";
|
|
31
|
+
|
|
32
|
+
const epg = new EpgReader();
|
|
33
|
+
for await (const chunk of stream) {
|
|
34
|
+
// 長さはそろっていなくてよい
|
|
35
|
+
if (epg.feed(chunk)) {
|
|
36
|
+
for (const event of epg.takeChanged()) {
|
|
37
|
+
console.log(event.serviceId, new Date(event.startAt), event.name);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { decodeAribText } from "@danything/arib/aribtext";
|
|
45
|
+
|
|
46
|
+
decodeAribText(bytes); // "🈟ニュース"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { B24CaptionDecoder } from "@danything/arib/b24caption";
|
|
51
|
+
|
|
52
|
+
const captions = new B24CaptionDecoder();
|
|
53
|
+
// 字幕の PES の中身 (データ識別 0x80 から) を1つずつ渡す
|
|
54
|
+
const page = captions.decode(pesPayload);
|
|
55
|
+
if (page !== null) draw(page); // 形式は caption-text.ts の CaptionPage
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 版
|
|
59
|
+
|
|
60
|
+
0.x の間は、小さい版 (0.1 → 0.2) で互換のない変更をすることがあります。
|
|
61
|
+
|
|
62
|
+
## ライセンス
|
|
63
|
+
|
|
64
|
+
MIT。字幕の表とデコードの方法は [libaribcaption](https://github.com/xqq/libaribcaption) (MIT)、
|
|
65
|
+
外字表の一部は [ariblib](https://github.com/youzaka/ariblib) (MIT) から来ています。
|
|
66
|
+
著作権表示は [LICENSE](LICENSE) にあります。
|
package/dist/ait.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIT (Application Information Table) を読む。**Hybridcast の在り処。**
|
|
3
|
+
*
|
|
4
|
+
* データ放送 (BML) と違って、**Hybridcast のアプリは電波に乗っていません**。
|
|
5
|
+
* 乗っているのはこの表だけで、中身は「どこにアプリがあるか」のアドレス (URL) です。
|
|
6
|
+
* アプリそのものは放送局のサーバから HTTPS で取ってきます。
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* PMT ─ ES に application_signalling_descriptor (0x6F) ─→ その PID に AIT
|
|
10
|
+
* AIT ─ transport_protocol_descriptor (URL の頭)
|
|
11
|
+
* └ simple_application_location_descriptor (その先のパス) → アプリのURL
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* ここは読んで在ることを言うだけです。開くのはデータ放送が起動を求めたときで
|
|
15
|
+
* (画面の `HybridcastFrame`)、受信機の API は用意できる範囲だけ。放送局のサーバが
|
|
16
|
+
* 認証された受信機を期待するものは、denpa 側の努力では越えられません
|
|
17
|
+
* ([docs/stream.md](https://github.com/danything/denpa/blob/main/docs/stream.md#hybridcast))。
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* HTML5 のアプリ。**Hybridcast はこれ** (ARIB STD-B23)。
|
|
21
|
+
*
|
|
22
|
+
* 他の値は DVB のもの (DVB-J は 1、DVB-HTML は 2) で、日本の放送には出てきません
|
|
23
|
+
*/
|
|
24
|
+
export declare const APPLICATION_TYPE_HTML5 = 16;
|
|
25
|
+
/**
|
|
26
|
+
* 起動のしかた (application_control_code)。
|
|
27
|
+
*
|
|
28
|
+
* **`autostart` は「選局したら勝手に始めよ」**で、テレビは黙って動かします。
|
|
29
|
+
* `present` は「用意はあるが、人が呼ぶまで始めるな」。denpa は選局しただけでは
|
|
30
|
+
* どちらも動かさない (開くのはデータ放送が求めたとき) ので、**画面に出すときの言い方と、
|
|
31
|
+
* どれを開くかの手がかりが変わるだけ**です
|
|
32
|
+
*/
|
|
33
|
+
export declare const CONTROL: {
|
|
34
|
+
readonly autostart: 1;
|
|
35
|
+
readonly present: 2;
|
|
36
|
+
readonly destroy: 3;
|
|
37
|
+
readonly kill: 4;
|
|
38
|
+
readonly prefetch: 5;
|
|
39
|
+
readonly remote: 6;
|
|
40
|
+
readonly disabled: 7;
|
|
41
|
+
readonly playbackAutostart: 8;
|
|
42
|
+
};
|
|
43
|
+
interface HybridcastApp {
|
|
44
|
+
/** 放送局を表す番号 (organisation_id) */
|
|
45
|
+
organisationId: number;
|
|
46
|
+
applicationId: number;
|
|
47
|
+
/** `CONTROL` のどれか。生の値をそのまま持つ (知らない値でも捨てない) */
|
|
48
|
+
controlCode: number;
|
|
49
|
+
/** アプリの名前。放送が入れていなければ空 */
|
|
50
|
+
name: string;
|
|
51
|
+
/** 取得先。`transport_protocol` と `location` を繋いだもの */
|
|
52
|
+
url: string;
|
|
53
|
+
}
|
|
54
|
+
export interface Ait {
|
|
55
|
+
/** `APPLICATION_TYPE_HTML5` なら Hybridcast */
|
|
56
|
+
applicationType: number;
|
|
57
|
+
applications: HybridcastApp[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* PMT から「AIT が流れている PID」を読み取る。
|
|
61
|
+
*
|
|
62
|
+
* **記述子が付いている ES を見るだけ。** `stream_type` では決められません —
|
|
63
|
+
* AIT は DSM-CC セクション (0x0D) で運ばれ、データ放送のカルーセルと同じ型を
|
|
64
|
+
* 名乗るので、型だけでは見分けが付きません
|
|
65
|
+
*/
|
|
66
|
+
export declare function aitPidsFromPmt(section: Uint8Array): number[];
|
|
67
|
+
/**
|
|
68
|
+
* AIT のセクションを1つ読む。**壊れていたら `null`** (放送は途中から読むので、
|
|
69
|
+
* 読めないものが来るのは普通のこと)
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseAit(section: Uint8Array): Ait | null;
|
|
72
|
+
/**
|
|
73
|
+
* TS から Hybridcast のアプリを見つける。
|
|
74
|
+
*
|
|
75
|
+
* **PMT を見てから AIT を読む** という2段なので、繋いだ直後は何も返しません。
|
|
76
|
+
* 局を変えたら作り直します (`reset`)。
|
|
77
|
+
*
|
|
78
|
+
* 局に Hybridcast が無ければ、**PMT に印が無い**ので AIT を待つこともしません
|
|
79
|
+
* — その局では永久に何も返さないのが正しい振る舞いです。
|
|
80
|
+
*/
|
|
81
|
+
export declare class AitReader {
|
|
82
|
+
private readonly packets;
|
|
83
|
+
private readonly pmt;
|
|
84
|
+
/** AIT を組み立てるもの。PID ごとに1つ */
|
|
85
|
+
private aits;
|
|
86
|
+
constructor(serviceId: number);
|
|
87
|
+
/**
|
|
88
|
+
* TS を食べて、見つかったものを返す。**見つからない間は空**。
|
|
89
|
+
*
|
|
90
|
+
* 同じものが何度も返るのは呼ぶ側で畳んでください — 放送は同じ表を
|
|
91
|
+
* 繰り返し流すので、ここで覚えると「局を変えても消えない」になります
|
|
92
|
+
*/
|
|
93
|
+
feed(chunk: Uint8Array): Ait[];
|
|
94
|
+
/** 切り分け済みのパケットを1つ。見つかったものは `found` に足す (`BmlDecoder.feedPacket` と同じ事情) */
|
|
95
|
+
feedPacket(packet: Uint8Array, found: Ait[]): void;
|
|
96
|
+
}
|
|
97
|
+
export {};
|
package/dist/ait.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIT (Application Information Table) を読む。**Hybridcast の在り処。**
|
|
3
|
+
*
|
|
4
|
+
* データ放送 (BML) と違って、**Hybridcast のアプリは電波に乗っていません**。
|
|
5
|
+
* 乗っているのはこの表だけで、中身は「どこにアプリがあるか」のアドレス (URL) です。
|
|
6
|
+
* アプリそのものは放送局のサーバから HTTPS で取ってきます。
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* PMT ─ ES に application_signalling_descriptor (0x6F) ─→ その PID に AIT
|
|
10
|
+
* AIT ─ transport_protocol_descriptor (URL の頭)
|
|
11
|
+
* └ simple_application_location_descriptor (その先のパス) → アプリのURL
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* ここは読んで在ることを言うだけです。開くのはデータ放送が起動を求めたときで
|
|
15
|
+
* (画面の `HybridcastFrame`)、受信機の API は用意できる範囲だけ。放送局のサーバが
|
|
16
|
+
* 認証された受信機を期待するものは、denpa 側の努力では越えられません
|
|
17
|
+
* ([docs/stream.md](https://github.com/danything/denpa/blob/main/docs/stream.md#hybridcast))。
|
|
18
|
+
*/
|
|
19
|
+
import { decodeAribText } from "./aribtext.js";
|
|
20
|
+
import { u16, u32 } from "./bytes.js";
|
|
21
|
+
import { descriptors, packetPid, PacketStream, pmtStreams, PmtTracker, SectionAssembler, } from "./psi.js";
|
|
22
|
+
/** AIT のセクション */
|
|
23
|
+
const TABLE_AIT = 0x74;
|
|
24
|
+
/** PMT の ES に付く。**その PID に AIT が流れている**という印 */
|
|
25
|
+
const DESC_APPLICATION_SIGNALLING = 0x6f;
|
|
26
|
+
/** AIT の中の記述子 */
|
|
27
|
+
const DESC_APPLICATION_NAME = 0x01;
|
|
28
|
+
const DESC_TRANSPORT_PROTOCOL = 0x02;
|
|
29
|
+
const DESC_SIMPLE_APPLICATION_LOCATION = 0x15;
|
|
30
|
+
/** 通信路で運ぶ (HTTP/HTTPS)。1 は放送のカルーセルで運ぶもの */
|
|
31
|
+
const PROTOCOL_HTTP = 0x0003;
|
|
32
|
+
/**
|
|
33
|
+
* HTML5 のアプリ。**Hybridcast はこれ** (ARIB STD-B23)。
|
|
34
|
+
*
|
|
35
|
+
* 他の値は DVB のもの (DVB-J は 1、DVB-HTML は 2) で、日本の放送には出てきません
|
|
36
|
+
*/
|
|
37
|
+
export const APPLICATION_TYPE_HTML5 = 0x0010;
|
|
38
|
+
/**
|
|
39
|
+
* 起動のしかた (application_control_code)。
|
|
40
|
+
*
|
|
41
|
+
* **`autostart` は「選局したら勝手に始めよ」**で、テレビは黙って動かします。
|
|
42
|
+
* `present` は「用意はあるが、人が呼ぶまで始めるな」。denpa は選局しただけでは
|
|
43
|
+
* どちらも動かさない (開くのはデータ放送が求めたとき) ので、**画面に出すときの言い方と、
|
|
44
|
+
* どれを開くかの手がかりが変わるだけ**です
|
|
45
|
+
*/
|
|
46
|
+
export const CONTROL = {
|
|
47
|
+
autostart: 0x01,
|
|
48
|
+
present: 0x02,
|
|
49
|
+
destroy: 0x03,
|
|
50
|
+
kill: 0x04,
|
|
51
|
+
prefetch: 0x05,
|
|
52
|
+
remote: 0x06,
|
|
53
|
+
disabled: 0x07,
|
|
54
|
+
playbackAutostart: 0x08,
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* PMT から「AIT が流れている PID」を読み取る。
|
|
58
|
+
*
|
|
59
|
+
* **記述子が付いている ES を見るだけ。** `stream_type` では決められません —
|
|
60
|
+
* AIT は DSM-CC セクション (0x0D) で運ばれ、データ放送のカルーセルと同じ型を
|
|
61
|
+
* 名乗るので、型だけでは見分けが付きません
|
|
62
|
+
*/
|
|
63
|
+
export function aitPidsFromPmt(section) {
|
|
64
|
+
const pids = [];
|
|
65
|
+
for (const [, pid, info] of pmtStreams(section)) {
|
|
66
|
+
for (const [tag] of descriptors(info)) {
|
|
67
|
+
if (tag === DESC_APPLICATION_SIGNALLING) {
|
|
68
|
+
pids.push(pid);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return pids;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* URL の頭を取り出す (transport_protocol_descriptor)。
|
|
77
|
+
*
|
|
78
|
+
* **通信路で運ぶものだけ見ます。** `protocol_id` が 1 のものは放送のカルーセルで
|
|
79
|
+
* 運ぶアプリ (データ放送と同じ経路) で、Hybridcast の話ではありません
|
|
80
|
+
*/
|
|
81
|
+
function transportUrl(body) {
|
|
82
|
+
if (body.length < 4)
|
|
83
|
+
return null;
|
|
84
|
+
if (u16(body, 0) !== PROTOCOL_HTTP)
|
|
85
|
+
return null;
|
|
86
|
+
// [0..1] protocol_id, [2] transport_protocol_label, [3] URL_base_length
|
|
87
|
+
const baseLength = body[3];
|
|
88
|
+
if (body.length < 4 + baseLength)
|
|
89
|
+
return null;
|
|
90
|
+
return new TextDecoder().decode(body.subarray(4, 4 + baseLength));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 名前を取り出す (application_name_descriptor)。
|
|
94
|
+
*
|
|
95
|
+
* 言語ごとに何本も入っていることがあるので**最初の1本**を採ります。中身は
|
|
96
|
+
* ARIB の8単位符号なので、番組名と同じ方法で読みます (`aribtext.ts`)
|
|
97
|
+
*/
|
|
98
|
+
function applicationName(body) {
|
|
99
|
+
// [0..2] ISO_639_language_code, [3] application_name_length
|
|
100
|
+
if (body.length < 4)
|
|
101
|
+
return "";
|
|
102
|
+
const length = body[3];
|
|
103
|
+
if (body.length < 4 + length)
|
|
104
|
+
return "";
|
|
105
|
+
return decodeAribText(body.subarray(4, 4 + length)).trim();
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* AIT のセクションを1つ読む。**壊れていたら `null`** (放送は途中から読むので、
|
|
109
|
+
* 読めないものが来るのは普通のこと)
|
|
110
|
+
*/
|
|
111
|
+
export function parseAit(section) {
|
|
112
|
+
if (section[0] !== TABLE_AIT || section.length < 16)
|
|
113
|
+
return null;
|
|
114
|
+
const applicationType = u16(section, 3) & 0x7fff;
|
|
115
|
+
const commonLength = u16(section, 8) & 0x0fff;
|
|
116
|
+
let at = 10 + commonLength;
|
|
117
|
+
const end = section.length - 4;
|
|
118
|
+
if (at + 2 > end)
|
|
119
|
+
return null;
|
|
120
|
+
const loopLength = u16(section, at) & 0x0fff;
|
|
121
|
+
at += 2;
|
|
122
|
+
const stop = Math.min(at + loopLength, end);
|
|
123
|
+
const applications = [];
|
|
124
|
+
while (at + 9 <= stop) {
|
|
125
|
+
const organisationId = u32(section, at);
|
|
126
|
+
const applicationId = u16(section, at + 4);
|
|
127
|
+
const controlCode = section[at + 6];
|
|
128
|
+
const infoLength = u16(section, at + 7) & 0x0fff;
|
|
129
|
+
const info = section.subarray(at + 9, at + 9 + infoLength);
|
|
130
|
+
at += 9 + infoLength;
|
|
131
|
+
let base = null;
|
|
132
|
+
let path = "";
|
|
133
|
+
let name = "";
|
|
134
|
+
for (const [tag, descriptor] of descriptors(info)) {
|
|
135
|
+
if (tag === DESC_TRANSPORT_PROTOCOL)
|
|
136
|
+
base = transportUrl(descriptor) ?? base;
|
|
137
|
+
else if (tag === DESC_SIMPLE_APPLICATION_LOCATION)
|
|
138
|
+
path = new TextDecoder().decode(descriptor);
|
|
139
|
+
else if (tag === DESC_APPLICATION_NAME)
|
|
140
|
+
name = applicationName(descriptor);
|
|
141
|
+
// application_descriptor (優先度と可視性) は読まない。動かさないので使い道がない
|
|
142
|
+
}
|
|
143
|
+
// **URL の無いものは出さない。** 在ることだけ言われても行き先が無い
|
|
144
|
+
if (base === null)
|
|
145
|
+
continue;
|
|
146
|
+
applications.push({
|
|
147
|
+
organisationId,
|
|
148
|
+
applicationId,
|
|
149
|
+
controlCode,
|
|
150
|
+
name,
|
|
151
|
+
url: base + path,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return { applicationType, applications };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* TS から Hybridcast のアプリを見つける。
|
|
158
|
+
*
|
|
159
|
+
* **PMT を見てから AIT を読む** という2段なので、繋いだ直後は何も返しません。
|
|
160
|
+
* 局を変えたら作り直します (`reset`)。
|
|
161
|
+
*
|
|
162
|
+
* 局に Hybridcast が無ければ、**PMT に印が無い**ので AIT を待つこともしません
|
|
163
|
+
* — その局では永久に何も返さないのが正しい振る舞いです。
|
|
164
|
+
*/
|
|
165
|
+
export class AitReader {
|
|
166
|
+
packets = new PacketStream();
|
|
167
|
+
pmt;
|
|
168
|
+
/** AIT を組み立てるもの。PID ごとに1つ */
|
|
169
|
+
aits = new Map();
|
|
170
|
+
constructor(serviceId) {
|
|
171
|
+
this.pmt = new PmtTracker(serviceId);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* TS を食べて、見つかったものを返す。**見つからない間は空**。
|
|
175
|
+
*
|
|
176
|
+
* 同じものが何度も返るのは呼ぶ側で畳んでください — 放送は同じ表を
|
|
177
|
+
* 繰り返し流すので、ここで覚えると「局を変えても消えない」になります
|
|
178
|
+
*/
|
|
179
|
+
feed(chunk) {
|
|
180
|
+
const found = [];
|
|
181
|
+
for (const packet of this.packets.feed(chunk))
|
|
182
|
+
this.feedPacket(packet, found);
|
|
183
|
+
return found;
|
|
184
|
+
}
|
|
185
|
+
/** 切り分け済みのパケットを1つ。見つかったものは `found` に足す (`BmlDecoder.feedPacket` と同じ事情) */
|
|
186
|
+
feedPacket(packet, found) {
|
|
187
|
+
const pid = packetPid(packet);
|
|
188
|
+
const before = this.pmt.pid;
|
|
189
|
+
const sections = this.pmt.feed(packet, pid);
|
|
190
|
+
// 局が変わった。AIT も読み直す
|
|
191
|
+
if (this.pmt.pid !== before)
|
|
192
|
+
this.aits = new Map();
|
|
193
|
+
for (const section of sections) {
|
|
194
|
+
for (const ait of aitPidsFromPmt(section)) {
|
|
195
|
+
if (!this.aits.has(ait))
|
|
196
|
+
this.aits.set(ait, new SectionAssembler(ait));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
for (const section of this.aits.get(pid)?.feed(packet) ?? []) {
|
|
200
|
+
const ait = parseAit(section);
|
|
201
|
+
if (ait !== null)
|
|
202
|
+
found.push(ait);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARIB 外字。放送で使われる、JIS X 0208 の外にある文字の対応表 (85区から先)。
|
|
3
|
+
*
|
|
4
|
+
* 番組名の「🈟」「🈑」「🈞」([新][字][再]) はここにある。読めないと、番組表から
|
|
5
|
+
* その印が丸ごと消えるか、化けた文字が並ぶ。
|
|
6
|
+
*
|
|
7
|
+
* **市販のテレビと同じく、規格の字そのままを出す。** 𠮷 を「吉」に、🈟 を「[新]」に
|
|
8
|
+
* 寄せたりはしない。「吉」や「[新]」で探して当たるようにするのは検索の側
|
|
9
|
+
* ([fold.ts](https://github.com/danything/denpa/blob/main/src/lib/fold.ts))。
|
|
10
|
+
*
|
|
11
|
+
* **字は字幕と同じ表を引く** ([b24-tables.ts](b24-tables.ts) の `ADDITIONAL`。ARIB STD-B24 表 7-19 /
|
|
12
|
+
* STD-B62 表 5-2)。番組表で違えるのは下の `EPG_ONLY` だけ:
|
|
13
|
+
*
|
|
14
|
+
* - 92区56〜85点 (楽器の略号) は規格でも私用領域 (U+E2A5〜) にしか字が無い。どこでも読めるよう
|
|
15
|
+
* 「(vn)」のような文字列に開く (字幕は私用領域のまま。フォントがその字形を持つ)
|
|
16
|
+
* - 93区46・47点と87点は規格で空き。昔の表 (epgdump_py から ariblib に引き継がれた表) のとおり
|
|
17
|
+
* 中黒と全角の空白で埋める (字幕では空きのまま)
|
|
18
|
+
*
|
|
19
|
+
* 表で空き (U+FFFD) の点は持たない。`aribtext.ts` が「□」にする。
|
|
20
|
+
*
|
|
21
|
+
* 鍵は区点そのまま — 第1バイトが 0x20+区、第2バイトが 0x20+点。
|
|
22
|
+
*/
|
|
23
|
+
/** 番組表だけで違える字。区点 → 字 (上の説明) */
|
|
24
|
+
export declare const EPG_ONLY: Map<number, string>;
|
|
25
|
+
export declare const GAIJI: Map<number, string>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARIB 外字。放送で使われる、JIS X 0208 の外にある文字の対応表 (85区から先)。
|
|
3
|
+
*
|
|
4
|
+
* 番組名の「🈟」「🈑」「🈞」([新][字][再]) はここにある。読めないと、番組表から
|
|
5
|
+
* その印が丸ごと消えるか、化けた文字が並ぶ。
|
|
6
|
+
*
|
|
7
|
+
* **市販のテレビと同じく、規格の字そのままを出す。** 𠮷 を「吉」に、🈟 を「[新]」に
|
|
8
|
+
* 寄せたりはしない。「吉」や「[新]」で探して当たるようにするのは検索の側
|
|
9
|
+
* ([fold.ts](https://github.com/danything/denpa/blob/main/src/lib/fold.ts))。
|
|
10
|
+
*
|
|
11
|
+
* **字は字幕と同じ表を引く** ([b24-tables.ts](b24-tables.ts) の `ADDITIONAL`。ARIB STD-B24 表 7-19 /
|
|
12
|
+
* STD-B62 表 5-2)。番組表で違えるのは下の `EPG_ONLY` だけ:
|
|
13
|
+
*
|
|
14
|
+
* - 92区56〜85点 (楽器の略号) は規格でも私用領域 (U+E2A5〜) にしか字が無い。どこでも読めるよう
|
|
15
|
+
* 「(vn)」のような文字列に開く (字幕は私用領域のまま。フォントがその字形を持つ)
|
|
16
|
+
* - 93区46・47点と87点は規格で空き。昔の表 (epgdump_py から ariblib に引き継がれた表) のとおり
|
|
17
|
+
* 中黒と全角の空白で埋める (字幕では空きのまま)
|
|
18
|
+
*
|
|
19
|
+
* 表で空き (U+FFFD) の点は持たない。`aribtext.ts` が「□」にする。
|
|
20
|
+
*
|
|
21
|
+
* 鍵は区点そのまま — 第1バイトが 0x20+区、第2バイトが 0x20+点。
|
|
22
|
+
*/
|
|
23
|
+
import { ADDITIONAL } from "./b24-tables.js";
|
|
24
|
+
/** 番組表だけで違える字。区点 → 字 (上の説明) */
|
|
25
|
+
export const EPG_ONLY = new Map([
|
|
26
|
+
// 92区56〜85点。規格でも私用領域
|
|
27
|
+
[0x7c58, "(vn)"],
|
|
28
|
+
[0x7c59, "(ob)"],
|
|
29
|
+
[0x7c5a, "(cb)"],
|
|
30
|
+
[0x7c5b, "(ce"],
|
|
31
|
+
[0x7c5c, "mb)"],
|
|
32
|
+
[0x7c5d, "(hp)"],
|
|
33
|
+
[0x7c5e, "(br)"],
|
|
34
|
+
[0x7c5f, "(p)"],
|
|
35
|
+
[0x7c60, "(s)"],
|
|
36
|
+
[0x7c61, "(ms)"],
|
|
37
|
+
[0x7c62, "(t)"],
|
|
38
|
+
[0x7c63, "(bs)"],
|
|
39
|
+
[0x7c64, "(b)"],
|
|
40
|
+
[0x7c65, "(tb)"],
|
|
41
|
+
[0x7c66, "(tp)"],
|
|
42
|
+
[0x7c67, "(ds)"],
|
|
43
|
+
[0x7c68, "(ag)"],
|
|
44
|
+
[0x7c69, "(eg)"],
|
|
45
|
+
[0x7c6a, "(vo)"],
|
|
46
|
+
[0x7c6b, "(fl)"],
|
|
47
|
+
[0x7c6c, "(ke"],
|
|
48
|
+
[0x7c6d, "y)"],
|
|
49
|
+
[0x7c6e, "(sa"],
|
|
50
|
+
[0x7c6f, "x)"],
|
|
51
|
+
[0x7c70, "(sy"],
|
|
52
|
+
[0x7c71, "n)"],
|
|
53
|
+
[0x7c72, "(or"],
|
|
54
|
+
[0x7c73, "g)"],
|
|
55
|
+
[0x7c74, "(pe"],
|
|
56
|
+
[0x7c75, "r)"],
|
|
57
|
+
// 93区46・47点・87点。規格で空き
|
|
58
|
+
[0x7d4e, "・"],
|
|
59
|
+
[0x7d4f, "・"],
|
|
60
|
+
[0x7d77, " "],
|
|
61
|
+
]);
|
|
62
|
+
export const GAIJI = new Map();
|
|
63
|
+
[...ADDITIONAL].forEach((char, i) => {
|
|
64
|
+
if (char === "�")
|
|
65
|
+
return;
|
|
66
|
+
GAIJI.set(((0x20 + 85 + Math.floor(i / 94)) << 8) | (0x21 + (i % 94)), char);
|
|
67
|
+
});
|
|
68
|
+
for (const [code, text] of EPG_ONLY)
|
|
69
|
+
GAIJI.set(code, text);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ARIB STD-B24 の8単位符号を読む。番組名・概要・局名はこの形式で流れてくる。
|
|
3
|
+
*
|
|
4
|
+
* ## 仕組み
|
|
5
|
+
*
|
|
6
|
+
* 符号表を4つ (G0〜G3) 持ち、**どの表を今使うか**を切り替えながら読む。
|
|
7
|
+
* 0x21〜0x7E は GL に、0xA1〜0xFE は GR に割り当てられた表で引く。
|
|
8
|
+
* SI の初期状態は G0=漢字 / G1=英数 / G2=ひらがな / G3=カタカナ、GL=G0・GR=G2。
|
|
9
|
+
*
|
|
10
|
+
* ## 英数の幅は文字の大きさで決まる
|
|
11
|
+
*
|
|
12
|
+
* 英数集合 (と空白) は**いまの文字の大きさ**で描く。SI の初期状態は標準 (NSZ) で、
|
|
13
|
+
* テレビはこれを全角で描く。半角にしたい局は中型 (MSZ) を挟んでくる (ARIB TR-B14)。
|
|
14
|
+
* 番組名の「Venue101」は英数集合を標準のまま送ったもの。**放送のとおりに出す** —
|
|
15
|
+
* 探すときや比べるときに半角へ寄せるのは使う側 (`fold.toHalfWidth` / `title.searchable`)。
|
|
16
|
+
* 全角は `toHalfWidth` でそのまま戻る字 (U+FF01〜U+FF5E) にする (0x5C は \、0x7E は ~)
|
|
17
|
+
*
|
|
18
|
+
* ## 漢字は EUC-JP に押し付ける
|
|
19
|
+
*
|
|
20
|
+
* 2バイトの漢字集合は JIS X 0208 そのもので、区点は (第1-0x20, 第2-0x20)。
|
|
21
|
+
* **各バイトの最上位を立てれば EUC-JP になる**ので、変換表を抱え込まずに
|
|
22
|
+
* `TextDecoder('euc-jp')` に渡せる。自前で持つのは規格の外にある外字だけ
|
|
23
|
+
* ([aribtext-gaiji.ts](aribtext-gaiji.ts))。
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* 8単位符号を読んで文字列にする。
|
|
27
|
+
*
|
|
28
|
+
* 壊れた並びで例外を投げない。放送波は落ちるもので、1つの記述子が読めなくても
|
|
29
|
+
* 番組表全体を落とすほうが害が大きい。**読めたところまで返す。**
|
|
30
|
+
*/
|
|
31
|
+
export declare function decodeAribText(data: Uint8Array): string;
|