@danything/arib 0.0.0-stage → 1.0.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 +121 -0
- package/README.md +69 -2
- package/dist/ait.d.ts +97 -0
- package/dist/ait.js +208 -0
- package/dist/aribtext-gaiji.d.ts +25 -0
- package/dist/aribtext-gaiji.js +69 -0
- package/dist/aribtext.d.ts +34 -0
- package/dist/aribtext.js +348 -0
- package/dist/b24-tables.d.ts +68 -0
- package/dist/b24-tables.js +501 -0
- package/dist/b24caption.d.ts +102 -0
- package/dist/b24caption.js +1016 -0
- package/dist/bml-protocol.d.ts +144 -0
- package/dist/bml-protocol.js +13 -0
- package/dist/bml.d.ts +87 -0
- package/dist/bml.js +510 -0
- package/dist/bytes.d.ts +20 -0
- package/dist/bytes.js +45 -0
- package/dist/caption-text.d.ts +127 -0
- package/dist/caption-text.js +39 -0
- package/dist/drcs-smooth.d.ts +52 -0
- package/dist/drcs-smooth.js +164 -0
- package/dist/dsmcc.d.ts +110 -0
- package/dist/dsmcc.js +231 -0
- package/dist/eit.d.ts +349 -0
- package/dist/eit.js +738 -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 +244 -0
- package/dist/psi.d.ts +169 -0
- package/dist/psi.js +497 -0
- package/dist/service-filter.d.ts +106 -0
- package/dist/service-filter.js +280 -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 +201 -0
- package/dist/synth.js +541 -0
- package/package.json +30 -4
package/dist/dsmcc.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* データカルーセル (DSM-CC) をデコードする。**放送が同じものを何度も回してくる**中から、
|
|
3
|
+
* ブロックを集めて1つのモジュールに組み立て直すところまで。
|
|
4
|
+
*
|
|
5
|
+
* 使う側が2つある。**中身の読み方だけが違って、運び方は同じ**なので、ここに寄せる。
|
|
6
|
+
*
|
|
7
|
+
* - [logo-dsmcc.ts](https://github.com/danything/denpa/blob/main/src/lib/ts/logo-dsmcc.ts) … 衛星の局ロゴ (ARIB STD-B21)
|
|
8
|
+
* - [bml.ts](bml.ts) … データ放送 (ARIB STD-B24 / TR-B14・TR-B15)
|
|
9
|
+
*
|
|
10
|
+
* ## 流れ
|
|
11
|
+
*
|
|
12
|
+
* 1. **DII** (table_id 0x3B) が「どのモジュールが何バイトで、何ブロックか」を伝える
|
|
13
|
+
* 2. **DDB** (0x3C) がそのモジュールを `blockSize` ごとに割って何度も流す
|
|
14
|
+
* 3. 全ブロック揃うとモジュールになる
|
|
15
|
+
*
|
|
16
|
+
* セクションを取り出すのは [psi.ts](psi.ts) の `SectionAssembler`。**`'syntax'` を
|
|
17
|
+
* 渡すこと** — DSM-CC は `section_syntax_indicator` が立たないことがあり、そのときは
|
|
18
|
+
* 末尾4バイトが CRC ではなく Checksum なので、いつも確かめると全部捨ててしまう。
|
|
19
|
+
*/
|
|
20
|
+
import { u16, u32 } from "./bytes.js";
|
|
21
|
+
import { descriptors } from "./psi.js";
|
|
22
|
+
export const TABLE_DII = 0x3b;
|
|
23
|
+
const TABLE_DDB = 0x3c;
|
|
24
|
+
/** ストリーム記述子。NPT と イベントメッセージが載る (データ放送だけが使う) */
|
|
25
|
+
export const TABLE_STREAM_DESCRIPTOR = 0x3d;
|
|
26
|
+
/** モジュール記述子 (STD-B24 第三分冊 第三編 6.2.3) */
|
|
27
|
+
const MODULE_DESC_TYPE = 0x01;
|
|
28
|
+
const MODULE_DESC_NAME = 0x02;
|
|
29
|
+
const MODULE_DESC_COMPRESSION = 0xc2;
|
|
30
|
+
/** DII が blockSize を持っていないときの既定 (ARIB TR-B15) */
|
|
31
|
+
const DEFAULT_BLOCK_SIZE = 4066;
|
|
32
|
+
/**
|
|
33
|
+
* 同じカルーセルの中の「回」。**downloadId の上位4ビットに入っている**
|
|
34
|
+
* (下位28ビットは常に1で運用される)。
|
|
35
|
+
*
|
|
36
|
+
* 番組が変わるとここが変わるので、**前の番組のモジュールを捨てる合図**になる。
|
|
37
|
+
*/
|
|
38
|
+
export function dataEventIdOf(downloadId) {
|
|
39
|
+
return (downloadId >>> 28) & 0x0f;
|
|
40
|
+
}
|
|
41
|
+
/** DSM-CC セクションの中身 (メッセージ本体) を切り出す */
|
|
42
|
+
function messageOf(section) {
|
|
43
|
+
const length = u16(section, 1) & 0x0fff;
|
|
44
|
+
const end = 3 + length - 4;
|
|
45
|
+
if (end <= 8 || end > section.length)
|
|
46
|
+
return null;
|
|
47
|
+
return section.subarray(8, end);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* DII (Download Info Indication)。
|
|
51
|
+
*
|
|
52
|
+
* 頭は共通で protocolDiscriminator/dsmccType/messageId/transaction_id と続き、
|
|
53
|
+
* adaptationLength ぶん飛ばした先に本体が入っている。
|
|
54
|
+
*/
|
|
55
|
+
export function parseDii(section) {
|
|
56
|
+
if (section[0] !== TABLE_DII)
|
|
57
|
+
return null;
|
|
58
|
+
const message = messageOf(section);
|
|
59
|
+
if (message === null || message.length < 20)
|
|
60
|
+
return null;
|
|
61
|
+
const transactionId = u32(message, 4);
|
|
62
|
+
const adaptationLength = message[9];
|
|
63
|
+
let at = 12 + adaptationLength;
|
|
64
|
+
if (at + 20 > message.length)
|
|
65
|
+
return null;
|
|
66
|
+
const downloadId = u32(message, at);
|
|
67
|
+
const blockSize = u16(message, at + 4) || DEFAULT_BLOCK_SIZE;
|
|
68
|
+
at += 4 + 2 + 1 + 1 + 4 + 4;
|
|
69
|
+
// compatibilityDescriptor。中身は使わないので長さぶん飛ばす
|
|
70
|
+
if (at + 2 > message.length)
|
|
71
|
+
return null;
|
|
72
|
+
at += 2 + u16(message, at);
|
|
73
|
+
if (at + 2 > message.length)
|
|
74
|
+
return null;
|
|
75
|
+
const count = u16(message, at);
|
|
76
|
+
at += 2;
|
|
77
|
+
const modules = [];
|
|
78
|
+
for (let i = 0; i < count; i++) {
|
|
79
|
+
if (at + 8 > message.length)
|
|
80
|
+
return null;
|
|
81
|
+
const moduleId = u16(message, at);
|
|
82
|
+
const moduleSize = u32(message, at + 2);
|
|
83
|
+
const moduleVersion = message[at + 6];
|
|
84
|
+
const infoLength = message[at + 7];
|
|
85
|
+
const info = message.subarray(at + 8, at + 8 + infoLength);
|
|
86
|
+
at += 8 + infoLength;
|
|
87
|
+
let name = null;
|
|
88
|
+
let contentType = null;
|
|
89
|
+
let compression = null;
|
|
90
|
+
for (const [tag, descriptor] of descriptors(info)) {
|
|
91
|
+
if (tag === MODULE_DESC_NAME)
|
|
92
|
+
name = new TextDecoder().decode(descriptor);
|
|
93
|
+
else if (tag === MODULE_DESC_TYPE)
|
|
94
|
+
contentType = new TextDecoder("ascii").decode(descriptor);
|
|
95
|
+
else if (tag === MODULE_DESC_COMPRESSION && descriptor.length >= 5) {
|
|
96
|
+
// 先頭1バイトは符号付き。運用されるのは 0 (zlib) だけ
|
|
97
|
+
compression = {
|
|
98
|
+
type: (descriptor[0] << 24) >> 24,
|
|
99
|
+
originalSize: u32(descriptor, 1),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
modules.push({
|
|
104
|
+
moduleId,
|
|
105
|
+
moduleSize,
|
|
106
|
+
moduleVersion,
|
|
107
|
+
name,
|
|
108
|
+
contentType,
|
|
109
|
+
compression,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/*
|
|
113
|
+
* privateData。長さの手前まで飛ばしたところに記述子が並ぶ。
|
|
114
|
+
* **無いほうがふつう**なので、読めなければ null のまま進む
|
|
115
|
+
*/
|
|
116
|
+
let returnToEntry = null;
|
|
117
|
+
if (at + 2 <= message.length) {
|
|
118
|
+
const length = u16(message, at);
|
|
119
|
+
for (const [tag, descriptor] of descriptors(message.subarray(at + 2, at + 2 + length))) {
|
|
120
|
+
// arib_bxml_privatedata_descriptor (STD-B24 第二分冊 (1/2) 第二編 9.3.4)
|
|
121
|
+
if (tag === 0xf0 && descriptor.length >= 1)
|
|
122
|
+
returnToEntry = (descriptor[0] & 0x80) !== 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { downloadId, transactionId, blockSize, modules, returnToEntry };
|
|
126
|
+
}
|
|
127
|
+
/** DDB (Download Data Block)。モジュールを割ったブロックが1つ入っている */
|
|
128
|
+
export function parseDdb(section) {
|
|
129
|
+
if (section[0] !== TABLE_DDB)
|
|
130
|
+
return null;
|
|
131
|
+
const message = messageOf(section);
|
|
132
|
+
if (message === null || message.length < 12)
|
|
133
|
+
return null;
|
|
134
|
+
const downloadId = u32(message, 4);
|
|
135
|
+
const adaptationLength = message[9];
|
|
136
|
+
const messageLength = u16(message, 10);
|
|
137
|
+
let at = 12 + adaptationLength;
|
|
138
|
+
if (at + 6 > message.length)
|
|
139
|
+
return null;
|
|
140
|
+
const moduleId = u16(message, at);
|
|
141
|
+
const moduleVersion = message[at + 2];
|
|
142
|
+
const blockNumber = u16(message, at + 4);
|
|
143
|
+
at += 6;
|
|
144
|
+
const size = messageLength - adaptationLength - 6;
|
|
145
|
+
if (size < 0 || at + size > message.length)
|
|
146
|
+
return null;
|
|
147
|
+
return {
|
|
148
|
+
downloadId,
|
|
149
|
+
moduleId,
|
|
150
|
+
moduleVersion,
|
|
151
|
+
blockNumber,
|
|
152
|
+
block: message.subarray(at, at + size),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* 組み立てるモジュールの大きさの上限 (16MiB)。**これより大きいと言う DII は信じない。**
|
|
157
|
+
*
|
|
158
|
+
* `moduleSize` は32ビットで、そのまま信じると DII 1つで 4GB を確保させられる。
|
|
159
|
+
* 実際のデータ放送のモジュールは数 KB〜数百 KB、衛星のロゴも全局ぶんで数百 KB なので、
|
|
160
|
+
* 十分に余裕を見た値。zlib で伸ばしたあとの大きさにも同じ上限を使う ([bml.ts](bml.ts) の `inflate`)
|
|
161
|
+
*/
|
|
162
|
+
export const MAX_MODULE_SIZE = 16 * 1024 * 1024;
|
|
163
|
+
/**
|
|
164
|
+
* 組み立て中のモジュール。DDB が来るたびに然るべき位置へ置く。
|
|
165
|
+
*
|
|
166
|
+
* **入れ物は届いたところまでしか確保しない。** DII の言う大きさを先に確保すると、
|
|
167
|
+
* 大きいと言うだけの DII でメモリを使い切らせられる。`MAX_MODULE_SIZE` を超える
|
|
168
|
+
* モジュールはブロックを受け取らない (いつまでも揃わない)。
|
|
169
|
+
*
|
|
170
|
+
* 順番は保証されない (途中から見れば真ん中から始まる) ので、
|
|
171
|
+
* **受け取ったブロック番号を数えて**揃ったかを見る。
|
|
172
|
+
*/
|
|
173
|
+
export class ModuleBuilder {
|
|
174
|
+
moduleVersion;
|
|
175
|
+
moduleSize;
|
|
176
|
+
blockSize;
|
|
177
|
+
buffer = new Uint8Array(0);
|
|
178
|
+
/** 受け取ったブロック番号 (1ブロック1ビット)。最初のブロックが来てから確保する */
|
|
179
|
+
received = null;
|
|
180
|
+
count = 0;
|
|
181
|
+
total;
|
|
182
|
+
constructor(moduleVersion, moduleSize, blockSize) {
|
|
183
|
+
this.moduleVersion = moduleVersion;
|
|
184
|
+
this.moduleSize = moduleSize;
|
|
185
|
+
this.blockSize = blockSize;
|
|
186
|
+
this.total = Math.ceil(moduleSize / blockSize);
|
|
187
|
+
}
|
|
188
|
+
/** 組み立てた中身。まだ届いていないところは 0。**揃ってから読むこと** (読むとモジュールの大きさぶんを確保する) */
|
|
189
|
+
get data() {
|
|
190
|
+
if (this.buffer.length < this.moduleSize &&
|
|
191
|
+
this.moduleSize <= MAX_MODULE_SIZE)
|
|
192
|
+
this.grow(this.moduleSize);
|
|
193
|
+
return this.buffer;
|
|
194
|
+
}
|
|
195
|
+
/** 受け取ったブロックの数 (同じブロックを2度受けても1つ) */
|
|
196
|
+
get blocks() {
|
|
197
|
+
return this.count;
|
|
198
|
+
}
|
|
199
|
+
/** 揃ったか。**大きさ 0 のモジュールは初めから揃っている** (運ぶブロックが無いので DDB を待たない) */
|
|
200
|
+
get complete() {
|
|
201
|
+
return this.count >= this.total;
|
|
202
|
+
}
|
|
203
|
+
/** ブロックを1つ置く。**揃ったら true** */
|
|
204
|
+
add(blockNumber, block) {
|
|
205
|
+
if (this.moduleSize > MAX_MODULE_SIZE)
|
|
206
|
+
return false;
|
|
207
|
+
// blockSize を超えるブロックは受けない。重なったブロックで受け取る量が膨らむ
|
|
208
|
+
if (blockNumber >= this.total || block.length > this.blockSize)
|
|
209
|
+
return false;
|
|
210
|
+
const at = this.blockSize * blockNumber;
|
|
211
|
+
if (at + block.length > this.moduleSize)
|
|
212
|
+
return false;
|
|
213
|
+
if (at + block.length > this.buffer.length)
|
|
214
|
+
this.grow(at + block.length);
|
|
215
|
+
this.buffer.set(block, at);
|
|
216
|
+
this.received ??= new Uint8Array(Math.ceil(this.total / 8));
|
|
217
|
+
const index = blockNumber >> 3;
|
|
218
|
+
const bit = 1 << (blockNumber & 7);
|
|
219
|
+
if ((this.received[index] & bit) === 0) {
|
|
220
|
+
this.received[index] = this.received[index] | bit;
|
|
221
|
+
this.count++;
|
|
222
|
+
}
|
|
223
|
+
return this.complete;
|
|
224
|
+
}
|
|
225
|
+
/** 入れ物を広げる。何度も写し直さないよう倍々で、モジュールの大きさまで */
|
|
226
|
+
grow(length) {
|
|
227
|
+
const next = new Uint8Array(Math.min(this.moduleSize, Math.max(length, this.buffer.length * 2)));
|
|
228
|
+
next.set(this.buffer);
|
|
229
|
+
this.buffer = next;
|
|
230
|
+
}
|
|
231
|
+
}
|
package/dist/eit.d.ts
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EIT を読む。番組表の中身そのもの。
|
|
3
|
+
*
|
|
4
|
+
* **denpa が自分で読む。** 番組表を誰かに聞きに行かないので、**チューナーが
|
|
5
|
+
* 空いているだけ並列に回せる**し、録画で開いているチャンネルのぶんはただで
|
|
6
|
+
* 手に入る ([agent.md](https://github.com/danything/denpa/blob/main/docs/agent.md))。
|
|
7
|
+
*
|
|
8
|
+
* 扱うのは2種類。
|
|
9
|
+
*
|
|
10
|
+
* - **EIT[p/f]** (table_id 0x4E) … いま流れている番組と次の番組。**放送に追従する。**
|
|
11
|
+
* 野球で延びた終了時刻はここに出る
|
|
12
|
+
* - **EIT[schedule]** (table_id 0x50〜0x5F) … 数日先までの番組表
|
|
13
|
+
*
|
|
14
|
+
* どちらも PID 0x0012 に流れてくる。
|
|
15
|
+
*/
|
|
16
|
+
interface EventAudio {
|
|
17
|
+
componentType: number;
|
|
18
|
+
langs: string[];
|
|
19
|
+
samplingRate?: number;
|
|
20
|
+
/**
|
|
21
|
+
* 放送が自分で付けた名前。**「主音声ステレオ」「解説ステレオ」。**
|
|
22
|
+
*
|
|
23
|
+
* 同じ構成の音声が2本ある番組 (解説放送・二重音声) は、種別も言語も同じに
|
|
24
|
+
* なるので**符号からは区別が付かない**。実機の日テレでは、どちらも
|
|
25
|
+
* `component_type=3 lang=jpn` で「ステレオ (日本語)」が2つ並んでいた。
|
|
26
|
+
* 見分けが付くものはここにしか無い
|
|
27
|
+
*/
|
|
28
|
+
text?: string;
|
|
29
|
+
/** 主音声か (`main_component_flag`)。**解説放送はこちらが立っていないほう** */
|
|
30
|
+
main?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* シリーズ (series_descriptor。ARIB STD-B10 第2部)。**載せる局と載せない局がある。**
|
|
34
|
+
*
|
|
35
|
+
* `seriesId` で決まるのは**その局の中だけ**。番号は局ごとに振るので、別の局の同じ番号は
|
|
36
|
+
* 別のシリーズ。比べるときは局 (original_network_id + service_id) と組にする
|
|
37
|
+
*/
|
|
38
|
+
export interface EitSeries {
|
|
39
|
+
seriesId: number;
|
|
40
|
+
/**
|
|
41
|
+
* 再放送の印 (repeat_label)。**0 はオリジナル**、それ以外は、オリジナルの放送期間と
|
|
42
|
+
* 重なって流れる再放送のシリーズを別のものとして見分けるための番号。
|
|
43
|
+
* 重ならない再放送は 0 のまま来ることがあるので、**0 でも初回とは言えない**
|
|
44
|
+
*/
|
|
45
|
+
repeatLabel: number;
|
|
46
|
+
/**
|
|
47
|
+
* 編成の型 (program_pattern)。0=不定期 1=帯 (毎日・平日のみなど) 2=週1回 3=月1回
|
|
48
|
+
* 4=同じ日に何話か 5=長い番組を分けたもの。6・7 はここでは意味を読まない
|
|
49
|
+
*/
|
|
50
|
+
programPattern: number;
|
|
51
|
+
/** 何話目か (episode_number)。0 (話数が決まっていない) は null */
|
|
52
|
+
episode: number | null;
|
|
53
|
+
/** 全何話か (last_episode_number)。0 (分からない) は null */
|
|
54
|
+
lastEpisode: number | null;
|
|
55
|
+
/** シリーズ名。載っていなければ空文字 */
|
|
56
|
+
name: string;
|
|
57
|
+
}
|
|
58
|
+
/** ジャンル。大分類 (`lv1`) と中分類 (`lv2`) の番号 */
|
|
59
|
+
export interface Genre {
|
|
60
|
+
lv1: number;
|
|
61
|
+
lv2: number;
|
|
62
|
+
}
|
|
63
|
+
export interface EitEvent {
|
|
64
|
+
serviceId: number;
|
|
65
|
+
transportStreamId: number;
|
|
66
|
+
originalNetworkId: number;
|
|
67
|
+
eventId: number;
|
|
68
|
+
/** 開始時刻 (epoch ms)。未定 (全ビット1) なら null */
|
|
69
|
+
startAt: number | null;
|
|
70
|
+
/** 尺 (ms)。未定なら null。番組表に入れられるのは決まっているものだけ */
|
|
71
|
+
duration: number | null;
|
|
72
|
+
isFree: boolean;
|
|
73
|
+
/** 1=まもなく 2=停止中 3=一時停止 4=放送中。p/f の「いま」を見分けるのに使う */
|
|
74
|
+
runningStatus: number;
|
|
75
|
+
name: string;
|
|
76
|
+
description: string;
|
|
77
|
+
/** 「出演者」などの見出し付き。extended_event_descriptor を繋ぎ直したもの */
|
|
78
|
+
extended: Record<string, string>;
|
|
79
|
+
/** content_descriptor の大分類・中分類 (ARIB STD-B10 付録 H) */
|
|
80
|
+
genres: Genre[];
|
|
81
|
+
audios: EventAudio[];
|
|
82
|
+
video: {
|
|
83
|
+
type: string | null;
|
|
84
|
+
resolution: string | null;
|
|
85
|
+
} | null;
|
|
86
|
+
/**
|
|
87
|
+
* **コピー禁止か** (デジタルコピー制御記述子)。禁止の番組は録らない (denpa の docs/data.md)。
|
|
88
|
+
*
|
|
89
|
+
* `null` は「この表は言っていない」。記述子は基本の表 (と p/f) に載り、詳細の表
|
|
90
|
+
* (0x58〜) には載らないので、詳細だけ届いた回で「禁止ではない」と上書きしないため
|
|
91
|
+
* (`EpgReader.merge`)
|
|
92
|
+
*/
|
|
93
|
+
copyNever: boolean | null;
|
|
94
|
+
/**
|
|
95
|
+
* シリーズ (series_descriptor)。載っていなければ null。
|
|
96
|
+
*
|
|
97
|
+
* **この項目が無い**のは「この表は言っていない」。`copyNever` と同じく基本の表 (と p/f) に載り、
|
|
98
|
+
* 詳細の表 (0x58〜) には載らない。詳細だけ届いた回で消さないため (`EpgReader.merge`)
|
|
99
|
+
*/
|
|
100
|
+
series?: EitSeries | null;
|
|
101
|
+
}
|
|
102
|
+
export interface EitSection {
|
|
103
|
+
tableId: number;
|
|
104
|
+
serviceId: number;
|
|
105
|
+
transportStreamId: number;
|
|
106
|
+
originalNetworkId: number;
|
|
107
|
+
version: number;
|
|
108
|
+
sectionNumber: number;
|
|
109
|
+
lastSectionNumber: number;
|
|
110
|
+
/** このセグメント (8セクション単位) で実際に使われている最後のセクション */
|
|
111
|
+
segmentLastSectionNumber: number;
|
|
112
|
+
/** この局で使われている最後の table_id。番組表がどこまで先を持っているか */
|
|
113
|
+
lastTableId: number;
|
|
114
|
+
events: EitEvent[];
|
|
115
|
+
}
|
|
116
|
+
/** TDT / TOT が流れている PID。**放送の現在時刻** */
|
|
117
|
+
export declare const PID_TIME = 20;
|
|
118
|
+
/**
|
|
119
|
+
* TDT / TOT のセクションから**放送の実時刻**を読む。別のテーブルなら null。
|
|
120
|
+
*
|
|
121
|
+
* 頭3バイトのあとに MJD + BCD が5バイト。**TOT も同じ並び**で、後ろに
|
|
122
|
+
* 時差の記述子が付くだけなので同じに読める。
|
|
123
|
+
*
|
|
124
|
+
* データ放送 (`bml.ts` の `onTime`) が読み、BML の時計にします
|
|
125
|
+
*/
|
|
126
|
+
export declare function parseTimeTable(section: Uint8Array): number | null;
|
|
127
|
+
/**
|
|
128
|
+
* MJD + BCD の5バイトを epoch ms に直す。
|
|
129
|
+
*
|
|
130
|
+
* 全ビットが1なら「未定」。開始時刻が未定の番組は番組表に置けないので null で返す。
|
|
131
|
+
*/
|
|
132
|
+
export declare function parseMjdTime(data: Uint8Array, at: number): number | null;
|
|
133
|
+
/** BCD 3バイトの尺 (時分秒) を ms に。全ビット1なら「終了未定」 */
|
|
134
|
+
export declare function parseBcdDuration(data: Uint8Array, at: number): number | null;
|
|
135
|
+
/**
|
|
136
|
+
* EIT のセクションを1本読む。自局のもの以外は null。
|
|
137
|
+
*
|
|
138
|
+
* 壊れたセクションは `SectionAssembler` が CRC で落としているので、ここに来るのは
|
|
139
|
+
* 中身が揃っているものだけ。それでも長さの辻褄が合わないことはあるので、
|
|
140
|
+
* **読める番組まで返す**。
|
|
141
|
+
*/
|
|
142
|
+
export declare function parseEit(section: Uint8Array, options?: {
|
|
143
|
+
other?: boolean;
|
|
144
|
+
}): EitSection | null;
|
|
145
|
+
/**
|
|
146
|
+
* どこまで集まったかを数える。
|
|
147
|
+
*
|
|
148
|
+
* **これが無いと「もう十分」が分からない。** 何分開いておくかを決め打ちにすると、
|
|
149
|
+
* 早く揃った局でも待たされ、遅い局は取りこぼす。EIT は自分がどこまであるかを
|
|
150
|
+
* セクションの中で言っているので、それを数えれば足りる。
|
|
151
|
+
*
|
|
152
|
+
* セクションは**8本ずつのセグメント**に分かれていて、セグメントの中で実際に
|
|
153
|
+
* 使われている最後の番号が `segment_last_section_number`。使われていない番号は
|
|
154
|
+
* 永久に来ないので、そこまで揃えばそのセグメントは終わり。
|
|
155
|
+
*
|
|
156
|
+
* **過ぎた時間帯のセグメントは流れてこない** (`stale`)。空でも先頭の1本は必ず
|
|
157
|
+
* 来るものとして待っていた頃は、**どのチャンネルも例外なく上限まで
|
|
158
|
+
* 開きっぱなし**になっていた (実機14チャンネル、揃って離せたのは0本)。
|
|
159
|
+
*/
|
|
160
|
+
export declare class ScheduleProgress {
|
|
161
|
+
private readonly now;
|
|
162
|
+
/** `table_id` → 受け取ったセクション番号 */
|
|
163
|
+
private readonly seen;
|
|
164
|
+
/** `table_id:セグメント先頭` → そのセグメントの最後の番号 */
|
|
165
|
+
private readonly segmentLast;
|
|
166
|
+
/** `table_id` → 最後のセクション番号 */
|
|
167
|
+
private readonly lastSection;
|
|
168
|
+
private lastTableId;
|
|
169
|
+
/**
|
|
170
|
+
* `table_id` → バージョン。変わったらその表だけ数え直す。
|
|
171
|
+
*
|
|
172
|
+
* **バージョンは `table_id` ごとに別々に振られる。** 番組表の1つの表 (sub_table) は
|
|
173
|
+
* `table_id` + 局 + TS で決まり、`version_number` はその単位で動く。
|
|
174
|
+
* 局に1つだけ持っていた頃は、**基本 (0x50〜) と詳細 (0x58〜) を行き来する
|
|
175
|
+
* たびにバージョンが違って数えたものを全部捨てて**いた。揃ったと分かることが
|
|
176
|
+
* 二度と無いので、どのチャンネルも上限の10分 (denpa の `epgChannelTimeout`) まで開きっぱなしになる。
|
|
177
|
+
*/
|
|
178
|
+
private readonly version;
|
|
179
|
+
/**
|
|
180
|
+
* @param now いまの時刻。過ぎた時間帯を判断するのに使う。
|
|
181
|
+
* 時計を跨いで開いていることがあるので、値ではなく関数で受ける
|
|
182
|
+
*/
|
|
183
|
+
constructor(now?: () => number);
|
|
184
|
+
add(section: EitSection): void;
|
|
185
|
+
/**
|
|
186
|
+
* 使われている table_id を全部揃えられたか。
|
|
187
|
+
*
|
|
188
|
+
* **番号が飛ぶ。** 1つの `table_id` が受け持つのは4日ぶんで、基本は
|
|
189
|
+
* `0x50` から、詳細は `0x58` から始まる。日本の放送は8日ぶんなので、
|
|
190
|
+
* 実際に流れてくるのは `0x50` `0x51` と `0x58` `0x59` あたりだけで、
|
|
191
|
+
* **`0x52`〜`0x57` は永久に来ません**。
|
|
192
|
+
*
|
|
193
|
+
* `0x50` から `last_table_id` まで全部を待っていた頃は、詳細を積んでいる局が
|
|
194
|
+
* **一度も「揃った」にならず**、どのチャンネルも上限の10分 (denpa の `epgChannelTimeout`) まで開きっぱなしに
|
|
195
|
+
* なっていました (実機で、詳細の無い BS11 だけが早く閉じていた)。
|
|
196
|
+
*
|
|
197
|
+
* 見かけた表と、`last_table_id` が指す表だけを見ます。
|
|
198
|
+
*/
|
|
199
|
+
get complete(): boolean;
|
|
200
|
+
/** その表でまだ来ていない節。セグメントの先頭すら来ていなければ、先頭の番号を数える */
|
|
201
|
+
private holes;
|
|
202
|
+
/**
|
|
203
|
+
* そのセグメントの時間帯がもう過ぎているか。**過ぎたものは待たない。**
|
|
204
|
+
*
|
|
205
|
+
* セグメントは 0:00 から3時間ずつの枠で、`table_id` と節番号から
|
|
206
|
+
* それがいつの枠なのかが決まる — 基本は `0x50` が当日 0:00 から4日ぶん、
|
|
207
|
+
* `0x51` がその次の4日ぶん。詳細 (`0x58`〜) も同じ並びをもう一度たどる。
|
|
208
|
+
* 終わった枠は放送に乗らない (クラスの説明)。そのぶんの番組は、そこが未来だった数日前に取り込んである。
|
|
209
|
+
*/
|
|
210
|
+
private stale;
|
|
211
|
+
/**
|
|
212
|
+
* 何が足りないか。**上限まで開いても揃わなかったときに出す。**
|
|
213
|
+
*
|
|
214
|
+
* 揃ったかどうかしか分からなかった頃は、上限まで開きっぱなしになっている局を
|
|
215
|
+
* 見つけても**その先を追えなかった** — 電波が弱くて欠けているのか、
|
|
216
|
+
* こちらが来ない表を待っているのかで、直し方がまるで違う。
|
|
217
|
+
*/
|
|
218
|
+
report(): string;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* 流れてくる TS を食べて番組を溜める。
|
|
222
|
+
*
|
|
223
|
+
* 局ごとに `ScheduleProgress` を持ち、**その物理チャンネルに入っている局が
|
|
224
|
+
* 全部揃ったら**読むのをやめられる。同じ番組が何度も来る (カルーセルなので) ので、
|
|
225
|
+
* event_id で上書きしていく。
|
|
226
|
+
*/
|
|
227
|
+
export declare class EpgReader {
|
|
228
|
+
private readonly now;
|
|
229
|
+
private readonly options;
|
|
230
|
+
private readonly sections;
|
|
231
|
+
private readonly packets;
|
|
232
|
+
private readonly progress;
|
|
233
|
+
/** `serviceId:eventId` → 番組。あとから来たもので上書きする */
|
|
234
|
+
private readonly events;
|
|
235
|
+
/** p/f で「いま流れている」と言われた番組。録画の延長追従はこれを見る */
|
|
236
|
+
readonly present: Map<number, EitEvent>;
|
|
237
|
+
/** `takeChanged` のあとに増えた・変わった番組 (`serviceId:eventId`) */
|
|
238
|
+
private readonly changed;
|
|
239
|
+
/** 最後に新しい局を見かけた時刻 (`other` のときだけ使う) */
|
|
240
|
+
private lastNewService;
|
|
241
|
+
/**
|
|
242
|
+
* @param now いまの時刻。局ごとの `ScheduleProgress` にそのまま渡す
|
|
243
|
+
* @param options.other ほかの TS の局の番組表も読む。ネットワーク全局ぶんを
|
|
244
|
+
* 1つの中継から先に入れるときに使う (denpa の `epg-collect.ts` の初回)
|
|
245
|
+
* @param options.settle `other` のとき、新しい局が出てこなくなってから閉じるまで (ms)
|
|
246
|
+
* @param options.expect `other` のとき、来るはずの局 (service_id)。全部見えて揃えば
|
|
247
|
+
* `settle` を待たずに閉じる。受信できない局が混ざっていれば `settle` で閉じる
|
|
248
|
+
*/
|
|
249
|
+
constructor(now?: () => number, options?: {
|
|
250
|
+
other?: boolean;
|
|
251
|
+
settle?: number;
|
|
252
|
+
expect?: readonly number[];
|
|
253
|
+
});
|
|
254
|
+
/** 任意の長さのバイト列を食わせる。番組が1つでも増えたら true */
|
|
255
|
+
feed(chunk: Uint8Array): boolean;
|
|
256
|
+
/**
|
|
257
|
+
* 溜めてあるものと混ぜる。**別の表で来るものを消し合わせない。**
|
|
258
|
+
*
|
|
259
|
+
* 番組表は2つの表に分かれて流れてくる。同じ event_id が両方に載っていて、
|
|
260
|
+
* **それぞれ自分のぶんの記述子しか持っていない**。
|
|
261
|
+
*
|
|
262
|
+
* - **基本** (table_id 0x50〜0x57) … タイトルと短い説明 (short_event_descriptor)
|
|
263
|
+
* - **詳細** (0x58〜0x5F) … 「番組内容」「出演者」など (extended_event_descriptor)
|
|
264
|
+
*
|
|
265
|
+
* あとから来たもので丸ごと置き換えていた頃は、**詳細のほうが後に届いた番組の
|
|
266
|
+
* タイトルが空になっていた**。実機では番組 23,000 件のうち 11,000 件が名無しで、
|
|
267
|
+
* うち 2,700 件は「タイトルは無いのに番組内容だけある」状態 — 詳細に上書きされた
|
|
268
|
+
* 跡そのもの。番組表には「(番組情報なし)」の列が並んでいた。
|
|
269
|
+
*
|
|
270
|
+
* **中身のあるほうを残す。** 書き換え (「[新]」が付く、誤字が直る) は
|
|
271
|
+
* 空でない値で来るので、これまでどおり新しいほうが勝つ。
|
|
272
|
+
*/
|
|
273
|
+
private merge;
|
|
274
|
+
/**
|
|
275
|
+
* p/f で「いま流れている」と言えるか。
|
|
276
|
+
*
|
|
277
|
+
* **`running_status` を当てにしきれない。** 4 (放送中) を入れてくる局も
|
|
278
|
+
* あれば、**0 (未定義) しか入れてこない局もある** — 実機の NHK は 74 節
|
|
279
|
+
* ぜんぶ 0 だった。4 だけを見ていると、そういう局では延長追従が丸ごと
|
|
280
|
+
* 効かなくなる (denpa の docs/data.md)。
|
|
281
|
+
*
|
|
282
|
+
* そこで 0 のときは**節の番号で決める**。p/f は仕様で
|
|
283
|
+
* **section 0 が現在・section 1 が次**と決まっていて、こちらは局の都合で
|
|
284
|
+
* 変わらない。1 (まもなく) や 2 (停止中) は今までどおり入れない。
|
|
285
|
+
*/
|
|
286
|
+
private onAir;
|
|
287
|
+
/**
|
|
288
|
+
* 前に呼んでから増えた・変わった番組。**読みながら少しずつ保存する**ため
|
|
289
|
+
* (denpa の `epg-collect.ts`)。揃うまで待ってからまとめて書くと、全局読みの数分のあいだ
|
|
290
|
+
* 画面に何も出ない
|
|
291
|
+
*/
|
|
292
|
+
takeChanged(): EitEvent[];
|
|
293
|
+
/** 書けなかったぶんを、次の `takeChanged` でもう一度返す */
|
|
294
|
+
requeue(events: EitEvent[]): void;
|
|
295
|
+
/** 溜まった番組。開始時刻の順に並べて返す */
|
|
296
|
+
all(): EitEvent[];
|
|
297
|
+
/** 番組表を1つでも見かけた局 */
|
|
298
|
+
services(): number[];
|
|
299
|
+
/**
|
|
300
|
+
* 見かけた局が**全部**揃ったか。
|
|
301
|
+
*
|
|
302
|
+
* 「1局でも揃えば閉じる」にはしない。1つの物理チャンネルには複数の局が
|
|
303
|
+
* 入っていて (地上波なら MX1 と MX2)、先に揃ったほうで閉じると残りが
|
|
304
|
+
* 永久に埋まらない。
|
|
305
|
+
*/
|
|
306
|
+
get complete(): boolean;
|
|
307
|
+
/** 来るはずの局を教わっていて、それが全部見えたか */
|
|
308
|
+
private sawExpected;
|
|
309
|
+
/**
|
|
310
|
+
* 揃っていない局と、その理由。**上限まで開いてしまったときに出す。**
|
|
311
|
+
*
|
|
312
|
+
* 1局でも揃わなければチャンネルごと上限まで開き続けるので、
|
|
313
|
+
* 「どの局のせいか」が分からないと直しようがない。
|
|
314
|
+
*/
|
|
315
|
+
shortfall(): string[];
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* **1局の「いま」と「次」(EIT[p/f]) を追う。** ライブで番組の切り替わりを知るのに使う。
|
|
319
|
+
*
|
|
320
|
+
* 番組表 (schedule) は数日先までの予定で、**延長や繰り下げには追いつかない**
|
|
321
|
+
* (局が送り直してくるのは後になってから)。p/f は放送そのものに追従していて、
|
|
322
|
+
* 野球が延びれば「いま」の尺が延び、終われば「次」が「いま」に繰り上がる。
|
|
323
|
+
*
|
|
324
|
+
* section 0 が「いま」、section 1 が「次」と決まっている (`EpgReader.onAir` と同じ)。
|
|
325
|
+
*
|
|
326
|
+
* **中身で比べる。** 書き換われば version が上がるはずだが、上げずに中身だけ替える
|
|
327
|
+
* 送り手を当てにしないよう、番組の番号・時刻・尺・タイトルで比べる。p/f は1局ぶん数百バイトが
|
|
328
|
+
* 数秒おきに来るだけなので、毎回読んでも軽い
|
|
329
|
+
*/
|
|
330
|
+
export declare class PresentFollowing {
|
|
331
|
+
private readonly serviceId;
|
|
332
|
+
private readonly sections;
|
|
333
|
+
/** section 0 (いま) と 1 (次) で最後に読んだ番組。来ていなければ null */
|
|
334
|
+
private readonly held;
|
|
335
|
+
/** 中身の印 (`mark`)。同じものが繰り返し来ても変わったと言わない */
|
|
336
|
+
private readonly marks;
|
|
337
|
+
/** @param serviceId 追う局 (放送が名乗っている service_id) */
|
|
338
|
+
constructor(serviceId: number);
|
|
339
|
+
/** いま流れている番組。まだ来ていない・放送が空けているなら null */
|
|
340
|
+
get present(): EitEvent | null;
|
|
341
|
+
/** 次の番組 */
|
|
342
|
+
get following(): EitEvent | null;
|
|
343
|
+
/**
|
|
344
|
+
* 188 バイトのパケットを1つ。**いまか次のどちらかが変わったら true**。
|
|
345
|
+
* 最初に読めたときも true (知らなかったものが分かった)
|
|
346
|
+
*/
|
|
347
|
+
feedPacket(packet: Uint8Array): boolean;
|
|
348
|
+
}
|
|
349
|
+
export {};
|