6 from .fragment
import FragmentFD
7 from ..compat
import compat_urllib_error
9 u8
= struct
.Struct('>B')
10 u88
= struct
.Struct('>Bx')
11 u16
= struct
.Struct('>H')
12 u1616
= struct
.Struct('>Hxx')
13 u32
= struct
.Struct('>I')
14 u64
= struct
.Struct('>Q')
16 s88
= struct
.Struct('>bx')
17 s16
= struct
.Struct('>h')
18 s1616
= struct
.Struct('>hxx')
19 s32
= struct
.Struct('>i')
21 unity_matrix
= (s32
.pack(0x10000) + s32
.pack(0) * 3) * 2 + s32
.pack(0x40000000)
25 TRACK_IN_PREVIEW
= 0x4
30 def box(box_type
, payload
):
31 return u32
.pack(8 + len(payload
)) + box_type
+ payload
34 def full_box(box_type
, version
, flags
, payload
):
35 return box(box_type
, u8
.pack(version
) + u32
.pack(flags
)[1:] + payload
)
38 def write_piff_header(stream
, params
):
39 track_id
= params
['track_id']
40 fourcc
= params
['fourcc']
41 duration
= params
['duration']
42 timescale
= params
.get('timescale', 10000000)
43 language
= params
.get('language', 'und')
44 height
= params
.get('height', 0)
45 width
= params
.get('width', 0)
46 stream_type
= params
['stream_type']
47 creation_time
= modification_time
= int(time
.time())
49 ftyp_payload
= b
'isml' # major brand
50 ftyp_payload
+= u32
.pack(1) # minor version
51 ftyp_payload
+= b
'piff' + b
'iso2' # compatible brands
52 stream
.write(box(b
'ftyp', ftyp_payload
)) # File Type Box
54 mvhd_payload
= u64
.pack(creation_time
)
55 mvhd_payload
+= u64
.pack(modification_time
)
56 mvhd_payload
+= u32
.pack(timescale
)
57 mvhd_payload
+= u64
.pack(duration
)
58 mvhd_payload
+= s1616
.pack(1) # rate
59 mvhd_payload
+= s88
.pack(1) # volume
60 mvhd_payload
+= u16
.pack(0) # reserved
61 mvhd_payload
+= u32
.pack(0) * 2 # reserved
62 mvhd_payload
+= unity_matrix
63 mvhd_payload
+= u32
.pack(0) * 6 # pre defined
64 mvhd_payload
+= u32
.pack(0xffffffff) # next track id
65 moov_payload
= full_box(b
'mvhd', 1, 0, mvhd_payload
) # Movie Header Box
67 tkhd_payload
= u64
.pack(creation_time
)
68 tkhd_payload
+= u64
.pack(modification_time
)
69 tkhd_payload
+= u32
.pack(track_id
) # track id
70 tkhd_payload
+= u32
.pack(0) # reserved
71 tkhd_payload
+= u64
.pack(duration
)
72 tkhd_payload
+= u32
.pack(0) * 2 # reserved
73 tkhd_payload
+= s16
.pack(0) # layer
74 tkhd_payload
+= s16
.pack(0) # alternate group
75 tkhd_payload
+= s88
.pack(1 if stream_type
== 'audio' else 0) # volume
76 tkhd_payload
+= u16
.pack(0) # reserved
77 tkhd_payload
+= unity_matrix
78 tkhd_payload
+= u1616
.pack(width
)
79 tkhd_payload
+= u1616
.pack(height
)
80 trak_payload
= full_box(b
'tkhd', 1, TRACK_ENABLED | TRACK_IN_MOVIE | TRACK_IN_PREVIEW
, tkhd_payload
) # Track Header Box
82 mdhd_payload
= u64
.pack(creation_time
)
83 mdhd_payload
+= u64
.pack(modification_time
)
84 mdhd_payload
+= u32
.pack(timescale
)
85 mdhd_payload
+= u64
.pack(duration
)
86 mdhd_payload
+= u16
.pack(((ord(language
[0]) - 0x60) << 10) |
((ord(language
[1]) - 0x60) << 5) |
(ord(language
[2]) - 0x60))
87 mdhd_payload
+= u16
.pack(0) # pre defined
88 mdia_payload
= full_box(b
'mdhd', 1, 0, mdhd_payload
) # Media Header Box
90 hdlr_payload
= u32
.pack(0) # pre defined
91 if stream_type
== 'audio': # handler type
92 hdlr_payload
+= b
'soun'
93 hdlr_payload
+= u32
.pack(0) * 3 # reserved
94 hdlr_payload
+= b
'SoundHandler\0' # name
95 elif stream_type
== 'video':
96 hdlr_payload
+= b
'vide'
97 hdlr_payload
+= u32
.pack(0) * 3 # reserved
98 hdlr_payload
+= b
'VideoHandler\0' # name
99 elif stream_type
== 'text':
100 hdlr_payload
+= b
'subt'
101 hdlr_payload
+= u32
.pack(0) * 3 # reserved
102 hdlr_payload
+= b
'SubtitleHandler\0' # name
105 mdia_payload
+= full_box(b
'hdlr', 0, 0, hdlr_payload
) # Handler Reference Box
107 if stream_type
== 'audio':
108 smhd_payload
= s88
.pack(0) # balance
109 smhd_payload
+= u16
.pack(0) # reserved
110 media_header_box
= full_box(b
'smhd', 0, 0, smhd_payload
) # Sound Media Header
111 elif stream_type
== 'video':
112 vmhd_payload
= u16
.pack(0) # graphics mode
113 vmhd_payload
+= u16
.pack(0) * 3 # opcolor
114 media_header_box
= full_box(b
'vmhd', 0, 1, vmhd_payload
) # Video Media Header
115 elif stream_type
== 'text':
116 media_header_box
= full_box(b
'sthd', 0, 0, b
'') # Subtitle Media Header
119 minf_payload
= media_header_box
121 dref_payload
= u32
.pack(1) # entry count
122 dref_payload
+= full_box(b
'url ', 0, SELF_CONTAINED
, b
'') # Data Entry URL Box
123 dinf_payload
= full_box(b
'dref', 0, 0, dref_payload
) # Data Reference Box
124 minf_payload
+= box(b
'dinf', dinf_payload
) # Data Information Box
126 stsd_payload
= u32
.pack(1) # entry count
128 sample_entry_payload
= u8
.pack(0) * 6 # reserved
129 sample_entry_payload
+= u16
.pack(1) # data reference index
130 if stream_type
== 'audio':
131 sample_entry_payload
+= u32
.pack(0) * 2 # reserved
132 sample_entry_payload
+= u16
.pack(params
.get('channels', 2))
133 sample_entry_payload
+= u16
.pack(params
.get('bits_per_sample', 16))
134 sample_entry_payload
+= u16
.pack(0) # pre defined
135 sample_entry_payload
+= u16
.pack(0) # reserved
136 sample_entry_payload
+= u1616
.pack(params
['sampling_rate'])
139 sample_entry_box
= box(b
'mp4a', sample_entry_payload
)
140 elif stream_type
== 'video':
141 sample_entry_payload
+= u16
.pack(0) # pre defined
142 sample_entry_payload
+= u16
.pack(0) # reserved
143 sample_entry_payload
+= u32
.pack(0) * 3 # pre defined
144 sample_entry_payload
+= u16
.pack(width
)
145 sample_entry_payload
+= u16
.pack(height
)
146 sample_entry_payload
+= u1616
.pack(0x48) # horiz resolution 72 dpi
147 sample_entry_payload
+= u1616
.pack(0x48) # vert resolution 72 dpi
148 sample_entry_payload
+= u32
.pack(0) # reserved
149 sample_entry_payload
+= u16
.pack(1) # frame count
150 sample_entry_payload
+= u8
.pack(0) * 32 # compressor name
151 sample_entry_payload
+= u16
.pack(0x18) # depth
152 sample_entry_payload
+= s16
.pack(-1) # pre defined
154 codec_private_data
= binascii
.unhexlify(params
['codec_private_data'].encode())
155 if fourcc
in ('H264', 'AVC1'):
156 sps
, pps
= codec_private_data
.split(u32
.pack(1))[1:]
157 avcc_payload
= u8
.pack(1) # configuration version
158 avcc_payload
+= sps
[1:4] # avc profile indication + profile compatibility + avc level indication
159 avcc_payload
+= u8
.pack(0xfc |
(params
.get('nal_unit_length_field', 4) - 1)) # complete representation (1) + reserved (11111) + length size minus one
160 avcc_payload
+= u8
.pack(1) # reserved (0) + number of sps (0000001)
161 avcc_payload
+= u16
.pack(len(sps
))
163 avcc_payload
+= u8
.pack(1) # number of pps
164 avcc_payload
+= u16
.pack(len(pps
))
166 sample_entry_payload
+= box(b
'avcC', avcc_payload
) # AVC Decoder Configuration Record
167 sample_entry_box
= box(b
'avc1', sample_entry_payload
) # AVC Simple Entry
170 elif stream_type
== 'text':
172 sample_entry_payload
+= b
'http://www.w3.org/ns/ttml\0' # namespace
173 sample_entry_payload
+= b
'\0' # schema location
174 sample_entry_payload
+= b
'\0' # auxilary mime types(??)
175 sample_entry_box
= box(b
'stpp', sample_entry_payload
)
180 stsd_payload
+= sample_entry_box
182 stbl_payload
= full_box(b
'stsd', 0, 0, stsd_payload
) # Sample Description Box
184 stts_payload
= u32
.pack(0) # entry count
185 stbl_payload
+= full_box(b
'stts', 0, 0, stts_payload
) # Decoding Time to Sample Box
187 stsc_payload
= u32
.pack(0) # entry count
188 stbl_payload
+= full_box(b
'stsc', 0, 0, stsc_payload
) # Sample To Chunk Box
190 stco_payload
= u32
.pack(0) # entry count
191 stbl_payload
+= full_box(b
'stco', 0, 0, stco_payload
) # Chunk Offset Box
193 minf_payload
+= box(b
'stbl', stbl_payload
) # Sample Table Box
195 mdia_payload
+= box(b
'minf', minf_payload
) # Media Information Box
197 trak_payload
+= box(b
'mdia', mdia_payload
) # Media Box
199 moov_payload
+= box(b
'trak', trak_payload
) # Track Box
201 mehd_payload
= u64
.pack(duration
)
202 mvex_payload
= full_box(b
'mehd', 1, 0, mehd_payload
) # Movie Extends Header Box
204 trex_payload
= u32
.pack(track_id
) # track id
205 trex_payload
+= u32
.pack(1) # default sample description index
206 trex_payload
+= u32
.pack(0) # default sample duration
207 trex_payload
+= u32
.pack(0) # default sample size
208 trex_payload
+= u32
.pack(0) # default sample flags
209 mvex_payload
+= full_box(b
'trex', 0, 0, trex_payload
) # Track Extends Box
211 moov_payload
+= box(b
'mvex', mvex_payload
) # Movie Extends Box
212 stream
.write(box(b
'moov', moov_payload
)) # Movie Box
215 def extract_box_data(data
, box_sequence
):
216 data_reader
= io
.BytesIO(data
)
218 box_size
= u32
.unpack(data_reader
.read(4))[0]
219 box_type
= data_reader
.read(4)
220 if box_type
== box_sequence
[0]:
221 box_data
= data_reader
.read(box_size
- 8)
222 if len(box_sequence
) == 1:
224 return extract_box_data(box_data
, box_sequence
[1:])
225 data_reader
.seek(box_size
- 8, 1)
228 class IsmFD(FragmentFD
):
230 Download segments in a ISM manifest
233 def real_download(self
, filename
, info_dict
):
234 segments
= info_dict
['fragments'][:1] if self
.params
.get(
235 'test', False) else info_dict
['fragments']
238 'filename': filename
,
239 'total_frags': len(segments
),
242 self
._prepare
_and
_start
_frag
_download
(ctx
, info_dict
)
244 extra_state
= ctx
.setdefault('extra_state', {
245 'ism_track_written': False,
248 fragment_retries
= self
.params
.get('fragment_retries', 0)
249 skip_unavailable_fragments
= self
.params
.get('skip_unavailable_fragments', True)
252 for i
, segment
in enumerate(segments
):
254 if frag_index
<= ctx
['fragment_index']:
257 while count
<= fragment_retries
:
259 success
= self
._download
_fragment
(ctx
, segment
['url'], info_dict
)
262 frag_content
= self
._read
_fragment
(ctx
)
264 if not extra_state
['ism_track_written']:
265 tfhd_data
= extract_box_data(frag_content
, [b
'moof', b
'traf', b
'tfhd'])
266 info_dict
['_download_params']['track_id'] = u32
.unpack(tfhd_data
[4:8])[0]
267 write_piff_header(ctx
['dest_stream'], info_dict
['_download_params'])
268 extra_state
['ism_track_written'] = True
269 self
._append
_fragment
(ctx
, frag_content
)
271 except compat_urllib_error
.HTTPError
as err
:
273 if count
<= fragment_retries
:
274 self
.report_retry_fragment(err
, frag_index
, count
, fragment_retries
)
275 if count
> fragment_retries
:
276 if skip_unavailable_fragments
:
277 self
.report_skip_fragment(frag_index
)
279 self
.report_error('giving up after %s fragment retries' % fragment_retries
)
282 self
._finish
_frag
_download
(ctx
, info_dict
)