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