├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── VERSION.md ├── ariblib ├── __init__.py ├── __main__.py ├── aribgaiji.py ├── aribstr.py ├── caption.py ├── command │ ├── __init__.py │ ├── split.py │ └── vtt.py ├── constants.py ├── descriptors.py ├── drcs.py ├── drcs.tsv ├── event.py ├── mnemonics.py ├── packet.py ├── sections.py ├── service.py ├── syntax.py └── tables.py ├── setup.cfg ├── setup.py └── util ├── sidump └── tsdump /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[co] 2 | .DS_Store 3 | 4 | # Packages 5 | *.egg 6 | *.egg-info 7 | dist 8 | build 9 | eggs 10 | parts 11 | bin 12 | var 13 | sdist 14 | develop-eggs 15 | .installed.cfg 16 | 17 | # OSX 18 | .AppleDouble 19 | 20 | # Installer logs 21 | pip-log.txt 22 | 23 | # Unit test / coverage reports 24 | .cache 25 | .coverage 26 | .tox 27 | 28 | #Translations 29 | *.mo 30 | 31 | #Mr Developer 32 | .mr.developer.cfg 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 youzaka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include LICENSE 3 | include ariblib/drcs.tsv 4 | recursive-include ariblib *.py 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ariblib 2 | 3 | ARIB-STD-B10 や ARIB-STD-B24 などの Python3+ での実装です。 4 | 5 | m2tsをパースするライブラリと、応用を行ういくつかのコマンドからなります。 6 | 7 | ## インストール 8 | pipからインストールするには以下のようにします: 9 | ``` 10 | $ sudo pip install ariblib 11 | ``` 12 | 13 | パッケージインストールがうまくいかない場合や、直接ソースコードからパッケージを作成する場合は: 14 | ``` 15 | $ git clone https://github.com/youzaka/ariblib.git 16 | $ sudo python setup.py install 17 | ``` 18 | 19 | ## コマンド利用例 20 | ### WebVTT 互換の字幕ファイルを作成する 21 | ``` 22 | $ python -m ariblib vtt SRC DST 23 | ``` 24 | とすると、 SRC にある ts ファイルを読みこみ、 DST に出力します。 25 | 26 | - DST に `-` を指定すると標準出力に書き出します。 27 | 28 | ### tsから必要なストリームのみを取り出す(ワンセグなどの削除) 29 | ``` 30 | $ python -m ariblib split SRC DST 31 | ``` 32 | とすると、 SRC にある ts ファイルが指定する PAT 情報を読み込み、最初のストリームの動画・音声のみを保存した TS ファイルを DST に保存します。 TSSplitter のようなことができます。 33 | 34 | ## ライブラリ利用例 35 | コマンド化されていないことも、直接ライブラリを使って操作すると実現できます。 (PullRequestは随時受け付けています) 36 | 37 | ### 例1: 字幕を表示 38 | ```python 39 | 40 | from ariblib import tsopen 41 | from ariblib.caption import captions 42 | 43 | import sys 44 | 45 | with tsopen(sys.argv[1]) as ts: 46 | for caption in captions(ts, color=True): 47 | body = str(caption.body) 48 | 49 | # アダプテーションフィールドの PCR の値と、そこから一番近い TOT テーブルの値から、 50 | # 字幕の表示された時刻を計算します (若干誤差が出ます) 51 | # PCR が一周した場合の処理は実装されていません 52 | datetime = caption.datetime.strftime('%Y-%m-%d %H:%M:%S') 53 | print('\033[35m' + datetime + '\33[37m') 54 | print(body) 55 | ``` 56 | 57 | ### 例2: いま放送中の番組と次の番組を表示 58 | ```python 59 | 60 | import sys 61 | 62 | from ariblib import tsopen 63 | from ariblib.descriptors import ShortEventDescriptor 64 | from ariblib.sections import EventInformationSection 65 | 66 | def show_program(eit): 67 | event = iter(eit.events).__next__() 68 | program_title = event.descriptors[ShortEventDescriptor][0].event_name_char 69 | start = event.start_time 70 | return "{} {}".format(program_title, start) 71 | 72 | with tsopen(sys.argv[1]) as ts: 73 | # 自ストリームの現在と次の番組を表示する 74 | EventInformationSection._table_ids = [0x4E] 75 | current = next(table for table in ts.sections(EventInformationSection) 76 | if table.section_number == 0) 77 | following = next(table for table in ts.sections(EventInformationSection) 78 | if table.section_number == 1) 79 | print('今の番組', show_program(current)) 80 | print('次の番組', show_program(following)) 81 | ``` 82 | 83 | ### 例3: 放送局名の一欄を表示 84 | (地上波ではその局, BSでは全局が表示される) 85 | ```python 86 | 87 | import sys 88 | 89 | from ariblib import tsopen 90 | from ariblib.constants import SERVICE_TYPE 91 | from ariblib.descriptors import ServiceDescriptor 92 | from ariblib.sections import ServiceDescriptionSection 93 | 94 | with tsopen(sys.argv[1]) as ts: 95 | for sdt in ts.sections(ServiceDescriptionSection): 96 | for service in sdt.services: 97 | for sd in service.descriptors[ServiceDescriptor]: 98 | print(service.service_id, SERVICE_TYPE[sd.service_type], 99 | sd.service_provider_name, sd.service_name) 100 | ``` 101 | 102 | ### 例4: 動画パケットの PID とその動画の解像度を表示 103 | ```python 104 | 105 | import sys 106 | 107 | from ariblib import tsopen 108 | from ariblib.constants import VIDEO_ENCODE_FORMAT 109 | from ariblib.descriptors import VideoDecodeControlDescriptor 110 | from ariblib.sections import ProgramAssociationSection, ProgramMapSection 111 | 112 | with tsopen(sys.argv[1]) as ts: 113 | pat = next(ts.sections(ProgramAssociationSection)) 114 | ProgramMapSection._pids = list(pat.pmt_pids) 115 | for pmt in ts.sections(ProgramMapSection): 116 | for tsmap in pmt.maps: 117 | for vd in tsmap.descriptors.get(VideoDecodeControlDescriptor, []): 118 | print(tsmap.elementary_PID, VIDEO_ENCODE_FORMAT[vd.video_encode_format]) 119 | ``` 120 | 121 | ### 例5: EPG情報の表示 122 | ```python 123 | from ariblib import tsopen 124 | from ariblib.event import events 125 | 126 | import sys 127 | 128 | with tsopen(sys.argv[1]) as ts: 129 | for event in events(ts): 130 | max_len = max(map(len, event.__dict__.keys())) 131 | template = "{:%ds} {}" % max_len 132 | for key, value in event.__dict__.items(): 133 | print(template.format(key, value)) 134 | print('-' * 80) 135 | ``` 136 | 137 | ### 例6: 深夜アニメの出力 138 | ```python 139 | 140 | import sys 141 | 142 | from ariblib import tsopen 143 | from ariblib.descriptors import ContentDescriptor, ShortEventDescriptor 144 | from ariblib.sections import EventInformationSection 145 | 146 | with tsopen(sys.argv[1]) as ts: 147 | EventInformationSection._table_ids = range(0x50, 0x70) 148 | for eit in ts.sections(EventInformationSection): 149 | for event in eit.events: 150 | for genre in event.descriptors.get(ContentDescriptor, []): 151 | nibble = genre.nibbles[0] 152 | # ジャンルがアニメでないイベント、アニメであっても放送開始時刻が5時から21時のものを除きます 153 | if nibble.content_nibble_level_1 != 0x07 or 4 < event.start_time.hour < 22: 154 | continue 155 | for sed in event.descriptors.get(ShortEventDescriptor, []): 156 | print(eit.service_id, event.event_id, event.start_time, 157 | event.duration, sed.event_name_char, sed.text_char) 158 | ``` 159 | -------------------------------------------------------------------------------- /VERSION.md: -------------------------------------------------------------------------------- 1 | バージョン履歴 2 | ============== 3 | 4 | 0.0.5 5 | ---- 6 | - 巡回カウンターの取得関数がなかったので追加 7 | - Eventオブジェクトについて、番組詳細がない場合に番組詳細キーを追加しないように修正 8 | - SDTTの定義を追加 9 | - EIT[p/f]を表すEITのサブクラスを追加 10 | - コールバックAPIを追加 11 | 12 | - ifセクションから親のプロパティを読めるように修正 13 | 14 | 0.0.4 15 | ----- 16 | - 回数固定ループの入れ子が正しく処理できていなかったのを修正 17 | - それにともないジェネレータを返していた処理をリストを返すように修正 18 | - tsopen関数を追加 19 | - イベントと字幕のラッパークラスを追加 20 | - DRCSイメージ出力機能を追加 21 | - Python3 に対応しているオフィシャルの PIL がないため、 https://github.com/sloonz/pil-py3k のを利用 22 | - PILがインストールされていない場合はドットイメージをテキストで出力 23 | - DRCSイメージは ~/.ariblib/drcs/ 以下に、 ```.png``` または ```.txt``` として保存される 24 | - DRCSイメージと文字のマッピングは ~/.ariblib/drcs.tsv に ```[TAB]``` と記述する 25 | - 一度取得したディスクリプタの値をキャッシュするように変更 26 | 27 | 0.0.3 28 | ----- 29 | - CATの定義を追加 30 | - いくつかの記述子の定義を追加 31 | - バッファに溜まってるsection_length以上のバイナリを最後にyieldするように変更 32 | 33 | 0.0.2 34 | ----- 35 | - 1テーブルに複数のセクションが入っている場合の対応 36 | 37 | 0.0.1 38 | ----- 39 | - とりあえず動く 40 | 41 | -------------------------------------------------------------------------------- /ariblib/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.0.5' 2 | 3 | from ariblib.packet import TransportStreamFile, tsopen 4 | -------------------------------------------------------------------------------- /ariblib/__main__.py: -------------------------------------------------------------------------------- 1 | from ariblib import command 2 | 3 | if __name__ == '__main__': 4 | command.main() 5 | -------------------------------------------------------------------------------- /ariblib/aribgaiji.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | ARIB外字 5 | original: https://github.com/murakamiy/epgdump_py/blob/master/aribgaiji.py 6 | Copyright (C) 2011 Yasumasa Murakami. All Rights Reserved. 7 | """ 8 | 9 | GAIJI_MAP_TITLE = { 10 | 0x7A50: "[HV]", 11 | 0x7A51: "[SD]", 12 | 0x7A52: "[P]", 13 | 0x7A53: "[W]", 14 | 0x7A54: "[MV]", 15 | 0x7A55: "[手]", 16 | 0x7A56: "[字]", 17 | 0x7A57: "[双]", 18 | 0x7A58: "[デ]", 19 | 0x7A59: "[S]", 20 | 0x7A5A: "[二]", 21 | 0x7A5B: "[多]", 22 | 0x7A5C: "[解]", 23 | 0x7A5D: "[SS]", 24 | 0x7A5E: "[B]", 25 | 0x7A5F: "[N]", 26 | 0x7A62: "[天]", 27 | 0x7A63: "[交]", 28 | 0x7A64: "[映]", 29 | 0x7A65: "[無]", 30 | 0x7A66: "[料]", 31 | 0x7A67: "[年齢制限]", 32 | 0x7A68: "[前]", 33 | 0x7A69: "[後]", 34 | 0x7A6A: "[再]", 35 | 0x7A6B: "[新]", 36 | 0x7A6C: "[初]", 37 | 0x7A6D: "[終]", 38 | 0x7A6E: "[生]", 39 | 0x7A6F: "[販]", 40 | 0x7A70: "[声]", 41 | 0x7A71: "[吹]", 42 | 0x7A72: "[PPV]", 43 | } 44 | 45 | GAIJI_MAP_OTHER = { 46 | 0x7A60: "■", 47 | 0x7A61: "●", 48 | 0x7A73: "㊙", 49 | 0x7A74: "\U0001F200", # ほか 50 | 51 | 0x7C21: "→", 52 | 0x7C22: "←", 53 | 0x7C23: "↑", 54 | 0x7C24: "↓", 55 | 0x7C25: "●", 56 | 0x7C26: "○", 57 | 0x7C27: "年", 58 | 0x7C28: "月", 59 | 0x7C29: "日", 60 | 0x7C2A: "円", 61 | 0x7C2B: "㎡", 62 | 0x7C2C: "㎥", 63 | 0x7C2D: "㎝", 64 | 0x7C2E: "㎠", 65 | 0x7C2F: "㎤", 66 | 0x7C30: "0.", 67 | 0x7C31: "1.", 68 | 0x7C32: "2.", 69 | 0x7C33: "3.", 70 | 0x7C34: "4.", 71 | 0x7C35: "5.", 72 | 0x7C36: "6.", 73 | 0x7C37: "7.", 74 | 0x7C38: "8.", 75 | 0x7C39: "9.", 76 | 0x7C3A: "氏", 77 | 0x7C3B: "副", 78 | 0x7C3C: "元", 79 | 0x7C3D: "故", 80 | 0x7C3E: "前", 81 | 0x7C3F: "新", 82 | 0x7C40: "0,", 83 | 0x7C41: "1,", 84 | 0x7C42: "2,", 85 | 0x7C43: "3,", 86 | 0x7C44: "4,", 87 | 0x7C45: "5,", 88 | 0x7C46: "6,", 89 | 0x7C47: "7,", 90 | 0x7C48: "8,", 91 | 0x7C49: "9,", 92 | 0x7C4A: "㈳", 93 | 0x7C4B: "㈶", 94 | 0x7C4C: "㈲", 95 | 0x7C4D: "㈱", 96 | 0x7C4E: "㈹", 97 | 0x7C4F: "㉄", 98 | 0x7C50: "▶", 99 | 0x7C51: "◀", 100 | 0x7C52: "〖", 101 | 0x7C53: "〗", 102 | 0x7C54: "⟐", 103 | 0x7C55: "^2", 104 | 0x7C56: "^3", 105 | 0x7C57: "(CD)", 106 | 0x7C58: "(vn)", 107 | 0x7C59: "(ob)", 108 | 0x7C5A: "(cb)", 109 | 0x7C5B: "(ce", 110 | 0x7C5C: "mb)", 111 | 0x7C5D: "(hp)", 112 | 0x7C5E: "(br)", 113 | 0x7C5F: "(p)", 114 | 0x7C60: "(s)", 115 | 0x7C61: "(ms)", 116 | 0x7C62: "(t)", 117 | 0x7C63: "(bs)", 118 | 0x7C64: "(b)", 119 | 0x7C65: "(tb)", 120 | 0x7C66: "(tp)", 121 | 0x7C67: "(ds)", 122 | 0x7C68: "(ag)", 123 | 0x7C69: "(eg)", 124 | 0x7C6A: "(vo)", 125 | 0x7C6B: "(fl)", 126 | 0x7C6C: "(ke", 127 | 0x7C6D: "y)", 128 | 0x7C6E: "(sa", 129 | 0x7C6F: "x)", 130 | 0x7C70: "(sy", 131 | 0x7C71: "n)", 132 | 0x7C72: "(or", 133 | 0x7C73: "g)", 134 | 0x7C74: "(pe", 135 | 0x7C75: "r)", 136 | 0x7C76: "(R)", 137 | 0x7C77: "(C)", 138 | 0x7C78: "(箏)", 139 | 0x7C79: "DJ", 140 | 0x7C7A: "\U0001F226", # [演] 141 | 0x7C7B: "Fax", 142 | 143 | 0x7D21: "㈪", 144 | 0x7D22: "㈫", 145 | 0x7D23: "㈬", 146 | 0x7D24: "㈭", 147 | 0x7D25: "㈮", 148 | 0x7D26: "㈯", 149 | 0x7D27: "㈰", 150 | 0x7D28: "㈷", 151 | 0x7D29: "㍾", 152 | 0x7D2A: "㍽", 153 | 0x7D2B: "㍼", 154 | 0x7D2C: "㍻", 155 | 0x7D2D: "№", 156 | 0x7D2E: "℡", 157 | 0x7D2F: "〶", 158 | 0x7D30: "○", 159 | 0x7D31: "\U0001F240", # 〔本〕 160 | 0x7D32: "\U0001F241", # 〔三〕 161 | 0x7D33: "\U0001F242", # 〔二〕 162 | 0x7D34: "\U0001F243", # 〔安〕 163 | 0x7D35: "\U0001F244", # 〔点〕 164 | 0x7D36: "\U0001F245", # 〔打〕 165 | 0x7D37: "\U0001F246", # 〔盗〕 166 | 0x7D38: "\U0001F247", # 〔勝〕 167 | 0x7D39: "\U0001F248", # 〔敗〕 168 | 0x7D3A: "\U0001F122", # 〔S〕 169 | 0x7D3B: "\U0001F227", # [投] 170 | 0x7D3C: "\U0001F228", # [捕] 171 | 0x7D3D: "\U0001F229", # [一] 172 | 0x7D3E: "[二]", 173 | 0x7D3F: "\U0001F22A", # [三] 174 | 0x7D40: "\U0001F22B", # [遊] 175 | 0x7D41: "\U0001F22C", # [左] 176 | 0x7D42: "\U0001F22D", # [中] 177 | 0x7D43: "\U0001F22E", # [右] 178 | 0x7D44: "\U0001F22F", # [指] 179 | 0x7D45: "\U0001F230", # [走] 180 | 0x7D46: "\U0001F231", # [打] 181 | 0x7D47: "㍑", 182 | 0x7D48: "㎏", 183 | 0x7D49: "㎐", 184 | 0x7D4A: "ha", 185 | 0x7D4B: "㎞", 186 | 0x7D4C: "㎢", 187 | 0x7D4D: "㍱", 188 | 0x7D4E: "・", 189 | 0x7D4F: "・", 190 | 0x7D50: "1/2", 191 | 0x7D51: "0/3", 192 | 0x7D52: "1/3", 193 | 0x7D53: "2/3", 194 | 0x7D54: "1/4", 195 | 0x7D55: "3/4", 196 | 0x7D56: "1/5", 197 | 0x7D57: "2/5", 198 | 0x7D58: "3/5", 199 | 0x7D59: "4/5", 200 | 0x7D5A: "1/6", 201 | 0x7D5B: "5/6", 202 | 0x7D5C: "1/7", 203 | 0x7D5D: "1/8", 204 | 0x7D5E: "1/9", 205 | 0x7D5F: "1/10", 206 | 0x7D60: "☀", 207 | 0x7D61: "☁", 208 | 0x7D62: "☂", 209 | 0x7D63: "☃", 210 | 0x7D64: "☖", 211 | 0x7D65: "☗", 212 | 0x7D66: "▽", 213 | 0x7D67: "▼", 214 | 0x7D68: "♦", 215 | 0x7D69: "♥", 216 | 0x7D6A: "♣", 217 | 0x7D6B: "♠", 218 | 0x7D6C: "⌺", 219 | 0x7D6D: "⦿", 220 | 0x7D6E: "‼", 221 | 0x7D6F: "⁉", 222 | 0x7D70: "(曇/晴)", 223 | 0x7D71: "☔", 224 | 0x7D72: "(雨)", 225 | 0x7D73: "(雪)", 226 | 0x7D74: "(大雪)", 227 | 0x7D75: "⚡", 228 | 0x7D76: "(雷雨)", 229 | 0x7D77: " ", 230 | 0x7D78: "・", 231 | 0x7D79: "・", 232 | 0x7D7A: "♬", 233 | 0x7D7B: "☎", 234 | 235 | 0x7E21: "Ⅰ", 236 | 0x7E22: "Ⅱ", 237 | 0x7E23: "Ⅲ", 238 | 0x7E24: "Ⅳ", 239 | 0x7E25: "Ⅴ", 240 | 0x7E26: "Ⅵ", 241 | 0x7E27: "Ⅶ", 242 | 0x7E28: "Ⅷ", 243 | 0x7E29: "Ⅸ", 244 | 0x7E2A: "Ⅹ", 245 | 0x7E2B: "Ⅺ", 246 | 0x7E2C: "Ⅻ", 247 | 0x7E2D: "⑰", 248 | 0x7E2E: "⑱", 249 | 0x7E2F: "⑲", 250 | 0x7E30: "⑳", 251 | 0x7E31: "⑴", 252 | 0x7E32: "⑵", 253 | 0x7E33: "⑶", 254 | 0x7E34: "⑷", 255 | 0x7E35: "⑸", 256 | 0x7E36: "⑹", 257 | 0x7E37: "⑺", 258 | 0x7E38: "⑻", 259 | 0x7E39: "⑼", 260 | 0x7E3A: "⑽", 261 | 0x7E3B: "⑾", 262 | 0x7E3C: "⑿", 263 | 0x7E3D: "㉑", 264 | 0x7E3E: "㉒", 265 | 0x7E3F: "㉓", 266 | 0x7E40: "㉔", 267 | 0x7E41: "(A)", 268 | 0x7E42: "(B)", 269 | 0x7E43: "(C)", 270 | 0x7E44: "(D)", 271 | 0x7E45: "(E)", 272 | 0x7E46: "(F)", 273 | 0x7E47: "(G)", 274 | 0x7E48: "(H)", 275 | 0x7E49: "(I)", 276 | 0x7E4A: "(J)", 277 | 0x7E4B: "(K)", 278 | 0x7E4C: "(L)", 279 | 0x7E4D: "(M)", 280 | 0x7E4E: "(N)", 281 | 0x7E4F: "(O)", 282 | 0x7E50: "(P)", 283 | 0x7E51: "(Q)", 284 | 0x7E52: "(R)", 285 | 0x7E53: "(S)", 286 | 0x7E54: "(T)", 287 | 0x7E55: "(U)", 288 | 0x7E56: "(V)", 289 | 0x7E57: "(W)", 290 | 0x7E58: "(X)", 291 | 0x7E59: "(Y)", 292 | 0x7E5A: "(Z)", 293 | 0x7E5B: "㉕", 294 | 0x7E5C: "㉖", 295 | 0x7E5D: "㉗", 296 | 0x7E5E: "㉘", 297 | 0x7E5F: "㉙", 298 | 0x7E60: "㉚", 299 | 0x7E61: "①", 300 | 0x7E62: "②", 301 | 0x7E63: "③", 302 | 0x7E64: "④", 303 | 0x7E65: "⑤", 304 | 0x7E66: "⑥", 305 | 0x7E67: "⑦", 306 | 0x7E68: "⑧", 307 | 0x7E69: "⑨", 308 | 0x7E6A: "⑩", 309 | 0x7E6B: "⑪", 310 | 0x7E6C: "⑫", 311 | 0x7E6D: "⑬", 312 | 0x7E6E: "⑭", 313 | 0x7E6F: "⑮", 314 | 0x7E70: "⑯", 315 | 0x7E71: "❶", 316 | 0x7E72: "❷", 317 | 0x7E73: "❸", 318 | 0x7E74: "❹", 319 | 0x7E75: "❺", 320 | 0x7E76: "❻", 321 | 0x7E77: "❼", 322 | 0x7E78: "❽", 323 | 0x7E79: "❾", 324 | 0x7E7A: "❿", 325 | 0x7E7B: "⓫", 326 | 0x7E7C: "⓬", 327 | 0x7E7D: "㉛", 328 | 329 | 0x7521: "㐂", 330 | 0x7522: "亭", 331 | 0x7523: "份", 332 | 0x7524: "仿", 333 | 0x7525: "侚", 334 | 0x7526: "俉", 335 | 0x7527: "傜", 336 | 0x7528: "儞", 337 | 0x7529: "冼", 338 | 0x752A: "㔟", 339 | 0x752B: "匇", 340 | 0x752C: "卡", 341 | 0x752D: "卬", 342 | 0x752E: "詹", 343 | 0x752F: "吉", 344 | 0x7530: "呍", 345 | 0x7531: "咖", 346 | 0x7532: "咜", 347 | 0x7533: "咩", 348 | 0x7534: "唎", 349 | 0x7535: "啊", 350 | 0x7536: "噲", 351 | 0x7537: "囤", 352 | 0x7538: "圳", 353 | 0x7539: "圴", 354 | 0x753A: "塚", 355 | 0x753B: "墀", 356 | 0x753C: "姤", 357 | 0x753D: "娣", 358 | 0x753E: "婕", 359 | 0x753F: "寬", 360 | 0x7540: "﨑", 361 | 0x7541: "㟢", 362 | 0x7542: "庬", 363 | 0x7543: "弴", 364 | 0x7544: "彅", 365 | 0x7545: "德", 366 | 0x7546: "怗", 367 | 0x7547: "恵", 368 | 0x7548: "愰", 369 | 0x7549: "昤", 370 | 0x754A: "曈", 371 | 0x754B: "曙", 372 | 0x754C: "曺", 373 | 0x754D: "曻", 374 | 0x754E: "桒", 375 | 0x754F: "・", 376 | 0x7550: "椑", 377 | 0x7551: "椻", 378 | 0x7552: "橅", 379 | 0x7553: "檑", 380 | 0x7554: "櫛", 381 | 0x7555: "・", 382 | 0x7556: "・", 383 | 0x7557: "・", 384 | 0x7558: "毱", 385 | 0x7559: "泠", 386 | 0x755A: "洮", 387 | 0x755B: "海", 388 | 0x755C: "涿", 389 | 0x755D: "淊", 390 | 0x755E: "淸", 391 | 0x755F: "渚", 392 | 0x7560: "潞", 393 | 0x7561: "濹", 394 | 0x7562: "灤", 395 | 0x7563: "・", 396 | 0x7564: "・", 397 | 0x7565: "煇", 398 | 0x7566: "燁", 399 | 0x7567: "爀", 400 | 0x7568: "玟", 401 | 0x7569: "・", 402 | 0x756A: "珉", 403 | 0x756B: "珖", 404 | 0x756C: "琛", 405 | 0x756D: "琡", 406 | 0x756E: "琢", 407 | 0x756F: "琦", 408 | 0x7570: "琪", 409 | 0x7571: "琬", 410 | 0x7572: "琹", 411 | 0x7573: "瑋", 412 | 0x7574: "㻚", 413 | 0x7575: "畵", 414 | 0x7576: "疁", 415 | 0x7577: "睲", 416 | 0x7578: "䂓", 417 | 0x7579: "磈", 418 | 0x757A: "磠", 419 | 0x757B: "祇", 420 | 0x757C: "禮", 421 | 0x757D: "・", 422 | 0x757E: "・", 423 | 424 | 0x7621: "・", 425 | 0x7622: "秚", 426 | 0x7623: "稞", 427 | 0x7624: "筿", 428 | 0x7625: "簱", 429 | 0x7626: "䉤", 430 | 0x7627: "綋", 431 | 0x7628: "羡", 432 | 0x7629: "脘", 433 | 0x762A: "脺", 434 | 0x762B: "・", 435 | 0x762C: "芮", 436 | 0x762D: "葛", 437 | 0x762E: "蓜", 438 | 0x762F: "蓬", 439 | 0x7630: "蕙", 440 | 0x7631: "藎", 441 | 0x7632: "蝕", 442 | 0x7633: "蟬", 443 | 0x7634: "蠋", 444 | 0x7635: "裵", 445 | 0x7636: "角", 446 | 0x7637: "諶", 447 | 0x7638: "跎", 448 | 0x7639: "辻", 449 | 0x763A: "迶", 450 | 0x763B: "郝", 451 | 0x763C: "鄧", 452 | 0x763D: "鄭", 453 | 0x763E: "醲", 454 | 0x763F: "鈳", 455 | 0x7640: "銈", 456 | 0x7641: "錡", 457 | 0x7642: "鍈", 458 | 0x7643: "閒", 459 | 0x7644: "雞", 460 | 0x7645: "餃", 461 | 0x7646: "饀", 462 | 0x7647: "髙", 463 | 0x7648: "鯖", 464 | 0x7649: "鷗", 465 | 0x764A: "麴", 466 | 0x764B: "麵", 467 | } 468 | 469 | GAIJI_MAP = {} 470 | GAIJI_MAP.update(GAIJI_MAP_TITLE) 471 | GAIJI_MAP.update(GAIJI_MAP_OTHER) 472 | -------------------------------------------------------------------------------- /ariblib/aribstr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.2 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import io 6 | 7 | from ariblib.aribgaiji import * 8 | 9 | """ 10 | ARIB文字の実装クラス 11 | original: https://github.com/murakamiy/epgdump_py/blob/master/aribstr.py 12 | Copyright (C) 2011 Yasumasa Murakami. All Rights Reserved. 13 | """ 14 | 15 | 16 | class Code: 17 | (KANJI, ALPHANUMERIC, HIRAGANA, KATAKANA, MOSAIC_A, MOSAIC_B, MOSAIC_C, 18 | MOSAIC_D, PROP_ALPHANUMERIC, PROP_HIRAGANA, PROP_KATAKANA, 19 | JIS_X0201_KATAKANA, JIS_KANJI_PLANE_1, JIS_KANJI_PLANE_2, 20 | ADDITIONAL_SYMBOLS, UNSUPPORTED) = range(16) 21 | 22 | CODE_SET_G = { 23 | 0x42: (Code.KANJI, 2), 24 | 0x4A: (Code.ALPHANUMERIC, 1), 25 | 0x30: (Code.HIRAGANA, 1), 26 | 0x31: (Code.KATAKANA, 1), 27 | 0x32: (Code.MOSAIC_A, 1), 28 | 0x33: (Code.MOSAIC_B, 1), 29 | 0x34: (Code.MOSAIC_C, 1), 30 | 0x35: (Code.MOSAIC_D, 1), 31 | 0x36: (Code.PROP_ALPHANUMERIC, 1), 32 | 0x37: (Code.PROP_HIRAGANA, 1), 33 | 0x38: (Code.PROP_KATAKANA, 1), 34 | 0x49: (Code.JIS_X0201_KATAKANA, 1), 35 | 0x39: (Code.JIS_KANJI_PLANE_1, 2), 36 | 0x3A: (Code.JIS_KANJI_PLANE_2, 2), 37 | 0x3B: (Code.ADDITIONAL_SYMBOLS, 2), 38 | } 39 | 40 | CODE_SET_DRCS = { 41 | 0x40: (Code.UNSUPPORTED, 2), # DRCS-0 42 | 0x41: (Code.UNSUPPORTED, 1), # DRCS-1 43 | 0x42: (Code.UNSUPPORTED, 1), # DRCS-2 44 | 0x43: (Code.UNSUPPORTED, 1), # DRCS-3 45 | 0x44: (Code.UNSUPPORTED, 1), # DRCS-4 46 | 0x45: (Code.UNSUPPORTED, 1), # DRCS-5 47 | 0x46: (Code.UNSUPPORTED, 1), # DRCS-6 48 | 0x47: (Code.UNSUPPORTED, 1), # DRCS-7 49 | 0x48: (Code.UNSUPPORTED, 1), # DRCS-8 50 | 0x49: (Code.UNSUPPORTED, 1), # DRCS-9 51 | 0x4A: (Code.UNSUPPORTED, 1), # DRCS-10 52 | 0x4B: (Code.UNSUPPORTED, 1), # DRCS-11 53 | 0x4C: (Code.UNSUPPORTED, 1), # DRCS-12 54 | 0x4D: (Code.UNSUPPORTED, 1), # DRCS-13 55 | 0x4E: (Code.UNSUPPORTED, 1), # DRCS-14 56 | 0x4F: (Code.UNSUPPORTED, 1), # DRCS-15 57 | 0x70: (Code.UNSUPPORTED, 1), # MACRO 58 | } 59 | 60 | CODE_SET_KEYS = list(CODE_SET_DRCS.keys()) + list(CODE_SET_G.keys()) 61 | 62 | ARIB_BASE = { 63 | 0x79: 0x3C, 64 | 0x7A: 0x23, 65 | 0x7B: 0x56, 66 | 0x7C: 0x57, 67 | 0x7D: 0x22, 68 | 0x7E: 0x26, 69 | } 70 | 71 | ARIB_HIRAGANA_MAP = { 72 | 0x77: 0x35, 73 | 0x78: 0x36, 74 | } 75 | 76 | ARIB_KATAKANA_MAP = { 77 | 0x77: 0x33, 78 | 0x78: 0x34, 79 | } 80 | 81 | ARIB_KATAKANA_MAP.update(ARIB_BASE) 82 | ARIB_HIRAGANA_MAP.update(ARIB_BASE) 83 | # ひらがな カタカナ 84 | # ゝ 35 ヽ 33 85 | # ゞ 36 ヾ 34 86 | # ー 3c ー 3c 87 | # 。 23 。 23 88 | # 「 56 「 56 89 | # 」 57 」 57 90 | # 、 22 、 22 91 | # ・ 26 ・ 26 92 | 93 | ESC_SEQ_ASCII = (0x1B, 0x28, 0x42) 94 | ESC_SEQ_ZENKAKU = (0x1B, 0x24, 0x42) 95 | ESC_SEQ_HANKAKU = (0x1B, 0x28, 0x49) 96 | 97 | 98 | class Buffer: 99 | G0, G1, G2, G3 = range(4) 100 | 101 | 102 | class CodeArea: 103 | LEFT, RIGHT = range(2) 104 | 105 | 106 | class AribIndexError(Exception): 107 | pass 108 | 109 | 110 | class EscapeSequenceError(Exception): 111 | pass 112 | 113 | 114 | class DegignationError(Exception): 115 | pass 116 | 117 | 118 | class CodeSetController: 119 | 120 | def __init__(self): 121 | self.v_buffer = { 122 | Buffer.G0: CODE_SET_G[0x42], # KANJI 123 | Buffer.G1: CODE_SET_G[0x4a], # ALPHANUMERIC 124 | Buffer.G2: CODE_SET_G[0x30], # HIRAGANA 125 | Buffer.G3: CODE_SET_G[0x31], # KATAKANA 126 | } 127 | self.single_shift = None 128 | self.graphic_left = Buffer.G0 # KANJI 129 | self.graphic_right = Buffer.G2 # HIRAGANA 130 | self.esc_seq_count = 0 131 | self.esc_buffer_index = Buffer.G0 132 | self.esc_drcs = False 133 | 134 | def degignate(self, code): 135 | if not code in CODE_SET_KEYS: 136 | raise DegignationError( 137 | 'esc_seq_count=%i esc_buffer_index=%s code=0x%02X' % ( 138 | self.esc_seq_count, self.esc_buffer_index, code 139 | ) 140 | ) 141 | if self.esc_drcs: 142 | self.v_buffer[self.esc_buffer_index] = CODE_SET_DRCS[code] 143 | else: 144 | self.v_buffer[self.esc_buffer_index] = CODE_SET_G[code] 145 | self.esc_seq_count = 0 146 | 147 | def invoke(self, buffer_index, area, locking_shift=True): 148 | if CodeArea.LEFT == area: 149 | if locking_shift: 150 | self.graphic_left = buffer_index 151 | else: 152 | self.single_shift = buffer_index 153 | elif CodeArea.RIGHT == area: 154 | self.graphic_right = buffer_index 155 | self.esc_seq_count = 0 156 | 157 | def get_current_code(self, data): 158 | if 0x21 <= data <= 0x7E: 159 | if self.single_shift: 160 | code = self.v_buffer[self.single_shift] 161 | self.single_shift = None 162 | return code 163 | return self.v_buffer[self.graphic_left] 164 | elif 0xA1 <= data <= 0xFE: 165 | return self.v_buffer[self.graphic_right] 166 | return None 167 | 168 | def set_escape(self, buffer_index, drcs): 169 | if buffer_index is not None: 170 | self.esc_buffer_index = buffer_index 171 | self.esc_drcs = drcs 172 | self.esc_seq_count += 1 173 | 174 | 175 | class AribArray(bytearray): 176 | esc_seq = None 177 | 178 | def pop0(self): 179 | try: 180 | return self.pop(0) 181 | except IndexError: 182 | raise AribIndexError 183 | 184 | def append_str(self, esc_seq, *string): 185 | if self.esc_seq != esc_seq: 186 | self.extend(esc_seq) 187 | self.esc_seq = esc_seq 188 | if len(string) > 1: 189 | self.extend(string) 190 | else: 191 | self.append(string[0]) 192 | 193 | 194 | class AribString: 195 | def __init__(self, array): 196 | self.control = CodeSetController() 197 | self.arib_array = AribArray(array) 198 | self.jis_array = AribArray() 199 | self.utf_buffer = io.StringIO() 200 | self.utf_buffer_symbol = io.StringIO() 201 | self.split_symbol = False 202 | 203 | def __add__(self, other): 204 | self.arib_array += other.arib_array 205 | return self 206 | 207 | def __str__(self): 208 | return self.convert_utf().rstrip() 209 | 210 | def __nonzero__(self): 211 | return not self.arib_array 212 | 213 | def convert_utf_split(self): 214 | self.split_symbol = True 215 | self.convert() 216 | self.flush_jis_array() 217 | return (self.utf_buffer.getvalue(), self.utf_buffer_symbol.getvalue()) 218 | 219 | def convert_utf(self, with_gaiji=True): 220 | self.convert(with_gaiji) 221 | self.flush_jis_array() 222 | return self.utf_buffer.getvalue() 223 | 224 | def flush_jis_array(self): 225 | if len(self.jis_array) > 0: 226 | try: 227 | uni = self.jis_array.decode('iso-2022-jp') 228 | except UnicodeDecodeError: 229 | uni = 'UnicodeDecodeError' 230 | self.utf_buffer.write(uni) 231 | self.jis_array = AribArray() 232 | 233 | def convert(self, with_gaiji=True): 234 | while True: 235 | try: 236 | data = self.arib_array.pop0() 237 | if self.control.esc_seq_count: 238 | self.do_escape(data) 239 | else: 240 | if 0x21 <= data <= 0x7E or 0xA1 <= data <= 0xFE: 241 | # GL/GR Table 242 | self.do_convert(data, with_gaiji) 243 | elif data in ( 244 | 0x20, # space 245 | 0xA0, # space (arib) 246 | 0x09, # HT 247 | ): 248 | self.jis_array.append_str(ESC_SEQ_ASCII, 0x20) 249 | elif data in ( 250 | 0x0D, # CR 251 | 0x0A, # LF 252 | ): 253 | self.jis_array.append_str(ESC_SEQ_ASCII, 0x0A) 254 | else: 255 | # Control Character 256 | self.do_control(data) 257 | except AribIndexError: 258 | break 259 | return self.jis_array 260 | 261 | def do_convert(self, data, with_gaiji=True): 262 | code, size = self.control.get_current_code(data) 263 | char = data 264 | char2 = 0x0 265 | if size == 2: 266 | char2 = self.arib_array.pop0() 267 | if 0xA1 <= char <= 0xFE: 268 | char = char & 0x7F 269 | char2 = char2 & 0x7F 270 | 271 | if code in ( 272 | Code.KANJI, Code.JIS_KANJI_PLANE_1, Code.JIS_KANJI_PLANE_2 273 | ): 274 | # 漢字コード出力 275 | self.jis_array.append_str(ESC_SEQ_ZENKAKU, char, char2) 276 | elif code in (Code.ALPHANUMERIC, Code.PROP_ALPHANUMERIC): 277 | # 英数字コード出力 278 | self.jis_array.append_str(ESC_SEQ_ASCII, char) 279 | elif code in (Code.HIRAGANA, Code.PROP_HIRAGANA): 280 | # ひらがなコード出力 281 | if char >= 0x77: 282 | self.jis_array.append_str( 283 | ESC_SEQ_ZENKAKU, 0x21, ARIB_HIRAGANA_MAP[char]) 284 | else: 285 | self.jis_array.append_str( 286 | ESC_SEQ_ZENKAKU, 0x24, char) 287 | elif code in (Code.PROP_KATAKANA, Code.KATAKANA): 288 | # カタカナコード出力 289 | if char >= 0x77: 290 | self.jis_array.append_str( 291 | ESC_SEQ_ZENKAKU, 0x21, ARIB_KATAKANA_MAP[char]) 292 | else: 293 | self.jis_array.append_str(ESC_SEQ_ZENKAKU, 0x25, char) 294 | elif code == Code.JIS_X0201_KATAKANA: 295 | # 半角カタカナコード出力 296 | self.jis_array.append_str(ESC_SEQ_HANKAKU, char) 297 | elif code == Code.ADDITIONAL_SYMBOLS: 298 | # 追加シンボル文字コード出力 299 | self.flush_jis_array() 300 | if with_gaiji: 301 | if self.split_symbol: 302 | wchar = ((char << 8) + char2) 303 | gaiji = GAIJI_MAP_TITLE.get(wchar) 304 | if gaiji is not None: 305 | self.utf_buffer_symbol.write(gaiji) 306 | else: 307 | self.utf_buffer.write(GAIJI_MAP_OTHER.get(wchar, "??")) 308 | else: 309 | self.utf_buffer.write( 310 | GAIJI_MAP.get(((char << 8) + char2), "??")) 311 | 312 | def do_control(self, data): 313 | if data == 0x0F: 314 | self.control.invoke(Buffer.G0, CodeArea.LEFT, True) # LS0 315 | elif data == 0x0E: 316 | self.control.invoke(Buffer.G1, CodeArea.LEFT, True) # LS1 317 | elif data == 0x19: 318 | self.control.invoke(Buffer.G2, CodeArea.LEFT, False) # SS2 319 | elif data == 0x1D: 320 | self.control.invoke(Buffer.G3, CodeArea.LEFT, False) # SS3 321 | elif data == 0x1B: 322 | self.control.esc_seq_count = 1 323 | 324 | def do_escape(self, data): 325 | if self.control.esc_seq_count == 1: 326 | if data == 0x6E: 327 | self.control.invoke(Buffer.G2, CodeArea.LEFT, True) # LS2 328 | elif data == 0x6F: 329 | self.control.invoke(Buffer.G3, CodeArea.LEFT, True) # LS3 330 | elif data == 0x7E: 331 | self.control.invoke(Buffer.G1, CodeArea.RIGHT, True) # LS1R 332 | elif data == 0x7D: 333 | self.control.invoke(Buffer.G2, CodeArea.RIGHT, True) # LS2R 334 | elif data == 0x7C: 335 | self.control.invoke(Buffer.G3, CodeArea.RIGHT, True) # LS3R 336 | elif data in (0x24, 0x28): 337 | self.control.set_escape(Buffer.G0, False) 338 | elif data == 0x29: 339 | self.control.set_escape(Buffer.G1, False) 340 | elif data == 0x2A: 341 | self.control.set_escape(Buffer.G2, False) 342 | elif data == 0x2B: 343 | self.control.set_escape(Buffer.G3, False) 344 | else: 345 | raise EscapeSequenceError('esc_seq_count=%i data=0x%02X' % ( 346 | self.control.esc_seq_count, data, 347 | )) 348 | elif self.control.esc_seq_count == 2: 349 | if data == 0x20: 350 | self.control.set_escape(None, True) 351 | elif data == 0x28: 352 | self.control.set_escape(Buffer.G0, False) 353 | elif data == 0x29: 354 | self.control.set_escape(Buffer.G1, False) 355 | elif data == 0x2A: 356 | self.control.set_escape(Buffer.G2, False) 357 | elif data == 0x2B: 358 | self.control.set_escape(Buffer.G3, False) 359 | else: 360 | self.control.degignate(data) 361 | elif self.control.esc_seq_count == 3: 362 | if data == 0x20: 363 | self.control.set_escape(None, True) 364 | else: 365 | self.control.degignate(data) 366 | elif self.control.esc_seq_count == 4: 367 | self.control.degignate(data) 368 | 369 | def __repr__(self): 370 | return self.convert_utf().rstrip() 371 | 372 | if __name__ == '__main__': 373 | f = open(sys.argv[1], 'rb') 374 | f.seek(0, 2) 375 | byte = f.tell() 376 | f.seek(0) 377 | arr = array.array('B') 378 | arr.fromfile(f, byte) 379 | f.close() 380 | 381 | arib = AribString(arr) 382 | arib.convert() 383 | 384 | f = open("output.txt", 'wb') 385 | arib.jis_array.tofile(f) 386 | f.close() 387 | -------------------------------------------------------------------------------- /ariblib/caption.py: -------------------------------------------------------------------------------- 1 | """字幕処理関係""" 2 | 3 | from ariblib.aribgaiji import GAIJI_MAP 4 | from ariblib.drcs import DRCSImage, mapping 5 | from ariblib.packet import SynchronizedPacketizedElementaryStream 6 | from ariblib.sections import TimeOffsetSection 7 | 8 | 9 | def captions(ts, color=False): 10 | """トランスポートストリームから字幕オブジェクトを返すジェネレータ""" 11 | 12 | SynchronizedPacketizedElementaryStream._pids = [ts.get_caption_pid()] 13 | if color: 14 | CProfileString = ColoredCProfileString 15 | 16 | base_pcr = next(ts.pcrs()) 17 | base_time = next(ts.sections(TimeOffsetSection)).JST_time 18 | 19 | for spes in ts.sections(SynchronizedPacketizedElementaryStream): 20 | caption_date = base_time + (spes.pts - base_pcr) 21 | for data in spes.data_units: 22 | if data.data_unit_parameter == 0x20: 23 | yield Caption(caption_date, 24 | CProfileString(data.data_unit_data)) 25 | elif data.data_unit_parameter == 0x30: 26 | for code in data.codes: 27 | drcs_code = code.character_code & 0xFF 28 | for font in code.fonts: 29 | image = DRCSImage(font.width, font.height) 30 | image.point(font.patterns) 31 | CProfileString.drcs[drcs_code] = image.hash 32 | image.save() 33 | 34 | 35 | class Caption(object): 36 | def __init__(self, datetime, body): 37 | self.datetime = datetime 38 | self.body = body 39 | 40 | 41 | class CProfileString(object): 42 | """CProfile文字列""" 43 | mapping = { 44 | 0: ' ', 45 | 7: '\a', 46 | 12: '\n', 47 | 13: '\n', 48 | 32: ' ', 49 | } 50 | 51 | drcs = {} 52 | strings = {} 53 | 54 | def __init__(self, data): 55 | self.data = data 56 | 57 | def __iter__(self): 58 | return self 59 | 60 | def __next__(self): 61 | return next(self.character()) 62 | 63 | def character(self): 64 | """一文字ずつUnicode型として返すジェネレータ""" 65 | while self.data: 66 | char1 = self.data.pop(0) 67 | if 0xa0 < char1 < 0xff: 68 | try: 69 | char2 = self.data.pop(0) 70 | yield bytes((char1, char2)).decode('euc-jp') 71 | except UnicodeDecodeError: 72 | gaiji = ((char1 & 0x7f) << 8) | (char2 & 0x7f) 73 | if gaiji == 0x7c21: 74 | # 次の字幕パケットへセリフが続いていることを示す矢印 75 | continue 76 | try: 77 | yield GAIJI_MAP[gaiji] 78 | except KeyError: 79 | yield '(0x{:x}{:x}, {}区{}点)'.format( 80 | char1, char2, char1 & 0x5f, char2 & 0x5f) 81 | except IndexError: 82 | try: 83 | yield GAIJI_MAP[char1] 84 | except KeyError: 85 | yield '(0x{:x})'.format(char1) 86 | elif 0x20 < char1 < 0x2f: 87 | if char1 in self.drcs: 88 | if self.drcs[char1] in mapping: 89 | yield mapping[self.drcs[char1]] 90 | else: 91 | yield '{{{{drcs:0x{:02X}:{}}}}}'.format( 92 | char1, self.drcs[char1]) 93 | elif char1 in self.mapping: 94 | yield self.mapping[char1] 95 | 96 | def __str__(self): 97 | str = ''.join(self).strip() 98 | self.__str__ = lambda self: str 99 | return str 100 | 101 | 102 | class ColoredCProfileString(CProfileString): 103 | 104 | """色付き CProfile 文字列""" 105 | 106 | mapping = { 107 | 0: ' ', 108 | 7: '\a', 109 | 12: '\n', 110 | 13: '\n', 111 | 32: ' ', 112 | 0x80: '\033[30m', 113 | 0x81: '\033[31m', 114 | 0x82: '\033[32m', 115 | 0x83: '\033[33m', 116 | 0x84: '\033[34m', 117 | 0x85: '\033[35m', 118 | 0x86: '\033[36m', 119 | 0x87: '\033[37m', 120 | } 121 | 122 | def __str__(self): 123 | str = ''.join(self).strip() + '\033[0m' 124 | self.__str__ = lambda self: str 125 | return str 126 | 127 | 128 | class WebVTTCProfileString(ColoredCProfileString): 129 | 130 | """WebVTT用 CProfile 文字列""" 131 | 132 | mapping = { 133 | 0: ' ', 134 | 7: '\a', 135 | 12: '\n', 136 | 13: '\n', 137 | 32: ' ', 138 | 0x80: '', 139 | 0x81: '', 140 | 0x82: '', 141 | 0x83: '', 142 | 0x84: '', 143 | 0x85: '', 144 | 0x86: '', 145 | 0x87: '', 146 | } 147 | 148 | def __str__(self): 149 | string = ''.join(self).strip() 150 | if string: 151 | string += '' 152 | self.__str__ = lambda self: string 153 | return string 154 | -------------------------------------------------------------------------------- /ariblib/command/__init__.py: -------------------------------------------------------------------------------- 1 | from argparse import ArgumentParser 2 | import importlib 3 | import os 4 | 5 | 6 | def main(): 7 | parser = ArgumentParser() 8 | add_parsers(parser) 9 | 10 | args = parser.parse_args() 11 | args.command(args) 12 | 13 | 14 | def add_parsers(parser): 15 | base_dir = os.path.dirname(__file__) 16 | sub_parsers = parser.add_subparsers() 17 | for name in os.listdir(base_dir): 18 | root, ext = os.path.splitext(name) 19 | if root == '__init__' or ext != '.py': 20 | continue 21 | module = importlib.import_module('.'.join(( 22 | 'ariblib', 'command', root))) 23 | try: 24 | getattr(module, 'add_parser')(sub_parsers) 25 | except AttributeError: 26 | pass 27 | -------------------------------------------------------------------------------- /ariblib/command/split.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import struct 3 | 4 | from ariblib import packet, tsopen 5 | from ariblib.sections import ProgramAssociationSection, ProgramMapSection 6 | 7 | 8 | def bits(data): 9 | masks = range(7, -1, -1) 10 | return ((x >> mask) & 0x01 for x, mask in itertools.product(data, masks)) 11 | 12 | 13 | def crc32(data): 14 | """CRC32を計算する""" 15 | 16 | crc = 0xFFFFFFFF 17 | for bit in bits(data): 18 | c = 1 if crc & 0x80000000 else 0 19 | crc <<= 1 20 | if c ^ bit: 21 | crc ^= 0x04c11db7 22 | crc &= 0xFFFFFFFF 23 | return crc 24 | 25 | 26 | def replace_pat(pat): 27 | new_pat = bytearray(pat[:16]) 28 | new_pat[2] = 0x11 29 | crc = crc32(new_pat) 30 | new_pat.extend(struct.pack('>L', crc)) 31 | new_pat.extend([0xFF] * 163) 32 | return new_pat 33 | 34 | 35 | def split(args): 36 | """必要なストリームのみ残す""" 37 | 38 | remained_pids = set() 39 | with tsopen(args.inpath) as ts: 40 | pat = next(ts.sections(ProgramAssociationSection)) 41 | # 置き換え後の新しいPAT 42 | new_pat = replace_pat(pat._packet) 43 | remained_pmt_pid = next(pat.pmt_pids) 44 | remained_pids.add(remained_pmt_pid) 45 | ProgramMapSection._pids = [remained_pmt_pid] 46 | pmt = next(ts.sections(ProgramMapSection)) 47 | # PCRと最初のストリームのPIDを残す 48 | remained_pids.add(pmt.PCR_PID) 49 | remained_pids.update(pmt_map.elementary_PID for pmt_map in pmt.maps 50 | if pmt_map.stream_type != 0x0d) 51 | 52 | pat_pid = ProgramAssociationSection._pids[0] 53 | with tsopen(args.inpath) as ts, open(args.outpath, 'wb') as out: 54 | for p in ts: 55 | pid = packet.pid(p) 56 | if pid == pat_pid: 57 | out.write(p[:5]) 58 | out.write(new_pat) 59 | elif pid in remained_pids: 60 | out.write(p) 61 | 62 | 63 | def add_parser(parsers): 64 | parser = parsers.add_parser('split') 65 | parser.set_defaults(command=split) 66 | parser.add_argument('inpath', help='input file path') 67 | parser.add_argument('outpath', help='output file path') 68 | -------------------------------------------------------------------------------- /ariblib/command/vtt.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | from hashlib import md5 3 | import os.path 4 | import sys 5 | 6 | from ariblib import tsopen 7 | from ariblib.caption import WebVTTCProfileString 8 | from ariblib.packet import SynchronizedPacketizedElementaryStream 9 | from ariblib.sections import TimeOffsetSection 10 | from ariblib.mnemonics import hexdump 11 | 12 | 13 | def vtt(args): 14 | if args.outpath == '-': 15 | outpath = sys.stdout.fileno() 16 | else: 17 | outpath = args.outpath 18 | with tsopen(args.inpath) as ts, open(outpath, 'w') as out: 19 | SynchronizedPacketizedElementaryStream._pids = [ts.get_caption_pid()] 20 | 21 | base_pcr = next(ts.pcrs()) 22 | base_time = next(ts.sections(TimeOffsetSection)).JST_time 23 | 24 | out.write('WEBVTT\n\n') 25 | number = 1 26 | base_date = datetime(2000, 1, 1) 27 | #fixme: DRCS処理を隠蔽して字幕文字オブジェクトだけ返すラッパークラスが必要 28 | # for caption in ts.captions(drcs=True, color=True): 29 | # print(caption.datetime, caption.body) みたいな 30 | prev_caption_date = None 31 | prev_caption = '' 32 | for spes in ts.sections(SynchronizedPacketizedElementaryStream): 33 | caption_date = base_date + (spes.pts - base_pcr) 34 | for data in spes.data_units: 35 | if data.data_unit_parameter == 0x20: 36 | caption = WebVTTCProfileString(data.data_unit_data) 37 | if prev_caption_date: 38 | out.write('{}\n{}.{:03d} --> {}.{:03d}\n{}\n\n'.format( 39 | number, prev_caption_date.strftime('%H:%M:%S'), 40 | prev_caption_date.microsecond // 1000, 41 | caption_date.strftime('%H:%M:%S'), 42 | caption_date.microsecond // 1000, 43 | prev_caption)) 44 | number += 1 45 | prev_caption_date = caption_date 46 | prev_caption = caption 47 | if prev_caption: 48 | out.write('{}\n{}.{:03d} --> {}.{:03d}\n{}\n\n'.format( 49 | number, prev_caption_date.strftime('%H:%M:%S'), 50 | prev_caption_date.microsecond // 1000, 51 | caption_date.strftime('%H:%M:%S'), 52 | caption_date.microsecond // 1000, 53 | prev_caption)) 54 | 55 | 56 | def add_parser(parsers): 57 | parser = parsers.add_parser('vtt') 58 | parser.set_defaults(command=vtt) 59 | parser.add_argument('inpath', help='input file path') 60 | parser.add_argument('outpath', help='output file path') 61 | -------------------------------------------------------------------------------- /ariblib/constants.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | """定数定義""" 4 | 5 | # ARIB-STD-B10-2-H コンテント記述子におけるジャンル指定 6 | CONTENT_TYPE = { 7 | 0x0: ('ニュース/報道', { 8 | 0x0: '定時・総合', 9 | 0x1: '天気', 10 | 0x2: '特集・ドキュメント', 11 | 0x3: '政治・国会', 12 | 0x4: '経済・市況', 13 | 0x5: '海外・国際', 14 | 0x6: '解説', 15 | 0x7: '討論・会談', 16 | 0x8: '報道特番', 17 | 0x9: 'ローカル・地域', 18 | 0xA: '交通', 19 | 0xF: 'その他', 20 | }), 21 | 0x1: ('スポーツ', { 22 | 0x0: 'スポーツニュース', 23 | 0x1: '野球', 24 | 0x2: 'サッカー', 25 | 0x3: 'ゴルフ', 26 | 0x4: 'その他の球技', 27 | 0x5: '相撲・格闘技', 28 | 0x6: 'オリンピック・国際大会', 29 | 0x7: 'マラソン・陸上・水泳', 30 | 0x8: 'モータースポーツ', 31 | 0x9: 'マリン・ウィンタースポーツ', 32 | 0xA: '競馬・公営競技', 33 | 0xF: 'その他', 34 | }), 35 | 0x2: ('情報/ワイドショー', { 36 | 0x0: '芸能・ワイドショー', 37 | 0x1: 'ファッション', 38 | 0x2: '暮らし・住まい', 39 | 0x3: '健康・医療', 40 | 0x4: 'ショッピング・通販', 41 | 0x5: 'グルメ・料理', 42 | 0x6: 'イベント', 43 | 0x7: '番組紹介・お知らせ', 44 | 0xF: 'その他', 45 | }), 46 | 0x3: ('ドラマ', { 47 | 0x0: '国内ドラマ', 48 | 0x1: '海外ドラマ', 49 | 0x2: '時代劇', 50 | 0xF: 'その他', 51 | }), 52 | 0x4: ('音楽', { 53 | 0x0: '国内ロック・ポップス', 54 | 0x1: '海外ロック・ポップス', 55 | 0x2: 'クラシック・オペラ', 56 | 0x3: 'ジャズ・フュージョン', 57 | 0x4: '歌謡曲・演歌', 58 | 0x5: 'ライブ・コンサート', 59 | 0x6: 'ランキング・リクエスト', 60 | 0x7: 'カラオケ・のど自慢', 61 | 0x8: '民謡・邦楽', 62 | 0x9: '童謡・キッズ', 63 | 0xA: '民族音楽・ワールドミュージック', 64 | 0xF: 'その他', 65 | }), 66 | 0x5: ('バラエティ', { 67 | 0x0: 'クイズ', 68 | 0x1: 'ゲーム', 69 | 0x2: 'トークバラエティ', 70 | 0x3: 'お笑い・コメディ', 71 | 0x4: '音楽バラエティ', 72 | 0x5: '旅バラエティ', 73 | 0x6: '料理バラエティ', 74 | 0xF: 'その他', 75 | }), 76 | 0x6: ('映画', { 77 | 0x0: '洋画', 78 | 0x1: '邦画', 79 | 0x2: 'アニメ', 80 | 0xF: 'その他', 81 | }), 82 | 0x7: ('アニメ/特撮', { 83 | 0x0: '国内アニメ', 84 | 0x1: '海外アニメ', 85 | 0x2: '特撮', 86 | 0xF: 'その他', 87 | }), 88 | 0x8: ('ドキュメンタリー/教養', { 89 | 0x0: '社会・時事', 90 | 0x1: '歴史・紀行', 91 | 0x2: '自然・動物・環境', 92 | 0x3: '宇宙・科学・医学', 93 | 0x4: 'カルチャー・伝統文化', 94 | 0x5: '文学・文芸', 95 | 0x6: 'スポーツ', 96 | 0x7: 'ドキュメンタリー全般', 97 | 0x8: 'インタビュー・討論', 98 | 0xF: 'その他', 99 | }), 100 | 0x9: ('劇場/公演', { 101 | 0x0: '現代劇・新劇', 102 | 0x1: 'ミュージカル', 103 | 0x2: 'ダンス・バレエ', 104 | 0x3: '落語・演芸', 105 | 0x4: '歌舞伎・古典', 106 | 0xF: 'その他', 107 | }), 108 | 0xA: ('趣味/教育', { 109 | 0x0: '旅・釣り・アウトドア', 110 | 0x1: '園芸・ペット・手芸', 111 | 0x2: '音楽・美術・工芸', 112 | 0x3: '囲碁・将棋', 113 | 0x4: '麻雀・パチンコ', 114 | 0x5: '車・オートバイ', 115 | 0x6: 'コンピュータ・TVゲーム', 116 | 0x7: '会話・語学', 117 | 0x8: '幼児・小学生', 118 | 0x9: '中学生・高校生', 119 | 0xA: '大学生・受験', 120 | 0xB: '生涯教育・資格', 121 | 0xC: '教育問題', 122 | 0xF: 'その他', 123 | }), 124 | 0xB: ('福祉', { 125 | 0x0: '高齢者', 126 | 0x1: '障害者', 127 | 0x2: '社会福祉', 128 | 0x3: 'ボランティア', 129 | 0x4: '手話', 130 | 0x5: '文字(字幕)', 131 | 0x6: '音声解説', 132 | 0xF: 'その他', 133 | }), 134 | 0xE: ('拡張', { 135 | 0x0: 'BS/地上デジタル放送用番組付属情報', 136 | 0x1: '広帯域CS デジタル放送用拡張', 137 | 0x2: '衛星デジタル音声放送用拡張', 138 | 0x3: 'サーバー型番組付属情報', 139 | 0x4: 'IP 放送用番組付属情報', 140 | }), 141 | 0xF: ('その他', { 142 | 0xF: 'その他', 143 | }), 144 | } 145 | 146 | # ARIB-TR-B24-4.B 表B-1 地上デジタルテレビジョン放送用番組付属情報 147 | # ARIB-TR-B25-4.B 表B-1 BSデジタル放送用番組付属情報 148 | USER_TYPE = { 149 | 0x00: '中止の可能性あり', 150 | 0x01: '延長の可能性あり', 151 | 0x02: '中断の可能性あり', 152 | 0x03: '同一シリーズの別話数放送の可能性あり', 153 | 0x04: '編成未定枠', 154 | 0x05: '繰り上げの可能性あり', 155 | 0x10: '中断ニュースあり', 156 | 0x11: '当該イベントに関連する臨時サービスあり', 157 | 0x20: '当該イベント中に3D映像あり', 158 | } 159 | 160 | # ARIB-STD-B10-2-6.2.3 表6-5 コンポーネント内容とコンポーネント種別 161 | COMPONENT_TYPE = { 162 | 0x01: { 163 | 0x00: '将来使用のためリザーブ', 164 | 0x01: '映像480i(525i)、アスペクト比4:3', 165 | 0x02: '映像480i(525i)、アスペクト比16:9 パンベクトルあり', 166 | 0x03: '映像480i(525i)、アスペクト比16:9 パンベクトルなし', 167 | 0x04: '映像480i(525i)、アスペクト比16:9', 168 | 0x91: '映像2160p、アスペクト比4:3', 169 | 0x92: '映像2160p、アスペクト比16:9 パンベクトルあり', 170 | 0x93: '映像2160p、アスペクト比16:9 パンベクトルなし', 171 | 0x94: '映像2160p、アスペクト比16:9', 172 | 0xA1: '映像480p(525p)、アスペクト比4:3', 173 | 0xA2: '映像480p(525p)、アスペクト比16:9 パンベクトルあり', 174 | 0xA3: '映像480p(525p)、アスペクト比16:9 パンベクトルなし', 175 | 0xA4: '映像480p(525p)、アスペクト比16:9', 176 | 0xB1: '映像1080i(1125i)、アスペクト比4:3', 177 | 0xB2: '映像1080i(1125i)、アスペクト比16:9 パンベクトルあり', 178 | 0xB3: '映像1080i(1125i)、アスペクト比16:9 パンベクトルなし', 179 | 0xB4: '映像1080i(1125i)、アスペクト比16:9', 180 | 0xC1: '映像720p(750p)、アスペクト比4:3', 181 | 0xC2: '映像720p(750p)、アスペクト比16:9 パンベクトルあり', 182 | 0xC3: '映像720p(750p)、アスペクト比16:9 パンベクトルなし', 183 | 0xC4: '映像720p(750p)、アスペクト比16:9', 184 | 0xD1: '映像240p アスペクト比4:3', 185 | 0xD2: '映像240p アスペクト比16:9 パンベクトルあり', 186 | 0xD3: '映像240p アスペクト比16:9 パンベクトルなし', 187 | 0xD4: '映像240p アスペクト比16:9', 188 | 0xE1: '映像1080p(1125p)、アスペクト比4:3', 189 | 0xE2: '映像1080p(1125p)、アスペクト比16:9 パンベクトルあり', 190 | 0xE3: '映像1080p(1125p)、アスペクト比16:9 パンベクトルなし', 191 | 0xE4: '映像1080p(1125p)、アスペクト比16:9', 192 | 0xF1: '映像180p アスペクト比4:3', 193 | 0xF2: '映像180p アスペクト比16:9 パンベクトルあり', 194 | 0xF3: '映像180p アスペクト比16:9 パンベクトルなし', 195 | 0xF4: '映像180p アスペクト比16:9', 196 | }, 197 | 0x02: { 198 | 0x00: '将来使用のためリザーブ', 199 | 0x01: '1/0モード(シングルモノ)', 200 | 0x02: '1/0+1/0モード(デュアルモノ)', 201 | 0x03: '2/0モード(ステレオ)', 202 | 0x04: '2/1モード', 203 | 0x05: '3/0モード', 204 | 0x06: '2/2モード', 205 | 0x07: '3/1モード', 206 | 0x08: '3/2モード', 207 | 0x09: '3/2+LFEモード(3/2.1モード)', 208 | 0x0A: '3/3.1モード', 209 | 0x0B: '2/0/0-2/0/2-0.1モード', 210 | 0x0C: '5/2.1モード', 211 | 0x0D: '3/2/2.1モード', 212 | 0x0E: '2/0/0-3/0/2-0.1モード', 213 | 0x0F: '0/2/0-3/0/2-0.2モード', 214 | 0x10: '2/0/0-3/2/3-0.2モード', 215 | 0x11: '3/3/3-5/2/3-3/0/0.2モード', 216 | 0x40: '視覚障害者用音声解説', 217 | 0x41: '聴覚障害者用音声', 218 | }, 219 | 0x05: { 220 | 0x01: 'H.264|MPEG-4 AVC、 映像480i(525i)、アスペクト比4:3', 221 | 0x02: 'H.264|MPEG-4 AVC、 映像480i(525i)、アスペクト比16:9 パンベクトルあり', 222 | 0x03: 'H.264|MPEG-4 AVC、 映像480i(525i)、アスペクト比16:9 パンベクトルなし ', 223 | 0x04: 'H.264|MPEG-4 AVC、 映像480i(525i)、アスペクト比 > 16:9', 224 | 0x91: 'H.264|MPEG-4 AVC、 映像2160p、アスペクト比4:3', 225 | 0x92: 'H.264|MPEG-4 AVC、 映像2160p、アスペクト比16:9 パンベクトルあり', 226 | 0x93: 'H.264|MPEG-4 AVC、 映像2160p、アスペクト比16:9 パンベクトルなし', 227 | 0x94: 'H.264|MPEG-4 AVC、 映像2160p、アスペクト比 > 16:9', 228 | 0xA1: 'H.264|MPEG-4 AVC、 映像480p(525p)、アスペクト比4:3', 229 | 0xA2: 'H.264|MPEG-4 AVC、 映像480p(525p)、アスペクト比16:9 パンベクトルあり', 230 | 0xA3: 'H.264|MPEG-4 AVC、 映像480p(525p)、アスペクト比16:9 パンベクトルなし', 231 | 0xA4: 'H.264|MPEG-4 AVC、 映像480p(525p)、アスペクト比 > 16:9', 232 | 0xB1: 'H.264|MPEG-4 AVC、 映像1080i(1125i)、アスペクト比4:3', 233 | 0xB2: 'H.264|MPEG-4 AVC、 映像1080i(1125i)、アスペクト比16:9 パンベクトルあり', 234 | 0xB3: 'H.264|MPEG-4 AVC、 映像1080i(1125i)、アスペクト比16:9 パンベクトルなし', 235 | 0xB4: 'H.264|MPEG-4 AVC、 映像1080i(1125i)、アスペクト比 > 16:9', 236 | 0xC1: 'H.264|MPEG-4 AVC、 映像720p(750p)、アスペクト比4:3', 237 | 0xC2: 'H.264|MPEG-4 AVC、 映像720p(750p)、アスペクト比16:9 パンベクトルあり', 238 | 0xC3: 'H.264|MPEG-4 AVC、 映像720p(750p)、アスペクト比16:9 パンベクトルなし', 239 | 0xC4: 'H.264|MPEG-4 AVC、 映像720p(750p)、アスペクト比 > 16:9', 240 | 0xD1: 'H.264|MPEG-4 AVC、 映像240p アスペクト比4:3', 241 | 0xD2: 'H.264|MPEG-4 AVC、 映像240p アスペクト比16:9 パンベクトルあり', 242 | 0xD3: 'H.264|MPEG-4 AVC、 映像240p アスペクト比16:9 パンベクトルなし', 243 | 0xD4: 'H.264|MPEG-4 AVC、 映像240p アスペクト比 > 16:9', 244 | 0xE1: 'H.264|MPEG-4 AVC、 映像1080p(1125p)、アスペクト比4:3', 245 | 0xE2: 'H.264|MPEG-4 AVC、 映像1080p(1125p)、アスペクト比16:9 パンベクトルあり', 246 | 0xE3: 'H.264|MPEG-4 AVC、 映像1080p(1125p)、アスペクト比16:9 パンベクトルなし', 247 | 0xE4: 'H.264|MPEG-4 AVC、 映像1080p(1125p)、アスペクト比 > 16:9', 248 | 0xF1: 'H.264|MPEG-4 AVC、 映像180p アスペクト比4:3', 249 | 0xF2: 'H.264|MPEG-4 AVC、 映像180p アスペクト比16:9 パンベクトルあり', 250 | 0xF3: 'H.264|MPEG-4 AVC、 映像180p アスペクト比16:9 パンベクトルなし', 251 | 0xF4: 'H.264|MPEG-4 AVC、 映像180p アスペクト比 > 16:9', 252 | }, 253 | } 254 | 255 | # ARIB-STD-B10-2-6.2.26 表6-44 音質表示 256 | QUOLITY = { 257 | 0b00: '将来使用のためリザーブ', 258 | 0b01: 'モード1', 259 | 0b10: 'モード2', 260 | 0b11: 'モード3', 261 | } 262 | 263 | # ARIB-STD-B10-2-6.2.26 表6-45 サンプリング周波数 264 | SAMPLING_RATE = { 265 | 0b000: '将来使用のためリザーブ', 266 | 0b001: '16kHz', 267 | 0b010: '22.05kHz', 268 | 0b011: '24kHz', 269 | 0b100: 'リザーブ', 270 | 0b101: '32kHz', 271 | 0b110: '44.1kHz', 272 | 0b111: '48kHz' 273 | } 274 | 275 | # ARIB-STD-B10-2-6.2.13 表6-25 サービス形式種別 276 | SERVICE_TYPE = { 277 | 0x00: '未定義', 278 | 0x01: 'デジタルTVサービス', 279 | 0x02: 'デジタル音声サービス', 280 | 0xA1: '臨時映像サービス', 281 | 0xA2: '臨時音声サービス', 282 | 0xA3: '臨時データサービス', 283 | 0xA4: 'エンジニアリングサービス', 284 | 0xA5: 'プロモーション映像サービス', 285 | 0xA6: 'プロモーション音声サービス', 286 | 0xA7: 'プロモーションデータサービス', 287 | 0xA8: '事前蓄積用データサービス', 288 | 0xA9: '蓄積専用データサービス', 289 | 0xAA: 'ブックマーク一覧データサービス', 290 | 0xAB: 'サーバー型サイマルサービス', 291 | 0xAC: '独立ファイルサービス', 292 | 0xC0: 'データサービス', 293 | 0xC1: 'TLVを用いた蓄積型サービス', 294 | 0xC2: 'マルチメディアサービス', 295 | } 296 | 297 | # ARIB-STD-B1aS0-2-6.2.8 表6-14 リンク種別のコード 298 | LINKAGE_TYPE = { 299 | 0x01: '情報サービス', 300 | 0x02: '電子番組ガイド(EPG)サービス', 301 | 0x03: 'CA代替サービス', 302 | 0x04: '全てのネットワーク/ブーケSIを含むトランスポートストリーム', 303 | 0x05: '代替サービス', 304 | 0x06: 'データ放送サービス', 305 | 0x0B: 'INT', 306 | 0xFE: '再送信用', 307 | } 308 | 309 | # ARIB-STD-B10-2-6.2.23 表6-39 デジタルコピー制御情報 310 | DIGITAL_RECORDING_CONTROL_TYPE = { 311 | 0b00: '制約条件なしにコピー可', 312 | 0b01: 'コピー禁止', 313 | 0b10: '1世代のみコピー可', 314 | 0b11: 'コピー禁止', 315 | } 316 | 317 | # ARIB-STD-B10-2-6.2.34 表6-68 グループ種別 318 | EVENT_GROUP_TYPE = { 319 | 0x00: '未定義', 320 | 0x01: 'イベント共有', 321 | 0x02: 'イベントリレー', 322 | 0x03: 'イベント移動', 323 | 0x04: '他ネットワークへのイベントリレー', 324 | 0x05: '他ネットワークからのイベント移動', 325 | } 326 | 327 | # ISO 13818-1 2.4.4.10 Table 2-29 Stream type assignments 328 | STREAM_TYPE = { 329 | 0x00: 'ITU-T | ISO/IEC Reserved', 330 | 0x01: 'ISO/IEC 11172 Video', 331 | 0x02: ('ITU-T Rec. H.262 | ISO/IEC 13818-2 Video or ISO/IEC 11172-2 ' 332 | 'constrained parameter video stream'), 333 | 0x03: 'ISO/IEC 11172 Audio', 334 | 0x04: 'ISO/IEC 13818-3 Audio', 335 | 0x05: 'ITU-T Rec. H.222.0 | ISO/IEC 13818-1 private_sections', 336 | 0x06: 'ITU-T Rec. H.222.0 | ISO/IEC 13818-1 PES packets containing private data', 337 | 0x07: 'ISO/IEC 13522 MHEG', 338 | 0x08: 'ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A DSM-CC', 339 | 0x09: 'ITU-T Rec. H.222.1', 340 | 0x0A: 'ISO/IEC 13818-6 type A', 341 | 0x0B: 'ISO/IEC 13818-6 type B', 342 | 0x0C: 'ISO/IEC 13818-6 type C', 343 | 0x0D: 'ISO/IEC 13818-6 type D', 344 | 0x0E: 'ITU-T Rec. H.222.0 | ISO/IEC 13818-1 auxiliary', 345 | 0x0F: 'ISO/IEC 13818-7 Audio with ADTS transport syntax', 346 | 0x10: 'ISO/IEC 14496-2 Visual', 347 | 0x11: ('ISO/IEC 14496-3 Audio with the LATM transport syntax as defined ' 348 | 'in ISO/IEC 14496-3 / AMD 1'), 349 | 0x12: ('ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried ' 350 | 'in PES packets'), 351 | 0x13: ('ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried ' 352 | 'in ISO/IEC14496_sections.'), 353 | 0x14: 'ISO/IEC 13818-6 Synchronized Download Protocol', 354 | } 355 | 356 | # ARIB-STD-B10-2-D 表D-2 地域符号 357 | AREA_CODE = { 358 | 0b101001011010: '静岡県', 359 | 0b100101100110: '愛知県', 360 | 0b001011011100: '三重県', 361 | 0b110011100100: '滋賀県', 362 | 0b010110011010: '京都府', 363 | 0b110010110010: '大阪府', 364 | 0b011001110100: '兵庫県', 365 | 0b101010010011: '奈良県', 366 | 0b001110010110: '和歌山県', 367 | 0b110100100011: '鳥取県', 368 | 0b001100011011: '島根県', 369 | 0b001010110101: '岡山県', 370 | 0b101100110001: '広島県', 371 | 0b101110011000: '山口県', 372 | 0b111001100010: '徳島県', 373 | 0b100110110100: '香川県', 374 | 0b000110011101: '愛媛県', 375 | 0b001011100011: '高知県', 376 | 0b011000101101: '福岡県', 377 | 0b100101011001: '佐賀県', 378 | 0b101000101011: '長崎県', 379 | 0b100010100111: '熊本県', 380 | 0b110010001101: '大分県', 381 | 0b110100011100: '宮崎県', 382 | 0b110101000101: '鹿児島県', 383 | 0b001101110010: '沖縄県', 384 | 0b001101001101: '地域共通', 385 | 0b010110100101: '関東広域圏', 386 | 0b011100101010: '中京広域圏', 387 | 0b100011010101: '近畿広域圏', 388 | 0b011010011001: '鳥取・島根圏', 389 | 0b010101010011: '岡山・香川圏', 390 | 0b000101101011: '北海道', 391 | 0b010001100111: '青森県', 392 | 0b010111010100: '岩手県', 393 | 0b011101011000: '宮城県', 394 | 0b101011000110: '秋田県', 395 | 0b111001001100: '山形県', 396 | 0b000110101110: '福島県', 397 | 0b110001101001: '茨城県', 398 | 0b111000111000: '栃木県', 399 | 0b100110001011: '群馬県', 400 | 0b011001001011: '埼玉県', 401 | 0b000111000111: '千葉県', 402 | 0b101010101100: '東京都', 403 | 0b010101101100: '神奈川県', 404 | 0b010011001110: '新潟県', 405 | 0b010100111001: '富山県', 406 | 0b011010100110: '石川県', 407 | 0b100100101101: '福井県', 408 | 0b110101001010: '山梨県', 409 | 0b100111010010: '長野県', 410 | 0b101001100101: '岐阜県', 411 | } 412 | 413 | # ARIB-STD-B10-2-6.2.30 表6-60 ビデオエンコードフォーマット 414 | VIDEO_ENCODE_FORMAT = { 415 | 0b0000: '1080p', 416 | 0b0001: '1080i', 417 | 0b0010: '720p', 418 | 0b0011: '480p', 419 | 0b0100: '480i', 420 | 0b0101: '240o', 421 | 0b0110: '120p', 422 | 0b0111: '2160p', 423 | 0b1000: '180p', 424 | } 425 | -------------------------------------------------------------------------------- /ariblib/descriptors.py: -------------------------------------------------------------------------------- 1 | """記述子の実装""" 2 | 3 | from collections import defaultdict 4 | 5 | from ariblib.mnemonics import ( 6 | aribstr, 7 | bcd, 8 | bslbf, 9 | cache, 10 | case, 11 | char, 12 | loop, 13 | mjd, 14 | mnemonic, 15 | raw, 16 | times, 17 | uimsbf 18 | ) 19 | from ariblib.syntax import Syntax 20 | 21 | tags = {} 22 | 23 | 24 | def tag(tag_id): 25 | def wrapper(cls): 26 | cls._tag = tag_id 27 | tags[tag_id] = cls 28 | return cls 29 | return wrapper 30 | 31 | 32 | class descriptors(mnemonic): 33 | 34 | """記述子リスト""" 35 | 36 | @cache 37 | def __get__(self, instance, owner): 38 | length = self.real_length(instance) // 8 39 | start = self.start(instance) // 8 40 | end = start + length 41 | result = defaultdict(list) 42 | while start < end: 43 | descriptor_tag = instance._packet[start] 44 | descriptor_length = instance._packet[start + 1] + 2 45 | block_end = start + descriptor_length 46 | desc_class = Descriptor.get(descriptor_tag) 47 | inner = desc_class(instance._packet[start:block_end]) 48 | result[desc_class].append(inner) 49 | start = block_end 50 | return result 51 | 52 | 53 | class Descriptor(Syntax): 54 | 55 | """記述子の親クラス""" 56 | 57 | descriptor_tag = uimsbf(8) 58 | descriptor_length = uimsbf(8) 59 | descriptor = bslbf(descriptor_length) 60 | 61 | @staticmethod 62 | def get(tag): 63 | return tags.get(tag, Descriptor) 64 | 65 | 66 | @tag(0x09) 67 | class ConditionalAccessDescriptor(Descriptor): 68 | 69 | """限定受信方式記述子 (ARIB-TR-B14-4-3.2.2.1, ARIB-TR-B15-4-3.2.2.1)""" 70 | 71 | descriptor_tag = uimsbf(8) 72 | descriptor_length = uimsbf(8) 73 | CA_system_ID = uimsbf(16) 74 | reserved = bslbf(3) 75 | CA_PID = uimsbf(13) 76 | private_data_byte = bslbf(lambda self: self.descriptor_length - 4) 77 | 78 | 79 | @tag(0x0D) 80 | class CopyrightDescriptor(Descriptor): 81 | 82 | """著作権記述子 (ISO 13818-1 2.6.24)""" 83 | 84 | descriptor_length = uimsbf(8) 85 | copyright_identifier = uimsbf(32) 86 | additional_copyright_info = bslbf(lambda self: self.descriptor_length - 4) 87 | 88 | 89 | @tag(0x40) 90 | class NetworkNameDescriptor(Descriptor): 91 | 92 | """ネットワーク名記述子(ARIB-STD-B10-2.6.2.11)""" 93 | 94 | descriptor_tag = uimsbf(8) 95 | descriptor_length = uimsbf(8) 96 | char = aribstr(descriptor_length) 97 | 98 | 99 | @tag(0x41) 100 | class ServiceListDescriptor(Descriptor): 101 | 102 | """サービスリスト記述子(ARIB-STD-B10-2-6.2.14)""" 103 | 104 | descriptor_tag = uimsbf(8) 105 | descriptor_length = uimsbf(8) 106 | 107 | @loop(descriptor_length) 108 | class services(Syntax): 109 | service_id = uimsbf(16) 110 | service_type = uimsbf(8) 111 | 112 | 113 | @tag(0x43) 114 | class SatelliteDeliverySystemDescriptor(Descriptor): 115 | 116 | """衛星分配システム記述子(ARIB-STD-B10-2-6.2.6)""" 117 | 118 | descriptor_tag = uimsbf(8) 119 | descriptor_length = uimsbf(8) 120 | frequency = bcd(32, 6) 121 | orbital_position = bcd(16, 1) 122 | west_east_flag = bslbf(1) 123 | polarisation = bslbf(2) 124 | modulation = bslbf(5) 125 | symbol_rate = bcd(28, 5) 126 | FEC_inner = bslbf(4) 127 | 128 | 129 | @tag(0x47) 130 | class BouquetNameDescriptor(Descriptor): 131 | 132 | """ブーケ名記述子(ARIB-STD-B10-2.6.2.1)""" 133 | 134 | descriptor_tag = uimsbf(8) 135 | descriptor_length = uimsbf(8) 136 | char = aribstr(descriptor_length) 137 | 138 | 139 | @tag(0x48) 140 | class ServiceDescriptor(Descriptor): 141 | 142 | """サービス記述子(ARIB-STD-B10-2-6.2.13)""" 143 | 144 | descriptor_tag = uimsbf(8) 145 | descriptor_length = uimsbf(8) 146 | service_type = uimsbf(8) 147 | service_provider_name_length = uimsbf(8) 148 | service_provider_name = aribstr(service_provider_name_length) 149 | service_name_length = uimsbf(8) 150 | service_name = aribstr(service_name_length) 151 | 152 | 153 | @tag(0x49) 154 | class CountryAvailabilityDescriptor(Descriptor): 155 | 156 | """国別受信可否記述子(ARIB-STD-B10-2.6.2.5)""" 157 | 158 | descriptor_tag = uimsbf(8) 159 | descriptor_length = uimsbf(8) 160 | country_availability_flag = bslbf(1) 161 | reserved_future_use = bslbf(7) 162 | 163 | @loop(lambda self: self.descriptor_length - 1) 164 | class countries(Syntax): 165 | country_code = char(24) 166 | 167 | 168 | @tag(0x4A) 169 | class LinkageDescriptor(Descriptor): 170 | 171 | """リンク記述子(ARIB-STD-B10-2.6.2.8)""" 172 | 173 | descriptor_tag = uimsbf(8) 174 | descriptor_length = uimsbf(8) 175 | transport_stream_id = uimsbf(16) 176 | original_network_id = uimsbf(16) 177 | service_id = uimsbf(16) 178 | linkage_type = uimsbf(8) 179 | 180 | @case(lambda self: self.linkage_type == 0x0B) 181 | class linkage_type_0x0B(Syntax): 182 | platform_id_data_length = uimsbf(8) 183 | 184 | @loop(platform_id_data_length) 185 | class platforms(Syntax): 186 | platform_id = uimsbf(24) 187 | platform_name_loop_length = uimsbf(8) 188 | 189 | @loop(platform_name_loop_length) 190 | class names(Syntax): 191 | ISO_639_language_code = char(24) 192 | platform_name_length = uimsbf(8) 193 | text_char = aribstr(platform_name_length) 194 | 195 | @case(lambda self: self.linkage_type == 0x03) 196 | class linkage_type_0x03(Syntax): 197 | message_id = uimsbf(8) 198 | message = aribstr(lambda self: self.descriptor_length - 8) 199 | 200 | @case(lambda self: self.linkage_type not in (0x03, 0x0B)) 201 | class default(Syntax): 202 | private_data_byte = bslbf(lambda self: self.descriptor_length - 7) 203 | 204 | 205 | @tag(0x4C) 206 | class TimeShiftedServiceDescriptor(Descriptor): 207 | 208 | """タイムシフトサービス記述子 (ARIB-STD-B10-2-6.2.19)""" 209 | 210 | descriptor_tag = uimsbf(8) 211 | descriptor_length = uimsbf(8) 212 | reference_service_id = uimsbf(16) 213 | 214 | 215 | @tag(0x4D) 216 | class ShortEventDescriptor(Descriptor): 217 | 218 | """短形式イベント記述子(ARIB-STD-B10-2-6.2.15)""" 219 | 220 | descriptor_tag = uimsbf(8) 221 | descriptor_length = uimsbf(8) 222 | ISO_639_language_code = char(24) 223 | event_name_length = uimsbf(8) 224 | event_name_char = aribstr(event_name_length) 225 | text_length = uimsbf(8) 226 | text_char = aribstr(text_length) 227 | 228 | 229 | @tag(0x4E) 230 | class ExtendedEventDescriptor(Descriptor): 231 | 232 | """拡張形式イベント記述子(ARIB-STD-B10-2-6.2.7)""" 233 | 234 | descriptor_tag = uimsbf(8) 235 | descriptor_length = uimsbf(8) 236 | descriptor_number = uimsbf(4) 237 | last_descriptor_number = uimsbf(4) 238 | ISO_639_language_code = char(24) 239 | length_of_items = uimsbf(8) 240 | 241 | @loop(lambda self: self.length_of_items) 242 | class items(Syntax): 243 | item_description_length = uimsbf(8) 244 | item_description_char = aribstr(item_description_length) 245 | item_length = uimsbf(8) 246 | # マルチバイトの途中でitemが別れていることがあるので、 247 | # ここでaribstrとしてパースすると文字化けすることがある 248 | item_char = raw(item_length) 249 | 250 | text_length = uimsbf(8) 251 | text_char = aribstr(text_length) 252 | 253 | 254 | @tag(0x4F) 255 | class TimeShiftedEventDescriptor(Descriptor): 256 | 257 | """タイムシフトイベント記述子 (ARIB-STD-B10-2-6.2.18)""" 258 | 259 | descriptor_tag = uimsbf(8) 260 | descriptor_length = uimsbf(8) 261 | reference_service_id = uimsbf(16) 262 | reference_event_id = uimsbf(16) 263 | 264 | 265 | @tag(0x50) 266 | class ComponentDescriptor(Descriptor): 267 | 268 | """コンポーネント記述子(ARIB-STD-B10-2-6.2.3)""" 269 | 270 | descriptor_tag = uimsbf(8) 271 | descriptor_length = uimsbf(8) 272 | reserved_future_use = bslbf(4) 273 | stream_content = uimsbf(4) 274 | component_type = uimsbf(8) 275 | component_tag = uimsbf(8) 276 | ISO_639_language_code = char(24) 277 | component_text = aribstr(lambda self: self.descriptor_length - 6) 278 | 279 | 280 | @tag(0x52) 281 | class StreamIdentifierDescriptor(Descriptor): 282 | 283 | """ストリーム識別記述子 (ARIB-STD-B10-2-6.2.16)""" 284 | 285 | descriptor_tag = uimsbf(8) 286 | descriptor_length = uimsbf(8) 287 | component_tag = uimsbf(8) 288 | 289 | 290 | @tag(0x53) 291 | class CAIdentifierDescriptor(Descriptor): 292 | 293 | """CA識別記述子 (ARIB-STD-B10-2-6.2.2)""" 294 | 295 | descriptor_tag = uimsbf(8) 296 | descriptor_length = uimsbf(8) 297 | 298 | @loop(descriptor_length) 299 | class CAs(Syntax): 300 | CA_system_id = uimsbf(16) 301 | 302 | 303 | @tag(0x54) 304 | class ContentDescriptor(Descriptor): 305 | 306 | """コンテント記述子(ARIB-STD-B10-2-6.2.4)""" 307 | 308 | descriptor_tag = uimsbf(8) 309 | descriptor_length = uimsbf(8) 310 | 311 | @loop(descriptor_length) 312 | class nibbles(Syntax): 313 | content_nibble_level_1 = uimsbf(4) 314 | content_nibble_level_2 = uimsbf(4) 315 | user_nibble = uimsbf(8) 316 | 317 | 318 | @tag(0xC0) 319 | class HierarchicalTransmissionDescriptor(Descriptor): 320 | 321 | """階層伝送記述子 (ARIB-STD-B10-2-6.2.22)""" 322 | 323 | descriptor_tag = uimsbf(8) 324 | descriptor_length = uimsbf(8) 325 | reserved_future_use_1 = bslbf(7) 326 | quality_level = bslbf(1) 327 | reserved_future_use_2 = bslbf(3) 328 | reference_PID = uimsbf(13) 329 | 330 | 331 | @tag(0xC1) 332 | class DigitalCopyControlDescriptor(Descriptor): 333 | 334 | """デジタルコピー制御記述子 (ARIB-STD-B10-2-6.2.23)""" 335 | 336 | descriptor_tag = uimsbf(8) 337 | descriptor_length = uimsbf(8) 338 | digital_recording_control_data = bslbf(2) 339 | maximum_bitrate_flag = bslbf(1) 340 | component_control_flag = bslbf(1) 341 | copy_control_type = bslbf(2) 342 | 343 | @case(lambda self: self.copy_control_type == 0b01) 344 | class with_APS(Syntax): 345 | APS_control_data = bslbf(2) 346 | 347 | @case(lambda self: self.copy_control_type != 0b01) 348 | class without_APS(Syntax): 349 | reserved_future_use = bslbf(2) 350 | 351 | @case(maximum_bitrate_flag) 352 | class with_maximum_bitrate(Syntax): 353 | maximum_bitrate = uimsbf(8) 354 | 355 | @case(component_control_flag) 356 | class with_component_control(Syntax): 357 | component_control_length = uimsbf(8) 358 | 359 | @loop(component_control_length) 360 | class components(Syntax): 361 | component_tag = uimsbf(8) 362 | digirtal_recording_contorl_data = bslbf(2) 363 | maximum_bitrate_flag = bslbf(1) 364 | reserved_future_use = bslbf(1) 365 | user_defined = bslbf(4) 366 | 367 | @case(maximum_bitrate_flag) 368 | class with_maximum_bitrate(Syntax): 369 | maximum_bitrate = uimsbf(8) 370 | 371 | 372 | @tag(0xC4) 373 | class AudioComponentDescriptor(Descriptor): 374 | 375 | """音声コンポーネント記述子(ARIB-STD-B10-2-6.2.26)""" 376 | 377 | descriptor_tag = uimsbf(8) 378 | descriptor_length = uimsbf(8) 379 | reserved_future_use = bslbf(4) 380 | stream_content = uimsbf(4) 381 | component_type = uimsbf(8) 382 | component_tag = uimsbf(8) 383 | stream_type = uimsbf(8) 384 | simulcast_group_tag = bslbf(8) 385 | ES_multi_lingual_flag = bslbf(1) 386 | main_component_flag = bslbf(1) 387 | quality_indicator = bslbf(2) 388 | sampling_rate = uimsbf(3) 389 | reserved_future_use_2 = bslbf(1) 390 | ISO_639_language_code = char(24) 391 | 392 | @case(ES_multi_lingual_flag) 393 | class with_ES_multi_lingual(Syntax): 394 | ISO_639_language_code_2 = char(24) 395 | 396 | audio_text = aribstr(lambda self: ( 397 | self.descriptor_length - 12 398 | if self.ES_multi_lingual_flag == 1 399 | else self.descriptor_length - 9 400 | )) 401 | 402 | 403 | @tag(0xC5) 404 | class HyperLinkDescriptor(Descriptor): 405 | 406 | """ハイパーリンク記述子(ARIB-STD-B10-2.6.2.29)""" 407 | 408 | descriptor_tag = uimsbf(8) 409 | descriptor_length = uimsbf(8) 410 | hyper_linkage_type = uimsbf(8) 411 | link_destination_type = uimsbf(8) 412 | selector_length = uimsbf(8) 413 | 414 | @case(lambda self: self.link_destination_type == 0x01) 415 | class type_01(Syntax): 416 | original_network_id = uimsbf(16) 417 | transport_stream_id = uimsbf(16) 418 | service_id = uimsbf(16) 419 | 420 | @case(lambda self: self.link_destination_type == 0x02) 421 | class type_02(Syntax): 422 | original_network_id = uimsbf(16) 423 | transport_stream_id = uimsbf(16) 424 | service_id = uimsbf(16) 425 | event_id = uimsbf(16) 426 | 427 | @case(lambda self: self.link_destination_type == 0x03) 428 | class type_03(Syntax): 429 | original_network_id = uimsbf(16) 430 | transport_stream_id = uimsbf(16) 431 | service_id = uimsbf(16) 432 | event_id = uimsbf(16) 433 | component_tag = uimsbf(8) 434 | module_id = uimsbf(16) 435 | 436 | @case(lambda self: self.link_destination_type == 0x04) 437 | class type_04(Syntax): 438 | original_network_id = uimsbf(16) 439 | transport_stream_id = uimsbf(16) 440 | service_id = uimsbf(16) 441 | content_id = uimsbf(32) 442 | 443 | @case(lambda self: self.link_destination_type == 0x05) 444 | class type_05(Syntax): 445 | original_network_id = uimsbf(16) 446 | transport_stream_id = uimsbf(16) 447 | service_id = uimsbf(16) 448 | content_id = uimsbf(32) 449 | component_tag = uimsbf(8) 450 | module_id = uimsbf(16) 451 | 452 | @case(lambda self: self.link_destination_type == 0x06) 453 | class type_06(Syntax): 454 | information_provide_id = uimsbf(16) 455 | event_relation_id = uimsbf(16) 456 | node_id = uimsbf(16) 457 | 458 | @case(lambda self: self.link_destination_type == 0x07) 459 | class type_07(Syntax): 460 | uri_char = char(lambda self: self.selector_length) 461 | 462 | 463 | @tag(0xC7) 464 | class DataContentDescriptor(Descriptor): 465 | 466 | """データコンテンツ記述子(ARIB-STD-B10-2-6.2.28) 467 | 468 | data_component_idが0x0008のものは、selector_byteに 469 | 字幕・文字スーパーの識別情報が入っている(ARIB-STD-B24-1-3-9.6.2) 470 | 471 | """ 472 | 473 | descriptor_tag = uimsbf(8) 474 | descriptor_length = uimsbf(8) 475 | data_component_id = uimsbf(16) 476 | entry_component = uimsbf(8) 477 | selector_length = uimsbf(8) 478 | 479 | # ARIB-STD-B24-1-3-9.6.2 480 | # これ以外のselector_byteの実装は、ifの入れ子が正しく処理できないと実装できない 481 | @case(lambda self: self.data_component_id == 0x08) 482 | class arib_caption_info(Syntax): 483 | num_languages = uimsbf(8) 484 | 485 | @times(num_languages) 486 | class languages(Syntax): 487 | language_tag = bslbf(3) 488 | reserved = bslbf(1) 489 | DMF = bslbf(4) 490 | ISO_639_language_code = char(24) 491 | 492 | @case(lambda self: self.data_component_id != 0x08) 493 | class other(Syntax): 494 | selector_byte = uimsbf(lambda self: self.selector_length) 495 | 496 | num_of_component_ref = uimsbf(8) 497 | 498 | @times(num_of_component_ref) 499 | class component_refs(Syntax): 500 | component_ref = uimsbf(8) 501 | 502 | ISO_639_language_code = char(24) 503 | text_length = uimsbf(8) 504 | data_text = aribstr(text_length) 505 | 506 | 507 | @tag(0xC8) 508 | class VideoDecodeControlDescriptor(Descriptor): 509 | 510 | """ビデオデコードコントロール記述子 (ARIB-STD-B10-2-6.2.30)""" 511 | 512 | descriptor_tag = uimsbf(8) 513 | descriptor_length = uimsbf(8) 514 | still_picture_flag = bslbf(1) 515 | sequence_end_code_flag = bslbf(1) 516 | video_encode_format = bslbf(4) 517 | reserved_future_use = bslbf(2) 518 | 519 | 520 | @tag(0xC9) 521 | class DownloadContentDescriptor(Descriptor): 522 | 523 | """ダウンロードコンテンツ記述子(ARIB-STD-B21-12.2.1.1)""" 524 | 525 | descriptor_tag = uimsbf(8) 526 | descriptor_length = uimsbf(8) 527 | reboot = bslbf(1) 528 | add_on = bslbf(1) 529 | compatibility_flag = bslbf(1) 530 | module_info_flag = bslbf(1) 531 | text_info_flag = bslbf(1) 532 | reserved_1 = bslbf(3) 533 | component_size = uimsbf(32) 534 | download_id = uimsbf(32) 535 | time_out_value_DII = uimsbf(32) 536 | leak_rate = uimsbf(22) 537 | reserved_2 = bslbf(2) 538 | component_tag = uimsbf(8) 539 | 540 | @case(compatibility_flag) 541 | class CompatibilityDescriptor(Syntax): 542 | """Compatibility Descriptor (ARIB-STD-B21-12.2.2.1)""" 543 | 544 | compatibility_descriptor_length = uimsbf(16) 545 | descriptor_count = uimsbf(16) 546 | 547 | @times(descriptor_count) 548 | class compatibility_descriptors(Syntax): 549 | descriptor_type = uimsbf(8) 550 | descriptor_length = uimsbf(8) 551 | specifier_type = uimsbf(8) 552 | specifier_data = bslbf(24) 553 | model = uimsbf(16) 554 | version = uimsbf(16) 555 | sub_descriptor_count = uimsbf(8) 556 | 557 | @times(lambda self: self.sub_descriptor_count) 558 | class sub_descriptors(Syntax): 559 | sub_descriptor_type = uimsbf(8) 560 | sub_descriptor_length = uimsbf(8) 561 | additional_information = uimsbf(sub_descriptor_length) 562 | 563 | @case(lambda self: self.module_info_flag) 564 | class ModuleInfo(Syntax): 565 | num_of_modules = uimsbf(16) 566 | 567 | @times(lambda self: self.num_of_modules) 568 | class modules(Syntax): 569 | module_id = uimsbf(16) 570 | module_size = uimsbf(32) 571 | module_info_length = uimsbf(8) 572 | module_info_byte = uimsbf(module_info_length) 573 | 574 | private_data_length = uimsbf(8) 575 | private_date_byte = uimsbf(private_data_length) 576 | 577 | @case(text_info_flag) 578 | class TextInfo(Syntax): 579 | ISO_639_language_code = char(24) 580 | text_length = uimsbf(8) 581 | text_char = aribstr(text_length) 582 | 583 | 584 | @tag(0xCB) 585 | class EncryptDescriptor(Descriptor): 586 | 587 | """Encrypt記述子(ARIB-STD-B25-3.4.4.7)""" 588 | 589 | descriptor_tag = uimsbf(8) 590 | descriptor_length = uimsbf(8) 591 | encrypt_id = uimsbf(8) 592 | 593 | 594 | @tag(0xCC) 595 | class CAServiceDescriptor(Descriptor): 596 | 597 | """CAサービス記述子(ARIB-STD-B25-4.7.3)""" 598 | 599 | descriptor_tag = uimsbf(8) 600 | descriptor_length = uimsbf(8) 601 | CA_system_id = uimsbf(16) 602 | ca_broadcaster_group_id = uimsbf(8) 603 | message_control = uimsbf(8) 604 | 605 | @loop(lambda self: self.descriptor_length - 4) 606 | class services(Syntax): 607 | service_id = uimsbf(16) 608 | 609 | 610 | @tag(0xCD) 611 | class TSInformationDescriptor(Descriptor): 612 | 613 | """TS情報記述子(ARIB-STD-B10-2.6.2.42)""" 614 | 615 | descriptor_tag = uimsbf(8) 616 | descriptor_length = uimsbf(8) 617 | remote_control_key_id = uimsbf(8) 618 | length_of_ts_name = uimsbf(6) 619 | transmission_type_count = uimsbf(2) 620 | ts_name_char = aribstr(length_of_ts_name) 621 | 622 | @times(transmission_type_count) 623 | class transmissions(Syntax): 624 | transmission_type_info = bslbf(8) 625 | num_of_service = uimsbf(8) 626 | 627 | @times(num_of_service) 628 | class services(Syntax): 629 | service_id = uimsbf(16) 630 | 631 | 632 | @tag(0xCE) 633 | class ExtendedBroadcasterDescriptor(Descriptor): 634 | 635 | """拡張ブロードキャスタ記述子(ARIB-STD-B10-2-6.2.43)""" 636 | 637 | descriptor_tag = uimsbf(8) 638 | descriptor_length = uimsbf(8) 639 | broadcaster_type = uimsbf(4) 640 | reserved_future_use = bslbf(4) 641 | 642 | @case(lambda self: self.broadcaster_type == 0x1) 643 | class type_1(Syntax): 644 | terrestrial_broadcaster_id = uimsbf(16) 645 | number_of_affiliation_id_loop = uimsbf(4) 646 | number_of_broadcaster_id_loop = uimsbf(4) 647 | 648 | @times(number_of_affiliation_id_loop) 649 | class affiliations(Syntax): 650 | affiliation_id = uimsbf(8) 651 | 652 | @times(number_of_broadcaster_id_loop) 653 | class broadcasters(Syntax): 654 | original_network_id = uimsbf(8) 655 | broadcaster_id = uimsbf(8) 656 | 657 | private_data_byte = bslbf(lambda self: ( 658 | self.descriptor_length - ( 659 | 4 + self.number_of_affiliation_id_loop + 660 | self.number_of_broadcaster_id_loop * 3 661 | ) 662 | )) 663 | 664 | @case(lambda self: self.broadcaster_type == 0x2) 665 | class type_2(Syntax): 666 | terrestrial_sound_broadcaster_id = uimsbf(16) 667 | number_of_sound_broadcast_affiliation_id_loop = uimsbf(4) 668 | number_of_broadcaster_id_loop = uimsbf(4) 669 | 670 | @times(number_of_sound_broadcast_affiliation_id_loop) 671 | class sound_broadcast_affiliations(Syntax): 672 | sound_broadcast_affiliations_id = uimsbf(8) 673 | 674 | @times(number_of_broadcaster_id_loop) 675 | class broadcasters(Syntax): 676 | original_network_id = uimsbf(8) 677 | broadcaster_id = uimsbf(8) 678 | 679 | private_data_byte = bslbf(lambda self: ( 680 | self.descriptor_length - ( 681 | 4 + self.number_of_sound_broadcast_affiliation_id_loop + 682 | self.number_of_broadcaster_id_loop * 3 683 | ) 684 | )) 685 | 686 | @case(lambda self: self.broadcaster_type not in (0x1, 0x2)) 687 | class type_other(Syntax): 688 | reserved_future_use = bslbf(lambda self: self.description_length - 1) 689 | 690 | 691 | @tag(0xCF) 692 | class LogoTransmissionDescriptor(Descriptor): 693 | 694 | """ロゴ伝送記述子(ARIB-STD-B10-2-6.2.44)""" 695 | 696 | descriptor_tag = uimsbf(8) 697 | descriptor_length = uimsbf(8) 698 | logo_transmission_type = uimsbf(8) 699 | 700 | @case(lambda self: self.logo_transmission_type == 0x01) 701 | class type_01(Syntax): 702 | reserved_future_use_1 = bslbf(7) 703 | logo_id = uimsbf(9) 704 | reserved_future_use_2 = bslbf(4) 705 | logo_version = uimsbf(12) 706 | download_data_id = uimsbf(16) 707 | 708 | @case(lambda self: self.logo_transmission_type == 0x02) 709 | class type_02(Syntax): 710 | reserved_future_use_1 = bslbf(7) 711 | logo_id = uimsbf(9) 712 | 713 | @case(lambda self: self.logo_transmission_type == 0x03) 714 | class type_03(Syntax): 715 | logo_char = aribstr(lambda self: self.descriptor_length - 1) 716 | 717 | @case(lambda self: self.logo_transmission_type not in (0x01, 0x02, 0x03)) 718 | class type_else(Syntax): 719 | reserved_future_use = bslbf(lambda self: self.descriptor_length - 1) 720 | 721 | 722 | @tag(0xD5) 723 | class SeriesDescriptor(Descriptor): 724 | 725 | """シリーズ記述子 (ARIB-STD-B10-2-6.2.33)""" 726 | 727 | descriptor_tag = uimsbf(8) 728 | descriptor_length = uimsbf(8) 729 | series_id = uimsbf(16) 730 | repeat_label = uimsbf(4) 731 | program_pattern = uimsbf(3) 732 | expire_date_valid_flag = uimsbf(1) 733 | expire_date = mjd(16) 734 | episode_number = uimsbf(12) 735 | last_episode_number = uimsbf(12) 736 | series_name_char = aribstr(lambda self: self.descriptor_length - 8) 737 | 738 | 739 | @tag(0xD6) 740 | class EventGroupDescriptor(Descriptor): 741 | 742 | """イベントグループ記述子 (ARIB-STD-B10-2-6.2.34)""" 743 | 744 | descriptor_tag = uimsbf(8) 745 | descriptor_length = uimsbf(8) 746 | group_type = uimsbf(4) 747 | event_count = uimsbf(4) 748 | 749 | @times(event_count) 750 | class events(Syntax): 751 | service_id = uimsbf(16) 752 | event_id = uimsbf(16) 753 | 754 | @case(lambda self: self.group_type in (4, 5)) 755 | class with_network(Syntax): 756 | @times(lambda self: self.event_count) 757 | class networks(Syntax): 758 | original_network_id = uimsbf(16) 759 | transport_stream_id = uimsbf(16) 760 | service_id = uimsbf(16) 761 | event_id = uimsbf(16) 762 | 763 | @case(lambda self: self.group_type not in (4, 5)) 764 | class without_network(Syntax): 765 | private_data_byte = aribstr(lambda self: ( 766 | self.descriptor_length - 1 - self.event_count * 4 767 | )) 768 | 769 | 770 | @tag(0xD7) 771 | class SIParameterDescriptor(Descriptor): 772 | 773 | """SI伝送パラメータ記述子(ARIB-STD-B10-2-6.2.35)""" 774 | 775 | descriptor_tag = uimsbf(8) 776 | descriptor_length = uimsbf(8) 777 | parameter_version = uimsbf(8) # 地デジ 0xFF 778 | update_time = mjd(16) 779 | 780 | @loop(lambda self: self.descriptor_length - 3) 781 | class parameters(Syntax): 782 | table_id = uimsbf(8) 783 | table_description_length = uimsbf(8) 784 | 785 | # ARIB-TR-B14-31.1.2.1, ARIB-TR-B15-31.2.2.1 786 | @case(lambda self: ( 787 | self.table_id in (0x40, 0x42, 0x46, 0x4E, 0x4F, 0xC4) 788 | and self.table_description_length == 1 789 | )) 790 | class table_description_1_1(Syntax): 791 | table_cycle = bcd(8) 792 | 793 | @case(lambda self: self.table_id in (0xC3, 0xC8)) 794 | class table_description_1_2(Syntax): 795 | table_cycle = bcd(16) 796 | 797 | @case(lambda self: ( 798 | self.table_id == 0x4E 799 | and self.table_description_length == 4 800 | )) 801 | class table_description_2(Syntax): 802 | table_cycle_H_EIT_PF = bcd(8) 803 | table_cycle_M_EIT = bcd(8) 804 | table_cycle_L_EIT = bcd(8) 805 | num_of_M_EIT_event = uimsbf(4) 806 | num_of_L_EIT_event = uimsbf(4) 807 | 808 | @case(lambda self: self.table_id in (0x50, 0x58, 0x60)) 809 | class table_description_3(Syntax): 810 | @loop(lambda self: self.table_description_length) 811 | class cycles(Syntax): 812 | media_type = uimsbf(2) 813 | pattern = uimsbf(2) 814 | reserved_1 = bslbf(4) 815 | schdule_range = bcd(8) 816 | base_cycle = bcd(12) 817 | reserved_2 = bslbf(2) 818 | cycle_group_count = uimsbf(2) 819 | 820 | @times(cycle_group_count) 821 | class groups(Syntax): 822 | num_of_segment = bcd(8) 823 | cycle = bcd(8) 824 | 825 | @case(lambda self: self.table_id not in( 826 | 0x40, 0x42, 0x46, 0x4E, 0x4F, 0x50, 0x58, 0x60, 0xC3, 0xC4, 0xC8)) 827 | class table_description_else(Syntax): 828 | table_description_byte = bslbf( 829 | lambda self: self.table_description_length) 830 | 831 | 832 | @tag(0xD8) 833 | class BroadcasterNameDescriptor(Descriptor): 834 | 835 | """ブロードキャスタ名記述子(ARIB-STD-B10-2-6.2.36)""" 836 | 837 | descriptor_tag = uimsbf(8) 838 | descriptor_length = uimsbf(8) 839 | char = aribstr(descriptor_length) 840 | 841 | 842 | @tag(0xDA) 843 | class SIPrimeTSDescriptor(Descriptor): 844 | 845 | """SIプライムTS記述子(ARIB-DTD-B10-2.6.2.38)""" 846 | 847 | descriptor_tag = uimsbf(8) 848 | descriptor_length = uimsbf(8) 849 | parameter_version = uimsbf(8) 850 | update_time = mjd(16) 851 | SI_prime_ts_network_id = uimsbf(16) 852 | SI_prime_transport_stream_id = uimsbf(16) 853 | 854 | @loop(lambda self: self.descriptor_length - 8) 855 | class tables(Syntax): 856 | table_id = uimsbf(8) 857 | table_description_length = uimsbf(8) 858 | 859 | @case(lambda self: ( 860 | self.table_id in (0x42, 0x46, 0x4E, 0x4F, 0xC5, 0xC6) 861 | )) 862 | class table_description_1(Syntax): 863 | table_cycle = bslbf(8) 864 | 865 | @case(lambda self: self.table_id in (0x50, 0x60)) 866 | class table_description_2(Syntax): 867 | @loop(lambda self: self.table_description_length) 868 | class cycles(Syntax): 869 | media_type = uimsbf(2) 870 | pattern = uimsbf(2) 871 | reserved_1 = bslbf(4) 872 | schdule_range = bcd(8) 873 | base_cycle = bcd(12) 874 | reserved_2 = bslbf(2) 875 | cycle_group_count = uimsbf(2) 876 | 877 | @times(cycle_group_count) 878 | class groups(Syntax): 879 | num_of_segment = bcd(8) 880 | cycle = bcd(8) 881 | 882 | @case(lambda self: ( 883 | self.table_id not in ( 884 | 0x42, 0x46, 0x4E, 0x4F, 0x50, 0x60, 0xC5, 0xC6 885 | ) 886 | )) 887 | class table_description_else(Syntax): 888 | table_description_byte = bslbf( 889 | lambda self: self.table_description_length) 890 | 891 | 892 | @tag(0xDC) 893 | class LDTLinkageDescriptor(Descriptor): 894 | 895 | """LDTリンク記述子(ARIB-STD-B10-2.6.2.40)""" 896 | 897 | descriptor_tag = uimsbf(8) 898 | descriptor_length = uimsbf(8) 899 | original_service_id = uimsbf(16) 900 | transport_stream_id = uimsbf(16) 901 | original_network_id = uimsbf(16) 902 | 903 | @loop(lambda self: self.descriptor_length - 6) 904 | class descriptions(Syntax): 905 | description_id = uimsbf(16) 906 | reserved_future_use = bslbf(4) 907 | description_type = uimsbf(4) 908 | user_defined = bslbf(8) 909 | 910 | 911 | @tag(0xDE) 912 | class ContentAvailabilityDescriptor(Descriptor): 913 | 914 | """コンテント利用記述子 (ARIB-STD-B10-2-6.2.45)""" 915 | 916 | descriptor_tag = uimsbf(8) 917 | descriptor_length = uimsbf(8) 918 | reserved_future_use_1 = bslbf(1) 919 | copy_restriction_mode = bslbf(1) 920 | image_constraint_token = bslbf(1) 921 | retention_mode = bslbf(1) 922 | retention_state = bslbf(3) 923 | encryption_mode = bslbf(1) 924 | reserved_future_use_2 = bslbf(lambda self: self.descriptor_length - 1) 925 | 926 | 927 | @tag(0xF6) 928 | class AccessControlDescriptor(Descriptor): 929 | 930 | """アクセス制御記述子 (ARIB-TR-B14 第四篇改定案 30.2.2.2""" 931 | 932 | descriptor_tag = uimsbf(8) 933 | descriptor_length = uimsbf(8) 934 | CA_system_ID = uimsbf(16) 935 | transmission_type = bslbf(3) 936 | PID = uimsbf(13) 937 | private_data_byte = bslbf(lambda self: self.descriptor_length - 4) 938 | 939 | 940 | @tag(0xFA) 941 | class TerrestrialDeliverySystemDescriptor(Descriptor): 942 | 943 | """地上分配システム記述子(ARIB-STD-B10-2-6.2.31)""" 944 | 945 | descriptor_tag = uimsbf(8) 946 | descriptor_length = uimsbf(8) 947 | area_code = bslbf(12) 948 | guard_interval = bslbf(2) 949 | transmission_mode = bslbf(2) 950 | 951 | @loop(lambda self: self.descriptor_length - 2) 952 | class freqs(Syntax): 953 | frequency = uimsbf(16) 954 | 955 | 956 | @tag(0xFB) 957 | class PartialReceptionDescriptor(Descriptor): 958 | 959 | """部分受信記述子(ARIB-STD-B10-2.6.2.32)""" 960 | 961 | descriptor_tag = uimsbf(8) 962 | descriptor_length = uimsbf(8) 963 | 964 | @loop(descriptor_length) 965 | class services(Syntax): 966 | service_id = uimsbf(16) 967 | 968 | 969 | @tag(0xFC) 970 | class EmergencyInformationDescriptor(Descriptor): 971 | 972 | """緊急情報記述子(ARIB-STD-B10-2-7.2.24)""" 973 | 974 | descriptor_tag = uimsbf(8) 975 | descriptor_length = uimsbf(8) 976 | 977 | @loop(descriptor_length) 978 | class services(Syntax): 979 | service_id = uimsbf(16) 980 | start_end_flag = bslbf(1) 981 | signal_level = bslbf(1) 982 | reserved_future_use = bslbf(6) 983 | area_code_length = uimsbf(8) 984 | 985 | @loop(lambda self: self.area_code_length) 986 | class area_codes(Syntax): 987 | area_code = uimsbf(12) 988 | reserved_future_use = bslbf(4) 989 | 990 | 991 | @tag(0xFD) 992 | class DataComponentDescriptor(Descriptor): 993 | 994 | """データ符号化方式記述子(ARIB-STD-B10-2-6.2.20) 995 | 996 | data_component_idが0x0008のものは、additional_data_component_infoに 997 | 字幕・文字スーパーの識別情報が入っている(ARIB-STD-B24-1-3-9.6.1) 998 | 999 | """ 1000 | 1001 | descriptor_tag = uimsbf(8) 1002 | descriptor_length = uimsbf(8) 1003 | data_component_id = uimsbf(16) 1004 | 1005 | @case(lambda self: self.data_component_id == 0x08) 1006 | class component_08(Syntax): 1007 | DMF = bslbf(4) 1008 | reserved = bslbf(2) 1009 | timing = bslbf(2) 1010 | 1011 | @case(lambda self: self.data_component_id not in (0x08,)) 1012 | class default_component(Syntax): 1013 | additional_data_component_info =\ 1014 | uimsbf(lambda self: self.descriptor_length - 2) 1015 | 1016 | 1017 | @tag(0xFE) 1018 | class SystemManagementDescriptor(Descriptor): 1019 | 1020 | """システム管理記述子(ARIB-STD-B10-2-6.2.21, ARIB-TR-B14-30.4.2.2)""" 1021 | 1022 | descriptor_tag = uimsbf(8) 1023 | descriptor_length = uimsbf(8) 1024 | broadcasting_flag = uimsbf(2) # 放送 0x00 1025 | broadcasting_identifier = uimsbf(6) # 地デジ 0x03, BS 0x02, CS 0x04 1026 | additional_broadcasting_identification = uimsbf(8) # 0x01 1027 | additional_identification_info =\ 1028 | uimsbf(lambda self: self.descriptor_length - 2) 1029 | 1030 | """ 1031 | 未実装の記述子 1032 | #0x05: 登録記述子 1033 | #0x13: カルーセル識別記述子 1034 | #0x14: アソシエーションタグ記述子, 1035 | #0x15: 拡張アソシエーションタグ記述子 1036 | #0x1C: MPEG-4 オーディオ記述子, 1037 | #0x28: AVCビデオ記述子, 1038 | #0x2A: AVCタイミングHRD記述子, 1039 | #0x2E: MPEG-4オーディオ拡張記述子, 1040 | #0x42: スタッフ記述子, 1041 | #0x44: 優先分配システム記述子, 1042 | #0x4B: NVOD基準サービス記述子, 1043 | #0x51: モザイク記述子, 1044 | #0x55: パレンタルレート記述子, 1045 | #0x58: ローカル時間オフセット記述子, 1046 | #0x63: パーシャルトランスポートストリーム識別子, 1047 | #0x66: データブロードキャスト識別記述子, 1048 | #0xC2: ネットワーク識別記述子, 1049 | #0xC3: パーシャルトランスポートストリームタイム記述子, 1050 | #0xC6: 対象地域記述子, 1051 | #0xCA: CA_EMM_TS記述子, 1052 | #0xCB: CA契約情報記述子, 1053 | #0xD0: 基本ローカルイベント記述子, 1054 | #0xD1: リファレンス記述子, 1055 | #0xD2: ノード関係記述子, 1056 | #0xD3: 短形式ノード情報記述子, 1057 | #0xD4: STC参照記述子, 1058 | #0xD9: コンポーネントグループ記述子, 1059 | #0xDB: 掲示板情報記述子, 1060 | #0xDD: 連結送信記述子, 1061 | #0xE0: サービスグループ記述子, 1062 | #0xF7: カルーセル互換複合記述子, 1063 | #0xF8: 限定再生方式記述子, 1064 | #0xF9: 有線TS分割システム記述子, 1065 | #0xFC: 緊急情報記述子, 1066 | """ 1067 | -------------------------------------------------------------------------------- /ariblib/drcs.py: -------------------------------------------------------------------------------- 1 | from hashlib import md5 2 | 3 | try: 4 | from PIL import Image 5 | from PIL.ImageDraw import Draw 6 | except ImportError: 7 | text_output = True 8 | else: 9 | text_output = False 10 | 11 | import csv 12 | import os.path 13 | 14 | import ariblib 15 | 16 | save_dir = os.path.expanduser('~/.ariblib/drcs/') 17 | mapping_path = os.path.join((os.path.split(ariblib.__file__)[0]), 'drcs.tsv') 18 | user_mapping_path = os.path.expanduser('~/.ariblib/drcs.tsv') 19 | mapping = {} 20 | 21 | # DRCSイメージ保存ディレクトリが存在しない場合は作成する 22 | if not os.path.isdir(save_dir): 23 | import os 24 | os.makedirs(save_dir) 25 | 26 | for path in (mapping_path, user_mapping_path): 27 | try: 28 | with open(path) as f: 29 | reader = csv.reader(f, delimiter='\t') 30 | mapping.update(line for line in reader) 31 | except IOError: 32 | pass 33 | 34 | 35 | class DRCSImage(object): 36 | 37 | """DRCSの画像イメージ""" 38 | 39 | def __init__(self, width, height, bgcolor='white'): 40 | self.image = Image.new('RGB', (width, height), bgcolor) 41 | self.hash = None 42 | 43 | def point(self, patterns, color='black'): 44 | hasher = md5() 45 | draw = Draw(self.image) 46 | for y, pattern in enumerate(patterns): 47 | pattern_data = pattern.pattern_data 48 | hasher.update(pattern_data) 49 | points = [ 50 | (x, y) 51 | for x, dot in enumerate(_to_bit(pattern_data)) if dot == '1' 52 | ] 53 | draw.point(points, color) 54 | self.hash = hasher.hexdigest() 55 | 56 | def save(self, ext='png', path=None): 57 | if path is None: 58 | if self.hash is None: 59 | raise ValueError('画像イメージが作成されていません') 60 | path = self.hash 61 | self.image.save(os.path.join(save_dir, path + '.' + ext)) 62 | 63 | 64 | class DRCSText(object): 65 | 66 | """DRCSのテキストイメージ""" 67 | 68 | def __init__(self, width, height, bgcolor='white'): 69 | self.hash = None 70 | self.dots = [] 71 | 72 | def point(self, patterns, color='black'): 73 | hasher = md5() 74 | for pattern in patterns: 75 | pattern_data = pattern.pattern_data 76 | hasher.update(pattern_data) 77 | points = _to_bit(pattern_data).replace('0', ' ').replace('1', '■') 78 | self.dots.append(points) 79 | self.hash = hasher.hexdigest() 80 | 81 | def save(self, ext='txt', path=None): 82 | if path is None: 83 | if self.hash is None: 84 | raise ValueError('画像イメージが作成されていません') 85 | path = self.hash 86 | with open(os.path.join(save_dir, path + '.' + ext), 'w') as out: 87 | out.write('\n'.join(self.dots)) 88 | 89 | 90 | def _to_bit(raw): 91 | """bytearrayを0,1のビットを表す文字列に変換する""" 92 | 93 | return ''.join(map(lambda s: format(s, '08b'), raw)) 94 | 95 | # 画像出力ができないときはテキスト出力を行う 96 | if text_output: 97 | DRCSImage = DRCSText 98 | -------------------------------------------------------------------------------- /ariblib/drcs.tsv: -------------------------------------------------------------------------------- 1 | 4360dd96063ce1a9660cc8437e8238e3 ?! 2 | 372b3d8f2e6c79dd33904eaf4b663e12 (携帯電話) 3 | 2c256506f406bac4c214318f196ad5db 『 4 | 70376e1ea05a3438a19c062ad49a7960 』 5 | 71c94bb6d963e47443eac448a09d22ce (( 6 | 8c810b8cbe6159e837a88575bb4e6033 )) 7 | -------------------------------------------------------------------------------- /ariblib/event.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.2 2 | 3 | """イベントラッパー""" 4 | 5 | from ariblib.aribstr import AribString 6 | from ariblib.constants import ( 7 | COMPONENT_TYPE, 8 | CONTENT_TYPE, 9 | DIGITAL_RECORDING_CONTROL_TYPE, 10 | EVENT_GROUP_TYPE, 11 | SAMPLING_RATE, 12 | USER_TYPE, 13 | ) 14 | from ariblib.descriptors import ( 15 | AudioComponentDescriptor, 16 | ComponentDescriptor, 17 | ContentDescriptor, 18 | DigitalCopyControlDescriptor, 19 | EventGroupDescriptor, 20 | ExtendedEventDescriptor, 21 | ShortEventDescriptor, 22 | ) 23 | from ariblib.sections import ActualStreamEventInformationSection 24 | 25 | 26 | def events(ts, section=ActualStreamEventInformationSection): 27 | """トランスポートストリームから Event オブジェクトを返すジェネレータ""" 28 | 29 | for eit in ts.sections(section): 30 | for event in eit.events: 31 | yield Event(eit, event) 32 | 33 | 34 | class Event(object): 35 | 36 | """イベントラッパークラス""" 37 | 38 | from_eit = ['service_id', 'transport_stream_id', 'original_network_id'] 39 | from_event = ['event_id', 'start_time', 'duration', 'free_CA_mode'] 40 | 41 | def __init__(self, eit, event): 42 | for field in self.from_eit: 43 | setattr(self, field, getattr(eit, field)) 44 | for field in self.from_event: 45 | setattr(self, field, getattr(event, field)) 46 | 47 | desc = event.descriptors 48 | for sed in desc.get(ShortEventDescriptor, []): 49 | self.title = sed.event_name_char 50 | self.desc = sed.text_char 51 | for cd in desc.get(ComponentDescriptor, []): 52 | self.video_content = cd.stream_content 53 | self.video_component = cd.component_type 54 | self.video = COMPONENT_TYPE[cd.stream_content][cd.component_type] 55 | self.video_text = cd.component_text 56 | for dccd in desc.get(DigitalCopyControlDescriptor, []): 57 | self.copy_control_type = dccd.copy_control_type 58 | self.copy = DIGITAL_RECORDING_CONTROL_TYPE[dccd.copy_control_type] 59 | for acd in desc.get(AudioComponentDescriptor, []): 60 | if acd.main_component_flag: 61 | self.audio_content = acd.stream_content 62 | self.audio_component = acd.component_type 63 | self.samplint_rate_type = acd.sampling_rate 64 | self.audio =\ 65 | COMPONENT_TYPE[acd.stream_content][acd.component_type] 66 | self.sampling_rate = acd.sampling_rate 67 | self.sampling_rate_string = SAMPLING_RATE[acd.sampling_rate] 68 | self.audio_text = acd.audio_text 69 | else: 70 | self.second_audio_content = acd.stream_content 71 | self.second_audio_component = acd.component_type 72 | self.second_audio =\ 73 | COMPONENT_TYPE[acd.stream_content][acd.component_type] 74 | self.second_sampling_rate = acd.sampling_rate 75 | self.second_sampling_rate_string =\ 76 | SAMPLING_RATE[acd.sampling_rate] 77 | self.second_audio_text = acd.audio_text 78 | for egd in desc.get(EventGroupDescriptor, []): 79 | self.group_type = egd.group_type 80 | self.group_type_string = EVENT_GROUP_TYPE[egd.group_type] 81 | self.events = dict((e.service_id, e.event_id) for e in egd.events) 82 | for ctd in desc.get(ContentDescriptor, []): 83 | self.nibble1 = [] 84 | self.nibble2 = [] 85 | self.user_nibble = [] 86 | self.genre = [] 87 | self.subgenre = [] 88 | self.user_genre = [] 89 | for nibble in ctd.nibbles: 90 | self.nibble1.append( 91 | nibble.content_nibble_level_1) 92 | self.nibble2.append( 93 | nibble.content_nibble_level_2) 94 | self.genre.append( 95 | CONTENT_TYPE[nibble.content_nibble_level_1][0]) 96 | self.subgenre.append( 97 | CONTENT_TYPE[nibble.content_nibble_level_1][1] 98 | [nibble.content_nibble_level_2]) 99 | self.user_nibble.append(nibble.user_nibble) 100 | self.user_genre.append(USER_TYPE.get(nibble.user_nibble, '')) 101 | detail = [('', [])] 102 | for eed in desc.get(ExtendedEventDescriptor, []): 103 | for item in eed.items: 104 | key = item.item_description_char 105 | # タイトルが空か一つ前と同じ場合は本文を一つ前のものにつなげる 106 | if str(key) == '' or str(detail[-1][0]) == str(key): 107 | detail[-1][1].extend(item.item_char) 108 | else: 109 | detail.append((key, item.item_char)) 110 | detail = [(str(key), AribString(value)) for key, value in detail[1:]] 111 | if detail: 112 | self.detail = dict(detail) 113 | self.longdesc = '\n'.join( 114 | "{}\n{}\n".format(key, value) for key, value in detail) 115 | 116 | if __name__ == '__main__': 117 | from subprocess import Popen, PIPE 118 | from ariblib import tsopen 119 | recpt1 = Popen(['recpt1', '236', '130', '-'], stdout=PIPE) 120 | with tsopen(recpt1.stdout.fileno()) as ts: 121 | for event in events(ts): 122 | print(dir(event)) 123 | try: 124 | print(event.title, event.free_CA_mode) 125 | except: 126 | pass 127 | -------------------------------------------------------------------------------- /ariblib/mnemonics.py: -------------------------------------------------------------------------------- 1 | """ビット列表記の実装 2 | 仕様書ではビット列表記は全て小文字になっているので、ここで実装するクラスも全て小文字とする 3 | """ 4 | 5 | from datetime import datetime, timedelta 6 | from functools import reduce 7 | 8 | from ariblib.aribstr import AribString 9 | 10 | try: 11 | callable 12 | except NameError: 13 | from types import FunctionType 14 | 15 | def callable(function): 16 | return function.__class__ is FunctionType 17 | 18 | 19 | def meta_cache(suffix): 20 | """real_length, real_count用のキャッシュデコレータ""" 21 | 22 | def cache(func): 23 | def cached(self, instance): 24 | cache_name = '_{}_{}'.format(self.name, suffix) 25 | caches = instance.__dict__ 26 | if cache_name in caches: 27 | return caches[cache_name] 28 | result = func(self, instance) 29 | caches[cache_name] = result 30 | return result 31 | return cached 32 | return cache 33 | 34 | 35 | def cache(func): 36 | """ビット列キャッシュデコレータ""" 37 | 38 | def cached(self, instance, owner): 39 | result = func(self, instance, owner) 40 | setattr(instance, self.name, result) 41 | return result 42 | return cached 43 | 44 | 45 | class mnemonic(object): 46 | 47 | """ビット列表記の親クラス""" 48 | 49 | def __init__(self, length): 50 | self.length = length 51 | self.start = lambda instance: 0 52 | self.name = '' 53 | 54 | @meta_cache('len') 55 | def real_length(self, instance): 56 | """ビット列の実際の長さを求める""" 57 | 58 | if isinstance(self.length, int): 59 | return self.length 60 | 61 | if self.length is None: 62 | return sum(mnemonic.real_length(instance) 63 | for mnemonic in self.cls._mnemonics) 64 | if isinstance(self.length, mnemonic): 65 | return getattr(instance, self.length.name) * 8 66 | if callable(self.length): 67 | return self.length(instance) * 8 68 | if isinstance(self.length, str): 69 | return getattr(instance, self.length) * 8 70 | return self.length 71 | 72 | 73 | class uimsbf(mnemonic): 74 | 75 | """unsigned integer most significant bit first[符号無し整数、最上位ビットが先頭]""" 76 | 77 | @cache 78 | def __get__(self, instance, owner): 79 | start = self.start(instance) 80 | length = self.real_length(instance) 81 | return self.uimsbf(instance._packet, start, length) 82 | 83 | @staticmethod 84 | def uimsbf(packet, index, length): 85 | if length == 0: 86 | return 0 87 | 88 | block = index // 8 89 | start = index % 8 90 | pos = 8 - start 91 | if length + start <= 8: 92 | shift = pos - length 93 | filter = 2 ** pos - 2 ** shift 94 | return (packet[block] & filter) >> shift 95 | 96 | return (uimsbf.uimsbf(packet, index, pos) << (length - pos) | 97 | uimsbf.uimsbf(packet, index + pos, length - pos)) 98 | 99 | 100 | class bslbf(uimsbf): 101 | 102 | """bit string,left bit first[ビット列、左ビットが先頭] 103 | 104 | 実質 uimsbf と同じ処理をすればよい。""" 105 | 106 | 107 | class rpchof(uimsbf): 108 | 109 | """remainder polynominal coefficients, highest order first[多項式係数の剰余、最上位階数が先頭] 110 | 111 | 正しい CRC の値かどうか検証する必要があるが、とりあえず uimsbf と同じ処理とする。""" 112 | 113 | 114 | class mjd(mnemonic): 115 | 116 | """Modified Julian Date[修正ユリウス日]""" 117 | 118 | @cache 119 | def __get__(self, instance, owner): 120 | start = self.start(instance) 121 | block = start // 8 122 | last = block + self.real_length(instance) // 8 123 | pmjd = instance._packet[block:last] 124 | if pmjd == bytearray(b'\xff\xff\xff\xff\xff'): 125 | return None 126 | return datetime(*mjd2datetime(pmjd)) 127 | 128 | 129 | class bcd(mnemonic): 130 | 131 | """二進化十進数で表現された数値""" 132 | 133 | def __init__(self, length, decimal_point=0): 134 | self.decimal_point = decimal_point 135 | mnemonic.__init__(self, length) 136 | 137 | @cache 138 | def __get__(self, instance, owner): 139 | start = self.start(instance) 140 | block = start // 8 141 | last = block + self.real_length(instance) // 8 142 | pbcd = instance._packet[block:last] 143 | int_values = map(bcd2int, pbcd) 144 | value = reduce(lambda x, y: x * 100 + y, int_values) 145 | return value / (10 ** self.decimal_point) 146 | 147 | 148 | class bcdtime(mnemonic): 149 | 150 | """二進化十進数で表現された時分秒""" 151 | 152 | @cache 153 | def __get__(self, instance, owner): 154 | start = self.start(instance) 155 | block = start // 8 156 | last = block + 3 157 | bcd = instance._packet[block:last] 158 | if bcd == bytearray(b'\xff\xff\xff'): 159 | return None 160 | hour, minute, second = map(bcd2int, bcd) 161 | return timedelta(hours=hour, minutes=minute, seconds=second) 162 | 163 | 164 | class otm(mnemonic): 165 | 166 | """オフセット時刻。二進化十進数で表現された時・分・秒・ミリ秒""" 167 | 168 | @cache 169 | def __get__(self, instance, owner): 170 | start = self.start(instance) 171 | block = start // 8 172 | last = block + 3 173 | bcd = map(ord, instance._packet[block:last]) 174 | msec = map(ord, instance._packet[last:]) 175 | millisecond = ( 176 | ((msec[0] & 0xF0) >> 4) * 100 + 177 | ((msec[0] & 0x0F) * 10) + 178 | ((msec[1] & 0x0F) >> 4) 179 | ) 180 | hour, minute, second = map(bcd2int, bcd) 181 | return timedelta(hours=hour, minutes=minute, seconds=second, 182 | microseconds=millisecond) 183 | 184 | 185 | class aribstr(mnemonic): 186 | 187 | """8単位符号で符号化された文字列""" 188 | 189 | @cache 190 | def __get__(self, instance, owner): 191 | start = self.start(instance) 192 | block = start // 8 193 | last = block + self.real_length(instance) // 8 194 | binary = bytearray(instance._packet[block:last]) 195 | return AribString(binary) 196 | 197 | 198 | class char(mnemonic): 199 | 200 | """ISO 8859-1に従って8ビットで符号化された文字列""" 201 | 202 | @cache 203 | def __get__(self, instance, owner): 204 | start = self.start(instance) 205 | block = start // 8 206 | last = block + self.real_length(instance) // 8 207 | return ''.join(map(chr, instance._packet[block:last])) 208 | 209 | 210 | class cp932(mnemonic): 211 | 212 | """CP932文字列""" 213 | 214 | @cache 215 | def __get__(self, instance, owner): 216 | start = self.start(instance) 217 | block = start // 8 218 | last = block + self.real_length(instance) // 8 219 | return unicode(char(instance._packet, size, cur), 'CP932') 220 | 221 | 222 | class raw(mnemonic): 223 | """アレイそのまま""" 224 | 225 | @cache 226 | def __get__(self, instance, owner): 227 | start = self.start(instance) 228 | block = start // 8 229 | last = block + self.real_length(instance) // 8 230 | return instance._packet[block:last] 231 | 232 | 233 | class fixed_size_loop(mnemonic): 234 | 235 | """バイト数固定のループ""" 236 | 237 | def __init__(self, cls, length): 238 | self.cls = cls 239 | mnemonic.__init__(self, length) 240 | 241 | @cache 242 | def __get__(self, instance, owner): 243 | length = self.real_length(instance) // 8 244 | start = self.start(instance) // 8 245 | end = start + length 246 | result = [] 247 | while start < end: 248 | start_pos = start * 8 249 | obj = self.cls(instance._packet, pos=start_pos) 250 | result.append(obj) 251 | start += len(obj) // 8 252 | return result 253 | 254 | 255 | class fixed_count_loop(mnemonic): 256 | 257 | """回数固定のループ""" 258 | 259 | def __init__(self, cls, count): 260 | self.cls = cls 261 | self.count = count 262 | mnemonic.__init__(self, None) 263 | 264 | @cache 265 | def __get__(self, instance, owner): 266 | start = self.start(instance) // 8 267 | result = [] 268 | for _ in range(self.real_count(instance)): 269 | start_pos = start * 8 270 | obj = self.cls(instance._packet, pos=start_pos) 271 | result.append(obj) 272 | start += len(obj) // 8 273 | return result 274 | 275 | @meta_cache('count') 276 | def real_count(self, instance): 277 | if isinstance(self.count, int): 278 | return self.count 279 | if isinstance(self.count, mnemonic): 280 | return getattr(instance, self.count.name) 281 | if callable(self.count): 282 | return self.count(instance) 283 | if isinstance(self.count, str): 284 | return getattr(instance, self.count) 285 | return self.count 286 | 287 | @meta_cache('len') 288 | def real_length(self, instance): 289 | return sum(mnemonic.real_length(sub) 290 | for sub in getattr(instance, self.name) 291 | for mnemonic in sub._mnemonics) 292 | 293 | 294 | class case_table(mnemonic): 295 | 296 | """if""" 297 | 298 | def __init__(self, cls, condition): 299 | self.cls = cls 300 | self.count = 1 301 | if isinstance(condition, mnemonic): 302 | self.condition = lambda instance: getattr(instance, condition.name) 303 | else: 304 | self.condition = condition 305 | 306 | mnemonic.__init__(self, None) 307 | 308 | @cache 309 | def __get__(self, instance, owner): 310 | if self.condition(instance): 311 | start_pos = self.start(instance) 312 | return self.cls(instance._packet, pos=start_pos, parent=instance) 313 | return None 314 | 315 | @meta_cache('len') 316 | def real_length(self, instance): 317 | if self.condition(instance): 318 | return sum(mnemonic.real_length(instance) 319 | for mnemonic in self.cls._mnemonics) 320 | return 0 321 | 322 | 323 | def bindump(target): 324 | """バイナリダンプ""" 325 | return ' '.join(map(lambda x: format(x, '08b'), target)) 326 | 327 | 328 | def hexdump(target): 329 | """16進ダンプ""" 330 | return ' '.join(map(lambda x: format(x, '02X'), target)) 331 | 332 | 333 | def mjd2datetime(pmjd): 334 | """mjdを年月日時分秒のタプルとして返す 335 | ARIB-STD-B10第2部付録Cの通りに実装""" 336 | 337 | mjd = (pmjd[0] << 8) | pmjd[1] 338 | if len(pmjd) > 2: 339 | bcd = pmjd[2:] 340 | else: 341 | bcd = [0, 0, 0] 342 | yy_ = int((mjd - 15078.2) / 365.25) 343 | mm_ = int((mjd - 14956.1 - int(yy_ * 365.25)) / 30.6001) 344 | k = 1 if 14 <= mm_ <= 15 else 0 345 | day = mjd - 14956 - int(yy_ * 365.25) - int(mm_ * 30.6001) 346 | year = 1900 + yy_ + k 347 | month = mm_ - 1 - k * 12 348 | return (year, month, day) + tuple(map(bcd2int, bcd)) 349 | 350 | 351 | def bcd2int(bcd): 352 | """bcdを10進数にする""" 353 | 354 | return ((bcd & 0xF0) >> 4) * 10 + (bcd & 0x0F) 355 | 356 | 357 | def loop(length): 358 | """サイズ固定のサブシンタックスを返すデコレータ""" 359 | 360 | return lambda cls: fixed_size_loop(cls, length) 361 | 362 | 363 | def times(count): 364 | """回数固定のサブシンタックスを返すデコレータ""" 365 | 366 | return lambda cls: fixed_count_loop(cls, count) 367 | 368 | 369 | def case(condition): 370 | """ifセクションを返すデコレータ""" 371 | 372 | return lambda cls: case_table(cls, condition) 373 | -------------------------------------------------------------------------------- /ariblib/packet.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from datetime import timedelta 3 | from io import BufferedReader, FileIO 4 | from itertools import chain 5 | 6 | from ariblib.mnemonics import ( 7 | bcdtime, 8 | bslbf, 9 | case, 10 | char, 11 | loop, 12 | otm, 13 | raw, 14 | times, 15 | uimsbf, 16 | ) 17 | from ariblib.syntax import Syntax 18 | from ariblib.sections import Section 19 | 20 | """ 21 | パケット層の定義 22 | 23 | 188バイトずつの各パケットを class として実装するとかなり遅くなるので、 24 | パケットについては bytearray のまま受け渡して、関数で適宜対応することにした。 25 | """ 26 | 27 | 28 | class TransportStreamFile(BufferedReader): 29 | 30 | """TSファイル""" 31 | 32 | PACKET_SIZE = 188 33 | 34 | def __init__(self, path, chunk_size=10000): 35 | BufferedReader.__init__(self, FileIO(path)) 36 | self.chunk_size = chunk_size 37 | self._callbacks = dict() 38 | 39 | def __iter__(self): 40 | packet_size = self.PACKET_SIZE 41 | chunk_size = self.chunk_size 42 | buffer_size = packet_size * chunk_size 43 | packets = iter(lambda: self.read(buffer_size), b'') 44 | for packet in packets: 45 | for start, stop in zip( 46 | range(0, buffer_size - packet_size + 1, packet_size), 47 | range(packet_size, buffer_size + 1, packet_size) 48 | ): 49 | next = packet[start:stop] 50 | if not next: 51 | raise StopIteration 52 | yield next 53 | 54 | def __next__(self): 55 | return self.read(self.PACKET_SIZE) 56 | 57 | def on(self, Section): 58 | """セクションごとにコールバック関数を設定する 59 | いまのところ、一つのセクションについてコールバック関数は1つのみ定義できる。 60 | """ 61 | 62 | def attach_callback(callback): 63 | self._callbacks[Section] = callback 64 | return attach_callback 65 | 66 | def execute(self): 67 | """指定されたセクションがyieldされるごとにコールバック関数を実行する""" 68 | 69 | for section in self.sections(*self._callbacks.keys()): 70 | self._callbacks[type(section)](section) 71 | 72 | def sections(self, *Sections): 73 | """パケットストリームから指定のセクションを返す""" 74 | 75 | buf = defaultdict(bytearray) 76 | 77 | target_pids =\ 78 | set(chain.from_iterable(Section._pids for Section in Sections)) 79 | table_map = defaultdict(set) 80 | target_ids = dict() 81 | for Section in Sections: 82 | for PID in Section._pids: 83 | for table_id in Section._table_ids: 84 | target_ids[(PID, table_id)] = Section 85 | table_map[PID].add(table_id) 86 | 87 | for packet in self: 88 | PID = pid(packet) 89 | if PID not in target_pids: 90 | continue 91 | 92 | buffer = buf[PID] 93 | table_ids = table_map[PID] 94 | prev, current = payload(packet) 95 | if payload_unit_start_indicator(packet): 96 | if buffer: 97 | buffer.extend(prev) 98 | while buffer and buffer[0] != 0xFF: 99 | if buffer[0] in table_ids: 100 | section = target_ids[(PID, buffer[0])](buffer[:]) 101 | yield section 102 | try: 103 | if buffer[0:3] == b'\x00\x00\x01': 104 | break 105 | else: 106 | next_start =\ 107 | ((buffer[1] & 0x0F) << 8 | buffer[2]) + 3 108 | buffer[:] = buffer[next_start:] 109 | except (IndexError, AttributeError): 110 | break 111 | buffer[:] = current 112 | elif not buffer: 113 | continue 114 | else: 115 | buffer.extend(current) 116 | 117 | # 残ったバッファを片付ける 118 | for PID, buffer in buf.items(): 119 | if buffer and buffer[0] in table_ids: 120 | section = target_ids[(PID, buffer[0])](buffer[:]) 121 | if section.isfull(): 122 | yield section 123 | 124 | tables = sections 125 | 126 | def get_caption_pid(self): 127 | """字幕パケットの PID を返す 128 | 129 | FIXME: 2か国語対応の場合複数の PID で字幕が提供されているかも? (未確認) 130 | """ 131 | 132 | from ariblib.sections import ( 133 | ProgramAssociationSection, 134 | ProgramMapSection, 135 | ) 136 | from ariblib.descriptors import StreamIdentifierDescriptor 137 | 138 | pat = next(self.sections(ProgramAssociationSection)) 139 | ProgramMapSection._pids = list(pat.pmt_pids) 140 | for pmt in self.sections(ProgramMapSection): 141 | for tsmap in pmt.maps: 142 | if tsmap.stream_type != 0x06: 143 | continue 144 | for si in tsmap.descriptors.get(StreamIdentifierDescriptor, 145 | []): 146 | if si.component_tag == 0x87: 147 | return tsmap.elementary_PID 148 | 149 | def get_video_pid(self, video_encode_format): 150 | """指定のエンコードフォーマットの動画PIDを返す""" 151 | 152 | from ariblib.sections import ( 153 | ProgramAssociationSection, 154 | ProgramMapSection, 155 | ) 156 | from ariblib.descriptors import VideoDecodeControlDescriptor 157 | 158 | pat = next(self.sections(ProgramAssociationSection)) 159 | ProgramMapSection._pids = list(pat.pmt_pids) 160 | for pmt in self.sections(ProgramMapSection): 161 | for tsmap in pmt.maps: 162 | for vdc in tsmap.descriptors.get(VideoDecodeControlDescriptor, 163 | []): 164 | if vdc.video_encode_format == video_encode_format: 165 | return tsmap.elementary_PID 166 | 167 | def pcrs(self): 168 | """adaptation filed にある PCR から求めた timedelta オブジェクトを返す 169 | 170 | FIXME: PCR フラグでなく OPCR フラグが立っているときに、この関数は OPCR を返す 171 | """ 172 | 173 | for packet in self: 174 | if has_adaptation(packet) and packet[4] and packet[5]: 175 | pcr = ((packet[6] << 25) | (packet[7] << 17) | 176 | (packet[8] << 9) | (packet[9] << 1) | 177 | ((packet[10] & 0x80) >> 7)) 178 | yield timedelta(seconds=pcr / 90000) 179 | 180 | 181 | def tsopen(path, chunk=10000): 182 | """TransportStreamFileオブジェクトを返すラッパー関数""" 183 | return TransportStreamFile(path, chunk) 184 | 185 | 186 | def transport_error_indicator(packet): 187 | """パケットの transport_error_indocator を返す""" 188 | return (packet[1] & 0x80) >> 7 189 | 190 | 191 | def payload_unit_start_indicator(packet): 192 | """パケットの payload_unit_start_indicator を返す""" 193 | return (packet[1] & 0x40) >> 6 194 | 195 | 196 | def transport_priority(packet): 197 | """パケットの transport_priority を返す""" 198 | return (packet[1] & 0x20) >> 5 199 | 200 | 201 | def pid(packet): 202 | """パケットの pid を返す""" 203 | return ((packet[1] & 0x1F) << 8) | packet[2] 204 | 205 | 206 | def transport_scrambling_control(packet): 207 | return (packet[3] & 0xC0) >> 6 208 | 209 | 210 | def has_adaptation(packet): 211 | """このパケットが adaptation field を持っているかどうかを返す""" 212 | return (packet[3] & 0x20) >> 5 213 | 214 | 215 | def has_payload(packet): 216 | """このパケットが payload を持っているかどうかを返す""" 217 | return (packet[3] & 0x10) >> 4 218 | 219 | 220 | def continuity_counter(packet): 221 | """このパケットの continuity_counter を返す""" 222 | return packet[3] & 0x0F 223 | 224 | 225 | def adaptation_field(packet): 226 | """パケットから adaptaton field 部分を返す""" 227 | 228 | if not has_adaptation(packet): 229 | return b'' 230 | 231 | start = 4 232 | adaptation_length = packet[start] 233 | end = start + adaptation_length + 1 234 | return AdaptationField(packet[start:end]) 235 | 236 | 237 | def payload(packet): 238 | """パケットから payload 部分を返す 239 | 240 | args: 241 | packet - payloadを求めたい TS パケット 242 | 243 | return: 244 | (前のパケットの payload の続き, このパケットの payload) の tuple 245 | """ 246 | 247 | if not has_payload(packet): 248 | return (b'', b'') 249 | 250 | start = 4 251 | 252 | if has_adaptation(packet): 253 | adaptation_length = packet[start] 254 | start += 1 + adaptation_length 255 | 256 | if not payload_unit_start_indicator(packet): 257 | return (b'', packet[start:]) 258 | 259 | packet_start_code_prefix = packet[start:start + 3] 260 | if packet_start_code_prefix == b'\x00\x00\x01': 261 | return (b'', packet[start:]) 262 | 263 | pointer = packet[start] 264 | prev_start = start + 1 265 | start += 1 + pointer 266 | return (packet[prev_start:start], packet[start:]) 267 | 268 | 269 | class AdaptationField(Syntax): 270 | 271 | """ISO/IEC 13818-1 2.4.3.5""" 272 | 273 | adaptation_field_length = uimsbf(8) 274 | discontinuity_indicator = bslbf(1) 275 | random_access_indicator = bslbf(1) 276 | elementary_stream_priority_indicator = bslbf(1) 277 | PCR_flag = bslbf(1) 278 | OPCR_flag = bslbf(1) 279 | splicing_point_flag = bslbf(1) 280 | transport_private_data_flag = bslbf(1) 281 | adaptation_field_extension_flag = bslbf(1) 282 | 283 | @case(PCR_flag) 284 | class with_PCR(Syntax): 285 | program_clock_reference_base = uimsbf(33) 286 | reserved_1 = bslbf(6) 287 | program_clock_reference_extension = uimsbf(9) 288 | 289 | @case(OPCR_flag) 290 | class with_OPCR(Syntax): 291 | original_program_clock_reference_base = uimsbf(33) 292 | reserved_2 = bslbf(6) 293 | original_program_clock_reference_extension = uimsbf(9) 294 | 295 | @case(splicing_point_flag) 296 | class with_splicing(Syntax): 297 | splice_countdown = uimsbf(8) # tcimsbf 298 | 299 | @case(transport_private_data_flag) 300 | class with_transport_private_data(Syntax): 301 | transport_private_data_length = uimsbf(8) 302 | private_data_byte = bslbf(transport_private_data_length) 303 | 304 | 305 | class SynchronizedPacketizedElementaryStream(Section): 306 | 307 | """ISO/IEC 13818-1 2.4.3.7 308 | 309 | FIXME: 字幕 PES パケット専用の実装になっている 310 | 字幕部分の仕様は ARIB STD-B24-1-3-9 311 | """ 312 | 313 | packet_start_code_prefix = bslbf(24) 314 | stream_id = uimsbf(8) 315 | PES_packet_length = uimsbf(16) 316 | should_be_10 = bslbf(2) 317 | PES_scrambling_control = bslbf(2) 318 | PES_priority = bslbf(1) 319 | data_alignment_indicator = bslbf(1) 320 | copyright = bslbf(1) 321 | original_or_copy = bslbf(1) 322 | PTS_DTS_flags = bslbf(2) 323 | ESCR_flag = bslbf(1) 324 | ES_rate_flag = bslbf(1) 325 | DSM_trick_mode_flag = bslbf(1) 326 | additional_copy_into_flag = bslbf(1) 327 | PES_CRC_flag = bslbf(1) 328 | PES_extension_flag = bslbf(1) 329 | PES_header_data_length = uimsbf(8) 330 | should_be_0010 = bslbf(4) 331 | PTS_1 = uimsbf(3) 332 | marker_bit_1 = bslbf(1) 333 | PTS_2 = uimsbf(15) 334 | marler_bit_2 = bslbf(1) 335 | PTS_3 = uimsbf(15) 336 | marker_bit_3 = bslbf(1) 337 | PES_private_data_flag = bslbf(1) 338 | pack_header_field_flag = bslbf(1) 339 | program_packet_sequence_counter_flag = bslbf(1) 340 | P_STD_buffer_flag = bslbf(1) 341 | reserved4 = bslbf(3) 342 | PES_extension_flag_2 = bslbf(1) 343 | PES_private_data = bslbf(128) 344 | stuffing_byte = bslbf(lambda self: self.PES_header_data_length - 22) 345 | data_identifier = uimsbf(8) 346 | private_stream_id = uimsbf(8) 347 | reserved_future_use = bslbf(4) 348 | PES_data_packet_header_length = uimsbf(4) 349 | PES_data_private_data_byte = bslbf(PES_data_packet_header_length) 350 | data_group_id = uimsbf(6) 351 | data_group_version = bslbf(2) 352 | data_group_link_number = uimsbf(8) 353 | last_data_group_link_number = uimsbf(8) 354 | data_group_size = uimsbf(16) 355 | 356 | @case(lambda self: self.data_group_id in (0x0, 0x20)) 357 | class with_languages(Syntax): 358 | """ARIB-STD-B24-1-3-9.3.1 表9-3 字幕管理データ""" 359 | 360 | TMD = bslbf(2) 361 | reserved10 = bslbf(6) 362 | 363 | @case(lambda self: self.TMD == 0b10) 364 | class with_OTM(Syntax): 365 | OTM = otm(40) 366 | 367 | num_languages = uimsbf(8) 368 | 369 | @times(num_languages) 370 | class languages(Syntax): 371 | language_tag = bslbf(3) 372 | reserved11 = bslbf(1) 373 | DMF1 = bslbf(2) 374 | DMF2 = bslbf(2) 375 | 376 | @case(lambda self: self.DMF1 == 0b11) 377 | class with_DC(Syntax): 378 | DC = bslbf(8) 379 | 380 | ISO_639_language_code = char(24) 381 | format = bslbf(4) 382 | TCS = bslbf(2) 383 | rollup_mode = bslbf(2) 384 | 385 | @case(lambda self: self.data_group_id not in (0x0, 0x20)) 386 | class without_languages(Syntax): 387 | """ARIB-STD-B24-1-3-9.3.2 表9-10 字幕文データ""" 388 | 389 | TMD = bslbf(2) 390 | reserved10 = bslbf(6) 391 | 392 | @case(lambda self: self.TMD in (0b01, 0b10)) 393 | class with_STM(Syntax): 394 | STM = bcdtime(40) 395 | 396 | data_unit_loop_length = uimsbf(24) 397 | 398 | @loop(data_unit_loop_length) 399 | class data_units(Syntax): 400 | unit_separator = uimsbf(8) 401 | data_unit_parameter = uimsbf(8) 402 | 403 | @case(lambda self: self.data_unit_parameter == 0x20) 404 | class CProfileString(Syntax): 405 | data_unit_size = uimsbf(24) 406 | data_unit_data = raw(data_unit_size) 407 | 408 | @case(lambda self: self.data_unit_parameter == 0x30) 409 | class DRCSString(Syntax): 410 | """ARIB-STD-B24-1-2-D 表D-1 DRCS_data_structure 411 | 412 | FIXME: width が16固定""" 413 | 414 | data_unit_size = uimsbf(24) 415 | number_of_code = uimsbf(8) 416 | 417 | @times(number_of_code) 418 | class codes(Syntax): 419 | character_code = uimsbf(16) 420 | number_of_font = uimsbf(8) 421 | 422 | @times(number_of_font) 423 | class fonts(Syntax): 424 | font_id = uimsbf(4) 425 | mode = bslbf(4) 426 | depth = uimsbf(8) 427 | width = uimsbf(8) 428 | height = uimsbf(8) 429 | 430 | @times(height) 431 | class patterns(Syntax): 432 | pattern_data = raw(16) 433 | 434 | @property 435 | def pts(self): 436 | pts = (self.PTS_1 << 30) | (self.PTS_2 << 15) | self.PTS_3 437 | pts_hz = 90000 438 | second = pts / pts_hz 439 | return timedelta(seconds=second) 440 | 441 | def isfull(self): 442 | """section_length などで指定された分以上の 443 | パケットを持っているかどうかを返す""" 444 | 445 | return self.PES_packet_length <= len(self) + 3 446 | 447 | 448 | def raw_dump(packet): 449 | return ' '.join(map(lambda s: format(s, '02X'), packet)) 450 | -------------------------------------------------------------------------------- /ariblib/sections.py: -------------------------------------------------------------------------------- 1 | """各種 PSI セクションの定義""" 2 | 3 | from ariblib.descriptors import ( 4 | descriptors, 5 | ExtendedEventDescriptor, 6 | StreamIdentifierDescriptor, 7 | ) 8 | from ariblib.mnemonics import ( 9 | bcdtime, 10 | bslbf, 11 | loop, 12 | raw, 13 | rpchof, 14 | mjd, 15 | times, 16 | uimsbf, 17 | ) 18 | from ariblib.syntax import Syntax 19 | 20 | 21 | class Section(Syntax): 22 | 23 | """セクションの親クラス 24 | 25 | PSI はこれの子クラスとする。字幕 PES も便宜的にそうする 26 | """ 27 | 28 | _table_ids = range(256) 29 | 30 | def __init__(self, packet, pos=0, parent=None): 31 | Syntax.__init__(self, packet, pos, parent) 32 | self.callbacks = dict() 33 | 34 | def __getattr__(self, name): 35 | result = Syntax.__getattr__(self, name) 36 | if result is not None: 37 | return result 38 | 39 | # Section からサブシンタックスを全て走査しても見つからなかった場合は 40 | # AttributeError を投げる 41 | raise AttributeError("'{}' object has no attribute '{}'".format( 42 | self.__class__.__name__, name)) 43 | 44 | def __len__(self): 45 | return len(self._packet) 46 | 47 | def isfull(self): 48 | """section_length などで指定された分以上の 49 | パケットを持っているかどうかを返す""" 50 | 51 | return self.section_length <= len(self) + 3 52 | 53 | def on(self, Descriptor): 54 | """記述子ごとにコールバック関数を設定する 55 | いまのところ、一つの記述子についてコールバック関数は1つのみ定義できる 56 | """ 57 | 58 | def attach_callback(callback): 59 | self.callbacks[Descriptor] = callback 60 | return attach_callback 61 | 62 | def execute(self): 63 | """指定された記述子がyieldされるごとにコールバック関数を実行する""" 64 | 65 | for Descriptor, descriptors in self.descriptors.items(): 66 | if Descriptor == ExtendedEventDescriptor: 67 | self.callbacks[Descriptor](descriptors) 68 | else: 69 | for descriptor in descriptors: 70 | self.callbacks[Descriptor](descriptor) 71 | 72 | 73 | class ProgramAssociationSection(Section): 74 | 75 | """Program Association Section PAT (ISO 13818-1 2.4.4.3)""" 76 | 77 | _pids = [0x00] 78 | _table_ids = [0x00] 79 | 80 | table_id = uimsbf(8) 81 | section_syntax_indicator = bslbf(1) 82 | reserved_future_use = bslbf(1) 83 | reserved_1 = bslbf(2) 84 | section_length = uimsbf(12) 85 | transport_stream_id = uimsbf(16) 86 | reserved_2 = bslbf(2) 87 | version_number = uimsbf(5) 88 | current_next_indicator = bslbf(1) 89 | section_number = uimsbf(8) 90 | last_section_number = uimsbf(8) 91 | 92 | @loop(lambda self: self.section_length - 9) 93 | class pids(Syntax): 94 | program_number = uimsbf(16) 95 | reserved = bslbf(3) 96 | program_map_PID = uimsbf(13) 97 | 98 | CRC_32 = rpchof(32) 99 | 100 | @property 101 | def pmt_items(self): 102 | """program_number と program_map_PID のタプルを返すジェネレータ""" 103 | for pid in self.pids: 104 | if pid.program_number: 105 | yield (pid.program_number, pid.program_map_PID) 106 | 107 | @property 108 | def pmt_pids(self): 109 | """program_map_PID を返すジェネレータ""" 110 | for pid in self.pids: 111 | if pid.program_number: 112 | yield pid.program_map_PID 113 | 114 | 115 | class ProgramMapSection(Section): 116 | 117 | """Program Map Section PMT (ISO 13818-1 2.4.4.8)""" 118 | 119 | _table_ids = [0x02] 120 | 121 | table_id = uimsbf(8) 122 | section_syntax_indicator = bslbf(1) 123 | reserved_future_use = bslbf(1) 124 | reserved_1 = bslbf(2) 125 | section_length = uimsbf(12) 126 | program_number = uimsbf(16) 127 | reserved_2 = bslbf(2) 128 | version_number = uimsbf(5) 129 | current_next_indicator = bslbf(1) 130 | section_number = uimsbf(8) 131 | last_section_number = uimsbf(8) 132 | reserved_3 = bslbf(3) 133 | PCR_PID = uimsbf(13) 134 | reserved_4 = bslbf(4) 135 | program_info_length = uimsbf(12) 136 | descriptors = descriptors(program_info_length) 137 | 138 | @loop(lambda self: self.section_length - (13 + self.program_info_length)) 139 | class maps(Syntax): 140 | stream_type = uimsbf(8) 141 | reserved_1 = bslbf(3) 142 | elementary_PID = uimsbf(13) 143 | reserved_2 = bslbf(4) 144 | ES_info_length = uimsbf(12) 145 | descriptors = descriptors(ES_info_length) 146 | 147 | CRC_32 = rpchof(32) 148 | 149 | @property 150 | def caption_pid(self): 151 | """字幕データのあるPIDを返す。 152 | 153 | 字幕データはStream_typeが0x06(private data)で 154 | ストリーム識別記述子のcomponent_tagが0x87であるもの。 155 | 参考: http://linux.papa.to/?date=20081224#p01 156 | 157 | """ 158 | for stream in self.maps: 159 | try: 160 | if stream.stream_type == 0x06 and\ 161 | stream.descriptors[StreamIdentifierDescriptor][0].component_tag == 0x87: 162 | return stream.elementary_PID 163 | except KeyError: 164 | pass 165 | 166 | def video_pids(self): 167 | "動画パケットの PID を返すジェネレータ""" 168 | 169 | video_types = (0x01, 0x02, 0x10) 170 | for stream in self.maps: 171 | if stream.stream_type in video_types: 172 | yield stream.elementary_PID 173 | 174 | def audio_pids(self): 175 | """音声パケットの PID を返すジェネレータ""" 176 | 177 | audio_types = (0x03, 0x04, 0x0F, 0x11) 178 | for stream in self.maps: 179 | if stream.stream_type in audio_types: 180 | yield stream.elementary_PID 181 | 182 | 183 | class ConditionalAccessSection(Section): 184 | 185 | """限定受信セクション CAT (ISO 13818-1 2.4.4.6)""" 186 | 187 | _pids = [0x01] 188 | _table_ids = [0x01] 189 | 190 | table_id = uimsbf(8) 191 | section_syntax_indicator = bslbf(1) 192 | reserved_future_use = bslbf(1) 193 | reserved_1 = bslbf(2) 194 | section_length = uimsbf(12) 195 | reserved_2 = bslbf(18) 196 | version_number = uimsbf(5) 197 | current_next_indicator = bslbf(1) 198 | section_number = uimsbf(8) 199 | last_section_number = uimsbf(8) 200 | descriptors = descriptors(lambda self: self.section_length - 9) 201 | CRC_32 = rpchof(32) 202 | 203 | 204 | class NetworkInformationSection(Section): 205 | 206 | """ネットワーク情報セクション NIT (ARIB-STD-B10-2-5.2.4)""" 207 | 208 | _pids = [0x10] 209 | _table_ids = [0x40, 0x41] 210 | 211 | table_id = uimsbf(8) 212 | section_syntax_indicator = bslbf(1) 213 | reserved_future_use_1 = bslbf(1) 214 | reserved_1 = bslbf(2) 215 | section_length = uimsbf(12) 216 | network_id = uimsbf(16) 217 | reserved_2 = bslbf(2) 218 | version_number = uimsbf(5) 219 | current_next_indicator = bslbf(1) 220 | section_number = uimsbf(8) 221 | last_section_number = uimsbf(8) 222 | reserved_future_use_2 = bslbf(4) 223 | network_descriptors_length = uimsbf(12) 224 | network_descriptors = descriptors(network_descriptors_length) 225 | reserved_future_use_3 = bslbf(4) 226 | transport_stream_loop_length = uimsbf(12) 227 | 228 | @loop(transport_stream_loop_length) 229 | class transport_streams(Syntax): 230 | transport_stream_id = uimsbf(16) 231 | original_network_id = uimsbf(16) 232 | reserved_future_use = bslbf(4) 233 | transport_descriptors_length = uimsbf(12) 234 | descriptors = descriptors(transport_descriptors_length) 235 | 236 | CRC_32 = rpchof(32) 237 | 238 | 239 | class ServiceDescriptionSection(Section): 240 | 241 | """サービス記述セクション SDT (ARIB-STD-B10-2-5.2.6)""" 242 | 243 | _pids = [0x11] 244 | _table_ids = [0x42, 0x46] 245 | 246 | table_id = uimsbf(8) 247 | section_syntax_indicator = bslbf(1) 248 | reserved_future_use_1 = bslbf(1) 249 | reserved_1 = bslbf(2) 250 | section_length = uimsbf(12) 251 | transport_stream_id = uimsbf(16) 252 | reserved_2 = bslbf(2) 253 | version_number = uimsbf(5) 254 | current_next_indicator = bslbf(1) 255 | section_number = uimsbf(8) 256 | last_section_number = uimsbf(8) 257 | original_network_id = uimsbf(16) 258 | reserved_future_use_2 = bslbf(8) 259 | 260 | @loop(lambda self: self.section_length - 12) 261 | class services(Syntax): 262 | service_id = uimsbf(16) 263 | reserved_future_use = bslbf(3) 264 | EIT_user_defined_flags = bslbf(3) 265 | EIT_schedule_flag = bslbf(1) 266 | EIT_present_following_flag = bslbf(1) 267 | running_status = uimsbf(3) 268 | free_CA_mode = bslbf(1) 269 | descriptors_loop_length = uimsbf(12) 270 | descriptors = descriptors(descriptors_loop_length) 271 | 272 | CRC_32 = rpchof(32) 273 | 274 | 275 | class ActualStreamServiceDescriptionSection(ServiceDescriptionSection): 276 | 277 | """自ストリームSDT""" 278 | 279 | _table_ids = [0x42] 280 | 281 | 282 | class OtherStreamServiceDescriptionSection(ServiceDescriptionSection): 283 | 284 | """他ストリームSDT""" 285 | 286 | _tale_ids = [0x46] 287 | 288 | 289 | class BouquetAssociationSection(Section): 290 | 291 | """ブーケアソシエーションセクション BAT (ARIB-STD-B10-2-5.2.5)""" 292 | 293 | _pids = [0x11] 294 | _table_ids = [0x4A] 295 | 296 | table_id = uimsbf(8) 297 | section_syntax_indicator = bslbf(1) 298 | reserved_future_use_1 = bslbf(1) 299 | reserved_1 = bslbf(2) 300 | section_length = uimsbf(12) 301 | bouquet_id = uimsbf(16) 302 | reserved_2 = bslbf(2) 303 | version_number = uimsbf(5) 304 | current_next_indicator = bslbf(1) 305 | section_number = uimsbf(8) 306 | last_section_number = uimsbf(8) 307 | reserved_future_use_2 = bslbf(4) 308 | bouquet_descriptors_length = uimsbf(12) 309 | bouquet_descriptors = descriptors(bouquet_descriptors_length) 310 | reserved_future_use_3 = bslbf(4) 311 | transport_stream_loop_length = uimsbf(12) 312 | 313 | @loop(transport_stream_loop_length) 314 | class transport_streams(Syntax): 315 | transport_stream_id = uimsbf(16) 316 | original_network_id = uimsbf(16) 317 | reserved_future_use = bslbf(4) 318 | transport_descriptors_length = uimsbf(12) 319 | descriptors = descriptors(transport_descriptors_length) 320 | 321 | CRC_32 = rpchof(32) 322 | 323 | 324 | class EventInformationSection(Section): 325 | 326 | """イベント情報セクション EIT (ARIB-STD-B10-2-5.2.7)""" 327 | 328 | _pids = [0x12, 0x26, 0x27] 329 | _table_ids = range(0x4E, 0x70) 330 | 331 | table_id = uimsbf(8) 332 | section_syntax_indicator = bslbf(1) 333 | reserved_future_use = bslbf(1) 334 | reserved_1 = bslbf(2) 335 | section_length = uimsbf(12) 336 | service_id = uimsbf(16) 337 | reserved_2 = bslbf(2) 338 | version_number = uimsbf(5) 339 | current_next_indicator = bslbf(1) 340 | section_number = uimsbf(8) 341 | last_section_number = uimsbf(8) 342 | transport_stream_id = uimsbf(16) 343 | original_network_id = uimsbf(16) 344 | segment_last_section_number = uimsbf(8) 345 | last_table_id = uimsbf(8) 346 | 347 | @loop(lambda self: self.section_length - 15) 348 | class events(Syntax): 349 | event_id = uimsbf(16) 350 | start_time = mjd(40) 351 | duration = bcdtime(24) 352 | running_status = uimsbf(3) 353 | free_CA_mode = bslbf(1) 354 | descriptors_loop_length = uimsbf(12) 355 | descriptors = descriptors(descriptors_loop_length) 356 | 357 | CRC_32 = rpchof(32) 358 | 359 | 360 | class PresentFollowingEventInformationSection(EventInformationSection): 361 | 362 | """EIT[p/f]""" 363 | 364 | _table_ids = [0x4E, 0x4F] 365 | 366 | 367 | class ActualStreamEventInformationSection(EventInformationSection): 368 | 369 | """自ストリームEIT""" 370 | 371 | _table_ids = range(0x50, 0x60) 372 | 373 | 374 | class ActualStreamPresentFollowingEventInformationSection( 375 | ActualStreamEventInformationSection 376 | ): 377 | 378 | """自ストリームEIT[p/f]""" 379 | 380 | _table_ids = [0x4E] 381 | 382 | 383 | class OtherStreamEventInformationSection(EventInformationSection): 384 | 385 | """他ストリームEIT""" 386 | 387 | _table_ids = range(0x60, 0x70) 388 | 389 | 390 | class OtherStreamPresentFollowingEventInformationSection( 391 | OtherStreamEventInformationSection 392 | ): 393 | 394 | """他ストリームEIT[p/f]""" 395 | 396 | _table_ids = [0x4F] 397 | 398 | 399 | class RunningStatusSection(Section): 400 | 401 | """進行状態セクション RST (ARIB-STD-B10-2-5.2.10)""" 402 | 403 | _pids = [0x13] 404 | _table_ids = [0x71] 405 | 406 | table_id = uimsbf(8) 407 | section_syntax_indicator = bslbf(1) 408 | reserved_future_use = bslbf(1) 409 | reserved = bslbf(2) 410 | section_length = uimsbf(12) 411 | 412 | @loop(section_length) 413 | class statuses(Syntax): 414 | transport_stream_id = uimsbf(16) 415 | original_network_id = uimsbf(16) 416 | service_id = uimsbf(16) 417 | event_id = uimsbf(16) 418 | reserved_future_use = bslbf(5) 419 | running_status = uimsbf(3) 420 | 421 | 422 | class TimeAndDateSection(Section): 423 | 424 | """時刻日付セクション TDT (ARIB-STD-B10-2-5.2.8)""" 425 | 426 | _pids = [0x14] 427 | _table_ids = [0x70] 428 | 429 | table_id = uimsbf(8) 430 | section_syntax_indicator = bslbf(1) 431 | reserved_future_use = bslbf(1) 432 | reserved = bslbf(2) 433 | section_length = uimsbf(12) 434 | JST_time = mjd(40) 435 | 436 | 437 | class TimeOffsetSection(Section): 438 | 439 | """時刻日付オフセットセクション TOT (ARIB-STD-B10-2-5.2.9)""" 440 | 441 | _pids = [0x14] 442 | _table_ids = [0x73] 443 | 444 | table_id = uimsbf(8) 445 | section_syntax_indicator = bslbf(1) 446 | reserved_future_use = bslbf(1) 447 | reserved1 = bslbf(2) 448 | section_length = uimsbf(12) 449 | JST_time = mjd(40) 450 | reserved_2 = bslbf(4) 451 | descriptors_loop_length = uimsbf(12) 452 | descriptors = descriptors(descriptors_loop_length) 453 | CRC_32 = rpchof(32) 454 | 455 | 456 | class LocalEventInformationSection(Section): 457 | 458 | """ローカルイベント情報セクション LIT (ARIB-STD-B10-2-5.1.1)""" 459 | 460 | _pids = [0x20] 461 | _table_ids = [0xD0] 462 | 463 | table_id = uimsbf(8) 464 | section_syntax_indicator = bslbf(1) 465 | reserved_future_use = bslbf(1) 466 | reserved_1 = bslbf(2) 467 | section_length = uimsbf(12) 468 | event_id = uimsbf(16) 469 | reserved_2 = bslbf(2) 470 | version_number = uimsbf(5) 471 | current_next_indicator = bslbf(1) 472 | section_number = uimsbf(8) 473 | last_section_number = uimsbf(8) 474 | service_id = uimsbf(16) 475 | transport_stream_id = uimsbf(16) 476 | original_network_id = uimsbf(16) 477 | 478 | @loop(lambda self: self.section_length - 15) 479 | class events(Syntax): 480 | local_event_id = uimsbf(16) 481 | reserved_fugure_use = bslbf(4) 482 | descriptors_loop_length = uimsbf(12) 483 | descriptors = descriptors(descriptors_loop_length) 484 | 485 | CRC_32 = rpchof(32) 486 | 487 | 488 | class EventRelationSection(Section): 489 | 490 | """イベント関係セクション ERT (ARIB-STD-B10-2-5.1.2)""" 491 | 492 | _pids = [0x21] 493 | _table_ids = [0xD1] 494 | 495 | table_id = uimsbf(8) 496 | section_syntax_indicator = bslbf(1) 497 | reserved_future_use_1 = bslbf(1) 498 | reserved_1 = bslbf(2) 499 | section_length = uimsbf(12) 500 | event_relation_id = uimsbf(16) 501 | reserved_2 = bslbf(2) 502 | version_number = uimsbf(5) 503 | current_next_indicator = bslbf(1) 504 | section_number = uimsbf(8) 505 | last_section_number = uimsbf(8) 506 | information_provider_id = uimsbf(16) 507 | relation_type = uimsbf(4) 508 | reserved_future_use_2 = bslbf(4) 509 | 510 | @loop(lambda self: self.section_length - 12) 511 | class references(Syntax): 512 | node_id = uimsbf(16) 513 | collection_mode = uimsbf(4) 514 | reserved_future_use_1 = bslbf(4) 515 | parent_node_id = uimsbf(16) 516 | referece_number = uimsbf(8) 517 | reserved_future_use_2 = bslbf(4) 518 | descriptors_loop_length = uimsbf(12) 519 | descriptors = descriptors(descriptors_loop_length) 520 | 521 | CRC_32 = rpchof(32) 522 | 523 | 524 | class IndexTransmissionSection(Section): 525 | 526 | """番組インデックス送出情報セクション ITT (ARIB-STD-B10-2-5.1.3)""" 527 | 528 | _table_ids = [0xD2] 529 | 530 | table_id = uimsbf(8) 531 | section_syntax_indicator = bslbf(1) 532 | reserved_future_use_1 = bslbf(1) 533 | reserved_1 = bslbf(2) 534 | section_length = uimsbf(12) 535 | event_id = uimsbf(16) 536 | reserved_2 = bslbf(2) 537 | version_number = uimsbf(5) 538 | current_next_indicator = bslbf(1) 539 | section_number = uimsbf(8) 540 | last_section_number = uimsbf(8) 541 | reserved_future_use_2 = uimsbf(4) 542 | descriptors_loop_length = uimsbf(12) 543 | descriptors = descriptors(descriptors_loop_length) 544 | CRC_32 = rpchof(32) 545 | 546 | 547 | class PartialContentAnnouncementSection(Section): 548 | 549 | """差分配信告知セクション PCAT (ARIB-STD-B10-2-5.2.12)""" 550 | 551 | _pids = [0x22] 552 | _table_ids = [0xC2] 553 | 554 | table_id = uimsbf(8) 555 | section_syntax_indicator = bslbf(1) 556 | reserved_future_use_1 = bslbf(1) 557 | reserved_1 = bslbf(2) 558 | section_length = uimsbf(12) 559 | event_relation_id = uimsbf(16) 560 | reserved_2 = bslbf(2) 561 | version_number = uimsbf(5) 562 | current_next_indicator = bslbf(1) 563 | section_number = uimsbf(8) 564 | last_section_number = uimsbf(8) 565 | information_provider_id = uimsbf(16) 566 | relation_type = uimsbf(4) 567 | reserved_future_use_2 = uimsbf(4) 568 | 569 | @loop(lambda self: self.section_length - 10) 570 | class relations(Syntax): 571 | node_id = uimsbf(16) 572 | collection_mode = uimsbf(4) 573 | reserved_future_use_1 = bslbf(4) 574 | parent_node_id = uimsbf(16) 575 | reference_number = uimsbf(8) 576 | reserved_future_use_2 = bslbf(4) 577 | descriptors_loop_length = uimsbf(12) 578 | descriptors = descriptors(descriptors_loop_length) 579 | 580 | CRC_32 = rpchof(32) 581 | 582 | 583 | class StuffingSection(Section): 584 | 585 | """スタッフセクション ST (ARIB-STD-B10-2.5.2.11)""" 586 | 587 | _table_ids = [0x72] 588 | 589 | table_id = uimsbf(8) 590 | section_syntax_indicator = bslbf(1) 591 | reserved_future_use = bslbf(1) 592 | reserved = bslbf(2) 593 | section_length = uimsbf(12) 594 | 595 | @loop(section_length) 596 | class data(Syntax): 597 | data_byte = uimsbf(8) 598 | 599 | 600 | class BroadcasterInformationSection(Section): 601 | 602 | """ブロードキャスタ情報セクション BIT (ARIB-STD-B10-2-5.2.13)""" 603 | 604 | _pids = [0x24] 605 | _table_ids = [0xC4] 606 | 607 | table_id = uimsbf(8) 608 | section_syntax_indicator = bslbf(1) 609 | reserved_future_use_1 = bslbf(1) 610 | reserved_1 = bslbf(2) 611 | section_length = uimsbf(12) 612 | original_network_id = uimsbf(16) 613 | reserved_2 = bslbf(2) 614 | version_number = uimsbf(5) 615 | current_next_indicator = bslbf(1) 616 | section_number = uimsbf(8) 617 | last_section_number = uimsbf(8) 618 | reserved_future_use_2 = bslbf(3) 619 | broadcast_view_propriety = bslbf(1) 620 | first_descriptors_length = uimsbf(12) 621 | descriptors = descriptors(first_descriptors_length) 622 | 623 | @loop(lambda self: ( 624 | self.section_length - (self.first_descriptors_length + 11) 625 | )) 626 | class broadcasters(Syntax): 627 | broadcaster_id = uimsbf(8) 628 | reserved_future_use = bslbf(4) 629 | broadcaster_descriptor_length = uimsbf(12) 630 | descriptors = descriptors(broadcaster_descriptor_length) 631 | 632 | CRC_32 = rpchof(32) 633 | 634 | 635 | class NetworkBoardInformationSection(Section): 636 | 637 | """ネットワーク掲示板情報セクション NBIT (ARIB-STD-B10-2-5.14) 638 | 639 | FIXME: 未実装 640 | """ 641 | 642 | _pids = [0x25] 643 | _table_ids = [0x40, 0x41] 644 | 645 | 646 | class CommonDataSection(Section): 647 | 648 | """全受信機共通データセクション CDT (ARIB-STD-B21-12.2.2.2)""" 649 | 650 | _pids = [0x29] 651 | _table_ids = [0xC8] 652 | 653 | table_id = uimsbf(8) 654 | section_syntax_indicator = bslbf(1) 655 | reserved_future_use_1 = bslbf(1) 656 | reserved_1 = bslbf(2) 657 | section_length = uimsbf(12) 658 | download_data_id = uimsbf(16) 659 | reserved_2 = bslbf(2) 660 | version_number = uimsbf(5) 661 | current_next_indicator = bslbf(1) 662 | section_number = uimsbf(8) 663 | last_section_number = uimsbf(8) 664 | original_network_id = uimsbf(16) 665 | data_type = uimsbf(8) 666 | reserved_future_use_2 = bslbf(4) 667 | descriptors_loop_length = uimsbf(12) 668 | descriptors = descriptors(descriptors_loop_length) 669 | 670 | # 地上デジタル放送ではdata_module_byteはCDT伝送方式サービスロゴである (ARIB-TR-B14-1-5.4.1.2) 671 | logo_type = uimsbf(8) 672 | reserved_future_use_3 = bslbf(7) 673 | logo_id = uimsbf(9) 674 | reserved_future_use_4 = bslbf(4) 675 | logo_version = uimsbf(12) 676 | data_size = uimsbf(16) 677 | data_byte = raw(data_size) 678 | 679 | CRC_32 = rpchof(32) 680 | 681 | 682 | class LinkedDescriptionSection(Section): 683 | 684 | """リンク記述セクション LDT (ARIB-STD-B10-2-5.2.15)""" 685 | 686 | _pids = [0x25] 687 | _table_ids = [0xC7] 688 | 689 | table_id = uimsbf(8) 690 | section_syntax_indicator = bslbf(1) 691 | reserved_future_use_1 = bslbf(1) 692 | reserved_1 = bslbf(2) 693 | section_length = uimsbf(12) 694 | original_network_id = uimsbf(16) 695 | reserved_2 = bslbf(2) 696 | version_number = uimsbf(5) 697 | current_next_indicator = bslbf(1) 698 | section_number = uimsbf(8) 699 | last_section_number = uimsbf(8) 700 | transport_stream_id = uimsbf(16) 701 | original_network_id = uimsbf(16) 702 | 703 | @loop(lambda self: self.section_length - 13) 704 | class links(Syntax): 705 | description_id = uimsbf(16) 706 | reserved_future_use = bslbf(12) 707 | descriptors_loop_length = uimsbf(12) 708 | descriptors = descriptors(descriptors_loop_length) 709 | 710 | CRC_32 = rpchof(32) 711 | 712 | 713 | class EntitlementControlMessage(Section): 714 | 715 | """ARIB-STD-B1, B21 716 | 717 | FIXME: 未実装 718 | """ 719 | 720 | _table_ids = [0x82, 0x83] 721 | 722 | 723 | class SoftwareDownloadTriggerSection(Section): 724 | 725 | """ソフトウェアダウンロードトリガーセクション SDTT (ARIB-STD-B21-12.2.1.1)""" 726 | 727 | _pids = [0x23, 0x28] 728 | _table_ids = [0xC3] 729 | 730 | table_id = uimsbf(8) 731 | section_syntax_indicator = bslbf(1) 732 | reserved_future_use = bslbf(1) 733 | reserved_1 = bslbf(2) 734 | section_length = uimsbf(12) 735 | maker_id = uimsbf(8) 736 | model_id = uimsbf(8) 737 | reserved_2 = bslbf(2) 738 | version_number = uimsbf(5) 739 | current_next_indicator = uimsbf(1) 740 | section_number = uimsbf(8) 741 | last_section_number = uimsbf(8) 742 | transport_stream_id = uimsbf(16) 743 | original_networl_id = uimsbf(16) 744 | service_id = uimsbf(16) 745 | num_of_contents = uimsbf(8) 746 | 747 | @times(num_of_contents) 748 | class groups(Syntax): 749 | group = bslbf(4) 750 | target_version = uimsbf(12) 751 | new_version = uimsbf(12) 752 | download_level = bslbf(2) 753 | version_indicator = bslbf(2) 754 | content_description_length = uimsbf(12) 755 | reserved = bslbf(4) 756 | schedule_description_length = uimsbf(12) 757 | schedule_timeshift_information = uimsbf(4) 758 | 759 | @loop(schedule_description_length) 760 | class schedules(Syntax): 761 | start_time = mjd(40) 762 | duration = bcdtime(24) 763 | 764 | descriptors = descriptors(lambda self: ( 765 | self.content_description_length - self.schedule_description_length 766 | )) 767 | 768 | CRC_32 = rpchof(32) 769 | 770 | 771 | class DSMCCSection(Section): 772 | 773 | """ARIB-STD-B24-3-6.5 774 | 775 | FIXME: 未実装 776 | """ 777 | 778 | _table_ids = [0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F] 779 | -------------------------------------------------------------------------------- /ariblib/service.py: -------------------------------------------------------------------------------- 1 | """サービスラッパー""" 2 | 3 | from ariblib.descriptors import ( 4 | LogoTransmissionDescriptor, 5 | ServiceDescriptor, 6 | ) 7 | from ariblib.sections import ( 8 | ActualStreamServiceDescriptionSection, 9 | OtherStreamServiceDescriptionSection, 10 | ServiceDescriptionSection, 11 | ) 12 | 13 | 14 | def services(ts, channel_id=None, single=False, stream=None): 15 | """トランスポートストリームから Service オブジェクトを返すジェネレータ""" 16 | 17 | if channel_id is None: 18 | get_channel_id = lambda sdt: tsid2channel(sdt.transport_stream_id) 19 | else: 20 | get_channel_id = lambda sdt: str(channel_id) 21 | 22 | if stream == 'actual': 23 | SDT = ActualStreamServiceDescriptionSection 24 | elif stream == 'other': 25 | SDT = OtherStreamServiceDescriptionSection 26 | else: 27 | SDT = ServiceDescriptionSection 28 | 29 | if single: 30 | sdt = next(ts.sections(SDT)) 31 | for service in sdt.services: 32 | yield Service(service, get_channel_id(sdt)) 33 | else: 34 | for sdt in ts.sections(SDT): 35 | for service in sdt.services: 36 | yield Service(service, get_channel_id(sdt)) 37 | 38 | 39 | def parse_tsid(tsid): 40 | # NHK-BS対応 41 | if 16625 <= tsid <= 16626: 42 | tsid -= 1 43 | network_lower_4bit = (tsid & 0xF000) >> 12 44 | new = (tsid & 0x0E000) >> 9 45 | repeater = (tsid & 0x01F0) >> 4 46 | slot = tsid & 0x0007 47 | 48 | return (network_lower_4bit, new, repeater, slot) 49 | 50 | 51 | def tsid2channel(tsid): 52 | """transport_stream_id を recpt1 が認識する channel 形式に変える""" 53 | lower, new, repeater, slot = parse_tsid(tsid) 54 | # ほんとはSystemManagementDescriptorのbroadcasting_identifierで 55 | # BS/CSの判別をすべきだと思う 56 | if repeater % 2 == 0: 57 | return "CS{:02d}".format(repeater) 58 | else: 59 | return "BS{:02d}_{}".format(repeater, slot) 60 | 61 | 62 | class Service(object): 63 | 64 | """サービスラッパークラス""" 65 | 66 | def __init__(self, service, channel_id): 67 | self.channel_id = channel_id 68 | if 'BS' in channel_id: 69 | self.broadcasting_type = 'BS' 70 | self.channel_number =\ 71 | int(channel_id.split('_')[0].replace('BS', '')) 72 | elif 'CS' in channel_id: 73 | self.broadcasting_type = 'CS' 74 | self.channel_number = int(channel_id.replace('CS', '')) 75 | else: 76 | self.broadcasting_type = 'GR' 77 | self.channel_number = int(channel_id) 78 | self.service_id = service.service_id 79 | self.eit_flags = service.EIT_user_defined_flags 80 | self.eit_schedule = service.EIT_schedule_flag 81 | self.pseit = service.EIT_present_following_flag 82 | descs = service.descriptors 83 | sd = descs[ServiceDescriptor][0] 84 | self.service_type = sd.service_type 85 | self.provider = sd.service_provider_name 86 | self.name = sd.service_name 87 | self.free_CA_mode = service.free_CA_mode 88 | self.logo = '' 89 | for ltd in descs[LogoTransmissionDescriptor]: 90 | if ltd.logo_transmission_type == 3: 91 | self.logo = ltd.logo_char 92 | break 93 | -------------------------------------------------------------------------------- /ariblib/syntax.py: -------------------------------------------------------------------------------- 1 | """TSシンタックスの実装""" 2 | 3 | from ariblib.mnemonics import case_table, mnemonic 4 | 5 | 6 | class SyntaxDict(dict): 7 | 8 | """シンタックスを記述する辞書クラス 9 | 10 | 責務: 11 | 1: 宣言された順番に記述子を格納するリストを提供する 12 | 2: ビット列表記クラスに開始位置と変数名を与える 13 | 3: ifセクション解決用のリストを提供する 14 | """ 15 | 16 | def __init__(self): 17 | self.mnemonics = [] 18 | self.conditions = [] 19 | 20 | def __setitem__(self, key, value): 21 | if isinstance(value, case_table): 22 | self.conditions.append(value) 23 | 24 | if isinstance(value, mnemonic): 25 | value.name = key 26 | value.start = self.get_start() 27 | self.mnemonics.append(value) 28 | 29 | dict.__setitem__(self, key, value) 30 | 31 | def get_start(self): 32 | mnemonics = self.mnemonics[:] 33 | 34 | def start(instance): 35 | return sum(m.real_length(instance) for m in mnemonics) +\ 36 | instance._pos 37 | 38 | return start 39 | 40 | 41 | class SyntaxType(type): 42 | 43 | """シンタックスのメタクラス 44 | 45 | SyntaxDict が生成したサブ情報を各インスタンスに付与する 46 | see: PEP3115 47 | """ 48 | 49 | @classmethod 50 | def __prepare__(cls, name, bases): 51 | return SyntaxDict() 52 | 53 | def __new__(cls, name, args, classdict): 54 | instance = type.__new__(cls, name, args, classdict) 55 | instance._mnemonics = classdict.mnemonics 56 | instance._conditions = classdict.conditions 57 | return instance 58 | 59 | 60 | class Syntax(metaclass=SyntaxType): 61 | 62 | """シンタックスの親クラス""" 63 | 64 | def __init__(self, packet, pos=0, parent=None): 65 | self._packet = packet 66 | self._pos = pos 67 | self._parent = parent 68 | self._callbacks = dict() 69 | 70 | def __len__(self): 71 | """このシンタックスが持っているビット列表記の長さを全て数え上げ、 72 | シンタックスの長さをバイト数として返す""" 73 | 74 | return sum(mnemonic.real_length(self) for mnemonic in self._mnemonics) 75 | 76 | def __getattr__(self, name): 77 | """指定されたプロパティが直下から見つからない場合に、 78 | 条件に合うifシンタックスを順番に探していく""" 79 | 80 | for mnemonic in self._conditions: 81 | try: 82 | if mnemonic.condition(self): 83 | sub = getattr(self, mnemonic.name) 84 | return getattr(sub, name) 85 | except AttributeError: 86 | pass 87 | 88 | # 親が与えられている場合は親のプロパティも参照する 89 | if ( 90 | self._parent and 91 | any(mnemonic.name == name for mnemonic in self._parent._mnemonics) 92 | ): 93 | return getattr(self._parent, name) 94 | 95 | def get_names(self): 96 | """このシンタックスインスタンスでアクセスできる 97 | ビット列プロパティの一覧を返す""" 98 | 99 | result = [] 100 | for mnemonic in self._mnemonics: 101 | name = mnemonic.name 102 | if isinstance(mnemonic, case_table): 103 | if mnemonic.condition(self): 104 | result.extend(mnemonic.cls(self).get_names()) 105 | else: 106 | result.append(name) 107 | return result 108 | 109 | def dump(self, indent=0): 110 | from ariblib.aribstr import AribString 111 | from ariblib.sections import Section 112 | from ariblib.descriptors import Descriptor 113 | from types import GeneratorType 114 | from collections import defaultdict 115 | print('{}{}'.format(' ' * indent, '-' * (80 - indent))) 116 | if isinstance(self, Section) or isinstance(self, Descriptor): 117 | print('{}<<{}>>'.format(' ' * indent, self.__class__.__name__)) 118 | for name in self.get_names(): 119 | value = getattr(self, name) 120 | if isinstance(value, Syntax): 121 | value.dump(indent + 2) 122 | elif isinstance(value, defaultdict): 123 | for value in value.values(): 124 | for child in value: 125 | child.dump(indent + 2) 126 | elif isinstance(value, list): 127 | for child in value: 128 | child.dump(indent + 2) 129 | elif type(value) is GeneratorType: 130 | for child in value: 131 | child.dump(indent + 2) 132 | elif isinstance(value, bytearray): 133 | print("{}{}\t{}".format(' ' * indent, name, AribString(value))) 134 | else: 135 | print("{}{}\t{}".format(' ' * indent, name, value)) 136 | 137 | def on(self, Descriptor, descriptor_name='descriptors'): 138 | """記述子ごとにコールバック関数を設定する 139 | いまのところ、一つの記述子についてコールバック関数は1つのみ定義できる。 140 | """ 141 | 142 | self.descriptor_name = descriptor_name 143 | 144 | def attach_callback(callback): 145 | self._callbacks[Descriptor] = callback 146 | return attach_callback 147 | 148 | def execute(self): 149 | """指定された記述子がyieldされるごとにコールバック関数を実行する""" 150 | from ariblib.descriptors import ExtendedEventDescriptor 151 | 152 | for descriptor_type, descriptors in\ 153 | getattr(self, self.descriptor_name).items(): 154 | if descriptor_type not in self._callbacks: 155 | continue 156 | 157 | callback = self._callbacks[descriptor_type] 158 | if descriptor_type == ExtendedEventDescriptor: 159 | # 拡張形式イベント記述子の場合は別途対応が必要。TODO 160 | callback(descriptors) 161 | else: 162 | for descriptor in descriptors: 163 | callback(descriptor) 164 | -------------------------------------------------------------------------------- /ariblib/tables.py: -------------------------------------------------------------------------------- 1 | """セクションとテーブルの違いがわからない頃の実装の後方互換""" 2 | 3 | from ariblib.sections import ( 4 | Section, 5 | ProgramAssociationSection, 6 | ProgramMapSection, 7 | ConditionalAccessSection, 8 | NetworkInformationSection, 9 | ServiceDescriptionSection, 10 | BouquetAssociationSection, 11 | EventInformationSection, 12 | RunningStatusSection, 13 | TimeAndDateSection, 14 | TimeOffsetSection, 15 | LocalEventInformationSection, 16 | EventRelationSection, 17 | IndexTransmissionSection, 18 | PartialContentAnnouncementSection, 19 | StuffingSection, 20 | BroadcasterInformationSection, 21 | NetworkBoardInformationSection, 22 | CommonDataSection, 23 | LinkedDescriptionSection, 24 | SoftwareDownloadTriggerSection, 25 | DSMCCSection, 26 | ) 27 | 28 | Table = Section 29 | ProgramAssociationTable = ProgramAssociationSection 30 | ProgramMapTable = ProgramMapSection 31 | ConditionalAccessTable = ConditionalAccessSection 32 | NetworkInformationTable = NetworkInformationSection 33 | ServiceDescriptionTable = ServiceDescriptionSection 34 | BouquetAssociationTable = BouquetAssociationSection 35 | EventInformationTable = EventInformationSection 36 | RunningStatusTable = RunningStatusSection 37 | TimeAndDateTable = TimeAndDateSection 38 | TimeOffsetTable = TimeOffsetSection 39 | LocalEventInformationTable = LocalEventInformationSection 40 | EventRelationTable = EventRelationSection 41 | IndexTransmissionTable = IndexTransmissionSection 42 | PartialContentAnnouncementTable = PartialContentAnnouncementSection 43 | StuffingTable = StuffingSection 44 | BroadcasterInformationTable = BroadcasterInformationSection 45 | NetworkBoardInformationTable = NetworkBoardInformationSection 46 | CommonDataTable = CommonDataSection 47 | LinkedDescriptionTable = LinkedDescriptionSection 48 | SoftwareDownloadTriggerTable = SoftwareDownloadTriggerSection 49 | DSMCCTable = DSMCCSection 50 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [aliases] 2 | develop = register sdist bdist_egg upload 3 | 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.2 2 | 3 | from setuptools import setup, find_packages 4 | from ariblib import __version__ 5 | 6 | setup(name='ariblib', 7 | version=__version__, 8 | author='Chisa Youzaka', 9 | author_email='ariblib@txqz.net', 10 | url='http://github.com/youzaka/ariblib', 11 | classifiers=[ 12 | 'Programming Language :: Python :: 3', 13 | 'License :: OSI Approved :: MIT License', 14 | 'Intended Audience :: Developers', 15 | 'Development Status :: 3 - Alpha', 16 | 'Natural Language :: Japanese' 17 | ], 18 | license='MIT', 19 | description='python implementation of arib-std-b10 and arib-std-b24', 20 | packages=find_packages(), 21 | package_data={'ariblib': ['drcs.tsv']}, 22 | ) 23 | 24 | -------------------------------------------------------------------------------- /util/sidump: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.2 2 | # coding: utf-8 3 | 4 | import sys 5 | from ariblib import tsopen 6 | from ariblib.sections import * 7 | 8 | if len(sys.argv) > 1: 9 | source = sys.argv[1] 10 | else: 11 | source = sys.stdin.fileno() 12 | 13 | with tsopen(source) as ts: 14 | pat = next(ts.sections(ProgramAssociationSection)) 15 | pat.dump() 16 | ProgramMapSection._pids = list(pat.pmt_pids) 17 | pmt = next(ts.sections(ProgramMapSection)) 18 | pmt.dump() 19 | nit = next(ts.sections(NetworkInformationSection)) 20 | nit.dump() 21 | cat = next(ts.sections(ConditionalAccessSection)) 22 | cat.dump() 23 | -------------------------------------------------------------------------------- /util/tsdump: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.2 2 | # coding: utf-8 3 | 4 | import sys 5 | from ariblib import tsopen 6 | from ariblib.sections import * 7 | 8 | if len(sys.argv) > 1: 9 | source = sys.argv[1] 10 | else: 11 | source = sys.stdin.fileno() 12 | 13 | with tsopen(source) as ts: 14 | for section in ts.sections( 15 | #BroadcasterInformationSection, 16 | #EventInformationSection, 17 | ServiceDescriptionSection, 18 | #NetworkInformationSection, 19 | #LinkedDescriptionSection, 20 | #SoftwareDownloadTriggerSection 21 | ): 22 | section.dump() 23 | 24 | --------------------------------------------------------------------------------